Skip to content
Merged
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
4 changes: 4 additions & 0 deletions tooling/ast_fuzzer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ pub struct Config {
pub avoid_large_int_literals: bool,
/// Avoid using loop control (break/continue).
pub avoid_loop_control: bool,
/// Avoid using function pointers in parameters.
pub avoid_lambdas: bool,
/// Only use comptime friendly expressions.
pub comptime_friendly: bool,
}
Expand Down Expand Up @@ -117,6 +119,8 @@ impl Default for Config {
avoid_large_int_literals: false,
avoid_negative_int_literals: false,
avoid_loop_control: false,
// TODO(#8543): Allow lambdas when ICE is fixed.
avoid_lambdas: true,
comptime_friendly: false,
}
}
Expand Down
132 changes: 91 additions & 41 deletions tooling/ast_fuzzer/src/program/func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
};

use super::{
Context, VariableId, expr,
CallableId, Context, VariableId, expr,
freq::Freq,
make_name,
scope::{Scope, ScopeStack, Variable},
Expand Down Expand Up @@ -161,7 +161,7 @@
in_loop: bool,
/// All the functions callable from this one, with the types we can
/// produce from their return value.
call_targets: BTreeMap<FuncId, HashSet<Type>>,
call_targets: BTreeMap<CallableId, HashSet<Type>>,
/// Indicate that we have generated a `Call`.
has_call: bool,
}
Expand All @@ -185,17 +185,25 @@
);

// Collect all the functions we can call from this one.
// TODO(#8484): Look for call targets in function-valued arguments as well.
let call_targets = ctx
.function_declarations
.iter()
.filter_map(|(callee_id, callee_decl)| {
if !can_call(id, decl.unconstrained, *callee_id, callee_decl.unconstrained) {
return None;
}
Some((*callee_id, types::types_produced(&callee_decl.return_type)))
})
.collect();
let mut call_targets = BTreeMap::new();

// Consider calling any allowed global function.
for (callee_id, callee_decl) in &ctx.function_declarations {
if !can_call(id, decl.unconstrained, *callee_id, callee_decl.unconstrained) {
continue;
}
let produces = types::types_produced(&callee_decl.return_type);
call_targets.insert(CallableId::Global(*callee_id), produces);
}

// Consider function pointers as callable; they are already filtered during construction.
for (callee_id, _, _, typ, _) in &decl.params {
let Type::Function(_, return_type, _, _) = typ else {
continue;
};
let produces = types::types_produced(return_type);
call_targets.insert(CallableId::Local(*callee_id), produces);
}

Self {
ctx,
Expand Down Expand Up @@ -316,12 +324,20 @@
max_depth: usize,
flags: Flags,
) -> arbitrary::Result<Expression> {
// For now if we need a function, return one without further nesting,
// e.g. avoid `if <cond> { func_1 } else { func_2 }`, because it makes rewriting
// harder when we need to deal with proxies.
// For now if we need a function, return one without further nesting, e.g. avoid `if <cond> { func_1 } else { func_2 }`,
// because it makes it harder to rewrite functions to add recursion limit: we would need to replace functions in the
// expressions to proxy version if we call Brillig from ACIR, but we would also need to keep track whether we are calling a function,
// For example if we could return function pointers, we could have something like this:
// `acir_func_1(if c { brillig_func_2 } else { unsafe { brillig_func_3(brillig_func_4) } })`
// We could replace `brillig_func_2` with `brillig_func_2_proxy`, but we wouldn't replace `brillig_func_4` with `brillig_func_4_proxy`
// because that is a parameter of another call. But we would have to deal with the return value.
// For this reason we handle function parameters directly here.
if matches!(typ, Type::Function(_, _, _, _)) {
// Local variables we should consider in `gen_expr_from_vars`, so here we just look through global functions.
return self.find_function_with_signature(u, typ);
// Prefer functions in variables over globals.
return match self.gen_expr_from_vars(u, typ, max_depth)? {
Some(expr) => Ok(expr),
None => self.find_global_function_with_signature(u, typ),
};
}

let mut freq = Freq::new(u, &self.ctx.config.expr_freqs)?;
Expand Down Expand Up @@ -969,9 +985,7 @@

let callee_id = *u.choose_iter(opts)?;
let callee_ident = self.function_ident(callee_id);

let callee = self.ctx.function_decl(callee_id).clone();
let param_types = callee.params.iter().map(|p| p.3.clone()).collect::<Vec<_>>();
let (param_types, return_type) = self.callable_signature(callee_id);

// Generate an expression for each argument.
let mut args = Vec::new();
Expand All @@ -982,12 +996,12 @@
let call_expr = Expression::Call(Call {
func: Box::new(callee_ident),
arguments: args,
return_type: callee.return_type.clone(),
return_type: return_type.clone(),
location: Location::dummy(),
});

// Derive the final result from the call, e.g. by casting, or accessing a member.
self.gen_expr_from_source(u, call_expr, &callee.return_type, typ, self.max_depth())
self.gen_expr_from_source(u, call_expr, &return_type, typ, self.max_depth())
}

/// Generate a call to a specific function, with arbitrary literals
Expand Down Expand Up @@ -1163,7 +1177,9 @@
}

/// Find a global function matching a type signature.
fn find_function_with_signature(
///
/// For local functions we use `gen_expr_from_vars`.
fn find_global_function_with_signature(
&mut self,
u: &mut Unstructured,
typ: &Type,
Expand Down Expand Up @@ -1196,26 +1212,60 @@

let callee_id = u.choose_iter(candidates)?;

Ok(self.function_ident(callee_id))
Ok(self.function_ident(CallableId::Global(callee_id)))
}

/// Generate an identifier for calling a global function.
fn function_ident(&mut self, callee_id: FuncId) -> Expression {
let callee = self.ctx.function_decl(callee_id).clone();
let param_types = callee.params.iter().map(|p| p.3.clone()).collect::<Vec<_>>();
Expression::Ident(Ident {
location: None,
definition: Definition::Function(callee_id),
mutable: false,
name: callee.name.clone(),
typ: Type::Function(
param_types,
Box::new(callee.return_type.clone()),
Box::new(Type::Unit),
callee.unconstrained,
),
id: self.next_ident_id(),
})
fn function_ident(&mut self, callee_id: CallableId) -> Expression {
match callee_id {
CallableId::Global(id) => {
let callee = self.ctx.function_decl(id).clone();
let param_types = callee.params.iter().map(|p| p.3.clone()).collect::<Vec<_>>();
Expression::Ident(Ident {
location: None,
definition: Definition::Function(id),
mutable: false,
name: callee.name.clone(),
typ: Type::Function(
param_types,
Box::new(callee.return_type.clone()),
Box::new(Type::Unit),
callee.unconstrained,
),
id: self.next_ident_id(),
})
}
CallableId::Local(id) => {
let (mutable, name, typ) = self.locals.current().get_variable(&id);
Expression::Ident(Ident {
location: None,
definition: Definition::Local(id),
mutable: *mutable,
name: name.clone(),
typ: typ.clone(),
id: self.next_ident_id(),
})
}
}
}

/// Get the parameter types and return type of a callable function.
fn callable_signature(&self, callee_id: CallableId) -> (Vec<Type>, Type) {
match callee_id {
CallableId::Global(id) => {
let decl = self.ctx.function_decl(id);
let return_type = decl.return_type.clone();
let param_types = decl.params.iter().map(|p| p.3.clone()).collect::<Vec<_>>();
(param_types, return_type)
}
CallableId::Local(id) => {
let (_, _, typ) = self.locals.current().get_variable(&id);
let Type::Function(param_types, return_type, _, _) = typ else {
unreachable!("function pointers should have function type; got {typ}")
};
(param_types.clone(), return_type.as_ref().clone())
}
}
}
}

Expand All @@ -1226,9 +1276,9 @@
ctx.config.max_loop_size = 10;
ctx.config.vary_loop_size = false;
ctx.gen_main_decl(&mut u);
let mut fctx = FunctionContext::new(&mut ctx, FuncId(0));

Check warning on line 1279 in tooling/ast_fuzzer/src/program/func.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (fctx)
fctx.budget = 2;

Check warning on line 1280 in tooling/ast_fuzzer/src/program/func.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (fctx)
let loop_code = format!("{}", fctx.gen_loop(&mut u).unwrap()).replace(" ", "");

Check warning on line 1281 in tooling/ast_fuzzer/src/program/func.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (fctx)

println!("{loop_code}");
assert!(
Expand All @@ -1252,8 +1302,8 @@
ctx.config.max_loop_size = 10;
ctx.config.vary_loop_size = false;
ctx.gen_main_decl(&mut u);
let mut fctx = FunctionContext::new(&mut ctx, FuncId(0));

Check warning on line 1305 in tooling/ast_fuzzer/src/program/func.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (fctx)
fctx.budget = 2;

Check warning on line 1306 in tooling/ast_fuzzer/src/program/func.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (fctx)
let while_code = format!("{}", fctx.gen_while(&mut u).unwrap()).replace(" ", "");

println!("{while_code}");
Expand Down
10 changes: 9 additions & 1 deletion tooling/ast_fuzzer/src/program/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
ctx.set_function_decl(FuncId(1), decl_inner.clone());
ctx.gen_function(u, FuncId(1))?;

// Parameterless main declaration wrapping the inner "main"

Check warning on line 54 in tooling/ast_fuzzer/src/program/mod.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (Parameterless)
// function call
let decl_main = FunctionDeclaration {
name: "main".into(),
Expand All @@ -63,7 +63,7 @@
};

ctx.set_function_decl(FuncId(0), decl_main);
ctx.gen_function_with_body(u, FuncId(0), |u, fctx| fctx.gen_body_with_lit_call(u, FuncId(1)))?;

Check warning on line 66 in tooling/ast_fuzzer/src/program/mod.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (fctx)

Check warning on line 66 in tooling/ast_fuzzer/src/program/mod.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (fctx)
ctx.rewrite_functions(u)?;

let program = ctx.finalize();
Expand All @@ -77,6 +77,14 @@
Global(GlobalId),
}

/// ID of a function we can call, either as a pointer in a local variable,
/// or directly as a global function.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub(crate) enum CallableId {
Local(LocalId),
Global(FuncId),
}

/// Name of a variable.
type Name = String;

Expand Down Expand Up @@ -181,7 +189,7 @@
|| bool::arbitrary(u)?;

// Which existing functions we could receive as parameters.
let func_param_candidates: Vec<FuncId> = if is_main {
let func_param_candidates: Vec<FuncId> = if is_main || self.config.avoid_lambdas {
// Main cannot receive function parameters from outside.
vec![]
} else {
Expand Down Expand Up @@ -293,7 +301,7 @@

/// Generate random function body.
fn gen_function(&mut self, u: &mut Unstructured, id: FuncId) -> arbitrary::Result<()> {
self.gen_function_with_body(u, id, |u, fctx| fctx.gen_body(u))

Check warning on line 304 in tooling/ast_fuzzer/src/program/mod.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (fctx)

Check warning on line 304 in tooling/ast_fuzzer/src/program/mod.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (fctx)
}

/// Generate function with a specified body generator.
Expand Down
Loading
Loading