Skip to content

Commit 49999e9

Browse files
committed
optimize sanity check path printing
During the sanity check, we keep track of the path we are below in a `Vec`. We avoid cloning that `Vec` unless we hit a pointer indirection. The `String` representation is only computed when validation actually fails.
1 parent 42a1239 commit 49999e9

File tree

8 files changed

+179
-126
lines changed

8 files changed

+179
-126
lines changed

src/librustc_lint/builtin.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1622,13 +1622,13 @@ fn validate_const<'a, 'tcx>(
16221622
let layout = ecx.layout_of(constant.ty)?;
16231623
let place = ecx.allocate_op(OpTy { op, layout })?.into();
16241624

1625-
let mut todo = vec![(place, String::new())];
1625+
let mut todo = vec![(place, Vec::new())];
16261626
let mut seen = FxHashSet();
16271627
seen.insert(place);
1628-
while let Some((place, path)) = todo.pop() {
1628+
while let Some((place, mut path)) = todo.pop() {
16291629
ecx.validate_mplace(
16301630
place,
1631-
path,
1631+
&mut path,
16321632
&mut seen,
16331633
&mut todo,
16341634
)?;

src/librustc_mir/interpret/validity.rs

Lines changed: 97 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::fmt::Write;
22

3+
use syntax_pos::symbol::Symbol;
34
use rustc::ty::layout::{self, Size, Primitive};
45
use rustc::ty::{self, Ty};
56
use rustc_data_structures::fx::FxHashSet;
@@ -13,21 +14,23 @@ use super::{
1314

1415
macro_rules! validation_failure{
1516
($what:expr, $where:expr, $details:expr) => {{
16-
let where_ = if $where.is_empty() {
17+
let where_ = path_format($where);
18+
let where_ = if where_.is_empty() {
1719
String::new()
1820
} else {
19-
format!(" at {}", $where)
21+
format!(" at {}", where_)
2022
};
2123
err!(ValidationFailure(format!(
2224
"encountered {}{}, but expected {}",
2325
$what, where_, $details,
2426
)))
2527
}};
2628
($what:expr, $where:expr) => {{
27-
let where_ = if $where.is_empty() {
29+
let where_ = path_format($where);
30+
let where_ = if where_.is_empty() {
2831
String::new()
2932
} else {
30-
format!(" at {}", $where)
33+
format!(" at {}", where_)
3134
};
3235
err!(ValidationFailure(format!(
3336
"encountered {}{}",
@@ -36,13 +39,59 @@ macro_rules! validation_failure{
3639
}};
3740
}
3841

42+
/// We want to show a nice path to the invalid field for diagnotsics,
43+
/// but avoid string operations in the happy case where no error happens.
44+
/// So we track a `Vec<PathElem>` where `PathElem` contains all the data we
45+
/// need to later print something for the user.
46+
#[derive(Copy, Clone, Debug)]
47+
pub enum PathElem {
48+
Field(Symbol),
49+
ClosureVar(Symbol),
50+
ArrayElem(usize),
51+
TupleElem(usize),
52+
Deref,
53+
Tag,
54+
}
55+
56+
// Adding a Deref and making a copy of the path to be put into the queue
57+
// always go together. This one does it with only new allocation.
58+
fn path_clone_and_deref(path: &Vec<PathElem>) -> Vec<PathElem> {
59+
let mut new_path = Vec::with_capacity(path.len()+1);
60+
new_path.clone_from(path);
61+
new_path.push(PathElem::Deref);
62+
new_path
63+
}
64+
65+
/// Format a path
66+
fn path_format(path: &Vec<PathElem>) -> String {
67+
use self::PathElem::*;
68+
69+
let mut out = String::new();
70+
for elem in path.iter() {
71+
match elem {
72+
Field(name) => write!(out, ".{}", name).unwrap(),
73+
ClosureVar(name) => write!(out, ".<closure-var({})>", name).unwrap(),
74+
TupleElem(idx) => write!(out, ".{}", idx).unwrap(),
75+
ArrayElem(idx) => write!(out, "[{}]", idx).unwrap(),
76+
Deref =>
77+
// This does not match Rust syntax, but it is more readable for long paths -- and
78+
// some of the other items here also are not Rust syntax. Actually we can't
79+
// even use the usual syntax because we are just showing the projections,
80+
// not the root.
81+
write!(out, ".<deref>").unwrap(),
82+
Tag => write!(out, ".<enum-tag>").unwrap(),
83+
}
84+
}
85+
out
86+
}
87+
3988
impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
4089
fn validate_scalar(
4190
&self,
4291
value: ScalarMaybeUndef,
4392
size: Size,
4493
scalar: &layout::Scalar,
45-
path: &str,
94+
path: &Vec<PathElem>,
4695
ty: Ty,
4796
) -> EvalResult<'tcx> {
4897
trace!("validate scalar: {:#?}, {:#?}, {:#?}, {}", value, size, scalar, ty);
@@ -93,7 +142,11 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
93142
ty::TyChar => {
94143
debug_assert_eq!(size.bytes(), 4);
95144
if ::std::char::from_u32(bits as u32).is_none() {
96-
return err!(InvalidChar(bits));
145+
return validation_failure!(
146+
"character",
147+
path,
148+
"a valid unicode codepoint"
149+
);
97150
}
98151
}
99152
_ => {},
@@ -127,46 +180,57 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
127180
/// This function checks the memory where `dest` points to. The place must be sized
128181
/// (i.e., dest.extra == PlaceExtra::None).
129182
/// It will error if the bits at the destination do not match the ones described by the layout.
183+
/// The `path` may be pushed to, but the part that is present when the function
184+
/// starts must not be changed!
130185
pub fn validate_mplace(
131186
&self,
132187
dest: MPlaceTy<'tcx>,
133-
path: String,
188+
path: &mut Vec<PathElem>,
134189
seen: &mut FxHashSet<(MPlaceTy<'tcx>)>,
135-
todo: &mut Vec<(MPlaceTy<'tcx>, String)>,
190+
todo: &mut Vec<(MPlaceTy<'tcx>, Vec<PathElem>)>,
136191
) -> EvalResult<'tcx> {
137192
self.memory.dump_alloc(dest.to_ptr()?.alloc_id);
138193
trace!("validate_mplace: {:?}, {:#?}", *dest, dest.layout);
139194

140-
// Find the right variant
195+
// Find the right variant. We have to handle this as a prelude, not via
196+
// proper recursion with the new inner layout, to be able to later nicely
197+
// print the field names of the enum field that is being accessed.
141198
let (variant, dest) = match dest.layout.variants {
142199
layout::Variants::NicheFilling { niche: ref tag, .. } |
143200
layout::Variants::Tagged { ref tag, .. } => {
144201
let size = tag.value.size(self);
145202
// we first read the tag value as scalar, to be able to validate it
146203
let tag_mplace = self.mplace_field(dest, 0)?;
147204
let tag_value = self.read_scalar(tag_mplace.into())?;
148-
let path = format!("{}.TAG", path);
205+
path.push(PathElem::Tag);
149206
self.validate_scalar(
150207
tag_value, size, tag, &path, tag_mplace.layout.ty
151208
)?;
209+
path.pop(); // remove the element again
152210
// then we read it again to get the index, to continue
153211
let variant = self.read_discriminant_as_variant_index(dest.into())?;
154-
let dest = self.mplace_downcast(dest, variant)?;
212+
let inner_dest = self.mplace_downcast(dest, variant)?;
213+
// Put the variant projection onto the path, as a field
214+
path.push(PathElem::Field(dest.layout.ty.ty_adt_def().unwrap().variants[variant].name));
155215
trace!("variant layout: {:#?}", dest.layout);
156-
(variant, dest)
216+
(variant, inner_dest)
157217
},
158218
layout::Variants::Single { index } => {
159219
(index, dest)
160220
}
161221
};
162222

223+
// Remember the length, in case we need to truncate
224+
let path_len = path.len();
225+
163226
// Validate all fields
164227
match dest.layout.fields {
165228
// primitives are unions with zero fields
166229
layout::FieldPlacement::Union(0) => {
167230
match dest.layout.abi {
168231
// nothing to do, whatever the pointer points to, it is never going to be read
169-
layout::Abi::Uninhabited => validation_failure!("a value of an uninhabited type", path),
232+
layout::Abi::Uninhabited =>
233+
return validation_failure!("a value of an uninhabited type", path),
170234
// check that the scalar is a valid pointer or that its bit range matches the
171235
// expectation.
172236
layout::Abi::Scalar(ref scalar_layout) => {
@@ -179,8 +243,8 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
179243
if let Scalar::Ptr(ptr) = scalar.not_undef()? {
180244
let alloc_kind = self.tcx.alloc_map.lock().get(ptr.alloc_id);
181245
if let Some(AllocType::Static(did)) = alloc_kind {
182-
// statics from other crates are already checked
183-
// extern statics should not be validated as they have no body
246+
// statics from other crates are already checked.
247+
// extern statics should not be validated as they have no body.
184248
if !did.is_local() || self.tcx.is_foreign_item(did) {
185249
return Ok(());
186250
}
@@ -190,12 +254,11 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
190254
let ptr_place = self.ref_to_mplace(value)?;
191255
// we have not encountered this pointer+layout combination before
192256
if seen.insert(ptr_place) {
193-
todo.push((ptr_place, format!("(*{})", path)))
257+
todo.push((ptr_place, path_clone_and_deref(path)));
194258
}
195259
}
196260
}
197261
}
198-
Ok(())
199262
},
200263
_ => bug!("bad abi for FieldPlacement::Union(0): {:#?}", dest.layout.abi),
201264
}
@@ -204,16 +267,14 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
204267
// We can't check unions, their bits are allowed to be anything.
205268
// The fields don't need to correspond to any bit pattern of the union's fields.
206269
// See https://github.com/rust-lang/rust/issues/32836#issuecomment-406875389
207-
Ok(())
208270
},
209271
layout::FieldPlacement::Array { .. } => {
210272
for (i, field) in self.mplace_array_fields(dest)?.enumerate() {
211273
let field = field?;
212-
let mut path = path.clone();
213-
self.dump_field_name(&mut path, dest.layout.ty, i as usize, variant).unwrap();
274+
path.push(PathElem::ArrayElem(i));
214275
self.validate_mplace(field, path, seen, todo)?;
276+
path.truncate(path_len);
215277
}
216-
Ok(())
217278
},
218279
layout::FieldPlacement::Arbitrary { ref offsets, .. } => {
219280
// fat pointers need special treatment
@@ -232,9 +293,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
232293
assert_eq!(ptr.extra, PlaceExtra::Length(len));
233294
let unpacked_ptr = self.unpack_unsized_mplace(ptr)?;
234295
if seen.insert(unpacked_ptr) {
235-
let mut path = path.clone();
236-
self.dump_field_name(&mut path, dest.layout.ty, 0, 0).unwrap();
237-
todo.push((unpacked_ptr, path))
296+
todo.push((unpacked_ptr, path_clone_and_deref(path)));
238297
}
239298
},
240299
Some(ty::TyDynamic(..)) => {
@@ -249,9 +308,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
249308
assert_eq!(ptr.extra, PlaceExtra::Vtable(vtable));
250309
let unpacked_ptr = self.unpack_unsized_mplace(ptr)?;
251310
if seen.insert(unpacked_ptr) {
252-
let mut path = path.clone();
253-
self.dump_field_name(&mut path, dest.layout.ty, 0, 0).unwrap();
254-
todo.push((unpacked_ptr, path))
311+
todo.push((unpacked_ptr, path_clone_and_deref(path)));
255312
}
256313
// FIXME: More checks for the vtable... making sure it is exactly
257314
// the one one would expect for this type.
@@ -261,84 +318,42 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
261318
None => {
262319
// Not a pointer, perform regular aggregate handling below
263320
for i in 0..offsets.len() {
264-
let mut path = path.clone();
265-
self.dump_field_name(&mut path, dest.layout.ty, i, variant).unwrap();
266321
let field = self.mplace_field(dest, i as u64)?;
322+
path.push(self.aggregate_field_path_elem(dest.layout.ty, variant, i));
267323
self.validate_mplace(field, path, seen, todo)?;
324+
path.truncate(path_len);
268325
}
269326
// FIXME: For a TyStr, check that this is valid UTF-8.
270327
},
271328
}
272-
273-
Ok(())
274329
}
275330
}
331+
Ok(())
276332
}
277333

278-
fn dump_field_name(&self, s: &mut String, ty: Ty<'tcx>, i: usize, variant: usize) -> ::std::fmt::Result {
334+
fn aggregate_field_path_elem(&self, ty: Ty<'tcx>, variant: usize, field: usize) -> PathElem {
279335
match ty.sty {
280-
ty::TyBool |
281-
ty::TyChar |
282-
ty::TyInt(_) |
283-
ty::TyUint(_) |
284-
ty::TyFloat(_) |
285-
ty::TyFnPtr(_) |
286-
ty::TyNever |
287-
ty::TyFnDef(..) |
288-
ty::TyGeneratorWitness(..) |
289-
ty::TyForeign(..) |
290-
ty::TyDynamic(..) => {
291-
bug!("field_name({:?}): not applicable", ty)
292-
}
293-
294-
// Potentially-fat pointers.
295-
ty::TyRef(_, pointee, _) |
296-
ty::TyRawPtr(ty::TypeAndMut { ty: pointee, .. }) => {
297-
assert!(i < 2);
298-
299-
// Reuse the fat *T type as its own thin pointer data field.
300-
// This provides information about e.g. DST struct pointees
301-
// (which may have no non-DST form), and will work as long
302-
// as the `Abi` or `FieldPlacement` is checked by users.
303-
if i == 0 {
304-
return write!(s, ".data_ptr");
305-
}
306-
307-
match self.tcx.struct_tail(pointee).sty {
308-
ty::TySlice(_) |
309-
ty::TyStr => write!(s, ".len"),
310-
ty::TyDynamic(..) => write!(s, ".vtable_ptr"),
311-
_ => bug!("field_name({:?}): not applicable", ty)
312-
}
313-
}
314-
315-
// Arrays and slices.
316-
ty::TyArray(_, _) |
317-
ty::TySlice(_) |
318-
ty::TyStr => write!(s, "[{}]", i),
319-
320336
// generators and closures.
321337
ty::TyClosure(def_id, _) | ty::TyGenerator(def_id, _, _) => {
322338
let node_id = self.tcx.hir.as_local_node_id(def_id).unwrap();
323-
let freevar = self.tcx.with_freevars(node_id, |fv| fv[i]);
324-
write!(s, ".upvar({})", self.tcx.hir.name(freevar.var_id()))
339+
let freevar = self.tcx.with_freevars(node_id, |fv| fv[field]);
340+
PathElem::ClosureVar(self.tcx.hir.name(freevar.var_id()))
325341
}
326342

327-
ty::TyTuple(_) => write!(s, ".{}", i),
343+
// tuples
344+
ty::TyTuple(_) => PathElem::TupleElem(field),
328345

329346
// enums
330347
ty::TyAdt(def, ..) if def.is_enum() => {
331348
let variant = &def.variants[variant];
332-
write!(s, ".{}::{}", variant.name, variant.fields[i].ident)
349+
PathElem::Field(variant.fields[field].ident.name)
333350
}
334351

335-
// other ADTs.
336-
ty::TyAdt(def, _) => write!(s, ".{}", def.non_enum_variant().fields[i].ident),
352+
// other ADTs
353+
ty::TyAdt(def, _) => PathElem::Field(def.non_enum_variant().fields[field].ident.name),
337354

338-
ty::TyProjection(_) | ty::TyAnon(..) | ty::TyParam(_) |
339-
ty::TyInfer(_) | ty::TyError => {
340-
bug!("dump_field_name: unexpected type `{}`", ty)
341-
}
355+
// nothing else has an aggregate layout
356+
_ => bug!("aggregate_field_path_elem: got non-aggregate type {:?}", ty),
342357
}
343358
}
344359
}

src/test/ui/consts/const-eval/double_check2.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ LL | / static FOO: (&Foo, &Bar) = unsafe {( //~ undefined behavior
55
LL | | Union { usize: &BAR }.foo,
66
LL | | Union { usize: &BAR }.bar,
77
LL | | )};
8-
| |___^ type validation failed: encountered 5 at (*.1).TAG, but expected something in the range 42..=99
8+
| |___^ type validation failed: encountered 5 at .1.<deref>.<enum-tag>, but expected something in the range 42..=99
99
|
1010
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
1111

src/test/ui/consts/const-eval/ub-enum-ptr.rs

Lines changed: 0 additions & 27 deletions
This file was deleted.

0 commit comments

Comments
 (0)