diff --git a/c2rust-analyze/src/context.rs b/c2rust-analyze/src/context.rs index 942219554f..a991eac1d2 100644 --- a/c2rust-analyze/src/context.rs +++ b/c2rust-analyze/src/context.rs @@ -4,7 +4,7 @@ use crate::pointer_id::{ GlobalPointerTable, LocalPointerTable, NextGlobalPointerId, NextLocalPointerId, PointerTable, PointerTableMut, }; -use crate::util::{self, describe_rvalue, RvalueDesc}; +use crate::util::{self, describe_rvalue, PhantomLifetime, RvalueDesc}; use crate::AssignPointerIds; use bitflags::bitflags; use rustc_hir::def_id::DefId; @@ -13,7 +13,7 @@ use rustc_middle::mir::{ Body, Field, HasLocalDecls, Local, LocalDecls, Location, Operand, Place, PlaceElem, PlaceRef, Rvalue, }; -use rustc_middle::ty::{AdtDef, FieldDef, Ty, TyCtxt, TyKind}; +use rustc_middle::ty::{self, AdtDef, FieldDef, Ty, TyCtxt, TyKind}; use std::collections::HashMap; use std::ops::Index; @@ -51,6 +51,22 @@ bitflags! { } } +impl PermissionSet { + pub fn for_const_ref_ty(ty: Ty) -> Self { + let ty = match ty.kind() { + ty::Ref(_, ty, _) => ty, + _ => panic!("expected only `Ref`s for constants: {ty:?}"), + }; + if ty.is_array() || ty.is_str() { + Self::READ | Self::OFFSET_ADD + } else if ty.is_primitive_ty() { + Self::READ + } else { + panic!("expected an array, str, or primitive type: {ty:?}"); + } + } +} + bitflags! { /// Additional flags describing a given pointer type. These are mainly derived from /// `PermissionSet`, but don't follow the normal subtyping rules and propagation algorithm. @@ -97,13 +113,44 @@ pub struct AnalysisCtxt<'a, 'tcx> { /// those `PointerId`s consistent, the `Rvalue`'s type must be stored rather than recomputed on /// the fly. pub rvalue_tys: HashMap>, + + /// [`Location`]s of const ref [`rvalue_tys`](Self::rvalue_tys). + pub const_ref_locs: Vec, + next_ptr_id: NextLocalPointerId, } +impl<'a, 'tcx> AnalysisCtxt<'_, 'tcx> { + pub fn const_ref_tys(&'a self) -> impl Iterator> + 'a { + self.const_ref_locs.iter().map(|loc| self.rvalue_tys[loc]) + } + + pub fn const_ref_perms( + &'a self, + ) -> impl Iterator + PhantomLifetime<'tcx> + 'a { + self.const_ref_tys() + .map(|lty| (lty.label, PermissionSet::for_const_ref_ty(lty.ty))) + } + + pub fn check_const_ref_perms(&self, asn: &Assignment) { + for const_ref_lty in self.const_ref_tys() { + let ptr_id = const_ref_lty.label; + let expected_perms = PermissionSet::for_const_ref_ty(const_ref_lty.ty); + let mut actual_perms = asn.perms()[ptr_id]; + // Ignore `UNIQUE` as it gets automatically added to all permissions + // and then removed later if it can't apply. + // We don't care about `UNIQUE` for const refs, so just unset it here. + actual_perms.set(PermissionSet::UNIQUE, false); + assert_eq!(expected_perms, actual_perms); + } + } +} + pub struct AnalysisCtxtData<'tcx> { local_tys: IndexVec>, addr_of_local: IndexVec, rvalue_tys: HashMap>, + const_ref_locs: Vec, next_ptr_id: NextLocalPointerId, } @@ -196,6 +243,7 @@ impl<'a, 'tcx> AnalysisCtxt<'a, 'tcx> { c_void_casts: CVoidCasts::new(mir, tcx), addr_of_local: IndexVec::new(), rvalue_tys: HashMap::new(), + const_ref_locs: Default::default(), next_ptr_id: NextLocalPointerId::new(), } } @@ -209,6 +257,7 @@ impl<'a, 'tcx> AnalysisCtxt<'a, 'tcx> { local_tys, addr_of_local, rvalue_tys, + const_ref_locs, next_ptr_id, } = data; AnalysisCtxt { @@ -218,6 +267,7 @@ impl<'a, 'tcx> AnalysisCtxt<'a, 'tcx> { c_void_casts: CVoidCasts::default(), addr_of_local, rvalue_tys, + const_ref_locs, next_ptr_id, } } @@ -227,6 +277,7 @@ impl<'a, 'tcx> AnalysisCtxt<'a, 'tcx> { local_tys: self.local_tys, addr_of_local: self.addr_of_local, rvalue_tys: self.rvalue_tys, + const_ref_locs: self.const_ref_locs, next_ptr_id: self.next_ptr_id, } } @@ -363,12 +414,13 @@ impl<'tcx> AnalysisCtxtData<'tcx> { map: PointerTable, counter: NextLocalPointerId, ) { - let AnalysisCtxtData { - ref mut local_tys, - ref mut addr_of_local, - ref mut rvalue_tys, - ref mut next_ptr_id, - } = *self; + let Self { + local_tys, + addr_of_local, + rvalue_tys, + const_ref_locs: _, + next_ptr_id, + } = self; for lty in local_tys { *lty = remap_lty_pointers(lcx, &map, lty); diff --git a/c2rust-analyze/src/dataflow/type_check.rs b/c2rust-analyze/src/dataflow/type_check.rs index afb9ebb9e3..f37123a728 100644 --- a/c2rust-analyze/src/dataflow/type_check.rs +++ b/c2rust-analyze/src/dataflow/type_check.rs @@ -525,6 +525,10 @@ pub fn visit<'tcx>( equiv_constraints: Vec::new(), }; + for (ptr, perms) in acx.const_ref_perms() { + tc.constraints.add_all_perms(ptr, perms); + } + for (bb, bb_data) in mir.basic_blocks().iter_enumerated() { for (i, stmt) in bb_data.statements.iter().enumerate() { tc.visit_statement( diff --git a/c2rust-analyze/src/main.rs b/c2rust-analyze/src/main.rs index 65b5007efb..3b60fe945c 100644 --- a/c2rust-analyze/src/main.rs +++ b/c2rust-analyze/src/main.rs @@ -34,8 +34,8 @@ use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_index::vec::IndexVec; use rustc_middle::mir::visit::Visitor; use rustc_middle::mir::{ - AggregateKind, BindingForm, Body, LocalDecl, LocalInfo, LocalKind, Location, Operand, Rvalue, - StatementKind, + AggregateKind, BindingForm, Body, ConstantKind, LocalDecl, LocalInfo, LocalKind, Location, + Operand, Rvalue, StatementKind, }; use rustc_middle::ty::tls; use rustc_middle::ty::{GenericArgKind, Ty, TyCtxt, TyKind, WithOptConstParam}; @@ -397,6 +397,28 @@ fn label_rvalue_tys<'tcx>(acx: &mut AnalysisCtxt<'_, 'tcx>, mir: &Body<'tcx>) { _ => continue, }, Rvalue::Cast(_, _, ty) => acx.assign_pointer_ids(*ty), + Rvalue::Use(Operand::Constant(c)) => { + let c = &**c; + if !c.ty().is_ref() { + continue; + } + // Constants can include, for example, `""` and `b""` string literals. + match c.literal { + ConstantKind::Val(_, ty) => { + // The [`Constant`] is an inline value and thus local to this function, + // as opposed to a global, named `const`s, for example. + // This might miss local, named `const`s. + acx.const_ref_locs.push(loc); + acx.assign_pointer_ids(ty) + } + ConstantKind::Ty(ty) => { + ::log::error!( + "TODO: handle global, named `const` refs: {c:?}, ty = {ty:?}" + ); + continue; + } + } + } _ => continue, }; @@ -609,6 +631,8 @@ fn run(tcx: TyCtxt) { let mut asn = gasn.and(&mut info.lasn); info.dataflow.propagate_cell(&mut asn); + acx.check_const_ref_perms(&asn); + // Print labeling and rewrites for the current function. eprintln!("\nfinal labeling for {:?}:", name); diff --git a/c2rust-analyze/src/util.rs b/c2rust-analyze/src/util.rs index ecd0300590..ef7488ed35 100644 --- a/c2rust-analyze/src/util.rs +++ b/c2rust-analyze/src/util.rs @@ -348,3 +348,6 @@ pub fn is_null_const(constant: Constant) -> bool { _ => false, } } + +pub trait PhantomLifetime<'a> {} +impl<'a, T: ?Sized> PhantomLifetime<'a> for T {} diff --git a/c2rust-analyze/tests/analyze/string_literals.rs b/c2rust-analyze/tests/analyze/string_literals.rs index 366df06906..62cecaff73 100644 --- a/c2rust-analyze/tests/analyze/string_literals.rs +++ b/c2rust-analyze/tests/analyze/string_literals.rs @@ -1,9 +1,32 @@ +pub fn inline_desugared_bstr() -> &'static [u8; 0] { + &[] +} + +const DESUGARED_BSTR: &'static [u8; 0] = &[]; + +#[cfg(any())] +pub fn outline_desugared_bstr() -> &'static [u8; 0] { + DESUGARED_BSTR +} + +pub fn inline_bstr() -> &'static [u8; 0] { + b"" +} + +const BSTR: &'static [u8; 0] = b""; + #[cfg(any())] -pub fn str() { - ""; +pub fn outline_bstr() -> &'static [u8; 0] { + BSTR } +pub fn inline_str() -> &'static str { + "" +} + +const STR: &'static str = ""; + #[cfg(any())] -pub fn bstr() { - b""; +pub fn outline_str() -> &'static str { + STR }