Skip to content

Commit

Permalink
(c2rust-analyze) Relaxed the transmutable checks from two-way to on…
Browse files Browse the repository at this point in the history
…e-way, now allowing for arrays and slices to decay.

This expands the definition of safe transmutability to be one-way.
That is, it checks if `*T as *U` is safe, rather than also `*U as *T`.

Thus, we can now allow for casts decaying
pointers to arrays and slices to pointers to their element type.

`do_unify` is modified to also be one-way,
which it was already in all call sites.

New tests are also added to `string_casts.rs`
for all the types of ptr-to-ptr casts.

Out of the full string cast, `b"" as *const u8 as *const core::ffi::c_char`,
this adds support for the `as *const u8` (from `&[u8; _]`),
so only support for the string literal itself remains.
  • Loading branch information
kkysen committed Feb 16, 2023
1 parent d78b925 commit 3b071f7
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 29 deletions.
4 changes: 2 additions & 2 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, are_transmutable_ptrs, describe_rvalue, RvalueDesc};
use crate::util::{self, describe_rvalue, is_transmutable_ptr_cast, RvalueDesc};
use crate::AssignPointerIds;
use bitflags::bitflags;
use rustc_hir::def_id::DefId;
Expand Down Expand Up @@ -358,7 +358,7 @@ impl<'a, 'tcx> AnalysisCtxt<'a, 'tcx> {
// 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) {
match is_transmutable_ptr_cast(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),
Expand Down
20 changes: 10 additions & 10 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, are_transmutable, describe_rvalue, Callee, RvalueDesc};
use crate::util::{self, describe_rvalue, is_transmutable_to, 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 @@ -201,19 +201,19 @@ impl<'tcx> TypeChecker<'tcx, '_> {

/// Unify corresponding `PointerId`s in `lty1` and `lty2`.
///
/// The two inputs must have compatible ([safely transmutable](are_transmutable)) underlying types.
/// The two inputs must have compatible ([safely transmutable](is_transmutable_to)) underlying types.
/// For any position where the underlying type has a pointer,
/// this function unifies the `PointerId`s that `lty1` and `lty2` have at
/// 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>) {
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()) {
fn do_unify(&mut self, pl_lty: LTy<'tcx>, rv_lty: LTy<'tcx>) {
let pl_ty = pl_lty.ty;
let rv_ty = rv_lty.ty;
assert!(is_transmutable_to(
self.acx.tcx().erase_regions(rv_ty),
self.acx.tcx().erase_regions(pl_ty),
), "types not transmutable (compatible), so PointerId unification cannot be done: *{rv_ty:?} as *{pl_ty:?}");
for (sub_lty1, sub_lty2) in pl_lty.iter().zip(rv_lty.iter()) {
eprintln!("equate {:?} = {:?}", sub_lty1, sub_lty2);
if sub_lty1.label != PointerId::NONE || sub_lty2.label != PointerId::NONE {
assert!(sub_lty1.label != PointerId::NONE);
Expand Down
40 changes: 24 additions & 16 deletions c2rust-analyze/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,19 +307,23 @@ pub fn lty_project<'tcx, L: Debug>(
}
}

/// Determine if two types are safe to transmute to each other.
/// Determine if `from_ty` can be safely transmuted to `to_ty`.
///
/// Safe transmutability is difficult to check abstractly,
/// so here it is limited to integer types of the same size
/// (but potentially different signedness).
/// so here it is limited to
/// * integer types of the same size (but potentially different signedness)
/// * e.x. `*u8 as *i8`
/// * decaying arrays and slices to their element type
/// * e.x. `*[u8; 0] as *u8`
/// * e.x. `*[u8] as *u8`
///
/// 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 = {
pub fn is_transmutable_to<'tcx>(from_ty: Ty<'tcx>, to_ty: Ty<'tcx>) -> bool {
let transmutable_ints = || {
use IntTy::*;
use UintTy::*;
match (a.kind(), b.kind()) {
match (from_ty.kind(), to_ty.kind()) {
(ty::Uint(u), ty::Int(i)) | (ty::Int(i), ty::Uint(u)) => {
matches!((u, i), |(Usize, Isize)| (U8, I8)
| (U16, I16)
Expand All @@ -330,20 +334,24 @@ pub fn are_transmutable<'tcx>(a: Ty<'tcx>, b: Ty<'tcx>) -> bool {
}
};

// only check for transmutable ints so far
a == b || transmutable_ints
let one_way_transmutable = || match from_ty.kind() {
&ty::Array(from_ty, _) | &ty::Slice(from_ty) => is_transmutable_to(from_ty, to_ty),
_ => false,
};

from_ty == to_ty || transmutable_ints() || one_way_transmutable()
}

/// 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.
/// Determine if the `from_ty as to_ty` is a ptr-to-ptr cast.
/// and if it is, if the pointee types are compatible,
/// i.e. they are safely transmutable.
///
/// 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))
/// See [`is_transmutable_to`] for the definition of safe transmutability.
pub fn is_transmutable_ptr_cast<'tcx>(from_ty: Ty<'tcx>, to_ty: Ty<'tcx>) -> Option<bool> {
let from_ty = from_ty.builtin_deref(true)?.ty;
let to_ty = to_ty.builtin_deref(true)?.ty;
Some(is_transmutable_to(from_ty, to_ty))
}
14 changes: 13 additions & 1 deletion c2rust-analyze/tests/analyze/string_casts.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
fn cast_only(s: *const u8) {
fn cast_ptr_to_ptr(s: *const u8) {
s as *const core::ffi::c_char;
}

fn cast_slice_ptr_to_ptr(s: *const [u8]) {
s as *const u8;
}

fn cast_array_to_slice_ptr(s: &[u8; 0]) {
s as *const [u8];
}

fn cast_array_to_ptr(s: &[u8; 0]) {
s as *const u8;
}

#[cfg(any())]
fn cast_from_literal() {
b"" as *const u8 as *const core::ffi::c_char;
Expand Down

0 comments on commit 3b071f7

Please sign in to comment.