diff --git a/c2rust-analyze/src/dataflow/type_check.rs b/c2rust-analyze/src/dataflow/type_check.rs index 4bd7e37074..a27fa00222 100644 --- a/c2rust-analyze/src/dataflow/type_check.rs +++ b/c2rust-analyze/src/dataflow/type_check.rs @@ -5,7 +5,7 @@ use crate::pointee_type::PointeeTypes; use crate::pointer_id::PointerTable; use crate::recent_writes::RecentWrites; use crate::util::{ - describe_rvalue, is_null_const, is_transmutable_ptr_cast, ty_callee, Callee, RvalueDesc, + self, describe_rvalue, is_transmutable_ptr_cast, ty_callee, Callee, RvalueDesc, UnknownDefCallee, }; use assert_matches::assert_matches; @@ -120,7 +120,7 @@ impl<'tcx> TypeChecker<'tcx, '_> { CastKind::PointerFromExposedAddress => { // We support only one case here, which is the case of null pointers // constructed via casts such as `0 as *const T` - if !op.constant().copied().map(is_null_const).unwrap_or(false) { + if !util::is_null_const_operand(op) { panic!("Creating non-null pointers from exposed addresses not supported"); } // The target type of the cast must not have `NON_NULL` permission. diff --git a/c2rust-analyze/src/rewrite/apply.rs b/c2rust-analyze/src/rewrite/apply.rs index bd9d9d4ad9..2f9370a28b 100644 --- a/c2rust-analyze/src/rewrite/apply.rs +++ b/c2rust-analyze/src/rewrite/apply.rs @@ -411,6 +411,13 @@ impl Emitter<'_, S> { self.emit(rw, 0) } + Rewrite::Closure1(ref name, ref rw) => { + self.emit_str("|")?; + self.emit_str(name)?; + self.emit_str("| ")?; + self.emit(rw, 0) + } + Rewrite::TyPtr(ref rw, mutbl) => { match mutbl { Mutability::Not => self.emit_str("*const ")?, diff --git a/c2rust-analyze/src/rewrite/expr/convert.rs b/c2rust-analyze/src/rewrite/expr/convert.rs index 164af12ccf..2a9991cb05 100644 --- a/c2rust-analyze/src/rewrite/expr/convert.rs +++ b/c2rust-analyze/src/rewrite/expr/convert.rs @@ -119,6 +119,287 @@ impl<'tcx> ConvertVisitor<'tcx> { } rw_sub } + + fn rewrite_from_mir_rw( + &self, + ex: Option<&'tcx hir::Expr<'tcx>>, + rw: &mir_op::RewriteKind, + hir_rw: Rewrite, + ) -> Rewrite { + if ex.is_none() { + return convert_cast_rewrite(rw, hir_rw); + } + let ex = ex.unwrap(); + + // Cases that extract a subexpression are handled here; cases that only wrap the + // top-level expression (and thus can handle a non-`Identity` `hir_rw`) are handled by + // `convert_cast_rewrite`. + match *rw { + mir_op::RewriteKind::OffsetSlice { mutbl } => { + // `p.offset(i)` -> `&p[i as usize ..]` + assert!(matches!(hir_rw, Rewrite::Identity)); + let arr = self.get_subexpr(ex, 0); + let idx = Rewrite::Cast( + Box::new(self.get_subexpr(ex, 1)), + Box::new(Rewrite::Print("usize".to_owned())), + ); + let elem = Rewrite::SliceRange(Box::new(arr), Some(Box::new(idx)), None); + Rewrite::Ref(Box::new(elem), mutbl_from_bool(mutbl)) + } + + mir_op::RewriteKind::OptionMapOffsetSlice { mutbl } => { + // `p.offset(i)` -> `p.as_ref().map(|p| &p[i as usize ..])` + assert!(matches!(hir_rw, Rewrite::Identity)); + + // Build let binding + let arr = self.get_subexpr(ex, 0); + let idx = Rewrite::Cast( + Box::new(self.get_subexpr(ex, 1)), + Box::new(Rewrite::Print("usize".to_owned())), + ); + let rw_let = Rewrite::Let(vec![("arr".into(), arr), ("idx".into(), idx)]); + let arr = Rewrite::Text("arr".into()); + let idx = Rewrite::Text("idx".into()); + + // Build closure + let elem = Rewrite::SliceRange(Box::new(arr.clone()), Some(Box::new(idx)), None); + let ref_elem = Rewrite::Ref(Box::new(elem), mutbl_from_bool(mutbl)); + let closure = Rewrite::Closure1("arr".into(), Box::new(ref_elem)); + + // Call `Option::map` + let call = Rewrite::MethodCall("map".into(), Box::new(arr), vec![closure]); + Rewrite::Block(vec![rw_let], Some(Box::new(call))) + } + + mir_op::RewriteKind::RemoveAsPtr => { + // `slice.as_ptr()` -> `slice` + assert!(matches!(hir_rw, Rewrite::Identity)); + self.get_subexpr(ex, 0) + } + + mir_op::RewriteKind::RemoveCast => { + // `x as T` -> `x` + match hir_rw { + Rewrite::Identity => { + assert!(matches!(hir_rw, Rewrite::Identity)); + self.get_subexpr(ex, 0) + } + // Can happen when attempting to delete a cast adjustment. + Rewrite::Cast(rw, _) => *rw, + Rewrite::RemovedCast(rw) => *rw, + _ => panic!("unexpected hir_rw {hir_rw:?} for RawToRef"), + } + } + + mir_op::RewriteKind::RawToRef { mutbl } => { + // &raw _ to &_ or &raw mut _ to &mut _ + match hir_rw { + Rewrite::Identity => { + Rewrite::Ref(Box::new(self.get_subexpr(ex, 0)), mutbl_from_bool(mutbl)) + } + Rewrite::AddrOf(rw, mutbl) => Rewrite::Ref(rw, mutbl), + _ => panic!("unexpected hir_rw {hir_rw:?} for RawToRef"), + } + } + + mir_op::RewriteKind::IsNullToIsNone => { + // `p.is_null()` -> `p.is_none()` + assert!(matches!(hir_rw, Rewrite::Identity)); + Rewrite::MethodCall("is_none".into(), Box::new(self.get_subexpr(ex, 0)), vec![]) + } + mir_op::RewriteKind::IsNullToConstFalse => { + // `p.is_null()` -> `false` + assert!(matches!(hir_rw, Rewrite::Identity)); + Rewrite::Text("false".into()) + } + mir_op::RewriteKind::PtrNullToNone => { + // `ptr::null()` -> `None` + assert!(matches!(hir_rw, Rewrite::Identity)); + Rewrite::Text("None".into()) + } + mir_op::RewriteKind::ZeroAsPtrToNone => { + // `0 as *const T` -> `None` + assert!(matches!(hir_rw, Rewrite::Identity)); + Rewrite::Text("None".into()) + } + + mir_op::RewriteKind::MemcpySafe { + elem_size, + dest_single, + src_single, + } => { + // `memcpy(dest, src, n)` to a `copy_from_slice` call + assert!(matches!(hir_rw, Rewrite::Identity)); + assert!(!dest_single, "&T -> &[T] conversion for memcpy dest NYI"); + assert!(!src_single, "&T -> &[T] conversion for memcpy src NYI"); + Rewrite::Block( + vec![ + Rewrite::Let(vec![ + ("dest".into(), self.get_subexpr(ex, 0)), + ("src".into(), self.get_subexpr(ex, 1)), + ("byte_len".into(), self.get_subexpr(ex, 2)), + ]), + Rewrite::Let(vec![( + "n".into(), + format_rewrite!("byte_len as usize / {elem_size}"), + )]), + Rewrite::MethodCall( + "copy_from_slice".into(), + Box::new(format_rewrite!("dest[..n]")), + vec![format_rewrite!("&src[..n]")], + ), + ], + Some(Box::new(format_rewrite!("dest"))), + ) + } + + mir_op::RewriteKind::MemsetZeroize { + ref zero_ty, + elem_size, + dest_single, + } => { + // `memset(dest, 0, n)` to assignments that zero out each field of `*dest` + assert!(matches!(hir_rw, Rewrite::Identity)); + let zeroize_body = if dest_single { + Rewrite::Text(generate_zeroize_code(zero_ty, "(*dest)")) + } else { + format_rewrite!( + "for i in 0..n {{\n {};\n}}", + generate_zeroize_code(zero_ty, "(*dest)[i]") + ) + }; + Rewrite::Block( + vec![ + Rewrite::Let(vec![ + ("dest".into(), self.get_subexpr(ex, 0)), + ("val".into(), self.get_subexpr(ex, 1)), + ("byte_len".into(), self.get_subexpr(ex, 2)), + ]), + Rewrite::Let(vec![( + "n".into(), + format_rewrite!("byte_len as usize / {elem_size}"), + )]), + format_rewrite!("assert_eq!(val, 0, \"non-zero memset NYI\")"), + zeroize_body, + ], + Some(Box::new(format_rewrite!("dest"))), + ) + } + + mir_op::RewriteKind::CellGet => { + // `*x` to `Cell::get(x)` + assert!(matches!(hir_rw, Rewrite::Identity)); + Rewrite::MethodCall("get".to_string(), Box::new(self.get_subexpr(ex, 0)), vec![]) + } + + mir_op::RewriteKind::CellSet => { + // `*x` to `Cell::set(x)` + assert!(matches!(hir_rw, Rewrite::Identity)); + let deref_lhs = assert_matches!(ex.kind, ExprKind::Assign(lhs, ..) => lhs); + let lhs = self.get_subexpr(deref_lhs, 0); + let rhs = self.get_subexpr(ex, 1); + Rewrite::MethodCall("set".to_string(), Box::new(lhs), vec![rhs]) + } + + _ => convert_cast_rewrite(rw, hir_rw), + } + } + + /// Generate an `Option::map` call from the rewrites in `mir_rws`. After seeing an + /// `OptionMapBegin` in a list of MIR rewrites, pass the remaining rewrites to this method. If + /// it returns `Ok((new_hir_rw, remaining_mir_rws))`, then the `OptionMapBegin` and some + /// additional rewrites have been processed, and only `remaining_mir_rws` are left. Otherwise, + /// if it failed to process any rewrites, it returns `Err(original_hir_rw)`. + fn try_rewrite_option_map<'a>( + &self, + mut mir_rws: &'a [DistRewrite], + mut hir_rw: Rewrite, + ) -> Result<(Rewrite, &'a [DistRewrite]), Rewrite> { + /// Find the next `OptionMapEnd` and return the parts of `mir_rws` that come strictly + /// before it and strictly after it. Returns `None` if there's an `OptionMapBegin` before + /// the next `OptionMapEnd`, or if there's no `OptionMapEnd` found in `mir_rws`. + fn split_option_map_rewrites( + mir_rws: &[DistRewrite], + ) -> Option<(&[DistRewrite], &[DistRewrite])> { + for (i, mir_rw) in mir_rws.iter().enumerate() { + match mir_rw.rw { + // Bail out if we see nested delimiters. This prevents the special + // `Option::map` rewrite from applying, so the caller will fall back on the + // `unwrap()` + `Some(_)` rewrites for `OptionMapBegin/End` that are + // implemented in `convert_cast_rewrite`. + mir_op::RewriteKind::OptionMapBegin => return None, + mir_op::RewriteKind::OptionMapEnd => { + let (a, b) = mir_rws.split_at(i); + return Some((a, &b[1..])); + } + _ => {} + } + } + None + } + + // Build up the body of the closure. We expect `mir_rws` to start just after an + // `OptionMapBegin` delimiter. If we find a matching `OptionMapEnd` (with no intervening + // `OptionMapBegin`; nesting is unsupported), then we add all the rewrites between the + // delimiters to the `Option::map` body. Furthermore, as long as the `OptionMapEnd` is + // followed by another `OptionMapBegin/End` delimited range, we add those rewrites to the + // body as well. + let mut opt_body_hir_rw = None; + // Did we find a matching delimiter for the implicit `OptionMapBegin` that precedes + // `mir_rws`? + let mut found_matching_delim = false; + while let Some((body_rws, other_rws)) = split_option_map_rewrites(mir_rws) { + found_matching_delim = true; + mir_rws = other_rws; + if body_rws.is_empty() { + // Don't initialize `opt_body_hir_rw` until we've seen at least one actual rewrite. + // This lets us omit empty `Option::map` calls. + continue; + } + + let mut body_hir_rw = opt_body_hir_rw.unwrap_or_else(|| Rewrite::Text("__ptr".into())); + body_hir_rw = self.rewrite_from_mir_rws(None, body_rws, body_hir_rw); + + opt_body_hir_rw = Some(body_hir_rw); + } + + if !found_matching_delim { + return Err(hir_rw); + } + + // If some actual rewrites were collected, generate the `map` call. + if let Some(body) = opt_body_hir_rw { + let closure = Rewrite::Closure1("__ptr".into(), Box::new(body)); + hir_rw = Rewrite::MethodCall("map".into(), Box::new(hir_rw), vec![closure]); + } + return Ok((hir_rw, mir_rws)); + } + + fn rewrite_from_mir_rws( + &self, + ex: Option<&'tcx hir::Expr<'tcx>>, + mir_rws: &[DistRewrite], + mut hir_rw: Rewrite, + ) -> Rewrite { + let mut iter = mir_rws.iter(); + while let Some(mir_rw) = iter.next() { + if let mir_op::RewriteKind::OptionMapBegin = mir_rw.rw { + match self.try_rewrite_option_map(iter.as_slice(), hir_rw) { + Ok((new_hir_rw, remaining_mir_rws)) => { + hir_rw = new_hir_rw; + iter = remaining_mir_rws.iter(); + continue; + } + Err(orig_hir_rw) => { + hir_rw = orig_hir_rw; + } + } + } + + hir_rw = self.rewrite_from_mir_rw(ex, &mir_rw.rw, hir_rw); + } + hir_rw + } } impl<'tcx> Visitor<'tcx> for ConvertVisitor<'tcx> { @@ -146,148 +427,12 @@ impl<'tcx> Visitor<'tcx> for ConvertVisitor<'tcx> { intravisit::walk_expr(this, ex); }); - let rewrite_from_mir_rws = |rw: &mir_op::RewriteKind, hir_rw: Rewrite| -> Rewrite { - // Cases that extract a subexpression are handled here; cases that only wrap the - // top-level expression (and thus can handle a non-`Identity` `hir_rw`) are handled by - // `convert_cast_rewrite`. - match *rw { - mir_op::RewriteKind::OffsetSlice { mutbl } => { - // `p.offset(i)` -> `&p[i as usize ..]` - assert!(matches!(hir_rw, Rewrite::Identity)); - let arr = self.get_subexpr(ex, 0); - let idx = Rewrite::Cast( - Box::new(self.get_subexpr(ex, 1)), - Box::new(Rewrite::Print("usize".to_owned())), - ); - let elem = Rewrite::SliceRange(Box::new(arr), Some(Box::new(idx)), None); - Rewrite::Ref(Box::new(elem), mutbl_from_bool(mutbl)) - } - - mir_op::RewriteKind::RemoveAsPtr => { - // `slice.as_ptr()` -> `slice` - assert!(matches!(hir_rw, Rewrite::Identity)); - self.get_subexpr(ex, 0) - } - - mir_op::RewriteKind::RemoveCast => { - // `x as T` -> `x` - match hir_rw { - Rewrite::Identity => { - assert!(matches!(hir_rw, Rewrite::Identity)); - self.get_subexpr(ex, 0) - } - // Can happen when attempting to delete a cast adjustment. - Rewrite::Cast(rw, _) => *rw, - Rewrite::RemovedCast(rw) => *rw, - _ => panic!("unexpected hir_rw {hir_rw:?} for RawToRef"), - } - } - - mir_op::RewriteKind::RawToRef { mutbl } => { - // &raw _ to &_ or &raw mut _ to &mut _ - match hir_rw { - Rewrite::Identity => { - Rewrite::Ref(Box::new(self.get_subexpr(ex, 0)), mutbl_from_bool(mutbl)) - } - Rewrite::AddrOf(rw, mutbl) => Rewrite::Ref(rw, mutbl), - _ => panic!("unexpected hir_rw {hir_rw:?} for RawToRef"), - } - } - - mir_op::RewriteKind::MemcpySafe { - elem_size, - dest_single, - src_single, - } => { - // `memcpy(dest, src, n)` to a `copy_from_slice` call - assert!(matches!(hir_rw, Rewrite::Identity)); - assert!(!dest_single, "&T -> &[T] conversion for memcpy dest NYI"); - assert!(!src_single, "&T -> &[T] conversion for memcpy src NYI"); - Rewrite::Block( - vec![ - Rewrite::Let(vec![ - ("dest".into(), self.get_subexpr(ex, 0)), - ("src".into(), self.get_subexpr(ex, 1)), - ("byte_len".into(), self.get_subexpr(ex, 2)), - ]), - Rewrite::Let(vec![( - "n".into(), - format_rewrite!("byte_len as usize / {elem_size}"), - )]), - Rewrite::MethodCall( - "copy_from_slice".into(), - Box::new(format_rewrite!("dest[..n]")), - vec![format_rewrite!("&src[..n]")], - ), - ], - Some(Box::new(format_rewrite!("dest"))), - ) - } - - mir_op::RewriteKind::MemsetZeroize { - ref zero_ty, - elem_size, - dest_single, - } => { - // `memcpy(dest, src, n)` to a `copy_from_slice` call - assert!(matches!(hir_rw, Rewrite::Identity)); - let zeroize_body = if dest_single { - Rewrite::Text(generate_zeroize_code(zero_ty, "(*dest)")) - } else { - format_rewrite!( - "for i in 0..n {{\n {};\n}}", - generate_zeroize_code(zero_ty, "(*dest)[i]") - ) - }; - Rewrite::Block( - vec![ - Rewrite::Let(vec![ - ("dest".into(), self.get_subexpr(ex, 0)), - ("val".into(), self.get_subexpr(ex, 1)), - ("byte_len".into(), self.get_subexpr(ex, 2)), - ]), - Rewrite::Let(vec![( - "n".into(), - format_rewrite!("byte_len as usize / {elem_size}"), - )]), - format_rewrite!("assert_eq!(val, 0, \"non-zero memset NYI\")"), - zeroize_body, - ], - Some(Box::new(format_rewrite!("dest"))), - ) - } - - mir_op::RewriteKind::CellGet => { - // `*x` to `Cell::get(x)` - assert!(matches!(hir_rw, Rewrite::Identity)); - Rewrite::MethodCall( - "get".to_string(), - Box::new(self.get_subexpr(ex, 0)), - vec![], - ) - } - - mir_op::RewriteKind::CellSet => { - // `*x` to `Cell::set(x)` - assert!(matches!(hir_rw, Rewrite::Identity)); - let deref_lhs = assert_matches!(ex.kind, ExprKind::Assign(lhs, ..) => lhs); - let lhs = self.get_subexpr(deref_lhs, 0); - let rhs = self.get_subexpr(ex, 1); - Rewrite::MethodCall("set".to_string(), Box::new(lhs), vec![rhs]) - } - - _ => convert_cast_rewrite(rw, hir_rw), - } - }; - // Apply rewrites on the expression itself. These will be the first rewrites in the sorted // list produced by `distribute`. let expr_rws = take_prefix_while(&mut mir_rws, |x: &DistRewrite| { matches!(x.desc, MirOriginDesc::Expr) }); - for mir_rw in expr_rws { - hir_rw = rewrite_from_mir_rws(&mir_rw.rw, hir_rw); - } + hir_rw = self.rewrite_from_mir_rws(Some(ex), expr_rws, hir_rw); // Materialize adjustments if requested by an ancestor or required locally. let has_adjustment_rewrites = mir_rws @@ -295,25 +440,21 @@ impl<'tcx> Visitor<'tcx> for ConvertVisitor<'tcx> { .any(|x| matches!(x.desc, MirOriginDesc::Adjustment(_))); if self.materialize_adjustments || has_adjustment_rewrites { let adjusts = self.typeck_results.expr_adjustments(ex); - hir_rw = materialize_adjustments(self.tcx, adjusts, hir_rw, |i, mut hir_rw| { + hir_rw = materialize_adjustments(self.tcx, adjusts, hir_rw, |i, hir_rw| { let adj_rws = take_prefix_while(&mut mir_rws, |x| x.desc == MirOriginDesc::Adjustment(i)); - for mir_rw in adj_rws { - eprintln!("would apply {mir_rw:?} for adjustment #{i}, over {hir_rw:?}"); - hir_rw = rewrite_from_mir_rws(&mir_rw.rw, hir_rw); - } - hir_rw + self.rewrite_from_mir_rws(Some(ex), adj_rws, hir_rw) }); } // Apply late rewrites. - for mir_rw in mir_rws { - assert!(matches!( + assert!(mir_rws.iter().all(|mir_rw| { + matches!( mir_rw.desc, MirOriginDesc::StoreIntoLocal | MirOriginDesc::LoadFromTemp - )); - hir_rw = rewrite_from_mir_rws(&mir_rw.rw, hir_rw); - } + ) + })); + hir_rw = self.rewrite_from_mir_rws(Some(ex), mir_rws, hir_rw); if !matches!(hir_rw, Rewrite::Identity) { eprintln!( @@ -470,6 +611,45 @@ pub fn convert_cast_rewrite(kind: &mir_op::RewriteKind, hir_rw: Rewrite) -> Rewr Rewrite::Ref(Box::new(place), hir::Mutability::Not) } + mir_op::RewriteKind::OptionUnwrap => { + // `p` -> `p.unwrap()` + Rewrite::MethodCall("unwrap".to_string(), Box::new(hir_rw), vec![]) + } + mir_op::RewriteKind::OptionSome => { + // `p` -> `Some(p)` + Rewrite::Call("std::option::Option::Some".to_string(), vec![hir_rw]) + } + + mir_op::RewriteKind::OptionMapBegin => { + // `p` -> `p.unwrap()` + Rewrite::MethodCall("unwrap /*map_begin*/".to_string(), Box::new(hir_rw), vec![]) + } + mir_op::RewriteKind::OptionMapEnd => { + // `p` -> `Some(p)` + Rewrite::Call( + "std::option::Option::Some /*map_end*/".to_string(), + vec![hir_rw], + ) + } + + mir_op::RewriteKind::OptionDowngrade { mutbl, deref } => { + // `p` -> `Some(p)` + let ref_method = if deref { + if mutbl { + "as_deref_mut".into() + } else { + "as_deref".into() + } + } else { + if mutbl { + "as_mut".into() + } else { + "as_ref".into() + } + }; + Rewrite::MethodCall(ref_method, Box::new(hir_rw), vec![]) + } + mir_op::RewriteKind::CastRefToRaw { mutbl } => { // `addr_of!(*p)` is cleaner than `p as *const _`; we don't know the pointee // type here, so we can't emit `p as *const T`. diff --git a/c2rust-analyze/src/rewrite/expr/mir_op.rs b/c2rust-analyze/src/rewrite/expr/mir_op.rs index e5b3917526..a490b80deb 100644 --- a/c2rust-analyze/src/rewrite/expr/mir_op.rs +++ b/c2rust-analyze/src/rewrite/expr/mir_op.rs @@ -12,11 +12,12 @@ use crate::panic_detail; use crate::pointee_type::PointeeTypes; use crate::pointer_id::{PointerId, PointerTable}; use crate::type_desc::{self, Ownership, Quantity, TypeDesc}; -use crate::util::{ty_callee, Callee}; +use crate::util::{self, ty_callee, Callee}; +use log::{error, trace}; use rustc_ast::Mutability; use rustc_middle::mir::{ - BasicBlock, Body, Location, Operand, Place, Rvalue, Statement, StatementKind, Terminator, - TerminatorKind, + BasicBlock, Body, BorrowKind, Location, Operand, Place, PlaceElem, PlaceRef, Rvalue, Statement, + StatementKind, Terminator, TerminatorKind, }; use rustc_middle::ty::print::FmtPrinter; use rustc_middle::ty::print::Print; @@ -41,14 +42,20 @@ pub enum SubLoc { RvaluePlace(usize), /// The place referenced by an operand. `Operand::Move/Operand::Copy -> Place` OperandPlace, - /// The pointer used in the Nth innermost deref within a place. `Place -> Place` - PlacePointer(usize), + /// The pointer used in a deref projection. `Place -> Place` + PlaceDerefPointer, + /// The base of a field projection. `Place -> Place` + PlaceFieldBase, + /// The array used in an index or slice projection. `Place -> Place` + PlaceIndexArray, } #[derive(Clone, PartialEq, Eq, Debug)] pub enum RewriteKind { /// Replace `ptr.offset(i)` with something like `&ptr[i..]`. OffsetSlice { mutbl: bool }, + /// Replace `ptr.offset(i)` with something like `ptr.as_ref().map(|p| &p[i..])`. + OptionMapOffsetSlice { mutbl: bool }, /// Replace `slice` with `&slice[0]`. SliceFirst { mutbl: bool }, /// Replace `ptr` with `&*ptr`, converting `&mut T` to `&T`. @@ -60,6 +67,16 @@ pub enum RewriteKind { /// Replace &raw with & or &raw mut with &mut RawToRef { mutbl: bool }, + /// Replace `ptr.is_null()` with `ptr.is_none()`. + IsNullToIsNone, + /// Replace `ptr.is_null()` with the constant `false`. We use this in cases where the rewritten + /// type of `ptr` is non-optional because we inferred `ptr` to be non-nullable. + IsNullToConstFalse, + /// Replace `ptr::null()` or `ptr::null_mut()` with `None`. + PtrNullToNone, + /// Replace `0 as *const T` or `0 as *mut T` with `None`. + ZeroAsPtrToNone, + /// Replace a call to `memcpy(dest, src, n)` with a safe copy operation that works on slices /// instead of raw pointers. `elem_size` is the size of the original, unrewritten pointee /// type, which is used to convert the byte length `n` to an element count. `dest_single` and @@ -78,6 +95,24 @@ pub enum RewriteKind { dest_single: bool, }, + /// Convert `Option` to `T` by calling `.unwrap()`. + OptionUnwrap, + /// Convert `T` to `Option` by wrapping the value in `Some`. + OptionSome, + /// Begin an `Option::map` operation, converting `Option` to `T`. + OptionMapBegin, + /// End an `Option::map` operation, converting `T` to `Option`. + /// + /// `OptionMapBegin` and `OptionMapEnd` could legally be implemented as aliases for + /// `OptionUnwrap` and `OptionSome` respectively. However, when `OptionMapBegin` and + /// `OptionMapEnd` are paired, we instead emit a call to `Option::map` with the intervening + /// rewrites applied within the closure. This has the same effect when the input is `Some`, + /// but passes through `None` unchanged instead of panicking. + OptionMapEnd, + /// Downgrade ownership of an `Option` to `Option<&_>` or `Option<&mut _>` by calling + /// `as_ref()`/`as_mut()` and optionally `as_deref()`/`as_deref_mut()`. + OptionDowngrade { mutbl: bool, deref: bool }, + /// Cast `&T` to `*const T` or `&mut T` to `*mut T`. CastRefToRaw { mutbl: bool }, /// Cast `*const T` to `*mut T` or vice versa. If `to_mutbl` is true, we are casting to @@ -188,14 +223,20 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { self.enter(SubLoc::RvaluePlace(i), f) } - #[allow(dead_code)] - fn _enter_operand_place R, R>(&mut self, f: F) -> R { + fn enter_operand_place R, R>(&mut self, f: F) -> R { self.enter(SubLoc::OperandPlace, f) } - #[allow(dead_code)] - fn _enter_place_pointer R, R>(&mut self, i: usize, f: F) -> R { - self.enter(SubLoc::PlacePointer(i), f) + fn enter_place_deref_pointer R, R>(&mut self, f: F) -> R { + self.enter(SubLoc::PlaceDerefPointer, f) + } + + fn enter_place_field_base R, R>(&mut self, f: F) -> R { + self.enter(SubLoc::PlaceFieldBase, f) + } + + fn enter_place_index_array R, R>(&mut self, f: F) -> R { + self.enter(SubLoc::PlaceIndexArray, f) } /// Get the pointee type of `lty`. Returns the inferred pointee type from `self.pointee_types` @@ -299,7 +340,7 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { self.enter_rvalue(|v| v.visit_rvalue(rv, Some(rv_lty))); // The cast from `rv_lty` to `pl_lty` should be applied to the RHS. self.enter_rvalue(|v| v.emit_cast_lty_lty(rv_lty, pl_lty)); - self.enter_dest(|v| v.visit_place(pl)); + self.enter_dest(|v| v.visit_place(pl, true)); } StatementKind::FakeRead(..) => {} StatementKind::SetDiscriminant { .. } => todo!("statement {:?}", stmt), @@ -461,6 +502,33 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { }); } + Callee::IsNull => { + self.enter_rvalue(|v| { + let arg_lty = v.acx.type_of(&args[0]); + if !v.flags[arg_lty.label].contains(FlagSet::FIXED) { + let arg_non_null = + v.perms[arg_lty.label].contains(PermissionSet::NON_NULL); + if arg_non_null { + v.emit(RewriteKind::IsNullToConstFalse); + } else { + v.emit(RewriteKind::IsNullToIsNone); + } + } + }); + } + + Callee::Null { .. } => { + self.enter_rvalue(|v| { + if !v.flags[pl_ty.label].contains(FlagSet::FIXED) { + assert!( + !v.perms[pl_ty.label].contains(PermissionSet::NON_NULL), + "impossible: result of null() is a NON_NULL pointer?" + ); + v.emit(RewriteKind::PtrNullToNone); + } + }); + } + _ => {} } } @@ -484,14 +552,18 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { Rvalue::Repeat(ref op, _) => { self.enter_rvalue_operand(0, |v| v.visit_operand(op, None)); } - Rvalue::Ref(_rg, _kind, pl) => { - self.enter_rvalue_place(0, |v| v.visit_place(pl)); + Rvalue::Ref(_rg, kind, pl) => { + let mutbl = match kind { + BorrowKind::Mut { .. } => true, + BorrowKind::Shared | BorrowKind::Shallow | BorrowKind::Unique => false, + }; + self.enter_rvalue_place(0, |v| v.visit_place(pl, mutbl)); } Rvalue::ThreadLocalRef(_def_id) => { // TODO } Rvalue::AddressOf(mutbl, pl) => { - self.enter_rvalue_place(0, |v| v.visit_place(pl)); + self.enter_rvalue_place(0, |v| v.visit_place(pl, mutbl == Mutability::Mut)); if let Some(expect_ty) = expect_ty { let desc = type_desc::perms_to_desc_with_pointee( self.acx.tcx(), @@ -510,9 +582,20 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { } } Rvalue::Len(pl) => { - self.enter_rvalue_place(0, |v| v.visit_place(pl)); + self.enter_rvalue_place(0, |v| v.visit_place(pl, false)); } - Rvalue::Cast(_kind, ref op, _ty) => { + Rvalue::Cast(_kind, ref op, ty) => { + if util::is_null_const_operand(op) && ty.is_unsafe_ptr() { + // Special case: convert `0 as *const T` to `None`. + if let Some(rv_lty) = expect_ty { + if !self.perms[rv_lty.label].contains(PermissionSet::NON_NULL) + && !self.flags[rv_lty.label].contains(FlagSet::FIXED) + { + self.emit(RewriteKind::ZeroAsPtrToNone); + } + } + } + self.enter_rvalue_operand(0, |v| v.visit_operand(op, None)); if let Some(rv_lty) = expect_ty { let op_lty = self.acx.type_of(op); @@ -560,7 +643,7 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { self.enter_rvalue_operand(0, |v| v.visit_operand(op, None)); } Rvalue::Discriminant(pl) => { - self.enter_rvalue_place(0, |v| v.visit_place(pl)); + self.enter_rvalue_place(0, |v| v.visit_place(pl, false)); } Rvalue::Aggregate(ref _kind, ref ops) => { for (i, op) in ops.iter().enumerate() { @@ -571,7 +654,7 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { self.enter_rvalue_operand(0, |v| v.visit_operand(op, None)); } Rvalue::CopyForDeref(pl) => { - self.enter_rvalue_place(0, |v| v.visit_place(pl)); + self.enter_rvalue_place(0, |v| v.visit_place(pl, false)); } } } @@ -581,7 +664,7 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { fn visit_operand(&mut self, op: &Operand<'tcx>, expect_ty: Option>) { match *op { Operand::Copy(pl) | Operand::Move(pl) => { - self.visit_place(pl); + self.enter_operand_place(|v| v.visit_place(pl, false)); if let Some(expect_ty) = expect_ty { let ptr_lty = self.acx.type_of(pl); @@ -598,7 +681,7 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { fn visit_operand_desc(&mut self, op: &Operand<'tcx>, expect_desc: TypeDesc<'tcx>) { match *op { Operand::Copy(pl) | Operand::Move(pl) => { - self.visit_place(pl); + self.visit_place(pl, false); let ptr_lty = self.acx.type_of(pl); if !ptr_lty.label.is_none() { @@ -609,8 +692,76 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { } } - fn visit_place(&mut self, _pl: Place<'tcx>) { - // TODO: walk over `pl` to handle all derefs (casts, `*x` -> `(*x).get()`) + fn visit_place(&mut self, pl: Place<'tcx>, in_mutable_context: bool) { + let mut ltys = Vec::with_capacity(1 + pl.projection.len()); + ltys.push(self.acx.type_of(pl.local)); + for proj in pl.projection { + let prev_lty = ltys.last().copied().unwrap(); + ltys.push(self.acx.projection_lty(prev_lty, &proj)); + } + self.visit_place_ref(pl.as_ref(), <ys, in_mutable_context); + } + + /// Generate rewrites for a `Place` represented as a `PlaceRef`. `proj_ltys` gives the `LTy` + /// for the `Local` and after each projection. `in_mutable_context` is `true` if the `Place` + /// is in a mutable context, such as the LHS of an assignment. + fn visit_place_ref( + &mut self, + pl: PlaceRef<'tcx>, + proj_ltys: &[LTy<'tcx>], + in_mutable_context: bool, + ) { + let (&last_proj, rest) = match pl.projection.split_last() { + Some(x) => x, + None => return, + }; + + debug_assert!(pl.projection.len() >= 1); + // `LTy` of the base place, before the last projection. + let base_lty = proj_ltys[pl.projection.len() - 1]; + // `LTy` resulting from applying `last_proj` to `base_lty`. + let _proj_lty = proj_ltys[pl.projection.len()]; + + let base_pl = PlaceRef { + local: pl.local, + projection: rest, + }; + match last_proj { + PlaceElem::Deref => { + self.enter_place_deref_pointer(|v| { + v.visit_place_ref(base_pl, proj_ltys, in_mutable_context); + if !v.perms[base_lty.label].contains(PermissionSet::NON_NULL) + && !v.flags[base_lty.label].contains(FlagSet::FIXED) + { + // If the pointer type is non-copy, downgrade (borrow) before calling + // `unwrap()`. + let desc = type_desc::perms_to_desc( + base_lty.ty, + v.perms[base_lty.label], + v.flags[base_lty.label], + ); + if !desc.own.is_copy() { + v.emit(RewriteKind::OptionDowngrade { + mutbl: in_mutable_context, + deref: true, + }); + } + v.emit(RewriteKind::OptionUnwrap); + } + }); + } + PlaceElem::Field(_idx, _ty) => { + self.enter_place_field_base(|v| { + v.visit_place_ref(base_pl, proj_ltys, in_mutable_context) + }); + } + PlaceElem::Index(_) | PlaceElem::ConstantIndex { .. } | PlaceElem::Subslice { .. } => { + self.enter_place_index_array(|v| { + v.visit_place_ref(base_pl, proj_ltys, in_mutable_context) + }); + } + PlaceElem::Downcast(_, _) => {} + } } fn visit_ptr_offset(&mut self, op: &Operand<'tcx>, result_ty: LTy<'tcx>) { @@ -627,6 +778,7 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { Quantity::OffsetPtr => Quantity::OffsetPtr, Quantity::Array => unreachable!("perms_to_desc should not return Quantity::Array"), }, + option: result_desc.option, pointee_ty: result_desc.pointee_ty, }; @@ -635,7 +787,11 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { // Emit `OffsetSlice` for the offset itself. let mutbl = matches!(result_desc.own, Ownership::Mut); - v.emit(RewriteKind::OffsetSlice { mutbl }); + if !result_desc.option { + v.emit(RewriteKind::OffsetSlice { mutbl }); + } else { + v.emit(RewriteKind::OptionMapOffsetSlice { mutbl }); + } // The `OffsetSlice` operation returns something of the same type as its input. // Afterward, we must cast the result to the `result_ty`/`result_desc`. @@ -801,6 +957,70 @@ where return Ok(()); } + if from.option && from.own != to.own { + // Downgrade ownership before unwrapping the `Option` when possible. This can avoid + // moving/consuming the input. For example, if the `from` type is `Option>` and + // `to` is `&mut T`, we start by calling `p.as_deref_mut()`, which produces + // `Option<&mut T>` without consuming `p`. + if !from.own.is_copy() { + // Note that all non-`Copy` ownership types are also safe. We don't reach this + // code when `from.own` is `Raw` or `RawMut`. + match to.own { + Ownership::Raw | Ownership::Imm => { + (self.emit)(RewriteKind::OptionDowngrade { + mutbl: false, + deref: true, + }); + from.own = Ownership::Imm; + } + Ownership::RawMut | Ownership::Cell | Ownership::Mut => { + (self.emit)(RewriteKind::OptionDowngrade { + mutbl: true, + deref: true, + }); + from.own = Ownership::Mut; + } + Ownership::Rc if from.own == Ownership::Rc => { + // `p.clone()` allows using an `Option>` without consuming the + // original. However, `RewriteKind::Clone` is not yet implemented. + error!("Option -> Option clone rewrite NYI"); + } + _ => { + // Remaining cases don't have a valid downgrade operation. We leave them + // as is, and the `unwrap`/`map` operations below will consume the original + // value. Some cases are also impossible to implement, like casting from + // `Rc` to `Box`, which will be caught when attempting the `qty`/`own` + // casts below. + } + } + } + } + + let mut in_option_map = false; + if from.option && !to.option { + // Unwrap first, then perform remaining casts. + (self.emit)(RewriteKind::OptionUnwrap); + from.option = false; + } else if from.option && to.option { + trace!("try_build_cast_desc_desc: emit OptionMapBegin"); + if from.own != to.own { + trace!(" own differs: {:?} != {:?}", from.own, to.own); + } + if from.qty != to.qty { + trace!(" qty differs: {:?} != {:?}", from.qty, to.qty); + } + if from.pointee_ty != to.pointee_ty { + trace!( + " pointee_ty differs: {:?} != {:?}", + from.pointee_ty, + to.pointee_ty + ); + } + (self.emit)(RewriteKind::OptionMapBegin); + from.option = false; + in_option_map = true; + } + // Early `Ownership` casts. We do certain casts here in hopes of reaching an `Ownership` // on which we can safely adjust `Quantity`. from.own = self.cast_ownership(from, to, true)?; @@ -855,6 +1075,17 @@ where // Late `Ownership` casts. from.own = self.cast_ownership(from, to, false)?; + if in_option_map { + assert!(!from.option); + assert!(to.option); + (self.emit)(RewriteKind::OptionMapEnd); + from.option = true; + } else if !from.option && to.option { + // Wrap at the end, after performing all other steps of the cast. + (self.emit)(RewriteKind::OptionSome); + from.option = true; + } + if from != to { return Err(format!( "unsupported cast kind: {:?} -> {:?} (original input: {:?})", diff --git a/c2rust-analyze/src/rewrite/expr/unlower.rs b/c2rust-analyze/src/rewrite/expr/unlower.rs index 20cc293701..4905072e25 100644 --- a/c2rust-analyze/src/rewrite/expr/unlower.rs +++ b/c2rust-analyze/src/rewrite/expr/unlower.rs @@ -114,15 +114,10 @@ impl<'a, 'tcx> UnlowerVisitor<'a, 'tcx> { } fn operand_desc(&self, op: &Operand<'tcx>) -> MirOriginDesc { - match *op { - mir::Operand::Copy(pl) | mir::Operand::Move(pl) => { - if is_temp_var(self.mir, pl.as_ref()) { - MirOriginDesc::LoadFromTemp - } else { - MirOriginDesc::Expr - } - } - mir::Operand::Constant(..) => MirOriginDesc::Expr, + if is_temp_var_operand(self.mir, op) { + MirOriginDesc::LoadFromTemp + } else { + MirOriginDesc::Expr } } @@ -215,20 +210,16 @@ impl<'a, 'tcx> UnlowerVisitor<'a, 'tcx> { match ex.kind { hir::ExprKind::Assign(pl, rv, _span) => { // For `Assign`, we expect the assignment to be the whole thing. - let (loc, _mir_pl, mir_rv) = match self.get_sole_assign(&locs) { + let (loc, mir_pl, mir_rv) = match self.get_sole_assign(&locs) { Some(x) => x, None => { warn("expected exactly one StatementKind::Assign"); return; } }; - let desc = match mir_rv { - mir::Rvalue::Use(op) => self.operand_desc(op), - _ => MirOriginDesc::Expr, - }; self.record(loc, &[], ex); - self.record(loc, &[SubLoc::Dest], pl); - self.record_desc(loc, &[SubLoc::Rvalue], rv, desc); + self.visit_expr_place(pl, loc, vec![SubLoc::Dest], mir_pl, &[]); + self.visit_expr_rvalue(rv, loc, vec![SubLoc::Rvalue], mir_rv, &[]); } hir::ExprKind::Call(_, args) | hir::ExprKind::MethodCall(_, args, _) => { @@ -275,8 +266,9 @@ impl<'a, 'tcx> UnlowerVisitor<'a, 'tcx> { self.record(loc, &[SubLoc::Rvalue], ex); for (i, (arg, mir_arg)) in args.iter().zip(mir_args).enumerate() { - self.record_operand(loc, &[SubLoc::Rvalue, SubLoc::CallArg(i)], arg, mir_arg); - self.visit_expr_operand(arg, mir_arg, &[]); + let sub_loc = vec![SubLoc::Rvalue, SubLoc::CallArg(i)]; + self.record_operand(loc, &sub_loc, arg, mir_arg); + self.visit_expr_operand(arg, loc, sub_loc, mir_arg, &[]); } if extra_locs.len() > 0 { @@ -306,8 +298,7 @@ impl<'a, 'tcx> UnlowerVisitor<'a, 'tcx> { } }; self.record_desc(cursor.loc, &[], ex, MirOriginDesc::StoreIntoLocal); - self.peel_adjustments(ex, &mut cursor); - self.record_desc(cursor.loc, &cursor.sub_loc, ex, MirOriginDesc::Expr); + self.walk_expr(ex, &mut cursor); self.finish_visit_expr_cursor(ex, cursor); } } @@ -436,14 +427,124 @@ impl<'a, 'tcx> UnlowerVisitor<'a, 'tcx> { } } + fn visit_expr_rvalue( + &mut self, + ex: &'tcx hir::Expr<'tcx>, + loc: Location, + sub_loc: Vec, + rv: &'a mir::Rvalue<'tcx>, + extra_locs: &[Location], + ) { + let _g = panic_detail::set_current_span(ex.span); + + // TODO: We do this check early to ensure that the `LoadFromTemp` is emitted with subloc + // `[Rvalue]` rather than `[Rvalue, RvalueOperand(0)]`, since `mir_op` emits rewrites with + // just `[Rvalue]`. For `Rvalue::Use`, the rvalue and its operand are basically + // synonymous, so ideally we would accept both sublocs as well. We should probably add an + // aliasing system or some kind of cleverness in `distribute` to make both versions work, + // or else modify the definition of `SubLoc` so there's only one way to express this + // location. + if is_temp_var_rvalue(self.mir, rv) { + self.record_desc(loc, &sub_loc, ex, MirOriginDesc::LoadFromTemp); + return; + } + + let mut cursor = VisitExprCursor::new( + self.mir, + ExprMir::Rvalue(rv), + extra_locs, + loc, + sub_loc.to_owned(), + ); + self.walk_expr(ex, &mut cursor); + + self.finish_visit_expr_cursor(ex, cursor); + } + fn visit_expr_operand( &mut self, ex: &'tcx hir::Expr<'tcx>, - _rv: &'a mir::Operand<'tcx>, - _extra_locs: &[Location], + loc: Location, + sub_loc: Vec, + op: &'a mir::Operand<'tcx>, + extra_locs: &[Location], + ) { + let _g = panic_detail::set_current_span(ex.span); + + if is_temp_var_operand(self.mir, op) { + self.record_desc(loc, &sub_loc, ex, MirOriginDesc::LoadFromTemp); + return; + } + + let mut cursor = VisitExprCursor::new( + self.mir, + ExprMir::Operand(op), + extra_locs, + loc, + sub_loc.to_owned(), + ); + self.walk_expr(ex, &mut cursor); + + self.finish_visit_expr_cursor(ex, cursor); + } + + fn visit_expr_place( + &mut self, + ex: &'tcx hir::Expr<'tcx>, + loc: Location, + sub_loc: Vec, + pl: mir::Place<'tcx>, + extra_locs: &[Location], ) { let _g = panic_detail::set_current_span(ex.span); - // TODO: handle adjustments, overloaded operators, etc + + let mut cursor = VisitExprCursor::new( + self.mir, + ExprMir::Place(pl.as_ref()), + extra_locs, + loc, + sub_loc.to_owned(), + ); + self.walk_expr(ex, &mut cursor); + + self.finish_visit_expr_cursor(ex, cursor); + } + + fn walk_expr(&mut self, ex: &'tcx hir::Expr<'tcx>, cursor: &mut VisitExprCursor<'a, '_, 'tcx>) { + let mut ex = ex; + loop { + self.peel_adjustments(ex, cursor); + if cursor.is_temp_var() { + self.record_desc(cursor.loc, &cursor.sub_loc, ex, MirOriginDesc::LoadFromTemp); + break; + } else { + self.record_desc(cursor.loc, &cursor.sub_loc, ex, MirOriginDesc::Expr); + } + + match ex.kind { + hir::ExprKind::Unary(hir::UnOp::Deref, ptr_ex) => { + if let Some(()) = cursor.peel_deref() { + ex = ptr_ex; + continue; + } + } + hir::ExprKind::Field(base_ex, _field_ident) => { + if let Some(()) = cursor.peel_field() { + ex = base_ex; + continue; + } + } + hir::ExprKind::Index(arr_ex, _idx_ex) => { + if let Some(()) = cursor.peel_index() { + ex = arr_ex; + continue; + } + } + _ => {} + } + // Keep looping only in cases that we specifically recognize. + break; + } } } @@ -526,6 +627,16 @@ impl<'a, 'b, 'tcx> VisitExprCursor<'a, 'b, 'tcx> { } } + /// Check whether the current MIR is a temporary. + pub fn is_temp_var(&self) -> bool { + match self.cur { + ExprMir::Rvalue(rv) => is_temp_var_rvalue(self.mir, rv), + ExprMir::Operand(op) => is_temp_var_operand(self.mir, op), + ExprMir::Place(pl) => is_temp_var(self.mir, pl), + ExprMir::Call(_) => false, + } + } + /// If the current MIR is a temporary, and the previous `Location` is an assignment to /// that temporary, peel it off, leaving the temporary's initializer as the current /// `Rvalue`. This also adds `LoadFromTemp` and `StoreIntoLocal` entries in `self.temp_info` @@ -673,12 +784,37 @@ impl<'a, 'b, 'tcx> VisitExprCursor<'a, 'b, 'tcx> { if let Some((&outer_proj, remaining_proj)) = pl.projection.split_last() { if matches!(outer_proj, mir::PlaceElem::Deref) { pl.projection = remaining_proj; - // Number of derefs, not counting the outermost one we just peeled off. - let num_inner_derefs = remaining_proj - .iter() - .filter(|p| matches!(p, mir::PlaceElem::Deref)) - .count(); - self.sub_loc.push(SubLoc::PlacePointer(num_inner_derefs)); + self.sub_loc.push(SubLoc::PlaceDerefPointer); + return Some(()); + } + } + self.peel_temp()?; + } + } + + /// If the current MIR is a `Place` ending with a `Field` projection, peel off the `Field`. + pub fn peel_field(&mut self) -> Option<()> { + loop { + let pl = self.require_place_mut()?; + if let Some((&outer_proj, remaining_proj)) = pl.projection.split_last() { + if matches!(outer_proj, mir::PlaceElem::Field(_idx, _ty)) { + pl.projection = remaining_proj; + self.sub_loc.push(SubLoc::PlaceFieldBase); + return Some(()); + } + } + self.peel_temp()?; + } + } + + /// If the current MIR is a `Place` ending with an `Index` projection, peel off the `Index`. + pub fn peel_index(&mut self) -> Option<()> { + loop { + let pl = self.require_place_mut()?; + if let Some((&outer_proj, remaining_proj)) = pl.projection.split_last() { + if matches!(outer_proj, mir::PlaceElem::Index(_)) { + pl.projection = remaining_proj; + self.sub_loc.push(SubLoc::PlaceIndexArray); return Some(()); } } @@ -712,25 +848,77 @@ fn is_temp_var(mir: &Body, pl: mir::PlaceRef) -> bool { pl.projection.len() == 0 && mir.local_kind(pl.local) == mir::LocalKind::Temp } +fn is_temp_var_operand(mir: &Body, op: &mir::Operand) -> bool { + match get_operand_place(op) { + Some(pl) => is_temp_var(mir, pl.as_ref()), + None => false, + } +} + +fn is_temp_var_rvalue(mir: &Body, rv: &mir::Rvalue) -> bool { + match get_rvalue_operand(rv) { + Some(op) => is_temp_var_operand(mir, op), + None => false, + } +} + +fn get_rvalue_operand<'a, 'tcx>(rv: &'a mir::Rvalue<'tcx>) -> Option<&'a mir::Operand<'tcx>> { + match *rv { + mir::Rvalue::Use(ref op) => Some(op), + _ => None, + } +} + +fn get_operand_place<'tcx>(op: &mir::Operand<'tcx>) -> Option> { + match *op { + mir::Operand::Copy(pl) | mir::Operand::Move(pl) => Some(pl), + _ => None, + } +} + +/// Indicate whether a given MIR statement should be considered when building the unlowering map. +fn filter_stmt(stmt: &mir::Statement) -> bool { + match stmt.kind { + // Ignore `AscribeUserType` annotations. These appear in the middle of some expressions. + // It's easier to ignore them all at this level rather than try to handle them in all the + // places they might appear. + mir::StatementKind::AscribeUserType(..) => false, + _ => true, + } +} + +/// Indicate whether a given MIR terminator should be considered when building the unlowering map. +fn filter_term(term: &mir::Terminator) -> bool { + match term.kind { + _ => true, + } +} + fn build_span_index(mir: &Body<'_>) -> SpanIndex { - eprintln!("building span index for {:?}:", mir.source); + debug!("building span index for {:?}:", mir.source); let mut span_index_items = Vec::new(); for (bb, bb_data) in mir.basic_blocks().iter_enumerated() { for (i, stmt) in bb_data.statements.iter().enumerate() { + if !filter_stmt(stmt) { + continue; + } let loc = Location { block: bb, statement_index: i, }; - eprintln!(" {:?}: {:?}", loc, stmt.source_info.span); + debug!(" {:?}: {:?}", loc, stmt.source_info.span); span_index_items.push((stmt.source_info.span, loc)); } - let loc = Location { - block: bb, - statement_index: bb_data.statements.len(), - }; - eprintln!(" {:?}: {:?}", loc, bb_data.terminator().source_info.span); - span_index_items.push((bb_data.terminator().source_info.span, loc)); + let term = bb_data.terminator(); + if filter_term(term) { + let loc = Location { + block: bb, + statement_index: bb_data.statements.len(), + }; + debug!(" {:?}: {:?}", loc, term.source_info.span); + span_index_items.push((term.source_info.span, loc)); + } } SpanIndex::new(span_index_items) diff --git a/c2rust-analyze/src/rewrite/mod.rs b/c2rust-analyze/src/rewrite/mod.rs index affebe42e3..52b3996531 100644 --- a/c2rust-analyze/src/rewrite/mod.rs +++ b/c2rust-analyze/src/rewrite/mod.rs @@ -98,6 +98,9 @@ pub enum Rewrite { /// Single-variable `let` binding. This has the same scoping issues as multi-variable `Let`; /// because of this, `Let` should generally be used instead of multiple `Let1`s. Let1(String, Box), + /// Single-argument closure. As with `Let` and `Let1`, the body must be carefully constructed + /// to avoid potential shadowing. + Closure1(String, Box), // Type builders /// Emit a complete pretty-printed type, discarding the original annotation. @@ -196,6 +199,7 @@ impl Rewrite { Let(new_vars) } Let1(ref name, ref rw) => Let1(String::clone(name), try_subst(rw)?), + Closure1(ref name, ref rw) => Closure1(String::clone(name), try_subst(rw)?), Print(ref s) => Print(String::clone(s)), TyPtr(ref rw, mutbl) => TyPtr(try_subst(rw)?, mutbl), diff --git a/c2rust-analyze/src/rewrite/ty.rs b/c2rust-analyze/src/rewrite/ty.rs index d529ac1341..8b5cf12107 100644 --- a/c2rust-analyze/src/rewrite/ty.rs +++ b/c2rust-analyze/src/rewrite/ty.rs @@ -17,7 +17,7 @@ use crate::labeled_ty::{LabeledTy, LabeledTyCtxt}; use crate::pointee_type::PointeeTypes; use crate::pointer_id::{GlobalPointerTable, PointerId, PointerTable}; use crate::rewrite::Rewrite; -use crate::type_desc::{self, Ownership, Quantity, TypeDesc}; +use crate::type_desc::{self, Ownership, PtrDesc, Quantity, TypeDesc}; use hir::{ FnRetTy, GenericParamKind, Generics, ItemKind, Path, PathSegment, VariantData, WherePredicate, }; @@ -33,6 +33,7 @@ use rustc_middle::mir::{self, Body, LocalDecl}; use rustc_middle::ty::print::{FmtPrinter, Print}; use rustc_middle::ty::{self, AdtDef, GenericArg, GenericArgKind, List, ReErased, TyCtxt}; use rustc_middle::ty::{Ty, TyKind, TypeAndMut}; +use rustc_span::symbol::Symbol; use rustc_span::Span; use super::LifetimeName; @@ -41,7 +42,7 @@ use super::LifetimeName; #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] struct RewriteLabel<'tcx> { /// Rewrite a raw pointer, whose ownership and quantity have been inferred as indicated. - ty_desc: Option<(Ownership, Quantity)>, + ty_desc: Option, /// Rewrite the pointee type of this pointer. pointee_ty: Option>, /// If set, a child or other descendant of this type requires rewriting. @@ -107,7 +108,7 @@ fn create_rewrite_label<'tcx>( // can be `None` (no rewriting required). This might let us avoid inlining a type // alias for some pointers where no actual improvement was possible. let desc = type_desc::perms_to_desc(pointer_lty.ty, perms, flags); - Some((desc.own, desc.qty)) + Some(desc.into()) } }; @@ -333,37 +334,47 @@ impl Convert for mir::Mutability { } } -fn mk_cell<'tcx>(tcx: TyCtxt<'tcx>, ty: ty::Ty<'tcx>) -> ty::Ty<'tcx> { - let core_crate = tcx +fn mk_adt_with_arg<'tcx>(tcx: TyCtxt<'tcx>, path: &str, arg_ty: ty::Ty<'tcx>) -> ty::Ty<'tcx> { + let mut path_parts_iter = path.split("::"); + let crate_name = path_parts_iter + .next() + .unwrap_or_else(|| panic!("couldn't find crate name in {path:?}")); + let crate_name = Symbol::intern(crate_name); + + let krate = tcx .crates(()) .iter() .cloned() - .find(|&krate| tcx.crate_name(krate).as_str() == "core") - .expect("failed to find crate `core`"); + .find(|&krate| tcx.crate_name(krate) == crate_name) + .unwrap_or_else(|| panic!("couldn't find crate {crate_name:?} for {path:?}")); - let cell_mod_child = tcx - .module_children(core_crate.as_def_id()) - .iter() - .find(|child| child.ident.as_str() == "cell") - .expect("failed to find module `core::cell`"); - let cell_mod_did = match cell_mod_child.res { - Res::Def(DefKind::Mod, did) => did, - ref r => panic!("unexpected resolution {:?} for `core::cell`", r), - }; + let mut cur_did = krate.as_def_id(); + for part in path_parts_iter { + let mod_child = tcx + .module_children(cur_did) + .iter() + .find(|child| child.ident.as_str() == part) + .unwrap_or_else(|| panic!("failed to find {part:?} for {path:?}")); + cur_did = match mod_child.res { + Res::Def(DefKind::Mod, did) => did, + Res::Def(DefKind::Struct, did) => did, + Res::Def(DefKind::Enum, did) => did, + Res::Def(DefKind::Union, did) => did, + ref r => panic!("unexpected resolution {r:?} for {part:?} in {path:?}"), + }; + } - let cell_struct_child = tcx - .module_children(cell_mod_did) - .iter() - .find(|child| child.ident.as_str() == "Cell") - .expect("failed to find struct `core::cell::Cell`"); - let cell_struct_did = match cell_struct_child.res { - Res::Def(DefKind::Struct, did) => did, - ref r => panic!("unexpected resolution {:?} for `core::cell::Cell`", r), - }; + let adt = tcx.adt_def(cur_did); + let substs = tcx.mk_substs([GenericArg::from(arg_ty)].into_iter()); + tcx.mk_adt(adt, substs) +} + +fn mk_cell<'tcx>(tcx: TyCtxt<'tcx>, ty: ty::Ty<'tcx>) -> ty::Ty<'tcx> { + mk_adt_with_arg(tcx, "core::cell::Cell", ty) +} - let cell_adt = tcx.adt_def(cell_struct_did); - let substs = tcx.mk_substs([GenericArg::from(ty)].into_iter()); - tcx.mk_adt(cell_adt, substs) +fn mk_option<'tcx>(tcx: TyCtxt<'tcx>, ty: ty::Ty<'tcx>) -> ty::Ty<'tcx> { + mk_adt_with_arg(tcx, "core::option::Option", ty) } /// Produce a `Ty` reflecting the rewrites indicated by the labels in `rw_lty`. @@ -373,11 +384,11 @@ fn mk_rewritten_ty<'tcx>( ) -> ty::Ty<'tcx> { let tcx = *lcx; lcx.rewrite_unlabeled(rw_lty, &mut |ptr_ty, args, label| { - let (ty, own, qty) = match (label.pointee_ty, label.ty_desc) { - (Some(pointee_ty), Some((own, qty))) => { + let (ty, ptr_desc) = match (label.pointee_ty, label.ty_desc) { + (Some(pointee_ty), Some(ptr_desc)) => { // The `ty` should be a pointer. assert_eq!(args.len(), 1); - (pointee_ty, own, qty) + (pointee_ty, ptr_desc) } (Some(pointee_ty), None) => { // Just change the pointee type and nothing else. @@ -391,10 +402,10 @@ fn mk_rewritten_ty<'tcx>( }; return new_ty; } - (None, Some((own, qty))) => { + (None, Some(ptr_desc)) => { // The `ty` should be a pointer; the sole argument is the pointee type. assert_eq!(args.len(), 1); - (args[0], own, qty) + (args[0], ptr_desc) } (None, None) => { // No rewrite to apply. @@ -402,17 +413,17 @@ fn mk_rewritten_ty<'tcx>( } }; - desc_parts_to_ty(tcx, own, qty, ty) + desc_parts_to_ty(tcx, ptr_desc, ty) }) } pub fn desc_parts_to_ty<'tcx>( tcx: TyCtxt<'tcx>, - own: Ownership, - qty: Quantity, + ptr_desc: PtrDesc, pointee_ty: Ty<'tcx>, ) -> Ty<'tcx> { let mut ty = pointee_ty; + let PtrDesc { own, qty, option } = ptr_desc; if own == Ownership::Cell { ty = mk_cell(tcx, ty); @@ -436,11 +447,15 @@ pub fn desc_parts_to_ty<'tcx>( Ownership::Box => tcx.mk_box(ty), }; + if option { + ty = mk_option(tcx, ty); + } + ty } pub fn desc_to_ty<'tcx>(tcx: TyCtxt<'tcx>, desc: TypeDesc<'tcx>) -> Ty<'tcx> { - desc_parts_to_ty(tcx, desc.own, desc.qty, desc.pointee_ty) + desc_parts_to_ty(tcx, PtrDesc::from(desc), desc.pointee_ty) } struct HirTyVisitor<'a, 'tcx> { @@ -523,8 +538,9 @@ fn rewrite_ty<'tcx>( Rewrite::Sub(0, hir_args[0].span) }; - if let Some((own, qty)) = rw_lty.label.ty_desc { + if let Some(ptr_desc) = rw_lty.label.ty_desc { assert_eq!(hir_args.len(), 1); + let PtrDesc { own, qty, option } = ptr_desc; if own == Ownership::Cell { rw = Rewrite::TyCtor("core::cell::Cell".into(), vec![rw]); @@ -548,6 +564,10 @@ fn rewrite_ty<'tcx>( Ownership::Rc => todo!(), Ownership::Box => todo!(), }; + + if option { + rw = Rewrite::TyCtor("core::option::Option".into(), vec![rw]); + } } else { rw = match *rw_lty.ty.kind() { TyKind::Ref(_rg, _ty, mutbl) => Rewrite::TyRef(lifetime_type, Box::new(rw), mutbl), diff --git a/c2rust-analyze/src/type_desc.rs b/c2rust-analyze/src/type_desc.rs index 44526f305f..dbfc5eb2d4 100644 --- a/c2rust-analyze/src/type_desc.rs +++ b/c2rust-analyze/src/type_desc.rs @@ -39,10 +39,51 @@ pub enum Quantity { pub struct TypeDesc<'tcx> { pub own: Ownership, pub qty: Quantity, + pub option: bool, pub pointee_ty: Ty<'tcx>, } -fn perms_to_own_and_qty(perms: PermissionSet, flags: FlagSet) -> (Ownership, Quantity) { +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] +pub struct PtrDesc { + pub own: Ownership, + pub qty: Quantity, + pub option: bool, +} + +impl<'tcx> From> for PtrDesc { + fn from(x: TypeDesc<'tcx>) -> PtrDesc { + let TypeDesc { + own, + qty, + option, + pointee_ty: _, + } = x; + PtrDesc { own, qty, option } + } +} + +impl PtrDesc { + pub fn to_type_desc<'tcx>(self, pointee_ty: Ty<'tcx>) -> TypeDesc<'tcx> { + let PtrDesc { own, qty, option } = self; + TypeDesc { + own, + qty, + option, + pointee_ty, + } + } +} + +impl Ownership { + pub fn is_copy(&self) -> bool { + match *self { + Ownership::Raw | Ownership::RawMut | Ownership::Imm | Ownership::Cell => true, + Ownership::Mut | Ownership::Rc | Ownership::Box => false, + } + } +} + +fn perms_to_ptr_desc(perms: PermissionSet, flags: FlagSet) -> PtrDesc { let own = if perms.contains(PermissionSet::UNIQUE | PermissionSet::WRITE) { Ownership::Mut } else if flags.contains(FlagSet::CELL) { @@ -61,7 +102,9 @@ fn perms_to_own_and_qty(perms: PermissionSet, flags: FlagSet) -> (Ownership, Qua Quantity::Single }; - (own, qty) + let option = !perms.contains(PermissionSet::NON_NULL); + + PtrDesc { own, qty, option } } /// Obtain the `TypeDesc` for a pointer. `ptr_ty` should be the `Ty` of the pointer, and `perms` @@ -73,7 +116,7 @@ pub fn perms_to_desc(ptr_ty: Ty, perms: PermissionSet, flags: FlagSet) -> TypeDe "building TypeDesc for FIXED pointer requires a related pointee type" ); - let (own, qty) = perms_to_own_and_qty(perms, flags); + let ptr_desc = perms_to_ptr_desc(perms, flags); let pointee_ty = match *ptr_ty.kind() { TyKind::Ref(_, ty, _) => ty, @@ -83,23 +126,15 @@ pub fn perms_to_desc(ptr_ty: Ty, perms: PermissionSet, flags: FlagSet) -> TypeDe _ => panic!("expected a pointer type, but got {:?}", ptr_ty), }; - TypeDesc { - own, - qty, - pointee_ty, - } + ptr_desc.to_type_desc(pointee_ty) } /// Obtain the `TypeDesc` for a pointer to a local. `local_ty` should be the `Ty` of the local /// itself, and `perms` and `flags` should be taken from its `addr_of_local` `PointerId`. pub fn local_perms_to_desc(local_ty: Ty, perms: PermissionSet, flags: FlagSet) -> TypeDesc { - let (own, qty) = perms_to_own_and_qty(perms, flags); + let ptr_desc = perms_to_ptr_desc(perms, flags); let pointee_ty = local_ty; - TypeDesc { - own, - qty, - pointee_ty, - } + ptr_desc.to_type_desc(pointee_ty) } pub fn perms_to_desc_with_pointee<'tcx>( @@ -109,26 +144,18 @@ pub fn perms_to_desc_with_pointee<'tcx>( perms: PermissionSet, flags: FlagSet, ) -> TypeDesc<'tcx> { - let (own, qty) = if flags.contains(FlagSet::FIXED) { + let ptr_desc = if flags.contains(FlagSet::FIXED) { unpack_pointer_type(tcx, ptr_ty, pointee_ty) } else { - perms_to_own_and_qty(perms, flags) + perms_to_ptr_desc(perms, flags) }; - TypeDesc { - own, - qty, - pointee_ty, - } + ptr_desc.to_type_desc(pointee_ty) } /// Unpack an existing `Ty` into its ownership and quantity. The pointee type must already be /// known. Panics if there are no `Ownership` and `Quantity` that combine with `pointee_ty` to /// produce `ty`. -pub fn unpack_pointer_type<'tcx>( - tcx: TyCtxt<'tcx>, - ty: Ty<'tcx>, - pointee_ty: Ty<'tcx>, -) -> (Ownership, Quantity) { +pub fn unpack_pointer_type<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, pointee_ty: Ty<'tcx>) -> PtrDesc { #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] enum Step { Ref(Mutability), @@ -139,6 +166,7 @@ pub fn unpack_pointer_type<'tcx>( Slice, OffsetPtr, Array, + Option, } let mut steps = Vec::new(); @@ -152,6 +180,9 @@ pub fn unpack_pointer_type<'tcx>( TyKind::Adt(adt_def, substs) if is_cell(tcx, adt_def) => { (Step::Cell, substs.type_at(0)) } + TyKind::Adt(adt_def, substs) if is_option(tcx, adt_def) => { + (Step::Option, substs.type_at(0)) + } TyKind::Adt(adt_def, substs) if is_offset_ptr(tcx, adt_def) => { (Step::OffsetPtr, substs.type_at(0)) } @@ -181,6 +212,8 @@ pub fn unpack_pointer_type<'tcx>( }; // This logic is roughly the inverse of that in `rewrite::ty::mk_rewritten_ty`. + let option = eat(Step::Option); + let mut own = if eat(Step::Ref(Mutability::Not)) { Ownership::Imm } else if eat(Step::Ref(Mutability::Mut)) { @@ -226,7 +259,7 @@ pub fn unpack_pointer_type<'tcx>( &steps[i..], ); - (own, qty) + PtrDesc { own, qty, option } } /// Returns `true` if `adt_def` is the type `std::cell::Cell`. @@ -235,6 +268,12 @@ fn is_cell<'tcx>(_tcx: TyCtxt<'tcx>, _adt_def: AdtDef<'tcx>) -> bool { false } +/// Returns `true` if `adt_def` is the type `std::option::Option`. +fn is_option<'tcx>(_tcx: TyCtxt<'tcx>, _adt_def: AdtDef<'tcx>) -> bool { + // TODO + false +} + /// Returns `true` if `adt_def` is the type `std::rc::Rc`. fn is_rc<'tcx>(_tcx: TyCtxt<'tcx>, _adt_def: AdtDef<'tcx>) -> bool { // TODO diff --git a/c2rust-analyze/src/util.rs b/c2rust-analyze/src/util.rs index 9c6902ecf1..037f3a46d2 100644 --- a/c2rust-analyze/src/util.rs +++ b/c2rust-analyze/src/util.rs @@ -430,6 +430,10 @@ pub fn is_null_const(constant: Constant) -> bool { } } +pub fn is_null_const_operand(op: &Operand) -> bool { + op.constant().copied().map_or(false, is_null_const) +} + pub trait PhantomLifetime<'a> {} impl<'a, T: ?Sized> PhantomLifetime<'a> for T {} diff --git a/c2rust-analyze/tests/filecheck.rs b/c2rust-analyze/tests/filecheck.rs index c33f1f322e..94672a3865 100644 --- a/c2rust-analyze/tests/filecheck.rs +++ b/c2rust-analyze/tests/filecheck.rs @@ -57,6 +57,7 @@ define_tests! { known_fn, non_null, non_null_force, + non_null_rewrites, offset1, offset2, pointee, diff --git a/c2rust-analyze/tests/filecheck/cast.rs b/c2rust-analyze/tests/filecheck/cast.rs index 810fd198f7..523fbe58d4 100644 --- a/c2rust-analyze/tests/filecheck/cast.rs +++ b/c2rust-analyze/tests/filecheck/cast.rs @@ -1,10 +1,4 @@ -// CHECK-DAG: struct S<'h0> { -struct S { - // CHECK-DAG: i: &'h0 i32 - i: *const i32, -} - -// The below ensures the concrete origins for `s` and `s.i` are the same and are hypothetical +// These lines ensure the concrete origins for `s` and `s.i` are the same and are hypothetical // CHECK-DAG: assign {{.*}}#*mut S{{.*}}origin_params: [('h0, Origin([[HYPO_ORIGIN:[0-9]+]]))]{{.*}} = Label{{.*}}origin_params: [('h0, Origin({{.*}}))] // CHECK-DAG: assign Label { origin: Some(Origin([[HYPO_ORIGIN]])){{.*}}*const i32{{.*}} = Label @@ -12,11 +6,17 @@ struct S { pub unsafe fn null_ptr() { // CHECK-DAG: ([[@LINE+3]]: s): addr_of = UNIQUE | NON_NULL, type = READ | WRITE | UNIQUE# // CHECK-LABEL: type assignment for "null_ptr": - // CHECK-DAG: ([[@LINE+1]]: s): &mut S + // CHECK-DAG: ([[@LINE+1]]: s): std::option::Option<&mut S> let s = 0 as *mut S; (*s).i = 0 as *const i32; } +// CHECK-LABEL: struct S<'h0> { +struct S { + // CHECK: i: core::option::Option<&'h0 (i32)> + i: *const i32, +} + #[repr(C)] pub struct Foo { y: *mut i32, @@ -34,9 +34,9 @@ pub unsafe fn cell_as_mut_as_cell(mut x: *mut i32, mut f: Foo) { *z = 1; *r = 1; *z = 4; - // CHECK-DAG: f.y = (x).as_ptr(); + // CHECK: f.y = (x).as_ptr(); f.y = x; - // CHECK-DAG: x = &*((f.y) as *const std::cell::Cell); + // CHECK: x = &*((f.y) as *const std::cell::Cell); x = f.y; } pub struct fdnode { diff --git a/c2rust-analyze/tests/filecheck/non_null_rewrites.rs b/c2rust-analyze/tests/filecheck/non_null_rewrites.rs new file mode 100644 index 0000000000..b76ba1d7da --- /dev/null +++ b/c2rust-analyze/tests/filecheck/non_null_rewrites.rs @@ -0,0 +1,108 @@ +use std::ptr; + +// CHECK-LABEL: unsafe fn f{{[<(]}} +pub unsafe fn f(cond: bool, p: *mut i32) { + let mut p = p; + if cond { + // Both ways of writing null constants should be rewritten to `None`. + + // CHECK: p = None; + p = ptr::null_mut(); + // CHECK: [[@LINE-1]]: ptr::null_mut(): + + // CHECK: p = None; + p = 0 as *mut _; + // CHECK: [[@LINE-1]]: 0 as *mut _: + } + + + // CHECK: let ([[arr:.+]], [[idx:.+]], ) = ((p), (3) as usize, ); + // CHECK-NEXT: [[arr]].map(|arr| &arr{{\[}}[[idx]] ..]) + let q = p.offset(3); +} + + +// CHECK-LABEL: unsafe fn call_use_mut( +unsafe fn call_use_mut(cond: bool) -> i32 { + let mut x = 1; + let p = if cond { + // CHECK: Some(&mut (x)) + ptr::addr_of_mut!(x) + } else { + // CHECK: None + ptr::null_mut() + }; + use_mut(p) +} + +// CHECK-LABEL: unsafe fn use_mut{{[<(]}} +// CHECK-SAME: p: core::option::Option<&{{('[^ ]* )?}}mut (i32)> +unsafe fn use_mut(mut p: *mut i32) -> i32 { + if !p.is_null() { + // CHECK: *(p).as_deref_mut().unwrap() = 1; + *p = 1; + } + // CHECK: use_const + // CHECK-SAME: (p).as_deref() + use_const(p) +} + +// CHECK-LABEL: unsafe fn use_const{{[<(]}} +// CHECK-SAME: p: core::option::Option<&{{('[^ ]* )?}}(i32)> +unsafe fn use_const(p: *const i32) -> i32 { + // CHECK: *(p).unwrap() + *p +} + +// CHECK-LABEL: unsafe fn call_use_slice{{[<(]}} +// CHECK-SAME: q: &{{('[^ ]* )?}}[(i32)] +unsafe fn call_use_slice(cond: bool, q: *const i32) -> i32 { + // `q` is not nullable, so `q.is_null()` should be rewritten to `false`. + // CHECK: if !false { + if !q.is_null() { + // No-op + } + let p = if cond { + // CHECK: Some((q)) + q + } else { + // CHECK: None + ptr::null_mut() + }; + use_slice(p) +} + +// CHECK-LABEL: unsafe fn use_slice{{[<(]}} +// CHECK-SAME: p: core::option::Option<&{{('[^ ]* )?}}[(i32)]> +unsafe fn use_slice(p: *const i32) -> i32 { + // `p`'s new type is `Option<&[i32]>`, so `is_null()` should become `is_none()`. + // CHECK: .is_none() + if !p.is_null() { + let x = *p.offset(1); + } + // CHECK: use_single((p).map(|__ptr| &__ptr[0])) + use_single(p) +} + +// CHECK-LABEL: unsafe fn use_single{{[<(]}} +// CHECK-SAME: p: core::option::Option<&{{('[^ ]* )?}}(i32)> +unsafe fn use_single(p: *const i32) -> i32 { + *p +} + + +// CHECK-LABEL: unsafe fn downgrade_mut_to_imm_on_deref{{[<(]}} +// CHECK-SAME: p: core::option::Option<&{{('[^ ]* )?}}mut (i32)> +unsafe fn downgrade_mut_to_imm_on_deref(cond: bool, mut p: *mut i32) -> i32 { + if cond { + // Ensure `p` is wrapped in `Option`. + p = ptr::null_mut(); + } + // Ensure `p` is mutable. + *p = 1; + // This read needs a downgrade via `as_deref()` to avoid moving `p: Option<&mut _>`. + // CHECK: let x = *(p).as_deref().unwrap(); + let x = *p; + *p = 2; + x +}