Skip to content

Commit eeb12d0

Browse files
committed
(c2rust-analyze) Support ptr-to-ptr casts between safely transmutable 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 #833. Note that the above cast is still not supported due to the string literal `b""` (#837), but the cast itself (in `string_casts.rs` in `fn cast_only`) works.
1 parent 722d54b commit eeb12d0

File tree

4 files changed

+66
-24
lines changed

4 files changed

+66
-24
lines changed

c2rust-analyze/src/context.rs

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::pointer_id::{
33
GlobalPointerTable, LocalPointerTable, NextGlobalPointerId, NextLocalPointerId, PointerTable,
44
PointerTableMut,
55
};
6-
use crate::util::{self, describe_rvalue, RvalueDesc};
6+
use crate::util::{self, are_transmutable_ptrs, describe_rvalue, RvalueDesc};
77
use crate::AssignPointerIds;
88
use bitflags::bitflags;
99
use rustc_hir::def_id::DefId;
@@ -349,23 +349,22 @@ impl<'a, 'tcx> AnalysisCtxt<'a, 'tcx> {
349349
Rvalue::Cast(_, ref op, ty) => {
350350
let op_lty = self.type_of(op);
351351

352-
// We support this category of pointer casts as a special case.
353-
let op_is_ptr = matches!(op_lty.ty.kind(), TyKind::Ref(..) | TyKind::RawPtr(..));
354-
let op_pointee = op_is_ptr.then(|| op_lty.args[0]);
355-
let ty_pointee = match *ty.kind() {
356-
TyKind::Ref(_, ty, _) => Some(ty),
357-
TyKind::RawPtr(tm) => Some(tm.ty),
358-
_ => None,
359-
};
360-
if op_pointee.is_some() && op_pointee.map(|lty| lty.ty) == ty_pointee {
361-
// The source and target types are both pointers, and they have identical
362-
// pointee types. We label the target type with the same `PointerId`s as the
363-
// source type in all positions. This works because the two types have the
364-
// same structure.
365-
return self.lcx().mk(ty, op_lty.args, op_lty.label);
352+
// We only support pointer casts when:
353+
// * both types are pointers
354+
// * they have compatible (safely transmutable) pointee types
355+
// Safe transmutability is difficult to check abstractly,
356+
// so we limit it to integer types of the same size
357+
// (but potentially different signedness).
358+
// In particular, this allows casts from `*u8` to `core::ffi::c_char`.
359+
let from_ty = op_lty.ty;
360+
let to_ty = ty;
361+
match dbg!(are_transmutable_ptrs(from_ty, to_ty)) {
362+
// Label the to type with the same [`PointerId`]s as the from type in all positions.
363+
// This works because the two types have the same structure.
364+
Some(true) => self.lcx().mk(ty, op_lty.args, op_lty.label),
365+
Some(false) => todo!("unsupported ptr-to-ptr cast between pointee types not yet supported as safely transmutable: `{from_ty:?} as {to_ty:?}`"),
366+
None => label_no_pointers(self, ty),
366367
}
367-
368-
label_no_pointers(self, ty)
369368
}
370369
Rvalue::Len(..)
371370
| Rvalue::BinaryOp(..)

c2rust-analyze/src/dataflow/type_check.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use super::DataflowConstraints;
22
use crate::context::{AnalysisCtxt, LTy, PermissionSet, PointerId};
3-
use crate::util::{self, describe_rvalue, Callee, RvalueDesc};
3+
use crate::util::{self, are_transmutable, describe_rvalue, Callee, RvalueDesc};
44
use rustc_hir::def_id::DefId;
55
use rustc_middle::mir::{
66
AggregateKind, BinOp, Body, Location, Mutability, Operand, Place, PlaceRef, ProjectionElem,
@@ -206,10 +206,12 @@ impl<'tcx> TypeChecker<'tcx, '_> {
206206
/// that position. For example, given `lty1 = *mut /*l1*/ *const /*l2*/ u8` and `lty2 = *mut
207207
/// /*l3*/ *const /*l4*/ u8`, this function will unify `l1` with `l3` and `l2` with `l4`.
208208
fn do_unify(&mut self, lty1: LTy<'tcx>, lty2: LTy<'tcx>) {
209-
assert_eq!(
210-
self.acx.tcx().erase_regions(lty1.ty),
211-
self.acx.tcx().erase_regions(lty2.ty)
212-
);
209+
let ty1 = lty1.ty;
210+
let ty2 = lty2.ty;
211+
assert!(are_transmutable(
212+
self.acx.tcx().erase_regions(ty1),
213+
self.acx.tcx().erase_regions(ty2),
214+
), "types not transmutable (compatible), so PointerId unification cannot be done: {ty1:?} !~ {ty2:?}");
213215
for (sub_lty1, sub_lty2) in lty1.iter().zip(lty2.iter()) {
214216
eprintln!("equate {:?} = {:?}", sub_lty1, sub_lty2);
215217
if sub_lty1.label != PointerId::NONE || sub_lty2.label != PointerId::NONE {

c2rust-analyze/src/util.rs

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ use rustc_hir::def_id::DefId;
44
use rustc_middle::mir::{
55
Field, Local, Mutability, Operand, PlaceElem, PlaceRef, ProjectionElem, Rvalue,
66
};
7-
use rustc_middle::ty::{AdtDef, DefIdTree, SubstsRef, Ty, TyCtxt, TyKind, UintTy};
7+
use rustc_middle::ty::{self, AdtDef, DefIdTree, SubstsRef, Ty, TyCtxt, TyKind, UintTy};
8+
use rustc_type_ir::IntTy;
89
use std::fmt::Debug;
910

1011
#[derive(Debug)]
@@ -305,3 +306,44 @@ pub fn lty_project<'tcx, L: Debug>(
305306
ProjectionElem::Downcast(..) => todo!("type_of Downcast"),
306307
}
307308
}
309+
310+
/// Determine if two types are safe to transmute to each other.
311+
///
312+
/// Safe transmutability is difficult to check abstractly,
313+
/// so here it is limited to integer types of the same size
314+
/// (but potentially different signedness).
315+
///
316+
/// Thus, [`true`] means it is definitely transmutable,
317+
/// while [`false`] means it may not be transmutable.
318+
pub fn are_transmutable<'tcx>(a: Ty<'tcx>, b: Ty<'tcx>) -> bool {
319+
let transmutable_ints = {
320+
use IntTy::*;
321+
use UintTy::*;
322+
match (a.kind(), b.kind()) {
323+
(ty::Uint(u), ty::Int(i)) | (ty::Int(i), ty::Uint(u)) => {
324+
matches!((u, i), |(Usize, Isize)| (U8, I8)
325+
| (U16, I16)
326+
| (U32, I32)
327+
| (U64, I64))
328+
}
329+
_ => false,
330+
}
331+
};
332+
333+
// only check for transmutable ints so far
334+
a == b || transmutable_ints
335+
}
336+
337+
/// Determine if two types (e.x. in a cast) are pointers,
338+
/// and if they are, if the pointee types are compatible,
339+
/// i.e. they are safely transmutable to each other.
340+
///
341+
/// This returns [`Some`]`(is_transmutable)` if they're both pointers,
342+
/// and [`None`] if its some other types.
343+
///
344+
/// See [`are_transmutable`] for the definition of safe transmutability.
345+
pub fn are_transmutable_ptrs<'tcx>(a: Ty<'tcx>, b: Ty<'tcx>) -> Option<bool> {
346+
let a = a.builtin_deref(true)?.ty;
347+
let b = b.builtin_deref(true)?.ty;
348+
Some(are_transmutable(a, b))
349+
}

c2rust-analyze/tests/analyze/string_casts.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
#[cfg(any())]
21
fn cast_only(s: *const u8) {
32
s as *const core::ffi::c_char;
43
}

0 commit comments

Comments
 (0)