Skip to content

Commit 7bf22e0

Browse files
authored
analyze: support rewriting field projections on nullable pointers (#1096)
This adds support for rewriting field projections like `&(*p).x` when `p` is a nullable pointer. The result looks like `Some(&(*p.unwrap()).x)`. I initially tried to avoid a panic when `p` is null by implementing a rewrite using `Option::map`: `p.map(|ptr| &ptr.x)`. However, implementing this correctly wound up being quite complex. It's undefined behavior in C to do `&p->x` when `p == NULL`, so it seems reasonable to introduce a panic in that case. The `mir_op` changes for this are relatively straightforward, but `unlower`, `distribute`, and `convert` needed some work. In particular, `unlower` now has a new variant `MirOriginDesc::LoadFromTempForAdjustment(i)`, which disambiguates cases like this: ```Rust // Rust: f(&(*p).x) // MIR: // Evaluate the main expression: _tmp1 = &(*_p).x; // unlower_map entries for &(*p).x: // * Expr // * StoreIntoLocal // Adjustments inserted to coerce `&T` to `*const T`: _tmp2 = addr_of!(*_tmp1); // * LoadFromTempForAdjustment(0) (load of _tmp1) // * Adjustment(0) (deref) // * Adjustment(1) (addr-of) // * StoreIntoLocal // The actual function call: _result = f(_tmp2); // * LoadFromTemp (load final result of &(*p).x from _tmp2) ``` Previously, the `LoadFromTempForAdjustment` would be recorded as `LoadFromTemp`, meaning there would be two `LoadFromTemp` entries in the unlower_map for the expression `&(*p).x`. Rewrites attached to the first `LoadFromTemp` (in this case, the use of `_tmp1` in the second statement) would be wrongly applied at the site of the last `LoadFromTemp`. This caused `unwrap()` and `Some(_)` rewrites to be applied in the wrong order, causing type errors in the rewritten code.
2 parents d59dbd3 + 8434b34 commit 7bf22e0

File tree

7 files changed

+353
-100
lines changed

7 files changed

+353
-100
lines changed

c2rust-analyze/src/analyze.rs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1581,29 +1581,42 @@ fn run2<'tcx>(
15811581
let acx = gacx.function_context_with_data(&mir, info.acx_data.take());
15821582
let asn = gasn.and(&mut info.lasn);
15831583

1584-
// Generate inline annotations for pointer-typed locals
1585-
for (local, decl) in mir.local_decls.iter_enumerated() {
1586-
let span = local_span(decl);
1584+
let mut emit_lty_annotations = |span, lty: LTy, desc: &str| {
15871585
let mut ptrs = Vec::new();
1588-
let ty_str = context::print_ty_with_pointer_labels(acx.local_tys[local], |ptr| {
1586+
let ty_str = context::print_ty_with_pointer_labels(lty, |ptr| {
15891587
if ptr.is_none() {
15901588
return String::new();
15911589
}
15921590
ptrs.push(ptr);
15931591
format!("{{{}}}", ptr)
15941592
});
15951593
if ptrs.is_empty() {
1596-
continue;
1594+
return;
15971595
}
1598-
// TODO: emit addr_of when it's nontrivial
15991596
// TODO: emit pointee_types when nontrivial
1600-
ann.emit(span, format_args!("typeof({:?}) = {}", local, ty_str));
1597+
ann.emit(span, format_args!("typeof({}) = {}", desc, ty_str));
16011598
for ptr in ptrs {
16021599
ann.emit(
16031600
span,
16041601
format_args!(" {} = {:?}, {:?}", ptr, asn.perms()[ptr], asn.flags()[ptr]),
16051602
);
16061603
}
1604+
};
1605+
1606+
// Generate inline annotations for pointer-typed locals
1607+
for (local, decl) in mir.local_decls.iter_enumerated() {
1608+
let span = local_span(decl);
1609+
// TODO: emit addr_of when it's nontrivial
1610+
let desc = format!("{:?}", local);
1611+
emit_lty_annotations(span, acx.local_tys[local], &desc);
1612+
}
1613+
1614+
for (&loc, &rv_lty) in &acx.rvalue_tys {
1615+
// `loc` must refer to a statement. Terminators don't have `Rvalue`s and thus never
1616+
// appear in `rvalue_tys`.
1617+
let stmt = mir.stmt_at(loc).either(|stmt| stmt, |_term| unreachable!());
1618+
let span = stmt.source_info.span;
1619+
emit_lty_annotations(span, rv_lty, &format!("{:?}", stmt));
16071620
}
16081621

16091622
info.acx_data.set(acx.into_data());

c2rust-analyze/src/rewrite/expr/convert.rs

Lines changed: 43 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -440,20 +440,34 @@ impl<'tcx> Visitor<'tcx> for ConvertVisitor<'tcx> {
440440
.any(|x| matches!(x.desc, MirOriginDesc::Adjustment(_)));
441441
if self.materialize_adjustments || has_adjustment_rewrites {
442442
let adjusts = self.typeck_results.expr_adjustments(ex);
443-
hir_rw = materialize_adjustments(self.tcx, adjusts, hir_rw, |i, hir_rw| {
444-
let adj_rws =
445-
take_prefix_while(&mut mir_rws, |x| x.desc == MirOriginDesc::Adjustment(i));
446-
self.rewrite_from_mir_rws(Some(ex), adj_rws, hir_rw)
447-
});
443+
hir_rw =
444+
materialize_adjustments(self.tcx, adjusts, hir_rw, |step, hir_rw| match step {
445+
AdjustmentStep::Before(i) => {
446+
let load_rws = take_prefix_while(&mut mir_rws, |x| {
447+
x.desc == MirOriginDesc::LoadFromTempForAdjustment(i)
448+
});
449+
self.rewrite_from_mir_rws(Some(ex), load_rws, hir_rw)
450+
}
451+
AdjustmentStep::After(i) => {
452+
let adj_rws = take_prefix_while(&mut mir_rws, |x| {
453+
x.desc == MirOriginDesc::Adjustment(i)
454+
});
455+
self.rewrite_from_mir_rws(Some(ex), adj_rws, hir_rw)
456+
}
457+
});
448458
}
449459

450460
// Apply late rewrites.
451-
assert!(mir_rws.iter().all(|mir_rw| {
452-
matches!(
461+
for mir_rw in mir_rws {
462+
assert!(
463+
matches!(
464+
mir_rw.desc,
465+
MirOriginDesc::StoreIntoLocal | MirOriginDesc::LoadFromTemp
466+
),
467+
"bad desc {:?} for late rewrite: {mir_rw:?}",
453468
mir_rw.desc,
454-
MirOriginDesc::StoreIntoLocal | MirOriginDesc::LoadFromTemp
455-
)
456-
}));
469+
);
470+
}
457471
hir_rw = self.rewrite_from_mir_rws(Some(ex), mir_rws, hir_rw);
458472

459473
if !matches!(hir_rw, Rewrite::Identity) {
@@ -474,7 +488,7 @@ fn mutbl_from_bool(m: bool) -> hir::Mutability {
474488
}
475489
}
476490

477-
fn apply_identity_adjustment<'tcx>(
491+
fn apply_adjustment<'tcx>(
478492
tcx: TyCtxt<'tcx>,
479493
adjustment: &Adjustment<'tcx>,
480494
rw: Rewrite,
@@ -509,41 +523,40 @@ fn apply_identity_adjustment<'tcx>(
509523
}
510524
}
511525

526+
enum AdjustmentStep {
527+
Before(usize),
528+
After(usize),
529+
}
530+
512531
fn materialize_adjustments<'tcx>(
513532
tcx: TyCtxt<'tcx>,
514533
adjustments: &[Adjustment<'tcx>],
515534
hir_rw: Rewrite,
516-
mut callback: impl FnMut(usize, Rewrite) -> Rewrite,
535+
mut callback: impl FnMut(AdjustmentStep, Rewrite) -> Rewrite,
517536
) -> Rewrite {
518537
let adj_kinds: Vec<&_> = adjustments.iter().map(|a| &a.kind).collect();
519538
match (hir_rw, &adj_kinds[..]) {
520-
(Rewrite::Identity, []) => Rewrite::Identity,
521-
(Rewrite::Identity, _) => {
522-
let mut hir_rw = Rewrite::Identity;
523-
for (i, adj) in adjustments.iter().enumerate() {
524-
hir_rw = apply_identity_adjustment(tcx, adj, hir_rw);
525-
hir_rw = callback(i, hir_rw);
526-
}
527-
hir_rw
528-
}
529-
// TODO: ideally we should always materialize all adjustments (removing these special
530-
// cases), and use `MirRewrite`s to eliminate any adjustments we no longer need.
531-
(rw @ Rewrite::Ref(..), &[Adjust::Deref(..), Adjust::Borrow(..)]) => rw,
532-
(rw @ Rewrite::MethodCall(..), &[Adjust::Deref(..), Adjust::Borrow(..)]) => rw,
533539
// The mut-to-const cast should be unneeded once the inner rewrite switches to a safe
534540
// reference type appropriate for the pointer's uses. However, we still want to give
535541
// `callback` a chance to remove the cast itself so that if there's a `RemoveCast` rewrite
536542
// on this adjustment, we don't get an error about it failing to apply.
537-
(rw, &[Adjust::Pointer(PointerCast::MutToConstPointer)]) => {
538-
let mut hir_rw = Rewrite::RemovedCast(Box::new(rw));
539-
hir_rw = callback(0, hir_rw);
543+
(mut hir_rw, &[Adjust::Pointer(PointerCast::MutToConstPointer)]) => {
544+
hir_rw = callback(AdjustmentStep::Before(0), hir_rw);
545+
hir_rw = Rewrite::RemovedCast(Box::new(hir_rw));
546+
hir_rw = callback(AdjustmentStep::After(0), hir_rw);
540547
match hir_rw {
541548
Rewrite::RemovedCast(rw) => *rw,
542549
rw => rw,
543550
}
544551
}
545-
(rw, &[]) => rw,
546-
(rw, adjs) => panic!("rewrite {rw:?} and materializations {adjs:?} NYI"),
552+
(mut hir_rw, _) => {
553+
for (i, adj) in adjustments.iter().enumerate() {
554+
hir_rw = callback(AdjustmentStep::Before(i), hir_rw);
555+
hir_rw = apply_adjustment(tcx, adj, hir_rw);
556+
hir_rw = callback(AdjustmentStep::After(i), hir_rw);
557+
}
558+
hir_rw
559+
}
547560
}
548561
}
549562

c2rust-analyze/src/rewrite/expr/distribute.rs

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use log::*;
55
use rustc_hir::HirId;
66
use rustc_middle::mir::Location;
77
use rustc_middle::ty::TyCtxt;
8+
use std::cmp::Ordering;
89
use std::collections::{BTreeMap, HashMap};
910

1011
struct RewriteInfo {
@@ -19,18 +20,65 @@ struct RewriteInfo {
1920
///
2021
/// The order of variants follows the order of operations we typically see in generated MIR code.
2122
/// For a given HIR `Expr`, the MIR will usually evaluate the expression ([`Priority::Eval`]),
22-
/// apply zero or more adjustments ([`Priority::Adjust(i)`][Priority::Adjust]), store the result
23-
/// into a temporary ([`Priority::_StoreResult`]; currently unused), and later load the result back
24-
/// from the temporary ([`Priority::LoadResult`]).
25-
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
23+
/// apply zero or more adjustments ([`Priority::LoadForAdjust(i)`][Priority::LoadForAdjust] and
24+
/// [`Priority::Adjust(i)`][Priority::Adjust]), store the result into a temporary
25+
/// ([`Priority::_StoreResult`]; currently unused), and later load the result back from the
26+
/// temporary ([`Priority::LoadResult`]).
27+
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
2628
enum Priority {
2729
Eval,
30+
/// Load from a temporary for use in the adjustment at index `i`.
31+
LoadForAdjust(usize),
2832
/// Apply the rewrite just after the adjustment at index `i`.
2933
Adjust(usize),
3034
_StoreResult,
3135
LoadResult,
3236
}
3337

38+
impl PartialOrd for Priority {
39+
fn partial_cmp(&self, other: &Priority) -> Option<Ordering> {
40+
Some(self.cmp(other))
41+
}
42+
}
43+
44+
impl Ord for Priority {
45+
fn cmp(&self, other: &Priority) -> Ordering {
46+
use Priority::*;
47+
match (*self, *other) {
48+
// 1. Eval
49+
(Eval, Eval) => Ordering::Equal,
50+
(Eval, _) => Ordering::Less,
51+
(_, Eval) => Ordering::Greater,
52+
53+
// 2. LoadForAdjust(0), Adjust(0), LoadForAdjust(1), Adjust(1), ...
54+
(LoadForAdjust(i), LoadForAdjust(j)) => i.cmp(&j),
55+
(LoadForAdjust(i), Adjust(j)) => match i.cmp(&j) {
56+
Ordering::Equal => Ordering::Less,
57+
Ordering::Less => Ordering::Less,
58+
Ordering::Greater => Ordering::Greater,
59+
},
60+
(Adjust(i), Adjust(j)) => i.cmp(&j),
61+
(Adjust(i), LoadForAdjust(j)) => match i.cmp(&j) {
62+
Ordering::Equal => Ordering::Greater,
63+
Ordering::Less => Ordering::Less,
64+
Ordering::Greater => Ordering::Greater,
65+
},
66+
(LoadForAdjust(_), _) => Ordering::Less,
67+
(_, LoadForAdjust(_)) => Ordering::Greater,
68+
(Adjust(_), _) => Ordering::Less,
69+
(_, Adjust(_)) => Ordering::Greater,
70+
71+
// 3. _StoreResult
72+
(_StoreResult, _StoreResult) => Ordering::Equal,
73+
(_StoreResult, _) => Ordering::Less,
74+
(_, _StoreResult) => Ordering::Greater,
75+
76+
// 4. LoadResult
77+
(LoadResult, LoadResult) => Ordering::Equal,
78+
}
79+
}
80+
}
81+
3482
#[derive(Clone, Debug)]
3583
pub struct DistRewrite {
3684
pub rw: mir_op::RewriteKind,
@@ -96,6 +144,7 @@ pub fn distribute(
96144
MirOriginDesc::Expr => Priority::Eval,
97145
MirOriginDesc::Adjustment(i) => Priority::Adjust(i),
98146
MirOriginDesc::LoadFromTemp => Priority::LoadResult,
147+
MirOriginDesc::LoadFromTempForAdjustment(i) => Priority::LoadForAdjust(i),
99148
_ => {
100149
panic!(
101150
"can't distribute rewrites onto {:?} origin {:?}\n\

c2rust-analyze/src/rewrite/expr/mir_op.rs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,12 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> {
260260
Some(lty.args[0])
261261
}
262262

263+
fn is_nullable(&self, ptr: PointerId) -> bool {
264+
!ptr.is_none()
265+
&& !self.perms[ptr].contains(PermissionSet::NON_NULL)
266+
&& !self.flags[ptr].contains(FlagSet::FIXED)
267+
}
268+
263269
fn visit_statement(&mut self, stmt: &Statement<'tcx>, loc: Location) {
264270
let _g = panic_detail::set_current_span(stmt.source_info.span);
265271
eprintln!(
@@ -558,6 +564,14 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> {
558564
BorrowKind::Shared | BorrowKind::Shallow | BorrowKind::Unique => false,
559565
};
560566
self.enter_rvalue_place(0, |v| v.visit_place(pl, mutbl));
567+
568+
if let Some(expect_ty) = expect_ty {
569+
if self.is_nullable(expect_ty.label) {
570+
// Nullable (`Option`) output is expected, but `Ref` always produces a
571+
// `NON_NULL` pointer. Cast rvalue from `&T` to `Option<&T>` or similar.
572+
self.emit(RewriteKind::OptionSome);
573+
}
574+
}
561575
}
562576
Rvalue::ThreadLocalRef(_def_id) => {
563577
// TODO
@@ -579,6 +593,9 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> {
579593
}),
580594
_ => (),
581595
}
596+
if desc.option {
597+
self.emit(RewriteKind::OptionSome);
598+
}
582599
}
583600
}
584601
Rvalue::Len(pl) => {
@@ -588,9 +605,7 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> {
588605
if util::is_null_const_operand(op) && ty.is_unsafe_ptr() {
589606
// Special case: convert `0 as *const T` to `None`.
590607
if let Some(rv_lty) = expect_ty {
591-
if !self.perms[rv_lty.label].contains(PermissionSet::NON_NULL)
592-
&& !self.flags[rv_lty.label].contains(FlagSet::FIXED)
593-
{
608+
if self.is_nullable(rv_lty.label) {
594609
self.emit(RewriteKind::ZeroAsPtrToNone);
595610
}
596611
}
@@ -730,9 +745,7 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> {
730745
PlaceElem::Deref => {
731746
self.enter_place_deref_pointer(|v| {
732747
v.visit_place_ref(base_pl, proj_ltys, in_mutable_context);
733-
if !v.perms[base_lty.label].contains(PermissionSet::NON_NULL)
734-
&& !v.flags[base_lty.label].contains(FlagSet::FIXED)
735-
{
748+
if v.is_nullable(base_lty.label) {
736749
// If the pointer type is non-copy, downgrade (borrow) before calling
737750
// `unwrap()`.
738751
let desc = type_desc::perms_to_desc(

0 commit comments

Comments
 (0)