Skip to content
Closed
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
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