Skip to content

Commit 73f3054

Browse files
Aatcheddyb
authored andcommitted
Check arithmetic in the MIR
Add, Sub, Mul, Shl, and Shr are checked using a new Rvalue: CheckedBinaryOp, while Div, Rem and Neg are handled with explicit checks in the MIR.
1 parent f97c411 commit 73f3054

File tree

10 files changed

+428
-9
lines changed

10 files changed

+428
-9
lines changed

src/librustc/mir/repr.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -787,6 +787,7 @@ pub enum Rvalue<'tcx> {
787787
Cast(CastKind, Operand<'tcx>, Ty<'tcx>),
788788

789789
BinaryOp(BinOp, Operand<'tcx>, Operand<'tcx>),
790+
CheckedBinaryOp(BinOp, Operand<'tcx>, Operand<'tcx>),
790791

791792
UnaryOp(UnOp, Operand<'tcx>),
792793

@@ -880,6 +881,16 @@ pub enum BinOp {
880881
Gt,
881882
}
882883

884+
impl BinOp {
885+
pub fn is_checkable(self) -> bool {
886+
use self::BinOp::*;
887+
match self {
888+
Add | Sub | Mul | Shl | Shr => true,
889+
_ => false
890+
}
891+
}
892+
}
893+
883894
#[derive(Copy, Clone, Debug, PartialEq, Eq, RustcEncodable, RustcDecodable)]
884895
pub enum UnOp {
885896
/// The `!` operator for logical inversion
@@ -898,6 +909,9 @@ impl<'tcx> Debug for Rvalue<'tcx> {
898909
Len(ref a) => write!(fmt, "Len({:?})", a),
899910
Cast(ref kind, ref lv, ref ty) => write!(fmt, "{:?} as {:?} ({:?})", lv, ty, kind),
900911
BinaryOp(ref op, ref a, ref b) => write!(fmt, "{:?}({:?}, {:?})", op, a, b),
912+
CheckedBinaryOp(ref op, ref a, ref b) => {
913+
write!(fmt, "Checked{:?}({:?}, {:?})", op, a, b)
914+
}
901915
UnaryOp(ref op, ref a) => write!(fmt, "{:?}({:?})", op, a),
902916
Box(ref t) => write!(fmt, "Box({:?})", t),
903917
InlineAsm { ref asm, ref outputs, ref inputs } => {

src/librustc/mir/tcx.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,13 @@ impl<'a, 'gcx, 'tcx> Mir<'tcx> {
183183
let rhs_ty = self.operand_ty(tcx, rhs);
184184
Some(self.binop_ty(tcx, op, lhs_ty, rhs_ty))
185185
}
186+
Rvalue::CheckedBinaryOp(op, ref lhs, ref rhs) => {
187+
let lhs_ty = self.operand_ty(tcx, lhs);
188+
let rhs_ty = self.operand_ty(tcx, rhs);
189+
let ty = self.binop_ty(tcx, op, lhs_ty, rhs_ty);
190+
let ty = tcx.mk_tup(vec![ty, tcx.types.bool]);
191+
Some(ty)
192+
}
186193
Rvalue::UnaryOp(_, ref operand) => {
187194
Some(self.operand_ty(tcx, operand))
188195
}

src/librustc/mir/visit.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,9 @@ macro_rules! make_mir_visitor {
461461
}
462462

463463
Rvalue::BinaryOp(_bin_op,
464+
ref $($mutability)* lhs,
465+
ref $($mutability)* rhs) |
466+
Rvalue::CheckedBinaryOp(_bin_op,
464467
ref $($mutability)* lhs,
465468
ref $($mutability)* rhs) => {
466469
self.visit_operand(lhs);

src/librustc_borrowck/borrowck/mir/gather_moves.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,8 @@ fn gather_moves<'a, 'tcx>(mir: &Mir<'tcx>, tcx: TyCtxt<'a, 'tcx, 'tcx>) -> MoveD
595595
bb_ctxt.on_operand(SK::Repeat, operand, source),
596596
Rvalue::Cast(ref _kind, ref operand, ref _ty) =>
597597
bb_ctxt.on_operand(SK::Cast, operand, source),
598-
Rvalue::BinaryOp(ref _binop, ref operand1, ref operand2) => {
598+
Rvalue::BinaryOp(ref _binop, ref operand1, ref operand2) |
599+
Rvalue::CheckedBinaryOp(ref _binop, ref operand1, ref operand2) => {
599600
bb_ctxt.on_operand(SK::BinaryOp, operand1, source);
600601
bb_ctxt.on_operand(SK::BinaryOp, operand2, source);
601602
}

src/librustc_mir/build/expr/as_rvalue.rs

Lines changed: 194 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,19 @@
1010

1111
//! See docs in build/expr/mod.rs
1212
13+
use std;
14+
1315
use rustc_data_structures::fnv::FnvHashMap;
1416

1517
use build::{BlockAnd, BlockAndExtension, Builder};
1618
use build::expr::category::{Category, RvalueFunc};
1719
use hair::*;
20+
use rustc_const_math::{ConstInt, ConstIsize};
21+
use rustc::middle::const_val::ConstVal;
22+
use rustc::ty;
1823
use rustc::mir::repr::*;
24+
use syntax::ast;
25+
use syntax::codemap::Span;
1926

2027
impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
2128
/// Compile `expr`, yielding an rvalue.
@@ -66,10 +73,34 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
6673
ExprKind::Binary { op, lhs, rhs } => {
6774
let lhs = unpack!(block = this.as_operand(block, lhs));
6875
let rhs = unpack!(block = this.as_operand(block, rhs));
69-
block.and(Rvalue::BinaryOp(op, lhs, rhs))
76+
this.build_binary_op(block, op, expr_span, expr.ty,
77+
lhs, rhs)
7078
}
7179
ExprKind::Unary { op, arg } => {
7280
let arg = unpack!(block = this.as_operand(block, arg));
81+
// Check for -MIN on signed integers
82+
if op == UnOp::Neg && this.check_overflow() && expr.ty.is_signed() {
83+
let bool_ty = this.hir.bool_ty();
84+
85+
let minval = this.minval_literal(expr_span, expr.ty);
86+
let is_min = this.temp(bool_ty);
87+
88+
this.cfg.push_assign(block, scope_id, expr_span, &is_min,
89+
Rvalue::BinaryOp(BinOp::Eq, arg.clone(), minval));
90+
91+
let of_block = this.cfg.start_new_block();
92+
let ok_block = this.cfg.start_new_block();
93+
94+
this.cfg.terminate(block, scope_id, expr_span,
95+
TerminatorKind::If {
96+
cond: Operand::Consume(is_min),
97+
targets: (of_block, ok_block)
98+
});
99+
100+
this.panic(of_block, "attempted to negate with overflow", expr_span);
101+
102+
block = ok_block;
103+
}
73104
block.and(Rvalue::UnaryOp(op, arg))
74105
}
75106
ExprKind::Box { value, value_extents } => {
@@ -218,4 +249,166 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
218249
}
219250
}
220251
}
252+
253+
pub fn build_binary_op(&mut self, mut block: BasicBlock, op: BinOp, span: Span, ty: ty::Ty<'tcx>,
254+
lhs: Operand<'tcx>, rhs: Operand<'tcx>) -> BlockAnd<Rvalue<'tcx>> {
255+
let scope_id = self.innermost_scope_id();
256+
let bool_ty = self.hir.bool_ty();
257+
if self.check_overflow() && op.is_checkable() && ty.is_integral() {
258+
let result_tup = self.hir.tcx().mk_tup(vec![ty, bool_ty]);
259+
let result_value = self.temp(result_tup);
260+
261+
self.cfg.push_assign(block, scope_id, span,
262+
&result_value, Rvalue::CheckedBinaryOp(op,
263+
lhs,
264+
rhs));
265+
let val_fld = Field::new(0);
266+
let of_fld = Field::new(1);
267+
268+
let val = result_value.clone().field(val_fld, ty);
269+
let of = result_value.field(of_fld, bool_ty);
270+
271+
let success = self.cfg.start_new_block();
272+
let failure = self.cfg.start_new_block();
273+
274+
self.cfg.terminate(block, scope_id, span,
275+
TerminatorKind::If {
276+
cond: Operand::Consume(of),
277+
targets: (failure, success)
278+
});
279+
let msg = if op == BinOp::Shl || op == BinOp::Shr {
280+
"shift operation overflowed"
281+
} else {
282+
"arithmetic operation overflowed"
283+
};
284+
self.panic(failure, msg, span);
285+
success.and(Rvalue::Use(Operand::Consume(val)))
286+
} else {
287+
if ty.is_integral() && (op == BinOp::Div || op == BinOp::Rem) {
288+
// Checking division and remainder is more complex, since we 1. always check
289+
// and 2. there are two possible failure cases, divide-by-zero and overflow.
290+
291+
let (zero_msg, overflow_msg) = if op == BinOp::Div {
292+
("attempted to divide by zero",
293+
"attempted to divide with overflow")
294+
} else {
295+
("attempted remainder with a divisor of zero",
296+
"attempted remainder with overflow")
297+
};
298+
299+
// Check for / 0
300+
let is_zero = self.temp(bool_ty);
301+
let zero = self.zero_literal(span, ty);
302+
self.cfg.push_assign(block, scope_id, span, &is_zero,
303+
Rvalue::BinaryOp(BinOp::Eq, rhs.clone(), zero));
304+
305+
let zero_block = self.cfg.start_new_block();
306+
let ok_block = self.cfg.start_new_block();
307+
308+
self.cfg.terminate(block, scope_id, span,
309+
TerminatorKind::If {
310+
cond: Operand::Consume(is_zero),
311+
targets: (zero_block, ok_block)
312+
});
313+
314+
self.panic(zero_block, zero_msg, span);
315+
block = ok_block;
316+
317+
// We only need to check for the overflow in one case:
318+
// MIN / -1, and only for signed values.
319+
if ty.is_signed() {
320+
let neg_1 = self.neg_1_literal(span, ty);
321+
let min = self.minval_literal(span, ty);
322+
323+
let is_neg_1 = self.temp(bool_ty);
324+
let is_min = self.temp(bool_ty);
325+
let of = self.temp(bool_ty);
326+
327+
// this does (rhs == -1) & (lhs == MIN). It could short-circuit instead
328+
329+
self.cfg.push_assign(block, scope_id, span, &is_neg_1,
330+
Rvalue::BinaryOp(BinOp::Eq, rhs.clone(), neg_1));
331+
self.cfg.push_assign(block, scope_id, span, &is_min,
332+
Rvalue::BinaryOp(BinOp::Eq, lhs.clone(), min));
333+
334+
let is_neg_1 = Operand::Consume(is_neg_1);
335+
let is_min = Operand::Consume(is_min);
336+
self.cfg.push_assign(block, scope_id, span, &of,
337+
Rvalue::BinaryOp(BinOp::BitAnd, is_neg_1, is_min));
338+
339+
let of_block = self.cfg.start_new_block();
340+
let ok_block = self.cfg.start_new_block();
341+
342+
self.cfg.terminate(block, scope_id, span,
343+
TerminatorKind::If {
344+
cond: Operand::Consume(of),
345+
targets: (of_block, ok_block)
346+
});
347+
348+
self.panic(of_block, overflow_msg, span);
349+
350+
block = ok_block;
351+
}
352+
}
353+
354+
block.and(Rvalue::BinaryOp(op, lhs, rhs))
355+
}
356+
}
357+
358+
// Helper to get a `-1` value of the appropriate type
359+
fn neg_1_literal(&mut self, span: Span, ty: ty::Ty<'tcx>) -> Operand<'tcx> {
360+
let literal = match ty.sty {
361+
ty::TyInt(ity) => {
362+
let val = match ity {
363+
ast::IntTy::I8 => ConstInt::I8(-1),
364+
ast::IntTy::I16 => ConstInt::I16(-1),
365+
ast::IntTy::I32 => ConstInt::I32(-1),
366+
ast::IntTy::I64 => ConstInt::I64(-1),
367+
ast::IntTy::Is => {
368+
let int_ty = self.hir.tcx().sess.target.int_type;
369+
let val = ConstIsize::new(-1, int_ty).unwrap();
370+
ConstInt::Isize(val)
371+
}
372+
};
373+
374+
Literal::Value { value: ConstVal::Integral(val) }
375+
}
376+
_ => {
377+
span_bug!(span, "Invalid type for neg_1_literal: `{:?}`", ty)
378+
}
379+
};
380+
381+
self.literal_operand(span, ty, literal)
382+
}
383+
384+
// Helper to get the minimum value of the appropriate type
385+
fn minval_literal(&mut self, span: Span, ty: ty::Ty<'tcx>) -> Operand<'tcx> {
386+
let literal = match ty.sty {
387+
ty::TyInt(ity) => {
388+
let val = match ity {
389+
ast::IntTy::I8 => ConstInt::I8(std::i8::MIN),
390+
ast::IntTy::I16 => ConstInt::I16(std::i16::MIN),
391+
ast::IntTy::I32 => ConstInt::I32(std::i32::MIN),
392+
ast::IntTy::I64 => ConstInt::I64(std::i64::MIN),
393+
ast::IntTy::Is => {
394+
let int_ty = self.hir.tcx().sess.target.int_type;
395+
let min = match int_ty {
396+
ast::IntTy::I32 => std::i32::MIN as i64,
397+
ast::IntTy::I64 => std::i64::MIN,
398+
_ => unreachable!()
399+
};
400+
let val = ConstIsize::new(min, int_ty).unwrap();
401+
ConstInt::Isize(val)
402+
}
403+
};
404+
405+
Literal::Value { value: ConstVal::Integral(val) }
406+
}
407+
_ => {
408+
span_bug!(span, "Invalid type for minval_literal: `{:?}`", ty)
409+
}
410+
};
411+
412+
self.literal_operand(span, ty, literal)
413+
}
221414
}

src/librustc_mir/build/expr/stmt.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,17 +63,19 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
6363
// only affects weird things like `x += {x += 1; x}`
6464
// -- is that equal to `x + (x + 1)` or `2*(x+1)`?
6565

66+
let lhs = this.hir.mirror(lhs);
67+
let lhs_ty = lhs.ty;
68+
6669
// As above, RTL.
6770
let rhs = unpack!(block = this.as_operand(block, rhs));
6871
let lhs = unpack!(block = this.as_lvalue(block, lhs));
6972

7073
// we don't have to drop prior contents or anything
7174
// because AssignOp is only legal for Copy types
7275
// (overloaded ops should be desugared into a call).
73-
this.cfg.push_assign(block, scope_id, expr_span, &lhs,
74-
Rvalue::BinaryOp(op,
75-
Operand::Consume(lhs.clone()),
76-
rhs));
76+
let result = unpack!(block = this.build_binary_op(block, op, expr_span, lhs_ty,
77+
Operand::Consume(lhs.clone()), rhs));
78+
this.cfg.push_assign(block, scope_id, expr_span, &lhs, result);
7779

7880
block.unit()
7981
}

src/librustc_mir/build/misc.rs

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,14 @@
1212
//! kind of thing.
1313
1414
use build::Builder;
15-
use rustc::ty::Ty;
15+
16+
use rustc_const_math::{ConstInt, ConstUsize, ConstIsize};
17+
use rustc::middle::const_val::ConstVal;
18+
use rustc::ty::{self, Ty};
19+
1620
use rustc::mir::repr::*;
1721
use std::u32;
22+
use syntax::ast;
1823
use syntax::codemap::Span;
1924

2025
impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
@@ -50,6 +55,53 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
5055
Rvalue::Aggregate(AggregateKind::Tuple, vec![])
5156
}
5257

58+
// Returns a zero literal operand for the appropriate type, works for
59+
// bool, char, integers and floats.
60+
pub fn zero_literal(&mut self, span: Span, ty: Ty<'tcx>) -> Operand<'tcx> {
61+
let literal = match ty.sty {
62+
ty::TyBool => {
63+
self.hir.false_literal()
64+
}
65+
ty::TyChar => Literal::Value { value: ConstVal::Char('\0') },
66+
ty::TyUint(ity) => {
67+
let val = match ity {
68+
ast::UintTy::U8 => ConstInt::U8(0),
69+
ast::UintTy::U16 => ConstInt::U16(0),
70+
ast::UintTy::U32 => ConstInt::U32(0),
71+
ast::UintTy::U64 => ConstInt::U64(0),
72+
ast::UintTy::Us => {
73+
let uint_ty = self.hir.tcx().sess.target.uint_type;
74+
let val = ConstUsize::new(0, uint_ty).unwrap();
75+
ConstInt::Usize(val)
76+
}
77+
};
78+
79+
Literal::Value { value: ConstVal::Integral(val) }
80+
}
81+
ty::TyInt(ity) => {
82+
let val = match ity {
83+
ast::IntTy::I8 => ConstInt::I8(0),
84+
ast::IntTy::I16 => ConstInt::I16(0),
85+
ast::IntTy::I32 => ConstInt::I32(0),
86+
ast::IntTy::I64 => ConstInt::I64(0),
87+
ast::IntTy::Is => {
88+
let int_ty = self.hir.tcx().sess.target.int_type;
89+
let val = ConstIsize::new(0, int_ty).unwrap();
90+
ConstInt::Isize(val)
91+
}
92+
};
93+
94+
Literal::Value { value: ConstVal::Integral(val) }
95+
}
96+
ty::TyFloat(_) => Literal::Value { value: ConstVal::Float(0.0) },
97+
_ => {
98+
span_bug!(span, "Invalid type for zero_literal: `{:?}`", ty)
99+
}
100+
};
101+
102+
self.literal_operand(span, ty, literal)
103+
}
104+
53105
pub fn push_usize(&mut self,
54106
block: BasicBlock,
55107
scope_id: ScopeId,

src/librustc_mir/build/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,11 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
378378
}
379379
}
380380
}
381+
382+
pub fn check_overflow(&self) -> bool {
383+
self.hir.tcx().sess.opts.debugging_opts.force_overflow_checks.unwrap_or(
384+
self.hir.tcx().sess.opts.debug_assertions)
385+
}
381386
}
382387

383388
///////////////////////////////////////////////////////////////////////////

0 commit comments

Comments
 (0)