Skip to content

Commit

Permalink
(c2rust-analyze) Support ptr-to-ptr casts between safely transmutab…
Browse files Browse the repository at this point in the history
…le types, for now limited to same-sized integers.

This introduces the concept of equivalent/compatible/safely transmutable types.
This forms an equivalence class among types, as the safe transmutability must be mutual
(i.e. transmutable in both directions; no prefix-transmutability).

Thus, we can now allow ptr-to-ptr casts between safely transmutable pointee types,
whereas previously they were only allowed for equal types.
Equal types could have their `PointerId`s unified as they had the same structure,
which is still of safely transmutability types,
which are safely transmutability because they have the same structure/layout.

As safe transmutability is difficult to check abstractly for any two types,
for now we limit it to commonly transmuted types that we know are definitely transmutable:
same-sized integer types (with potentially different signedness).

Thus, this enables support for string casts like
`b"" as *const u8 as *const core::ffi::c_char`, where `c_char = i8`,
which fixes #840.

Note that the above cast (#833) is still not supported due to the string literal `b""` (#837),
but the cast itself (in `string_casts.rs` in `fn cast_only`) works.
  • Loading branch information
kkysen committed Feb 16, 2023
1 parent 722d54b commit fa98935
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 24 deletions.
33 changes: 16 additions & 17 deletions c2rust-analyze/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 dbg!(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(..)
Expand Down
12 changes: 7 additions & 5 deletions c2rust-analyze/src/dataflow/type_check.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -206,10 +206,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 {
Expand Down
44 changes: 43 additions & 1 deletion c2rust-analyze/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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<bool> {
let a = a.builtin_deref(true)?.ty;
let b = b.builtin_deref(true)?.ty;
Some(are_transmutable(a, b))
}
1 change: 0 additions & 1 deletion c2rust-analyze/tests/analyze/string_casts.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#[cfg(any())]
fn cast_only(s: *const u8) {
s as *const core::ffi::c_char;
}
Expand Down

0 comments on commit fa98935

Please sign in to comment.