Skip to content

Commit

Permalink
Auto merge of #52498 - oli-obk:const_prop, r=nikomatsakis
Browse files Browse the repository at this point in the history
Const propagate casts

fixes #49760

So... This fixes the original issue about the missing warnings.

But our test suite contains fun things like

```rust
fn foo() {}
assert_eq!(foo as i16, foo as usize as i16);
```

Which, will result in

> a raw memory access tried to access part of a pointer value as raw bytes

on both sides of the assertion. Because well... that's exactly what's going on! We're ripping out 16 bits of a pointer.
  • Loading branch information
bors committed Jul 20, 2018
2 parents 3bea4d1 + 9329957 commit 509cbf3
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 148 deletions.
146 changes: 143 additions & 3 deletions src/librustc_mir/interpret/cast.rs
@@ -1,13 +1,153 @@
use rustc::ty::Ty;
use rustc::ty::layout::LayoutOf;
use rustc::ty::{self, Ty};
use rustc::ty::layout::{self, LayoutOf};
use syntax::ast::{FloatTy, IntTy, UintTy};

use rustc_apfloat::ieee::{Single, Double};
use super::{EvalContext, Machine};
use rustc::mir::interpret::{Scalar, EvalResult, Pointer, PointerArithmetic};
use rustc::mir::interpret::{Scalar, EvalResult, Pointer, PointerArithmetic, Value, EvalErrorKind};
use rustc::mir::CastKind;
use rustc_apfloat::Float;
use interpret::eval_context::ValTy;
use interpret::Place;

impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
crate fn cast(
&mut self,
src: ValTy<'tcx>,
kind: CastKind,
dest_ty: Ty<'tcx>,
dest: Place,
) -> EvalResult<'tcx> {
use rustc::mir::CastKind::*;
match kind {
Unsize => {
let src_layout = self.layout_of(src.ty)?;
let dst_layout = self.layout_of(dest_ty)?;
self.unsize_into(src.value, src_layout, dest, dst_layout)?;
}

Misc => {
if self.type_is_fat_ptr(src.ty) {
match (src.value, self.type_is_fat_ptr(dest_ty)) {
(Value::ByRef { .. }, _) |
// pointers to extern types
(Value::Scalar(_),_) |
// slices and trait objects to other slices/trait objects
(Value::ScalarPair(..), true) => {
let valty = ValTy {
value: src.value,
ty: dest_ty,
};
self.write_value(valty, dest)?;
}
// slices and trait objects to thin pointers (dropping the metadata)
(Value::ScalarPair(data, _), false) => {
let valty = ValTy {
value: Value::Scalar(data),
ty: dest_ty,
};
self.write_value(valty, dest)?;
}
}
} else {
let src_layout = self.layout_of(src.ty)?;
match src_layout.variants {
layout::Variants::Single { index } => {
if let Some(def) = src.ty.ty_adt_def() {
let discr_val = def
.discriminant_for_variant(*self.tcx, index)
.val;
let defined = self
.layout_of(dest_ty)
.unwrap()
.size
.bits() as u8;
return self.write_scalar(
dest,
Scalar::Bits {
bits: discr_val,
defined,
},
dest_ty);
}
}
layout::Variants::Tagged { .. } |
layout::Variants::NicheFilling { .. } => {},
}

let src_val = self.value_to_scalar(src)?;
let dest_val = self.cast_scalar(src_val, src.ty, dest_ty)?;
let valty = ValTy {
value: Value::Scalar(dest_val),
ty: dest_ty,
};
self.write_value(valty, dest)?;
}
}

ReifyFnPointer => {
match src.ty.sty {
ty::TyFnDef(def_id, substs) => {
if self.tcx.has_attr(def_id, "rustc_args_required_const") {
bug!("reifying a fn ptr that requires \
const arguments");
}
let instance: EvalResult<'tcx, _> = ty::Instance::resolve(
*self.tcx,
self.param_env,
def_id,
substs,
).ok_or_else(|| EvalErrorKind::TooGeneric.into());
let fn_ptr = self.memory.create_fn_alloc(instance?);
let valty = ValTy {
value: Value::Scalar(fn_ptr.into()),
ty: dest_ty,
};
self.write_value(valty, dest)?;
}
ref other => bug!("reify fn pointer on {:?}", other),
}
}

UnsafeFnPointer => {
match dest_ty.sty {
ty::TyFnPtr(_) => {
let mut src = src;
src.ty = dest_ty;
self.write_value(src, dest)?;
}
ref other => bug!("fn to unsafe fn cast on {:?}", other),
}
}

ClosureFnPointer => {
match src.ty.sty {
ty::TyClosure(def_id, substs) => {
let substs = self.tcx.subst_and_normalize_erasing_regions(
self.substs(),
ty::ParamEnv::reveal_all(),
&substs,
);
let instance = ty::Instance::resolve_closure(
*self.tcx,
def_id,
substs,
ty::ClosureKind::FnOnce,
);
let fn_ptr = self.memory.create_fn_alloc(instance);
let valty = ValTy {
value: Value::Scalar(fn_ptr.into()),
ty: dest_ty,
};
self.write_value(valty, dest)?;
}
ref other => bug!("closure fn pointer on {:?}", other),
}
}
}
Ok(())
}

pub(super) fn cast_scalar(
&self,
val: Scalar,
Expand Down
133 changes: 3 additions & 130 deletions src/librustc_mir/interpret/eval_context.rs
Expand Up @@ -770,135 +770,8 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M

Cast(kind, ref operand, cast_ty) => {
debug_assert_eq!(self.monomorphize(cast_ty, self.substs()), dest_ty);
use rustc::mir::CastKind::*;
match kind {
Unsize => {
let src = self.eval_operand(operand)?;
let src_layout = self.layout_of(src.ty)?;
let dst_layout = self.layout_of(dest_ty)?;
self.unsize_into(src.value, src_layout, dest, dst_layout)?;
}

Misc => {
let src = self.eval_operand(operand)?;
if self.type_is_fat_ptr(src.ty) {
match (src.value, self.type_is_fat_ptr(dest_ty)) {
(Value::ByRef { .. }, _) |
// pointers to extern types
(Value::Scalar(_),_) |
// slices and trait objects to other slices/trait objects
(Value::ScalarPair(..), true) => {
let valty = ValTy {
value: src.value,
ty: dest_ty,
};
self.write_value(valty, dest)?;
}
// slices and trait objects to thin pointers (dropping the metadata)
(Value::ScalarPair(data, _), false) => {
let valty = ValTy {
value: Value::Scalar(data),
ty: dest_ty,
};
self.write_value(valty, dest)?;
}
}
} else {
let src_layout = self.layout_of(src.ty)?;
match src_layout.variants {
layout::Variants::Single { index } => {
if let Some(def) = src.ty.ty_adt_def() {
let discr_val = def
.discriminant_for_variant(*self.tcx, index)
.val;
let defined = self
.layout_of(dest_ty)
.unwrap()
.size
.bits() as u8;
return self.write_scalar(
dest,
Scalar::Bits {
bits: discr_val,
defined,
},
dest_ty);
}
}
layout::Variants::Tagged { .. } |
layout::Variants::NicheFilling { .. } => {},
}

let src_val = self.value_to_scalar(src)?;
let dest_val = self.cast_scalar(src_val, src.ty, dest_ty)?;
let valty = ValTy {
value: Value::Scalar(dest_val),
ty: dest_ty,
};
self.write_value(valty, dest)?;
}
}

ReifyFnPointer => {
match self.eval_operand(operand)?.ty.sty {
ty::TyFnDef(def_id, substs) => {
if self.tcx.has_attr(def_id, "rustc_args_required_const") {
bug!("reifying a fn ptr that requires \
const arguments");
}
let instance: EvalResult<'tcx, _> = ty::Instance::resolve(
*self.tcx,
self.param_env,
def_id,
substs,
).ok_or_else(|| EvalErrorKind::TooGeneric.into());
let fn_ptr = self.memory.create_fn_alloc(instance?);
let valty = ValTy {
value: Value::Scalar(fn_ptr.into()),
ty: dest_ty,
};
self.write_value(valty, dest)?;
}
ref other => bug!("reify fn pointer on {:?}", other),
}
}

UnsafeFnPointer => {
match dest_ty.sty {
ty::TyFnPtr(_) => {
let mut src = self.eval_operand(operand)?;
src.ty = dest_ty;
self.write_value(src, dest)?;
}
ref other => bug!("fn to unsafe fn cast on {:?}", other),
}
}

ClosureFnPointer => {
match self.eval_operand(operand)?.ty.sty {
ty::TyClosure(def_id, substs) => {
let substs = self.tcx.subst_and_normalize_erasing_regions(
self.substs(),
ty::ParamEnv::reveal_all(),
&substs,
);
let instance = ty::Instance::resolve_closure(
*self.tcx,
def_id,
substs,
ty::ClosureKind::FnOnce,
);
let fn_ptr = self.memory.create_fn_alloc(instance);
let valty = ValTy {
value: Value::Scalar(fn_ptr.into()),
ty: dest_ty,
};
self.write_value(valty, dest)?;
}
ref other => bug!("closure fn pointer on {:?}", other),
}
}
}
let src = self.eval_operand(operand)?;
self.cast(src, kind, dest_ty, dest)?;
}

Discriminant(ref place) => {
Expand Down Expand Up @@ -1565,7 +1438,7 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M
}
}

fn unsize_into(
crate fn unsize_into(
&mut self,
src: Value,
src_layout: TyLayout<'tcx>,
Expand Down
48 changes: 34 additions & 14 deletions src/librustc_mir/transform/const_prop.rs
Expand Up @@ -17,7 +17,7 @@ use rustc::mir::{Constant, Literal, Location, Place, Mir, Operand, Rvalue, Local
use rustc::mir::{NullOp, StatementKind, Statement, BasicBlock, LocalKind};
use rustc::mir::{TerminatorKind, ClearCrossCrate, SourceInfo, BinOp, ProjectionElem};
use rustc::mir::visit::{Visitor, PlaceContext};
use rustc::mir::interpret::ConstEvalErr;
use rustc::mir::interpret::{ConstEvalErr, EvalErrorKind};
use rustc::ty::{TyCtxt, self, Instance};
use rustc::mir::interpret::{Value, Scalar, GlobalId, EvalResult};
use interpret::EvalContext;
Expand Down Expand Up @@ -145,17 +145,23 @@ impl<'b, 'a, 'tcx:'b> ConstPropagator<'b, 'a, 'tcx> {
let r = match f(self) {
Ok(val) => Some(val),
Err(err) => {
let (frames, span) = self.ecx.generate_stacktrace(None);
let err = ConstEvalErr {
span,
error: err,
stacktrace: frames,
};
err.report_as_lint(
self.ecx.tcx,
"this expression will panic at runtime",
lint_root,
);
match err.kind {
// don't report these, they make no sense in a const prop context
EvalErrorKind::MachineError(_) => {},
_ => {
let (frames, span) = self.ecx.generate_stacktrace(None);
let err = ConstEvalErr {
span,
error: err,
stacktrace: frames,
};
err.report_as_lint(
self.ecx.tcx,
"this expression will panic at runtime",
lint_root,
);
}
}
None
},
};
Expand Down Expand Up @@ -257,10 +263,25 @@ impl<'b, 'a, 'tcx:'b> ConstPropagator<'b, 'a, 'tcx> {
},
Rvalue::Repeat(..) |
Rvalue::Ref(..) |
Rvalue::Cast(..) |
Rvalue::Aggregate(..) |
Rvalue::NullaryOp(NullOp::Box, _) |
Rvalue::Discriminant(..) => None,

Rvalue::Cast(kind, ref operand, _) => {
let (value, ty, 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 = ::interpret::Place::from_ptr(dest_ptr, place_align);
this.ecx.cast(ValTy { value, ty }, kind, place_ty, dest)?;
Ok((
Value::ByRef(dest_ptr.into(), place_align),
place_ty,
span,
))
})
}

// FIXME(oli-obk): evaluate static/constant slice lengths
Rvalue::Len(_) => None,
Rvalue::NullaryOp(NullOp::SizeOf, ty) => {
Expand Down Expand Up @@ -354,7 +375,6 @@ impl<'b, 'a, 'tcx:'b> ConstPropagator<'b, 'a, 'tcx> {
)
} else {
if overflow {
use rustc::mir::interpret::EvalErrorKind;
let err = EvalErrorKind::Overflow(op).into();
let _: Option<()> = self.use_ecx(source_info, |_| Err(err));
return None;
Expand Down
7 changes: 6 additions & 1 deletion src/test/run-pass/cast-rfc0401.rs
Expand Up @@ -166,7 +166,12 @@ fn main()

assert!(foo as usize != bar as usize);

assert_eq!(foo as i16, foo as usize as i16);
// Taking a few bits of a function's address is totally pointless and we detect that
// Disabling the lint to ensure that the assertion can still be run
#[allow(const_err)]
{
assert_eq!(foo as i16, foo as usize as i16);
}

// fptr-ptr-cast

Expand Down

0 comments on commit 509cbf3

Please sign in to comment.