diff --git a/crates/fuzzer/rustlantis/generate/src/generation/intrinsics.rs b/crates/fuzzer/rustlantis/generate/src/generation/intrinsics.rs index e1e1ebf80c..1dda85ac1e 100644 --- a/crates/fuzzer/rustlantis/generate/src/generation/intrinsics.rs +++ b/crates/fuzzer/rustlantis/generate/src/generation/intrinsics.rs @@ -67,7 +67,7 @@ impl CoreIntrinsic for ArithOffset { .into_weighted(&ctx.pt)?; let ptr = ctx .make_choice_weighted(ptrs.into_iter(), weights, |ppath| { - Ok(ppath.to_place(&ctx.pt)) + Ok(ppath.to_place(&ctx.pt, ctx.current_decls())) }) .ok()?; @@ -84,7 +84,7 @@ impl CoreIntrinsic for ArithOffset { } _ => PlaceSelector::for_known_val(ctx.tcx.clone()) .of_ty(TyCtxt::ISIZE) - .into_iter_place(&ctx.pt) + .into_iter_place(&ctx.pt, ctx.current_decls()) .choose(&mut *rng) .map(Operand::Copy) .unwrap_or_else(|| { @@ -162,7 +162,7 @@ impl CoreIntrinsic for Transmute { .into_weighted(&ctx.pt)?; let src = ctx .make_choice_weighted(srcs.into_iter(), weights, |ppath| { - Ok(ppath.to_place(&ctx.pt)) + Ok(ppath.to_place(&ctx.pt, ctx.current_decls())) }) .ok()?; if src.ty(ctx.current_decls(), &ctx.tcx).is_copy(&ctx.tcx) { diff --git a/crates/fuzzer/rustlantis/generate/src/generation/mod.rs b/crates/fuzzer/rustlantis/generate/src/generation/mod.rs index a9c9cbd851..e0f01d6209 100644 --- a/crates/fuzzer/rustlantis/generate/src/generation/mod.rs +++ b/crates/fuzzer/rustlantis/generate/src/generation/mod.rs @@ -79,9 +79,13 @@ impl GenerationCtx { .ok_or(SelectionError::Exhausted)?; self.make_choice_weighted(ppath.into_iter(), weights, |ppath| { if self.pt.ty(ppath.target_index()).is_copy(&self.tcx) { - Ok(Operand::Copy(ppath.to_place(&self.pt))) + Ok(Operand::Copy( + ppath.to_place(&self.pt, self.current_decls()), + )) } else { - Ok(Operand::Move(ppath.to_place(&self.pt))) + Ok(Operand::Move( + ppath.to_place(&self.pt, self.current_decls()), + )) } })? }; @@ -166,7 +170,7 @@ impl GenerationCtx { .into_weighted(&self.pt) .ok_or(SelectionError::Exhausted)?; let r = self.make_choice_weighted(ppath.into_iter(), weights, |ppath| { - Ok(Operand::Copy(ppath.to_place(&self.pt))) + Ok(Operand::Copy(ppath.to_place(&self.pt, self.current_decls()))) }); let r = r.unwrap_or_else(|_| { Operand::Constant( @@ -328,7 +332,10 @@ impl GenerationCtx { .into_weighted(&self.pt) .ok_or(SelectionError::Exhausted)?; self.make_choice_weighted(candidates.into_iter(), weights, |ppath| { - Ok(Rvalue::AddressOf(*mutability, ppath.to_place(&self.pt))) + Ok(Rvalue::AddressOf( + *mutability, + ppath.to_place(&self.pt, self.current_decls()), + )) }) } @@ -343,13 +350,20 @@ impl GenerationCtx { .except(lhs); 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(lhs.clone().project(ProjectionElem::Deref)); + selector = selector.except( + lhs.clone() + .project(ProjectionElem::Deref, self.current_decls(), &self.tcx) + .expect("a reference-typed place can be dereferenced"), + ); } let (candidates, weights) = selector .into_weighted(&self.pt) .ok_or(SelectionError::Exhausted)?; self.make_choice_weighted(candidates.into_iter(), weights, |ppath| { - Ok(Rvalue::Ref(*mutability, ppath.to_place(&self.pt))) + Ok(Rvalue::Ref( + *mutability, + ppath.to_place(&self.pt, self.current_decls()), + )) }) } @@ -436,7 +450,7 @@ impl GenerationCtx { .ok_or(SelectionError::Exhausted)?; self.make_choice_weighted(lhs_choices.into_iter(), weights, |ppath| { - let lhs = ppath.to_place(&self.pt); + let lhs = ppath.to_place(&self.pt, self.current_decls()); trace!( "generating an assignment statement with lhs {}: {}", lhs.serialize_place(&self.tcx), @@ -489,7 +503,7 @@ impl GenerationCtx { #[allow(dead_code)] fn generate_deinit(&self) -> Result { let place = PlaceSelector::for_operand(self.tcx.clone()) - .into_iter_place(&self.pt) + .into_iter_place(&self.pt, self.current_decls()) .choose(&mut *self.rng.borrow_mut()) .ok_or(SelectionError::Exhausted)?; Ok(Statement::Deinit(place)) @@ -508,7 +522,7 @@ impl GenerationCtx { .ok_or(SelectionError::Exhausted)?; self.make_choice_weighted(choices.into_iter(), weights, |ppath| { - let place = ppath.to_place(&self.pt); + let place = ppath.to_place(&self.pt, self.current_decls()); trace!( "generating a set discriminant statement with place {}", place.serialize_place(&self.tcx), @@ -658,7 +672,7 @@ impl GenerationCtx { let (place, place_val) = self.make_choice_weighted(places.into_iter(), weights, |ppath| { let val = self.pt.known_val(ppath.target_index()).expect("has_value"); - Ok((ppath.to_place(&self.pt), *val)) + Ok((ppath.to_place(&self.pt, self.current_decls()), *val)) })?; return Ok(TerminatorParams::SwitchInt { @@ -720,7 +734,7 @@ impl GenerationCtx { let return_place = self.make_choice_weighted(return_places.into_iter(), weights, |ppath| { - Result::Ok(ppath.to_place(&self.pt)) + Result::Ok(ppath.to_place(&self.pt, self.current_decls())) })?; // TODO: if return place has a ref, don't generate 0 argument as this can never be valid @@ -737,7 +751,7 @@ impl GenerationCtx { .into_weighted(&self.pt) .ok_or(SelectionError::Exhausted)?; let arg = self.make_choice_weighted(places.into_iter(), weights, |ppath| { - let place = ppath.to_place(&self.pt); + let place = ppath.to_place(&self.pt, self.current_decls()); let pidx = ppath.target_index(); let ty = self.pt.ty(pidx); @@ -802,7 +816,9 @@ impl GenerationCtx { let return_place = self.make_choice_weighted( return_places.into_iter(), weights, - |ppath: crate::pgraph::PlacePath| Result::Ok(ppath.to_place(&self.pt)), + |ppath: crate::pgraph::PlacePath| { + Result::Ok(ppath.to_place(&self.pt, self.current_decls())) + }, )?; let (callee, args) = self.choose_intrinsic(&return_place)?; diff --git a/crates/fuzzer/rustlantis/generate/src/pgraph.rs b/crates/fuzzer/rustlantis/generate/src/pgraph.rs index ff6987d766..3ff46147e2 100644 --- a/crates/fuzzer/rustlantis/generate/src/pgraph.rs +++ b/crates/fuzzer/rustlantis/generate/src/pgraph.rs @@ -9,8 +9,8 @@ use bimap::BiHashMap; use index_vec::IndexVec; use mir::{ syntax::{ - Body, FieldIdx, Literal, Local, Mutability, Operand, Place, ProjectionElem, Rvalue, TyId, - TyKind, UintTy, VariantIdx, + Body, FieldIdx, Literal, Local, LocalDecls, Mutability, Operand, Place, ProjectionElem, + Rvalue, TyId, TyKind, UintTy, VariantIdx, }, tyctxt::TyCtxt, }; @@ -1306,7 +1306,11 @@ impl PlacePath { self.source } - pub fn to_place(&self, pt: &PlaceGraph) -> Place { + /// The MIR place this path names in the current frame, validated against + /// that frame's `local_decls`. A path the graph cannot express as a valid + /// place is a generator bug, so it panics here rather than surfacing later + /// as a rustc error on the emitted program. + pub fn to_place(&self, pt: &PlaceGraph, local_decls: &LocalDecls) -> Place { let projs: SmallVec<[ProjectionElem; 8]> = self .path .iter() @@ -1330,7 +1334,10 @@ impl PlacePath { Place::from_projected( pt.current_frame().get_by_index(self.source).unwrap(), &projs, + local_decls, + &pt.tcx, ) + .unwrap_or_else(|err| panic!("place path does not name a valid place: {err}")) } pub fn projections<'pt>( @@ -1515,8 +1522,8 @@ mod tests { use index_vec::IndexVec; use mir::{ syntax::{ - Adt, BinOp, FieldIdx, Literal, Local, Mutability, Operand, Place, ProjectionElem, - Rvalue, TyId, TyKind, UintTy, VariantDef, VariantIdx, + Adt, BinOp, FieldIdx, Literal, Local, LocalDecl, LocalDecls, Mutability, Operand, + Place, ProjectionElem, Rvalue, TyId, TyKind, UintTy, VariantDef, VariantIdx, }, tyctxt::{AdtMeta, TyCtxt}, }; @@ -1528,6 +1535,26 @@ mod tests { use super::PlaceGraph; + /// The `LocalDecls` of a test body declaring `locals`; the return slot and + /// any undeclared gap are unit. Test places are built against it, as the + /// generator builds them against the current body. + fn decls(locals: &[(Local, TyId)]) -> LocalDecls { + let len = locals + .iter() + .map(|(local, _)| local.index() + 1) + .max() + .unwrap_or(1); + let mut decls: LocalDecls = (0..len).map(|_| LocalDecl::new_mut(TyCtxt::UNIT)).collect(); + for &(local, ty) in locals { + decls[local].ty = ty; + } + decls + } + + fn place(pt: &PlaceGraph, decls: &LocalDecls, local: Local, projs: &[ProjectionElem]) -> Place { + Place::from_projected(local, projs, decls, &pt.tcx).expect("test place is valid") + } + fn prepare_t() -> (PlaceGraph, Local, Place, Place, Place, Place, Place) { /* ┌──────┬──────┐ @@ -1545,18 +1572,44 @@ mod tests { let mut pt = PlaceGraph::new(Rc::new(tcx)); let local = Local::new(1); pt.allocate_local(local, t_root); + let decls = decls(&[(local, t_root)]); - let a = Place::from_projected(local, &[ProjectionElem::TupleField(FieldIdx::new(0))]); - let b = Place::from_projected(local, &[ProjectionElem::TupleField(FieldIdx::new(1))]); - let c = Place::from_projected(local, &[ProjectionElem::TupleField(FieldIdx::new(2))]); + let a = place( + &pt, + &decls, + local, + &[ProjectionElem::TupleField(FieldIdx::new(0))], + ); + let b = place( + &pt, + &decls, + local, + &[ProjectionElem::TupleField(FieldIdx::new(1))], + ); + let c = place( + &pt, + &decls, + local, + &[ProjectionElem::TupleField(FieldIdx::new(2))], + ); let d = b .clone() - .project(ProjectionElem::TupleField(FieldIdx::new(0))) + .project( + ProjectionElem::TupleField(FieldIdx::new(0)), + &decls, + &pt.tcx, + ) + .unwrap() .clone(); let e = b .clone() - .project(ProjectionElem::TupleField(FieldIdx::new(1))) + .project( + ProjectionElem::TupleField(FieldIdx::new(1)), + &decls, + &pt.tcx, + ) + .unwrap() .clone(); (pt, local, a, b, c, d, e) } @@ -1618,8 +1671,12 @@ mod tests { let int = Local::new(3); pt.allocate_local(int, TyCtxt::I32); + let decls = decls(&[(root, ty), (tuple, inner_ty), (int, TyCtxt::I32)]); + // root -[Deref]-> tuple -[Field(0)]-> tuple.0 -[Deref]-> int - let tuple_0 = Place::from_projected( + let tuple_0 = place( + &pt, + &decls, tuple, &[ProjectionElem::TupleField(FieldIdx::from_usize(0))], ); @@ -1659,8 +1716,15 @@ mod tests { let int = Local::new(2); pt.allocate_local(int, TyCtxt::I32); + let decls = decls(&[(root, ty), (int, TyCtxt::I32)]); + // root -[TupleField(0)]-> root.0 -[Deref]-> int - let root_0 = Place::from_projected(root, &[ProjectionElem::TupleField(FieldIdx::new(0))]); + let root_0 = place( + &pt, + &decls, + root, + &[ProjectionElem::TupleField(FieldIdx::new(0))], + ); pt.set_ref(root_0.clone(), int, None); // Violate the ProjectionIter invariant: subfield_edges must only @@ -1683,11 +1747,12 @@ mod tests { let mut pt = PlaceGraph::new(Rc::new(tcx)); let local = Local::new(1); pt.allocate_local(local, ty); + let decls = decls(&[(local, ty)]); let place = pt .reachable_from_node(local.to_place_index(&pt).unwrap()) .filter(|ppath| !ppath.path.is_empty()) - .map(|ppath| ppath.to_place(&pt)) + .map(|ppath| ppath.to_place(&pt, &decls)) .next() .unwrap(); @@ -1753,7 +1818,15 @@ mod tests { let target_ptr_pidx = pt.allocate_local(target_ptr, TyCtxt::I32); pt.mark_place_init(target_ptr); - let nested_ref = Place::from_projected( + let decls = decls(&[ + (root, t_root), + (target_ref, TyCtxt::I32), + (target_ptr, TyCtxt::I32), + ]); + + let nested_ref = place( + &pt, + &decls, root, &[ ProjectionElem::TupleField(FieldIdx::new(1)), @@ -1763,7 +1836,9 @@ mod tests { let nested_ref_pidx = nested_ref.to_place_index(&pt).unwrap(); pt.set_ref(nested_ref_pidx, target_ref_pidx, None); - let nested_ptr = Place::from_projected( + let nested_ptr = place( + &pt, + &decls, root, &[ ProjectionElem::TupleField(FieldIdx::new(1)), @@ -1810,11 +1885,27 @@ mod tests { let target_b_pidx = pt.allocate_local(target_b, TyCtxt::I32); pt.mark_place_init(target_b); - let elem_0 = Place::from_projected(arr, &[ProjectionElem::ConstantIndex { offset: 0 }]); + let decls = decls(&[ + (arr, t_arr), + (target_a, TyCtxt::I32), + (target_b, TyCtxt::I32), + ]); + + let elem_0 = place( + &pt, + &decls, + arr, + &[ProjectionElem::ConstantIndex { offset: 0 }], + ); let elem_0_pidx = elem_0.to_place_index(&pt).unwrap(); pt.set_ref(elem_0_pidx, target_a_pidx, None); - let elem_1 = Place::from_projected(arr, &[ProjectionElem::ConstantIndex { offset: 1 }]); + let elem_1 = place( + &pt, + &decls, + arr, + &[ProjectionElem::ConstantIndex { offset: 1 }], + ); let elem_1_pidx = elem_1.to_place_index(&pt).unwrap(); pt.set_ref(elem_1_pidx, target_b_pidx, None); @@ -1867,7 +1958,11 @@ mod tests { let target_pidx = pt.allocate_local(target, TyCtxt::I32); pt.mark_place_init(target); - let variant_ptr = Place::from_projected( + let decls = decls(&[(root, t_enum), (target, TyCtxt::I32)]); + + let variant_ptr = place( + &pt, + &decls, root, &[ProjectionElem::DowncastField( VariantIdx::new(0), @@ -1904,8 +1999,14 @@ mod tests { let target_pidx = pt.allocate_local(target, TyCtxt::I32); pt.mark_place_init(target); - let nested_ptr = - Place::from_projected(root, &[ProjectionElem::TupleField(FieldIdx::new(1))]); + let decls = decls(&[(root, t_root), (target, TyCtxt::I32)]); + + let nested_ptr = place( + &pt, + &decls, + root, + &[ProjectionElem::TupleField(FieldIdx::new(1))], + ); let nested_ptr_pidx = nested_ptr.to_place_index(&pt).unwrap(); pt.set_ref(nested_ptr_pidx, target_pidx, None); @@ -1995,8 +2096,12 @@ mod tests { pt.allocate_local(one, TyCtxt::USIZE); pt.assign_literal(one, Some(Literal::Uint(1, UintTy::Usize))); + let decls = decls(&[(local, ty), (one, TyCtxt::USIZE)]); + // local[one].0 - let one_zero = Place::from_projected( + let one_zero = place( + &pt, + &decls, local, &[ ProjectionElem::Index(one), @@ -2008,6 +2113,55 @@ mod tests { assert_eq!(pt.places[local_pidx].alloc_id, pt.places[one_zero].alloc_id); } + /// `arr: [i32; 2]` and `one: usize = 1` in the graph. The graph offers the + /// `arr[1]` path, which `PlacePath::to_place` spells `arr[one]` through + /// the local known to hold 1. The body declares `one` as `one_decl`, or + /// not at all. + fn index_path_place(one_decl: Option) -> Place { + let mut tcx = TyCtxt::from_primitives(TyConfig::default()); + let arr_ty = tcx.push(TyKind::Array(TyCtxt::I32, 2)); + let mut pt = PlaceGraph::new(Rc::new(tcx)); + + let arr = Local::new(1); + let arr_pidx = pt.allocate_local(arr, arr_ty); + pt.mark_place_init(arr); + let one = Local::new(2); + pt.allocate_local(one, TyCtxt::USIZE); + pt.assign_literal(one, Some(Literal::Uint(1, UintTy::Usize))); + pt.mark_place_init(one); + + let decls = match one_decl { + Some(ty) => decls(&[(arr, arr_ty), (one, ty)]), + None => decls(&[(arr, arr_ty)]), + }; + pt.reachable_from_node(arr_pidx) + .find(|ppath| { + ppath + .projections(&pt) + .eq([ProjectionElem::ConstantIndex { offset: 1 }]) + }) + .expect("the graph offers arr[1]") + .to_place(&pt, &decls) + } + + #[test] + fn to_place_indexes_through_a_declared_usize_local() { + let place = index_path_place(Some(TyCtxt::USIZE)); + assert_eq!(place.projection(), &[ProjectionElem::Index(Local::new(2))]); + } + + #[test] + #[should_panic(expected = "NonUsizeIndexLocal")] + fn to_place_rejects_an_index_local_declared_non_usize() { + index_path_place(Some(TyCtxt::I32)); + } + + #[test] + #[should_panic(expected = "MissingIndexLocal")] + fn to_place_rejects_an_undeclared_index_local() { + index_path_place(None); + } + #[test] fn shared_reference() { let mut tcx = TyCtxt::from_primitives(TyConfig::default()); @@ -2021,16 +2175,27 @@ mod tests { let root = Local::new(1); pt.allocate_local(root, t_i16_i32); pt.mark_place_init(root); + let root_ptr1 = Local::new(2); + let decls = decls(&[(root, t_i16_i32), (root_ptr1, t_ptr)]); - let root_0 = Place::from_projected(root, &[ProjectionElem::TupleField(FieldIdx::new(0))]) - .to_place_index(&pt) - .unwrap(); - let root_1 = Place::from_projected(root, &[ProjectionElem::TupleField(FieldIdx::new(1))]) - .to_place_index(&pt) - .unwrap(); + let root_0 = place( + &pt, + &decls, + root, + &[ProjectionElem::TupleField(FieldIdx::new(0))], + ) + .to_place_index(&pt) + .unwrap(); + let root_1 = place( + &pt, + &decls, + root, + &[ProjectionElem::TupleField(FieldIdx::new(1))], + ) + .to_place_index(&pt) + .unwrap(); // root_ptr1 = addr_of!(root) - let root_ptr1 = Local::new(2); pt.allocate_local(root_ptr1, t_ptr); pt.set_ref(root_ptr1, root, None); let root_ptr1_p = root_ptr1.to_place_index(&pt).unwrap(); @@ -2057,13 +2222,13 @@ mod tests { assert!(!pt.can_write_through(root_ptr2_p, root_0)); // Writing through (*root_ptr1).0 - pt.place_written( - &Place::from_projected( - root_ptr1, - &[ProjectionElem::Deref, ProjectionElem::TupleField(0.into())], - ), - pt.places[root_ptr1_p].tag, + let root_ptr1_0 = place( + &pt, + &decls, + root_ptr1, + &[ProjectionElem::Deref, ProjectionElem::TupleField(0.into())], ); + pt.place_written(&root_ptr1_0, pt.places[root_ptr1_p].tag); // (*root_ref).0 is invalidated assert!(!pt.can_read_through(root_ref_p, root_0)); diff --git a/crates/fuzzer/rustlantis/generate/src/place_select.rs b/crates/fuzzer/rustlantis/generate/src/place_select.rs index 8e4160548a..3b103274ca 100644 --- a/crates/fuzzer/rustlantis/generate/src/place_select.rs +++ b/crates/fuzzer/rustlantis/generate/src/place_select.rs @@ -2,7 +2,7 @@ use std::{rc::Rc, vec}; use abi::size::Size; use mir::{ - syntax::{Literal, Place, TyId}, + syntax::{Literal, LocalDecls, Place, TyId}, tyctxt::TyCtxt, }; use rand_distr::weighted::WeightedIndex; @@ -357,8 +357,13 @@ impl PlaceSelector { } } - pub fn into_iter_place(self, pt: &PlaceGraph) -> impl Iterator + Clone + '_ { - self.into_iter_path(pt).map(|ppath| ppath.to_place(pt)) + pub fn into_iter_place<'a>( + self, + pt: &'a PlaceGraph, + local_decls: &'a LocalDecls, + ) -> impl Iterator + Clone + 'a { + self.into_iter_path(pt) + .map(move |ppath| ppath.to_place(pt, local_decls)) } } @@ -369,7 +374,7 @@ mod tests { use config::TyConfig; use mir::{ - syntax::{Local, Place}, + syntax::{Local, LocalDecl, LocalDecls, Place}, tyctxt::TyCtxt, }; use rand::{ @@ -386,30 +391,33 @@ mod tests { use super::PlaceSelector; - fn build_pt(rng: &mut impl Rng) -> (PlaceGraph, Rc) { + fn build_pt(rng: &mut impl Rng) -> (PlaceGraph, Rc, LocalDecls) { let mut tcx = TyCtxt::from_primitives(TyConfig::default()); seed_tys(&mut tcx, rng); let tcx = Rc::new(tcx); let mut pt = PlaceGraph::new(tcx.clone()); let ty_weights = TySelect::new(&tcx); + let mut decls = LocalDecls::new(); for i in 0..=32 { - let pidx = pt.allocate_local(Local::new(i), ty_weights.choose_ty(rng, &tcx)); + let ty = ty_weights.choose_ty(rng, &tcx); + decls.push(LocalDecl::new_mut(ty)); + let pidx = pt.allocate_local(Local::new(i), ty); if i % 2 == 0 { pt.mark_place_init(pidx); } } - (pt, tcx) + (pt, tcx, decls) } #[bench] fn bench_select(b: &mut Bencher) { let mut rng = SmallRng::seed_from_u64(0); - let (pt, tcx) = build_pt(&mut rng); + let (pt, tcx, decls) = build_pt(&mut rng); b.iter(|| { PlaceSelector::for_lhs(tcx.clone()) .except(&Place::RETURN_SLOT) - .into_iter_place(&pt) + .into_iter_place(&pt, &decls) .choose(&mut rng) .expect("places not empty"); }) @@ -418,12 +426,12 @@ mod tests { #[bench] fn bench_materialise_into_vec(b: &mut Bencher) { let mut rng = SmallRng::seed_from_u64(0); - let (pt, tcx) = build_pt(&mut rng); + let (pt, tcx, decls) = build_pt(&mut rng); b.iter(|| { let places: Vec = PlaceSelector::for_lhs(tcx.clone()) .except(&Place::RETURN_SLOT) - .into_iter_place(&pt) + .into_iter_place(&pt, &decls) .collect(); // places.choose(&mut rng).expect("not empty"); diff --git a/crates/fuzzer/rustlantis/mir/src/lib.rs b/crates/fuzzer/rustlantis/mir/src/lib.rs index f4025f0838..42ba5d43fe 100644 --- a/crates/fuzzer/rustlantis/mir/src/lib.rs +++ b/crates/fuzzer/rustlantis/mir/src/lib.rs @@ -1,5 +1,4 @@ #![feature(iter_intersperse)] -#![feature(box_patterns)] pub mod serialize; pub mod syntax; diff --git a/crates/fuzzer/rustlantis/mir/src/syntax.rs b/crates/fuzzer/rustlantis/mir/src/syntax.rs index 3edf1d749f..b67b9bec7b 100644 --- a/crates/fuzzer/rustlantis/mir/src/syntax.rs +++ b/crates/fuzzer/rustlantis/mir/src/syntax.rs @@ -92,11 +92,21 @@ impl Place { } } - pub fn from_projected(local: Local, projections: &[ProjectionElem]) -> Self { - Place { + /// Builds `local` projected by `projections`, validated against the + /// body's `local_decls`: the base local and every `Index` local must be + /// declared, and each projection must apply to the type it projects. + pub fn from_projected( + local: Local, + projections: &[ProjectionElem], + local_decls: &LocalDecls, + tcx: &TyCtxt, + ) -> Result { + let place = Place { local, projection: SmallVec::from(projections), - } + }; + place.checked_ty(local_decls, tcx)?; + Ok(place) } pub fn local(&self) -> Local { @@ -107,19 +117,129 @@ impl Place { &self.projection } - pub fn project(&mut self, proj: ProjectionElem) -> &mut Self { - // TODO: validation + /// Appends `proj`, validated against the body's `local_decls` like + /// [`Place::from_projected`]. On error the place is left unchanged. + pub fn project( + &mut self, + proj: ProjectionElem, + local_decls: &LocalDecls, + tcx: &TyCtxt, + ) -> Result<&mut Self, ProjectionError> { + let base = self.checked_ty(local_decls, tcx)?; + project_elem(base, proj, local_decls, tcx).map_err(|reason| ProjectionError::Invalid { + position: self.projection.len(), + elem: proj, + base, + reason, + })?; self.projection.push(proj); - self + Ok(self) + } + + /// The type of this place, or the first reason its projection chain is + /// invalid for `local_decls`. + pub fn checked_ty( + &self, + local_decls: &LocalDecls, + tcx: &TyCtxt, + ) -> Result { + let decl = local_decls + .get(self.local) + .ok_or(ProjectionError::MissingLocal(self.local))?; + self.projection + .iter() + .enumerate() + .try_fold(decl.ty, |base, (position, &elem)| { + project_elem(base, elem, local_decls, tcx).map_err(|reason| { + ProjectionError::Invalid { + position, + elem, + base, + reason, + } + }) + }) } pub fn ty(&self, local_decl: &LocalDecls, tcx: &TyCtxt) -> TyId { - local_decl[self.local()] - .ty - .projected_ty(tcx, self.projection()) + self.checked_ty(local_decl, tcx) + .unwrap_or_else(|err| panic!("invalid place {self:?}: {err}")) + } +} + +/// One projection step with the checks that need the body: an `Index` local +/// must be a declared `usize`. Type semantics stay in [`TyId::project_one`]. +fn project_elem( + base: TyId, + elem: ProjectionElem, + local_decls: &LocalDecls, + tcx: &TyCtxt, +) -> Result { + if let ProjectionElem::Index(index) = elem { + let decl = local_decls + .get(index) + .ok_or(InvalidProjection::MissingIndexLocal(index))?; + if decl.ty != TyCtxt::USIZE { + return Err(InvalidProjection::NonUsizeIndexLocal { + local: index, + ty: decl.ty, + }); + } } + base.project_one(tcx, elem) +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ProjectionError { + /// The place's base local is not declared in the body. + MissingLocal(Local), + /// `elem`, at `position` in the chain, cannot project `base`. + Invalid { + position: usize, + elem: ProjectionElem, + base: TyId, + reason: InvalidProjection, + }, } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum InvalidProjection { + DerefOfNonPointer, + TupleFieldOfNonTuple, + TupleFieldOutOfBounds { len: usize }, + FieldOfNonStruct, + FieldOutOfBounds { len: usize }, + IndexOfNonArray, + MissingIndexLocal(Local), + NonUsizeIndexLocal { local: Local, ty: TyId }, + ConstantIndexOutOfBounds { len: usize }, + DowncastOfNonEnum, + VariantOutOfBounds { count: usize }, + VariantFieldOutOfBounds { len: usize }, + DowncastFieldTyMismatch { stored: TyId, actual: TyId }, +} + +impl std::fmt::Display for ProjectionError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ProjectionError::MissingLocal(local) => { + write!(f, "base local {local:?} is not declared") + } + ProjectionError::Invalid { + position, + elem, + base, + reason, + } => write!( + f, + "projection {position} ({elem:?}) cannot project {base:?}: {reason:?}" + ), + } + } +} + +impl std::error::Error for ProjectionError {} + impl From for Place { fn from(value: Local) -> Self { Self::from_local(value) @@ -350,37 +470,75 @@ impl TyId { } pub fn projected_ty(self, tcx: &TyCtxt, projs: &[ProjectionElem]) -> Self { - match projs { - [] => self, - [head, tail @ ..] => { - let projected = match head { - ProjectionElem::Deref => match self.kind(tcx) { - TyKind::RawPtr(pointee, ..) | TyKind::Ref(pointee, ..) => *pointee, - _ => panic!("not a reference"), - }, - ProjectionElem::TupleField(idx) => { - self.tuple_elems(tcx).expect("is a tuple")[idx.index()] - } - ProjectionElem::Index(_) | ProjectionElem::ConstantIndex { .. } => { - match self.kind(tcx) { - TyKind::Array(ty, ..) => *ty, - _ => panic!("not an array"), - } - } - ProjectionElem::Field(fid) => match self.kind(tcx) { - TyKind::Adt(adt) => { - let fields = &adt.variants.first().expect("adt is a struct").fields; - fields[*fid] - } - _ => panic!("not an adt"), - }, - ProjectionElem::DowncastField(vid, fid, _) => match self.kind(tcx) { - TyKind::Adt(adt) => adt.variants[*vid].fields[*fid], - _ => panic!("not an adt"), + projs.iter().fold(self, |ty, &elem| { + ty.project_one(tcx, elem) + .unwrap_or_else(|reason| panic!("{elem:?} cannot project {ty:?}: {reason:?}")) + }) + } + + /// The type `elem` projects out of `self`. The single owner of projection + /// type semantics, shared by [`TyId::projected_ty`] and [`Place`] + /// construction. An `Index` local's own type is checked by `Place`, which + /// has the body's `LocalDecls`. + pub fn project_one( + self, + tcx: &TyCtxt, + elem: ProjectionElem, + ) -> Result { + match (elem, self.kind(tcx)) { + (ProjectionElem::Deref, TyKind::RawPtr(pointee, ..) | TyKind::Ref(pointee, ..)) => { + Ok(*pointee) + } + (ProjectionElem::Deref, _) => Err(InvalidProjection::DerefOfNonPointer), + (ProjectionElem::TupleField(fid), TyKind::Tuple(elems)) => elems + .get(fid.index()) + .copied() + .ok_or(InvalidProjection::TupleFieldOutOfBounds { len: elems.len() }), + (ProjectionElem::TupleField(_), _) => Err(InvalidProjection::TupleFieldOfNonTuple), + (ProjectionElem::Index(_), TyKind::Array(elem_ty, _)) => Ok(*elem_ty), + (ProjectionElem::ConstantIndex { offset }, TyKind::Array(elem_ty, len)) => { + if offset < *len as u64 { + Ok(*elem_ty) + } else { + Err(InvalidProjection::ConstantIndexOutOfBounds { len: *len }) + } + } + (ProjectionElem::Index(_) | ProjectionElem::ConstantIndex { .. }, _) => { + Err(InvalidProjection::IndexOfNonArray) + } + (ProjectionElem::Field(fid), TyKind::Adt(adt)) if !adt.is_enum() => { + let fields = &adt + .variants + .first() + .ok_or(InvalidProjection::FieldOfNonStruct)? + .fields; + fields + .get(fid) + .copied() + .ok_or(InvalidProjection::FieldOutOfBounds { len: fields.len() }) + } + (ProjectionElem::Field(_), _) => Err(InvalidProjection::FieldOfNonStruct), + (ProjectionElem::DowncastField(vid, fid, stored), TyKind::Adt(adt)) + if adt.is_enum() => + { + let variant = + adt.variants + .get(vid) + .ok_or(InvalidProjection::VariantOutOfBounds { + count: adt.variants.len(), + })?; + let actual = variant.fields.get(fid).copied().ok_or( + InvalidProjection::VariantFieldOutOfBounds { + len: variant.fields.len(), }, - }; - projected.projected_ty(tcx, tail) + )?; + if stored == actual { + Ok(actual) + } else { + Err(InvalidProjection::DowncastFieldTyMismatch { stored, actual }) + } } + (ProjectionElem::DowncastField(..), _) => Err(InvalidProjection::DowncastOfNonEnum), } } @@ -933,3 +1091,279 @@ impl UnOp { } } } + +#[cfg(test)] +mod place_construction { + use config::TyConfig; + use index_vec::IndexVec; + + use super::*; + use crate::tyctxt::AdtMeta; + + struct Body { + tcx: TyCtxt, + decls: LocalDecls, + root: Local, + index: Local, + signed: Local, + arr: TyId, + } + + /// `root: *mut (u32, [S; 2])` with `struct S { i16, E }` and + /// `enum E { V0(u64), V1(i8) }`, plus `index: usize` and `signed: i32`. + fn body() -> Body { + let mut tcx = TyCtxt::from_primitives(TyConfig::default()); + let e = tcx.push_adt( + Adt { + variants: IndexVec::from_iter([ + VariantDef { + fields: IndexVec::from_iter([TyCtxt::U64]), + }, + VariantDef { + fields: IndexVec::from_iter([TyCtxt::I8]), + }, + ]), + }, + AdtMeta { copy: true }, + ); + let s = tcx.push_adt( + Adt { + variants: IndexVec::from_iter([VariantDef { + fields: IndexVec::from_iter([TyCtxt::I16, e]), + }]), + }, + AdtMeta { copy: true }, + ); + let arr = tcx.push(TyKind::Array(s, 2)); + let tuple = tcx.push(TyKind::Tuple(vec![TyCtxt::U32, arr])); + let ptr = tcx.push(TyKind::RawPtr(tuple, Mutability::Mut)); + + let mut decls = LocalDecls::new(); + decls.push(LocalDecl::new_mut(TyCtxt::UNIT)); + let root = decls.push(LocalDecl::new_mut(ptr)); + let index = decls.push(LocalDecl::new_mut(TyCtxt::USIZE)); + let signed = decls.push(LocalDecl::new_mut(TyCtxt::I32)); + Body { + tcx, + decls, + root, + index, + signed, + arr, + } + } + + fn reason(err: ProjectionError) -> InvalidProjection { + match err { + ProjectionError::Invalid { reason, .. } => reason, + other => panic!("expected an invalid projection, got {other:?}"), + } + } + + /// `(*root).1[index].1 as V1 .0`, and the same chain with a constant index. + fn valid_chain(index: ProjectionElem) -> [ProjectionElem; 5] { + [ + ProjectionElem::Deref, + ProjectionElem::TupleField(FieldIdx::new(1)), + index, + ProjectionElem::Field(FieldIdx::new(1)), + ProjectionElem::DowncastField(VariantIdx::new(1), FieldIdx::new(0), TyCtxt::I8), + ] + } + + #[test] + fn valid_chain_builds_the_same_place_either_way() { + let b = body(); + for index in [ + ProjectionElem::Index(b.index), + ProjectionElem::ConstantIndex { offset: 1 }, + ] { + let chain = valid_chain(index); + let built = Place::from_projected(b.root, &chain, &b.decls, &b.tcx).unwrap(); + assert_eq!(built.ty(&b.decls, &b.tcx), TyCtxt::I8); + + let mut stepped = Place::from_local(b.root); + for elem in chain { + stepped.project(elem, &b.decls, &b.tcx).unwrap(); + } + assert_eq!(stepped, built); + } + } + + #[test] + fn base_local_must_be_declared() { + let b = body(); + let missing = Local::new(b.decls.len()); + assert_eq!( + Place::from_projected(missing, &[ProjectionElem::Deref], &b.decls, &b.tcx), + Err(ProjectionError::MissingLocal(missing)) + ); + } + + #[test] + fn index_local_must_be_declared() { + let b = body(); + let missing = Local::new(b.decls.len()); + let err = Place::from_projected( + b.root, + &valid_chain(ProjectionElem::Index(missing)), + &b.decls, + &b.tcx, + ) + .unwrap_err(); + assert_eq!( + err, + ProjectionError::Invalid { + position: 2, + elem: ProjectionElem::Index(missing), + base: b.arr, + reason: InvalidProjection::MissingIndexLocal(missing), + } + ); + } + + #[test] + fn index_local_must_be_usize() { + let b = body(); + let signed = b.signed; + let err = Place::from_projected( + b.root, + &valid_chain(ProjectionElem::Index(signed)), + &b.decls, + &b.tcx, + ) + .unwrap_err(); + assert_eq!( + reason(err), + InvalidProjection::NonUsizeIndexLocal { + local: signed, + ty: TyCtxt::I32, + } + ); + } + + #[test] + fn each_projection_must_fit_its_base_type() { + let b = body(); + let deref = [ProjectionElem::Deref]; + let at_tuple = |elem| [ProjectionElem::Deref, elem]; + let at_s = |elem| { + [ + ProjectionElem::Deref, + ProjectionElem::TupleField(FieldIdx::new(1)), + ProjectionElem::ConstantIndex { offset: 0 }, + elem, + ] + }; + let at_e = |elem| { + [ + ProjectionElem::Deref, + ProjectionElem::TupleField(FieldIdx::new(1)), + ProjectionElem::ConstantIndex { offset: 0 }, + ProjectionElem::Field(FieldIdx::new(1)), + elem, + ] + }; + let cases: Vec<(Vec, InvalidProjection)> = vec![ + ( + [deref, deref].concat(), + InvalidProjection::DerefOfNonPointer, + ), + ( + at_tuple(ProjectionElem::TupleField(FieldIdx::new(2))).to_vec(), + InvalidProjection::TupleFieldOutOfBounds { len: 2 }, + ), + ( + at_tuple(ProjectionElem::Field(FieldIdx::new(0))).to_vec(), + InvalidProjection::FieldOfNonStruct, + ), + ( + at_tuple(ProjectionElem::Index(b.index)).to_vec(), + InvalidProjection::IndexOfNonArray, + ), + ( + [ + ProjectionElem::Deref, + ProjectionElem::TupleField(FieldIdx::new(1)), + ProjectionElem::ConstantIndex { offset: 2 }, + ] + .to_vec(), + InvalidProjection::ConstantIndexOutOfBounds { len: 2 }, + ), + ( + at_s(ProjectionElem::Field(FieldIdx::new(2))).to_vec(), + InvalidProjection::FieldOutOfBounds { len: 2 }, + ), + ( + at_s(ProjectionElem::DowncastField( + VariantIdx::new(0), + FieldIdx::new(0), + TyCtxt::I16, + )) + .to_vec(), + InvalidProjection::DowncastOfNonEnum, + ), + ( + at_e(ProjectionElem::Field(FieldIdx::new(0))).to_vec(), + InvalidProjection::FieldOfNonStruct, + ), + ( + at_e(ProjectionElem::DowncastField( + VariantIdx::new(2), + FieldIdx::new(0), + TyCtxt::I8, + )) + .to_vec(), + InvalidProjection::VariantOutOfBounds { count: 2 }, + ), + ( + at_e(ProjectionElem::DowncastField( + VariantIdx::new(1), + FieldIdx::new(1), + TyCtxt::I8, + )) + .to_vec(), + InvalidProjection::VariantFieldOutOfBounds { len: 1 }, + ), + ( + at_e(ProjectionElem::DowncastField( + VariantIdx::new(1), + FieldIdx::new(0), + TyCtxt::U64, + )) + .to_vec(), + InvalidProjection::DowncastFieldTyMismatch { + stored: TyCtxt::U64, + actual: TyCtxt::I8, + }, + ), + ]; + for (chain, expected) in cases { + let err = Place::from_projected(b.root, &chain, &b.decls, &b.tcx).unwrap_err(); + let ProjectionError::Invalid { + position, reason, .. + } = err + else { + panic!("{chain:?}: expected an invalid projection, got {err:?}"); + }; + assert_eq!(reason, expected, "{chain:?}"); + assert_eq!(position, chain.len() - 1, "{chain:?}"); + } + } + + #[test] + fn rejected_projection_leaves_the_place_unchanged() { + let b = body(); + let mut place = Place::from_local(b.root); + place + .project(ProjectionElem::Deref, &b.decls, &b.tcx) + .unwrap(); + let before = place.clone(); + + let err = place + .project(ProjectionElem::Deref, &b.decls, &b.tcx) + .unwrap_err(); + assert_eq!(reason(err), InvalidProjection::DerefOfNonPointer); + assert_eq!(place, before); + } +}