Auto merge of #51361 - oli-obk:sanity_check_consts, r=nikomatsakis

Do a basic sanity check for all constant values

## Motivation and high level overview

There has been some back and forth in this PR between @RalfJung and me in here about the motivation for this change and the stance it takes on unsafe coding guidelines.

The initial implementation ran its checks on every value read (so `*x`, `y = x`, ...). In unsafe code that isn't reasonable, because we might be invalidating invariants for a short time in order to build up a proper value.

The current implementation is a lint that runs its checks statics and constants. There is no need to check array lengths and enum variants, because it's a hard error to end up with anything but a number, and that one even has to have the required bits to be defined.

## What checks are done?

* Some type related checks
    * `char` needs to be a correct unicode character as defined by `char::from_u32`
    * A reference to a ZST must have the correct alignment (and be nonzero)
    * A reference to anything is dereferenced and its value is checked
* Layout checks use the information from `ty::Layout` to check
    * all fields of structs
    * all elements of arrays
    * enum discriminants
    * the fields of an enum variant (the variant is decided by the discriminant)
    * whether any union field succeeds in being checked (if none match the memory pattern, the check fails)
    * if the value is in the range described by the layout (e.g. for `NonZero*` types)

Changing the layout of a type will thus automatically cause the checks to check for the new layout.

fixes #51330
fixes #51471

cc @RalfJung

r? @eddyb
This commit is contained in:
bors 2018-07-29 19:37:28 +00:00
commit 70cac59031
36 changed files with 1018 additions and 182 deletions

View File

@ -355,7 +355,7 @@ dependencies = [
"cargo_metadata 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)",
"clippy-mini-macro-test 0.2.0",
"clippy_lints 0.0.212",
"compiletest_rs 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)",
"compiletest_rs 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
"derive-new 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
@ -461,7 +461,7 @@ dependencies = [
[[package]]
name = "compiletest_rs"
version = "0.3.11"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1319,7 +1319,7 @@ dependencies = [
"byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"cargo_metadata 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"colored 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"compiletest_rs 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)",
"compiletest_rs 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
@ -3118,7 +3118,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum colored 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b0aa3473e85a3161b59845d6096b289bb577874cafeaf75ea1b1beaa6572c7fc"
"checksum commoncrypto 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d056a8586ba25a1e4d61cb090900e495952c7886786fc55f909ab2f819b69007"
"checksum commoncrypto-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1fed34f46747aa73dfaa578069fd8279d2818ade2b55f38f22a9401c7f4083e2"
"checksum compiletest_rs 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "04cea0fe8b8aaca8143af607ad69076866c9f08b83c4b7faca0e993e5486831b"
"checksum compiletest_rs 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)" = "d3064bc712922596dd5ab449fca9261d411893356581fe5297b96aa8f53bb1b8"
"checksum core-foundation 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "286e0b41c3a20da26536c6000a280585d519fd07b3956b43aed8a79e9edce980"
"checksum core-foundation 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cc3532ec724375c7cb7ff0a097b714fde180bb1f6ed2ab27cfcd99ffca873cd2"
"checksum core-foundation-sys 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "716c271e8613ace48344f723b60b900a93150271e5be206212d052bbc0883efa"

View File

@ -73,6 +73,7 @@
#![feature(in_band_lifetimes)]
#![feature(macro_at_most_once_rep)]
#![feature(inclusive_range_methods)]
#![feature(crate_in_paths)]
#![recursion_limit="512"]

View File

@ -47,7 +47,7 @@ impl<'a, 'gcx, 'tcx> ConstEvalErr<'tcx> {
tcx: TyCtxtAt<'a, 'gcx, 'tcx>,
message: &str
) {
let err = self.struct_generic(tcx, message, None);
let err = self.struct_error(tcx, message);
if let Some(mut err) = err {
err.emit();
}

View File

@ -1599,7 +1599,7 @@ impl<'a, 'tcx, C> TyLayoutMethods<'tcx, C> for Ty<'tcx>
// Potentially-fat pointers.
ty::TyRef(_, pointee, _) |
ty::TyRawPtr(ty::TypeAndMut { ty: pointee, .. }) => {
assert!(i < 2);
assert!(i < this.fields.count());
// Reuse the fat *T type as its own thin pointer data field.
// This provides information about e.g. DST struct pointees
@ -1621,10 +1621,25 @@ impl<'a, 'tcx, C> TyLayoutMethods<'tcx, C> for Ty<'tcx>
match tcx.struct_tail(pointee).sty {
ty::TySlice(_) |
ty::TyStr => tcx.types.usize,
ty::TyDynamic(..) => {
// FIXME(eddyb) use an usize/fn() array with
// the correct number of vtables slots.
tcx.mk_imm_ref(tcx.types.re_static, tcx.mk_nil())
ty::TyDynamic(data, _) => {
let trait_def_id = data.principal().unwrap().def_id();
let num_fns: u64 = crate::traits::supertrait_def_ids(tcx, trait_def_id)
.map(|trait_def_id| {
tcx.associated_items(trait_def_id)
.filter(|item| item.kind == ty::AssociatedKind::Method)
.count() as u64
})
.sum();
tcx.mk_imm_ref(
tcx.types.re_static,
tcx.mk_array(tcx.types.usize, 3 + num_fns),
)
/* FIXME use actual fn pointers
tcx.mk_tup(&[
tcx.mk_array(tcx.types.usize, 3),
tcx.mk_array(Option<fn()>),
])
*/
}
_ => bug!("TyLayout::field_type({:?}): not applicable", this)
}

View File

@ -40,6 +40,7 @@ use lint::{LateContext, LintContext, LintArray};
use lint::{LintPass, LateLintPass, EarlyLintPass, EarlyContext};
use std::collections::HashSet;
use rustc::util::nodemap::FxHashSet;
use syntax::tokenstream::{TokenTree, TokenStream};
use syntax::ast;
@ -1576,6 +1577,57 @@ impl LintPass for UnusedBrokenConst {
}
}
fn validate_const<'a, 'tcx>(
tcx: ty::TyCtxt<'a, 'tcx, 'tcx>,
constant: &ty::Const<'tcx>,
param_env: ty::ParamEnv<'tcx>,
gid: ::rustc::mir::interpret::GlobalId<'tcx>,
what: &str,
) {
let mut ecx = ::rustc_mir::interpret::mk_eval_cx(tcx, gid.instance, param_env).unwrap();
let result = (|| {
let val = ecx.const_to_value(constant.val)?;
use rustc_target::abi::LayoutOf;
let layout = ecx.layout_of(constant.ty)?;
let place = ecx.allocate_place_for_value(val, layout, None)?;
let ptr = place.to_ptr()?;
let mut todo = vec![(ptr, layout.ty, String::new())];
let mut seen = FxHashSet();
seen.insert((ptr, layout.ty));
while let Some((ptr, ty, path)) = todo.pop() {
let layout = ecx.layout_of(ty)?;
ecx.validate_ptr_target(
ptr,
layout.align,
layout,
path,
&mut seen,
&mut todo,
)?;
}
Ok(())
})();
if let Err(err) = result {
let (trace, span) = ecx.generate_stacktrace(None);
let err = ::rustc::mir::interpret::ConstEvalErr {
error: err,
stacktrace: trace,
span,
};
let err = err.struct_error(
tcx.at(span),
&format!("this {} likely exhibits undefined behavior", what),
);
if let Some(mut err) = err {
err.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",
);
err.emit();
}
}
}
fn check_const(cx: &LateContext, body_id: hir::BodyId, what: &str) {
let def_id = cx.tcx.hir.body_owner_def_id(body_id);
let param_env = cx.tcx.param_env(def_id);
@ -1583,13 +1635,19 @@ fn check_const(cx: &LateContext, body_id: hir::BodyId, what: &str) {
instance: ty::Instance::mono(cx.tcx, def_id),
promoted: None
};
if let Err(err) = cx.tcx.const_eval(param_env.and(cid)) {
let span = cx.tcx.def_span(def_id);
err.report_as_lint(
cx.tcx.at(span),
&format!("this {} cannot be used", what),
cx.current_lint_root(),
);
match cx.tcx.const_eval(param_env.and(cid)) {
Ok(val) => validate_const(cx.tcx, val, param_env, cid, what),
Err(err) => {
// errors for statics are already reported directly in the query
if cx.tcx.is_static(def_id).is_none() {
let span = cx.tcx.def_span(def_id);
err.report_as_lint(
cx.tcx.at(span),
&format!("this {} cannot be used", what),
cx.current_lint_root(),
);
}
},
}
}
@ -1610,6 +1668,9 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnusedBrokenConst {
hir::ItemKind::Const(_, body_id) => {
check_const(cx, body_id, "constant");
},
hir::ItemKind::Static(_, _, body_id) => {
check_const(cx, body_id, "static");
},
hir::ItemKind::Ty(ref ty, _) => hir::intravisit::walk_ty(
&mut UnusedBrokenConstVisitor(cx),
ty

View File

@ -5,7 +5,7 @@ use rustc::hir;
use rustc::mir::interpret::{ConstEvalErr};
use rustc::mir;
use rustc::ty::{self, TyCtxt, Ty, Instance};
use rustc::ty::layout::{self, LayoutOf, Primitive};
use rustc::ty::layout::{self, LayoutOf, Primitive, TyLayout};
use rustc::ty::subst::Subst;
use syntax::ast::Mutability;
@ -16,7 +16,7 @@ use rustc::mir::interpret::{
EvalResult, EvalError, EvalErrorKind, GlobalId,
Value, Scalar, AllocId, Allocation, ConstValue,
};
use super::{Place, EvalContext, StackPopCleanup, ValTy, PlaceExtra, Memory, MemoryKind};
use super::{Place, EvalContext, StackPopCleanup, ValTy, Memory, MemoryKind};
pub fn mk_borrowck_eval_cx<'a, 'mir, 'tcx>(
tcx: TyCtxt<'a, 'tcx, 'tcx>,
@ -63,7 +63,7 @@ pub fn eval_promoted<'a, 'mir, 'tcx>(
cid: GlobalId<'tcx>,
mir: &'mir mir::Mir<'tcx>,
param_env: ty::ParamEnv<'tcx>,
) -> EvalResult<'tcx, (Value, Scalar, Ty<'tcx>)> {
) -> EvalResult<'tcx, (Value, Scalar, TyLayout<'tcx>)> {
ecx.with_fresh_body(|ecx| {
eval_body_using_ecx(ecx, cid, Some(mir), param_env)
})
@ -121,7 +121,7 @@ fn eval_body_and_ecx<'a, 'mir, 'tcx>(
cid: GlobalId<'tcx>,
mir: Option<&'mir mir::Mir<'tcx>>,
param_env: ty::ParamEnv<'tcx>,
) -> (EvalResult<'tcx, (Value, Scalar, Ty<'tcx>)>, EvalContext<'a, 'mir, 'tcx, CompileTimeEvaluator>) {
) -> (EvalResult<'tcx, (Value, Scalar, TyLayout<'tcx>)>, EvalContext<'a, 'mir, 'tcx, CompileTimeEvaluator>) {
debug!("eval_body_and_ecx: {:?}, {:?}", cid, param_env);
// we start out with the best span we have
// and try improving it down the road when more information is available
@ -137,7 +137,7 @@ fn eval_body_using_ecx<'a, 'mir, 'tcx>(
cid: GlobalId<'tcx>,
mir: Option<&'mir mir::Mir<'tcx>>,
param_env: ty::ParamEnv<'tcx>,
) -> EvalResult<'tcx, (Value, Scalar, Ty<'tcx>)> {
) -> EvalResult<'tcx, (Value, Scalar, TyLayout<'tcx>)> {
debug!("eval_body: {:?}, {:?}", cid, param_env);
let tcx = ecx.tcx.tcx;
let mut mir = match mir {
@ -182,7 +182,7 @@ fn eval_body_using_ecx<'a, 'mir, 'tcx>(
// point at the allocation
_ => Value::ByRef(ptr, layout.align),
};
Ok((value, ptr, layout.ty))
Ok((value, ptr, layout))
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
@ -434,19 +434,7 @@ pub fn const_val_field<'a, 'tcx>(
let ty = value.ty;
let value = ecx.const_to_value(value.val)?;
let layout = ecx.layout_of(ty)?;
let (ptr, align) = match value {
Value::ByRef(ptr, align) => (ptr, align),
Value::ScalarPair(..) | Value::Scalar(_) => {
let ptr = ecx.alloc_ptr(ty)?.into();
ecx.write_value_to_ptr(value, ptr, layout.align, ty)?;
(ptr, layout.align)
},
};
let place = Place::Ptr {
ptr,
align,
extra: variant.map_or(PlaceExtra::None, PlaceExtra::DowncastVariant),
};
let place = ecx.allocate_place_for_value(value, layout, variant)?;
let (place, layout) = ecx.place_field(place, field, layout)?;
let (ptr, align) = place.to_ptr_align();
let mut new_value = Value::ByRef(ptr, align);
@ -484,9 +472,9 @@ pub fn const_variant_index<'a, 'tcx>(
trace!("const_variant_index: {:?}, {:?}", instance, val);
let mut ecx = mk_eval_cx(tcx, instance, param_env).unwrap();
let value = ecx.const_to_value(val.val)?;
let layout = ecx.layout_of(val.ty)?;
let (ptr, align) = match value {
Value::ScalarPair(..) | Value::Scalar(_) => {
let layout = ecx.layout_of(val.ty)?;
let ptr = ecx.memory.allocate(layout.size, layout.align, MemoryKind::Stack)?.into();
ecx.write_value_to_ptr(value, ptr, layout.align, val.ty)?;
(ptr, layout.align)
@ -494,7 +482,7 @@ pub fn const_variant_index<'a, 'tcx>(
Value::ByRef(ptr, align) => (ptr, align),
};
let place = Place::from_scalar_ptr(ptr, align);
ecx.read_discriminant_as_variant_index(place, val.ty)
ecx.read_discriminant_as_variant_index(place, layout)
}
pub fn const_value_to_allocation_provider<'a, 'tcx>(
@ -560,11 +548,11 @@ pub fn const_eval_provider<'a, 'tcx>(
};
let (res, ecx) = eval_body_and_ecx(tcx, cid, None, key.param_env);
res.and_then(|(mut val, _, miri_ty)| {
res.and_then(|(mut val, _, layout)| {
if tcx.is_static(def_id).is_none() && cid.promoted.is_none() {
val = ecx.try_read_by_ref(val, miri_ty)?;
val = ecx.try_read_by_ref(val, layout.ty)?;
}
Ok(value_to_const_value(&ecx, val, miri_ty))
Ok(value_to_const_value(&ecx, val, layout.ty))
}).map_err(|err| {
let (trace, span) = ecx.generate_stacktrace(None);
let err = ConstEvalErr {

View File

@ -6,14 +6,14 @@ use rustc::hir::def_id::DefId;
use rustc::hir::def::Def;
use rustc::hir::map::definitions::DefPathData;
use rustc::mir;
use rustc::ty::layout::{self, Size, Align, HasDataLayout, IntegerExt, LayoutOf, TyLayout};
use rustc::ty::layout::{self, Size, Align, HasDataLayout, IntegerExt, LayoutOf, TyLayout, Primitive};
use rustc::ty::subst::{Subst, Substs};
use rustc::ty::{self, Ty, TyCtxt, TypeAndMut};
use rustc::ty::query::TyCtxtAt;
use rustc_data_structures::fx::{FxHashSet, FxHasher};
use rustc_data_structures::indexed_vec::{IndexVec, Idx};
use rustc::mir::interpret::{
FrameInfo, GlobalId, Value, Scalar,
GlobalId, Value, Scalar, FrameInfo, AllocType,
EvalResult, EvalErrorKind, Pointer, ConstValue,
};
@ -24,6 +24,31 @@ use super::{Place, PlaceExtra, Memory,
HasMemory, MemoryKind,
Machine};
macro_rules! validation_failure{
($what:expr, $where:expr, $details:expr) => {{
let where_ = if $where.is_empty() {
String::new()
} else {
format!(" at {}", $where)
};
err!(ValidationFailure(format!(
"encountered {}{}, but expected {}",
$what, where_, $details,
)))
}};
($what:expr, $where:expr) => {{
let where_ = if $where.is_empty() {
String::new()
} else {
format!(" at {}", $where)
};
err!(ValidationFailure(format!(
"encountered {}{}",
$what, where_,
)))
}};
}
pub struct EvalContext<'a, 'mir, 'tcx: 'a + 'mir, M: Machine<'mir, 'tcx>> {
/// Stores the `Machine` instance.
pub machine: M,
@ -324,8 +349,7 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M
r
}
pub fn alloc_ptr(&mut self, ty: Ty<'tcx>) -> EvalResult<'tcx, Pointer> {
let layout = self.layout_of(ty)?;
pub fn alloc_ptr(&mut self, layout: TyLayout<'tcx>) -> EvalResult<'tcx, Pointer> {
assert!(!layout.is_unsized(), "cannot alloc memory for unsized type");
self.memory.allocate(layout.size, layout.align, MemoryKind::Stack)
@ -776,8 +800,9 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M
Discriminant(ref place) => {
let ty = self.place_ty(place);
let layout = self.layout_of(ty)?;
let place = self.eval_place(place)?;
let discr_val = self.read_discriminant_value(place, ty)?;
let discr_val = self.read_discriminant_value(place, layout)?;
let defined = self.layout_of(dest_ty).unwrap().size.bits() as u8;
self.write_scalar(dest, Scalar::Bits {
bits: discr_val,
@ -843,16 +868,16 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M
/// reads a tag and produces the corresponding variant index
pub fn read_discriminant_as_variant_index(
&mut self,
&self,
place: Place,
ty: Ty<'tcx>,
layout: TyLayout<'tcx>,
) -> EvalResult<'tcx, usize> {
let layout = self.layout_of(ty)?;
match layout.variants {
ty::layout::Variants::Single { index } => Ok(index),
ty::layout::Variants::Tagged { .. } => {
let discr_val = self.read_discriminant_value(place, ty)?;
ty
let discr_val = self.read_discriminant_value(place, layout)?;
layout
.ty
.ty_adt_def()
.expect("tagged layout for non adt")
.discriminants(self.tcx.tcx)
@ -860,7 +885,7 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M
.ok_or_else(|| EvalErrorKind::InvalidDiscriminant.into())
}
ty::layout::Variants::NicheFilling { .. } => {
let discr_val = self.read_discriminant_value(place, ty)?;
let discr_val = self.read_discriminant_value(place, layout)?;
assert_eq!(discr_val as usize as u128, discr_val);
Ok(discr_val as usize)
},
@ -868,11 +893,10 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M
}
pub fn read_discriminant_value(
&mut self,
&self,
place: Place,
ty: Ty<'tcx>,
layout: TyLayout<'tcx>,
) -> EvalResult<'tcx, u128> {
let layout = self.layout_of(ty)?;
trace!("read_discriminant_value {:#?}", layout);
if layout.abi == layout::Abi::Uninhabited {
return Ok(0);
@ -880,7 +904,7 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M
match layout.variants {
layout::Variants::Single { index } => {
let discr_val = ty.ty_adt_def().map_or(
let discr_val = layout.ty.ty_adt_def().map_or(
index as u128,
|def| def.discriminant_for_variant(*self.tcx, index).val);
return Ok(discr_val);
@ -888,11 +912,11 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M
layout::Variants::Tagged { .. } |
layout::Variants::NicheFilling { .. } => {},
}
let (discr_place, discr) = self.place_field(place, mir::Field::new(0), layout)?;
trace!("discr place: {:?}, {:?}", discr_place, discr);
let discr_place_val = self.read_place(place)?;
let (discr_val, discr) = self.read_field(discr_place_val, None, mir::Field::new(0), layout)?;
trace!("discr value: {:?}, {:?}", discr_val, discr);
let raw_discr = self.value_to_scalar(ValTy {
value: self.read_place(discr_place)?,
value: discr_val,
ty: discr.ty
})?;
let discr_val = match layout.variants {
@ -906,7 +930,8 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M
let shift = 128 - discr.size.bits();
let sexted = (i << shift) >> shift;
// and then zeroing with the typeck discriminant type
let discr_ty = ty
let discr_ty = layout
.ty
.ty_adt_def().expect("tagged layout corresponds to adt")
.repr
.discr_type();
@ -1023,6 +1048,27 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M
self.tcx.const_eval(param_env.and(gid)).map_err(|err| EvalErrorKind::ReferencedConstant(err).into())
}
pub fn allocate_place_for_value(
&mut self,
value: Value,
layout: TyLayout<'tcx>,
variant: Option<usize>,
) -> EvalResult<'tcx, Place> {
let (ptr, align) = match value {
Value::ByRef(ptr, align) => (ptr, align),
Value::ScalarPair(..) | Value::Scalar(_) => {
let ptr = self.alloc_ptr(layout)?.into();
self.write_value_to_ptr(value, ptr, layout.align, layout.ty)?;
(ptr, layout.align)
},
};
Ok(Place::Ptr {
ptr,
align,
extra: variant.map_or(PlaceExtra::None, PlaceExtra::DowncastVariant),
})
}
pub fn force_allocation(&mut self, place: Place) -> EvalResult<'tcx, Place> {
let new_place = match place {
Place::Local { frame, local } => {
@ -1039,7 +1085,7 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M
let ty = self.stack[frame].mir.local_decls[local].ty;
let ty = self.monomorphize(ty, self.stack[frame].instance.substs);
let layout = self.layout_of(ty)?;
let ptr = self.alloc_ptr(ty)?;
let ptr = self.alloc_ptr(layout)?;
self.stack[frame].locals[local] =
Some(Value::ByRef(ptr.into(), layout.align)); // it stays live
let place = Place::from_ptr(ptr, layout.align);
@ -1074,11 +1120,7 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M
match self.follow_by_ref_value(value, ty)? {
Value::ByRef { .. } => bug!("follow_by_ref_value can't result in `ByRef`"),
Value::Scalar(scalar) => {
// TODO: Do we really want insta-UB here?
self.ensure_valid_value(scalar, ty)?;
Ok(scalar)
}
Value::Scalar(scalar) => Ok(scalar),
Value::ScalarPair(..) => bug!("value_to_scalar can't work with fat pointers"),
}
@ -1165,8 +1207,8 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M
if let Ok(Some(src_val)) = self.try_read_value(src_ptr, align, dest_ty) {
write_dest(self, src_val)?;
} else {
let dest_ptr = self.alloc_ptr(dest_ty)?.into();
let layout = self.layout_of(dest_ty)?;
let dest_ptr = self.alloc_ptr(layout)?.into();
self.memory.copy(src_ptr, align.min(layout.align), dest_ptr, layout.align, layout.size, false)?;
write_dest(self, Value::ByRef(dest_ptr, layout.align))?;
}
@ -1221,18 +1263,6 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M
}
}
fn ensure_valid_value(&self, val: Scalar, ty: Ty<'tcx>) -> EvalResult<'tcx> {
match ty.sty {
ty::TyBool => val.to_bool().map(|_| ()),
ty::TyChar if ::std::char::from_u32(val.to_bits(Size::from_bytes(4))? as u32).is_none() => {
err!(InvalidChar(val.to_bits(Size::from_bytes(4))? as u32 as u128))
}
_ => Ok(()),
}
}
pub fn read_value(&self, ptr: Scalar, align: Align, ty: Ty<'tcx>) -> EvalResult<'tcx, Value> {
if let Some(val) = self.try_read_value(ptr, align, ty)? {
Ok(val)
@ -1270,47 +1300,223 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M
}
}
fn validate_scalar(
&self,
value: Scalar,
size: Size,
scalar: &layout::Scalar,
path: &str,
ty: Ty,
) -> EvalResult<'tcx> {
trace!("validate scalar: {:#?}, {:#?}, {:#?}, {}", value, size, scalar, ty);
let (lo, hi) = scalar.valid_range.clone().into_inner();
let (bits, defined) = match value {
Scalar::Bits { bits, defined } => (bits, defined),
Scalar::Ptr(_) => {
let ptr_size = self.memory.pointer_size();
let ptr_max = u128::max_value() >> (128 - ptr_size.bits());
return if lo > hi {
if lo - hi == 1 {
// no gap, all values are ok
Ok(())
} else if hi < ptr_max || lo > 1 {
let max = u128::max_value() >> (128 - size.bits());
validation_failure!(
"pointer",
path,
format!("something in the range {:?} or {:?}", 0..=lo, hi..=max)
)
} else {
Ok(())
}
} else if hi < ptr_max || lo > 1 {
validation_failure!(
"pointer",
path,
format!("something in the range {:?}", scalar.valid_range)
)
} else {
Ok(())
};
},
};
// char gets a special treatment, because its number space is not contiguous so `TyLayout`
// has no special checks for chars
match ty.sty {
ty::TyChar => {
assert_eq!(size.bytes(), 4);
if ::std::char::from_u32(bits as u32).is_none() {
return err!(InvalidChar(bits));
}
}
_ => {},
}
use std::ops::RangeInclusive;
let in_range = |bound: RangeInclusive<u128>| {
defined as u64 >= size.bits() && bound.contains(&bits)
};
if lo > hi {
if in_range(0..=hi) || in_range(lo..=u128::max_value()) {
Ok(())
} else if defined as u64 >= size.bits() {
validation_failure!(
bits,
path,
format!("something in the range {:?} or {:?}", ..=hi, lo..)
)
} else {
validation_failure!("undefined bytes", path)
}
} else {
if in_range(scalar.valid_range.clone()) {
Ok(())
} else if defined as u64 >= size.bits() {
validation_failure!(
bits,
path,
format!("something in the range {:?}", scalar.valid_range)
)
} else {
validation_failure!("undefined bytes", path)
}
}
}
/// This function checks the memory where `ptr` points to.
/// It will error if the bits at the destination do not match the ones described by the layout.
pub fn validate_ptr_target(
&self,
ptr: Pointer,
ptr_align: Align,
ty: Ty<'tcx>
mut layout: TyLayout<'tcx>,
path: String,
seen: &mut FxHashSet<(Pointer, Ty<'tcx>)>,
todo: &mut Vec<(Pointer, Ty<'tcx>, String)>,
) -> EvalResult<'tcx> {
match ty.sty {
ty::TyBool => {
self.memory.read_scalar(ptr, ptr_align, Size::from_bytes(1))?.to_bool()?;
}
ty::TyChar => {
let c = self.memory.read_scalar(ptr, ptr_align, Size::from_bytes(4))?.to_bits(Size::from_bytes(4))? as u32;
match ::std::char::from_u32(c) {
Some(..) => (),
None => return err!(InvalidChar(c as u128)),
}
}
self.memory.dump_alloc(ptr.alloc_id);
trace!("validate_ptr_target: {:?}, {:#?}", ptr, layout);
ty::TyFnPtr(_) => {
self.memory.read_ptr_sized(ptr, ptr_align)?;
let variant;
match layout.variants {
layout::Variants::NicheFilling { niche: ref tag, .. } |
layout::Variants::Tagged { ref tag, .. } => {
let size = tag.value.size(self);
let (tag_value, tag_layout) = self.read_field(
Value::ByRef(ptr.into(), ptr_align),
None,
mir::Field::new(0),
layout,
)?;
let tag_value = self.value_to_scalar(ValTy {
value: tag_value,
ty: tag_layout.ty,
})?;
let path = format!("{}.TAG", path);
self.validate_scalar(tag_value, size, tag, &path, tag_layout.ty)?;
let variant_index = self.read_discriminant_as_variant_index(
Place::from_ptr(ptr, ptr_align),
layout,
)?;
variant = variant_index;
layout = layout.for_variant(self, variant_index);
trace!("variant layout: {:#?}", layout);
},
ty::TyRef(_, rty, _) |
ty::TyRawPtr(ty::TypeAndMut { ty: rty, .. }) => {
self.read_ptr(ptr, ptr_align, rty)?;
}
ty::TyAdt(def, _) => {
if def.is_box() {
self.read_ptr(ptr, ptr_align, ty.boxed_ty())?;
return Ok(());
}
if let layout::Abi::Scalar(ref scalar) = self.layout_of(ty)?.abi {
let size = scalar.value.size(self);
self.memory.read_scalar(ptr, ptr_align, size)?;
}
}
_ => (),
layout::Variants::Single { index } => variant = index,
}
match layout.fields {
// primitives are unions with zero fields
layout::FieldPlacement::Union(0) => {
match layout.abi {
// nothing to do, whatever the pointer points to, it is never going to be read
layout::Abi::Uninhabited => validation_failure!("a value of an uninhabited type", path),
// check that the scalar is a valid pointer or that its bit range matches the
// expectation.
layout::Abi::Scalar(ref scalar) => {
let size = scalar.value.size(self);
let value = self.memory.read_scalar(ptr, ptr_align, size)?;
self.validate_scalar(value, size, scalar, &path, layout.ty)?;
if scalar.value == Primitive::Pointer {
// ignore integer pointers, we can't reason about the final hardware
if let Scalar::Ptr(ptr) = value {
let alloc_kind = self.tcx.alloc_map.lock().get(ptr.alloc_id);
if let Some(AllocType::Static(did)) = alloc_kind {
// statics from other crates are already checked
// extern statics should not be validated as they have no body
if !did.is_local() || self.tcx.is_foreign_item(did) {
return Ok(());
}
}
if let Some(tam) = layout.ty.builtin_deref(false) {
// we have not encountered this pointer+layout combination before
if seen.insert((ptr, tam.ty)) {
todo.push((ptr, tam.ty, format!("(*{})", path)))
}
}
}
}
Ok(())
},
_ => bug!("bad abi for FieldPlacement::Union(0): {:#?}", layout.abi),
}
}
layout::FieldPlacement::Union(_) => {
// We can't check unions, their bits are allowed to be anything.
// The fields don't need to correspond to any bit pattern of the union's fields.
// See https://github.com/rust-lang/rust/issues/32836#issuecomment-406875389
Ok(())
},
layout::FieldPlacement::Array { stride, count } => {
let elem_layout = layout.field(self, 0)?;
for i in 0..count {
let mut path = path.clone();
self.write_field_name(&mut path, layout.ty, i as usize, variant).unwrap();
self.validate_ptr_target(ptr.offset(stride * i, self)?, ptr_align, elem_layout, path, seen, todo)?;
}
Ok(())
},
layout::FieldPlacement::Arbitrary { ref offsets, .. } => {
// check length field and vtable field
match layout.ty.builtin_deref(false).map(|tam| &tam.ty.sty) {
| Some(ty::TyStr)
| Some(ty::TySlice(_)) => {
let (len, len_layout) = self.read_field(
Value::ByRef(ptr.into(), ptr_align),
None,
mir::Field::new(1),
layout,
)?;
let len = self.value_to_scalar(ValTy { value: len, ty: len_layout.ty })?;
if len.to_bits(len_layout.size).is_err() {
return validation_failure!("length is not a valid integer", path);
}
},
Some(ty::TyDynamic(..)) => {
let (vtable, vtable_layout) = self.read_field(
Value::ByRef(ptr.into(), ptr_align),
None,
mir::Field::new(1),
layout,
)?;
let vtable = self.value_to_scalar(ValTy { value: vtable, ty: vtable_layout.ty })?;
if vtable.to_ptr().is_err() {
return validation_failure!("vtable address is not a pointer", path);
}
}
_ => {},
}
for (i, &offset) in offsets.iter().enumerate() {
let field_layout = layout.field(self, i)?;
let mut path = path.clone();
self.write_field_name(&mut path, layout.ty, i, variant).unwrap();
self.validate_ptr_target(ptr.offset(offset, self)?, ptr_align, field_layout, path, seen, todo)?;
}
Ok(())
}
}
Ok(())
}
pub fn try_read_by_ref(&self, mut val: Value, ty: Ty<'tcx>) -> EvalResult<'tcx, Value> {
@ -1333,9 +1539,6 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M
let ptr = ptr.to_ptr()?;
// Not the right place to do this
//self.validate_ptr_target(ptr, ptr_align, ty)?;
match layout.abi {
layout::Abi::Scalar(..) => {
let scalar = self.memory.read_scalar(ptr, ptr_align, layout.size)?;
@ -1623,6 +1826,73 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M
pub fn truncate(&self, value: u128, ty: Ty<'tcx>) -> EvalResult<'tcx, u128> {
super::truncate(self.tcx.tcx, value, ty)
}
fn write_field_name(&self, s: &mut String, ty: Ty<'tcx>, i: usize, variant: usize) -> ::std::fmt::Result {
match ty.sty {
ty::TyBool |
ty::TyChar |
ty::TyInt(_) |
ty::TyUint(_) |
ty::TyFloat(_) |
ty::TyFnPtr(_) |
ty::TyNever |
ty::TyFnDef(..) |
ty::TyGeneratorWitness(..) |
ty::TyForeign(..) |
ty::TyDynamic(..) => {
bug!("field_name({:?}): not applicable", ty)
}
// Potentially-fat pointers.
ty::TyRef(_, pointee, _) |
ty::TyRawPtr(ty::TypeAndMut { ty: pointee, .. }) => {
assert!(i < 2);
// Reuse the fat *T type as its own thin pointer data field.
// This provides information about e.g. DST struct pointees
// (which may have no non-DST form), and will work as long
// as the `Abi` or `FieldPlacement` is checked by users.
if i == 0 {
return write!(s, ".data_ptr");
}
match self.tcx.struct_tail(pointee).sty {
ty::TySlice(_) |
ty::TyStr => write!(s, ".len"),
ty::TyDynamic(..) => write!(s, ".vtable_ptr"),
_ => bug!("field_name({:?}): not applicable", ty)
}
}
// Arrays and slices.
ty::TyArray(_, _) |
ty::TySlice(_) |
ty::TyStr => write!(s, "[{}]", i),
// generators and closures.
ty::TyClosure(def_id, _) | ty::TyGenerator(def_id, _, _) => {
let node_id = self.tcx.hir.as_local_node_id(def_id).unwrap();
let freevar = self.tcx.with_freevars(node_id, |fv| fv[i]);
write!(s, ".upvar({})", self.tcx.hir.name(freevar.var_id()))
}
ty::TyTuple(_) => write!(s, ".{}", i),
// enums
ty::TyAdt(def, ..) if def.is_enum() => {
let variant = &def.variants[variant];
write!(s, ".{}::{}", variant.name, variant.fields[i].ident)
}
// other ADTs.
ty::TyAdt(def, _) => write!(s, ".{}", def.non_enum_variant().fields[i].ident),
ty::TyProjection(_) | ty::TyAnon(..) | ty::TyParam(_) |
ty::TyInfer(_) | ty::TyError => {
bug!("write_field_name: unexpected type `{}`", ty)
}
}
}
}
impl<'mir, 'tcx> Frame<'mir, 'tcx> {

View File

@ -763,6 +763,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
// Undef check happens *after* we established that the alignment is correct.
// We must not return Ok() for unaligned pointers!
if self.check_defined(ptr, size).is_err() {
// this inflates undefined bytes to the entire scalar, even if only a few bytes are undefined
return Ok(Scalar::undef().into());
}
// Now we do the actual reading

View File

@ -21,6 +21,7 @@ pub use self::memory::{Memory, MemoryKind, HasMemory};
pub use self::const_eval::{
eval_promoted,
mk_borrowck_eval_cx,
mk_eval_cx,
CompileTimeEvaluator,
const_value_to_allocation_provider,
const_eval_provider,

View File

@ -98,7 +98,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
/// Reads a value from the place without going through the intermediate step of obtaining
/// a `miri::Place`
pub fn try_read_place(
&mut self,
&self,
place: &mir::Place<'tcx>,
) -> EvalResult<'tcx, Option<Value>> {
use rustc::mir::Place::*;
@ -120,19 +120,18 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
base: Value,
variant: Option<usize>,
field: mir::Field,
base_ty: Ty<'tcx>,
) -> EvalResult<'tcx, ValTy<'tcx>> {
let mut base_layout = self.layout_of(base_ty)?;
mut base_layout: TyLayout<'tcx>,
) -> EvalResult<'tcx, (Value, TyLayout<'tcx>)> {
if let Some(variant_index) = variant {
base_layout = base_layout.for_variant(self, variant_index);
}
let field_index = field.index();
let field = base_layout.field(self, field_index)?;
if field.size.bytes() == 0 {
return Ok(ValTy {
value: Value::Scalar(Scalar::undef()),
ty: field.ty,
});
return Ok((
Value::Scalar(Scalar::undef()),
field,
));
}
let offset = base_layout.fields.offset(field_index);
let value = match base {
@ -151,16 +150,13 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
assert!(!field.is_unsized());
Value::ByRef(ptr, align)
},
Value::Scalar(val) => bug!("field access on non aggregate {:?}, {:?}", val, base_ty),
Value::Scalar(val) => bug!("field access on non aggregate {:#?}, {:#?}", val, base_layout),
};
Ok(ValTy {
value,
ty: field.ty,
})
Ok((value, field))
}
fn try_read_place_projection(
&mut self,
&self,
proj: &mir::PlaceProjection<'tcx>,
) -> EvalResult<'tcx, Option<Value>> {
use rustc::mir::ProjectionElem::*;
@ -169,8 +165,9 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
None => return Ok(None),
};
let base_ty = self.place_ty(&proj.base);
let base_layout = self.layout_of(base_ty)?;
match proj.elem {
Field(field, _) => Ok(Some(self.read_field(base, None, field, base_ty)?.value)),
Field(field, _) => Ok(Some(self.read_field(base, None, field, base_layout)?.0)),
// The NullablePointer cases should work fine, need to take care for normal enums
Downcast(..) |
Subslice { .. } |

View File

@ -351,8 +351,12 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
if self.frame().mir.args_iter().count() == layout.fields.count() + 1 {
for (i, arg_local) in arg_locals.enumerate() {
let field = mir::Field::new(i);
let valty = self.read_field(args[1].value, None, field, args[1].ty)?;
let (value, layout) = self.read_field(args[1].value, None, field, layout)?;
let dest = self.eval_place(&mir::Place::Local(arg_local))?;
let valty = ValTy {
value,
ty: layout.ty,
};
self.write_value(valty, dest)?;
}
} else {

View File

@ -65,7 +65,7 @@ impl MirPass for ConstProp {
}
}
type Const<'tcx> = (Value, ty::Ty<'tcx>, Span);
type Const<'tcx> = (Value, TyLayout<'tcx>, Span);
/// Finds optimization opportunities on the MIR.
struct ConstPropagator<'b, 'a, 'tcx:'a+'b> {
@ -258,7 +258,10 @@ impl<'b, 'a, 'tcx:'b> ConstPropagator<'b, 'a, 'tcx> {
) -> Option<Const<'tcx>> {
self.ecx.tcx.span = source_info.span;
match self.ecx.const_to_value(c.literal.val) {
Ok(val) => Some((val, c.literal.ty, c.span)),
Ok(val) => {
let layout = self.tcx.layout_of(self.param_env.and(c.literal.ty)).ok()?;
Some((val, layout, c.span))
},
Err(error) => {
let (stacktrace, span) = self.ecx.generate_stacktrace(None);
let err = ConstEvalErr {
@ -281,11 +284,11 @@ impl<'b, 'a, 'tcx:'b> ConstPropagator<'b, 'a, 'tcx> {
Place::Projection(ref proj) => match proj.elem {
ProjectionElem::Field(field, _) => {
trace!("field proj on {:?}", proj.base);
let (base, ty, span) = self.eval_place(&proj.base, source_info)?;
let (base, layout, span) = self.eval_place(&proj.base, source_info)?;
let valty = self.use_ecx(source_info, |this| {
this.ecx.read_field(base, None, field, ty)
this.ecx.read_field(base, None, field, layout)
})?;
Some((valty.value, valty.ty, span))
Some((valty.0, valty.1, span))
},
_ => None,
},
@ -325,14 +328,14 @@ impl<'b, 'a, 'tcx:'b> ConstPropagator<'b, 'a, 'tcx> {
fn const_prop(
&mut self,
rvalue: &Rvalue<'tcx>,
place_ty: ty::Ty<'tcx>,
place_layout: TyLayout<'tcx>,
source_info: SourceInfo,
) -> Option<Const<'tcx>> {
let span = source_info.span;
match *rvalue {
// This branch exists for the sanity type check
Rvalue::Use(Operand::Constant(ref c)) => {
assert_eq!(c.ty, place_ty);
assert_eq!(c.ty, place_layout.ty);
self.eval_constant(c, source_info)
},
Rvalue::Use(ref op) => {
@ -345,15 +348,15 @@ impl<'b, 'a, 'tcx:'b> ConstPropagator<'b, 'a, 'tcx> {
Rvalue::Discriminant(..) => None,
Rvalue::Cast(kind, ref operand, _) => {
let (value, ty, span) = self.eval_operand(operand, source_info)?;
let (value, layout, span) = self.eval_operand(operand, source_info)?;
self.use_ecx(source_info, |this| {
let dest_ptr = this.ecx.alloc_ptr(place_ty)?;
let place_align = this.ecx.layout_of(place_ty)?.align;
let dest_ptr = this.ecx.alloc_ptr(place_layout)?;
let place_align = place_layout.align;
let dest = ::interpret::Place::from_ptr(dest_ptr, place_align);
this.ecx.cast(ValTy { value, ty }, kind, place_ty, dest)?;
this.ecx.cast(ValTy { value, ty: layout.ty }, kind, place_layout.ty, dest)?;
Ok((
Value::ByRef(dest_ptr.into(), place_align),
place_ty,
place_layout,
span,
))
})
@ -362,15 +365,14 @@ impl<'b, 'a, 'tcx:'b> ConstPropagator<'b, 'a, 'tcx> {
// FIXME(oli-obk): evaluate static/constant slice lengths
Rvalue::Len(_) => None,
Rvalue::NullaryOp(NullOp::SizeOf, ty) => {
let param_env = self.tcx.param_env(self.source.def_id);
type_size_of(self.tcx, param_env, ty).map(|n| (
type_size_of(self.tcx, self.param_env, ty).and_then(|n| Some((
Value::Scalar(Scalar::Bits {
bits: n as u128,
defined: self.tcx.data_layout.pointer_size.bits() as u8,
}),
self.tcx.types.usize,
self.tcx.layout_of(self.param_env.and(self.tcx.types.usize)).ok()?,
span,
))
)))
}
Rvalue::UnaryOp(op, ref arg) => {
let def_id = if self.tcx.is_closure(self.source.def_id) {
@ -386,10 +388,10 @@ impl<'b, 'a, 'tcx:'b> ConstPropagator<'b, 'a, 'tcx> {
let val = self.eval_operand(arg, source_info)?;
let prim = self.use_ecx(source_info, |this| {
this.ecx.value_to_scalar(ValTy { value: val.0, ty: val.1 })
this.ecx.value_to_scalar(ValTy { value: val.0, ty: val.1.ty })
})?;
let val = self.use_ecx(source_info, |this| this.ecx.unary_op(op, prim, val.1))?;
Some((Value::Scalar(val), place_ty, span))
let val = self.use_ecx(source_info, |this| this.ecx.unary_op(op, prim, val.1.ty))?;
Some((Value::Scalar(val), place_layout, span))
}
Rvalue::CheckedBinaryOp(op, ref left, ref right) |
Rvalue::BinaryOp(op, ref left, ref right) => {
@ -407,7 +409,7 @@ impl<'b, 'a, 'tcx:'b> ConstPropagator<'b, 'a, 'tcx> {
}
let r = self.use_ecx(source_info, |this| {
this.ecx.value_to_scalar(ValTy { value: right.0, ty: right.1 })
this.ecx.value_to_scalar(ValTy { value: right.0, ty: right.1.ty })
})?;
if op == BinOp::Shr || op == BinOp::Shl {
let left_ty = left.ty(self.mir, self.tcx);
@ -417,7 +419,7 @@ impl<'b, 'a, 'tcx:'b> ConstPropagator<'b, 'a, 'tcx> {
.unwrap()
.size
.bits();
let right_size = self.tcx.layout_of(self.param_env.and(right.1)).unwrap().size;
let right_size = right.1.size;
if r.to_bits(right_size).ok().map_or(false, |b| b >= left_bits as u128) {
let source_scope_local_data = match self.mir.source_scope_local_data {
ClearCrossCrate::Set(ref data) => data,
@ -439,11 +441,11 @@ impl<'b, 'a, 'tcx:'b> ConstPropagator<'b, 'a, 'tcx> {
}
let left = self.eval_operand(left, source_info)?;
let l = self.use_ecx(source_info, |this| {
this.ecx.value_to_scalar(ValTy { value: left.0, ty: left.1 })
this.ecx.value_to_scalar(ValTy { value: left.0, ty: left.1.ty })
})?;
trace!("const evaluating {:?} for {:?} and {:?}", op, left, right);
let (val, overflow) = self.use_ecx(source_info, |this| {
this.ecx.binary_op(op, l, left.1, r, right.1)
this.ecx.binary_op(op, l, left.1.ty, r, right.1.ty)
})?;
let val = if let Rvalue::CheckedBinaryOp(..) = *rvalue {
Value::ScalarPair(
@ -458,7 +460,7 @@ impl<'b, 'a, 'tcx:'b> ConstPropagator<'b, 'a, 'tcx> {
}
Value::Scalar(val)
};
Some((val, place_ty, span))
Some((val, place_layout, span))
},
}
}
@ -544,16 +546,18 @@ impl<'b, 'a, 'tcx> Visitor<'tcx> for ConstPropagator<'b, 'a, 'tcx> {
) {
trace!("visit_statement: {:?}", statement);
if let StatementKind::Assign(ref place, ref rval) = statement.kind {
let place_ty = place
let place_ty: ty::Ty<'tcx> = place
.ty(&self.mir.local_decls, self.tcx)
.to_ty(self.tcx);
if let Some(value) = self.const_prop(rval, place_ty, statement.source_info) {
if let Place::Local(local) = *place {
trace!("checking whether {:?} can be stored to {:?}", value, local);
if self.can_const_prop[local] {
trace!("storing {:?} to {:?}", value, local);
assert!(self.places[local].is_none());
self.places[local] = Some(value);
if let Ok(place_layout) = self.tcx.layout_of(self.param_env.and(place_ty)) {
if let Some(value) = self.const_prop(rval, place_layout, statement.source_info) {
if let Place::Local(local) = *place {
trace!("checking whether {:?} can be stored to {:?}", value, local);
if self.can_const_prop[local] {
trace!("storing {:?} to {:?}", value, local);
assert!(self.places[local].is_none());
self.places[local] = Some(value);
}
}
}
}

View File

@ -634,6 +634,8 @@ impl Scalar {
#[derive(PartialEq, Eq, Hash, Debug)]
pub enum FieldPlacement {
/// All fields start at no offset. The `usize` is the field count.
///
/// In the case of primitives the number of fields is `0`.
Union(usize),
/// Array/vector-like placement, with all fields of identical types.

View File

@ -120,13 +120,13 @@ pub fn unsafe_slice(_: &[UnsafeInner]) {
pub fn str(_: &[u8]) {
}
// CHECK: @trait_borrow({}* nonnull %arg0.0, {}* noalias nonnull readonly %arg0.1)
// CHECK: @trait_borrow({}* nonnull %arg0.0, [4 x [[USIZE]]]* noalias readonly dereferenceable({{.*}}) %arg0.1)
// FIXME #25759 This should also have `nocapture`
#[no_mangle]
pub fn trait_borrow(_: &Drop) {
}
// CHECK: @trait_box({}* noalias nonnull, {}* noalias nonnull readonly)
// CHECK: @trait_box({}* noalias nonnull, [4 x [[USIZE]]]* noalias readonly dereferenceable({{.*}}))
#[no_mangle]
pub fn trait_box(_: Box<Drop>) {
}

View File

@ -11,12 +11,11 @@
//~^^^^^^^^^^ ERROR cycle detected when computing layout of
//~| NOTE ...which requires computing layout of
//~| NOTE ...which again requires computing layout of
//~| NOTE cycle used when compile_codegen_unit
trait Mirror { type It: ?Sized; }
impl<T: ?Sized> Mirror for T { type It = Self; }
struct S(Option<<S as Mirror>::It>);
fn main() {
fn main() { //~ NOTE cycle used when processing `main`
let _s = S(None);
}

View File

@ -0,0 +1,89 @@
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#[repr(C)]
#[derive(Copy, Clone)]
struct SliceRepr {
ptr: *const u8,
len: usize,
}
#[repr(C)]
#[derive(Copy, Clone)]
struct BadSliceRepr {
ptr: *const u8,
len: &'static u8,
}
union SliceTransmute {
repr: SliceRepr,
bad: BadSliceRepr,
slice: &'static [u8],
str: &'static str,
}
#[repr(C)]
#[derive(Copy, Clone)]
struct DynRepr {
ptr: *const u8,
vtable: *const u8,
}
#[repr(C)]
#[derive(Copy, Clone)]
struct DynRepr2 {
ptr: *const u8,
vtable: *const u64,
}
#[repr(C)]
#[derive(Copy, Clone)]
struct BadDynRepr {
ptr: *const u8,
vtable: usize,
}
union DynTransmute {
repr: DynRepr,
repr2: DynRepr2,
bad: BadDynRepr,
rust: &'static Trait,
}
trait Trait {}
// OK
const A: &str = unsafe { SliceTransmute { repr: SliceRepr { ptr: &42, len: 1 } }.str};
// should lint
const B: &str = unsafe { SliceTransmute { repr: SliceRepr { ptr: &42, len: 999 } }.str};
// bad
const C: &str = unsafe { SliceTransmute { bad: BadSliceRepr { ptr: &42, len: &3 } }.str};
//~^ ERROR this constant likely exhibits undefined behavior
// OK
const A2: &[u8] = unsafe { SliceTransmute { repr: SliceRepr { ptr: &42, len: 1 } }.slice};
// should lint
const B2: &[u8] = unsafe { SliceTransmute { repr: SliceRepr { ptr: &42, len: 999 } }.slice};
// bad
const C2: &[u8] = unsafe { SliceTransmute { bad: BadSliceRepr { ptr: &42, len: &3 } }.slice};
//~^ ERROR this constant likely exhibits undefined behavior
// bad
const D: &Trait = unsafe { DynTransmute { repr: DynRepr { ptr: &92, vtable: &3 } }.rust};
//~^ ERROR this constant likely exhibits undefined behavior
// bad
const E: &Trait = unsafe { DynTransmute { repr2: DynRepr2 { ptr: &92, vtable: &3 } }.rust};
//~^ ERROR this constant likely exhibits undefined behavior
// bad
const F: &Trait = unsafe { DynTransmute { bad: BadDynRepr { ptr: &92, vtable: 3 } }.rust};
//~^ ERROR this constant likely exhibits undefined behavior
fn main() {
}

View File

@ -0,0 +1,32 @@
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// compile-pass
enum Foo {
A = 5,
B = 42,
}
enum Bar {
C = 42,
D = 99,
}
union Union {
foo: &'static Foo,
bar: &'static Bar,
usize: &'static usize,
}
static BAR: usize = 42;
static FOO: (&Foo, &Bar) = unsafe {(
Union { usize: &BAR }.foo,
Union { usize: &BAR }.bar,
)};
fn main() {}

View File

@ -0,0 +1,30 @@
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
enum Foo {
A = 5,
B = 42,
}
enum Bar {
C = 42,
D = 99,
}
union Union {
foo: &'static Foo,
bar: &'static Bar,
usize: &'static usize,
}
static BAR: usize = 5;
static FOO: (&Foo, &Bar) = unsafe {( //~ undefined behavior
Union { usize: &BAR }.foo,
Union { usize: &BAR }.bar,
)};
fn main() {}

View File

@ -0,0 +1,14 @@
error[E0080]: this static likely exhibits undefined behavior
--> $DIR/double_check2.rs:25:1
|
LL | / static FOO: (&Foo, &Bar) = unsafe {( //~ undefined behavior
LL | | Union { usize: &BAR }.foo,
LL | | Union { usize: &BAR }.bar,
LL | | )};
| |___^ type validation failed: encountered 5 at (*.1).TAG, but expected something in the range 42..=99
|
= 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
error: aborting due to previous error
For more information about this error, try `rustc --explain E0080`.

View File

@ -11,7 +11,4 @@
static FOO: i32 = [][0];
//~^ ERROR E0080
fn main() {
let array = [std::env::args().len()];
array[1]; //~ ERROR index out of bounds
}
fn main() {}

View File

@ -4,14 +4,6 @@ error[E0080]: could not evaluate static initializer
LL | static FOO: i32 = [][0];
| ^^^^^ index out of bounds: the len is 0 but the index is 0
error: index out of bounds: the len is 1 but the index is 1
--> $DIR/index_out_of_bounds.rs:16:5
|
LL | array[1]; //~ ERROR index out of bounds
| ^^^^^^^^
|
= note: #[deny(const_err)] on by default
error: aborting due to 2 previous errors
error: aborting due to previous error
For more information about this error, try `rustc --explain E0080`.

View File

@ -0,0 +1,14 @@
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
fn main() {
let array = [std::env::args().len()];
array[1]; //~ ERROR index out of bounds
}

View File

@ -0,0 +1,10 @@
error: index out of bounds: the len is 1 but the index is 1
--> $DIR/index_out_of_bounds_propagated.rs:13:5
|
LL | array[1]; //~ ERROR index out of bounds
| ^^^^^^^^
|
= note: #[deny(const_err)] on by default
error: aborting due to previous error

View File

@ -0,0 +1,27 @@
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#[repr(usize)]
#[derive(Copy, Clone)]
enum Enum {
A = 0,
}
union Foo {
a: &'static u8,
b: Enum,
}
// A pointer is guaranteed non-null
const BAD_ENUM: Enum = unsafe { Foo { a: &1 }.b};
//~^ ERROR this constant likely exhibits undefined behavior
fn main() {
}

View File

@ -0,0 +1,11 @@
error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/ub-enum-ptr.rs:23:1
|
LL | const BAD_ENUM: Enum = unsafe { Foo { a: &1 }.b};
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered pointer at .TAG, but expected something in the range 0..=0
|
= 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
error: aborting due to previous error
For more information about this error, try `rustc --explain E0080`.

View File

@ -0,0 +1,22 @@
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// compile-pass
union Foo {
a: &'static u8,
b: usize,
}
// a usize's value may be a pointer, that's fine
const PTR_AS_USIZE: usize = unsafe { Foo { a: &1 }.b};
fn main() {
}

View File

@ -0,0 +1,23 @@
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
union Foo {
a: u8,
b: Bar,
}
#[derive(Copy, Clone)]
enum Bar {}
const BAD_BAD_BAD: Bar = unsafe { Foo { a: 1 }.b};
//~^ ERROR this constant likely exhibits undefined behavior
fn main() {
}

View File

@ -0,0 +1,11 @@
error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/ub-uninhabit.rs:19:1
|
LL | const BAD_BAD_BAD: Bar = unsafe { Foo { a: 1 }.b};
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a value of an uninhabited type
|
= 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
error: aborting due to previous error
For more information about this error, try `rustc --explain E0080`.

View File

@ -0,0 +1,22 @@
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// compile-pass
union Foo {
a: &'static u8,
b: usize,
}
// This might point to an invalid address, but that's the user's problem
const USIZE_AS_STATIC_REF: &'static u8 = unsafe { Foo { b: 1337 }.a};
fn main() {
}

View File

@ -0,0 +1,45 @@
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![feature(const_fn)]
type Field1 = i32;
type Field2 = f32;
type Field3 = i64;
union DummyUnion {
field1: Field1,
field2: Field2,
field3: Field3,
}
const FLOAT1_AS_I32: i32 = 1065353216;
const UNION: DummyUnion = DummyUnion { field1: FLOAT1_AS_I32 };
const fn read_field1() -> Field1 {
const FIELD1: Field1 = unsafe { UNION.field1 };
FIELD1
}
const fn read_field2() -> Field2 {
const FIELD2: Field2 = unsafe { UNION.field2 };
FIELD2
}
const fn read_field3() -> Field3 {
const FIELD3: Field3 = unsafe { UNION.field3 }; //~ ERROR exhibits undefined behavior
FIELD3
}
fn main() {
assert_eq!(read_field1(), FLOAT1_AS_I32);
assert_eq!(read_field2(), 1.0);
assert_eq!(read_field3(), unsafe { UNION.field3 });
}

View File

@ -0,0 +1,11 @@
error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/union-const-eval-field.rs:37:5
|
LL | const FIELD3: Field3 = unsafe { UNION.field3 }; //~ ERROR exhibits undefined behavior
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered undefined bytes
|
= 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
error: aborting due to previous error
For more information about this error, try `rustc --explain E0080`.

View File

@ -0,0 +1,51 @@
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![feature(const_fn)]
type Field1 = i32;
type Field3 = i64;
union DummyUnion {
field1: Field1,
field3: Field3,
}
const UNION: DummyUnion = DummyUnion { field1: 1065353216 };
const FIELD3: Field3 = unsafe { UNION.field3 }; //~ ERROR this constant likely exhibits undefined
const FIELD_PATH: Struct = Struct { //~ ERROR this constant likely exhibits undefined behavior
a: 42,
b: unsafe { UNION.field3 },
};
struct Struct {
a: u8,
b: Field3,
}
const FIELD_PATH2: Struct2 = Struct2 { //~ ERROR this constant likely exhibits undefined behavior
b: [
21,
unsafe { UNION.field3 },
23,
24,
],
a: 42,
};
struct Struct2 {
b: [Field3; 4],
a: u8,
}
fn main() {
}

View File

@ -0,0 +1,36 @@
error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/union-ice.rs:23:1
|
LL | const FIELD3: Field3 = unsafe { UNION.field3 }; //~ ERROR this constant likely exhibits undefined
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered undefined bytes
|
= 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
error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/union-ice.rs:25:1
|
LL | / const FIELD_PATH: Struct = Struct { //~ ERROR this constant likely exhibits undefined behavior
LL | | a: 42,
LL | | b: unsafe { UNION.field3 },
LL | | };
| |__^ type validation failed: encountered undefined bytes at .b
|
= 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
error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/union-ice.rs:35:1
|
LL | / const FIELD_PATH2: Struct2 = Struct2 { //~ ERROR this constant likely exhibits undefined behavior
LL | | b: [
LL | | 21,
LL | | unsafe { UNION.field3 },
... |
LL | | a: 42,
LL | | };
| |__^ type validation failed: encountered undefined bytes at .b[1]
|
= 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
error: aborting due to 3 previous errors
For more information about this error, try `rustc --explain E0080`.

View File

@ -0,0 +1,45 @@
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
union DummyUnion {
u8: u8,
bool: bool,
}
#[repr(C)]
#[derive(Copy, Clone)]
enum Enum {
A,
B,
C,
}
#[derive(Copy, Clone)]
union Foo {
a: bool,
b: Enum,
}
union Bar {
foo: Foo,
u8: u8,
}
// the value is not valid for bools
const BAD_BOOL: bool = unsafe { DummyUnion { u8: 42 }.bool};
//~^ ERROR this constant likely exhibits undefined behavior
// The value is not valid for any union variant, but that's fine
// unions are just a convenient way to transmute bits around
const BAD_UNION: Foo = unsafe { Bar { u8: 42 }.foo };
fn main() {
}

View File

@ -0,0 +1,11 @@
error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/union-ub.rs:36:1
|
LL | const BAD_BOOL: bool = unsafe { DummyUnion { u8: 42 }.bool};
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered 42, but expected something in the range 0..=1
|
= 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
error: aborting due to previous error
For more information about this error, try `rustc --explain E0080`.

@ -1 +1 @@
Subproject commit 911aedf736992e907d11cb494167f41f28d02368
Subproject commit e6f1e15676c26fdc7c4713647fe007b26f361a8e