Add a new Assert terminator to MIR for bounds & arithmetic checks.
This commit is contained in:
parent
7fbff36d01
commit
156b1fb9e1
22 changed files with 305 additions and 196 deletions
|
@ -10,7 +10,7 @@
|
|||
|
||||
use graphviz::IntoCow;
|
||||
use middle::const_val::ConstVal;
|
||||
use rustc_const_math::{ConstUsize, ConstInt};
|
||||
use rustc_const_math::{ConstUsize, ConstInt, ConstMathErr};
|
||||
use hir::def_id::DefId;
|
||||
use ty::subst::Substs;
|
||||
use ty::{self, AdtDef, ClosureSubsts, FnOutput, Region, Ty};
|
||||
|
@ -354,6 +354,16 @@ pub enum TerminatorKind<'tcx> {
|
|||
/// Cleanups to be done if the call unwinds.
|
||||
cleanup: Option<BasicBlock>
|
||||
},
|
||||
|
||||
/// Jump to the target if the condition has the expected value,
|
||||
/// otherwise panic with a message and a cleanup target.
|
||||
Assert {
|
||||
cond: Operand<'tcx>,
|
||||
expected: bool,
|
||||
msg: AssertMessage<'tcx>,
|
||||
target: BasicBlock,
|
||||
cleanup: Option<BasicBlock>
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> Terminator<'tcx> {
|
||||
|
@ -389,6 +399,8 @@ impl<'tcx> TerminatorKind<'tcx> {
|
|||
Drop { ref target, unwind: None, .. } => {
|
||||
slice::ref_slice(target).into_cow()
|
||||
}
|
||||
Assert { target, cleanup: Some(unwind), .. } => vec![target, unwind].into_cow(),
|
||||
Assert { ref target, .. } => slice::ref_slice(target).into_cow(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -413,6 +425,8 @@ impl<'tcx> TerminatorKind<'tcx> {
|
|||
Drop { ref mut target, unwind: None, .. } => {
|
||||
vec![target]
|
||||
}
|
||||
Assert { ref mut target, cleanup: Some(ref mut unwind), .. } => vec![target, unwind],
|
||||
Assert { ref mut target, .. } => vec![target]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -495,6 +509,26 @@ impl<'tcx> TerminatorKind<'tcx> {
|
|||
}
|
||||
write!(fmt, ")")
|
||||
}
|
||||
Assert { ref cond, expected, ref msg, .. } => {
|
||||
write!(fmt, "assert(")?;
|
||||
if !expected {
|
||||
write!(fmt, "!")?;
|
||||
}
|
||||
write!(fmt, "{:?}, ", cond)?;
|
||||
|
||||
match *msg {
|
||||
AssertMessage::BoundsCheck { ref len, ref index } => {
|
||||
write!(fmt, "{:?}, {:?}, {:?}",
|
||||
"index out of bounds: the len is {} but the index is {}",
|
||||
len, index)?;
|
||||
}
|
||||
AssertMessage::Math(ref err) => {
|
||||
write!(fmt, "{:?}", err.description())?;
|
||||
}
|
||||
}
|
||||
|
||||
write!(fmt, ")")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -532,10 +566,21 @@ impl<'tcx> TerminatorKind<'tcx> {
|
|||
Drop { unwind: Some(_), .. } => {
|
||||
vec!["return".into_cow(), "unwind".into_cow()]
|
||||
}
|
||||
Assert { cleanup: None, .. } => vec!["".into()],
|
||||
Assert { .. } =>
|
||||
vec!["success".into_cow(), "unwind".into_cow()]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, RustcEncodable, RustcDecodable)]
|
||||
pub enum AssertMessage<'tcx> {
|
||||
BoundsCheck {
|
||||
len: Operand<'tcx>,
|
||||
index: Operand<'tcx>
|
||||
},
|
||||
Math(ConstMathErr)
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Statements
|
||||
|
|
|
@ -127,6 +127,11 @@ macro_rules! make_mir_visitor {
|
|||
self.super_terminator_kind(block, kind);
|
||||
}
|
||||
|
||||
fn visit_assert_message(&mut self,
|
||||
msg: & $($mutability)* AssertMessage<'tcx>) {
|
||||
self.super_assert_message(msg);
|
||||
}
|
||||
|
||||
fn visit_rvalue(&mut self,
|
||||
rvalue: & $($mutability)* Rvalue<'tcx>) {
|
||||
self.super_rvalue(rvalue);
|
||||
|
@ -426,6 +431,31 @@ macro_rules! make_mir_visitor {
|
|||
}
|
||||
cleanup.map(|t| self.visit_branch(block, t));
|
||||
}
|
||||
|
||||
TerminatorKind::Assert { ref $($mutability)* cond,
|
||||
expected: _,
|
||||
ref $($mutability)* msg,
|
||||
target,
|
||||
cleanup } => {
|
||||
self.visit_operand(cond);
|
||||
self.visit_assert_message(msg);
|
||||
self.visit_branch(block, target);
|
||||
cleanup.map(|t| self.visit_branch(block, t));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn super_assert_message(&mut self,
|
||||
msg: & $($mutability)* AssertMessage<'tcx>) {
|
||||
match *msg {
|
||||
AssertMessage::BoundsCheck {
|
||||
ref $($mutability)* len,
|
||||
ref $($mutability)* index
|
||||
} => {
|
||||
self.visit_operand(len);
|
||||
self.visit_operand(index);
|
||||
}
|
||||
AssertMessage::Math(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -450,13 +450,14 @@ impl<'a, 'tcx: 'a, D> DataflowAnalysis<'a, 'tcx, D>
|
|||
repr::TerminatorKind::Return |
|
||||
repr::TerminatorKind::Resume => {}
|
||||
repr::TerminatorKind::Goto { ref target } |
|
||||
repr::TerminatorKind::Assert { ref target, cleanup: None, .. } |
|
||||
repr::TerminatorKind::Drop { ref target, location: _, unwind: None } |
|
||||
|
||||
repr::TerminatorKind::DropAndReplace {
|
||||
ref target, value: _, location: _, unwind: None
|
||||
} => {
|
||||
self.propagate_bits_into_entry_set_for(in_out, changed, target);
|
||||
}
|
||||
repr::TerminatorKind::Assert { ref target, cleanup: Some(ref unwind), .. } |
|
||||
repr::TerminatorKind::Drop { ref target, location: _, unwind: Some(ref unwind) } |
|
||||
repr::TerminatorKind::DropAndReplace {
|
||||
ref target, value: _, location: _, unwind: Some(ref unwind)
|
||||
|
|
|
@ -663,6 +663,22 @@ fn gather_moves<'a, 'tcx>(mir: &Mir<'tcx>, tcx: TyCtxt<'a, 'tcx, 'tcx>) -> MoveD
|
|||
bb_ctxt.on_operand(SK::If, cond, source);
|
||||
}
|
||||
|
||||
TerminatorKind::Assert {
|
||||
ref cond, expected: _,
|
||||
ref msg, target: _, cleanup: _
|
||||
} => {
|
||||
// The `cond` is always of (copyable) type `bool`,
|
||||
// so there will never be anything to move.
|
||||
let _ = cond;
|
||||
match *msg {
|
||||
AssertMessage:: BoundsCheck { ref len, ref index } => {
|
||||
// Same for the usize length and index in bounds-checking.
|
||||
let _ = (len, index);
|
||||
}
|
||||
AssertMessage::Math(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
TerminatorKind::SwitchInt { switch_ty: _, values: _, targets: _, ref discr } |
|
||||
TerminatorKind::Switch { adt_def: _, targets: _, ref discr } => {
|
||||
// The `discr` is not consumed; that is instead
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
use syntax::ast;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, RustcEncodable, RustcDecodable)]
|
||||
pub enum ConstMathErr {
|
||||
NotInRange,
|
||||
CmpBetweenUnequalTypes,
|
||||
|
@ -25,7 +25,7 @@ pub enum ConstMathErr {
|
|||
}
|
||||
pub use self::ConstMathErr::*;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, RustcEncodable, RustcDecodable)]
|
||||
pub enum Op {
|
||||
Add,
|
||||
Sub,
|
||||
|
|
|
@ -12,7 +12,6 @@ use build::{BlockAnd, BlockAndExtension, Builder};
|
|||
use hair::*;
|
||||
use rustc::mir::repr::*;
|
||||
use rustc::hir;
|
||||
use syntax::codemap::Span;
|
||||
|
||||
impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
|
||||
pub fn ast_block(&mut self,
|
||||
|
@ -82,22 +81,4 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
|
|||
block.unit()
|
||||
})
|
||||
}
|
||||
|
||||
// Helper method for generating a conditional branch
|
||||
// Returns (TrueBlock, FalseBlock)
|
||||
pub fn build_cond_br(&mut self, block: BasicBlock, span: Span,
|
||||
cond: Operand<'tcx>) -> (BasicBlock, BasicBlock) {
|
||||
let scope_id = self.innermost_scope_id();
|
||||
|
||||
let then_block = self.cfg.start_new_block();
|
||||
let else_block = self.cfg.start_new_block();
|
||||
|
||||
self.cfg.terminate(block, scope_id, span,
|
||||
TerminatorKind::If {
|
||||
cond: cond,
|
||||
targets: (then_block, else_block)
|
||||
});
|
||||
|
||||
(then_block, else_block)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,15 +66,12 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
|
|||
idx.clone(),
|
||||
Operand::Consume(len.clone())));
|
||||
|
||||
let (success, failure) = (this.cfg.start_new_block(), this.cfg.start_new_block());
|
||||
this.cfg.terminate(block,
|
||||
scope_id,
|
||||
expr_span,
|
||||
TerminatorKind::If {
|
||||
cond: Operand::Consume(lt),
|
||||
targets: (success, failure),
|
||||
});
|
||||
this.panic_bounds_check(failure, idx.clone(), Operand::Consume(len), expr_span);
|
||||
let msg = AssertMessage::BoundsCheck {
|
||||
len: Operand::Consume(len),
|
||||
index: idx.clone()
|
||||
};
|
||||
let success = this.assert(block, Operand::Consume(lt), true,
|
||||
msg, expr_span);
|
||||
success.and(slice.index(idx))
|
||||
}
|
||||
ExprKind::SelfRef => {
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
use std;
|
||||
|
||||
use rustc_const_math::{ConstMathErr, Op};
|
||||
use rustc_data_structures::fnv::FnvHashMap;
|
||||
|
||||
use build::{BlockAnd, BlockAndExtension, Builder};
|
||||
|
@ -88,10 +89,9 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
|
|||
this.cfg.push_assign(block, scope_id, expr_span, &is_min,
|
||||
Rvalue::BinaryOp(BinOp::Eq, arg.clone(), minval));
|
||||
|
||||
let (of_block, ok_block) = this.build_cond_br(block, expr_span,
|
||||
Operand::Consume(is_min));
|
||||
this.panic(of_block, "attempted to negate with overflow", expr_span);
|
||||
block = ok_block;
|
||||
let err = ConstMathErr::Overflow(Op::Neg);
|
||||
block = this.assert(block, Operand::Consume(is_min), false,
|
||||
AssertMessage::Math(err), expr_span);
|
||||
}
|
||||
block.and(Rvalue::UnaryOp(op, arg))
|
||||
}
|
||||
|
@ -261,27 +261,32 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
|
|||
let val = result_value.clone().field(val_fld, ty);
|
||||
let of = result_value.field(of_fld, bool_ty);
|
||||
|
||||
let msg = if op == BinOp::Shl || op == BinOp::Shr {
|
||||
"shift operation overflowed"
|
||||
} else {
|
||||
"arithmetic operation overflowed"
|
||||
};
|
||||
let err = ConstMathErr::Overflow(match op {
|
||||
BinOp::Add => Op::Add,
|
||||
BinOp::Sub => Op::Sub,
|
||||
BinOp::Mul => Op::Mul,
|
||||
BinOp::Shl => Op::Shl,
|
||||
BinOp::Shr => Op::Shr,
|
||||
_ => {
|
||||
bug!("MIR build_binary_op: {:?} is not checkable", op)
|
||||
}
|
||||
});
|
||||
|
||||
let (of_block, ok_block) = self.build_cond_br(block, span, Operand::Consume(of));
|
||||
self.panic(of_block, msg, span);
|
||||
block = self.assert(block, Operand::Consume(of), false,
|
||||
AssertMessage::Math(err), span);
|
||||
|
||||
ok_block.and(Rvalue::Use(Operand::Consume(val)))
|
||||
block.and(Rvalue::Use(Operand::Consume(val)))
|
||||
} else {
|
||||
if ty.is_integral() && (op == BinOp::Div || op == BinOp::Rem) {
|
||||
// Checking division and remainder is more complex, since we 1. always check
|
||||
// and 2. there are two possible failure cases, divide-by-zero and overflow.
|
||||
|
||||
let (zero_msg, overflow_msg) = if op == BinOp::Div {
|
||||
("attempted to divide by zero",
|
||||
"attempted to divide with overflow")
|
||||
let (zero_err, overflow_err) = if op == BinOp::Div {
|
||||
(ConstMathErr::DivisionByZero,
|
||||
ConstMathErr::Overflow(Op::Div))
|
||||
} else {
|
||||
("attempted remainder with a divisor of zero",
|
||||
"attempted remainder with overflow")
|
||||
(ConstMathErr::RemainderByZero,
|
||||
ConstMathErr::Overflow(Op::Rem))
|
||||
};
|
||||
|
||||
// Check for / 0
|
||||
|
@ -290,11 +295,8 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
|
|||
self.cfg.push_assign(block, scope_id, span, &is_zero,
|
||||
Rvalue::BinaryOp(BinOp::Eq, rhs.clone(), zero));
|
||||
|
||||
let (zero_block, ok_block) = self.build_cond_br(block, span,
|
||||
Operand::Consume(is_zero));
|
||||
self.panic(zero_block, zero_msg, span);
|
||||
|
||||
block = ok_block;
|
||||
block = self.assert(block, Operand::Consume(is_zero), false,
|
||||
AssertMessage::Math(zero_err), span);
|
||||
|
||||
// We only need to check for the overflow in one case:
|
||||
// MIN / -1, and only for signed values.
|
||||
|
@ -318,11 +320,8 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
|
|||
self.cfg.push_assign(block, scope_id, span, &of,
|
||||
Rvalue::BinaryOp(BinOp::BitAnd, is_neg_1, is_min));
|
||||
|
||||
let (of_block, ok_block) = self.build_cond_br(block, span,
|
||||
Operand::Consume(of));
|
||||
self.panic(of_block, overflow_msg, span);
|
||||
|
||||
block = ok_block;
|
||||
block = self.assert(block, Operand::Consume(of), false,
|
||||
AssertMessage::Math(overflow_err), span);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -517,7 +517,6 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
|
|||
}
|
||||
|
||||
|
||||
|
||||
pub fn build_drop_and_replace(&mut self,
|
||||
block: BasicBlock,
|
||||
span: Span,
|
||||
|
@ -538,48 +537,8 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
|
|||
next_target.unit()
|
||||
}
|
||||
|
||||
// Panicking
|
||||
// =========
|
||||
// FIXME: should be moved into their own module
|
||||
pub fn panic_bounds_check(&mut self,
|
||||
block: BasicBlock,
|
||||
index: Operand<'tcx>,
|
||||
len: Operand<'tcx>,
|
||||
span: Span) {
|
||||
// fn(&(filename: &'static str, line: u32), index: usize, length: usize) -> !
|
||||
let region = ty::ReStatic; // FIXME(mir-borrowck): use a better region?
|
||||
let func = self.lang_function(lang_items::PanicBoundsCheckFnLangItem);
|
||||
let args = self.hir.tcx().replace_late_bound_regions(&func.ty.fn_args(), |_| region).0;
|
||||
|
||||
let ref_ty = args[0];
|
||||
let tup_ty = if let ty::TyRef(_, tyandmut) = ref_ty.sty {
|
||||
tyandmut.ty
|
||||
} else {
|
||||
span_bug!(span, "unexpected panic_bound_check type: {:?}", func.ty);
|
||||
};
|
||||
|
||||
let (tuple, tuple_ref) = (self.temp(tup_ty), self.temp(ref_ty));
|
||||
let (file, line) = self.span_to_fileline_args(span);
|
||||
let elems = vec![Operand::Constant(file), Operand::Constant(line)];
|
||||
let scope_id = self.innermost_scope_id();
|
||||
// FIXME: We should have this as a constant, rather than a stack variable (to not pollute
|
||||
// icache with cold branch code), however to achieve that we either have to rely on rvalue
|
||||
// promotion or have some way, in MIR, to create constants.
|
||||
self.cfg.push_assign(block, scope_id, span, &tuple, // tuple = (file_arg, line_arg);
|
||||
Rvalue::Aggregate(AggregateKind::Tuple, elems));
|
||||
// FIXME: is this region really correct here?
|
||||
self.cfg.push_assign(block, scope_id, span, &tuple_ref, // tuple_ref = &tuple;
|
||||
Rvalue::Ref(region, BorrowKind::Shared, tuple));
|
||||
let cleanup = self.diverge_cleanup();
|
||||
self.cfg.terminate(block, scope_id, span, TerminatorKind::Call {
|
||||
func: Operand::Constant(func),
|
||||
args: vec![Operand::Consume(tuple_ref), index, len],
|
||||
destination: None,
|
||||
cleanup: cleanup,
|
||||
});
|
||||
}
|
||||
|
||||
/// Create diverge cleanup and branch to it from `block`.
|
||||
// FIXME: Remove this (used only for unreachable cases in match).
|
||||
pub fn panic(&mut self, block: BasicBlock, msg: &'static str, span: Span) {
|
||||
// fn(&(msg: &'static str filename: &'static str, line: u32)) -> !
|
||||
let region = ty::ReStatic; // FIXME(mir-borrowck): use a better region?
|
||||
|
@ -622,6 +581,32 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
|
|||
});
|
||||
}
|
||||
|
||||
/// Create an Assert terminator and return the success block.
|
||||
/// If the boolean condition operand is not the expected value,
|
||||
/// a runtime panic will be caused with the given message.
|
||||
pub fn assert(&mut self, block: BasicBlock,
|
||||
cond: Operand<'tcx>,
|
||||
expected: bool,
|
||||
msg: AssertMessage<'tcx>,
|
||||
span: Span)
|
||||
-> BasicBlock {
|
||||
let scope_id = self.innermost_scope_id();
|
||||
|
||||
let success_block = self.cfg.start_new_block();
|
||||
let cleanup = self.diverge_cleanup();
|
||||
|
||||
self.cfg.terminate(block, scope_id, span,
|
||||
TerminatorKind::Assert {
|
||||
cond: cond,
|
||||
expected: expected,
|
||||
msg: msg,
|
||||
target: success_block,
|
||||
cleanup: cleanup
|
||||
});
|
||||
|
||||
success_block
|
||||
}
|
||||
|
||||
fn lang_function(&mut self, lang_item: lang_items::LangItem) -> Constant<'tcx> {
|
||||
let funcdid = match self.hir.tcx().lang_items.require(lang_item) {
|
||||
Ok(d) => d,
|
||||
|
|
|
@ -30,6 +30,7 @@ impl<'tcx> MutVisitor<'tcx> for NoLandingPads {
|
|||
/* nothing to do */
|
||||
},
|
||||
TerminatorKind::Call { cleanup: ref mut unwind, .. } |
|
||||
TerminatorKind::Assert { cleanup: ref mut unwind, .. } |
|
||||
TerminatorKind::DropAndReplace { ref mut unwind, .. } |
|
||||
TerminatorKind::Drop { ref mut unwind, .. } => {
|
||||
unwind.take();
|
||||
|
|
|
@ -332,61 +332,6 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx, 'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns true if the block ends in a bounds check branch, i.e.:
|
||||
/// len = Len(array);
|
||||
/// cond = Lt(idx, len);
|
||||
/// if cond {
|
||||
/// ...
|
||||
/// } else {
|
||||
/// loc = (...);
|
||||
/// loc_ref = &loc;
|
||||
/// panic_bounds_check(loc_ref, idx, len);
|
||||
/// }
|
||||
fn is_bounds_check(&self, bb: BasicBlock,
|
||||
cond_op: &Operand<'tcx>,
|
||||
if_else: BasicBlock) -> bool {
|
||||
use rustc::mir::repr::Lvalue::*;
|
||||
use rustc::mir::repr::Operand::Consume;
|
||||
use rustc::mir::repr::Rvalue::*;
|
||||
use rustc::mir::repr::StatementKind::*;
|
||||
use rustc::mir::repr::TerminatorKind::*;
|
||||
|
||||
let stmts = &self.mir[bb].statements;
|
||||
let stmts_panic = &self.mir[if_else].statements;
|
||||
if stmts.len() < 2 || stmts_panic.len() != 2 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let all = (&stmts[stmts.len() - 2].kind,
|
||||
&stmts[stmts.len() - 1].kind,
|
||||
cond_op,
|
||||
&stmts_panic[0].kind,
|
||||
&stmts_panic[1].kind,
|
||||
&self.mir[if_else].terminator().kind);
|
||||
match all {
|
||||
(&Assign(Temp(len), Len(_)),
|
||||
&Assign(Temp(cond), BinaryOp(BinOp::Lt, ref idx, Consume(Temp(len2)))),
|
||||
/* if */ &Consume(Temp(cond2)), /* {...} else */
|
||||
&Assign(Temp(loc), Aggregate(..)),
|
||||
&Assign(Temp(loc_ref), Ref(_, _, Temp(loc2))),
|
||||
&Call {
|
||||
func: Operand::Constant(Constant {
|
||||
literal: Literal::Item { def_id, .. }, ..
|
||||
}),
|
||||
ref args,
|
||||
destination: None,
|
||||
..
|
||||
}) => {
|
||||
len == len2 && cond == cond2 && loc == loc2 &&
|
||||
args[0] == Consume(Temp(loc_ref)) &&
|
||||
args[1] == *idx &&
|
||||
args[2] == Consume(Temp(len)) &&
|
||||
Some(def_id) == self.tcx.lang_items.panic_bounds_check_fn()
|
||||
}
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
|
||||
/// Qualify a whole const, static initializer or const fn.
|
||||
fn qualify_const(&mut self) -> Qualif {
|
||||
let mir = self.mir;
|
||||
|
@ -402,6 +347,7 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx, 'tcx> {
|
|||
TerminatorKind::Goto { target } |
|
||||
// Drops are considered noops.
|
||||
TerminatorKind::Drop { target, .. } |
|
||||
TerminatorKind::Assert { target, .. } |
|
||||
TerminatorKind::Call { destination: Some((_, target)), .. } => {
|
||||
Some(target)
|
||||
}
|
||||
|
@ -411,15 +357,7 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx, 'tcx> {
|
|||
return Qualif::empty();
|
||||
}
|
||||
|
||||
// Need to allow bounds checking branches.
|
||||
TerminatorKind::If { ref cond, targets: (if_true, if_else) } => {
|
||||
if self.is_bounds_check(bb, cond, if_else) {
|
||||
Some(if_true)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
TerminatorKind::If {..} |
|
||||
TerminatorKind::Switch {..} |
|
||||
TerminatorKind::SwitchInt {..} |
|
||||
TerminatorKind::DropAndReplace { .. } |
|
||||
|
|
|
@ -181,7 +181,17 @@ fn simplify_branches(mir: &mut Mir) {
|
|||
}
|
||||
}
|
||||
|
||||
TerminatorKind::Assert { target, cond: Operand::Constant(Constant {
|
||||
literal: Literal::Value {
|
||||
value: ConstVal::Bool(cond)
|
||||
}, ..
|
||||
}), expected, .. } if cond == expected => {
|
||||
changed = true;
|
||||
TerminatorKind::Goto { target: target }
|
||||
}
|
||||
|
||||
TerminatorKind::SwitchInt { ref targets, .. } if targets.len() == 1 => {
|
||||
changed = true;
|
||||
TerminatorKind::Goto { target: targets[0] }
|
||||
}
|
||||
_ => continue
|
||||
|
|
|
@ -431,6 +431,21 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
|
|||
self.check_call_inputs(mir, term, &sig, args);
|
||||
}
|
||||
}
|
||||
TerminatorKind::Assert { ref cond, ref msg, .. } => {
|
||||
let cond_ty = mir.operand_ty(tcx, cond);
|
||||
if cond_ty != tcx.types.bool {
|
||||
span_mirbug!(self, term, "bad Assert ({:?}, not bool", cond_ty);
|
||||
}
|
||||
|
||||
if let AssertMessage::BoundsCheck { ref len, ref index } = *msg {
|
||||
if mir.operand_ty(tcx, len) != tcx.types.usize {
|
||||
span_mirbug!(self, len, "bounds-check length non-usize {:?}", len)
|
||||
}
|
||||
if mir.operand_ty(tcx, index) != tcx.types.usize {
|
||||
span_mirbug!(self, index, "bounds-check index non-usize {:?}", index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -561,7 +576,8 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
|
|||
}
|
||||
}
|
||||
TerminatorKind::Drop { target, unwind, .. } |
|
||||
TerminatorKind::DropAndReplace { target, unwind, .. } => {
|
||||
TerminatorKind::DropAndReplace { target, unwind, .. } |
|
||||
TerminatorKind::Assert { target, cleanup: unwind, .. } => {
|
||||
self.assert_iscleanup(mir, block, target, is_cleanup);
|
||||
if let Some(unwind) = unwind {
|
||||
if is_cleanup {
|
||||
|
|
|
@ -880,7 +880,7 @@ fn compare_values<'blk, 'tcx>(cx: Block<'blk, 'tcx>,
|
|||
rhs_t: Ty<'tcx>,
|
||||
debug_loc: DebugLoc)
|
||||
-> Result<'blk, 'tcx> {
|
||||
let did = langcall(bcx,
|
||||
let did = langcall(bcx.tcx(),
|
||||
None,
|
||||
&format!("comparison of `{}`", rhs_t),
|
||||
StrEqFnLangItem);
|
||||
|
|
|
@ -1165,18 +1165,18 @@ pub fn normalize_and_test_predicates<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
|||
})
|
||||
}
|
||||
|
||||
pub fn langcall(bcx: Block,
|
||||
pub fn langcall(tcx: TyCtxt,
|
||||
span: Option<Span>,
|
||||
msg: &str,
|
||||
li: LangItem)
|
||||
-> DefId {
|
||||
match bcx.tcx().lang_items.require(li) {
|
||||
match tcx.lang_items.require(li) {
|
||||
Ok(id) => id,
|
||||
Err(s) => {
|
||||
let msg = format!("{} {}", msg, s);
|
||||
match span {
|
||||
Some(span) => bcx.tcx().sess.span_fatal(span, &msg[..]),
|
||||
None => bcx.tcx().sess.fatal(&msg[..]),
|
||||
Some(span) => tcx.sess.span_fatal(span, &msg[..]),
|
||||
None => tcx.sess.fatal(&msg[..]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -400,7 +400,7 @@ pub fn trans_fail<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
|
|||
let align = machine::llalign_of_min(ccx, val_ty(expr_file_line_const));
|
||||
let expr_file_line = consts::addr_of(ccx, expr_file_line_const, align, "panic_loc");
|
||||
let args = vec!(expr_file_line);
|
||||
let did = langcall(bcx, Some(call_info.span), "", PanicFnLangItem);
|
||||
let did = langcall(bcx.tcx(), Some(call_info.span), "", PanicFnLangItem);
|
||||
Callee::def(ccx, did, ccx.tcx().mk_substs(Substs::empty()))
|
||||
.call(bcx, call_info.debug_loc(), ArgVals(&args), None).bcx
|
||||
}
|
||||
|
@ -428,7 +428,7 @@ pub fn trans_fail_bounds_check<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
|
|||
let align = machine::llalign_of_min(ccx, val_ty(file_line_const));
|
||||
let file_line = consts::addr_of(ccx, file_line_const, align, "panic_bounds_check_loc");
|
||||
let args = vec!(file_line, index, len);
|
||||
let did = langcall(bcx, Some(call_info.span), "", PanicBoundsCheckFnLangItem);
|
||||
let did = langcall(bcx.tcx(), Some(call_info.span), "", PanicBoundsCheckFnLangItem);
|
||||
Callee::def(ccx, did, ccx.tcx().mk_substs(Substs::empty()))
|
||||
.call(bcx, call_info.debug_loc(), ArgVals(&args), None).bcx
|
||||
}
|
||||
|
|
|
@ -2230,11 +2230,11 @@ impl OverflowOpViaIntrinsic {
|
|||
binop_debug_loc);
|
||||
|
||||
let expect = bcx.ccx().get_intrinsic(&"llvm.expect.i1");
|
||||
Call(bcx, expect, &[cond, C_integral(Type::i1(bcx.ccx()), 0, false)],
|
||||
binop_debug_loc);
|
||||
let expected = Call(bcx, expect, &[cond, C_bool(bcx.ccx(), false)],
|
||||
binop_debug_loc);
|
||||
|
||||
let bcx =
|
||||
base::with_cond(bcx, cond, |bcx|
|
||||
base::with_cond(bcx, expected, |bcx|
|
||||
controlflow::trans_fail(bcx, info,
|
||||
InternedString::new("arithmetic operation overflowed")));
|
||||
|
||||
|
|
|
@ -53,7 +53,7 @@ pub fn trans_exchange_free_dyn<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
|
|||
-> Block<'blk, 'tcx> {
|
||||
let _icx = push_ctxt("trans_exchange_free");
|
||||
|
||||
let def_id = langcall(bcx, None, "", ExchangeFreeFnLangItem);
|
||||
let def_id = langcall(bcx.tcx(), None, "", ExchangeFreeFnLangItem);
|
||||
let args = [PointerCast(bcx, v, Type::i8p(bcx.ccx())), size, align];
|
||||
Callee::def(bcx.ccx(), def_id, bcx.tcx().mk_substs(Substs::empty()))
|
||||
.call(bcx, debug_loc, ArgVals(&args), None).bcx
|
||||
|
|
|
@ -161,6 +161,7 @@ pub fn cleanup_kinds<'bcx,'tcx>(_bcx: Block<'bcx,'tcx>,
|
|||
/* nothing to do */
|
||||
}
|
||||
TerminatorKind::Call { cleanup: unwind, .. } |
|
||||
TerminatorKind::Assert { cleanup: unwind, .. } |
|
||||
TerminatorKind::DropAndReplace { unwind, .. } |
|
||||
TerminatorKind::Drop { unwind, .. } => {
|
||||
if let Some(unwind) = unwind {
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
// except according to those terms.
|
||||
|
||||
use llvm::{self, ValueRef};
|
||||
use rustc::middle::lang_items;
|
||||
use rustc::ty;
|
||||
use rustc::mir::repr as mir;
|
||||
use abi::{Abi, FnType, ArgType};
|
||||
|
@ -16,7 +17,9 @@ use adt;
|
|||
use base;
|
||||
use build;
|
||||
use callee::{Callee, CalleeData, Fn, Intrinsic, NamedTupleConstructor, Virtual};
|
||||
use common::{self, type_is_fat_ptr, Block, BlockAndBuilder, LandingPad, C_undef};
|
||||
use common::{self, type_is_fat_ptr, Block, BlockAndBuilder, LandingPad};
|
||||
use common::{C_bool, C_str_slice, C_struct, C_u32, C_undef};
|
||||
use consts;
|
||||
use debuginfo::DebugLoc;
|
||||
use Disr;
|
||||
use machine::{llalign_of_min, llbitsize_of_real};
|
||||
|
@ -24,7 +27,9 @@ use meth;
|
|||
use type_of;
|
||||
use glue;
|
||||
use type_::Type;
|
||||
|
||||
use rustc_data_structures::fnv::FnvHashMap;
|
||||
use syntax::parse::token;
|
||||
|
||||
use super::{MirContext, TempRef};
|
||||
use super::analyze::CleanupKind;
|
||||
|
@ -212,6 +217,92 @@ impl<'bcx, 'tcx> MirContext<'bcx, 'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
mir::TerminatorKind::Assert { ref cond, expected, ref msg, target, cleanup } => {
|
||||
let cond = self.trans_operand(&bcx, cond).immediate();
|
||||
let const_cond = common::const_to_opt_uint(cond).map(|c| c == 1);
|
||||
|
||||
// Don't translate the panic block if success if known.
|
||||
let lltarget = self.llblock(target);
|
||||
if const_cond == Some(expected) {
|
||||
funclet_br(bcx, lltarget);
|
||||
return;
|
||||
}
|
||||
|
||||
if const_cond == Some(!expected) {
|
||||
// Do nothing to end up with an unconditional panic.
|
||||
} else {
|
||||
// Pass the condition through llvm.expect for branch hinting.
|
||||
let expect = bcx.ccx().get_intrinsic(&"llvm.expect.i1");
|
||||
let cond = bcx.call(expect, &[cond, C_bool(bcx.ccx(), expected)], None);
|
||||
|
||||
// Create the failure block and the conditional branch to it.
|
||||
// After this point, bcx is the block for the call to panic.
|
||||
let panic_block = self.fcx.new_block("panic", None);
|
||||
if expected {
|
||||
bcx.cond_br(cond, lltarget, panic_block.llbb);
|
||||
} else {
|
||||
bcx.cond_br(cond, panic_block.llbb, lltarget);
|
||||
}
|
||||
bcx = panic_block.build();
|
||||
}
|
||||
|
||||
// Get the location information.
|
||||
let loc = bcx.sess().codemap().lookup_char_pos(terminator.span.lo);
|
||||
let filename = token::intern_and_get_ident(&loc.file.name);
|
||||
let filename = C_str_slice(bcx.ccx(), filename);
|
||||
let line = C_u32(bcx.ccx(), loc.line as u32);
|
||||
|
||||
// Put together the arguments to the panic entry point.
|
||||
let (lang_item, args) = match *msg {
|
||||
mir::AssertMessage::BoundsCheck { ref len, ref index } => {
|
||||
let len = self.trans_operand(&mut bcx, len);
|
||||
let index = self.trans_operand(&mut bcx, index);
|
||||
|
||||
let file_line = C_struct(bcx.ccx(), &[filename, line], false);
|
||||
let align = llalign_of_min(bcx.ccx(), common::val_ty(file_line));
|
||||
let file_line = consts::addr_of(bcx.ccx(),
|
||||
file_line,
|
||||
align,
|
||||
"panic_bounds_check_loc");
|
||||
(lang_items::PanicBoundsCheckFnLangItem,
|
||||
vec![file_line, index.immediate(), len.immediate()])
|
||||
}
|
||||
mir::AssertMessage::Math(ref err) => {
|
||||
let msg_str = token::intern_and_get_ident(err.description());
|
||||
let msg_str = C_str_slice(bcx.ccx(), msg_str);
|
||||
let msg_file_line = C_struct(bcx.ccx(),
|
||||
&[msg_str, filename, line],
|
||||
false);
|
||||
let align = llalign_of_min(bcx.ccx(), common::val_ty(msg_file_line));
|
||||
let msg_file_line = consts::addr_of(bcx.ccx(),
|
||||
msg_file_line,
|
||||
align,
|
||||
"panic_loc");
|
||||
(lang_items::PanicFnLangItem, vec![msg_file_line])
|
||||
}
|
||||
};
|
||||
|
||||
// Obtain the panic entry point.
|
||||
let def_id = common::langcall(bcx.tcx(), Some(terminator.span), "", lang_item);
|
||||
let callee = Callee::def(bcx.ccx(), def_id,
|
||||
bcx.ccx().empty_substs_for_def_id(def_id));
|
||||
let llfn = callee.reify(bcx.ccx()).val;
|
||||
|
||||
// Translate the actual panic invoke/call.
|
||||
if let Some(unwind) = cleanup {
|
||||
let uwbcx = self.bcx(unwind);
|
||||
let unwind = self.make_landing_pad(uwbcx);
|
||||
bcx.invoke(llfn,
|
||||
&args,
|
||||
self.unreachable_block().llbb,
|
||||
unwind.llbb(),
|
||||
cleanup_bundle.as_ref());
|
||||
} else {
|
||||
bcx.call(llfn, &args, cleanup_bundle.as_ref());
|
||||
bcx.unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
mir::TerminatorKind::DropAndReplace { .. } => {
|
||||
bug!("undesugared DropAndReplace in trans: {:?}", data);
|
||||
}
|
||||
|
|
|
@ -287,14 +287,21 @@ impl<'a, 'tcx> MirConstContext<'a, 'tcx> {
|
|||
}))
|
||||
}
|
||||
|
||||
// This is only supported to make bounds checking work.
|
||||
mir::TerminatorKind::If { ref cond, targets: (true_bb, false_bb) } => {
|
||||
mir::TerminatorKind::Assert { ref cond, expected, ref msg, target, .. } => {
|
||||
let cond = self.const_operand(cond, span)?;
|
||||
if common::const_to_uint(cond.llval) != 0 {
|
||||
true_bb
|
||||
} else {
|
||||
false_bb
|
||||
let cond_bool = common::const_to_uint(cond.llval) != 0;
|
||||
if cond_bool != expected {
|
||||
let err = match *msg {
|
||||
mir::AssertMessage::BoundsCheck {..} => {
|
||||
ErrKind::IndexOutOfBounds
|
||||
}
|
||||
mir::AssertMessage::Math(ref err) => {
|
||||
ErrKind::Math(err.clone())
|
||||
}
|
||||
};
|
||||
consts::const_err(self.ccx, span, Err(err), TrueConst::Yes)?;
|
||||
}
|
||||
target
|
||||
}
|
||||
|
||||
mir::TerminatorKind::Call { ref func, ref args, ref destination, .. } => {
|
||||
|
@ -308,13 +315,6 @@ impl<'a, 'tcx> MirConstContext<'a, 'tcx> {
|
|||
func, fn_ty)
|
||||
};
|
||||
|
||||
// Indexing OOB doesn't call a const fn, handle it.
|
||||
if Some(instance.def) == tcx.lang_items.panic_bounds_check_fn() {
|
||||
consts::const_err(self.ccx, span,
|
||||
Err(ErrKind::IndexOutOfBounds),
|
||||
TrueConst::Yes)?;
|
||||
}
|
||||
|
||||
let args = args.iter().map(|arg| {
|
||||
self.const_operand(arg, span)
|
||||
}).collect::<Result<Vec<_>, _>>()?;
|
||||
|
|
|
@ -17,13 +17,13 @@ use asm;
|
|||
use base;
|
||||
use callee::Callee;
|
||||
use common::{self, val_ty,
|
||||
C_null,
|
||||
C_uint, C_undef, C_u8, BlockAndBuilder, Result};
|
||||
C_null, C_uint, C_undef, BlockAndBuilder, Result};
|
||||
use datum::{Datum, Lvalue};
|
||||
use debuginfo::DebugLoc;
|
||||
use adt;
|
||||
use machine;
|
||||
use type_of;
|
||||
use type_::Type;
|
||||
use tvec;
|
||||
use value::Value;
|
||||
use Disr;
|
||||
|
@ -611,9 +611,7 @@ impl<'bcx, 'tcx> MirContext<'bcx, 'tcx> {
|
|||
(val, bcx.zext(of, Type::bool(bcx.ccx())))
|
||||
}
|
||||
_ => {
|
||||
// Fall back to regular translation with a constant-false overflow flag
|
||||
(self.trans_scalar_binop(bcx, op, lhs, rhs, input_ty),
|
||||
C_u8(bcx.ccx(), 0))
|
||||
bug!("Operator `{:?}` is not a checkable operator", op)
|
||||
}
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue