Skip to content

Commit a50a1c2

Browse files
committed
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 009a649 commit a50a1c2

File tree

9 files changed

+427
-9
lines changed

9 files changed

+427
-9
lines changed

src/librustc/mir/repr.rs

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

759759
BinaryOp(BinOp, Operand<'tcx>, Operand<'tcx>),
760+
CheckedBinaryOp(BinOp, Operand<'tcx>, Operand<'tcx>),
760761

761762
UnaryOp(UnOp, Operand<'tcx>),
762763

@@ -850,6 +851,16 @@ pub enum BinOp {
850851
Gt,
851852
}
852853

854+
impl BinOp {
855+
pub fn is_checkable(self) -> bool {
856+
use self::BinOp::*;
857+
match self {
858+
Add | Sub | Mul | Shl | Shr => true,
859+
_ => false
860+
}
861+
}
862+
}
863+
853864
#[derive(Copy, Clone, Debug, PartialEq, Eq, RustcEncodable, RustcDecodable)]
854865
pub enum UnOp {
855866
/// The `!` operator for logical inversion
@@ -868,6 +879,9 @@ impl<'tcx> Debug for Rvalue<'tcx> {
868879
Len(ref a) => write!(fmt, "Len({:?})", a),
869880
Cast(ref kind, ref lv, ref ty) => write!(fmt, "{:?} as {:?} ({:?})", lv, ty, kind),
870881
BinaryOp(ref op, ref a, ref b) => write!(fmt, "{:?}({:?}, {:?})", op, a, b),
882+
CheckedBinaryOp(ref op, ref a, ref b) => {
883+
write!(fmt, "Checked{:?}({:?}, {:?})", op, a, b)
884+
}
871885
UnaryOp(ref op, ref a) => write!(fmt, "{:?}({:?})", op, a),
872886
Box(ref t) => write!(fmt, "Box({:?})", t),
873887
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
@@ -189,6 +189,13 @@ impl<'tcx> Mir<'tcx> {
189189
let rhs_ty = self.operand_ty(tcx, rhs);
190190
Some(self.binop_ty(tcx, op, lhs_ty, rhs_ty))
191191
}
192+
Rvalue::CheckedBinaryOp(op, ref lhs, ref rhs) => {
193+
let lhs_ty = self.operand_ty(tcx, lhs);
194+
let rhs_ty = self.operand_ty(tcx, rhs);
195+
let ty = self.binop_ty(tcx, op, lhs_ty, rhs_ty);
196+
let ty = tcx.mk_tup(vec![ty, tcx.types.bool]);
197+
Some(ty)
198+
}
192199
Rvalue::UnaryOp(_, ref operand) => {
193200
Some(self.operand_ty(tcx, operand))
194201
}

src/librustc/mir/visit.rs

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

452452
Rvalue::BinaryOp(_bin_op,
453+
ref $($mutability)* lhs,
454+
ref $($mutability)* rhs) |
455+
Rvalue::CheckedBinaryOp(_bin_op,
453456
ref $($mutability)* lhs,
454457
ref $($mutability)* rhs) => {
455458
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
@@ -543,7 +543,8 @@ fn gather_moves<'tcx>(mir: &Mir<'tcx>, tcx: &TyCtxt<'tcx>) -> MoveData<'tcx> {
543543
bb_ctxt.on_operand(SK::Repeat, operand, source),
544544
Rvalue::Cast(ref _kind, ref operand, ref _ty) =>
545545
bb_ctxt.on_operand(SK::Cast, operand, source),
546-
Rvalue::BinaryOp(ref _binop, ref operand1, ref operand2) => {
546+
Rvalue::BinaryOp(ref _binop, ref operand1, ref operand2) |
547+
Rvalue::CheckedBinaryOp(ref _binop, ref operand1, ref operand2) => {
547548
bb_ctxt.on_operand(SK::BinaryOp, operand1, source);
548549
bb_ctxt.on_operand(SK::BinaryOp, operand2, source);
549550
}

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

src/librustc_mir/build/expr/stmt.rs

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

70+
let lhs = this.hir.mirror(lhs);
71+
let lhs_ty = lhs.ty;
72+
7073
// As above, RTL.
7174
let rhs = unpack!(block = this.as_operand(block, rhs));
7275
let lhs = unpack!(block = this.as_lvalue(block, lhs));
7376

7477
// we don't have to drop prior contents or anything
7578
// because AssignOp is only legal for Copy types
7679
// (overloaded ops should be desugared into a call).
77-
this.cfg.push_assign(block, scope_id, expr_span, &lhs,
78-
Rvalue::BinaryOp(op,
79-
Operand::Consume(lhs.clone()),
80-
rhs));
80+
let result = unpack!(block = this.build_binary_op(block, op, expr_span, lhs_ty,
81+
Operand::Consume(lhs.clone()), rhs));
82+
this.cfg.push_assign(block, scope_id, expr_span, &lhs, result);
8183

8284
block.unit()
8385
}

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,'tcx> Builder<'a,'tcx> {
@@ -50,6 +55,53 @@ impl<'a,'tcx> Builder<'a,'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
@@ -341,6 +341,11 @@ impl<'a,'tcx> Builder<'a,'tcx> {
341341
}
342342
}
343343
}
344+
345+
pub fn check_overflow(&self) -> bool {
346+
self.hir.tcx().sess.opts.debugging_opts.force_overflow_checks.unwrap_or(
347+
self.hir.tcx().sess.opts.debug_assertions)
348+
}
344349
}
345350

346351
///////////////////////////////////////////////////////////////////////////

0 commit comments

Comments
 (0)