Skip to content

Commit

Permalink
switch validation of scalars to be type-driven
Browse files Browse the repository at this point in the history
This does not actually regress anything.  It would regress NonNull, but we
didn't handle that correctly previously either.
  • Loading branch information
RalfJung committed Oct 2, 2018
1 parent 5c15b64 commit de79ea6
Show file tree
Hide file tree
Showing 12 changed files with 117 additions and 135 deletions.
162 changes: 63 additions & 99 deletions src/librustc_mir/interpret/validity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use rustc::ty::layout::{self, Size};
use rustc::ty::{self, Ty};
use rustc_data_structures::fx::FxHashSet;
use rustc::mir::interpret::{
Scalar, AllocType, EvalResult, EvalErrorKind, PointerArithmetic
Scalar, AllocType, EvalResult, EvalErrorKind
};

use super::{
Expand Down Expand Up @@ -50,6 +50,13 @@ macro_rules! validation_failure {
}

macro_rules! try_validation {
($e:expr, $what:expr, $where:expr, $details:expr) => {{
match $e {
Ok(x) => x,
Err(_) => return validation_failure!($what, $where, $details),
}
}};

($e:expr, $what:expr, $where:expr) => {{
match $e {
Ok(x) => x,
Expand Down Expand Up @@ -121,114 +128,65 @@ fn path_format(path: &Vec<PathElem>) -> String {
out
}

fn scalar_format(value: ScalarMaybeUndef) -> String {
match value {
ScalarMaybeUndef::Undef =>
"uninitialized bytes".to_owned(),
ScalarMaybeUndef::Scalar(Scalar::Ptr(_)) =>
"a pointer".to_owned(),
ScalarMaybeUndef::Scalar(Scalar::Bits { bits, .. }) =>
bits.to_string(),
}
}

impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
fn validate_scalar(
&self,
value: ScalarMaybeUndef,
size: Size,
scalar: &layout::Scalar,
path: &Vec<PathElem>,
ty: Ty,
) -> EvalResult<'tcx> {
trace!("validate scalar: {:#?}, {:#?}, {:#?}, {}", value, size, scalar, ty);
let (lo, hi) = scalar.valid_range.clone().into_inner();

let value = match value {
ScalarMaybeUndef::Scalar(scalar) => scalar,
ScalarMaybeUndef::Undef => return validation_failure!("undefined bytes", path),
};

let bits = match value {
Scalar::Bits { bits, size: value_size } => {
assert_eq!(value_size as u64, size.bytes());
bits
},
Scalar::Ptr(_) => {
match ty.sty {
ty::Bool |
ty::Char |
ty::Float(_) |
ty::Int(_) |
ty::Uint(_) => {
return validation_failure!(
"a pointer",
path,
format!("the type {}", ty.sty)
);
}
ty::RawPtr(_) |
ty::Ref(_, _, _) |
ty::FnPtr(_) => {}
_ => { unreachable!(); }
}
trace!("validate scalar: {:#?}, {:#?}, {}", value, size, ty);

let ptr_size = self.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
// Go over all the primitive types
match ty.sty {
ty::Bool => {
try_validation!(value.to_bool(),
scalar_format(value), path, "a boolean");
},
ty::Char => {
debug_assert_eq!(size.bytes(), 4);
if ::std::char::from_u32(bits as u32).is_none() {
return validation_failure!(
"character",
path,
"a valid unicode codepoint"
);
}
try_validation!(value.to_char(),
scalar_format(value), path, "a valid unicode codepoint");
},
ty::Float(_) | ty::Int(_) | ty::Uint(_) => {
// Must be scalar bits
try_validation!(value.to_bits(size),
scalar_format(value), path, "initialized plain bits");
}
_ => {},
}

use std::ops::RangeInclusive;
let in_range = |bound: RangeInclusive<u128>| bound.contains(&bits);
if lo > hi {
if in_range(0..=hi) || in_range(lo..=u128::max_value()) {
Ok(())
} else {
validation_failure!(
bits,
path,
format!("something in the range {:?} or {:?}", ..=hi, lo..)
)
ty::RawPtr(_) => {
// Anything but undef goes
try_validation!(value.not_undef(),
scalar_format(value), path, "a raw pointer");
},
ty::Ref(..) => {
// This is checked by the recursive reference handling, nothing to do here.
debug_assert!(ty.builtin_deref(true).is_some() && !ty.is_unsafe_ptr());

This comment has been minimized.

Copy link
@oli-obk

oli-obk Oct 2, 2018

I don't get this debug assert. are you sanity checking the methods on Ty?

This comment has been minimized.

Copy link
@RalfJung

RalfJung Oct 2, 2018

Author Owner

Yeah, I am being paranoid. Just to make sure we really recurse later.

}
} else {
if in_range(scalar.valid_range.clone()) {
Ok(())
} else {
validation_failure!(
bits,
path,
format!("something in the range {:?}", scalar.valid_range)
)
ty::FnPtr(_sig) => {
let ptr = try_validation!(value.to_ptr(),
scalar_format(value), path, "a pointer");
let _fn = try_validation!(self.memory.get_fn(ptr),
scalar_format(value), path, "a function pointer");
// TODO: Check if the signature matches
}
ty::FnDef(..) => {
// This is a zero-sized type with all relevant data sitting in the type.
// There is nothing to validate.
}
_ => bug!("Unexpected primitive type {}", ty)
}
Ok(())
}

/// Validate a reference, potentially recursively. `place` is assumed to already be
Expand All @@ -240,10 +198,12 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
ref_tracking: Option<&mut RefTracking<'tcx>>,
) -> EvalResult<'tcx> {
// Before we do anything else, make sure this is entirely in-bounds.
let (size, align) = self.size_and_align_of(place.extra, place.layout)?;
try_validation!(self.memory.check_align(place.ptr, align),
"unaligned reference", path);
if !place.layout.is_zst() {
let ptr = try_validation!(place.ptr.to_ptr(),
"integer pointer in non-ZST reference", path);
let size = self.size_and_align_of(place.extra, place.layout)?.0;
try_validation!(self.memory.check_bounds(ptr, size, false),
"dangling reference (not entirely in bounds)", path);
// Skip recursion for some external statics
Expand Down Expand Up @@ -277,6 +237,10 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
/// It will error if the bits at the destination do not match the ones described by the layout.
/// The `path` may be pushed to, but the part that is present when the function
/// starts must not be changed!
///
/// `ref_tracking` can be None to avoid recursive checking below references.
/// This also toggles between "run-time" (no recursion) and "compile-time" (with recursion)
/// validation (e.g., pointer values are fine in integers at runtime).
pub fn validate_operand(
&self,
dest: OpTy<'tcx>,
Expand Down Expand Up @@ -346,12 +310,11 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
return 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_layout) => {
let size = scalar_layout.value.size(self);
layout::Abi::Scalar(_) => {
let value = try_validation!(self.read_value(dest),
"uninitialized or unrepresentable data", path);
let scalar = value.to_scalar_or_undef();
self.validate_scalar(scalar, size, scalar_layout, &path, dest.layout.ty)?;
self.validate_scalar(scalar, dest.layout.size, &path, dest.layout.ty)?;
// Recursively check *safe* references
if dest.layout.ty.builtin_deref(true).is_some() &&
!dest.layout.ty.is_unsafe_ptr()
Expand All @@ -365,7 +328,8 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
layout::FieldPlacement::Arbitrary { .. }
if dest.layout.ty.builtin_deref(true).is_some() =>
{
// This is a fat pointer.
// This is a fat pointer. We also check fat raw pointers, their metadata must
// be valid!
let ptr = try_validation!(self.read_value(dest.into()),
"undefined location in fat pointer", path);
let ptr = try_validation!(self.ref_to_mplace(ptr),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/const-pointer-values-in-various-types.rs:24:5
|
LL | const I32_REF_USIZE_UNION: usize = unsafe { Nonsense { int_32_ref: &3 }.u };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a pointer, but expected the type usize
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a pointer, but expected initialized plain bits
|
= 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

Expand Down Expand Up @@ -36,7 +36,7 @@ error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/const-pointer-values-in-various-types.rs:36:5
|
LL | const I32_REF_U64_UNION: u64 = unsafe { Nonsense { int_32_ref: &3 }.uint_64 };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a pointer, but expected the type u64
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a pointer, but expected initialized plain bits
|
= 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

Expand Down Expand Up @@ -74,7 +74,7 @@ error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/const-pointer-values-in-various-types.rs:51:5
|
LL | const I32_REF_I64_UNION: i64 = unsafe { Nonsense { int_32_ref: &3 }.int_64 };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a pointer, but expected the type i64
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a pointer, but expected initialized plain bits
|
= 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

Expand All @@ -96,7 +96,7 @@ error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/const-pointer-values-in-various-types.rs:60:5
|
LL | const I32_REF_F64_UNION: f64 = unsafe { Nonsense { int_32_ref: &3 }.float_64 };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a pointer, but expected the type f64
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a pointer, but expected initialized plain bits
|
= 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

Expand Down Expand Up @@ -144,7 +144,7 @@ error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/const-pointer-values-in-various-types.rs:78:5
|
LL | const STR_U64_UNION: u64 = unsafe { Nonsense { stringy: "3" }.uint_64 };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a pointer, but expected the type u64
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a pointer, but expected initialized plain bits
|
= 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

Expand Down Expand Up @@ -184,7 +184,7 @@ error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/const-pointer-values-in-various-types.rs:93:5
|
LL | const STR_I64_UNION: i64 = unsafe { Nonsense { stringy: "3" }.int_64 };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a pointer, but expected the type i64
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a pointer, but expected initialized plain bits
|
= 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

Expand All @@ -208,7 +208,7 @@ error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/const-pointer-values-in-various-types.rs:102:5
|
LL | const STR_F64_UNION: f64 = unsafe { Nonsense { stringy: "3" }.float_64 };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a pointer, but expected the type f64
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a pointer, but expected initialized plain bits
|
= 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

Expand Down
1 change: 0 additions & 1 deletion src/test/ui/consts/const-eval/transmute-const.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,5 @@ use std::mem;

static FOO: bool = unsafe { mem::transmute(3u8) };
//~^ ERROR this static likely exhibits undefined behavior
//~^^ type validation failed: encountered 3, but expected something in the range 0..=1

fn main() {}
2 changes: 1 addition & 1 deletion src/test/ui/consts/const-eval/transmute-const.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ error[E0080]: this static likely exhibits undefined behavior
--> $DIR/transmute-const.rs:15:1
|
LL | static FOO: bool = unsafe { mem::transmute(3u8) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered 3, but expected something in the range 0..=1
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered 3, but expected a boolean
|
= 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

Expand Down
2 changes: 1 addition & 1 deletion src/test/ui/consts/const-eval/ub-enum.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/ub-enum.rs:45:1
|
LL | const BAD_ENUM_CHAR : Option<(char, char)> = Some(('x', unsafe { TransmuteChar { a: !0 }.b }));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered character at .Some.0.1, but expected a valid unicode codepoint
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered 4294967295 at .Some.0.1, but expected a valid unicode codepoint
|
= 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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,17 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

union Foo {
a: &'static u8,
b: usize,
}
#![feature(const_transmute)]

const USIZE_AS_STATIC_REF: &'static u8 = unsafe { Foo { b: 1337 }.a};
use std::mem;

const UNALIGNED: &u16 = unsafe { mem::transmute(&[0u8; 4]) };
//~^ ERROR this constant likely exhibits undefined behavior

const REF_AS_USIZE: usize = unsafe { mem::transmute(&0) };
//~^ ERROR this constant likely exhibits undefined behavior

const USIZE_AS_REF: &'static u8 = unsafe { mem::transmute(1337usize) };
//~^ ERROR this constant likely exhibits undefined behavior

fn main() {
}
fn main() {}
27 changes: 27 additions & 0 deletions src/test/ui/consts/const-eval/ub-ref.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/ub-ref.rs:15:1
|
LL | const UNALIGNED: &u16 = unsafe { mem::transmute(&[0u8; 4]) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered unaligned reference
|
= 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/ub-ref.rs:18:1
|
LL | const REF_AS_USIZE: usize = unsafe { mem::transmute(&0) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a pointer, but expected initialized plain bits
|
= 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/ub-ref.rs:21:1
|
LL | const USIZE_AS_REF: &'static u8 = unsafe { mem::transmute(1337usize) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered integer pointer in non-ZST reference
|
= 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`.
Loading

0 comments on commit de79ea6

Please sign in to comment.