From 1cf8bb29a74d4b9aacc758c1b004b4ecc5d6c2a6 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Wed, 27 Nov 2024 11:42:57 -0800 Subject: [PATCH 1/9] analyze: rewrite: add SublocInfo collection pass --- c2rust-analyze/src/rewrite/expr/mir_op.rs | 4 +- c2rust-analyze/src/rewrite/expr/mod.rs | 50 ++ .../src/rewrite/expr/subloc_info.rs | 731 ++++++++++++++++++ 3 files changed, 783 insertions(+), 2 deletions(-) create mode 100644 c2rust-analyze/src/rewrite/expr/subloc_info.rs diff --git a/c2rust-analyze/src/rewrite/expr/mir_op.rs b/c2rust-analyze/src/rewrite/expr/mir_op.rs index f31835d25..7c79b724d 100644 --- a/c2rust-analyze/src/rewrite/expr/mir_op.rs +++ b/c2rust-analyze/src/rewrite/expr/mir_op.rs @@ -208,7 +208,7 @@ pub struct MirRewrite { } #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] -enum PlaceAccess { +pub enum PlaceAccess { /// Enclosing context intends to read from the place. Imm, /// Enclosing context intends to write to the place. @@ -2028,7 +2028,7 @@ where } } -trait IsLocal { +pub trait IsLocal { fn is_local(&self) -> bool; } diff --git a/c2rust-analyze/src/rewrite/expr/mod.rs b/c2rust-analyze/src/rewrite/expr/mod.rs index 37bea6fdb..ceb7903fc 100644 --- a/c2rust-analyze/src/rewrite/expr/mod.rs +++ b/c2rust-analyze/src/rewrite/expr/mod.rs @@ -16,6 +16,7 @@ mod convert; mod distribute; mod hir_only_casts; mod mir_op; +mod subloc_info; mod unlower; // Helpers used by the shim builder. @@ -31,6 +32,9 @@ pub fn gen_expr_rewrites<'tcx>( mir: &Body<'tcx>, hir_body_id: BodyId, ) -> Vec<(Span, Rewrite)> { + let subloc_info = subloc_info::collect_subloc_info(acx, asn, pointee_types, last_use, mir); + debug_print_subloc_info_map(acx.tcx(), mir, &subloc_info); + let (mir_rewrites, errors) = mir_op::gen_mir_rewrites(acx, asn, pointee_types, last_use, mir); if !errors.is_empty() { acx.gacx.dont_rewrite_fns.add(def_id, errors); @@ -56,6 +60,52 @@ pub fn gen_expr_rewrites<'tcx>( hir_rewrites } +fn debug_print_subloc_info_map<'tcx>( + tcx: TyCtxt<'tcx>, + mir: &Body<'tcx>, + subloc_info: &HashMap<(Location, Vec), subloc_info::SublocInfo<'tcx>>, +) { + let mut by_loc = HashMap::new(); + for (&(loc, ref sub_loc), info) in subloc_info { + by_loc.entry(loc).or_insert(Vec::new()).push((sub_loc, info)); + } + + for v in by_loc.values_mut() { + v.sort_by_key(|&(sub_loc, _info)| sub_loc); + } + + let print_for_loc = |loc| { + for &(sub_loc, info) in by_loc.get(&loc).map_or(&[] as &[_], |x| x) { + eprintln!(" {sub_loc:?}: {info:?}"); + } + }; + + eprintln!("subloc_info for {:?}:", mir.source); + for (bb_id, bb) in mir.basic_blocks().iter_enumerated() { + eprintln!(" block {bb_id:?}:"); + for (i, stmt) in bb.statements.iter().enumerate() { + let loc = Location { + block: bb_id, + statement_index: i, + }; + + eprintln!(" {loc:?}: {stmt:?}"); + print_for_loc(loc); + } + + { + let term = bb.terminator(); + let loc = Location { + block: bb_id, + statement_index: bb.statements.len(), + }; + + eprintln!(" {loc:?}: {term:?}"); + print_for_loc(loc); + } + } +} + fn debug_print_unlower_map<'tcx>( tcx: TyCtxt<'tcx>, mir: &Body<'tcx>, diff --git a/c2rust-analyze/src/rewrite/expr/subloc_info.rs b/c2rust-analyze/src/rewrite/expr/subloc_info.rs new file mode 100644 index 000000000..3e49cda84 --- /dev/null +++ b/c2rust-analyze/src/rewrite/expr/subloc_info.rs @@ -0,0 +1,731 @@ +use crate::context::{AnalysisCtxt, Assignment, DontRewriteFnReason, FlagSet, LTy, PermissionSet}; +use crate::last_use::{self, LastUse}; +use crate::panic_detail; +use crate::pointee_type::PointeeTypes; +use crate::pointer_id::{GlobalPointerTable, PointerId, PointerTable}; +use crate::rewrite; +use crate::type_desc::{self, Ownership, Quantity, TypeDesc}; +use crate::util::{self, ty_callee, Callee}; +use log::{error, trace}; +use rustc_ast::Mutability; +use rustc_middle::mir::{ + BasicBlock, Body, BorrowKind, Location, Operand, Place, PlaceElem, PlaceRef, Rvalue, Statement, + StatementKind, Terminator, TerminatorKind, +}; +use rustc_middle::ty::print::{FmtPrinter, PrettyPrinter, Print}; +use rustc_middle::ty::{AdtDef, Ty, TyCtxt, TyKind}; +use std::collections::HashMap; +use std::ops::Index; +use super::mir_op::{SubLoc, PlaceAccess, IsLocal}; + +#[derive(Clone, Debug)] +pub struct SublocInfo<'tcx> { + /// The type of this node in the original MIR. + pub old_ty: SublocType<'tcx>, + /// The type this node would have after rewriting, if the MIR was unchanged. For example, if + /// this node is an `Operand` that reads from a `Local`, this would give the rewritten type of + /// that `Local`. + pub new_ty: SublocType<'tcx>, + /// The type required by the surrounding context. For example, in `StatementKind::Assign`, the + /// `expect_ty` fields of the `Place` and `Rvalue` will match, reflecting the fact that the LHS + /// and RHS must have the same type. If `new_ty != expect_ty`, then a cast must be inserted at + /// this node. + /// + /// This is set to `new_ty` by the initial pass, but in a later pass may be updated to fix type + /// errors that would be introduced by naïve rewriting. + pub expect_ty: SublocType<'tcx>, + /// If set, casts applied to this subloc can move the value if needed. Otherwise, casts may + /// only borrow the value. + pub can_move: bool, + /// If set, casts applied to this subloc can borrow the value mutably if needed. Otherwise, + /// casts may only borrow the value immutably (or move it, if `can_move` is set). + pub can_mutate: bool, + /// Indicates the type of access the surrounding context will perform on this subloc. This is + /// mainly relevant for `Place`s. + pub access: PlaceAccess, +} + +#[derive(Clone, Copy, Debug)] +pub enum SublocType<'tcx> { + Ptr(TypeDesc<'tcx>), + Other(Ty<'tcx>), +} + + +struct CollectInfoVisitor<'a, 'tcx> { + acx: &'a AnalysisCtxt<'a, 'tcx>, + perms: &'a GlobalPointerTable, + flags: &'a GlobalPointerTable, + pointee_types: PointerTable<'a, PointeeTypes<'tcx>>, + last_use: &'a LastUse, + mir: &'a Body<'tcx>, + loc: Location, + sub_loc: Vec, + errors: DontRewriteFnReason, + info_map: HashMap<(Location, Vec), SublocInfo<'tcx>>, +} + +impl<'a, 'tcx> CollectInfoVisitor<'a, 'tcx> { + pub fn new( + acx: &'a AnalysisCtxt<'a, 'tcx>, + asn: &'a Assignment, + pointee_types: PointerTable<'a, PointeeTypes<'tcx>>, + last_use: &'a LastUse, + mir: &'a Body<'tcx>, + ) -> CollectInfoVisitor<'a, 'tcx> { + let perms = asn.perms(); + let flags = asn.flags(); + CollectInfoVisitor { + acx, + perms, + flags, + pointee_types, + last_use, + mir, + loc: Location { + block: BasicBlock::from_usize(0), + statement_index: 0, + }, + sub_loc: Vec::new(), + errors: DontRewriteFnReason::empty(), + + info_map: HashMap::new(), + } + } + + fn err(&mut self, reason: DontRewriteFnReason) { + self.errors.insert(reason); + } + + fn enter R, R>(&mut self, sub: SubLoc, f: F) -> R { + self.sub_loc.push(sub); + let r = f(self); + self.sub_loc.pop(); + r + } + + fn enter_dest R, R>(&mut self, f: F) -> R { + self.enter(SubLoc::Dest, f) + } + + fn enter_rvalue R, R>(&mut self, f: F) -> R { + self.enter(SubLoc::Rvalue, f) + } + + fn enter_call_arg R, R>(&mut self, i: usize, f: F) -> R { + self.enter(SubLoc::CallArg(i), f) + } + + fn enter_rvalue_operand R, R>(&mut self, i: usize, f: F) -> R { + self.enter(SubLoc::RvalueOperand(i), f) + } + + fn enter_rvalue_place R, R>(&mut self, i: usize, f: F) -> R { + self.enter(SubLoc::RvaluePlace(i), f) + } + + fn enter_operand_place R, R>(&mut self, f: F) -> R { + self.enter(SubLoc::OperandPlace, f) + } + + fn enter_place_deref_pointer R, R>(&mut self, f: F) -> R { + self.enter(SubLoc::PlaceDerefPointer, f) + } + + fn enter_place_field_base R, R>(&mut self, f: F) -> R { + self.enter(SubLoc::PlaceFieldBase, f) + } + + fn enter_place_index_array R, R>(&mut self, f: F) -> R { + self.enter(SubLoc::PlaceIndexArray, f) + } + + + fn lty_to_subloc_types(&self, lty: LTy<'tcx>) -> (SublocType<'tcx>, SublocType<'tcx>) { + let tcx = self.acx.tcx(); + let ty = lty.ty; + let ptr = lty.label; + + if ptr.is_none() { + // Non-pointer case. + return (SublocType::Other(lty.ty), SublocType::Other(lty.ty)); + } + + let old_pointee_ty = match *lty.ty.kind() { + TyKind::Ref(_, ty, _) => ty, + TyKind::RawPtr(mt) => mt.ty, + TyKind::Adt(adt_def, substs) if adt_def.is_box() => substs.type_at(0), + _ => unreachable!("type {lty:?} has PointerId but has non-pointer TyKind?"), + }; + let old_ptr_desc = type_desc::unpack_pointer_type(tcx, lty.ty, old_pointee_ty); + let old_desc = old_ptr_desc.to_type_desc(old_pointee_ty); + let new_desc = type_desc::perms_to_desc_with_pointee( + tcx, + old_pointee_ty, + lty.ty, + self.perms[ptr], + self.flags[ptr], + ); + // FIXME: new_desc needs to reflect pointee_type analysis results + (SublocType::Ptr(old_desc), SublocType::Ptr(new_desc)) + } + + fn emit_info(&mut self, info: SublocInfo<'tcx>) { + let key = (self.loc, self.sub_loc.clone()); + let old = self.info_map.insert(key, info); + assert!(old.is_none(), "duplicate entry for {:?} {:?}", self.loc, self.sub_loc); + } + + fn emit( + &mut self, + lty: LTy<'tcx>, + can_move: bool, + can_mutate: bool, + access: PlaceAccess, + ) { + self.emit_adjust(lty, can_move, can_mutate, access, |x| x) + } + + fn emit_adjust( + &mut self, + lty: LTy<'tcx>, + can_move: bool, + can_mutate: bool, + access: PlaceAccess, + adjust_new_ty: impl FnOnce(SublocType<'tcx>) -> SublocType<'tcx>, + ) { + let (old_ty, new_ty) = self.lty_to_subloc_types(lty); + let new_ty = adjust_new_ty(new_ty); + self.emit_info(SublocInfo { + old_ty, + new_ty, + expect_ty: new_ty, + can_move, + can_mutate, + access, + }); + } + + /// Helper for temporaries / rvalues. These can always be moved and mutated, since they are + /// only temporary. + fn emit_temp(&mut self, lty: LTy<'tcx>) { + self.emit_temp_adjust(lty, |x| x) + } + + fn emit_temp_adjust( + &mut self, + lty: LTy<'tcx>, + adjust_new_ty: impl FnOnce(SublocType<'tcx>) -> SublocType<'tcx>, + ) { + self.emit_adjust(lty, true, true, PlaceAccess::Move, adjust_new_ty) + } + + + /// Get the pointee type of `lty`. Returns the inferred pointee type from `self.pointee_types` + /// if one is available, or the pointee type as represented in `lty` itself otherwise. Returns + /// `None` if `lty` is not a `RawPtr` or `Ref` type. + /// + /// TODO: This does not yet have any pointer-to-pointer support. For example, if `lty` is + /// `*mut *mut c_void` where the inner pointer is known to point to `u8`, this method will + /// still return `*mut c_void` instead of `*mut u8`. + fn pointee_lty(&self, lty: LTy<'tcx>) -> Option> { + if !matches!(lty.kind(), TyKind::Ref(..) | TyKind::RawPtr(..)) { + return None; + } + debug_assert_eq!(lty.args.len(), 1); + let ptr = lty.label; + if !ptr.is_none() { + if let Some(pointee_lty) = self.pointee_types[ptr].get_sole_lty() { + return Some(pointee_lty); + } + } + Some(lty.args[0]) + } + + fn is_nullable(&self, ptr: PointerId) -> bool { + !ptr.is_none() + && !self.perms[ptr].contains(PermissionSet::NON_NULL) + && !self.flags[ptr].contains(FlagSet::FIXED) + } + + fn is_dyn_owned(&self, lty: LTy) -> bool { + if !matches!(lty.kind(), TyKind::Ref(..) | TyKind::RawPtr(..)) { + return false; + } + if lty.label.is_none() { + return false; + } + let perms = self.perms[lty.label]; + let flags = self.flags[lty.label]; + if flags.contains(FlagSet::FIXED) { + return false; + } + let desc = type_desc::perms_to_desc(lty.ty, perms, flags); + desc.dyn_owned + } + + /// Check whether the `Place` identified by `which` within the current statement or terminator + /// is the last use of a local. + fn is_last_use(&self, which: last_use::WhichPlace) -> bool { + self.last_use.is_last_use(self.loc, which) + } + + fn current_sub_loc_as_which_place(&self) -> Option { + // This logic is a bit imprecise, but should be correct in the cases where we actually call + // it (namely, when the `Place`/`Rvalue` is simply a `Local`). + let mut which = None; + for sl in &self.sub_loc { + match *sl { + SubLoc::Dest => { which = Some(last_use::WhichPlace::Lvalue); }, + SubLoc::Rvalue => { + which = Some(last_use::WhichPlace::Operand(0)); + }, + SubLoc::CallArg(i) => { + // In a call, `WhichPlace::Operand(0)` refers to the callee function. + which = Some(last_use::WhichPlace::Operand(i + 1)); + }, + SubLoc::RvalueOperand(i) => { + which = Some(last_use::WhichPlace::Operand(i)); + }, + SubLoc::RvaluePlace(_) => {}, + SubLoc::OperandPlace => {}, + SubLoc::PlaceDerefPointer => {}, + SubLoc::PlaceFieldBase => {}, + SubLoc::PlaceIndexArray => {}, + } + } + which + } + + /// Like `is_last_use`, but infers `WhichPlace` based on `self.sub_loc`. + fn current_sub_loc_is_last_use(&self) -> bool { + if let Some(which) = self.current_sub_loc_as_which_place() { + self.is_last_use(which) + } else { + false + } + } + + fn visit_statement(&mut self, stmt: &Statement<'tcx>, loc: Location) { + let _g = panic_detail::set_current_span(stmt.source_info.span); + eprintln!( + "subloc_info::visit_statement: {:?} @ {:?}: {:?}", + loc, stmt.source_info.span, stmt + ); + self.loc = loc; + debug_assert!(self.sub_loc.is_empty()); + + match stmt.kind { + StatementKind::Assign(ref x) => { + let (pl, ref rv) = **x; + + self.enter_dest(|v| { + v.visit_place(pl, PlaceAccess::Mut); + }); + + self.enter_rvalue(|v| { + v.visit_rvalue(rv); + }); + } + StatementKind::FakeRead(..) => {} + StatementKind::SetDiscriminant { .. } => todo!("statement {:?}", stmt), + StatementKind::Deinit(..) => {} + StatementKind::StorageLive(..) => {} + StatementKind::StorageDead(..) => {} + StatementKind::Retag(..) => {} + StatementKind::AscribeUserType(..) => {} + StatementKind::Coverage(..) => {} + StatementKind::CopyNonOverlapping(..) => todo!("statement {:?}", stmt), + StatementKind::Nop => {} + } + } + + fn visit_terminator(&mut self, term: &Terminator<'tcx>, loc: Location) { + let tcx = self.acx.tcx(); + let _g = panic_detail::set_current_span(term.source_info.span); + self.loc = loc; + debug_assert!(self.sub_loc.is_empty()); + + match term.kind { + TerminatorKind::Goto { .. } => {} + TerminatorKind::SwitchInt { .. } => {} + TerminatorKind::Resume => {} + TerminatorKind::Abort => {} + TerminatorKind::Return => {} + TerminatorKind::Unreachable => {} + TerminatorKind::Drop { .. } => {} + TerminatorKind::DropAndReplace { .. } => {} + TerminatorKind::Call { + ref func, + ref args, + destination, + target: _, + .. + } => { + let func_ty = func.ty(self.mir, tcx); + let pl_ty = self.acx.type_of(destination); + + self.enter_rvalue(|v| { + for (i, arg) in args.iter().enumerate() { + v.enter_call_arg(i, |v| v.visit_operand(arg)); + } + }); + + // Special cases for particular functions. + match ty_callee(tcx, func_ty) { + // Normal call to a local function. + Callee::LocalDef { def_id, substs: _ } => { + if let Some(lsig) = self.acx.gacx.fn_sigs.get(&def_id) { + self.enter_rvalue(|v| v.emit_temp(lsig.output)); + } + } + + Callee::PtrOffset { .. } => { + // Set the call result type to match the destination type as closely as we + // can. The argument will be downcast to this type, then offset. However, + // our `PtrOffset` rewrite can't output `Single` or `Array` directly, so in + // those cases, the rewrite will operate on `Slice` and downcast afterward. + self.enter_rvalue(|v| v.emit_temp_adjust(pl_ty, |mut slty| { + if let SublocType::Ptr(ref mut desc) = slty { + match desc.qty { + Quantity::Single | Quantity::Array => { + desc.qty = Quantity::Slice; + }, + Quantity::Slice | Quantity::OffsetPtr => {}, + } + } + slty + })); + } + + Callee::SliceAsPtr { .. } => { + // Set result type equal to the argument type. The rewriter will insert an + // appropriate cast to convert to the destination type. + assert_eq!(args.len(), 1); + let arg_lty = self.acx.type_of(&args[0]); + self.enter_rvalue(|v| v.emit_temp(arg_lty)) + } + + Callee::Memcpy | Callee::Memset => { + // Match the type of the first (`dest`) argument exactly. The rewrite is + // responsible for preserving any combination of `Ownership` and + // `Quantity`. + assert_eq!(args.len(), 3); + let arg_lty = self.acx.type_of(&args[0]); + self.enter_rvalue(|v| v.emit_temp(arg_lty)); + } + + Callee::IsNull => { + // Result type is simply `bool`, which should be the same as the dest type. + self.enter_rvalue(|v| v.emit_temp(pl_ty)); + } + + Callee::Null { .. } => { + // Match the destination type, but `null()` always outputs a nullable + // pointer. + self.enter_rvalue(|v| v.emit_temp_adjust(pl_ty, |mut slty| { + if let SublocType::Ptr(ref mut desc) = slty { + desc.option = true; + } + slty + })); + } + + Callee::Malloc | Callee::Calloc | Callee::Realloc => { + // Match the destination type, but always produce a non-optional `Box`. + self.enter_rvalue(|v| v.emit_temp_adjust(pl_ty, |mut slty| { + if let SublocType::Ptr(ref mut desc) = slty { + desc.option = false; + desc.own = Ownership::Box; + } + slty + })); + } + + Callee::Free => { + // Result type is simply `()`, which should be the same as the dest type. + self.enter_rvalue(|v| v.emit_temp(pl_ty)); + } + + _ => {} + } + } + TerminatorKind::Assert { .. } => {} + TerminatorKind::Yield { .. } => {} + TerminatorKind::GeneratorDrop => {} + TerminatorKind::FalseEdge { .. } => {} + TerminatorKind::FalseUnwind { .. } => {} + TerminatorKind::InlineAsm { .. } => todo!("terminator {:?}", term), + } + } + + fn visit_rvalue(&mut self, rv: &Rvalue<'tcx>) { + eprintln!("subloc_info::visit_rvalue: {:?}", rv); + + let rv_lty = self.acx.type_of_rvalue(rv, self.loc); + let can_move = false; // TODO + let can_mutate = false; // TODO + self.emit(rv_lty, can_move, can_mutate, PlaceAccess::Move); + + match *rv { + Rvalue::Use(ref op) => { + self.enter_rvalue_operand(0, |v| v.visit_operand(op)); + } + Rvalue::Repeat(ref op, _) => { + self.enter_rvalue_operand(0, |v| v.visit_operand(op)); + } + Rvalue::Ref(_rg, kind, pl) => { + let mutbl = match kind { + BorrowKind::Mut { .. } => true, + BorrowKind::Shared | BorrowKind::Shallow | BorrowKind::Unique => false, + }; + self.enter_rvalue_place(0, |v| v.visit_place(pl, PlaceAccess::from_bool(mutbl))); + } + Rvalue::ThreadLocalRef(_def_id) => { + todo!("Rvalue::ThreadLocalRef") + } + Rvalue::AddressOf(mutbl, pl) => { + self.enter_rvalue_place(0, |v| v.visit_place(pl, PlaceAccess::from_mutbl(mutbl))); + } + Rvalue::Len(pl) => { + self.enter_rvalue_place(0, |v| v.visit_place(pl, PlaceAccess::Imm)); + } + Rvalue::Cast(_kind, ref op, ty) => { + self.enter_rvalue_operand(0, |v| v.visit_operand(op)); + } + Rvalue::BinaryOp(_bop, ref ops) => { + self.enter_rvalue_operand(0, |v| v.visit_operand(&ops.0)); + self.enter_rvalue_operand(1, |v| v.visit_operand(&ops.1)); + } + Rvalue::CheckedBinaryOp(_bop, ref ops) => { + self.enter_rvalue_operand(0, |v| v.visit_operand(&ops.0)); + self.enter_rvalue_operand(1, |v| v.visit_operand(&ops.1)); + } + Rvalue::NullaryOp(..) => {} + Rvalue::UnaryOp(_uop, ref op) => { + self.enter_rvalue_operand(0, |v| v.visit_operand(op)); + } + Rvalue::Discriminant(pl) => { + self.enter_rvalue_place(0, |v| v.visit_place(pl, PlaceAccess::Imm)); + } + Rvalue::Aggregate(ref _kind, ref ops) => { + for (i, op) in ops.iter().enumerate() { + self.enter_rvalue_operand(i, |v| v.visit_operand(op)); + } + } + Rvalue::ShallowInitBox(ref op, _ty) => { + self.enter_rvalue_operand(0, |v| v.visit_operand(op)); + } + Rvalue::CopyForDeref(pl) => { + self.enter_rvalue_place(0, |v| v.visit_place(pl, PlaceAccess::Imm)); + } + } + } + + fn visit_operand(&mut self, op: &Operand<'tcx>) { + match *op { + Operand::Copy(pl) | Operand::Move(pl) => { + let pl_lty = self.acx.type_of(pl); + // Moving out of a `DynOwned` pointer requires `Mut` access rather than `Move`. + // TODO: this should probably check `desc.dyn_owned` rather than perms directly + let access = if !pl_lty.label.is_none() && self.perms[pl_lty.label].contains(PermissionSet::FREE) { + PlaceAccess::Mut + } else { + PlaceAccess::Move + }; + self.enter_operand_place(|v| v.visit_place(pl, access)); + } + Operand::Constant(..) => {} + } + } + + /* + /// Like [`Self::visit_operand`], but takes an expected `TypeDesc` instead of an expected `LTy`. + fn visit_operand_desc(&mut self, op: &Operand<'tcx>) { + match *op { + Operand::Copy(pl) | Operand::Move(pl) => { + let pl_lty = self.acx.type_of(pl); + // Moving out of a `DynOwned` pointer requires `Mut` access rather than `Move`. + // TODO: this should probably check `desc.dyn_owned` rather than perms directly + let access = if !pl_lty.label.is_none() && self.perms[pl_lty.label].contains(PermissionSet::FREE) { + PlaceAccess::Mut + } else { + PlaceAccess::Move + }; + self.enter_operand_place(|v| v.visit_place(pl, access, RequireSinglePointer::Yes)); + + if !pl_lty.label.is_none() { + self.emit_cast_lty_desc(pl_lty, expect_desc); + } + } + Operand::Constant(..) => {} + } + } + */ + + fn visit_place( + &mut self, + pl: Place<'tcx>, + access: PlaceAccess, + ) { + let mut ltys = Vec::with_capacity(1 + pl.projection.len()); + ltys.push(self.acx.type_of(pl.local)); + for proj in pl.projection { + let prev_lty = ltys.last().copied().unwrap(); + ltys.push(self.acx.projection_lty(prev_lty, &proj)); + } + self.visit_place_ref(pl.as_ref(), <ys, access); + } + + /// Generate rewrites for a `Place` represented as a `PlaceRef`. `proj_ltys` gives the `LTy` + /// for the `Local` and after each projection. `access` describes how the place is being used: + /// immutably, mutably, or being moved out of. + fn visit_place_ref( + &mut self, + pl: PlaceRef<'tcx>, + proj_ltys: &[LTy<'tcx>], + access: PlaceAccess, + ) { + let (&last_proj, rest) = match pl.projection.split_last() { + Some(x) => x, + None => { + // `pl` is just a local with no projections. + let pl_lty = proj_ltys[0]; + let can_move = self.current_sub_loc_is_last_use(); + // We assume all locals are mutable, or at least can be made mutable if needed. + let can_mutate = true; + self.emit(pl_lty, can_move, can_mutate, access); + return; + }, + }; + + // TODO: downgrade Move to Imm if the new type is Copy + + let proj_lty = proj_ltys[pl.projection.len()]; + let can_move = false; // TODO + let can_mutate = false; // TODO + self.emit(proj_lty, can_move, can_mutate, access); + + let base_pl = PlaceRef { + local: pl.local, + projection: rest, + }; + match last_proj { + PlaceElem::Deref => { + let cast_can_move = base_pl.is_local() && self.current_sub_loc_is_last_use(); + self.enter_place_deref_pointer(|v| { + v.visit_place_ref(base_pl, proj_ltys, access); + }); + } + PlaceElem::Field(_idx, _ty) => { + self.enter_place_field_base(|v| v.visit_place_ref(base_pl, proj_ltys, access)); + } + PlaceElem::Index(_) | PlaceElem::ConstantIndex { .. } | PlaceElem::Subslice { .. } => { + self.enter_place_index_array(|v| v.visit_place_ref(base_pl, proj_ltys, access)); + } + PlaceElem::Downcast(_, _) => todo!("PlaceElem::Downcast"), + } + } + + /// Visit a `Callee::PtrOffset` call, and emit the `SublocInfo` for the call/RHS. + fn visit_ptr_offset(&mut self, op: &Operand<'tcx>, result_ty: LTy<'tcx>) { + todo!("visit_ptr_offset") + /* + // Compute the expected type for the argument, and emit a cast if needed. + let result_ptr = result_ty.label; + let result_desc = + type_desc::perms_to_desc(result_ty.ty, self.perms[result_ptr], self.flags[result_ptr]); + + let arg_expect_desc = TypeDesc { + own: result_desc.own, + qty: match result_desc.qty { + Quantity::Single => Quantity::Slice, + Quantity::Slice => Quantity::Slice, + Quantity::OffsetPtr => Quantity::OffsetPtr, + Quantity::Array => unreachable!("perms_to_desc should not return Quantity::Array"), + }, + dyn_owned: result_desc.dyn_owned, + option: result_desc.option, + pointee_ty: result_desc.pointee_ty, + }; + + self.enter_rvalue(|v| { + v.enter_call_arg(0, |v| v.visit_operand_desc(op, arg_expect_desc)); + + // Emit `OffsetSlice` for the offset itself. + let mutbl = matches!(result_desc.own, Ownership::Mut); + if !result_desc.option { + v.emit(RewriteKind::OffsetSlice { mutbl }); + } else { + v.emit(RewriteKind::OptionMapOffsetSlice { mutbl }); + } + + // The `OffsetSlice` operation returns something of the same type as its input. + // Afterward, we must cast the result to the `result_ty`/`result_desc`. + v.emit_cast_desc_desc(arg_expect_desc, result_desc); + }); + */ + } + + fn visit_slice_as_ptr(&mut self, elem_ty: Ty<'tcx>, op: &Operand<'tcx>, result_lty: LTy<'tcx>) { + todo!("visit_slice_as_ptr") + /* + let op_lty = self.acx.type_of(op); + let op_ptr = op_lty.label; + let result_ptr = result_lty.label; + + let op_desc = type_desc::perms_to_desc_with_pointee( + self.acx.tcx(), + elem_ty, + op_lty.ty, + self.perms[op_ptr], + self.flags[op_ptr], + ); + + let result_desc = type_desc::perms_to_desc_with_pointee( + self.acx.tcx(), + elem_ty, + result_lty.ty, + self.perms[result_ptr], + self.flags[result_ptr], + ); + + self.enter_rvalue(|v| { + // Generate a cast of our own, replacing the `as_ptr` call. + // TODO: leave the `as_ptr` in place if we can't produce a working cast + v.emit(RewriteKind::RemoveAsPtr); + v.emit_cast_desc_desc(op_desc, result_desc); + }); + */ + } +} + +pub fn collect_subloc_info<'tcx>( + acx: &AnalysisCtxt<'_, 'tcx>, + asn: &Assignment, + pointee_types: PointerTable>, + last_use: &LastUse, + mir: &Body<'tcx>, +) -> HashMap<(Location, Vec), SublocInfo<'tcx>> { + let mut v = CollectInfoVisitor::new(acx, asn, pointee_types, last_use, mir); + + for (bb_id, bb) in mir.basic_blocks().iter_enumerated() { + for (i, stmt) in bb.statements.iter().enumerate() { + let loc = Location { + block: bb_id, + statement_index: i, + }; + v.visit_statement(stmt, loc); + } + + if let Some(ref term) = bb.terminator { + let loc = Location { + block: bb_id, + statement_index: bb.statements.len(), + }; + v.visit_terminator(term, loc); + } + } + + v.info_map +} From 9b331d9fe7448e3c82d7e04264a531b03e583c36 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Wed, 27 Nov 2024 13:21:50 -0800 Subject: [PATCH 2/9] analyze: rewrite: various cleanup in subloc_info --- c2rust-analyze/src/rewrite/expr/mir_op.rs | 2 +- .../src/rewrite/expr/subloc_info.rs | 141 +----------------- 2 files changed, 8 insertions(+), 135 deletions(-) diff --git a/c2rust-analyze/src/rewrite/expr/mir_op.rs b/c2rust-analyze/src/rewrite/expr/mir_op.rs index 7c79b724d..a576430ea 100644 --- a/c2rust-analyze/src/rewrite/expr/mir_op.rs +++ b/c2rust-analyze/src/rewrite/expr/mir_op.rs @@ -2028,7 +2028,7 @@ where } } -pub trait IsLocal { +trait IsLocal { fn is_local(&self) -> bool; } diff --git a/c2rust-analyze/src/rewrite/expr/subloc_info.rs b/c2rust-analyze/src/rewrite/expr/subloc_info.rs index 3e49cda84..d368f458c 100644 --- a/c2rust-analyze/src/rewrite/expr/subloc_info.rs +++ b/c2rust-analyze/src/rewrite/expr/subloc_info.rs @@ -1,22 +1,17 @@ -use crate::context::{AnalysisCtxt, Assignment, DontRewriteFnReason, FlagSet, LTy, PermissionSet}; +use crate::context::{AnalysisCtxt, Assignment, FlagSet, LTy, PermissionSet}; use crate::last_use::{self, LastUse}; use crate::panic_detail; use crate::pointee_type::PointeeTypes; -use crate::pointer_id::{GlobalPointerTable, PointerId, PointerTable}; -use crate::rewrite; +use crate::pointer_id::{GlobalPointerTable, PointerTable}; use crate::type_desc::{self, Ownership, Quantity, TypeDesc}; -use crate::util::{self, ty_callee, Callee}; -use log::{error, trace}; -use rustc_ast::Mutability; +use crate::util::{ty_callee, Callee}; use rustc_middle::mir::{ BasicBlock, Body, BorrowKind, Location, Operand, Place, PlaceElem, PlaceRef, Rvalue, Statement, StatementKind, Terminator, TerminatorKind, }; -use rustc_middle::ty::print::{FmtPrinter, PrettyPrinter, Print}; -use rustc_middle::ty::{AdtDef, Ty, TyCtxt, TyKind}; +use rustc_middle::ty::{Ty, TyKind}; use std::collections::HashMap; -use std::ops::Index; -use super::mir_op::{SubLoc, PlaceAccess, IsLocal}; +use super::mir_op::{SubLoc, PlaceAccess}; #[derive(Clone, Debug)] pub struct SublocInfo<'tcx> { @@ -61,7 +56,6 @@ struct CollectInfoVisitor<'a, 'tcx> { mir: &'a Body<'tcx>, loc: Location, sub_loc: Vec, - errors: DontRewriteFnReason, info_map: HashMap<(Location, Vec), SublocInfo<'tcx>>, } @@ -87,16 +81,11 @@ impl<'a, 'tcx> CollectInfoVisitor<'a, 'tcx> { statement_index: 0, }, sub_loc: Vec::new(), - errors: DontRewriteFnReason::empty(), info_map: HashMap::new(), } } - fn err(&mut self, reason: DontRewriteFnReason) { - self.errors.insert(reason); - } - fn enter R, R>(&mut self, sub: SubLoc, f: F) -> R { self.sub_loc.push(sub); let r = f(self); @@ -143,7 +132,6 @@ impl<'a, 'tcx> CollectInfoVisitor<'a, 'tcx> { fn lty_to_subloc_types(&self, lty: LTy<'tcx>) -> (SublocType<'tcx>, SublocType<'tcx>) { let tcx = self.acx.tcx(); - let ty = lty.ty; let ptr = lty.label; if ptr.is_none() { @@ -167,6 +155,7 @@ impl<'a, 'tcx> CollectInfoVisitor<'a, 'tcx> { self.flags[ptr], ); // FIXME: new_desc needs to reflect pointee_type analysis results + // FIXME: new_desc should respect the FIXED flag (SublocType::Ptr(old_desc), SublocType::Ptr(new_desc)) } @@ -221,49 +210,6 @@ impl<'a, 'tcx> CollectInfoVisitor<'a, 'tcx> { } - /// Get the pointee type of `lty`. Returns the inferred pointee type from `self.pointee_types` - /// if one is available, or the pointee type as represented in `lty` itself otherwise. Returns - /// `None` if `lty` is not a `RawPtr` or `Ref` type. - /// - /// TODO: This does not yet have any pointer-to-pointer support. For example, if `lty` is - /// `*mut *mut c_void` where the inner pointer is known to point to `u8`, this method will - /// still return `*mut c_void` instead of `*mut u8`. - fn pointee_lty(&self, lty: LTy<'tcx>) -> Option> { - if !matches!(lty.kind(), TyKind::Ref(..) | TyKind::RawPtr(..)) { - return None; - } - debug_assert_eq!(lty.args.len(), 1); - let ptr = lty.label; - if !ptr.is_none() { - if let Some(pointee_lty) = self.pointee_types[ptr].get_sole_lty() { - return Some(pointee_lty); - } - } - Some(lty.args[0]) - } - - fn is_nullable(&self, ptr: PointerId) -> bool { - !ptr.is_none() - && !self.perms[ptr].contains(PermissionSet::NON_NULL) - && !self.flags[ptr].contains(FlagSet::FIXED) - } - - fn is_dyn_owned(&self, lty: LTy) -> bool { - if !matches!(lty.kind(), TyKind::Ref(..) | TyKind::RawPtr(..)) { - return false; - } - if lty.label.is_none() { - return false; - } - let perms = self.perms[lty.label]; - let flags = self.flags[lty.label]; - if flags.contains(FlagSet::FIXED) { - return false; - } - let desc = type_desc::perms_to_desc(lty.ty, perms, flags); - desc.dyn_owned - } - /// Check whether the `Place` identified by `which` within the current statement or terminator /// is the last use of a local. fn is_last_use(&self, which: last_use::WhichPlace) -> bool { @@ -490,7 +436,7 @@ impl<'a, 'tcx> CollectInfoVisitor<'a, 'tcx> { Rvalue::Len(pl) => { self.enter_rvalue_place(0, |v| v.visit_place(pl, PlaceAccess::Imm)); } - Rvalue::Cast(_kind, ref op, ty) => { + Rvalue::Cast(_kind, ref op, _ty) => { self.enter_rvalue_operand(0, |v| v.visit_operand(op)); } Rvalue::BinaryOp(_bop, ref ops) => { @@ -612,7 +558,6 @@ impl<'a, 'tcx> CollectInfoVisitor<'a, 'tcx> { }; match last_proj { PlaceElem::Deref => { - let cast_can_move = base_pl.is_local() && self.current_sub_loc_is_last_use(); self.enter_place_deref_pointer(|v| { v.visit_place_ref(base_pl, proj_ltys, access); }); @@ -626,78 +571,6 @@ impl<'a, 'tcx> CollectInfoVisitor<'a, 'tcx> { PlaceElem::Downcast(_, _) => todo!("PlaceElem::Downcast"), } } - - /// Visit a `Callee::PtrOffset` call, and emit the `SublocInfo` for the call/RHS. - fn visit_ptr_offset(&mut self, op: &Operand<'tcx>, result_ty: LTy<'tcx>) { - todo!("visit_ptr_offset") - /* - // Compute the expected type for the argument, and emit a cast if needed. - let result_ptr = result_ty.label; - let result_desc = - type_desc::perms_to_desc(result_ty.ty, self.perms[result_ptr], self.flags[result_ptr]); - - let arg_expect_desc = TypeDesc { - own: result_desc.own, - qty: match result_desc.qty { - Quantity::Single => Quantity::Slice, - Quantity::Slice => Quantity::Slice, - Quantity::OffsetPtr => Quantity::OffsetPtr, - Quantity::Array => unreachable!("perms_to_desc should not return Quantity::Array"), - }, - dyn_owned: result_desc.dyn_owned, - option: result_desc.option, - pointee_ty: result_desc.pointee_ty, - }; - - self.enter_rvalue(|v| { - v.enter_call_arg(0, |v| v.visit_operand_desc(op, arg_expect_desc)); - - // Emit `OffsetSlice` for the offset itself. - let mutbl = matches!(result_desc.own, Ownership::Mut); - if !result_desc.option { - v.emit(RewriteKind::OffsetSlice { mutbl }); - } else { - v.emit(RewriteKind::OptionMapOffsetSlice { mutbl }); - } - - // The `OffsetSlice` operation returns something of the same type as its input. - // Afterward, we must cast the result to the `result_ty`/`result_desc`. - v.emit_cast_desc_desc(arg_expect_desc, result_desc); - }); - */ - } - - fn visit_slice_as_ptr(&mut self, elem_ty: Ty<'tcx>, op: &Operand<'tcx>, result_lty: LTy<'tcx>) { - todo!("visit_slice_as_ptr") - /* - let op_lty = self.acx.type_of(op); - let op_ptr = op_lty.label; - let result_ptr = result_lty.label; - - let op_desc = type_desc::perms_to_desc_with_pointee( - self.acx.tcx(), - elem_ty, - op_lty.ty, - self.perms[op_ptr], - self.flags[op_ptr], - ); - - let result_desc = type_desc::perms_to_desc_with_pointee( - self.acx.tcx(), - elem_ty, - result_lty.ty, - self.perms[result_ptr], - self.flags[result_ptr], - ); - - self.enter_rvalue(|v| { - // Generate a cast of our own, replacing the `as_ptr` call. - // TODO: leave the `as_ptr` in place if we can't produce a working cast - v.emit(RewriteKind::RemoveAsPtr); - v.emit_cast_desc_desc(op_desc, result_desc); - }); - */ - } } pub fn collect_subloc_info<'tcx>( From d5603b602dc9cfe866bd3c4ddfdd336b72063dab Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Wed, 27 Nov 2024 16:08:50 -0800 Subject: [PATCH 3/9] analyze: revert some bad clippy fixes --- c2rust-analyze/src/type_desc.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/c2rust-analyze/src/type_desc.rs b/c2rust-analyze/src/type_desc.rs index e35943a20..4e58723d7 100644 --- a/c2rust-analyze/src/type_desc.rs +++ b/c2rust-analyze/src/type_desc.rs @@ -138,7 +138,7 @@ fn perms_to_ptr_desc(perms: PermissionSet, flags: FlagSet) -> PtrDesc { /// Obtain the `TypeDesc` for a pointer. `ptr_ty` should be the `Ty` of the pointer, and `perms` /// and `flags` should be taken from its outermost `PointerId`. -pub fn perms_to_desc(ptr_ty: Ty, perms: PermissionSet, flags: FlagSet) -> TypeDesc { +pub fn perms_to_desc<'tcx>(ptr_ty: Ty<'tcx>, perms: PermissionSet, flags: FlagSet) -> TypeDesc<'tcx> { // The FIXED case should be handled by calling `perm_to_desc_with_pointee` instead. assert!( !flags.contains(FlagSet::FIXED), @@ -160,7 +160,7 @@ pub fn perms_to_desc(ptr_ty: Ty, perms: PermissionSet, flags: FlagSet) -> TypeDe /// Obtain the `TypeDesc` for a pointer to a local. `local_ty` should be the `Ty` of the local /// itself, and `perms` and `flags` should be taken from its `addr_of_local` `PointerId`. -pub fn local_perms_to_desc(local_ty: Ty, perms: PermissionSet, flags: FlagSet) -> TypeDesc { +pub fn local_perms_to_desc<'tcx>(local_ty: Ty<'tcx>, perms: PermissionSet, flags: FlagSet) -> TypeDesc<'tcx> { let ptr_desc = perms_to_ptr_desc(perms, flags); let pointee_ty = local_ty; ptr_desc.to_type_desc(pointee_ty) From 60bc61bac44d2c3b635d7043b9364335231863e6 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Wed, 27 Nov 2024 17:01:34 -0800 Subject: [PATCH 4/9] analyze: subloc_info: add limited pointee support --- .../src/rewrite/expr/subloc_info.rs | 19 ++++++++++++++++--- c2rust-analyze/src/type_desc.rs | 8 +++++++- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/c2rust-analyze/src/rewrite/expr/subloc_info.rs b/c2rust-analyze/src/rewrite/expr/subloc_info.rs index d368f458c..9a182f6f7 100644 --- a/c2rust-analyze/src/rewrite/expr/subloc_info.rs +++ b/c2rust-analyze/src/rewrite/expr/subloc_info.rs @@ -42,6 +42,8 @@ pub struct SublocInfo<'tcx> { #[derive(Clone, Copy, Debug)] pub enum SublocType<'tcx> { + // TODO: modify to allow arbitrary nesting of `SublocType`s, maybe using `LabeledTy` or + // `rustc_type_ir`. Ptr(TypeDesc<'tcx>), Other(Ty<'tcx>), } @@ -131,6 +133,14 @@ impl<'a, 'tcx> CollectInfoVisitor<'a, 'tcx> { fn lty_to_subloc_types(&self, lty: LTy<'tcx>) -> (SublocType<'tcx>, SublocType<'tcx>) { + // TODO: Doing this correctly is potentially quite complex. This function needs to handle + // (1) FIXED, (2) `pointee_types`, (3) pointer-to-pointer, and (4) existing references in + // partially-safe code. Furthermore, the old and new `SublocType`s should be arranged to + // have the same or similar pointee types when possible. + // + // All `LTy` to `SublocType` conversions go through this function, so once it supports a + // feature, that feature should work throughout the visitor. + let tcx = self.acx.tcx(); let ptr = lty.label; @@ -147,15 +157,18 @@ impl<'a, 'tcx> CollectInfoVisitor<'a, 'tcx> { }; let old_ptr_desc = type_desc::unpack_pointer_type(tcx, lty.ty, old_pointee_ty); let old_desc = old_ptr_desc.to_type_desc(old_pointee_ty); - let new_desc = type_desc::perms_to_desc_with_pointee( + let mut new_desc = type_desc::perms_to_desc_with_pointee( tcx, old_pointee_ty, lty.ty, self.perms[ptr], self.flags[ptr], ); - // FIXME: new_desc needs to reflect pointee_type analysis results - // FIXME: new_desc should respect the FIXED flag + + if let Some(pointee_lty) = self.pointee_types[ptr].get_sole_lty() { + new_desc.pointee_ty = pointee_lty.ty; + } + (SublocType::Ptr(old_desc), SublocType::Ptr(new_desc)) } diff --git a/c2rust-analyze/src/type_desc.rs b/c2rust-analyze/src/type_desc.rs index 4e58723d7..5dcd99158 100644 --- a/c2rust-analyze/src/type_desc.rs +++ b/c2rust-analyze/src/type_desc.rs @@ -59,13 +59,19 @@ pub struct PtrDesc { impl<'tcx> From> for PtrDesc { fn from(x: TypeDesc<'tcx>) -> PtrDesc { + x.to_ptr_desc() + } +} + +impl TypeDesc<'_> { + pub fn to_ptr_desc(self) -> PtrDesc { let TypeDesc { own, qty, dyn_owned, option, pointee_ty: _, - } = x; + } = self; PtrDesc { own, qty, From cd84e878fe32bdd5ad509319f5816892b35d10a9 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Mon, 9 Dec 2024 14:54:12 -0800 Subject: [PATCH 5/9] analyze: subloc_info: factor out LTy to SublocType conversion --- .../src/rewrite/expr/subloc_info.rs | 95 +++++++++++-------- 1 file changed, 58 insertions(+), 37 deletions(-) diff --git a/c2rust-analyze/src/rewrite/expr/subloc_info.rs b/c2rust-analyze/src/rewrite/expr/subloc_info.rs index 9a182f6f7..4cbd709b5 100644 --- a/c2rust-analyze/src/rewrite/expr/subloc_info.rs +++ b/c2rust-analyze/src/rewrite/expr/subloc_info.rs @@ -9,7 +9,7 @@ use rustc_middle::mir::{ BasicBlock, Body, BorrowKind, Location, Operand, Place, PlaceElem, PlaceRef, Rvalue, Statement, StatementKind, Terminator, TerminatorKind, }; -use rustc_middle::ty::{Ty, TyKind}; +use rustc_middle::ty::{Ty, TyCtxt, TyKind}; use std::collections::HashMap; use super::mir_op::{SubLoc, PlaceAccess}; @@ -49,6 +49,56 @@ pub enum SublocType<'tcx> { } +struct TypeConversionContext<'a, 'tcx> { + tcx: TyCtxt<'tcx>, + perms: &'a GlobalPointerTable, + flags: &'a GlobalPointerTable, + pointee_types: PointerTable<'a, PointeeTypes<'tcx>>, +} + +impl<'a, 'tcx> TypeConversionContext<'a, 'tcx> { + fn lty_to_subloc_types(&self, lty: LTy<'tcx>) -> (SublocType<'tcx>, SublocType<'tcx>) { + // TODO: Doing this correctly is potentially quite complex. This function needs to handle + // (1) FIXED, (2) `pointee_types`, (3) pointer-to-pointer, and (4) existing references in + // partially-safe code. Furthermore, the old and new `SublocType`s should be arranged to + // have the same or similar pointee types when possible. + // + // All `LTy` to `SublocType` conversions go through this function, so once it supports a + // feature, that feature should work throughout the visitor. + + let tcx = self.tcx; + let ptr = lty.label; + + if ptr.is_none() { + // Non-pointer case. + return (SublocType::Other(lty.ty), SublocType::Other(lty.ty)); + } + + let old_pointee_ty = match *lty.ty.kind() { + TyKind::Ref(_, ty, _) => ty, + TyKind::RawPtr(mt) => mt.ty, + TyKind::Adt(adt_def, substs) if adt_def.is_box() => substs.type_at(0), + _ => unreachable!("type {lty:?} has PointerId but has non-pointer TyKind?"), + }; + let old_ptr_desc = type_desc::unpack_pointer_type(tcx, lty.ty, old_pointee_ty); + let old_desc = old_ptr_desc.to_type_desc(old_pointee_ty); + let mut new_desc = type_desc::perms_to_desc_with_pointee( + tcx, + old_pointee_ty, + lty.ty, + self.perms[ptr], + self.flags[ptr], + ); + + if let Some(pointee_lty) = self.pointee_types[ptr].get_sole_lty() { + new_desc.pointee_ty = pointee_lty.ty; + } + + (SublocType::Ptr(old_desc), SublocType::Ptr(new_desc)) + } +} + + struct CollectInfoVisitor<'a, 'tcx> { acx: &'a AnalysisCtxt<'a, 'tcx>, perms: &'a GlobalPointerTable, @@ -133,45 +183,16 @@ impl<'a, 'tcx> CollectInfoVisitor<'a, 'tcx> { fn lty_to_subloc_types(&self, lty: LTy<'tcx>) -> (SublocType<'tcx>, SublocType<'tcx>) { - // TODO: Doing this correctly is potentially quite complex. This function needs to handle - // (1) FIXED, (2) `pointee_types`, (3) pointer-to-pointer, and (4) existing references in - // partially-safe code. Furthermore, the old and new `SublocType`s should be arranged to - // have the same or similar pointee types when possible. - // - // All `LTy` to `SublocType` conversions go through this function, so once it supports a - // feature, that feature should work throughout the visitor. - - let tcx = self.acx.tcx(); - let ptr = lty.label; - - if ptr.is_none() { - // Non-pointer case. - return (SublocType::Other(lty.ty), SublocType::Other(lty.ty)); - } - - let old_pointee_ty = match *lty.ty.kind() { - TyKind::Ref(_, ty, _) => ty, - TyKind::RawPtr(mt) => mt.ty, - TyKind::Adt(adt_def, substs) if adt_def.is_box() => substs.type_at(0), - _ => unreachable!("type {lty:?} has PointerId but has non-pointer TyKind?"), + let conv_cx = TypeConversionContext { + tcx: self.acx.tcx(), + perms: self.perms, + flags: self.flags, + pointee_types: self.pointee_types, }; - let old_ptr_desc = type_desc::unpack_pointer_type(tcx, lty.ty, old_pointee_ty); - let old_desc = old_ptr_desc.to_type_desc(old_pointee_ty); - let mut new_desc = type_desc::perms_to_desc_with_pointee( - tcx, - old_pointee_ty, - lty.ty, - self.perms[ptr], - self.flags[ptr], - ); - - if let Some(pointee_lty) = self.pointee_types[ptr].get_sole_lty() { - new_desc.pointee_ty = pointee_lty.ty; - } - - (SublocType::Ptr(old_desc), SublocType::Ptr(new_desc)) + conv_cx.lty_to_subloc_types(lty) } + fn emit_info(&mut self, info: SublocInfo<'tcx>) { let key = (self.loc, self.sub_loc.clone()); let old = self.info_map.insert(key, info); From 7a61c656a89f64ea66ae46dd53f3edee8e2da375 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Tue, 10 Dec 2024 16:59:22 -0800 Subject: [PATCH 6/9] analyze: subloc_info: implement pass to collect SublocTypes for global defs --- .../src/rewrite/expr/subloc_info.rs | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/c2rust-analyze/src/rewrite/expr/subloc_info.rs b/c2rust-analyze/src/rewrite/expr/subloc_info.rs index 4cbd709b5..9c94f5239 100644 --- a/c2rust-analyze/src/rewrite/expr/subloc_info.rs +++ b/c2rust-analyze/src/rewrite/expr/subloc_info.rs @@ -5,6 +5,7 @@ use crate::pointee_type::PointeeTypes; use crate::pointer_id::{GlobalPointerTable, PointerTable}; use crate::type_desc::{self, Ownership, Quantity, TypeDesc}; use crate::util::{ty_callee, Callee}; +use rustc_hir::def_id::DefId; use rustc_middle::mir::{ BasicBlock, Body, BorrowKind, Location, Operand, Place, PlaceElem, PlaceRef, Rvalue, Statement, StatementKind, Terminator, TerminatorKind, @@ -49,6 +50,19 @@ pub enum SublocType<'tcx> { } +pub struct SublocFnSig<'tcx> { + pub inputs: Box<[SublocType<'tcx>]>, + pub output: SublocType<'tcx>, + pub c_variadic: bool, +} + +pub struct SublocGlobalTypes<'tcx> { + pub fn_sigs: HashMap>, + pub field_tys: HashMap>, + pub static_tys: HashMap>, +} + + struct TypeConversionContext<'a, 'tcx> { tcx: TyCtxt<'tcx>, perms: &'a GlobalPointerTable, @@ -96,6 +110,10 @@ impl<'a, 'tcx> TypeConversionContext<'a, 'tcx> { (SublocType::Ptr(old_desc), SublocType::Ptr(new_desc)) } + + fn lty_to_new_subloc_type(&self, lty: LTy<'tcx>) -> SublocType<'tcx> { + self.lty_to_subloc_types(lty).1 + } } @@ -636,3 +654,35 @@ pub fn collect_subloc_info<'tcx>( v.info_map } + +pub fn collect_global_subloc_info<'tcx>( + acx: &AnalysisCtxt<'_, 'tcx>, + asn: &Assignment, + pointee_types: PointerTable>, +) -> SublocGlobalTypes<'tcx> { + let conv_cx = TypeConversionContext { + tcx: acx.tcx(), + perms: &asn.perms, + flags: &asn.flags, + pointee_types, + }; + + let fn_sigs = acx.gacx.fn_sigs.iter().map(|(&def_id, sig)| { + let inputs = sig.inputs.iter().map(|lty| conv_cx.lty_to_new_subloc_type(lty)).collect(); + let output = conv_cx.lty_to_new_subloc_type(sig.output); + (def_id, SublocFnSig { + inputs, output, + c_variadic: sig.c_variadic, + }) + }).collect::>(); + + let field_tys = acx.gacx.field_ltys.iter().map(|(&def_id, <y)| { + (def_id, conv_cx.lty_to_new_subloc_type(lty)) + }).collect::>(); + + let static_tys = acx.gacx.static_tys.iter().map(|(&def_id, <y)| { + (def_id, conv_cx.lty_to_new_subloc_type(lty)) + }).collect::>(); + + SublocGlobalTypes { fn_sigs, field_tys, static_tys } +} From e3dcd0874c1f83457a44bc66d1538206febec5f0 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Fri, 13 Dec 2024 13:51:22 -0800 Subject: [PATCH 7/9] analyze: subloc_info: initial implementation of typecheck pass --- c2rust-analyze/src/rewrite/expr/mir_op.rs | 2 +- c2rust-analyze/src/rewrite/expr/mod.rs | 10 +- .../src/rewrite/expr/subloc_info.rs | 483 ++++++++++++++++-- 3 files changed, 460 insertions(+), 35 deletions(-) diff --git a/c2rust-analyze/src/rewrite/expr/mir_op.rs b/c2rust-analyze/src/rewrite/expr/mir_op.rs index a576430ea..1ecc5fcd4 100644 --- a/c2rust-analyze/src/rewrite/expr/mir_op.rs +++ b/c2rust-analyze/src/rewrite/expr/mir_op.rs @@ -28,7 +28,7 @@ use std::ops::Index; use rustc_hir::def::Namespace; -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] pub enum SubLoc { /// The LHS of an assignment or call. `StatementKind::Assign/TerminatorKind::Call -> Place` Dest, diff --git a/c2rust-analyze/src/rewrite/expr/mod.rs b/c2rust-analyze/src/rewrite/expr/mod.rs index ceb7903fc..bb663864b 100644 --- a/c2rust-analyze/src/rewrite/expr/mod.rs +++ b/c2rust-analyze/src/rewrite/expr/mod.rs @@ -32,8 +32,11 @@ pub fn gen_expr_rewrites<'tcx>( mir: &Body<'tcx>, hir_body_id: BodyId, ) -> Vec<(Span, Rewrite)> { + let subloc_globals = subloc_info::collect_global_subloc_info(acx, asn, pointee_types); let subloc_info = subloc_info::collect_subloc_info(acx, asn, pointee_types, last_use, mir); debug_print_subloc_info_map(acx.tcx(), mir, &subloc_info); + let subloc_info = subloc_info::typecheck_subloc_info(acx, &subloc_globals, subloc_info, mir); + debug_print_subloc_info_map(acx.tcx(), mir, &subloc_info); let (mir_rewrites, errors) = mir_op::gen_mir_rewrites(acx, asn, pointee_types, last_use, mir); if !errors.is_empty() { @@ -76,7 +79,12 @@ fn debug_print_subloc_info_map<'tcx>( let print_for_loc = |loc| { for &(sub_loc, info) in by_loc.get(&loc).map_or(&[] as &[_], |x| x) { - eprintln!(" {sub_loc:?}: {info:?}"); + //eprintln!(" {sub_loc:?}: {info:?}"); + if info.new_ty == info.expect_ty { + eprintln!(" {sub_loc:?}: {:?}", info.new_ty); + } else { + eprintln!(" {sub_loc:?}: {:?} -> {:?}", info.new_ty, info.expect_ty); + } } }; diff --git a/c2rust-analyze/src/rewrite/expr/subloc_info.rs b/c2rust-analyze/src/rewrite/expr/subloc_info.rs index 9c94f5239..10087755f 100644 --- a/c2rust-analyze/src/rewrite/expr/subloc_info.rs +++ b/c2rust-analyze/src/rewrite/expr/subloc_info.rs @@ -4,13 +4,15 @@ use crate::panic_detail; use crate::pointee_type::PointeeTypes; use crate::pointer_id::{GlobalPointerTable, PointerTable}; use crate::type_desc::{self, Ownership, Quantity, TypeDesc}; -use crate::util::{ty_callee, Callee}; +use crate::util::{ty_callee, Callee, UnknownDefCallee}; +use log::info; use rustc_hir::def_id::DefId; use rustc_middle::mir::{ BasicBlock, Body, BorrowKind, Location, Operand, Place, PlaceElem, PlaceRef, Rvalue, Statement, StatementKind, Terminator, TerminatorKind, }; use rustc_middle::ty::{Ty, TyCtxt, TyKind}; +use std::cell::Cell; use std::collections::HashMap; use super::mir_op::{SubLoc, PlaceAccess}; @@ -41,7 +43,7 @@ pub struct SublocInfo<'tcx> { pub access: PlaceAccess, } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum SublocType<'tcx> { // TODO: modify to allow arbitrary nesting of `SublocType`s, maybe using `LabeledTy` or // `rustc_type_ir`. @@ -363,12 +365,17 @@ impl<'a, 'tcx> CollectInfoVisitor<'a, 'tcx> { let func_ty = func.ty(self.mir, tcx); let pl_ty = self.acx.type_of(destination); + self.enter_dest(|v| { + v.visit_place(destination, PlaceAccess::Mut); + }); + self.enter_rvalue(|v| { for (i, arg) in args.iter().enumerate() { v.enter_call_arg(i, |v| v.visit_operand(arg)); } }); + info!("loc {loc:?}, callee {:?}", ty_callee(tcx, func_ty)); // Special cases for particular functions. match ty_callee(tcx, func_ty) { // Normal call to a local function. @@ -378,6 +385,19 @@ impl<'a, 'tcx> CollectInfoVisitor<'a, 'tcx> { } } + Callee::UnknownDef(UnknownDefCallee::Direct { + ty: _, + def_id, + substs: _, + is_foreign: true, + }) if self.acx.gacx.known_fn(def_id).is_some() => { + // As this is actually a known `fn`, we can treat it as a normal local + // call. It should have a signature in `gacx.fn_sigs`. + if let Some(lsig) = self.acx.gacx.fn_sigs.get(&def_id) { + self.enter_rvalue(|v| v.emit_temp(lsig.output)); + } + } + Callee::PtrOffset { .. } => { // Set the call result type to match the destination type as closely as we // can. The argument will be downcast to this type, then offset. However, @@ -413,11 +433,6 @@ impl<'a, 'tcx> CollectInfoVisitor<'a, 'tcx> { self.enter_rvalue(|v| v.emit_temp(arg_lty)); } - Callee::IsNull => { - // Result type is simply `bool`, which should be the same as the dest type. - self.enter_rvalue(|v| v.emit_temp(pl_ty)); - } - Callee::Null { .. } => { // Match the destination type, but `null()` always outputs a nullable // pointer. @@ -440,8 +455,12 @@ impl<'a, 'tcx> CollectInfoVisitor<'a, 'tcx> { })); } - Callee::Free => { - // Result type is simply `()`, which should be the same as the dest type. + Callee::Trivial | + Callee::IsNull | + Callee::Free | + Callee::SizeOf { .. } => { + // These return non-pointers, so the result type should be identical to the + // dest type. self.enter_rvalue(|v| v.emit_temp(pl_ty)); } @@ -467,7 +486,9 @@ impl<'a, 'tcx> CollectInfoVisitor<'a, 'tcx> { match *rv { Rvalue::Use(ref op) => { - self.enter_rvalue_operand(0, |v| v.visit_operand(op)); + self.enter_rvalue_operand(0, |v| { + v.visit_operand_with_rvalue_lty(op, Some(rv_lty)); + }); } Rvalue::Repeat(ref op, _) => { self.enter_rvalue_operand(0, |v| v.visit_operand(op)); @@ -520,26 +541,40 @@ impl<'a, 'tcx> CollectInfoVisitor<'a, 'tcx> { } } - fn visit_operand(&mut self, op: &Operand<'tcx>) { - match *op { - Operand::Copy(pl) | Operand::Move(pl) => { - let pl_lty = self.acx.type_of(pl); - // Moving out of a `DynOwned` pointer requires `Mut` access rather than `Move`. - // TODO: this should probably check `desc.dyn_owned` rather than perms directly - let access = if !pl_lty.label.is_none() && self.perms[pl_lty.label].contains(PermissionSet::FREE) { - PlaceAccess::Mut - } else { - PlaceAccess::Move - }; - self.enter_operand_place(|v| v.visit_place(pl, access)); + fn visit_operand( + &mut self, + op: &Operand<'tcx>, + ) { + self.visit_operand_with_rvalue_lty(op, None) + } + + fn visit_operand_with_rvalue_lty( + &mut self, + op: &Operand<'tcx>, + // Used for a workaround related to string literals + rvalue_lty: Option>, + ) { + // Currently, `type_of(op)` fails for constant pointers to non-`static`s, such as string + // literals. As a workaround, we handle string literals in `Rvalue::Use` by using the + // `type_of_rvalue` instead. + let op_is_constant_pointer = + op.constant().is_some() && + op.ty(self.mir, self.acx.tcx()).is_any_ptr(); + if !op_is_constant_pointer { + let op_lty = self.acx.type_of(op); + let can_move = false; // TODO + let can_mutate = false; // TODO + self.emit(op_lty, can_move, can_mutate, PlaceAccess::Move); + } else { + // Special case for constant pointers + // TODO: fix `type_of(op)` and remove this workaround + if let Some(rvalue_lty) = rvalue_lty { + let can_move = false; // TODO + let can_mutate = false; // TODO + self.emit(rvalue_lty, can_move, can_mutate, PlaceAccess::Move); } - Operand::Constant(..) => {} } - } - /* - /// Like [`Self::visit_operand`], but takes an expected `TypeDesc` instead of an expected `LTy`. - fn visit_operand_desc(&mut self, op: &Operand<'tcx>) { match *op { Operand::Copy(pl) | Operand::Move(pl) => { let pl_lty = self.acx.type_of(pl); @@ -550,16 +585,11 @@ impl<'a, 'tcx> CollectInfoVisitor<'a, 'tcx> { } else { PlaceAccess::Move }; - self.enter_operand_place(|v| v.visit_place(pl, access, RequireSinglePointer::Yes)); - - if !pl_lty.label.is_none() { - self.emit_cast_lty_desc(pl_lty, expect_desc); - } + self.enter_operand_place(|v| v.visit_place(pl, access)); } Operand::Constant(..) => {} } } - */ fn visit_place( &mut self, @@ -625,6 +655,363 @@ impl<'a, 'tcx> CollectInfoVisitor<'a, 'tcx> { } } + +/// This struct is identical to [`SublocInfo`], but `new_ty` and `expect_ty` (the fields that are +/// potentially modified by the `TypeChecker` pass) are wrapped in `Cell`. +/// +/// We use `Cell` instead of `&mut` for mutating `SublocInfo`s to make it easy to hold references +/// to multiple different `SublocInfo`s at the same and across other method calls. +struct TypeCheckerSublocInfo<'tcx> { + pub old_ty: SublocType<'tcx>, + pub new_ty: Cell>, + pub expect_ty: Cell>, + pub can_move: bool, + pub can_mutate: bool, + pub access: PlaceAccess, +} + +impl<'tcx> From> for TypeCheckerSublocInfo<'tcx> { + fn from(x: SublocInfo<'tcx>) -> TypeCheckerSublocInfo<'tcx> { + let SublocInfo { old_ty, new_ty, expect_ty, can_move, can_mutate, access } = x; + let new_ty = Cell::new(new_ty); + let expect_ty = Cell::new(expect_ty); + TypeCheckerSublocInfo { old_ty, new_ty, expect_ty, can_move, can_mutate, access } + } +} + +impl<'tcx> From> for SublocInfo<'tcx> { + fn from(x: TypeCheckerSublocInfo<'tcx>) -> SublocInfo<'tcx> { + let TypeCheckerSublocInfo { old_ty, new_ty, expect_ty, can_move, can_mutate, access } = x; + let new_ty = new_ty.into_inner(); + let expect_ty = expect_ty.into_inner(); + SublocInfo { old_ty, new_ty, expect_ty, can_move, can_mutate, access } + } +} + +struct TypeCheckerInfoMap<'tcx>( + HashMap<(Location, Vec), TypeCheckerSublocInfo<'tcx>> +); + +impl<'tcx> From), SublocInfo<'tcx>>> for TypeCheckerInfoMap<'tcx> { + fn from(x: HashMap<(Location, Vec), SublocInfo<'tcx>>) -> TypeCheckerInfoMap<'tcx> { + let y = x.into_iter().map(|(k, v)| (k, v.into())).collect(); + TypeCheckerInfoMap(y) + } +} + +impl<'tcx> From> for HashMap<(Location, Vec), SublocInfo<'tcx>> { + fn from(x: TypeCheckerInfoMap<'tcx>) -> HashMap<(Location, Vec), SublocInfo<'tcx>> { + x.0.into_iter().map(|(k, v)| (k, v.into())).collect() + } +} + +/// `SublocInfo` type checker. The goal of this pass is to ensure that the code is well-typed +/// after applying type rewrites and casts. Specifically, for each operation in the MIR, +/// performing that operation on inputs whose types match the child nodes' `expect_ty`s should +/// produce an output that matches the operation's `new_ty`. This can be achieved by modifying the +/// children's `expect_ty`s, the operation's `new_ty`, or both, though it should be done such that +/// each node's `new_ty` can be converted to its `expect_ty` through some valid cast. +struct TypeChecker<'a, 'tcx> { + acx: &'a AnalysisCtxt<'a, 'tcx>, + globals: &'a SublocGlobalTypes<'tcx>, + info_map: &'a TypeCheckerInfoMap<'tcx>, + mir: &'a Body<'tcx>, + precise_loc: (Location, Vec), +} + +impl<'a, 'tcx> TypeChecker<'a, 'tcx> { + pub fn new( + acx: &'a AnalysisCtxt<'a, 'tcx>, + globals: &'a SublocGlobalTypes<'tcx>, + info_map: &'a TypeCheckerInfoMap<'tcx>, + mir: &'a Body<'tcx>, + ) -> TypeChecker<'a, 'tcx> { + TypeChecker { + acx, globals, info_map, mir, + precise_loc: ( + Location { + block: BasicBlock::from_usize(0), + statement_index: 0, + }, + Vec::new(), + ), + } + } + + fn loc(&self) -> Location { + self.precise_loc.0 + } + + fn loc_mut(&mut self) -> &mut Location { + &mut self.precise_loc.0 + } + + fn sub_loc(&self) -> &[SubLoc] { + &self.precise_loc.1 + } + + fn sub_loc_mut(&mut self) -> &mut Vec { + &mut self.precise_loc.1 + } + + + fn info(&mut self, path: &[SubLoc]) -> &'a TypeCheckerSublocInfo<'tcx> { + let old_len = self.sub_loc().len(); + self.sub_loc_mut().extend_from_slice(&path); + let r = self.info_map.0.get(&self.precise_loc).unwrap_or_else(|| panic!( + "no subloc_info for {:?}, {:?} + {:?}", self.loc(), self.sub_loc(), path + )); + self.sub_loc_mut().truncate(old_len); + r + } + + + fn enter R, R>(&mut self, sub: SubLoc, f: F) -> R { + self.sub_loc_mut().push(sub); + let r = f(self); + self.sub_loc_mut().pop(); + r + } + + fn enter_dest R, R>(&mut self, f: F) -> R { + self.enter(SubLoc::Dest, f) + } + + fn enter_rvalue R, R>(&mut self, f: F) -> R { + self.enter(SubLoc::Rvalue, f) + } + + fn enter_call_arg R, R>(&mut self, i: usize, f: F) -> R { + self.enter(SubLoc::CallArg(i), f) + } + + fn enter_rvalue_operand R, R>(&mut self, i: usize, f: F) -> R { + self.enter(SubLoc::RvalueOperand(i), f) + } + + fn enter_rvalue_place R, R>(&mut self, i: usize, f: F) -> R { + self.enter(SubLoc::RvaluePlace(i), f) + } + + fn enter_operand_place R, R>(&mut self, f: F) -> R { + self.enter(SubLoc::OperandPlace, f) + } + + fn enter_place_deref_pointer R, R>(&mut self, f: F) -> R { + self.enter(SubLoc::PlaceDerefPointer, f) + } + + fn enter_place_field_base R, R>(&mut self, f: F) -> R { + self.enter(SubLoc::PlaceFieldBase, f) + } + + fn enter_place_index_array R, R>(&mut self, f: F) -> R { + self.enter(SubLoc::PlaceIndexArray, f) + } + + + fn visit_statement(&mut self, stmt: &Statement<'tcx>, loc: Location) { + let _g = panic_detail::set_current_span(stmt.source_info.span); + eprintln!( + "subloc_info::TypeChecker::visit_statement: {:?} @ {:?}: {:?}", + loc, stmt.source_info.span, stmt + ); + *self.loc_mut() = loc; + debug_assert!(self.sub_loc().is_empty()); + + match stmt.kind { + StatementKind::Assign(ref x) => { + let (pl, ref rv) = **x; + + let pl_info = self.enter_dest(|v| v.visit_place(pl)); + let rv_info = self.enter_rvalue(|v| v.visit_rvalue(rv)); + // Cast RHS to match LHS. + rv_info.expect_ty.set(pl_info.new_ty.get()); + } + StatementKind::FakeRead(..) => {} + StatementKind::SetDiscriminant { .. } => todo!("statement {:?}", stmt), + StatementKind::Deinit(..) => {} + StatementKind::StorageLive(..) => {} + StatementKind::StorageDead(..) => {} + StatementKind::Retag(..) => {} + StatementKind::AscribeUserType(..) => {} + StatementKind::Coverage(..) => {} + StatementKind::CopyNonOverlapping(..) => todo!("statement {:?}", stmt), + StatementKind::Nop => {} + } + } + + fn visit_terminator(&mut self, term: &Terminator<'tcx>, loc: Location) { + let _g = panic_detail::set_current_span(term.source_info.span); + eprintln!( + "subloc_info::TypeChecker::visit_terminator: {:?} @ {:?}: {:?}", + loc, term.source_info.span, term + ); + *self.loc_mut() = loc; + debug_assert!(self.sub_loc().is_empty()); + + let tcx = self.acx.tcx(); + + match term.kind { + TerminatorKind::Goto { .. } => {} + TerminatorKind::SwitchInt { .. } => {} + TerminatorKind::Resume => {} + TerminatorKind::Abort => {} + TerminatorKind::Return => {} + TerminatorKind::Unreachable => {} + TerminatorKind::Drop { .. } => {} + TerminatorKind::DropAndReplace { .. } => {} + TerminatorKind::Call { + ref func, + ref args, + destination, + target: _, + .. + } => { + let pl_info = self.enter_dest(|v| v.visit_place(destination)); + let rv_info = self.enter_rvalue(|v| { + let func_ty = func.ty(self.mir, tcx); + let callee = ty_callee(tcx, func_ty); + v.visit_call(callee, args) + }); + // Cast RHS to match LHS. + rv_info.expect_ty.set(pl_info.new_ty.get()); + + + // TODO: visit the call, args, etc + + /* + let pl_ty = self.acx.type_of(destination); + + for (i, arg) in args.iter().enumerate() { + v.enter_call_arg(i, |v| v.visit_operand(arg)); + } + }); + + // Special cases for particular functions. + */ + } + TerminatorKind::Assert { .. } => {} + TerminatorKind::Yield { .. } => {} + TerminatorKind::GeneratorDrop => {} + TerminatorKind::FalseEdge { .. } => {} + TerminatorKind::FalseUnwind { .. } => {} + TerminatorKind::InlineAsm { .. } => todo!("terminator {:?}", term), + } + } + + fn visit_place(&mut self, pl: Place<'tcx>) -> &'a TypeCheckerSublocInfo<'tcx> { + let info = self.info(&[]); + + // TODO + + info + } + + fn visit_operand(&mut self, op: &Operand<'tcx>) -> &'a TypeCheckerSublocInfo<'tcx> { + let info = self.info(&[]); + + // TODO + + info + } + + fn visit_rvalue(&mut self, rv: &Rvalue<'tcx>) -> &'a TypeCheckerSublocInfo<'tcx> { + let info = self.info(&[]); + + // TODO + + info + } + + fn visit_call( + &mut self, + callee: Callee<'tcx>, + args: &[Operand<'tcx>], + ) -> &'a TypeCheckerSublocInfo<'tcx> { + let info = self.info(&[]); + + match callee { + // Normal call to a local function. + Callee::LocalDef { def_id, substs: _ } => { + let sig = self.globals.fn_sigs.get(&def_id) + .unwrap_or_else(|| panic!("missing sig for {def_id:?}")); + for (i, (arg, &ty)) in args.iter().zip(sig.inputs.iter()).enumerate() { + let arg_info = self.enter_call_arg(i, |v| v.visit_operand(arg)); + // Cast argument to function parameter type. + arg_info.expect_ty.set(ty); + } + } + + Callee::PtrOffset { .. } => { + assert_eq!(args.len(), 2); + let arg0_info = self.enter_call_arg(0, |v| v.visit_operand(&args[0])); + self.enter_call_arg(1, |v| v.visit_operand(&args[1])); + // Result type is always the same as the input type. The collection pass + // constrains the input type to be either `Slice` or `OffsetPtr`. + info.new_ty.set(arg0_info.expect_ty.get()); + } + + Callee::SliceAsPtr { .. } => { + assert_eq!(args.len(), 1); + let arg0_info = self.enter_call_arg(0, |v| v.visit_operand(&args[0])); + // Result type is always the same as the input type. + info.new_ty.set(arg0_info.expect_ty.get()); + } + + /* + Callee::Memcpy | Callee::Memset => { + // Match the type of the first (`dest`) argument exactly. The rewrite is + // responsible for preserving any combination of `Ownership` and + // `Quantity`. + assert_eq!(args.len(), 3); + let arg_lty = self.acx.type_of(&args[0]); + self.enter_rvalue(|v| v.emit_temp(arg_lty)); + } + + Callee::IsNull => { + // Result type is simply `bool`, which should be the same as the dest type. + self.enter_rvalue(|v| v.emit_temp(pl_ty)); + } + + Callee::Null { .. } => { + // Match the destination type, but `null()` always outputs a nullable + // pointer. + self.enter_rvalue(|v| v.emit_temp_adjust(pl_ty, |mut slty| { + if let SublocType::Ptr(ref mut desc) = slty { + desc.option = true; + } + slty + })); + } + + Callee::Malloc | Callee::Calloc | Callee::Realloc => { + // Match the destination type, but always produce a non-optional `Box`. + self.enter_rvalue(|v| v.emit_temp_adjust(pl_ty, |mut slty| { + if let SublocType::Ptr(ref mut desc) = slty { + desc.option = false; + desc.own = Ownership::Box; + } + slty + })); + } + + Callee::Free => { + // Result type is simply `()`, which should be the same as the dest type. + self.enter_rvalue(|v| v.emit_temp(pl_ty)); + } + */ + + //_ => todo!("visit_call {callee:?}"), + _ => info!("TODO: visit_call {callee:?}"), + + } + + info + } +} + + pub fn collect_subloc_info<'tcx>( acx: &AnalysisCtxt<'_, 'tcx>, asn: &Assignment, @@ -686,3 +1073,33 @@ pub fn collect_global_subloc_info<'tcx>( SublocGlobalTypes { fn_sigs, field_tys, static_tys } } + +pub fn typecheck_subloc_info<'tcx>( + acx: &AnalysisCtxt<'_, 'tcx>, + globals: &SublocGlobalTypes<'tcx>, + info_map: HashMap<(Location, Vec), SublocInfo<'tcx>>, + mir: &Body<'tcx>, +) -> HashMap<(Location, Vec), SublocInfo<'tcx>> { + let info_map = TypeCheckerInfoMap::from(info_map); + let mut v = TypeChecker::new(acx, globals, &info_map, mir); + + for (bb_id, bb) in mir.basic_blocks().iter_enumerated() { + for (i, stmt) in bb.statements.iter().enumerate() { + let loc = Location { + block: bb_id, + statement_index: i, + }; + v.visit_statement(stmt, loc); + } + + if let Some(ref term) = bb.terminator { + let loc = Location { + block: bb_id, + statement_index: bb.statements.len(), + }; + v.visit_terminator(term, loc); + } + } + + info_map.into() +} From 0c0d1be9580510bd8a01e82f5b4aeb92548ceba0 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Mon, 16 Dec 2024 10:16:20 -0800 Subject: [PATCH 8/9] analyze: subloc_info: add more callee cases to typecheck pass --- .../src/rewrite/expr/subloc_info.rs | 69 +++++++++++-------- 1 file changed, 40 insertions(+), 29 deletions(-) diff --git a/c2rust-analyze/src/rewrite/expr/subloc_info.rs b/c2rust-analyze/src/rewrite/expr/subloc_info.rs index 10087755f..bfe01f6e4 100644 --- a/c2rust-analyze/src/rewrite/expr/subloc_info.rs +++ b/c2rust-analyze/src/rewrite/expr/subloc_info.rs @@ -449,6 +449,7 @@ impl<'a, 'tcx> CollectInfoVisitor<'a, 'tcx> { self.enter_rvalue(|v| v.emit_temp_adjust(pl_ty, |mut slty| { if let SublocType::Ptr(ref mut desc) = slty { desc.option = false; + desc.dyn_owned = false; desc.own = Ownership::Box; } slty @@ -959,48 +960,58 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { info.new_ty.set(arg0_info.expect_ty.get()); } - /* - Callee::Memcpy | Callee::Memset => { - // Match the type of the first (`dest`) argument exactly. The rewrite is - // responsible for preserving any combination of `Ownership` and - // `Quantity`. + Callee::Memcpy => { assert_eq!(args.len(), 3); - let arg_lty = self.acx.type_of(&args[0]); - self.enter_rvalue(|v| v.emit_temp(arg_lty)); + let arg0_info = self.enter_call_arg(0, |v| v.visit_operand(&args[0])); + let arg1_info = self.enter_call_arg(1, |v| v.visit_operand(&args[0])); + self.enter_call_arg(2, |v| v.visit_operand(&args[0])); + // Output type is exactly equal to the dest type. + info.new_ty.set(arg0_info.expect_ty.get()); + // Source type has no special constraints. The rewrite for `memcpy` will handle + // any combination of ownership and quantity. + } + + Callee::Memset => { + assert_eq!(args.len(), 3); + let arg0_info = self.enter_call_arg(0, |v| v.visit_operand(&args[0])); + let arg1_info = self.enter_call_arg(1, |v| v.visit_operand(&args[0])); + self.enter_call_arg(2, |v| v.visit_operand(&args[0])); + // Output type is exactly equal to the dest type. + info.new_ty.set(arg0_info.expect_ty.get()); } Callee::IsNull => { - // Result type is simply `bool`, which should be the same as the dest type. - self.enter_rvalue(|v| v.emit_temp(pl_ty)); + assert_eq!(args.len(), 1); + self.enter_call_arg(0, |v| v.visit_operand(&args[0])); + // Input and output types are not related. } Callee::Null { .. } => { - // Match the destination type, but `null()` always outputs a nullable - // pointer. - self.enter_rvalue(|v| v.emit_temp_adjust(pl_ty, |mut slty| { - if let SublocType::Ptr(ref mut desc) = slty { - desc.option = true; - } - slty - })); + assert_eq!(args.len(), 0); + // No inputs to adjust. } - Callee::Malloc | Callee::Calloc | Callee::Realloc => { - // Match the destination type, but always produce a non-optional `Box`. - self.enter_rvalue(|v| v.emit_temp_adjust(pl_ty, |mut slty| { - if let SublocType::Ptr(ref mut desc) = slty { - desc.option = false; - desc.own = Ownership::Box; - } - slty - })); + Callee::Malloc | Callee::Calloc => { + assert!(args.len() == 1 || args.len() == 2); + for (i, arg) in args.iter().enumerate() { + self.enter_call_arg(i, |v| v.visit_operand(arg)); + } + // Input and output types are not related. + } + + Callee::Realloc => { + assert_eq!(args.len(), 2); + self.enter_call_arg(0, |v| v.visit_operand(&args[0])); + self.enter_call_arg(1, |v| v.visit_operand(&args[1])); + // No special requirements. The input pointer will always be some kind of `Box` + // due to dataflow constraints. } Callee::Free => { - // Result type is simply `()`, which should be the same as the dest type. - self.enter_rvalue(|v| v.emit_temp(pl_ty)); + assert_eq!(args.len(), 1); + self.enter_call_arg(0, |v| v.visit_operand(&args[0])); + // Input and output types are not related. } - */ //_ => todo!("visit_call {callee:?}"), _ => info!("TODO: visit_call {callee:?}"), From f0470b8336fe1bd8b1b1cc57ff855281fbe9fdaf Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Mon, 16 Dec 2024 16:59:37 -0800 Subject: [PATCH 9/9] [wip] rework mir_op to use SublocInfo --- c2rust-analyze/src/rewrite/expr/mir_op.rs | 917 ++++++++++-------- c2rust-analyze/src/rewrite/expr/mod.rs | 3 +- .../src/rewrite/expr/subloc_info.rs | 14 + 3 files changed, 502 insertions(+), 432 deletions(-) diff --git a/c2rust-analyze/src/rewrite/expr/mir_op.rs b/c2rust-analyze/src/rewrite/expr/mir_op.rs index 1ecc5fcd4..b69a6137f 100644 --- a/c2rust-analyze/src/rewrite/expr/mir_op.rs +++ b/c2rust-analyze/src/rewrite/expr/mir_op.rs @@ -13,9 +13,10 @@ use crate::panic_detail; use crate::pointee_type::PointeeTypes; use crate::pointer_id::{GlobalPointerTable, PointerId, PointerTable}; use crate::rewrite; +use crate::rewrite::expr::subloc_info::{SublocInfo, SublocType}; use crate::type_desc::{self, Ownership, Quantity, TypeDesc}; use crate::util::{self, ty_callee, Callee}; -use log::{debug, error, trace}; +use log::{debug, error, info, trace}; use rustc_ast::Mutability; use rustc_middle::mir::{ BasicBlock, Body, BorrowKind, Location, Operand, Place, PlaceElem, PlaceRef, Rvalue, Statement, @@ -24,6 +25,7 @@ use rustc_middle::mir::{ use rustc_middle::ty::print::{FmtPrinter, PrettyPrinter, Print}; use rustc_middle::ty::{Ty, TyCtxt, TyKind}; use std::collections::HashMap; +use std::marker::PhantomData; use std::ops::Index; use rustc_hir::def::Namespace; @@ -251,53 +253,72 @@ impl RequireSinglePointer { struct ExprRewriteVisitor<'a, 'tcx> { acx: &'a AnalysisCtxt<'a, 'tcx>, - perms: &'a GlobalPointerTable, - flags: &'a GlobalPointerTable, - pointee_types: PointerTable<'a, PointeeTypes<'tcx>>, - last_use: &'a LastUse, + info_map: &'a HashMap<(Location, Vec), SublocInfo<'tcx>>, rewrites: &'a mut HashMap>, mir: &'a Body<'tcx>, - loc: Location, - sub_loc: Vec, + precise_loc: (Location, Vec), errors: DontRewriteFnReason, } impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { pub fn new( acx: &'a AnalysisCtxt<'a, 'tcx>, - asn: &'a Assignment, - pointee_types: PointerTable<'a, PointeeTypes<'tcx>>, - last_use: &'a LastUse, + info_map: &'a HashMap<(Location, Vec), SublocInfo<'tcx>>, rewrites: &'a mut HashMap>, mir: &'a Body<'tcx>, ) -> ExprRewriteVisitor<'a, 'tcx> { - let perms = asn.perms(); - let flags = asn.flags(); ExprRewriteVisitor { acx, - perms, - flags, - pointee_types, - last_use, + info_map, rewrites, mir, - loc: Location { - block: BasicBlock::from_usize(0), - statement_index: 0, - }, - sub_loc: Vec::new(), + precise_loc: ( + Location { + block: BasicBlock::from_usize(0), + statement_index: 0, + }, + Vec::new(), + ), errors: DontRewriteFnReason::empty(), } } + fn loc(&self) -> Location { + self.precise_loc.0 + } + + fn loc_mut(&mut self) -> &mut Location { + &mut self.precise_loc.0 + } + + fn sub_loc(&self) -> &[SubLoc] { + &self.precise_loc.1 + } + + fn sub_loc_mut(&mut self) -> &mut Vec { + &mut self.precise_loc.1 + } + + + fn info(&mut self, path: &[SubLoc]) -> &'a SublocInfo<'tcx> { + let old_len = self.sub_loc().len(); + self.sub_loc_mut().extend_from_slice(&path); + let r = self.info_map.get(&self.precise_loc).unwrap_or_else(|| panic!( + "no subloc_info for {:?}, {:?} + {:?}", self.loc(), self.sub_loc(), path + )); + self.sub_loc_mut().truncate(old_len); + r + } + + fn err(&mut self, reason: DontRewriteFnReason) { self.errors.insert(reason); } fn enter R, R>(&mut self, sub: SubLoc, f: F) -> R { - self.sub_loc.push(sub); + self.sub_loc_mut().push(sub); let r = f(self); - self.sub_loc.pop(); + self.sub_loc_mut().pop(); r } @@ -337,6 +358,8 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { self.enter(SubLoc::PlaceIndexArray, f) } + + /* /// Get the pointee type of `lty`. Returns the inferred pointee type from `self.pointee_types` /// if one is available, or the pointee type as represented in `lty` itself otherwise. Returns /// `None` if `lty` is not a `RawPtr` or `Ref` type. @@ -446,6 +469,7 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { false } } + */ fn visit_statement(&mut self, stmt: &Statement<'tcx>, loc: Location) { let _g = panic_detail::set_current_span(stmt.source_info.span); @@ -453,13 +477,16 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { "mir_op::visit_statement: {:?} @ {:?}: {:?}", loc, stmt.source_info.span, stmt ); - self.loc = loc; - debug_assert!(self.sub_loc.is_empty()); + *self.loc_mut() = loc; + debug_assert!(self.sub_loc().is_empty()); match stmt.kind { StatementKind::Assign(ref x) => { let (pl, ref rv) = **x; + self.enter_dest(|v| v.visit_place(pl)); + self.enter_rvalue(|v| v.visit_rvalue(rv)); + /* let pl_lty = self.acx.type_of(pl); // FIXME: Needs changes to handle CELL pointers in struct fields. Suppose `pl` is @@ -588,6 +615,7 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { v.emit_cast_lty_lty_or_borrow(rv_lty, pl_lty, cast_can_move) }); self.enter_dest(|v| v.visit_place(pl, PlaceAccess::Mut, RequireSinglePointer::Yes)); + */ } StatementKind::FakeRead(..) => {} StatementKind::SetDiscriminant { .. } => todo!("statement {:?}", stmt), @@ -605,8 +633,8 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { fn visit_terminator(&mut self, term: &Terminator<'tcx>, loc: Location) { let tcx = self.acx.tcx(); let _g = panic_detail::set_current_span(term.source_info.span); - self.loc = loc; - debug_assert!(self.sub_loc.is_empty()); + *self.loc_mut() = loc; + debug_assert!(self.sub_loc().is_empty()); match term.kind { TerminatorKind::Goto { .. } => {} @@ -624,394 +652,400 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { target: _, .. } => { - let func_ty = func.ty(self.mir, tcx); - let pl_ty = self.acx.type_of(destination); + self.enter_dest(|v| v.visit_place(destination)); + self.enter_rvalue(|v| { + let func_ty = func.ty(self.mir, tcx); + let callee = ty_callee(tcx, func_ty); + v.visit_call(callee, args) + }); + /* // Special cases for particular functions. match ty_callee(tcx, func_ty) { - Callee::PtrOffset { .. } => { - self.visit_ptr_offset(&args[0], pl_ty); - } - Callee::SliceAsPtr { elem_ty, .. } => { - self.visit_slice_as_ptr(elem_ty, &args[0], pl_ty); - } + } + */ + } + TerminatorKind::Assert { .. } => {} + TerminatorKind::Yield { .. } => {} + TerminatorKind::GeneratorDrop => {} + TerminatorKind::FalseEdge { .. } => {} + TerminatorKind::FalseUnwind { .. } => {} + TerminatorKind::InlineAsm { .. } => todo!("terminator {:?}", term), + } + } - Callee::LocalDef { def_id, substs: _ } => { - // TODO: handle substs (if nonempty) - if let Some(lsig) = self.acx.gacx.fn_sigs.get(&def_id) { - self.enter_rvalue(|v| { - for (i, op) in args.iter().enumerate() { - if let Some(<y) = lsig.inputs.get(i) { - v.enter_call_arg(i, |v| v.visit_operand(op, Some(lty))); - } else { - // This is a call to a variadic function, and we've gone - // past the end of the declared arguments. - // TODO: insert a cast to turn `op` back into its original - // declared type (i.e. upcast the chosen reference type - // back to a raw pointer) - continue; - } - } + fn visit_call( + &mut self, + callee: Callee<'tcx>, + args: &[Operand<'tcx>], + ) -> &'a SublocInfo<'tcx> { + let info = self.info(&[]); + + match callee { + Callee::LocalDef { def_id, substs: _ } => { + // TODO: handle substs (if nonempty) + for (i, arg) in args.iter().enumerate() { + self.enter_call_arg(i, |v| v.visit_operand(arg)); + } + // TODO: handle variadics? + } - if !pl_ty.label.is_none() { - // Emit a cast. The call returns a temporary, which the cast - // is always allowed to move. - v.emit_cast_lty_lty(lsig.output, pl_ty, true); - } - }); - } - } + Callee::PtrOffset { .. } => { + self.enter_call_arg(0, |v| v.visit_operand(&args[0])); - Callee::Memcpy => { - self.enter_rvalue(|v| { - // TODO: Only emit `MemcpySafe` if the rewritten argument types and - // pointees are suitable. Specifically, the `src` and `dest` arguments - // must both be rewritten to safe references, their pointee types must - // be the same, and the pointee type must implement `Copy`. If these - // conditions don't hold, leave the `memcpy` call intact and emit casts - // back to `void*` on the `dest` and `src` arguments. - let dest_lty = v.acx.type_of(&args[0]); - let dest_pointee = v.pointee_lty(dest_lty); - let src_lty = v.acx.type_of(&args[1]); - let src_pointee = v.pointee_lty(src_lty); - let common_pointee = dest_pointee.filter(|&x| Some(x) == src_pointee); - let pointee_lty = match common_pointee { - Some(x) => x, - // TODO: emit void* casts before bailing out, as described above - None => return, - }; - - let (_, elem_ty) = v.lty_to_rewritten_str(pointee_lty); - let dest_single = !v.perms[dest_lty.label] - .intersects(PermissionSet::OFFSET_ADD | PermissionSet::OFFSET_SUB); - let src_single = !v.perms[src_lty.label] - .intersects(PermissionSet::OFFSET_ADD | PermissionSet::OFFSET_SUB); - let dest_option = - !v.perms[dest_lty.label].contains(PermissionSet::NON_NULL); - let src_option = - !v.perms[src_lty.label].contains(PermissionSet::NON_NULL); - v.emit(RewriteKind::MemcpySafe { - elem_ty, - src_single, - src_option, - dest_single, - dest_option, - }); + let desc = match info.new_ty { + SublocType::Ptr(desc) => desc, + SublocType::Other(_) => + panic!("expected SublocType::Ptr for Callee::PtrOffset"), + }; - if !pl_ty.label.is_none() - && v.perms[pl_ty.label].intersects(PermissionSet::USED) - { - let dest_lty = v.acx.type_of(&args[0]); - // TODO: The result of `MemcpySafe` is always a slice, so this cast - // may be using an incorrect input type. See the comment on the - // `MemcpySafe` case of `rewrite::expr::convert` for details. - v.emit_cast_lty_lty(dest_lty, pl_ty, true); - } - }); + let mutbl = matches!(desc.own, Ownership::Mut); + if !desc.option { + self.emit(RewriteKind::OffsetSlice { mutbl }); + } else { + self.emit(RewriteKind::OptionMapOffsetSlice { mutbl }); + } + } + /* + Callee::SliceAsPtr { elem_ty, .. } => { + self.visit_slice_as_ptr(elem_ty, &args[0], pl_ty); + } + + Callee::Memcpy => { + self.enter_rvalue(|v| { + // TODO: Only emit `MemcpySafe` if the rewritten argument types and + // pointees are suitable. Specifically, the `src` and `dest` arguments + // must both be rewritten to safe references, their pointee types must + // be the same, and the pointee type must implement `Copy`. If these + // conditions don't hold, leave the `memcpy` call intact and emit casts + // back to `void*` on the `dest` and `src` arguments. + let dest_lty = v.acx.type_of(&args[0]); + let dest_pointee = v.pointee_lty(dest_lty); + let src_lty = v.acx.type_of(&args[1]); + let src_pointee = v.pointee_lty(src_lty); + let common_pointee = dest_pointee.filter(|&x| Some(x) == src_pointee); + let pointee_lty = match common_pointee { + Some(x) => x, + // TODO: emit void* casts before bailing out, as described above + None => return, + }; + + let (_, elem_ty) = v.lty_to_rewritten_str(pointee_lty); + let dest_single = !v.perms[dest_lty.label] + .intersects(PermissionSet::OFFSET_ADD | PermissionSet::OFFSET_SUB); + let src_single = !v.perms[src_lty.label] + .intersects(PermissionSet::OFFSET_ADD | PermissionSet::OFFSET_SUB); + let dest_option = + !v.perms[dest_lty.label].contains(PermissionSet::NON_NULL); + let src_option = + !v.perms[src_lty.label].contains(PermissionSet::NON_NULL); + v.emit(RewriteKind::MemcpySafe { + elem_ty, + src_single, + src_option, + dest_single, + dest_option, + }); + + if !pl_ty.label.is_none() + && v.perms[pl_ty.label].intersects(PermissionSet::USED) + { + let dest_lty = v.acx.type_of(&args[0]); + // TODO: The result of `MemcpySafe` is always a slice, so this cast + // may be using an incorrect input type. See the comment on the + // `MemcpySafe` case of `rewrite::expr::convert` for details. + v.emit_cast_lty_lty(dest_lty, pl_ty, true); } + }); + } - Callee::Memset => { - self.enter_rvalue(|v| { - // TODO: Only emit `MemsetSafe` if the rewritten argument type and - // pointee are suitable. Specifically, the `dest` arguments must be - // rewritten to a safe reference type. If these conditions don't hold, - // leave the `memset` call intact and emit casts back to `void*` on the - // `dest` argument. - let dest_lty = v.acx.type_of(&args[0]); - let dest_pointee = v.pointee_lty(dest_lty); - let pointee_lty = match dest_pointee { - Some(x) => x, - // TODO: emit void* cast before bailing out, as described above - None => return, - }; - - let (elem_ty, elem_ty_str) = v.lty_to_rewritten_str(pointee_lty); - let dest_single = !v.perms[dest_lty.label] - .intersects(PermissionSet::OFFSET_ADD | PermissionSet::OFFSET_SUB); - - let zero_ty = match ZeroizeType::from_ty(tcx, elem_ty) { - Some(x) => x, - // TODO: emit void* cast before bailing out, as described above - None => return, - }; - - v.emit(RewriteKind::MemsetZeroize { - zero_ty, - elem_ty: elem_ty_str, - dest_single, - }); + Callee::Memset => { + self.enter_rvalue(|v| { + // TODO: Only emit `MemsetSafe` if the rewritten argument type and + // pointee are suitable. Specifically, the `dest` arguments must be + // rewritten to a safe reference type. If these conditions don't hold, + // leave the `memset` call intact and emit casts back to `void*` on the + // `dest` argument. + let dest_lty = v.acx.type_of(&args[0]); + let dest_pointee = v.pointee_lty(dest_lty); + let pointee_lty = match dest_pointee { + Some(x) => x, + // TODO: emit void* cast before bailing out, as described above + None => return, + }; - if !pl_ty.label.is_none() - && v.perms[pl_ty.label].intersects(PermissionSet::USED) - { - let dest_lty = v.acx.type_of(&args[0]); - v.emit_cast_lty_lty(dest_lty, pl_ty, true); - } - }); + let (elem_ty, elem_ty_str) = v.lty_to_rewritten_str(pointee_lty); + let dest_single = !v.perms[dest_lty.label] + .intersects(PermissionSet::OFFSET_ADD | PermissionSet::OFFSET_SUB); + + let zero_ty = match ZeroizeType::from_ty(tcx, elem_ty) { + Some(x) => x, + // TODO: emit void* cast before bailing out, as described above + None => return, + }; + + v.emit(RewriteKind::MemsetZeroize { + zero_ty, + elem_ty: elem_ty_str, + dest_single, + }); + + if !pl_ty.label.is_none() + && v.perms[pl_ty.label].intersects(PermissionSet::USED) + { + let dest_lty = v.acx.type_of(&args[0]); + v.emit_cast_lty_lty(dest_lty, pl_ty, true); } + }); + } - Callee::IsNull => { - self.enter_rvalue(|v| { - let arg_lty = v.acx.type_of(&args[0]); - if !v.flags[arg_lty.label].contains(FlagSet::FIXED) { - let arg_non_null = - v.perms[arg_lty.label].contains(PermissionSet::NON_NULL); - if arg_non_null { - v.emit(RewriteKind::IsNullToConstFalse); - } else { - v.emit(RewriteKind::IsNullToIsNone); - } - } - }); + Callee::IsNull => { + self.enter_rvalue(|v| { + let arg_lty = v.acx.type_of(&args[0]); + if !v.flags[arg_lty.label].contains(FlagSet::FIXED) { + let arg_non_null = + v.perms[arg_lty.label].contains(PermissionSet::NON_NULL); + if arg_non_null { + v.emit(RewriteKind::IsNullToConstFalse); + } else { + v.emit(RewriteKind::IsNullToIsNone); + } } + }); + } - Callee::Null { .. } => { - self.enter_rvalue(|v| { - if !v.flags[pl_ty.label].contains(FlagSet::FIXED) { - assert!( - !v.perms[pl_ty.label].contains(PermissionSet::NON_NULL), - "impossible: result of null() is a NON_NULL pointer?" - ); - v.emit(RewriteKind::PtrNullToNone); - } - }); + Callee::Null { .. } => { + self.enter_rvalue(|v| { + if !v.flags[pl_ty.label].contains(FlagSet::FIXED) { + assert!( + !v.perms[pl_ty.label].contains(PermissionSet::NON_NULL), + "impossible: result of null() is a NON_NULL pointer?" + ); + v.emit(RewriteKind::PtrNullToNone); } + }); + } - ref callee @ (Callee::Malloc | Callee::Calloc) => { - self.enter_rvalue(|v| { - let dest_lty = v.acx.type_of(destination); - let dest_pointee = v.pointee_lty(dest_lty); - let pointee_lty = match dest_pointee { - Some(x) => x, - // TODO: emit void* cast before bailing out - None => { - trace!("{callee:?}: no pointee type for dest"); - return; - } - }; - - let (_, elem_ty) = v.lty_to_rewritten_str(pointee_lty); - let single = !v.perms[dest_lty.label] - .intersects(PermissionSet::OFFSET_ADD | PermissionSet::OFFSET_SUB); - - let opt_zero_ty = - ZeroizeType::from_lty(&v.acx, v.perms, v.flags, pointee_lty); - let zero_ty = match opt_zero_ty { - Some(x) => x, - // TODO: emit void* cast before bailing out - None => { - trace!( - "{callee:?}: failed to compute ZeroizeType \ - for {pointee_lty:?}" - ); - return; - } - }; + ref callee @ (Callee::Malloc | Callee::Calloc) => { + self.enter_rvalue(|v| { + let dest_lty = v.acx.type_of(destination); + let dest_pointee = v.pointee_lty(dest_lty); + let pointee_lty = match dest_pointee { + Some(x) => x, + // TODO: emit void* cast before bailing out + None => { + trace!("{callee:?}: no pointee type for dest"); + return; + } + }; - let rw = match *callee { - Callee::Malloc => RewriteKind::MallocSafe { - zero_ty, - elem_ty, - single, - }, - Callee::Calloc => RewriteKind::CallocSafe { - zero_ty, - elem_ty, - single, - }, - _ => unreachable!(), - }; - v.emit(rw); - - // `MallocSafe` produces either `Box` or `Box<[T]>`. Emit a cast - // from that type to the required output type. - v.emit_cast_adjust_lty( - |desc| TypeDesc { - own: Ownership::Box, - qty: if single { - Quantity::Single - } else { - Quantity::Slice - }, - dyn_owned: false, - option: false, - pointee_ty: desc.pointee_ty, - }, - dest_lty, + let (_, elem_ty) = v.lty_to_rewritten_str(pointee_lty); + let single = !v.perms[dest_lty.label] + .intersects(PermissionSet::OFFSET_ADD | PermissionSet::OFFSET_SUB); + + let opt_zero_ty = + ZeroizeType::from_lty(&v.acx, v.perms, v.flags, pointee_lty); + let zero_ty = match opt_zero_ty { + Some(x) => x, + // TODO: emit void* cast before bailing out + None => { + trace!( + "{callee:?}: failed to compute ZeroizeType \ + for {pointee_lty:?}" ); - }); - } + return; + } + }; - Callee::Free => { - self.enter_rvalue(|v| { - let src_lty = v.acx.type_of(&args[0]); - let src_pointee = v.pointee_lty(src_lty); - if src_pointee.is_none() { - // TODO: emit void* cast before bailing out - return; - } + let rw = match *callee { + Callee::Malloc => RewriteKind::MallocSafe { + zero_ty, + elem_ty, + single, + }, + Callee::Calloc => RewriteKind::CallocSafe { + zero_ty, + elem_ty, + single, + }, + _ => unreachable!(), + }; + v.emit(rw); + + // `MallocSafe` produces either `Box` or `Box<[T]>`. Emit a cast + // from that type to the required output type. + v.emit_cast_adjust_lty( + |desc| TypeDesc { + own: Ownership::Box, + qty: if single { + Quantity::Single + } else { + Quantity::Slice + }, + dyn_owned: false, + option: false, + pointee_ty: desc.pointee_ty, + }, + dest_lty, + ); + }); + } - let single = !v.perms[src_lty.label] - .intersects(PermissionSet::OFFSET_ADD | PermissionSet::OFFSET_SUB); - - // Cast to either `Box` or `Box<[T]>` (depending on `single`). This - // ensures a panic occurs when `free`ing a pointer that no longer has - // ownership. - v.enter_call_arg(0, |v| { - v.emit_cast_lty_adjust(src_lty, |desc| TypeDesc { - own: Ownership::Box, - qty: if single { - Quantity::Single - } else { - Quantity::Slice - }, - dyn_owned: false, - option: desc.option, - pointee_ty: desc.pointee_ty, - }); - }); + Callee::Free => { + self.enter_rvalue(|v| { + let src_lty = v.acx.type_of(&args[0]); + let src_pointee = v.pointee_lty(src_lty); + if src_pointee.is_none() { + // TODO: emit void* cast before bailing out + return; + } - v.emit(RewriteKind::FreeSafe { single }); + let single = !v.perms[src_lty.label] + .intersects(PermissionSet::OFFSET_ADD | PermissionSet::OFFSET_SUB); + + // Cast to either `Box` or `Box<[T]>` (depending on `single`). This + // ensures a panic occurs when `free`ing a pointer that no longer has + // ownership. + v.enter_call_arg(0, |v| { + v.emit_cast_lty_adjust(src_lty, |desc| TypeDesc { + own: Ownership::Box, + qty: if single { + Quantity::Single + } else { + Quantity::Slice + }, + dyn_owned: false, + option: desc.option, + pointee_ty: desc.pointee_ty, }); - } + }); - ref callee @ Callee::Realloc => { - self.enter_rvalue(|v| { - let src_lty = v.acx.type_of(&args[0]); - let src_pointee = v.pointee_lty(src_lty); - let dest_lty = v.acx.type_of(destination); - let dest_pointee = v.pointee_lty(dest_lty); - let common_pointee = dest_pointee.filter(|&x| Some(x) == src_pointee); - let pointee_lty = match common_pointee { - Some(x) => x, - // TODO: emit void* cast before bailing out - None => { - trace!( - "{callee:?}: no common pointee type \ - between {src_pointee:?} and {dest_pointee:?}" - ); - return; - } - }; - - let (elem_ty, elem_ty_str) = v.lty_to_rewritten_str(pointee_lty); - let dest_single = !v.perms[dest_lty.label] - .intersects(PermissionSet::OFFSET_ADD | PermissionSet::OFFSET_SUB); - let src_single = !v.perms[src_lty.label] - .intersects(PermissionSet::OFFSET_ADD | PermissionSet::OFFSET_SUB); - - let zero_ty = match ZeroizeType::from_ty(tcx, elem_ty) { - Some(x) => x, - // TODO: emit void* cast before bailing out - None => { - trace!( - "{callee:?}: failed to compute ZeroizeType \ - for {elem_ty:?}" - ); - return; - } - }; - - // Cast input to either `Box` or `Box<[T]>`, as in `free`. - let mut option = false; - v.enter_call_arg(0, |v| { - v.emit_cast_lty_adjust(src_lty, |desc| { - // `realloc(NULL, ...)` is explicitly allowed by the spec, so - // we can't force an unwrap here by returning `option: false`. - // Instead, we record the `option` flag as part of the rewrite - // so the nullable case can be handled appropriately. - option = desc.option; - TypeDesc { - own: Ownership::Box, - qty: if src_single { - Quantity::Single - } else { - Quantity::Slice - }, - dyn_owned: false, - option: desc.option, - pointee_ty: desc.pointee_ty, - } - }); - }); + v.emit(RewriteKind::FreeSafe { single }); + }); + } - v.emit(RewriteKind::ReallocSafe { - zero_ty, - elem_ty: elem_ty_str, - src_single, - dest_single, - option, - }); + ref callee @ Callee::Realloc => { + self.enter_rvalue(|v| { + let src_lty = v.acx.type_of(&args[0]); + let src_pointee = v.pointee_lty(src_lty); + let dest_lty = v.acx.type_of(destination); + let dest_pointee = v.pointee_lty(dest_lty); + let common_pointee = dest_pointee.filter(|&x| Some(x) == src_pointee); + let pointee_lty = match common_pointee { + Some(x) => x, + // TODO: emit void* cast before bailing out + None => { + trace!( + "{callee:?}: no common pointee type \ + between {src_pointee:?} and {dest_pointee:?}" + ); + return; + } + }; - // Cast output from `Box`/`Box<[T]>` to the target type, as in - // `malloc`. - v.emit_cast_adjust_lty( - |desc| { - TypeDesc { - own: Ownership::Box, - qty: if dest_single { - Quantity::Single - } else { - Quantity::Slice - }, - dyn_owned: false, - // We always return non-null from `realloc`. - option: false, - pointee_ty: desc.pointee_ty, - } - }, - dest_lty, + let (elem_ty, elem_ty_str) = v.lty_to_rewritten_str(pointee_lty); + let dest_single = !v.perms[dest_lty.label] + .intersects(PermissionSet::OFFSET_ADD | PermissionSet::OFFSET_SUB); + let src_single = !v.perms[src_lty.label] + .intersects(PermissionSet::OFFSET_ADD | PermissionSet::OFFSET_SUB); + + let zero_ty = match ZeroizeType::from_ty(tcx, elem_ty) { + Some(x) => x, + // TODO: emit void* cast before bailing out + None => { + trace!( + "{callee:?}: failed to compute ZeroizeType \ + for {elem_ty:?}" ); + return; + } + }; + + // Cast input to either `Box` or `Box<[T]>`, as in `free`. + let mut option = false; + v.enter_call_arg(0, |v| { + v.emit_cast_lty_adjust(src_lty, |desc| { + // `realloc(NULL, ...)` is explicitly allowed by the spec, so + // we can't force an unwrap here by returning `option: false`. + // Instead, we record the `option` flag as part of the rewrite + // so the nullable case can be handled appropriately. + option = desc.option; + TypeDesc { + own: Ownership::Box, + qty: if src_single { + Quantity::Single + } else { + Quantity::Slice + }, + dyn_owned: false, + option: desc.option, + pointee_ty: desc.pointee_ty, + } }); - } + }); - _ => {} - } + v.emit(RewriteKind::ReallocSafe { + zero_ty, + elem_ty: elem_ty_str, + src_single, + dest_single, + option, + }); + + // Cast output from `Box`/`Box<[T]>` to the target type, as in + // `malloc`. + v.emit_cast_adjust_lty( + |desc| { + TypeDesc { + own: Ownership::Box, + qty: if dest_single { + Quantity::Single + } else { + Quantity::Slice + }, + dyn_owned: false, + // We always return non-null from `realloc`. + option: false, + pointee_ty: desc.pointee_ty, + } + }, + dest_lty, + ); + }); } - TerminatorKind::Assert { .. } => {} - TerminatorKind::Yield { .. } => {} - TerminatorKind::GeneratorDrop => {} - TerminatorKind::FalseEdge { .. } => {} - TerminatorKind::FalseUnwind { .. } => {} - TerminatorKind::InlineAsm { .. } => todo!("terminator {:?}", term), + */ + + //_ => todo!("visit_call {callee:?}"), + _ => info!("TODO: visit_call {callee:?}"), } + + self.emit_cast_for_subloc(info); + info } - /// Visit an `Rvalue`. If `expect_ty` is `Some`, also emit whatever casts are necessary to - /// make the `Rvalue` produce a value of type `expect_ty`. - fn visit_rvalue(&mut self, rv: &Rvalue<'tcx>, expect_ty: Option>) { - debug!("mir_op::visit_rvalue: {:?}, expect {:?}", rv, expect_ty); + fn visit_rvalue(&mut self, rv: &Rvalue<'tcx>) -> &'a SublocInfo<'tcx> { + debug!("mir_op::visit_rvalue: {:?}", rv); + let info = self.info(&[]); match *rv { Rvalue::Use(ref op) => { - self.enter_rvalue_operand(0, |v| v.visit_operand(op, expect_ty)); + self.enter_rvalue_operand(0, |v| v.visit_operand(op)); } Rvalue::Repeat(ref op, _) => { - self.enter_rvalue_operand(0, |v| v.visit_operand(op, None)); + self.enter_rvalue_operand(0, |v| v.visit_operand(op)); } Rvalue::Ref(_rg, kind, pl) => { - let mutbl = match kind { - BorrowKind::Mut { .. } => true, - BorrowKind::Shared | BorrowKind::Shallow | BorrowKind::Unique => false, - }; self.enter_rvalue_place(0, |v| { - v.visit_place(pl, PlaceAccess::from_bool(mutbl), RequireSinglePointer::No) + v.visit_place(pl) }); - - if let Some(expect_ty) = expect_ty { - if self.is_nullable(expect_ty.label) { - // Nullable (`Option`) output is expected, but `Ref` always produces a - // `NON_NULL` pointer. Cast rvalue from `&T` to `Option<&T>` or similar. - self.emit(RewriteKind::OptionSome); - } - } } Rvalue::ThreadLocalRef(_def_id) => { // TODO } Rvalue::AddressOf(mutbl, pl) => { self.enter_rvalue_place(0, |v| { - v.visit_place(pl, PlaceAccess::from_mutbl(mutbl), RequireSinglePointer::No) + v.visit_place(pl) }); + /* if let Some(expect_ty) = expect_ty { let desc = type_desc::perms_to_desc_with_pointee( self.acx.tcx(), @@ -1031,94 +1065,73 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { self.emit(RewriteKind::OptionSome); } } + */ } Rvalue::Len(pl) => { self.enter_rvalue_place(0, |v| { - v.visit_place(pl, PlaceAccess::Imm, RequireSinglePointer::No) + v.visit_place(pl) }); } Rvalue::Cast(_kind, ref op, ty) => { if util::is_null_const_operand(op) && ty.is_unsafe_ptr() { // Special case: convert `0 as *const T` to `None`. + self.emit(RewriteKind::ZeroAsPtrToNone); + /* if let Some(rv_lty) = expect_ty { if self.is_nullable(rv_lty.label) { - self.emit(RewriteKind::ZeroAsPtrToNone); } } + */ } - self.enter_rvalue_operand(0, |v| v.visit_operand(op, None)); - if let Some(rv_lty) = expect_ty { - let op_lty = self.acx.type_of(op); - let op_pointee = self.pointee_lty(op_lty); - let rv_pointee = self.pointee_lty(rv_lty); - // TODO: Check `pointee_types` recursively to handle pointer-to-pointer cases. - // For example `op_pointee = *mut /*p1*/ c_void` and `rv_pointee = *mut /*p2*/ - // c_void`, where `p1` and `p2` both have `pointee_types` entries of `u8`. - let common_pointee = op_pointee.filter(|&x| Some(x) == rv_pointee); - if let Some(pointee_lty) = common_pointee { - let op_desc = type_desc::perms_to_desc_with_pointee( - self.acx.tcx(), - pointee_lty.ty, - op_lty.ty, - self.perms[op_lty.label], - self.flags[op_lty.label], - ); - let rv_desc = type_desc::perms_to_desc_with_pointee( - self.acx.tcx(), - pointee_lty.ty, - rv_lty.ty, - self.perms[rv_lty.label], - self.flags[rv_lty.label], - ); - debug!("Cast with common pointee {:?}:\n op_desc = {:?}\n rv_desc = {:?}\n matches? {}", - pointee_lty, op_desc, rv_desc, op_desc == rv_desc); - if op_desc == rv_desc { - // After rewriting, the input and output types of the cast will be - // identical. This means we can delete the cast. - self.emit(RewriteKind::RemoveCast); - } - } + let op_info = self.enter_rvalue_operand(0, |v| v.visit_operand(op)); + if op_info.expect_ty == info.new_ty { + // After rewriting, the input and output types of the cast will be identical. + // This means we can delete the cast. + self.emit(RewriteKind::RemoveCast); } } Rvalue::BinaryOp(_bop, ref ops) => { - self.enter_rvalue_operand(0, |v| v.visit_operand(&ops.0, None)); - self.enter_rvalue_operand(1, |v| v.visit_operand(&ops.1, None)); + self.enter_rvalue_operand(0, |v| v.visit_operand(&ops.0)); + self.enter_rvalue_operand(1, |v| v.visit_operand(&ops.1)); } Rvalue::CheckedBinaryOp(_bop, ref ops) => { - self.enter_rvalue_operand(0, |v| v.visit_operand(&ops.0, None)); - self.enter_rvalue_operand(1, |v| v.visit_operand(&ops.1, None)); + self.enter_rvalue_operand(0, |v| v.visit_operand(&ops.0)); + self.enter_rvalue_operand(1, |v| v.visit_operand(&ops.1)); } Rvalue::NullaryOp(..) => {} Rvalue::UnaryOp(_uop, ref op) => { - self.enter_rvalue_operand(0, |v| v.visit_operand(op, None)); + self.enter_rvalue_operand(0, |v| v.visit_operand(op)); } Rvalue::Discriminant(pl) => { self.enter_rvalue_place(0, |v| { - v.visit_place(pl, PlaceAccess::Imm, RequireSinglePointer::No) + v.visit_place(pl) }); } Rvalue::Aggregate(ref _kind, ref ops) => { for (i, op) in ops.iter().enumerate() { - self.enter_rvalue_operand(i, |v| v.visit_operand(op, None)); + self.enter_rvalue_operand(i, |v| v.visit_operand(op)); } } Rvalue::ShallowInitBox(ref op, _ty) => { - self.enter_rvalue_operand(0, |v| v.visit_operand(op, None)); + self.enter_rvalue_operand(0, |v| v.visit_operand(op)); } Rvalue::CopyForDeref(pl) => { self.enter_rvalue_place(0, |v| { - v.visit_place(pl, PlaceAccess::Imm, RequireSinglePointer::No) + v.visit_place(pl) }); } } + + self.emit_cast_for_subloc(info); + info } - /// Visit an `Operand`. If `expect_ty` is `Some`, also emit whatever casts are necessary to - /// make the `Operand` produce a value of type `expect_ty`. - fn visit_operand(&mut self, op: &Operand<'tcx>, expect_ty: Option>) { + fn visit_operand(&mut self, op: &Operand<'tcx>) -> &'a SublocInfo<'tcx> { + let info = self.info(&[]); match *op { Operand::Copy(pl) | Operand::Move(pl) => { + /* let pl_lty = self.acx.type_of(pl); // Moving out of a `DynOwned` pointer requires `Mut` access rather than `Move`. // TODO: this should probably check `desc.dyn_owned` rather than perms directly @@ -1129,19 +1142,26 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { } else { PlaceAccess::Move }; - self.enter_operand_place(|v| v.visit_place(pl, access, RequireSinglePointer::Yes)); + */ + self.enter_operand_place(|v| v.visit_place(pl)); + /* if let Some(expect_ty) = expect_ty { if !pl_lty.label.is_none() { let cast_can_move = pl.is_local() && self.current_sub_loc_is_last_use(); self.emit_cast_lty_lty(pl_lty, expect_ty, cast_can_move); } } + */ } Operand::Constant(..) => {} } + + self.emit_cast_for_subloc(info); + info } + /* /// Like [`Self::visit_operand`], but takes an expected `TypeDesc` instead of an expected `LTy`. fn visit_operand_desc(&mut self, op: &Operand<'tcx>, expect_desc: TypeDesc<'tcx>) { match *op { @@ -1165,12 +1185,11 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { Operand::Constant(..) => {} } } + */ fn visit_place( &mut self, pl: Place<'tcx>, - access: PlaceAccess, - require_single_ptr: RequireSinglePointer, ) { let mut ltys = Vec::with_capacity(1 + pl.projection.len()); ltys.push(self.acx.type_of(pl.local)); @@ -1178,18 +1197,15 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { let prev_lty = ltys.last().copied().unwrap(); ltys.push(self.acx.projection_lty(prev_lty, &proj)); } - self.visit_place_ref(pl.as_ref(), <ys, access, require_single_ptr); + self.visit_place_ref(pl.as_ref(), <ys); } /// Generate rewrites for a `Place` represented as a `PlaceRef`. `proj_ltys` gives the `LTy` - /// for the `Local` and after each projection. `access` describes how the place is being used: - /// immutably, mutably, or being moved out of. + /// for the `Local` and after each projection. fn visit_place_ref( &mut self, pl: PlaceRef<'tcx>, proj_ltys: &[LTy<'tcx>], - access: PlaceAccess, - require_single_ptr: RequireSinglePointer, ) { let (&last_proj, rest) = match pl.projection.split_last() { Some(x) => x, @@ -1210,8 +1226,10 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { }; match last_proj { PlaceElem::Deref => { - let cast_can_move = base_pl.is_local() && self.current_sub_loc_is_last_use(); + //let cast_can_move = base_pl.is_local() && self.current_sub_loc_is_last_use(); self.enter_place_deref_pointer(|v| { + // FIXME + /* v.visit_place_ref(base_pl, proj_ltys, access, RequireSinglePointer::Yes); if !v.flags[base_lty.label].contains(FlagSet::FIXED) { let desc = type_desc::perms_to_desc( @@ -1255,22 +1273,24 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { }); } } + */ }); } PlaceElem::Field(_idx, _ty) => { self.enter_place_field_base(|v| { - v.visit_place_ref(base_pl, proj_ltys, access, RequireSinglePointer::Yes) + v.visit_place_ref(base_pl, proj_ltys) }); } PlaceElem::Index(_) | PlaceElem::ConstantIndex { .. } | PlaceElem::Subslice { .. } => { self.enter_place_index_array(|v| { - v.visit_place_ref(base_pl, proj_ltys, access, RequireSinglePointer::Yes) + v.visit_place_ref(base_pl, proj_ltys) }); } PlaceElem::Downcast(_, _) => {} } } + /* fn visit_ptr_offset(&mut self, op: &Operand<'tcx>, result_ty: LTy<'tcx>) { // Compute the expected type for the argument, and emit a cast if needed. let result_ptr = result_ty.label; @@ -1335,17 +1355,53 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { v.emit_cast_desc_desc(op_desc, result_desc); }); } + */ + + fn emit_cast_for_subloc(&mut self, info: &'a SublocInfo<'tcx>) { + if info.expect_ty == info.new_ty { + // No cast is needed here. + return; + } + + struct DummyPointerTable(PhantomData>); + impl Index for DummyPointerTable { + type Output = T; + fn index(&self, _idx: PointerId) -> &T { + panic!("tried to access DummyPointerTable"); + } + } + + let perms = &DummyPointerTable(PhantomData); + let flags = &DummyPointerTable(PhantomData); + let mut builder = CastBuilder::new(self.acx.tcx(), perms, flags, |rk| self.emit(rk)); + + let from = match info.new_ty { + SublocType::Ptr(desc) => desc, + SublocType::Other(_) => panic!("can't cast SublocTypes {:?} -> {:?}", + info.new_ty, info.expect_ty), + }; + let to = match info.expect_ty { + SublocType::Ptr(desc) => desc, + SublocType::Other(_) => panic!("can't cast SublocTypes {:?} -> {:?}", + info.new_ty, info.expect_ty), + }; + builder.build_cast_desc_desc(from, to); + } fn emit(&mut self, rw: RewriteKind) { + let loc = self.loc(); + let sub_loc = self.sub_loc().to_owned(); + self.rewrites - .entry(self.loc) + .entry(loc) .or_insert_with(Vec::new) .push(MirRewrite { kind: rw, - sub_loc: self.sub_loc.clone(), + sub_loc, }); } + /* fn emit_cast_desc_desc(&mut self, from: TypeDesc<'tcx>, to: TypeDesc<'tcx>) { let perms = self.perms; let flags = self.flags; @@ -1418,6 +1474,7 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { let mut builder = CastBuilder::new(self.acx.tcx(), perms, flags, |rk| self.emit(rk)); builder.build_cast_adjust_lty(from_adjust, to_lty); } + */ } impl ZeroizeType { @@ -2080,14 +2137,12 @@ impl IsPlace for Rvalue<'_> { pub fn gen_mir_rewrites<'tcx>( acx: &AnalysisCtxt<'_, 'tcx>, - asn: &Assignment, - pointee_types: PointerTable>, - last_use: &LastUse, + info_map: &HashMap<(Location, Vec), SublocInfo<'tcx>>, mir: &Body<'tcx>, ) -> (HashMap>, DontRewriteFnReason) { let mut out = HashMap::new(); - let mut v = ExprRewriteVisitor::new(acx, asn, pointee_types, last_use, &mut out, mir); + let mut v = ExprRewriteVisitor::new(acx, info_map, &mut out, mir); for (bb_id, bb) in mir.basic_blocks().iter_enumerated() { for (i, stmt) in bb.statements.iter().enumerate() { diff --git a/c2rust-analyze/src/rewrite/expr/mod.rs b/c2rust-analyze/src/rewrite/expr/mod.rs index bb663864b..179c0b931 100644 --- a/c2rust-analyze/src/rewrite/expr/mod.rs +++ b/c2rust-analyze/src/rewrite/expr/mod.rs @@ -38,7 +38,8 @@ pub fn gen_expr_rewrites<'tcx>( let subloc_info = subloc_info::typecheck_subloc_info(acx, &subloc_globals, subloc_info, mir); debug_print_subloc_info_map(acx.tcx(), mir, &subloc_info); - let (mir_rewrites, errors) = mir_op::gen_mir_rewrites(acx, asn, pointee_types, last_use, mir); + //let (mir_rewrites, errors) = mir_op::gen_mir_rewrites(acx, asn, pointee_types, last_use, mir); + let (mir_rewrites, errors) = mir_op::gen_mir_rewrites(acx, &subloc_info, mir); if !errors.is_empty() { acx.gacx.dont_rewrite_fns.add(def_id, errors); } diff --git a/c2rust-analyze/src/rewrite/expr/subloc_info.rs b/c2rust-analyze/src/rewrite/expr/subloc_info.rs index bfe01f6e4..92efbe7dc 100644 --- a/c2rust-analyze/src/rewrite/expr/subloc_info.rs +++ b/c2rust-analyze/src/rewrite/expr/subloc_info.rs @@ -110,6 +110,20 @@ impl<'a, 'tcx> TypeConversionContext<'a, 'tcx> { new_desc.pointee_ty = pointee_lty.ty; } + // FIXME (hack): currently we sometimes get a desc with `pointee_ty = [u32]` etc, instead + // of `pointee_ty = u32` + `qty = Slice`. This causes confusion in later passes, so here + // we hack around it. + // TODO: investigate why this happens and fix the underlying issue + if new_desc.qty == Quantity::Single { + if let &TyKind::Slice(elem_ty) = new_desc.pointee_ty.kind() { + new_desc.qty = Quantity::Slice; + new_desc.pointee_ty = elem_ty; + //} else if let &TyKind::Array(elem_ty, _) = new_desc.pointee_ty.kind() { + // new_desc.qty = Quantity::Array; + // new_desc.pointee_ty = elem_ty; + } + } + (SublocType::Ptr(old_desc), SublocType::Ptr(new_desc)) }