Skip to content

Commit ab345b2

Browse files
authored
feat(fuzz): Push and pop for slices (#9262)
1 parent 57778ea commit ab345b2

File tree

5 files changed

+169
-40
lines changed

5 files changed

+169
-40
lines changed

tooling/ast_fuzzer/fuzz/src/targets/pass_vs_prev.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ fn clone_ssa(ssa: &Ssa) -> Ssa {
6666
mod tests {
6767
/// ```ignore
6868
/// NOIR_AST_FUZZER_SEED=0x6819c61400001000 \
69-
/// NOIR_AST_FUZZER_SHOW_AST=1 \
69+
/// RUST_LOG=debug \
7070
/// cargo test -p noir_ast_fuzzer_fuzz pass_vs_prev
7171
/// ```
7272
#[test]

tooling/ast_fuzzer/src/compare/compiled.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,14 @@ impl Comparable for NargoErrorWithTypes {
178178
AssertionFailed(_, _, _),
179179
SolvingError(OpcodeResolutionError::UnsatisfiedConstrain { .. }, _),
180180
) => msg1.is_some_and(|msg| msg.contains("divide by zero")),
181+
(
182+
SolvingError(OpcodeResolutionError::IndexOutOfBounds { .. }, _),
183+
AssertionFailed(_, _, _),
184+
) => msg2.is_some_and(|msg| msg.contains("Index out of bounds")),
185+
(
186+
AssertionFailed(_, _, _),
187+
SolvingError(OpcodeResolutionError::IndexOutOfBounds { .. }, _),
188+
) => msg1.is_some_and(|msg| msg.contains("Index out of bounds")),
181189
_ => false,
182190
}
183191
}

tooling/ast_fuzzer/src/program/func.rs

Lines changed: 145 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -664,8 +664,18 @@ impl<'a> FunctionContext<'a> {
664664
tgt_type: &Type,
665665
max_depth: usize,
666666
) -> arbitrary::Result<Option<TrackedExpression>> {
667-
// If we found our type, return it without further ado.
667+
// If we found our type, we can return it without further ado.
668668
if src_type == tgt_type {
669+
// If we want a slice, we can push onto it.
670+
if let Type::Slice(item_type) = src_type {
671+
if bool::arbitrary(u)? {
672+
let (item, item_dyn) = self.gen_expr(u, item_type, max_depth, Flags::TOP)?;
673+
let push_expr =
674+
self.call_slice_push(src_expr, src_type.clone(), bool::arbitrary(u)?, item);
675+
return Ok(Some((push_expr, src_dyn || item_dyn)));
676+
}
677+
}
678+
// Otherwise just return as-is.
669679
return Ok(Some((src_expr, src_dyn)));
670680
}
671681

@@ -710,7 +720,7 @@ impl<'a> FunctionContext<'a> {
710720
};
711721
Ok(Some((expr, src_dyn)))
712722
}
713-
(Type::Array(len, item_typ), _) if *len > 0 => {
723+
(Type::Array(len, item_type), _) if *len > 0 => {
714724
// Indexing arrays that contains references with dynamic indexes was banned in #8888
715725
// If we are already looking for an index where we can't use dynamic inputs,
716726
// don't switch to using them again, as the result can indirectly poison the outer array.
@@ -723,7 +733,7 @@ impl<'a> FunctionContext<'a> {
723733
// }
724734
let (idx_expr, idx_dyn) = {
725735
let no_dynamic = self.in_no_dynamic
726-
|| !self.unconstrained() && types::contains_reference(item_typ);
736+
|| !self.unconstrained() && types::contains_reference(item_type);
727737
let was_in_no_dynamic = std::mem::replace(&mut self.in_no_dynamic, no_dynamic);
728738

729739
// Choose a random index.
@@ -738,20 +748,36 @@ impl<'a> FunctionContext<'a> {
738748
let item_expr = Expression::Index(Index {
739749
collection: Box::new(src_expr),
740750
index: Box::new(idx_expr),
741-
element_type: *item_typ.clone(),
751+
element_type: *item_type.clone(),
742752
location: Location::dummy(),
743753
});
744754
// Produce the target type from the item.
745755
self.gen_expr_from_source(
746756
u,
747757
(item_expr, src_dyn || idx_dyn),
748-
item_typ,
758+
item_type,
749759
src_mutable,
750760
tgt_type,
751761
max_depth,
752762
)
753763
}
754-
(Type::Slice(item_typ), _) => {
764+
(Type::Slice(item_type), Type::Tuple(fields))
765+
if fields.len() == 2
766+
&& &fields[0] == item_type.as_ref()
767+
&& &fields[1] == src_type =>
768+
{
769+
let pop_front = self.call_slice_pop(src_expr, src_type.clone(), true);
770+
Ok(Some((pop_front, src_dyn)))
771+
}
772+
(Type::Slice(item_type), Type::Tuple(fields))
773+
if fields.len() == 2
774+
&& &fields[0] == src_type
775+
&& &fields[1] == item_type.as_ref() =>
776+
{
777+
let pop_back = self.call_slice_pop(src_expr, src_type.clone(), false);
778+
Ok(Some((pop_back, src_dyn)))
779+
}
780+
(Type::Slice(item_type), _) => {
755781
// We don't know the length of the slice at compile time,
756782
// so we need to call the builtin function to get it,
757783
// and use it for the length modulo.
@@ -762,7 +788,7 @@ impl<'a> FunctionContext<'a> {
762788
(self.gen_literal(u, &types::U32)?, false)
763789
} else {
764790
let no_dynamic = self.in_no_dynamic
765-
|| !self.unconstrained() && types::contains_reference(item_typ);
791+
|| !self.unconstrained() && types::contains_reference(item_type);
766792
let was_in_no_dynamic = std::mem::replace(&mut self.in_no_dynamic, no_dynamic);
767793

768794
// Choose a random index.
@@ -794,27 +820,7 @@ impl<'a> FunctionContext<'a> {
794820
let ident_2 = Ident { id: self.next_ident_id(), ..ident_1.clone() };
795821

796822
// Get the runtime length.
797-
let len_expr = {
798-
let array_len_ident = Ident {
799-
location: None,
800-
definition: Definition::Builtin("array_len".to_string()),
801-
mutable: false,
802-
name: "len".to_string(),
803-
typ: Type::Function(
804-
vec![src_type.clone()],
805-
Box::new(types::U32),
806-
Box::new(Type::Unit),
807-
false,
808-
),
809-
id: self.next_ident_id(),
810-
};
811-
Expression::Call(Call {
812-
func: Box::new(Expression::Ident(array_len_ident)),
813-
arguments: vec![Expression::Ident(ident_1)],
814-
return_type: types::U32,
815-
location: Location::dummy(),
816-
})
817-
};
823+
let len_expr = self.call_array_len(Expression::Ident(ident_1), src_type.clone());
818824

819825
// Take the modulo.
820826
let idx_expr = expr::modulo(idx_expr, len_expr);
@@ -823,15 +829,15 @@ impl<'a> FunctionContext<'a> {
823829
let item_expr = Expression::Index(Index {
824830
collection: Box::new(Expression::Ident(ident_2)),
825831
index: Box::new(idx_expr),
826-
element_type: *item_typ.clone(),
832+
element_type: *item_type.clone(),
827833
location: Location::dummy(),
828834
});
829835

830836
// Produce the target type from the item.
831837
let Some((expr, is_dyn)) = self.gen_expr_from_source(
832838
u,
833839
(item_expr, src_dyn || idx_dyn),
834-
item_typ,
840+
item_type,
835841
src_mutable,
836842
tgt_type,
837843
max_depth,
@@ -1149,7 +1155,23 @@ impl<'a> FunctionContext<'a> {
11491155
// Generate a type or choose an existing one.
11501156
let max_depth = self.max_depth();
11511157
let comptime_friendly = self.config().comptime_friendly;
1152-
let typ = self.ctx.gen_type(u, max_depth, false, false, true, comptime_friendly, true)?;
1158+
let mut typ =
1159+
self.ctx.gen_type(u, max_depth, false, false, true, comptime_friendly, true)?;
1160+
1161+
// If we picked the target type to be a slice, we can consider popping from it.
1162+
if let Type::Slice(ref item_type) = typ {
1163+
if bool::arbitrary(u)? {
1164+
let fields = if bool::arbitrary(u)? {
1165+
// ([T], T) <- pop_back
1166+
vec![typ.clone(), item_type.as_ref().clone()]
1167+
} else {
1168+
// (T, [T]) <- pop_front
1169+
vec![item_type.as_ref().clone(), typ.clone()]
1170+
};
1171+
typ = Type::Tuple(fields);
1172+
}
1173+
}
1174+
11531175
let (expr, is_dyn) = self.gen_expr(u, &typ, max_depth, Flags::TOP)?;
11541176
let mutable = bool::arbitrary(u)?;
11551177
Ok(self.let_var(mutable, typ, expr, true, is_dyn, local_name))
@@ -2045,6 +2067,98 @@ impl<'a> FunctionContext<'a> {
20452067
};
20462068
(*id, name.clone(), let_expr)
20472069
}
2070+
2071+
/// Construct a `Call` to the `array_len` builtin function, calling it with the
2072+
/// identifier of a slice or an array.
2073+
fn call_array_len(&mut self, array_or_slice: Expression, typ: Type) -> Expression {
2074+
let func_ident = Ident {
2075+
location: None,
2076+
definition: Definition::Builtin("array_len".to_string()),
2077+
mutable: false,
2078+
name: "len".to_string(),
2079+
typ: Type::Function(vec![typ], Box::new(types::U32), Box::new(Type::Unit), false),
2080+
id: self.next_ident_id(),
2081+
};
2082+
Expression::Call(Call {
2083+
func: Box::new(Expression::Ident(func_ident)),
2084+
arguments: vec![array_or_slice],
2085+
return_type: types::U32,
2086+
location: Location::dummy(),
2087+
})
2088+
}
2089+
2090+
/// Construct a `Call` to the `slice_push_front` or `slice_push_back` builtin function.
2091+
fn call_slice_push(
2092+
&mut self,
2093+
slice: Expression,
2094+
slice_type: Type,
2095+
is_front: bool,
2096+
item: Expression,
2097+
) -> Expression {
2098+
let item_type = match slice_type {
2099+
Type::Slice(ref item_type) => item_type.as_ref().clone(),
2100+
other => unreachable!("only called with slice type; got {other}"),
2101+
};
2102+
let name = if is_front { "push_front" } else { "push_back" };
2103+
let func_ident = Ident {
2104+
location: None,
2105+
definition: Definition::Builtin(format!("slice_{name}")),
2106+
mutable: false,
2107+
name: name.to_string(),
2108+
typ: Type::Function(
2109+
vec![slice_type.clone(), item_type],
2110+
Box::new(slice_type.clone()),
2111+
Box::new(Type::Unit),
2112+
false,
2113+
),
2114+
id: self.next_ident_id(),
2115+
};
2116+
Expression::Call(Call {
2117+
func: Box::new(Expression::Ident(func_ident)),
2118+
arguments: vec![slice, item],
2119+
return_type: slice_type,
2120+
location: Location::dummy(),
2121+
})
2122+
}
2123+
2124+
/// Construct a `Call` to the `slice_pop_front` or `slice_pop_back` builtin function.
2125+
fn call_slice_pop(
2126+
&mut self,
2127+
slice: Expression,
2128+
slice_type: Type,
2129+
is_front: bool,
2130+
) -> Expression {
2131+
let item_type = match slice_type {
2132+
Type::Slice(ref item_type) => item_type.as_ref().clone(),
2133+
other => unreachable!("only called with slice type; got {other}"),
2134+
};
2135+
let name = if is_front { "pop_front" } else { "pop_back" };
2136+
let fields = if is_front {
2137+
vec![item_type, slice_type.clone()]
2138+
} else {
2139+
vec![slice_type.clone(), item_type]
2140+
};
2141+
let return_type = Type::Tuple(fields);
2142+
let func_ident = Ident {
2143+
location: None,
2144+
definition: Definition::Builtin(format!("slice_{name}")),
2145+
mutable: false,
2146+
name: name.to_string(),
2147+
typ: Type::Function(
2148+
vec![slice_type],
2149+
Box::new(return_type.clone()),
2150+
Box::new(Type::Unit),
2151+
false,
2152+
),
2153+
id: self.next_ident_id(),
2154+
};
2155+
Expression::Call(Call {
2156+
func: Box::new(Expression::Ident(func_ident)),
2157+
arguments: vec![slice],
2158+
return_type,
2159+
location: Location::dummy(),
2160+
})
2161+
}
20482162
}
20492163

20502164
#[cfg(test)]

tooling/ast_fuzzer/src/program/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -565,7 +565,10 @@ fn make_name(mut id: usize, is_global: bool) -> String {
565565
id /= 26;
566566
}
567567
name.reverse();
568-
let name = name.into_iter().collect::<String>();
568+
let mut name = name.into_iter().collect::<String>();
569+
if matches!(name.as_str(), "as" | "if" | "fn" | "for" | "loop") {
570+
name = format!("{name}_");
571+
}
569572
if is_global { format!("G_{name}") } else { name }
570573
}
571574

tooling/ast_fuzzer/src/program/types.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,9 @@ pub fn types_produced(typ: &Type) -> HashSet<Type> {
7171
acc.insert(typ.clone());
7272

7373
match typ {
74-
Type::Array(len, typ) => {
74+
Type::Array(len, item_type) => {
7575
if *len > 0 {
76-
visit(acc, typ);
76+
visit(acc, item_type);
7777
}
7878
// Technically we could produce `[T; N]` from `[S; N]` if
7979
// we can produce `T` from `S`, but let's ignore that;
@@ -83,9 +83,9 @@ pub fn types_produced(typ: &Type) -> HashSet<Type> {
8383
// we might generate `[foo[1] as u64, 3u64]` instead of "mapping"
8484
// over the entire foo. Same goes for tuples.
8585
}
86-
Type::Tuple(types) => {
87-
for typ in types {
88-
visit(acc, typ);
86+
Type::Tuple(item_types) => {
87+
for item_type in item_types {
88+
visit(acc, item_type);
8989
}
9090
}
9191
Type::String(_) => {
@@ -119,8 +119,12 @@ pub fn types_produced(typ: &Type) -> HashSet<Type> {
119119
// Maybe we can also cast to u1 or u8 etc?
120120
acc.insert(Type::Field);
121121
}
122-
Type::Slice(typ) => {
123-
visit(acc, typ);
122+
Type::Slice(item_type) => {
123+
// pop_front -> (T, [T])
124+
acc.insert(Type::Tuple(vec![item_type.as_ref().clone(), typ.clone()]));
125+
// pop_back -> ([T], T)
126+
acc.insert(Type::Tuple(vec![typ.clone(), item_type.as_ref().clone()]));
127+
visit(acc, item_type);
124128
}
125129
Type::Reference(typ, _) => {
126130
visit(acc, typ);

0 commit comments

Comments
 (0)