Skip to content

Commit 156b1fb

Browse files
committed
Add a new Assert terminator to MIR for bounds & arithmetic checks.
1 parent 7fbff36 commit 156b1fb

File tree

22 files changed

+305
-196
lines changed

22 files changed

+305
-196
lines changed

src/librustc/mir/repr.rs

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
use graphviz::IntoCow;
1212
use middle::const_val::ConstVal;
13-
use rustc_const_math::{ConstUsize, ConstInt};
13+
use rustc_const_math::{ConstUsize, ConstInt, ConstMathErr};
1414
use hir::def_id::DefId;
1515
use ty::subst::Substs;
1616
use ty::{self, AdtDef, ClosureSubsts, FnOutput, Region, Ty};
@@ -354,6 +354,16 @@ pub enum TerminatorKind<'tcx> {
354354
/// Cleanups to be done if the call unwinds.
355355
cleanup: Option<BasicBlock>
356356
},
357+
358+
/// Jump to the target if the condition has the expected value,
359+
/// otherwise panic with a message and a cleanup target.
360+
Assert {
361+
cond: Operand<'tcx>,
362+
expected: bool,
363+
msg: AssertMessage<'tcx>,
364+
target: BasicBlock,
365+
cleanup: Option<BasicBlock>
366+
}
357367
}
358368

359369
impl<'tcx> Terminator<'tcx> {
@@ -389,6 +399,8 @@ impl<'tcx> TerminatorKind<'tcx> {
389399
Drop { ref target, unwind: None, .. } => {
390400
slice::ref_slice(target).into_cow()
391401
}
402+
Assert { target, cleanup: Some(unwind), .. } => vec![target, unwind].into_cow(),
403+
Assert { ref target, .. } => slice::ref_slice(target).into_cow(),
392404
}
393405
}
394406

@@ -413,6 +425,8 @@ impl<'tcx> TerminatorKind<'tcx> {
413425
Drop { ref mut target, unwind: None, .. } => {
414426
vec![target]
415427
}
428+
Assert { ref mut target, cleanup: Some(ref mut unwind), .. } => vec![target, unwind],
429+
Assert { ref mut target, .. } => vec![target]
416430
}
417431
}
418432
}
@@ -495,6 +509,26 @@ impl<'tcx> TerminatorKind<'tcx> {
495509
}
496510
write!(fmt, ")")
497511
}
512+
Assert { ref cond, expected, ref msg, .. } => {
513+
write!(fmt, "assert(")?;
514+
if !expected {
515+
write!(fmt, "!")?;
516+
}
517+
write!(fmt, "{:?}, ", cond)?;
518+
519+
match *msg {
520+
AssertMessage::BoundsCheck { ref len, ref index } => {
521+
write!(fmt, "{:?}, {:?}, {:?}",
522+
"index out of bounds: the len is {} but the index is {}",
523+
len, index)?;
524+
}
525+
AssertMessage::Math(ref err) => {
526+
write!(fmt, "{:?}", err.description())?;
527+
}
528+
}
529+
530+
write!(fmt, ")")
531+
}
498532
}
499533
}
500534

@@ -532,10 +566,21 @@ impl<'tcx> TerminatorKind<'tcx> {
532566
Drop { unwind: Some(_), .. } => {
533567
vec!["return".into_cow(), "unwind".into_cow()]
534568
}
569+
Assert { cleanup: None, .. } => vec!["".into()],
570+
Assert { .. } =>
571+
vec!["success".into_cow(), "unwind".into_cow()]
535572
}
536573
}
537574
}
538575

576+
#[derive(Clone, Debug, RustcEncodable, RustcDecodable)]
577+
pub enum AssertMessage<'tcx> {
578+
BoundsCheck {
579+
len: Operand<'tcx>,
580+
index: Operand<'tcx>
581+
},
582+
Math(ConstMathErr)
583+
}
539584

540585
///////////////////////////////////////////////////////////////////////////
541586
// Statements

src/librustc/mir/visit.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,11 @@ macro_rules! make_mir_visitor {
127127
self.super_terminator_kind(block, kind);
128128
}
129129

130+
fn visit_assert_message(&mut self,
131+
msg: & $($mutability)* AssertMessage<'tcx>) {
132+
self.super_assert_message(msg);
133+
}
134+
130135
fn visit_rvalue(&mut self,
131136
rvalue: & $($mutability)* Rvalue<'tcx>) {
132137
self.super_rvalue(rvalue);
@@ -426,6 +431,31 @@ macro_rules! make_mir_visitor {
426431
}
427432
cleanup.map(|t| self.visit_branch(block, t));
428433
}
434+
435+
TerminatorKind::Assert { ref $($mutability)* cond,
436+
expected: _,
437+
ref $($mutability)* msg,
438+
target,
439+
cleanup } => {
440+
self.visit_operand(cond);
441+
self.visit_assert_message(msg);
442+
self.visit_branch(block, target);
443+
cleanup.map(|t| self.visit_branch(block, t));
444+
}
445+
}
446+
}
447+
448+
fn super_assert_message(&mut self,
449+
msg: & $($mutability)* AssertMessage<'tcx>) {
450+
match *msg {
451+
AssertMessage::BoundsCheck {
452+
ref $($mutability)* len,
453+
ref $($mutability)* index
454+
} => {
455+
self.visit_operand(len);
456+
self.visit_operand(index);
457+
}
458+
AssertMessage::Math(_) => {}
429459
}
430460
}
431461

src/librustc_borrowck/borrowck/mir/dataflow/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -450,13 +450,14 @@ impl<'a, 'tcx: 'a, D> DataflowAnalysis<'a, 'tcx, D>
450450
repr::TerminatorKind::Return |
451451
repr::TerminatorKind::Resume => {}
452452
repr::TerminatorKind::Goto { ref target } |
453+
repr::TerminatorKind::Assert { ref target, cleanup: None, .. } |
453454
repr::TerminatorKind::Drop { ref target, location: _, unwind: None } |
454-
455455
repr::TerminatorKind::DropAndReplace {
456456
ref target, value: _, location: _, unwind: None
457457
} => {
458458
self.propagate_bits_into_entry_set_for(in_out, changed, target);
459459
}
460+
repr::TerminatorKind::Assert { ref target, cleanup: Some(ref unwind), .. } |
460461
repr::TerminatorKind::Drop { ref target, location: _, unwind: Some(ref unwind) } |
461462
repr::TerminatorKind::DropAndReplace {
462463
ref target, value: _, location: _, unwind: Some(ref unwind)

src/librustc_borrowck/borrowck/mir/gather_moves.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,22 @@ fn gather_moves<'a, 'tcx>(mir: &Mir<'tcx>, tcx: TyCtxt<'a, 'tcx, 'tcx>) -> MoveD
663663
bb_ctxt.on_operand(SK::If, cond, source);
664664
}
665665

666+
TerminatorKind::Assert {
667+
ref cond, expected: _,
668+
ref msg, target: _, cleanup: _
669+
} => {
670+
// The `cond` is always of (copyable) type `bool`,
671+
// so there will never be anything to move.
672+
let _ = cond;
673+
match *msg {
674+
AssertMessage:: BoundsCheck { ref len, ref index } => {
675+
// Same for the usize length and index in bounds-checking.
676+
let _ = (len, index);
677+
}
678+
AssertMessage::Math(_) => {}
679+
}
680+
}
681+
666682
TerminatorKind::SwitchInt { switch_ty: _, values: _, targets: _, ref discr } |
667683
TerminatorKind::Switch { adt_def: _, targets: _, ref discr } => {
668684
// The `discr` is not consumed; that is instead

src/librustc_const_math/err.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
use syntax::ast;
1212

13-
#[derive(Debug, PartialEq, Eq, Clone)]
13+
#[derive(Debug, PartialEq, Eq, Clone, RustcEncodable, RustcDecodable)]
1414
pub enum ConstMathErr {
1515
NotInRange,
1616
CmpBetweenUnequalTypes,
@@ -25,7 +25,7 @@ pub enum ConstMathErr {
2525
}
2626
pub use self::ConstMathErr::*;
2727

28-
#[derive(Debug, PartialEq, Eq, Clone)]
28+
#[derive(Debug, PartialEq, Eq, Clone, RustcEncodable, RustcDecodable)]
2929
pub enum Op {
3030
Add,
3131
Sub,

src/librustc_mir/build/block.rs

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ use build::{BlockAnd, BlockAndExtension, Builder};
1212
use hair::*;
1313
use rustc::mir::repr::*;
1414
use rustc::hir;
15-
use syntax::codemap::Span;
1615

1716
impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
1817
pub fn ast_block(&mut self,
@@ -82,22 +81,4 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
8281
block.unit()
8382
})
8483
}
85-
86-
// Helper method for generating a conditional branch
87-
// Returns (TrueBlock, FalseBlock)
88-
pub fn build_cond_br(&mut self, block: BasicBlock, span: Span,
89-
cond: Operand<'tcx>) -> (BasicBlock, BasicBlock) {
90-
let scope_id = self.innermost_scope_id();
91-
92-
let then_block = self.cfg.start_new_block();
93-
let else_block = self.cfg.start_new_block();
94-
95-
self.cfg.terminate(block, scope_id, span,
96-
TerminatorKind::If {
97-
cond: cond,
98-
targets: (then_block, else_block)
99-
});
100-
101-
(then_block, else_block)
102-
}
10384
}

src/librustc_mir/build/expr/as_lvalue.rs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,12 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
6666
idx.clone(),
6767
Operand::Consume(len.clone())));
6868

69-
let (success, failure) = (this.cfg.start_new_block(), this.cfg.start_new_block());
70-
this.cfg.terminate(block,
71-
scope_id,
72-
expr_span,
73-
TerminatorKind::If {
74-
cond: Operand::Consume(lt),
75-
targets: (success, failure),
76-
});
77-
this.panic_bounds_check(failure, idx.clone(), Operand::Consume(len), expr_span);
69+
let msg = AssertMessage::BoundsCheck {
70+
len: Operand::Consume(len),
71+
index: idx.clone()
72+
};
73+
let success = this.assert(block, Operand::Consume(lt), true,
74+
msg, expr_span);
7875
success.and(slice.index(idx))
7976
}
8077
ExprKind::SelfRef => {

src/librustc_mir/build/expr/as_rvalue.rs

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
1313
use std;
1414

15+
use rustc_const_math::{ConstMathErr, Op};
1516
use rustc_data_structures::fnv::FnvHashMap;
1617

1718
use build::{BlockAnd, BlockAndExtension, Builder};
@@ -88,10 +89,9 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
8889
this.cfg.push_assign(block, scope_id, expr_span, &is_min,
8990
Rvalue::BinaryOp(BinOp::Eq, arg.clone(), minval));
9091

91-
let (of_block, ok_block) = this.build_cond_br(block, expr_span,
92-
Operand::Consume(is_min));
93-
this.panic(of_block, "attempted to negate with overflow", expr_span);
94-
block = ok_block;
92+
let err = ConstMathErr::Overflow(Op::Neg);
93+
block = this.assert(block, Operand::Consume(is_min), false,
94+
AssertMessage::Math(err), expr_span);
9595
}
9696
block.and(Rvalue::UnaryOp(op, arg))
9797
}
@@ -261,27 +261,32 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
261261
let val = result_value.clone().field(val_fld, ty);
262262
let of = result_value.field(of_fld, bool_ty);
263263

264-
let msg = if op == BinOp::Shl || op == BinOp::Shr {
265-
"shift operation overflowed"
266-
} else {
267-
"arithmetic operation overflowed"
268-
};
264+
let err = ConstMathErr::Overflow(match op {
265+
BinOp::Add => Op::Add,
266+
BinOp::Sub => Op::Sub,
267+
BinOp::Mul => Op::Mul,
268+
BinOp::Shl => Op::Shl,
269+
BinOp::Shr => Op::Shr,
270+
_ => {
271+
bug!("MIR build_binary_op: {:?} is not checkable", op)
272+
}
273+
});
269274

270-
let (of_block, ok_block) = self.build_cond_br(block, span, Operand::Consume(of));
271-
self.panic(of_block, msg, span);
275+
block = self.assert(block, Operand::Consume(of), false,
276+
AssertMessage::Math(err), span);
272277

273-
ok_block.and(Rvalue::Use(Operand::Consume(val)))
278+
block.and(Rvalue::Use(Operand::Consume(val)))
274279
} else {
275280
if ty.is_integral() && (op == BinOp::Div || op == BinOp::Rem) {
276281
// Checking division and remainder is more complex, since we 1. always check
277282
// and 2. there are two possible failure cases, divide-by-zero and overflow.
278283

279-
let (zero_msg, overflow_msg) = if op == BinOp::Div {
280-
("attempted to divide by zero",
281-
"attempted to divide with overflow")
284+
let (zero_err, overflow_err) = if op == BinOp::Div {
285+
(ConstMathErr::DivisionByZero,
286+
ConstMathErr::Overflow(Op::Div))
282287
} else {
283-
("attempted remainder with a divisor of zero",
284-
"attempted remainder with overflow")
288+
(ConstMathErr::RemainderByZero,
289+
ConstMathErr::Overflow(Op::Rem))
285290
};
286291

287292
// Check for / 0
@@ -290,11 +295,8 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
290295
self.cfg.push_assign(block, scope_id, span, &is_zero,
291296
Rvalue::BinaryOp(BinOp::Eq, rhs.clone(), zero));
292297

293-
let (zero_block, ok_block) = self.build_cond_br(block, span,
294-
Operand::Consume(is_zero));
295-
self.panic(zero_block, zero_msg, span);
296-
297-
block = ok_block;
298+
block = self.assert(block, Operand::Consume(is_zero), false,
299+
AssertMessage::Math(zero_err), span);
298300

299301
// We only need to check for the overflow in one case:
300302
// MIN / -1, and only for signed values.
@@ -318,11 +320,8 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
318320
self.cfg.push_assign(block, scope_id, span, &of,
319321
Rvalue::BinaryOp(BinOp::BitAnd, is_neg_1, is_min));
320322

321-
let (of_block, ok_block) = self.build_cond_br(block, span,
322-
Operand::Consume(of));
323-
self.panic(of_block, overflow_msg, span);
324-
325-
block = ok_block;
323+
block = self.assert(block, Operand::Consume(of), false,
324+
AssertMessage::Math(overflow_err), span);
326325
}
327326
}
328327

0 commit comments

Comments
 (0)