Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions cuda-oxide/crates/fuzzer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,3 +226,32 @@ results:
seed 1437: PASS [run] CPU/GPU traces matched
summary: PASS=1, UNSUPPORTED=1
```

## Miri Tree Borrows regression sweep

Run generated Rustlantis programs through Miri without requiring CUDA:

```bash
python3 cuda-oxide/crates/fuzzer/tools/miri_sweep.py --start 358 --count 1
python3 cuda-oxide/crates/fuzzer/tools/miri_sweep.py --count 500
```

Before generating programs, the sweep runs two deterministic Miri
controls: a valid reference-provenance example that must succeed and
an invalid protected-reborrow example that must produce undefined behavior.

The sweep also executes seed 5 with `static_count = 0`.
This is a regression for a pre-existing local-reference provenance bug:
the original generator produced an invalid reborrow rejected by Miri
Tree Borrows. Checking local-reference read permissions prevents the
invalid reference from being selected. An isolated ablation reproduced
both the original generated program and its Miri error.

The sweep uses `nightly-2026-10-11`, enables `-Zmiri-tree-borrows`,
and generates programs with `static_count = 8`.

Failures preserve the generated Rust source and Miri diagnostics under
`/tmp/rustlantis-miri-regressions/`.

The reported reference-call coverage is a structural approximation;
it does not establish that every reference argument originates from a static.
1 change: 1 addition & 0 deletions cuda-oxide/crates/fuzzer/rustlantis/config.toml.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ max_bb_count_hard = 100
max_fn_count = 20
max_args_count = 12
var_dump_chance = 0.5
static_count = 0
tuple_max_len = 4
array_max_len = 8
struct_max_fields = 8
Expand Down
16 changes: 16 additions & 0 deletions cuda-oxide/crates/fuzzer/rustlantis/config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ pub struct GenerationConfig {
/// Expected proportion of variables to be dumped
#[serde(default = "var_dump_chance")]
pub var_dump_chance: f32,

/// Number of scalar static allocations to make available to the generator.
///
/// The default is zero so enabling static generation does not perturb existing seeds.
#[serde(default = "static_count")]
pub static_count: usize,
}

fn bb_max_len() -> usize {
Expand Down Expand Up @@ -111,6 +117,10 @@ fn var_dump_chance() -> f32 {
0.5
}

fn static_count() -> usize {
0
}

#[derive(Deserialize, Clone)]
pub struct TyConfig {
/// Max. arity of tuple
Expand Down Expand Up @@ -178,3 +188,9 @@ impl Default for TyConfig {
fn example_parses() {
let _ = load("../config.toml.example");
}

#[test]
fn static_count_defaults_to_zero() {
let config: Config = toml::from_str("[backends]\n").unwrap();
assert_eq!(config.generation.static_count, 0);
}
184 changes: 178 additions & 6 deletions cuda-oxide/crates/fuzzer/rustlantis/generate/src/generation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use mir::serialize::Serialize;
use mir::syntax::{
AggregateKind, BasicBlock, BasicBlockData, BinOp, Body, Callee, Function, IntTy, Literal,
Local, LocalDecls, Mutability, Operand, Place, Program, ProjectionElem, Rvalue, Statement,
SwitchTargets, Terminator, TyId, TyKind, UnOp, VariantIdx,
Static, StaticDecl, SwitchTargets, Terminator, TyId, TyKind, UnOp, VariantIdx,
};
use mir::tyctxt::TyCtxt;
use rand::{Rng, RngCore, SeedableRng, seq::IteratorRandom};
Expand Down Expand Up @@ -111,6 +111,31 @@ impl GenerationCtx {
}
})
}

fn choose_static_operand(&self, ty: TyId) -> Result<Operand> {
let candidates = self
.program
.statics
.iter_enumerated()
.filter(|(id, _)| self.pt.can_acquire_static(*id))
.filter_map(|(id, decl)| match ty.kind(&self.tcx) {
TyKind::Ref(pointee, Mutability::Not)
if decl.mutability == Mutability::Not && decl.ty == *pointee =>
{
Some(Operand::Static(id, ty))
}
TyKind::RawPtr(pointee, Mutability::Mut)
if decl.mutability == Mutability::Mut && decl.ty == *pointee =>
{
Some(Operand::StaticMut(id, ty))
}
_ => None,
});

candidates
.choose(&mut *self.rng.borrow_mut())
.ok_or(SelectionError::Exhausted)
}
}

// Rvalue
Expand All @@ -126,7 +151,18 @@ impl GenerationCtx {
lhs.serialize_value(&self.tcx),
lhs.ty(self.current_decls(), &self.tcx).serialize(&self.tcx)
);
let operand = self.choose_operand(&[lhs.ty(self.current_decls(), &self.tcx)], lhs)?;
let ty = lhs.ty(self.current_decls(), &self.tcx);
let operand = if self.program.statics.is_empty() {
self.choose_operand(&[ty], lhs)?
} else {
self.make_choice([false, true].into_iter(), |use_static| {
if use_static {
self.choose_static_operand(ty)
} else {
self.choose_operand(&[ty], lhs)
}
})?
};
Ok(Rvalue::Use(operand))
}

Expand Down Expand Up @@ -348,6 +384,9 @@ impl GenerationCtx {
let mut selector = PlaceSelector::for_pointee(self.tcx.clone(), false)
.of_ty(*source_ty)
.except(lhs);
if !self.program.statics.is_empty() && *mutability == Mutability::Mut {
selector = selector.requiring_pointee_write();
}
if let Some(_) = self.pt.pointee(lhs.to_place_index(&self.pt).unwrap()) {
// The MIR linter doesn't like it if we do x = &(*x)
selector = selector.except(
Expand Down Expand Up @@ -886,12 +925,75 @@ impl GenerationCtx {
self.enter_bb(bb);
}

fn insert_static_mut_workload(&mut self) {
if self.program.statics.is_empty() || self.cursor.function.index() != 0 {
return;
}

let mutable_statics: Vec<(Static, TyId)> = self
.program
.statics
.iter_enumerated()
.filter_map(|(id, decl)| {
(decl.mutability == Mutability::Mut && self.pt.can_acquire_static(id))
.then_some((id, decl.ty))
})
.collect();
let Some(&(static_id, pointee_ty)) =
mutable_statics.iter().choose(&mut *self.rng.borrow_mut())
else {
return;
};

let pointer_ty = self
.tcx
.iter_enumerated()
.find_map(|(ty, kind)| match kind {
TyKind::RawPtr(pointee, Mutability::Mut) if *pointee == pointee_ty => Some(ty),
_ => None,
})
.expect("mutable static pointee has a matching raw pointer type");

let pointer_local = self.declare_new_var(Mutability::Mut, pointer_ty);
let before_local = self.declare_new_var(Mutability::Mut, pointee_ty);
let after_local = self.declare_new_var(Mutability::Mut, pointee_ty);

let pointer = Place::from_local(pointer_local);
let mut pointee = pointer.clone();
pointee
.project(ProjectionElem::Deref, self.current_decls(), &self.tcx)
.expect("mutable static pointer can be dereferenced");
let before = Place::from_local(before_local);
let after = Place::from_local(after_local);
let write_value = self
.rng
.borrow_mut()
.gen_literal(pointee_ty, &self.tcx)
.expect("static pointee type is literalble");

let statements = [
Statement::Assign(
pointer.clone(),
Rvalue::Use(Operand::StaticMut(static_id, pointer_ty)),
),
Statement::Assign(before, Rvalue::Use(Operand::Copy(pointee.clone()))),
Statement::Assign(pointee.clone(), Rvalue::Use(Operand::Constant(write_value))),
Statement::Assign(after, Rvalue::Use(Operand::Copy(pointee))),
];

for statement in statements {
self.post_generation(&statement);
self.current_bb_mut().insert_statement(statement);
}
}

// Generate a Return terminator, returns false if it's being
// generated in fn0
fn add_return(&mut self) -> bool {
trace!("generating a Return terminator to {:?}", self.cursor);
debug_assert!(self.pt.can_return());

self.insert_static_mut_workload();
self.insert_dump_var_gadget();

self.current_bb_mut().set_terminator(Terminator::Return);
Expand Down Expand Up @@ -1168,17 +1270,59 @@ impl GenerationCtx {

pub fn new(config: Config, seed: u64, debug_dump: bool) -> Self {
let rng = RefCell::new(Box::new(rand::rngs::SmallRng::seed_from_u64(seed)));
let static_count = config.generation.static_count;
let mut tcx = TyCtxt::from_primitives(config.ty);
seed_tys(&mut tcx, &mut *rng.borrow_mut());
let tcx = Rc::new(tcx);
let ty_weights = TySelect::new(&tcx);
let mut program = Program::new(debug_dump);
let mut pt = PlaceGraph::new(tcx.clone());

// Keep the default-zero path byte-for-byte reproducible: do not even
// draw from the RNG unless static generation is explicitly enabled.
if static_count > 0 {
let static_specs: Vec<(TyId, Mutability)> = tcx
.indices()
.filter_map(|ty| match ty.kind(&tcx) {
TyKind::Ref(pointee, Mutability::Not)
if <dyn RngCore>::is_literalble(*pointee, &tcx) =>
{
Some((*pointee, Mutability::Not))
}
TyKind::RawPtr(pointee, Mutability::Mut)
if <dyn RngCore>::is_literalble(*pointee, &tcx) =>
{
Some((*pointee, Mutability::Mut))
}
_ => None,
})
.collect();

for _ in 0..static_count {
let Some(&(ty, mutability)) = static_specs.iter().choose(&mut *rng.borrow_mut())
else {
break;
};
let init = rng
.borrow_mut()
.gen_literal(ty, &tcx)
.expect("static pointee type is literalble");
let id = program.push_static(StaticDecl {
ty,
mutability,
init,
});
pt.allocate_static(id, ty, mutability, init);
}
}

// TODO: don't zero-initialize current_function and current_bb
Self {
rng,
tcx: tcx.clone(),
ty_weights,
program: Program::new(debug_dump),
pt: PlaceGraph::new(tcx.clone()),
program,
pt,
return_stack: vec![],
cursor: Cursor {
function: Function::new(0),
Expand Down Expand Up @@ -1301,10 +1445,26 @@ impl GenerationCtx {
pt.mark_place_init(lhs);
}));
match rvalue {
Rvalue::AddressOf(_, referent) | Rvalue::Ref(_, referent) => {
Rvalue::AddressOf(_, referent) => {
let inherited_from = matches!(
referent.projection().first(),
Some(ProjectionElem::Deref)
)
.then(|| referent.local().to_place_index(&self.pt).unwrap());
let referent = referent.to_place_index(&self.pt).unwrap();
actions.push(Box::new(move |pt| {
pt.set_address_of(lhs, referent, inherited_from);
}));
}
Rvalue::Ref(_, referent) => {
let derived_from = matches!(
referent.projection().first(),
Some(ProjectionElem::Deref)
)
.then(|| referent.local().to_place_index(&self.pt).unwrap());
let referent = referent.to_place_index(&self.pt).unwrap();
actions.push(Box::new(move |pt| {
pt.set_ref(lhs, referent, None);
pt.set_ref_from_source(lhs, referent, derived_from);
}));
}
_ => {
Expand Down Expand Up @@ -1356,6 +1516,12 @@ impl GenerationCtx {
pt.assign_literal(lhs, Some(*lit));
}));
}
Operand::Static(id, _) | Operand::StaticMut(id, _) => {
let id = *id;
actions.push(Box::new(move |pt| {
pt.set_static_ref(lhs, id);
}));
}
},
agg @ Rvalue::Aggregate(agg_kind, ..) => {
if self.pt.ty(lhs).kind(&self.tcx).is_enum() {
Expand All @@ -1379,6 +1545,12 @@ impl GenerationCtx {
pt.assign_literal(target, Some(*lit));
}));
}
Operand::Static(id, _) | Operand::StaticMut(id, _) => {
let id = *id;
actions.push(Box::new(move |pt| {
pt.set_static_ref(target, id);
}));
}
}
}
}
Expand Down
Loading