Skip to content

Commit bf5e6eb

Browse files
committed
miri validity: make recursive ref checking optional
1 parent 25a75a4 commit bf5e6eb

File tree

3 files changed

+121
-87
lines changed

3 files changed

+121
-87
lines changed

src/librustc_lint/builtin.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1558,15 +1558,12 @@ fn validate_const<'a, 'tcx>(
15581558
let ecx = ::rustc_mir::const_eval::mk_eval_cx(tcx, gid.instance, param_env).unwrap();
15591559
let result = (|| {
15601560
let op = ecx.const_to_op(constant)?;
1561-
let mut todo = vec![(op, Vec::new())];
1562-
let mut seen = FxHashSet();
1563-
seen.insert(op);
1564-
while let Some((op, mut path)) = todo.pop() {
1561+
let mut ref_tracking = ::rustc_mir::interpret::RefTracking::new(op);
1562+
while let Some((op, mut path)) = ref_tracking.todo.pop() {
15651563
ecx.validate_operand(
15661564
op,
15671565
&mut path,
1568-
&mut seen,
1569-
&mut todo,
1566+
Some(&mut ref_tracking),
15701567
)?;
15711568
}
15721569
Ok(())

src/librustc_mir/interpret/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,5 @@ pub use self::memory::{Memory, MemoryKind};
3535
pub use self::machine::Machine;
3636

3737
pub use self::operand::{ScalarMaybeUndef, Value, ValTy, Operand, OpTy};
38+
39+
pub use self::validity::RefTracking;

src/librustc_mir/interpret/validity.rs

Lines changed: 116 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@
1111
use std::fmt::Write;
1212

1313
use syntax_pos::symbol::Symbol;
14-
use rustc::ty::layout::{self, Size, Primitive};
14+
use rustc::ty::layout::{self, Size};
1515
use rustc::ty::{self, Ty};
1616
use rustc_data_structures::fx::FxHashSet;
1717
use rustc::mir::interpret::{
1818
Scalar, AllocType, EvalResult, EvalErrorKind, PointerArithmetic
1919
};
2020

2121
use super::{
22-
OpTy, Machine, EvalContext, ScalarMaybeUndef
22+
OpTy, MPlaceTy, Machine, EvalContext, ScalarMaybeUndef
2323
};
2424

2525
macro_rules! validation_failure{
@@ -63,6 +63,23 @@ pub enum PathElem {
6363
Tag,
6464
}
6565

66+
/// State for tracking recursive validation of references
67+
pub struct RefTracking<'tcx> {
68+
pub seen: FxHashSet<(OpTy<'tcx>)>,
69+
pub todo: Vec<(OpTy<'tcx>, Vec<PathElem>)>,
70+
}
71+
72+
impl<'tcx> RefTracking<'tcx> {
73+
pub fn new(op: OpTy<'tcx>) -> Self {
74+
let mut ref_tracking = RefTracking {
75+
seen: FxHashSet(),
76+
todo: vec![(op, Vec::new())],
77+
};
78+
ref_tracking.seen.insert(op);
79+
ref_tracking
80+
}
81+
}
82+
6683
// Adding a Deref and making a copy of the path to be put into the queue
6784
// always go together. This one does it with only new allocation.
6885
fn path_clone_and_deref(path: &Vec<PathElem>) -> Vec<PathElem> {
@@ -205,6 +222,41 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
205222
}
206223
}
207224

225+
/// Validate a reference, potentially recursively. `place` is assumed to already be
226+
/// dereferenced, i.e. it describes the target.
227+
fn validate_ref(
228+
&self,
229+
place: MPlaceTy<'tcx>,
230+
path: &mut Vec<PathElem>,
231+
ref_tracking: Option<&mut RefTracking<'tcx>>,
232+
) -> EvalResult<'tcx> {
233+
// Skip recursion for some external statics
234+
if let Scalar::Ptr(ptr) = place.ptr {
235+
let alloc_kind = self.tcx.alloc_map.lock().get(ptr.alloc_id);
236+
if let Some(AllocType::Static(did)) = alloc_kind {
237+
// statics from other crates are already checked.
238+
// they might be checked at a different type, but for now we want
239+
// to avoid recursing too deeply.
240+
// extern statics cannot be validated as they have no body.
241+
if !did.is_local() || self.tcx.is_foreign_item(did) {
242+
return Ok(());
243+
}
244+
}
245+
}
246+
// Check if we have encountered this pointer+layout combination
247+
// before. Proceed recursively even for integer pointers, no
248+
// reason to skip them! They are valid for some ZST, but not for others
249+
// (e.g. `!` is a ZST).
250+
let op = place.into();
251+
if let Some(ref_tracking) = ref_tracking {
252+
if ref_tracking.seen.insert(op) {
253+
trace!("Recursing below ptr {:#?}", *op);
254+
ref_tracking.todo.push((op, path_clone_and_deref(path)));
255+
}
256+
}
257+
Ok(())
258+
}
259+
208260
/// This function checks the data at `op`.
209261
/// It will error if the bits at the destination do not match the ones described by the layout.
210262
/// The `path` may be pushed to, but the part that is present when the function
@@ -213,8 +265,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
213265
&self,
214266
dest: OpTy<'tcx>,
215267
path: &mut Vec<PathElem>,
216-
seen: &mut FxHashSet<(OpTy<'tcx>)>,
217-
todo: &mut Vec<(OpTy<'tcx>, Vec<PathElem>)>,
268+
mut ref_tracking: Option<&mut RefTracking<'tcx>>,
218269
) -> EvalResult<'tcx> {
219270
trace!("validate_operand: {:?}, {:#?}", *dest, dest.layout);
220271

@@ -273,7 +324,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
273324

274325
// Validate all fields
275326
match dest.layout.fields {
276-
// primitives are unions with zero fields
327+
// Primitives appear as Union with 0 fields -- except for fat pointers.
277328
// We still check `layout.fields`, not `layout.abi`, because `layout.abi`
278329
// is `Scalar` for newtypes around scalars, but we want to descend through the
279330
// fields to get a proper `path`.
@@ -302,32 +353,61 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
302353
};
303354
let scalar = value.to_scalar_or_undef();
304355
self.validate_scalar(scalar, size, scalar_layout, &path, dest.layout.ty)?;
305-
if scalar_layout.value == Primitive::Pointer {
306-
// ignore integer pointers, we can't reason about the final hardware
307-
if let Scalar::Ptr(ptr) = scalar.not_undef()? {
308-
let alloc_kind = self.tcx.alloc_map.lock().get(ptr.alloc_id);
309-
if let Some(AllocType::Static(did)) = alloc_kind {
310-
// statics from other crates are already checked.
311-
// extern statics cannot be validated as they have no body.
312-
if !did.is_local() || self.tcx.is_foreign_item(did) {
313-
return Ok(());
314-
}
315-
}
316-
if value.layout.ty.builtin_deref(false).is_some() {
317-
let ptr_op = self.ref_to_mplace(value)?.into();
318-
// we have not encountered this pointer+layout combination
319-
// before.
320-
if seen.insert(ptr_op) {
321-
trace!("Recursing below ptr {:#?}", *value);
322-
todo.push((ptr_op, path_clone_and_deref(path)));
323-
}
324-
}
325-
}
356+
// Recursively check *safe* references
357+
if dest.layout.ty.builtin_deref(true).is_some() &&
358+
!dest.layout.ty.is_unsafe_ptr()
359+
{
360+
self.validate_ref(self.ref_to_mplace(value)?, path, ref_tracking)?;
326361
}
327362
},
328363
_ => bug!("bad abi for FieldPlacement::Union(0): {:#?}", dest.layout.abi),
329364
}
330365
}
366+
layout::FieldPlacement::Arbitrary { .. }
367+
if dest.layout.ty.builtin_deref(true).is_some() =>
368+
{
369+
// This is a fat pointer.
370+
let ptr = match self.read_value(dest.into())
371+
.and_then(|val| self.ref_to_mplace(val))
372+
{
373+
Ok(ptr) => ptr,
374+
Err(_) =>
375+
return validation_failure!(
376+
"undefined location or metadata in fat pointer", path
377+
),
378+
};
379+
// check metadata early, for better diagnostics
380+
match self.tcx.struct_tail(ptr.layout.ty).sty {
381+
ty::Dynamic(..) => {
382+
match ptr.extra.unwrap().to_ptr() {
383+
Ok(_) => {},
384+
Err(_) =>
385+
return validation_failure!(
386+
"non-pointer vtable in fat pointer", path
387+
),
388+
}
389+
// FIXME: More checks for the vtable.
390+
}
391+
ty::Slice(..) | ty::Str => {
392+
match ptr.extra.unwrap().to_usize(self) {
393+
Ok(_) => {},
394+
Err(_) =>
395+
return validation_failure!(
396+
"non-integer slice length in fat pointer", path
397+
),
398+
}
399+
}
400+
_ =>
401+
bug!("Unexpected unsized type tail: {:?}",
402+
self.tcx.struct_tail(ptr.layout.ty)
403+
),
404+
}
405+
// for safe ptrs, recursively check it
406+
if !dest.layout.ty.is_unsafe_ptr() {
407+
self.validate_ref(ptr, path, ref_tracking)?;
408+
}
409+
}
410+
// Compound data structures
331411
layout::FieldPlacement::Union(_) => {
332412
// We can't check unions, their bits are allowed to be anything.
333413
// The fields don't need to correspond to any bit pattern of the union's fields.
@@ -411,7 +491,11 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
411491
for (i, field) in self.mplace_array_fields(dest)?.enumerate() {
412492
let field = field?;
413493
path.push(PathElem::ArrayElem(i));
414-
self.validate_operand(field.into(), path, seen, todo)?;
494+
self.validate_operand(
495+
field.into(),
496+
path,
497+
ref_tracking.as_mut().map(|r| &mut **r)
498+
)?;
415499
path.truncate(path_len);
416500
}
417501
}
@@ -421,60 +505,11 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
421505
// An empty array. Nothing to do.
422506
}
423507
layout::FieldPlacement::Arbitrary { ref offsets, .. } => {
424-
// Fat pointers are treated like pointers, not aggregates.
425-
if dest.layout.ty.builtin_deref(true).is_some() {
426-
// This is a fat pointer.
427-
let ptr = match self.read_value(dest.into())
428-
.and_then(|val| self.ref_to_mplace(val))
429-
{
430-
Ok(ptr) => ptr,
431-
Err(_) =>
432-
return validation_failure!(
433-
"undefined location or metadata in fat pointer", path
434-
),
435-
};
436-
// check metadata early, for better diagnostics
437-
match self.tcx.struct_tail(ptr.layout.ty).sty {
438-
ty::Dynamic(..) => {
439-
match ptr.extra.unwrap().to_ptr() {
440-
Ok(_) => {},
441-
Err(_) =>
442-
return validation_failure!(
443-
"non-pointer vtable in fat pointer", path
444-
),
445-
}
446-
// FIXME: More checks for the vtable.
447-
}
448-
ty::Slice(..) | ty::Str => {
449-
match ptr.extra.unwrap().to_usize(self) {
450-
Ok(_) => {},
451-
Err(_) =>
452-
return validation_failure!(
453-
"non-integer slice length in fat pointer", path
454-
),
455-
}
456-
}
457-
_ =>
458-
bug!("Unexpected unsized type tail: {:?}",
459-
self.tcx.struct_tail(ptr.layout.ty)
460-
),
461-
}
462-
// for safe ptrs, recursively check it
463-
if !dest.layout.ty.is_unsafe_ptr() {
464-
let ptr = ptr.into();
465-
if seen.insert(ptr) {
466-
trace!("Recursing below fat ptr {:?}", ptr);
467-
todo.push((ptr, path_clone_and_deref(path)));
468-
}
469-
}
470-
} else {
471-
// Not a pointer, perform regular aggregate handling below
472-
for i in 0..offsets.len() {
473-
let field = self.operand_field(dest, i as u64)?;
474-
path.push(self.aggregate_field_path_elem(dest.layout.ty, variant, i));
475-
self.validate_operand(field, path, seen, todo)?;
476-
path.truncate(path_len);
477-
}
508+
for i in 0..offsets.len() {
509+
let field = self.operand_field(dest, i as u64)?;
510+
path.push(self.aggregate_field_path_elem(dest.layout.ty, variant, i));
511+
self.validate_operand(field, path, ref_tracking.as_mut().map(|r| &mut **r))?;
512+
path.truncate(path_len);
478513
}
479514
}
480515
}

0 commit comments

Comments
 (0)