Repository: rust-lang/rust
Pull request: miri recursive validation: only check one layer deep

As has been proposed in https://github.com/rust-lang/unsafe-code-guidelines/issues/414, let's see what happens if we make recursive checking in Miri shallow: we treat whatever is behind a reference as if it was inside `MaybeDangling`, which means nested references do not have to be dereferenceable.

This changes the meaning of the original flag -- I don't think it is worth supporting multiple variants of recursive checking (it'd require a bunch of new plumbing), and this seems to be the strictest variant that still has any traction in the discussion.

Changed files:
filename: compiler/rustc_const_eval/src/interpret/validity.rs
patch: @@ -1532,13 +1532,15 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValueVisitor<'tcx, M> for ValidityVisitor<'rt,
 }
 
 impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
+    /// The internal core entry point for all validation operations.
     fn validate_operand_internal(
         &mut self,
         val: &PlaceTy<'tcx, M::Provenance>,
         path: Path<'tcx>,
         ref_tracking: Option<&mut RefTracking<MPlaceTy<'tcx, M::Provenance>, Path<'tcx>>>,
         ctfe_mode: Option<CtfeValidationMode>,
         reset_provenance_and_padding: bool,
+        start_in_may_dangle: bool,
     ) -> InterpResult<'tcx> {
         trace!("validate_operand_internal: {:?}, {:?}", *val, val.layout.ty);
 
@@ -1556,7 +1558,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                 ecx,
                 reset_provenance_and_padding,
                 data_bytes: reset_padding.then_some(RangeSet(Vec::new())),
-                may_dangle: false,
+                may_dangle: start_in_may_dangle,
             };
             v.visit_value(val)?;
             v.reset_padding(val)?;
@@ -1599,6 +1601,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
             Some(ref_tracking),
             Some(ctfe_mode),
             /*reset_provenance*/ false,
+            /*start_in_may_dangle*/ false,
         )
     }
 
@@ -1629,6 +1632,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                 None,
                 None,
                 reset_provenance_and_padding,
+                /*start_in_may_dangle*/ false,
             );
         }
         // Do a recursive check.
@@ -1639,15 +1643,19 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
             Some(&mut ref_tracking),
             None,
             reset_provenance_and_padding,
+            /*start_in_may_dangle*/ false,
         )?;
         while let Some((mplace, path)) = ref_tracking.todo.pop() {
-            // Things behind reference do *not* have the provenance reset.
+            // Things behind reference do *not* have the provenance reset. In fact
+            // we treat the entire thing as being inside MaybeDangling, i.e., references
+            // do not have to be dereferenceable.
             self.validate_operand_internal(
                 &mplace.into(),
                 path,
-                Some(&mut ref_tracking),
+                None, // no further recursion
                 None,
                 /*reset_provenance_and_padding*/ false,
+                /*start_in_may_dangle*/ true,
             )?;
         }
         interp_ok(())

filename: src/tools/miri/README.md
patch: @@ -476,8 +476,9 @@ to Miri failing to detect cases of undefined behavior in a program.
   but reports to the program that it did actually write. This is useful when you
   are not interested in the actual program's output, but only want to see Miri's
   errors and warnings.
-* `-Zmiri-recursive-validation` is a *highly experimental* flag that makes validity checking
-  recurse below references.
+* `-Zmiri-recursive-validation` is a *highly experimental* flag that makes validity checking recurse
+  *one level* below references. The in-memory value is treated as-if it was inside a
+  `MaybeDangling`, i.e., nested references do not even have to be dereferenceable.
 * `-Zmiri-preemption-rate` configures the probability that at the end of a basic block, the active
   thread will be preempted. The default is `0.01` (i.e., 1%). Setting this to `0` disables
   preemption. Note that even without preemption, the schedule is still non-deterministic: