Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 2d81989

Browse files
committedOct 13, 2018
Auto merge of #54955 - RalfJung:miri-validate2, r=oli-obk
miri engine: Fix run-time validation This fixes all false positives that came up when actually enabling this in miri. r? @oli-obk
2 parents fb3b47a + 6426cbe commit 2d81989

File tree

16 files changed

+329
-123
lines changed

16 files changed

+329
-123
lines changed
 

‎src/Cargo.lock

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -825,16 +825,6 @@ name = "getopts"
825825
version = "0.2.17"
826826
source = "registry+https://github.com/rust-lang/crates.io-index"
827827

828-
[[package]]
829-
name = "getset"
830-
version = "0.0.6"
831-
source = "registry+https://github.com/rust-lang/crates.io-index"
832-
dependencies = [
833-
"proc-macro2 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
834-
"quote 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
835-
"syn 0.13.11 (registry+https://github.com/rust-lang/crates.io-index)",
836-
]
837-
838828
[[package]]
839829
name = "git2"
840830
version = "0.7.5"
@@ -1303,7 +1293,7 @@ dependencies = [
13031293
"compiletest_rs 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
13041294
"env_logger 0.5.12 (registry+https://github.com/rust-lang/crates.io-index)",
13051295
"log 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
1306-
"vergen 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
1296+
"vergen 3.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
13071297
]
13081298

13091299
[[package]]
@@ -3072,13 +3062,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
30723062

30733063
[[package]]
30743064
name = "vergen"
3075-
version = "2.0.0"
3065+
version = "3.0.3"
30763066
source = "registry+https://github.com/rust-lang/crates.io-index"
30773067
dependencies = [
30783068
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
30793069
"chrono 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
3080-
"error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
3081-
"getset 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
3070+
"failure 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
30823071
]
30833072

30843073
[[package]]
@@ -3245,7 +3234,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
32453234
"checksum futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "1a70b146671de62ec8c8ed572219ca5d594d9b06c0b364d5e67b722fc559b48c"
32463235
"checksum fwdansi 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "34dd4c507af68d37ffef962063dfa1944ce0dd4d5b82043dbab1dabe088610c3"
32473236
"checksum getopts 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)" = "b900c08c1939860ce8b54dc6a89e26e00c04c380fd0e09796799bd7f12861e05"
3248-
"checksum getset 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "54c7f36a235738bb25904d6a2b3dbb28f6f5736cd3918c4bf80d6bb236200782"
32493237
"checksum git2 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)" = "591f8be1674b421644b6c030969520bc3fa12114d2eb467471982ed3e9584e71"
32503238
"checksum git2-curl 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b502f6b1b467957403d168f0039e0c46fa6a1220efa2adaef25d5b267b5fe024"
32513239
"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
@@ -3419,7 +3407,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
34193407
"checksum utf8-ranges 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd70f467df6810094968e2fce0ee1bd0e87157aceb026a8c083bcf5e25b9efe4"
34203408
"checksum vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "def296d3eb3b12371b2c7d0e83bfe1403e4db2d7a0bba324a12b21c4ee13143d"
34213409
"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
3422-
"checksum vergen 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9a16834fc61e1492c07dae49b6c14b55f8b1d43a5f5f9e9a2ecc063f47b9f93c"
3410+
"checksum vergen 3.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1b9696d96ec5d68984d060af80d7ba0ed4eb533978a0efb05ed4b8465f20d04f"
34233411
"checksum version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7716c242968ee87e5542f8021178248f267f295a5c4803beae8b8b7fd9bc6051"
34243412
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
34253413
"checksum wait-timeout 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b9f3bf741a801531993db6478b95682117471f76916f5e690dd8d45395b09349"

‎src/librustc_mir/const_eval.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ use rustc::mir::interpret::{
3333
Scalar, Allocation, AllocId, ConstValue,
3434
};
3535
use interpret::{self,
36-
Place, PlaceTy, MemPlace, OpTy, Operand, Value,
36+
PlaceTy, MemPlace, OpTy, Operand, Value,
3737
EvalContext, StackPopCleanup, MemoryKind,
3838
snapshot,
3939
};
@@ -55,13 +55,14 @@ pub fn mk_borrowck_eval_cx<'a, 'mir, 'tcx>(
5555
let param_env = tcx.param_env(instance.def_id());
5656
let mut ecx = EvalContext::new(tcx.at(span), param_env, CompileTimeInterpreter::new(), ());
5757
// insert a stack frame so any queries have the correct substs
58+
// cannot use `push_stack_frame`; if we do `const_prop` explodes
5859
ecx.stack.push(interpret::Frame {
5960
block: mir::START_BLOCK,
6061
locals: IndexVec::new(),
6162
instance,
6263
span,
6364
mir,
64-
return_place: Place::null(tcx),
65+
return_place: None,
6566
return_to_block: StackPopCleanup::Goto(None), // never pop
6667
stmt: 0,
6768
});
@@ -82,7 +83,7 @@ pub fn mk_eval_cx<'a, 'tcx>(
8283
instance,
8384
mir.span,
8485
mir,
85-
Place::null(tcx),
86+
None,
8687
StackPopCleanup::Goto(None), // never pop
8788
)?;
8889
Ok(ecx)
@@ -187,7 +188,7 @@ fn eval_body_using_ecx<'mir, 'tcx>(
187188
cid.instance,
188189
mir.span,
189190
mir,
190-
Place::Ptr(*ret),
191+
Some(ret.into()),
191192
StackPopCleanup::None { cleanup: false },
192193
)?;
193194

@@ -342,7 +343,11 @@ impl<'a, 'mir, 'tcx> interpret::Machine<'a, 'mir, 'tcx>
342343
type MemoryMap = FxHashMap<AllocId, (MemoryKind<!>, Allocation<()>)>;
343344

344345
const STATIC_KIND: Option<!> = None; // no copying of statics allowed
345-
const ENFORCE_VALIDITY: bool = false; // for now, we don't
346+
347+
#[inline(always)]
348+
fn enforce_validity(_ecx: &EvalContext<'a, 'mir, 'tcx, Self>) -> bool {
349+
false // for now, we don't enforce validity
350+
}
346351

347352
fn find_fn(
348353
ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>,

‎src/librustc_mir/interpret/cast.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,8 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
322322
// For now, upcasts are limited to changes in marker
323323
// traits, and hence never actually require an actual
324324
// change to the vtable.
325-
self.copy_op(src, dest)
325+
let val = self.read_value(src)?;
326+
self.write_value(*val, dest)
326327
}
327328
(_, &ty::Dynamic(ref data, _)) => {
328329
// Initial cast from sized to dyn trait

‎src/librustc_mir/interpret/eval_context.rs

Lines changed: 52 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ use rustc::mir::interpret::{
3131
use syntax::source_map::{self, Span};
3232

3333
use super::{
34-
Value, Operand, MemPlace, MPlaceTy, Place, ScalarMaybeUndef,
34+
Value, Operand, MemPlace, MPlaceTy, Place, PlaceTy, ScalarMaybeUndef,
3535
Memory, Machine
3636
};
3737

@@ -73,8 +73,9 @@ pub struct Frame<'mir, 'tcx: 'mir, Tag=()> {
7373
/// Work to perform when returning from this function
7474
pub return_to_block: StackPopCleanup,
7575

76-
/// The location where the result of the current stack frame should be written to.
77-
pub return_place: Place<Tag>,
76+
/// The location where the result of the current stack frame should be written to,
77+
/// and its layout in the caller.
78+
pub return_place: Option<PlaceTy<'tcx, Tag>>,
7879

7980
/// The list of locals for this stack frame, stored in order as
8081
/// `[return_ptr, arguments..., variables..., temporaries...]`.
@@ -97,7 +98,8 @@ pub struct Frame<'mir, 'tcx: 'mir, Tag=()> {
9798
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
9899
pub enum StackPopCleanup {
99100
/// Jump to the next block in the caller, or cause UB if None (that's a function
100-
/// that may never return).
101+
/// that may never return). Also store layout of return place so
102+
/// we can validate it at that layout.
101103
Goto(Option<mir::BasicBlock>),
102104
/// Just do nohing: Used by Main and for the box_alloc hook in miri.
103105
/// `cleanup` says whether locals are deallocated. Static computation
@@ -330,22 +332,16 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tc
330332
}
331333

332334
/// Return the actual dynamic size and alignment of the place at the given type.
333-
/// Only the `meta` part of the place matters.
335+
/// Only the "meta" (metadata) part of the place matters.
336+
/// This can fail to provide an answer for extern types.
334337
pub(super) fn size_and_align_of(
335338
&self,
336339
metadata: Option<Scalar<M::PointerTag>>,
337340
layout: TyLayout<'tcx>,
338-
) -> EvalResult<'tcx, (Size, Align)> {
339-
let metadata = match metadata {
340-
None => {
341-
assert!(!layout.is_unsized());
342-
return Ok(layout.size_and_align())
343-
}
344-
Some(metadata) => {
345-
assert!(layout.is_unsized());
346-
metadata
347-
}
348-
};
341+
) -> EvalResult<'tcx, Option<(Size, Align)>> {
342+
if !layout.is_unsized() {
343+
return Ok(Some(layout.size_and_align()));
344+
}
349345
match layout.ty.sty {
350346
ty::Adt(..) | ty::Tuple(..) => {
351347
// First get the size of all statically known fields.
@@ -365,9 +361,11 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tc
365361
);
366362

367363
// Recurse to get the size of the dynamically sized field (must be
368-
// the last field).
364+
// the last field). Can't have foreign types here, how would we
365+
// adjust alignment and size for them?
369366
let field = layout.field(self, layout.fields.count() - 1)?;
370-
let (unsized_size, unsized_align) = self.size_and_align_of(Some(metadata), field)?;
367+
let (unsized_size, unsized_align) = self.size_and_align_of(metadata, field)?
368+
.expect("Fields cannot be extern types");
371369

372370
// FIXME (#26403, #27023): We should be adding padding
373371
// to `sized_size` (to accommodate the `unsized_align`
@@ -394,18 +392,22 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tc
394392
//
395393
// `(size + (align-1)) & -align`
396394

397-
Ok((size.abi_align(align), align))
395+
Ok(Some((size.abi_align(align), align)))
398396
}
399397
ty::Dynamic(..) => {
400-
let vtable = metadata.to_ptr()?;
398+
let vtable = metadata.expect("dyn trait fat ptr must have vtable").to_ptr()?;
401399
// the second entry in the vtable is the dynamic size of the object.
402-
self.read_size_and_align_from_vtable(vtable)
400+
Ok(Some(self.read_size_and_align_from_vtable(vtable)?))
403401
}
404402

405403
ty::Slice(_) | ty::Str => {
406-
let len = metadata.to_usize(self)?;
404+
let len = metadata.expect("slice fat ptr must have vtable").to_usize(self)?;
407405
let (elem_size, align) = layout.field(self, 0)?.size_and_align();
408-
Ok((elem_size * len, align))
406+
Ok(Some((elem_size * len, align)))
407+
}
408+
409+
ty::Foreign(_) => {
410+
Ok(None)
409411
}
410412

411413
_ => bug!("size_and_align_of::<{:?}> not supported", layout.ty),
@@ -415,7 +417,7 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tc
415417
pub fn size_and_align_of_mplace(
416418
&self,
417419
mplace: MPlaceTy<'tcx, M::PointerTag>
418-
) -> EvalResult<'tcx, (Size, Align)> {
420+
) -> EvalResult<'tcx, Option<(Size, Align)>> {
419421
self.size_and_align_of(mplace.meta, mplace.layout)
420422
}
421423

@@ -424,7 +426,7 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tc
424426
instance: ty::Instance<'tcx>,
425427
span: source_map::Span,
426428
mir: &'mir mir::Mir<'tcx>,
427-
return_place: Place<M::PointerTag>,
429+
return_place: Option<PlaceTy<'tcx, M::PointerTag>>,
428430
return_to_block: StackPopCleanup,
429431
) -> EvalResult<'tcx> {
430432
::log_settings::settings().indentation += 1;
@@ -509,15 +511,38 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tc
509511
}
510512
StackPopCleanup::None { cleanup } => {
511513
if !cleanup {
512-
// Leak the locals
514+
// Leak the locals. Also skip validation, this is only used by
515+
// static/const computation which does its own (stronger) final
516+
// validation.
513517
return Ok(());
514518
}
515519
}
516520
}
517-
// deallocate all locals that are backed by an allocation
521+
// Deallocate all locals that are backed by an allocation.
518522
for local in frame.locals {
519523
self.deallocate_local(local)?;
520524
}
525+
// Validate the return value.
526+
if let Some(return_place) = frame.return_place {
527+
if M::enforce_validity(self) {
528+
// Data got changed, better make sure it matches the type!
529+
// It is still possible that the return place held invalid data while
530+
// the function is running, but that's okay because nobody could have
531+
// accessed that same data from the "outside" to observe any broken
532+
// invariant -- that is, unless a function somehow has a ptr to
533+
// its return place... but the way MIR is currently generated, the
534+
// return place is always a local and then this cannot happen.
535+
self.validate_operand(
536+
self.place_to_op(return_place)?,
537+
&mut vec![],
538+
None,
539+
/*const_mode*/false,
540+
)?;
541+
}
542+
} else {
543+
// Uh, that shouln't happen... the function did not intend to return
544+
return err!(Unreachable);
545+
}
521546

522547
Ok(())
523548
}

‎src/librustc_mir/interpret/intrinsics.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -151,11 +151,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
151151
self.write_scalar(val, dest)?;
152152
}
153153
"transmute" => {
154-
// Go through an allocation, to make sure the completely different layouts
155-
// do not pose a problem. (When the user transmutes through a union,
156-
// there will not be a layout mismatch.)
157-
let dest = self.force_allocation(dest)?;
158-
self.copy_op(args[0], dest.into())?;
154+
self.copy_op_transmute(args[0], dest)?;
159155
}
160156

161157
_ => return Ok(false),

‎src/librustc_mir/interpret/machine.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ pub trait Machine<'a, 'mir, 'tcx>: Sized {
8686
const STATIC_KIND: Option<Self::MemoryKinds>;
8787

8888
/// Whether to enforce the validity invariant
89-
const ENFORCE_VALIDITY: bool;
89+
fn enforce_validity(ecx: &EvalContext<'a, 'mir, 'tcx, Self>) -> bool;
9090

9191
/// Called before a basic block terminator is executed.
9292
/// You can use this to detect endlessly running programs.

‎src/librustc_mir/interpret/memory.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -650,7 +650,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
650650
}
651651

652652
/// It is the caller's responsibility to handle undefined and pointer bytes.
653-
/// However, this still checks that there are no relocations on the egdes.
653+
/// However, this still checks that there are no relocations on the *egdes*.
654654
#[inline]
655655
fn get_bytes_with_undef_and_ptr(
656656
&self,
@@ -842,6 +842,29 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
842842
}
843843
}
844844

845+
pub fn check_bytes(
846+
&self,
847+
ptr: Scalar<M::PointerTag>,
848+
size: Size,
849+
allow_ptr_and_undef: bool,
850+
) -> EvalResult<'tcx> {
851+
// Empty accesses don't need to be valid pointers, but they should still be non-NULL
852+
let align = Align::from_bytes(1, 1).unwrap();
853+
if size.bytes() == 0 {
854+
self.check_align(ptr, align)?;
855+
return Ok(());
856+
}
857+
let ptr = ptr.to_ptr()?;
858+
// Check bounds, align and relocations on the edges
859+
self.get_bytes_with_undef_and_ptr(ptr, size, align)?;
860+
// Check undef and ptr
861+
if !allow_ptr_and_undef {
862+
self.check_defined(ptr, size)?;
863+
self.check_relocations(ptr, size)?;
864+
}
865+
Ok(())
866+
}
867+
845868
pub fn read_bytes(&self, ptr: Scalar<M::PointerTag>, size: Size) -> EvalResult<'tcx, &[u8]> {
846869
// Empty accesses don't need to be valid pointers, but they should still be non-NULL
847870
let align = Align::from_bytes(1, 1).unwrap();

‎src/librustc_mir/interpret/place.rs

Lines changed: 141 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -256,12 +256,6 @@ impl<'tcx, Tag: ::std::fmt::Debug> Place<Tag> {
256256
}
257257

258258
impl<'tcx, Tag: ::std::fmt::Debug> PlaceTy<'tcx, Tag> {
259-
/// Produces a Place that will error if attempted to be read from or written to
260-
#[inline]
261-
pub fn null(cx: impl HasDataLayout, layout: TyLayout<'tcx>) -> Self {
262-
PlaceTy { place: Place::from_scalar_ptr(Scalar::ptr_null(cx), layout.align), layout }
263-
}
264-
265259
#[inline]
266260
pub fn to_mem_place(self) -> MPlaceTy<'tcx, Tag> {
267261
MPlaceTy { mplace: self.place.to_mem_place(), layout: self.layout }
@@ -325,9 +319,9 @@ where
325319
// Offset may need adjustment for unsized fields
326320
let (meta, offset) = if field_layout.is_unsized() {
327321
// re-use parent metadata to determine dynamic field layout
328-
let (_, align) = self.size_and_align_of(base.meta, field_layout)?;
322+
let (_, align) = self.size_and_align_of(base.meta, field_layout)?
323+
.expect("Fields cannot be extern types");
329324
(base.meta, offset.abi_align(align))
330-
331325
} else {
332326
// base.meta could be present; we might be accessing a sized field of an unsized
333327
// struct.
@@ -565,9 +559,15 @@ where
565559
) -> EvalResult<'tcx, PlaceTy<'tcx, M::PointerTag>> {
566560
use rustc::mir::Place::*;
567561
let place = match *mir_place {
568-
Local(mir::RETURN_PLACE) => PlaceTy {
569-
place: self.frame().return_place,
570-
layout: self.layout_of_local(self.cur_frame(), mir::RETURN_PLACE)?,
562+
Local(mir::RETURN_PLACE) => match self.frame().return_place {
563+
Some(return_place) =>
564+
// We use our layout to verify our assumption; caller will validate
565+
// their layout on return.
566+
PlaceTy {
567+
place: *return_place,
568+
layout: self.layout_of_local(self.cur_frame(), mir::RETURN_PLACE)?,
569+
},
570+
None => return err!(InvalidNullPointerUsage),
571571
},
572572
Local(local) => PlaceTy {
573573
place: Place::Local {
@@ -599,18 +599,47 @@ where
599599
}
600600

601601
/// Write a value to a place
602+
#[inline(always)]
602603
pub fn write_value(
603604
&mut self,
604605
src_val: Value<M::PointerTag>,
605606
dest: PlaceTy<'tcx, M::PointerTag>,
606607
) -> EvalResult<'tcx> {
607-
trace!("write_value: {:?} <- {:?}", *dest, src_val);
608-
// Check that the value actually is okay for that type
609-
if M::ENFORCE_VALIDITY {
610-
// Something changed somewhere, better make sure it matches the type!
611-
let op = OpTy { op: Operand::Immediate(src_val), layout: dest.layout };
612-
self.validate_operand(op, &mut vec![], None, /*const_mode*/false)?;
608+
self.write_value_no_validate(src_val, dest)?;
609+
610+
if M::enforce_validity(self) {
611+
// Data got changed, better make sure it matches the type!
612+
self.validate_operand(self.place_to_op(dest)?, &mut vec![], None, /*const_mode*/false)?;
613+
}
614+
615+
Ok(())
616+
}
617+
618+
/// Write a value to a place.
619+
/// If you use this you are responsible for validating that things got copied at the
620+
/// right type.
621+
fn write_value_no_validate(
622+
&mut self,
623+
src_val: Value<M::PointerTag>,
624+
dest: PlaceTy<'tcx, M::PointerTag>,
625+
) -> EvalResult<'tcx> {
626+
if cfg!(debug_assertions) {
627+
// This is a very common path, avoid some checks in release mode
628+
assert!(!dest.layout.is_unsized(), "Cannot write unsized data");
629+
match src_val {
630+
Value::Scalar(ScalarMaybeUndef::Scalar(Scalar::Ptr(_))) =>
631+
assert_eq!(self.pointer_size(), dest.layout.size,
632+
"Size mismatch when writing pointer"),
633+
Value::Scalar(ScalarMaybeUndef::Scalar(Scalar::Bits { size, .. })) =>
634+
assert_eq!(Size::from_bytes(size.into()), dest.layout.size,
635+
"Size mismatch when writing bits"),
636+
Value::Scalar(ScalarMaybeUndef::Undef) => {}, // undef can have any size
637+
Value::ScalarPair(_, _) => {
638+
// FIXME: Can we check anything here?
639+
}
640+
}
613641
}
642+
trace!("write_value: {:?} <- {:?}: {}", *dest, src_val, dest.layout.ty);
614643

615644
// See if we can avoid an allocation. This is the counterpart to `try_read_value`,
616645
// but not factored as a separate function.
@@ -627,15 +656,16 @@ where
627656
},
628657
Place::Ptr(mplace) => mplace, // already in memory
629658
};
659+
let dest = MPlaceTy { mplace, layout: dest.layout };
630660

631661
// This is already in memory, write there.
632-
let dest = MPlaceTy { mplace, layout: dest.layout };
633-
self.write_value_to_mplace(src_val, dest)
662+
self.write_value_to_mplace_no_validate(src_val, dest)
634663
}
635664

636-
/// Write a value to memory. This does NOT do validation, so you better had already
637-
/// done that before calling this!
638-
fn write_value_to_mplace(
665+
/// Write a value to memory.
666+
/// If you use this you are responsible for validating that things git copied at the
667+
/// right type.
668+
fn write_value_to_mplace_no_validate(
639669
&mut self,
640670
value: Value<M::PointerTag>,
641671
dest: MPlaceTy<'tcx, M::PointerTag>,
@@ -653,8 +683,17 @@ where
653683
}
654684

655685
let ptr = ptr.to_ptr()?;
686+
// FIXME: We should check that there are dest.layout.size many bytes available in
687+
// memory. The code below is not sufficient, with enough padding it might not
688+
// cover all the bytes!
656689
match value {
657690
Value::Scalar(scalar) => {
691+
match dest.layout.abi {
692+
layout::Abi::Scalar(_) => {}, // fine
693+
_ => bug!("write_value_to_mplace: invalid Scalar layout: {:#?}",
694+
dest.layout)
695+
}
696+
658697
self.memory.write_scalar(
659698
ptr, ptr_align.min(dest.layout.align), scalar, dest.layout.size
660699
)
@@ -670,45 +709,109 @@ where
670709
let b_offset = a_size.abi_align(b_align);
671710
let b_ptr = ptr.offset(b_offset, &self)?.into();
672711

712+
// It is tempting to verify `b_offset` against `layout.fields.offset(1)`,
713+
// but that does not work: We could be a newtype around a pair, then the
714+
// fields do not match the `ScalarPair` components.
715+
673716
self.memory.write_scalar(ptr, ptr_align.min(a_align), a_val, a_size)?;
674717
self.memory.write_scalar(b_ptr, ptr_align.min(b_align), b_val, b_size)
675718
}
676719
}
677720
}
678721

679-
/// Copy the data from an operand to a place
722+
/// Copy the data from an operand to a place. This does not support transmuting!
723+
/// Use `copy_op_transmute` if the layouts could disagree.
724+
#[inline(always)]
680725
pub fn copy_op(
681726
&mut self,
682727
src: OpTy<'tcx, M::PointerTag>,
683728
dest: PlaceTy<'tcx, M::PointerTag>,
684729
) -> EvalResult<'tcx> {
685-
assert!(!src.layout.is_unsized() && !dest.layout.is_unsized(),
730+
self.copy_op_no_validate(src, dest)?;
731+
732+
if M::enforce_validity(self) {
733+
// Data got changed, better make sure it matches the type!
734+
self.validate_operand(self.place_to_op(dest)?, &mut vec![], None, /*const_mode*/false)?;
735+
}
736+
737+
Ok(())
738+
}
739+
740+
/// Copy the data from an operand to a place. This does not support transmuting!
741+
/// Use `copy_op_transmute` if the layouts could disagree.
742+
/// Also, if you use this you are responsible for validating that things git copied at the
743+
/// right type.
744+
fn copy_op_no_validate(
745+
&mut self,
746+
src: OpTy<'tcx, M::PointerTag>,
747+
dest: PlaceTy<'tcx, M::PointerTag>,
748+
) -> EvalResult<'tcx> {
749+
debug_assert!(!src.layout.is_unsized() && !dest.layout.is_unsized(),
686750
"Cannot copy unsized data");
687-
assert_eq!(src.layout.size, dest.layout.size,
688-
"Size mismatch when copying!\nsrc: {:#?}\ndest: {:#?}", src, dest);
751+
// We do NOT compare the types for equality, because well-typed code can
752+
// actually "transmute" `&mut T` to `&T` in an assignment without a cast.
753+
assert!(src.layout.details == dest.layout.details,
754+
"Layout mismatch when copying!\nsrc: {:#?}\ndest: {:#?}", src, dest);
689755

690756
// Let us see if the layout is simple so we take a shortcut, avoid force_allocation.
691-
let (src_ptr, src_align) = match self.try_read_value(src)? {
692-
Ok(src_val) =>
693-
// Yay, we got a value that we can write directly. We write with the
694-
// *source layout*, because that was used to load, and if they do not match
695-
// this is a transmute we want to support.
696-
return self.write_value(src_val, PlaceTy { place: *dest, layout: src.layout }),
697-
Err(mplace) => mplace.to_scalar_ptr_align(),
757+
let src = match self.try_read_value(src)? {
758+
Ok(src_val) => {
759+
// Yay, we got a value that we can write directly.
760+
return self.write_value_no_validate(src_val, dest);
761+
}
762+
Err(mplace) => mplace,
698763
};
699764
// Slow path, this does not fit into an immediate. Just memcpy.
700-
trace!("copy_op: {:?} <- {:?}", *dest, *src);
765+
trace!("copy_op: {:?} <- {:?}: {}", *dest, src, dest.layout.ty);
766+
701767
let dest = self.force_allocation(dest)?;
768+
let (src_ptr, src_align) = src.to_scalar_ptr_align();
702769
let (dest_ptr, dest_align) = dest.to_scalar_ptr_align();
703770
self.memory.copy(
704771
src_ptr, src_align,
705772
dest_ptr, dest_align,
706-
src.layout.size, false
773+
dest.layout.size, false
774+
)?;
775+
776+
Ok(())
777+
}
778+
779+
/// Copy the data from an operand to a place. The layouts may disagree, but they must
780+
/// have the same size.
781+
pub fn copy_op_transmute(
782+
&mut self,
783+
src: OpTy<'tcx, M::PointerTag>,
784+
dest: PlaceTy<'tcx, M::PointerTag>,
785+
) -> EvalResult<'tcx> {
786+
if src.layout.details == dest.layout.details {
787+
// Fast path: Just use normal `copy_op`
788+
return self.copy_op(src, dest);
789+
}
790+
// We still require the sizes to match
791+
debug_assert!(!src.layout.is_unsized() && !dest.layout.is_unsized(),
792+
"Cannot copy unsized data");
793+
assert!(src.layout.size == dest.layout.size,
794+
"Size mismatch when transmuting!\nsrc: {:#?}\ndest: {:#?}", src, dest);
795+
796+
// The hard case is `ScalarPair`. `src` is already read from memory in this case,
797+
// using `src.layout` to figure out which bytes to use for the 1st and 2nd field.
798+
// We have to write them to `dest` at the offsets they were *read at*, which is
799+
// not necessarily the same as the offsets in `dest.layout`!
800+
// Hence we do the copy with the source layout on both sides. We also make sure to write
801+
// into memory, because if `dest` is a local we would not even have a way to write
802+
// at the `src` offsets; the fact that we came from a different layout would
803+
// just be lost.
804+
let dest = self.force_allocation(dest)?;
805+
self.copy_op_no_validate(
806+
src,
807+
PlaceTy::from(MPlaceTy { mplace: *dest, layout: src.layout }),
707808
)?;
708-
if M::ENFORCE_VALIDITY {
709-
// Something changed somewhere, better make sure it matches the type!
809+
810+
if M::enforce_validity(self) {
811+
// Data got changed, better make sure it matches the type!
710812
self.validate_operand(dest.into(), &mut vec![], None, /*const_mode*/false)?;
711813
}
814+
712815
Ok(())
713816
}
714817

@@ -734,7 +837,7 @@ where
734837
let ptr = self.allocate(local_layout, MemoryKind::Stack)?;
735838
// We don't have to validate as we can assume the local
736839
// was already valid for its type.
737-
self.write_value_to_mplace(value, ptr)?;
840+
self.write_value_to_mplace_no_validate(value, ptr)?;
738841
let mplace = ptr.mplace;
739842
// Update the local
740843
*self.stack[frame].locals[local].access_mut()? =

‎src/librustc_mir/interpret/snapshot.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ struct FrameSnapshot<'a, 'tcx: 'a> {
337337
instance: &'a ty::Instance<'tcx>,
338338
span: &'a Span,
339339
return_to_block: &'a StackPopCleanup,
340-
return_place: Place<(), AllocIdSnapshot<'a>>,
340+
return_place: Option<Place<(), AllocIdSnapshot<'a>>>,
341341
locals: IndexVec<mir::Local, LocalValue<(), AllocIdSnapshot<'a>>>,
342342
block: &'a mir::BasicBlock,
343343
stmt: usize,
@@ -362,7 +362,7 @@ impl<'a, 'mir, 'tcx: 'mir> HashStable<StableHashingContext<'a>> for Frame<'mir,
362362
} = self;
363363

364364
(mir, instance, span, return_to_block).hash_stable(hcx, hasher);
365-
(return_place, locals, block, stmt).hash_stable(hcx, hasher);
365+
(return_place.as_ref().map(|r| &**r), locals, block, stmt).hash_stable(hcx, hasher);
366366
}
367367
}
368368
impl<'a, 'mir, 'tcx, Ctx> Snapshot<'a, Ctx> for &'a Frame<'mir, 'tcx>
@@ -388,7 +388,7 @@ impl<'a, 'mir, 'tcx, Ctx> Snapshot<'a, Ctx> for &'a Frame<'mir, 'tcx>
388388
return_to_block,
389389
block,
390390
stmt: *stmt,
391-
return_place: return_place.snapshot(ctx),
391+
return_place: return_place.map(|r| r.snapshot(ctx)),
392392
locals: locals.iter().map(|local| local.snapshot(ctx)).collect(),
393393
}
394394
}

‎src/librustc_mir/interpret/terminator.rs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use rustc_target::spec::abi::Abi;
1717

1818
use rustc::mir::interpret::{EvalResult, PointerArithmetic, EvalErrorKind, Scalar};
1919
use super::{
20-
EvalContext, Machine, Value, OpTy, Place, PlaceTy, Operand, StackPopCleanup
20+
EvalContext, Machine, Value, OpTy, PlaceTy, MPlaceTy, Operand, StackPopCleanup
2121
};
2222

2323
impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
@@ -39,7 +39,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
3939
use rustc::mir::TerminatorKind::*;
4040
match terminator.kind {
4141
Return => {
42-
self.dump_place(self.frame().return_place);
42+
self.frame().return_place.map(|r| self.dump_place(*r));
4343
self.pop_stack_frame()?
4444
}
4545

@@ -222,7 +222,8 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
222222
if !Self::check_argument_compat(caller_arg.layout, callee_arg.layout) {
223223
return err!(FunctionArgMismatch(caller_arg.layout.ty, callee_arg.layout.ty));
224224
}
225-
self.copy_op(caller_arg, callee_arg)
225+
// We allow some transmutes here
226+
self.copy_op_transmute(caller_arg, callee_arg)
226227
}
227228

228229
/// Call this function -- pushing the stack frame and initializing the arguments.
@@ -285,15 +286,11 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
285286
None => return Ok(()),
286287
};
287288

288-
let return_place = match dest {
289-
Some(place) => *place,
290-
None => Place::null(&self), // any access will error. good!
291-
};
292289
self.push_stack_frame(
293290
instance,
294291
span,
295292
mir,
296-
return_place,
293+
dest,
297294
StackPopCleanup::Goto(ret),
298295
)?;
299296

@@ -382,10 +379,13 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
382379
));
383380
}
384381
} else {
385-
// FIXME: The caller thinks this function cannot return. How do
386-
// we verify that the callee agrees?
387-
// On the plus side, the the callee ever writes to its return place,
388-
// that will be detected as UB (because we set that to NULL above).
382+
let callee_layout =
383+
self.layout_of_local(self.cur_frame(), mir::RETURN_PLACE)?;
384+
if !callee_layout.abi.is_uninhabited() {
385+
return err!(FunctionRetMismatch(
386+
self.tcx.types.never, callee_layout.ty
387+
));
388+
}
389389
}
390390
Ok(())
391391
})();
@@ -451,14 +451,14 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
451451
};
452452

453453
let ty = self.tcx.mk_unit(); // return type is ()
454-
let dest = PlaceTy::null(&self, self.layout_of(ty)?);
454+
let dest = MPlaceTy::dangling(self.layout_of(ty)?, &self);
455455

456456
self.eval_fn_call(
457457
instance,
458458
span,
459459
Abi::Rust,
460460
&[arg],
461-
Some(dest),
461+
Some(dest.into()),
462462
Some(target),
463463
)
464464
}

‎src/librustc_mir/interpret/validity.rs

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,11 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
208208
// for safe ptrs, also check the ptr values itself
209209
if !ty.is_unsafe_ptr() {
210210
// Make sure this is non-NULL and aligned
211-
let (size, align) = self.size_and_align_of(place.meta, place.layout)?;
211+
let (size, align) = self.size_and_align_of(place.meta, place.layout)?
212+
// for the purpose of validity, consider foreign types to have
213+
// alignment and size determined by the layout (size will be 0,
214+
// alignment should take attributes into account).
215+
.unwrap_or_else(|| place.layout.size_and_align());
212216
match self.memory.check_align(place.ptr, align) {
213217
Ok(_) => {},
214218
Err(err) => match err.kind {
@@ -218,7 +222,9 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
218222
return validation_failure!("unaligned reference", path),
219223
_ =>
220224
return validation_failure!(
221-
"dangling (deallocated) reference", path
225+
"dangling (out-of-bounds) reference (might be NULL at \
226+
run-time)",
227+
path
222228
),
223229
}
224230
}
@@ -490,9 +496,10 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
490496
}
491497
// Special handling for arrays/slices of builtin integer types
492498
ty::Array(tys, ..) | ty::Slice(tys) if {
493-
// This optimization applies only for integer types
499+
// This optimization applies only for integer and floating point types
500+
// (i.e., types that can hold arbitrary bytes).
494501
match tys.sty {
495-
ty::Int(..) | ty::Uint(..) => true,
502+
ty::Int(..) | ty::Uint(..) | ty::Float(..) => true,
496503
_ => false,
497504
}
498505
} => {
@@ -504,9 +511,20 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
504511
// This is the size in bytes of the whole array.
505512
let size = Size::from_bytes(ty_size * len);
506513

507-
match self.memory.read_bytes(dest.ptr, size) {
514+
// In run-time mode, we accept pointers in here. This is actually more
515+
// permissive than a per-element check would be, e.g. we accept
516+
// an &[u8] that contains a pointer even though bytewise checking would
517+
// reject it. However, that's good: We don't inherently want
518+
// to reject those pointers, we just do not have the machinery to
519+
// talk about parts of a pointer.
520+
// We also accept undef, for consistency with the type-based checks.
521+
match self.memory.check_bytes(
522+
dest.ptr,
523+
size,
524+
/*allow_ptr_and_undef*/!const_mode,
525+
) {
508526
// In the happy case, we needn't check anything else.
509-
Ok(_) => {},
527+
Ok(()) => {},
510528
// Some error happened, try to provide a more detailed description.
511529
Err(err) => {
512530
// For some errors we might be able to provide extra information
@@ -553,11 +571,11 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
553571
match layout.ty.sty {
554572
// generators and closures.
555573
ty::Closure(def_id, _) | ty::Generator(def_id, _, _) => {
556-
if let Some(node_id) = self.tcx.hir.as_local_node_id(def_id) {
557-
let freevar = self.tcx.with_freevars(node_id, |fv| fv[field]);
558-
PathElem::ClosureVar(self.tcx.hir.name(freevar.var_id()))
574+
if let Some(upvar) = self.tcx.optimized_mir(def_id).upvar_decls.get(field) {
575+
PathElem::ClosureVar(upvar.debug_name)
559576
} else {
560-
// The closure is not local, so we cannot get the name
577+
// Sometimes the index is beyond the number of freevars (seen
578+
// for a generator).
561579
PathElem::ClosureVar(Symbol::intern(&field.to_string()))
562580
}
563581
}

‎src/test/ui/consts/const-eval/ub-ref.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ const NULL: &u16 = unsafe { mem::transmute(0usize) };
2121
const REF_AS_USIZE: usize = unsafe { mem::transmute(&0) };
2222
//~^ ERROR this constant likely exhibits undefined behavior
2323

24+
const REF_AS_USIZE_SLICE: &[usize] = &[unsafe { mem::transmute(&0) }];
25+
//~^ ERROR this constant likely exhibits undefined behavior
26+
2427
const USIZE_AS_REF: &'static u8 = unsafe { mem::transmute(1337usize) };
2528
//~^ ERROR this constant likely exhibits undefined behavior
2629

‎src/test/ui/consts/const-eval/ub-ref.stderr

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,19 @@ LL | const REF_AS_USIZE: usize = unsafe { mem::transmute(&0) };
2525
error[E0080]: this constant likely exhibits undefined behavior
2626
--> $DIR/ub-ref.rs:24:1
2727
|
28+
LL | const REF_AS_USIZE_SLICE: &[usize] = &[unsafe { mem::transmute(&0) }];
29+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ a raw memory access tried to access part of a pointer value as raw bytes
30+
|
31+
= 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
32+
33+
error[E0080]: this constant likely exhibits undefined behavior
34+
--> $DIR/ub-ref.rs:27:1
35+
|
2836
LL | const USIZE_AS_REF: &'static u8 = unsafe { mem::transmute(1337usize) };
2937
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered integer pointer in non-ZST reference
3038
|
3139
= 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
3240

33-
error: aborting due to 4 previous errors
41+
error: aborting due to 5 previous errors
3442

3543
For more information about this error, try `rustc --explain E0080`.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
#![feature(const_transmute,const_let)]
12+
13+
use std::mem;
14+
15+
const BAD_UPVAR: &FnOnce() = &{ //~ ERROR this constant likely exhibits undefined behavior
16+
let bad_ref: &'static u16 = unsafe { mem::transmute(0usize) };
17+
let another_var = 13;
18+
move || { let _ = bad_ref; let _ = another_var; }
19+
};
20+
21+
fn main() {}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
error[E0080]: this constant likely exhibits undefined behavior
2+
--> $DIR/ub-upvars.rs:15:1
3+
|
4+
LL | / const BAD_UPVAR: &FnOnce() = &{ //~ ERROR this constant likely exhibits undefined behavior
5+
LL | | let bad_ref: &'static u16 = unsafe { mem::transmute(0usize) };
6+
LL | | let another_var = 13;
7+
LL | | move || { let _ = bad_ref; let _ = another_var; }
8+
LL | | };
9+
| |__^ type validation failed: encountered 0 at .<deref>.<closure-var(bad_ref)>, but expected something greater or equal to 1
10+
|
11+
= 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
12+
13+
error: aborting due to previous error
14+
15+
For more information about this error, try `rustc --explain E0080`.

‎src/tools/miri

Submodule miri updated from cc275c6 to 26f9d61

0 commit comments

Comments
 (0)
Please sign in to comment.