diff --git a/c2rust-analyze/src/context.rs b/c2rust-analyze/src/context.rs index 7e78cf51b..5ae0c5813 100644 --- a/c2rust-analyze/src/context.rs +++ b/c2rust-analyze/src/context.rs @@ -3,7 +3,7 @@ use crate::pointer_id::{ GlobalPointerTable, LocalPointerTable, NextGlobalPointerId, NextLocalPointerId, PointerTable, PointerTableMut, }; -use crate::util::{self, describe_rvalue, RvalueDesc}; +use crate::util::{self, are_transmutable_ptrs, describe_rvalue, RvalueDesc}; use crate::AssignPointerIds; use bitflags::bitflags; use rustc_hir::def_id::DefId; @@ -349,23 +349,22 @@ impl<'a, 'tcx> AnalysisCtxt<'a, 'tcx> { Rvalue::Cast(_, ref op, ty) => { let op_lty = self.type_of(op); - // We support this category of pointer casts as a special case. - let op_is_ptr = matches!(op_lty.ty.kind(), TyKind::Ref(..) | TyKind::RawPtr(..)); - let op_pointee = op_is_ptr.then(|| op_lty.args[0]); - let ty_pointee = match *ty.kind() { - TyKind::Ref(_, ty, _) => Some(ty), - TyKind::RawPtr(tm) => Some(tm.ty), - _ => None, - }; - if op_pointee.is_some() && op_pointee.map(|lty| lty.ty) == ty_pointee { - // The source and target types are both pointers, and they have identical - // pointee types. We label the target type with the same `PointerId`s as the - // source type in all positions. This works because the two types have the - // same structure. - return self.lcx().mk(ty, op_lty.args, op_lty.label); + // We only support pointer casts when: + // * both types are pointers + // * they have compatible (safely transmutable) pointee types + // Safe transmutability is difficult to check abstractly, + // so we limit it to integer types of the same size + // (but potentially different signedness). + // In particular, this allows casts from `*u8` to `*core::ffi::c_char`. + let from_ty = op_lty.ty; + let to_ty = ty; + match are_transmutable_ptrs(from_ty, to_ty) { + // Label the to type with the same [`PointerId`]s as the from type in all positions. + // This works because the two types have the same structure. + Some(true) => self.lcx().mk(ty, op_lty.args, op_lty.label), + Some(false) => todo!("unsupported ptr-to-ptr cast between pointee types not yet supported as safely transmutable: `{from_ty:?} as {to_ty:?}`"), + None => label_no_pointers(self, ty), } - - label_no_pointers(self, ty) } Rvalue::Len(..) | Rvalue::BinaryOp(..) diff --git a/c2rust-analyze/src/dataflow/type_check.rs b/c2rust-analyze/src/dataflow/type_check.rs index 3d74a0482..867f47567 100644 --- a/c2rust-analyze/src/dataflow/type_check.rs +++ b/c2rust-analyze/src/dataflow/type_check.rs @@ -1,6 +1,6 @@ use super::DataflowConstraints; use crate::context::{AnalysisCtxt, LTy, PermissionSet, PointerId}; -use crate::util::{self, describe_rvalue, Callee, RvalueDesc}; +use crate::util::{self, are_transmutable, describe_rvalue, Callee, RvalueDesc}; use rustc_hir::def_id::DefId; use rustc_middle::mir::{ AggregateKind, BinOp, Body, Location, Mutability, Operand, Place, PlaceRef, ProjectionElem, @@ -207,10 +207,12 @@ impl<'tcx> TypeChecker<'tcx, '_> { /// that position. For example, given `lty1 = *mut /*l1*/ *const /*l2*/ u8` and `lty2 = *mut /// /*l3*/ *const /*l4*/ u8`, this function will unify `l1` with `l3` and `l2` with `l4`. fn do_unify(&mut self, lty1: LTy<'tcx>, lty2: LTy<'tcx>) { - assert_eq!( - self.acx.tcx().erase_regions(lty1.ty), - self.acx.tcx().erase_regions(lty2.ty) - ); + let ty1 = lty1.ty; + let ty2 = lty2.ty; + assert!(are_transmutable( + self.acx.tcx().erase_regions(ty1), + self.acx.tcx().erase_regions(ty2), + ), "types not transmutable (compatible), so PointerId unification cannot be done: {ty1:?} !~ {ty2:?}"); for (sub_lty1, sub_lty2) in lty1.iter().zip(lty2.iter()) { eprintln!("equate {:?} = {:?}", sub_lty1, sub_lty2); if sub_lty1.label != PointerId::NONE || sub_lty2.label != PointerId::NONE { diff --git a/c2rust-analyze/src/util.rs b/c2rust-analyze/src/util.rs index 1c6e35590..b69a68209 100644 --- a/c2rust-analyze/src/util.rs +++ b/c2rust-analyze/src/util.rs @@ -4,7 +4,8 @@ use rustc_hir::def_id::DefId; use rustc_middle::mir::{ Field, Local, Mutability, Operand, PlaceElem, PlaceRef, ProjectionElem, Rvalue, }; -use rustc_middle::ty::{AdtDef, DefIdTree, SubstsRef, Ty, TyCtxt, TyKind, UintTy}; +use rustc_middle::ty::{self, AdtDef, DefIdTree, SubstsRef, Ty, TyCtxt, TyKind, UintTy}; +use rustc_type_ir::IntTy; use std::fmt::Debug; #[derive(Debug)] @@ -305,3 +306,44 @@ pub fn lty_project<'tcx, L: Debug>( ProjectionElem::Downcast(..) => todo!("type_of Downcast"), } } + +/// Determine if two types are safe to transmute to each other. +/// +/// Safe transmutability is difficult to check abstractly, +/// so here it is limited to integer types of the same size +/// (but potentially different signedness). +/// +/// Thus, [`true`] means it is definitely transmutable, +/// while [`false`] means it may not be transmutable. +pub fn are_transmutable<'tcx>(a: Ty<'tcx>, b: Ty<'tcx>) -> bool { + let transmutable_ints = { + use IntTy::*; + use UintTy::*; + match (a.kind(), b.kind()) { + (ty::Uint(u), ty::Int(i)) | (ty::Int(i), ty::Uint(u)) => { + matches!((u, i), |(Usize, Isize)| (U8, I8) + | (U16, I16) + | (U32, I32) + | (U64, I64)) + } + _ => false, + } + }; + + // only check for transmutable ints so far + a == b || transmutable_ints +} + +/// Determine if two types (e.x. in a cast) are pointers, +/// and if they are, if the pointee types are compatible, +/// i.e. they are safely transmutable to each other. +/// +/// This returns [`Some`]`(is_transmutable)` if they're both pointers, +/// and [`None`] if its some other types. +/// +/// See [`are_transmutable`] for the definition of safe transmutability. +pub fn are_transmutable_ptrs<'tcx>(a: Ty<'tcx>, b: Ty<'tcx>) -> Option { + let a = a.builtin_deref(true)?.ty; + let b = b.builtin_deref(true)?.ty; + Some(are_transmutable(a, b)) +} diff --git a/c2rust-analyze/tests/analyze/string_casts.rs b/c2rust-analyze/tests/analyze/string_casts.rs index 99a650237..9c959e76b 100644 --- a/c2rust-analyze/tests/analyze/string_casts.rs +++ b/c2rust-analyze/tests/analyze/string_casts.rs @@ -1,4 +1,3 @@ -#[cfg(any())] pub fn cast_only(s: *const u8) { s as *const core::ffi::c_char; }