Skip to content

Commit 29dd5df

Browse files
authored
Rollup merge of #69002 - RalfJung:miri-op-overflow, r=oli-obk,wesleywiser
miri: improve and simplify overflow detection This simplifies the overflow detection for signed binary operators, and adds overflow detection to unary operators so that const-prop doesn't have to crudely hand-roll that. It also fixes some bugs in the operator implementation that however, I think, were not observable. r? @oli-obk @wesleywiser
2 parents 2a3c1a3 + c561d23 commit 29dd5df

12 files changed

+272
-97
lines changed

Diff for: src/librustc_mir/interpret/operator.rs

+46-28
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
134134
let mut r = r as u32;
135135
let size = left_layout.size;
136136
oflo |= r >= size.bits() as u32;
137-
if oflo {
138-
r %= size.bits() as u32;
139-
}
137+
r %= size.bits() as u32;
140138
let result = if signed {
141139
let l = self.sign_extend(l, left_layout) as i128;
142140
let result = match bin_op {
@@ -168,6 +166,8 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
168166
)
169167
}
170168

169+
let size = left_layout.size;
170+
171171
// Operations that need special treatment for signed integers
172172
if left_layout.abi.is_signed() {
173173
let op: Option<fn(&i128, &i128) -> bool> = match bin_op {
@@ -195,32 +195,31 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
195195
if let Some(op) = op {
196196
let l128 = self.sign_extend(l, left_layout) as i128;
197197
let r = self.sign_extend(r, right_layout) as i128;
198-
let size = left_layout.size;
198+
// We need a special check for overflowing remainder:
199+
// "int_min % -1" overflows and returns 0, but after casting things to a larger int
200+
// type it does *not* overflow nor give an unrepresentable result!
199201
match bin_op {
200-
Rem | Div => {
201-
// int_min / -1
202+
Rem => {
202203
if r == -1 && l == (1 << (size.bits() - 1)) {
203-
return Ok((Scalar::from_uint(l, size), true, left_layout.ty));
204+
return Ok((Scalar::from_int(0, size), true, left_layout.ty));
204205
}
205206
}
206207
_ => {}
207208
}
208-
trace!("{}, {}, {}", l, l128, r);
209-
let (result, mut oflo) = op(l128, r);
210-
trace!("{}, {}", result, oflo);
211-
if !oflo && size.bits() != 128 {
212-
let max = 1 << (size.bits() - 1);
213-
oflo = result >= max || result < -max;
214-
}
215-
// this may be out-of-bounds for the result type, so we have to truncate ourselves
209+
210+
let (result, oflo) = op(l128, r);
211+
// This may be out-of-bounds for the result type, so we have to truncate ourselves.
212+
// If that truncation loses any information, we have an overflow.
216213
let result = result as u128;
217214
let truncated = self.truncate(result, left_layout);
218-
return Ok((Scalar::from_uint(truncated, size), oflo, left_layout.ty));
215+
return Ok((
216+
Scalar::from_uint(truncated, size),
217+
oflo || self.sign_extend(truncated, left_layout) != result,
218+
left_layout.ty,
219+
));
219220
}
220221
}
221222

222-
let size = left_layout.size;
223-
224223
let (val, ty) = match bin_op {
225224
Eq => (Scalar::from_bool(l == r), self.tcx.types.bool),
226225
Ne => (Scalar::from_bool(l != r), self.tcx.types.bool),
@@ -247,6 +246,8 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
247246
_ => bug!(),
248247
};
249248
let (result, oflo) = op(l, r);
249+
// Truncate to target type.
250+
// If that truncation loses any information, we have an overflow.
250251
let truncated = self.truncate(result, left_layout);
251252
return Ok((
252253
Scalar::from_uint(truncated, size),
@@ -341,7 +342,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
341342
}
342343
}
343344

344-
/// Typed version of `checked_binary_op`, returning an `ImmTy`. Also ignores overflows.
345+
/// Typed version of `overflowing_binary_op`, returning an `ImmTy`. Also ignores overflows.
345346
#[inline]
346347
pub fn binary_op(
347348
&self,
@@ -353,11 +354,13 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
353354
Ok(ImmTy::from_scalar(val, self.layout_of(ty)?))
354355
}
355356

356-
pub fn unary_op(
357+
/// Returns the result of the specified operation, whether it overflowed, and
358+
/// the result type.
359+
pub fn overflowing_unary_op(
357360
&self,
358361
un_op: mir::UnOp,
359362
val: ImmTy<'tcx, M::PointerTag>,
360-
) -> InterpResult<'tcx, ImmTy<'tcx, M::PointerTag>> {
363+
) -> InterpResult<'tcx, (Scalar<M::PointerTag>, bool, Ty<'tcx>)> {
361364
use rustc::mir::UnOp::*;
362365

363366
let layout = val.layout;
@@ -371,29 +374,44 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
371374
Not => !val,
372375
_ => bug!("Invalid bool op {:?}", un_op),
373376
};
374-
Ok(ImmTy::from_scalar(Scalar::from_bool(res), self.layout_of(self.tcx.types.bool)?))
377+
Ok((Scalar::from_bool(res), false, self.tcx.types.bool))
375378
}
376379
ty::Float(fty) => {
377380
let res = match (un_op, fty) {
378381
(Neg, FloatTy::F32) => Scalar::from_f32(-val.to_f32()?),
379382
(Neg, FloatTy::F64) => Scalar::from_f64(-val.to_f64()?),
380383
_ => bug!("Invalid float op {:?}", un_op),
381384
};
382-
Ok(ImmTy::from_scalar(res, layout))
385+
Ok((res, false, layout.ty))
383386
}
384387
_ => {
385388
assert!(layout.ty.is_integral());
386389
let val = self.force_bits(val, layout.size)?;
387-
let res = match un_op {
388-
Not => !val,
390+
let (res, overflow) = match un_op {
391+
Not => (self.truncate(!val, layout), false), // bitwise negation, then truncate
389392
Neg => {
393+
// arithmetic negation
390394
assert!(layout.abi.is_signed());
391-
(-(val as i128)) as u128
395+
let val = self.sign_extend(val, layout) as i128;
396+
let (res, overflow) = val.overflowing_neg();
397+
let res = res as u128;
398+
// Truncate to target type.
399+
// If that truncation loses any information, we have an overflow.
400+
let truncated = self.truncate(res, layout);
401+
(truncated, overflow || self.sign_extend(truncated, layout) != res)
392402
}
393403
};
394-
// res needs tuncating
395-
Ok(ImmTy::from_uint(self.truncate(res, layout), layout))
404+
Ok((Scalar::from_uint(res, layout.size), overflow, layout.ty))
396405
}
397406
}
398407
}
408+
409+
pub fn unary_op(
410+
&self,
411+
un_op: mir::UnOp,
412+
val: ImmTy<'tcx, M::PointerTag>,
413+
) -> InterpResult<'tcx, ImmTy<'tcx, M::PointerTag>> {
414+
let (val, _overflow, ty) = self.overflowing_unary_op(un_op, val)?;
415+
Ok(ImmTy::from_scalar(val, self.layout_of(ty)?))
416+
}
399417
}

Diff for: src/librustc_mir/transform/const_prop.rs

+17-17
Original file line numberDiff line numberDiff line change
@@ -518,18 +518,19 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
518518
}
519519
}
520520

521-
fn check_unary_op(&mut self, arg: &Operand<'tcx>, source_info: SourceInfo) -> Option<()> {
521+
fn check_unary_op(
522+
&mut self,
523+
op: UnOp,
524+
arg: &Operand<'tcx>,
525+
source_info: SourceInfo,
526+
) -> Option<()> {
522527
self.use_ecx(source_info, |this| {
523-
let ty = arg.ty(&this.local_decls, this.tcx);
524-
525-
if ty.is_integral() {
526-
let arg = this.ecx.eval_operand(arg, None)?;
527-
let prim = this.ecx.read_immediate(arg)?;
528-
// Need to do overflow check here: For actual CTFE, MIR
529-
// generation emits code that does this before calling the op.
530-
if prim.to_bits()? == (1 << (prim.layout.size.bits() - 1)) {
531-
throw_panic!(OverflowNeg)
532-
}
528+
let val = this.ecx.read_immediate(this.ecx.eval_operand(arg, None)?)?;
529+
let (_res, overflow, _ty) = this.ecx.overflowing_unary_op(op, val)?;
530+
531+
if overflow {
532+
assert_eq!(op, UnOp::Neg, "Neg is the only UnOp that can overflow");
533+
throw_panic!(OverflowNeg);
533534
}
534535

535536
Ok(())
@@ -576,11 +577,10 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
576577
if !overflow_check {
577578
self.use_ecx(source_info, |this| {
578579
let l = this.ecx.read_immediate(this.ecx.eval_operand(left, None)?)?;
579-
let (_, overflow, _ty) = this.ecx.overflowing_binary_op(op, l, r)?;
580+
let (_res, overflow, _ty) = this.ecx.overflowing_binary_op(op, l, r)?;
580581

581582
if overflow {
582-
let err = err_panic!(Overflow(op)).into();
583-
return Err(err);
583+
throw_panic!(Overflow(op));
584584
}
585585

586586
Ok(())
@@ -620,9 +620,9 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
620620
// Additional checking: if overflow checks are disabled (which is usually the case in
621621
// release mode), then we need to do additional checking here to give lints to the user
622622
// if an overflow would occur.
623-
Rvalue::UnaryOp(UnOp::Neg, arg) if !overflow_check => {
624-
trace!("checking UnaryOp(op = Neg, arg = {:?})", arg);
625-
self.check_unary_op(arg, source_info)?;
623+
Rvalue::UnaryOp(op, arg) if !overflow_check => {
624+
trace!("checking UnaryOp(op = {:?}, arg = {:?})", op, arg);
625+
self.check_unary_op(*op, arg, source_info)?;
626626
}
627627

628628
// Additional checking: check for overflows on integer binary operations and report

Diff for: src/test/ui/consts/const-err2.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,22 @@ fn black_box<T>(_: T) {
1717
fn main() {
1818
let a = -std::i8::MIN;
1919
//~^ ERROR const_err
20+
let a_i128 = -std::i128::MIN;
21+
//~^ ERROR const_err
2022
let b = 200u8 + 200u8 + 200u8;
2123
//~^ ERROR const_err
24+
let b_i128 = std::i128::MIN - std::i128::MAX;
25+
//~^ ERROR const_err
2226
let c = 200u8 * 4;
2327
//~^ ERROR const_err
2428
let d = 42u8 - (42u8 + 1);
2529
//~^ ERROR const_err
2630
let _e = [5u8][1];
27-
//~^ ERROR index out of bounds
31+
//~^ ERROR const_err
2832
black_box(a);
33+
black_box(a_i128);
2934
black_box(b);
35+
black_box(b_i128);
3036
black_box(c);
3137
black_box(d);
3238
}

Diff for: src/test/ui/consts/const-err2.stderr

+17-5
Original file line numberDiff line numberDiff line change
@@ -11,28 +11,40 @@ LL | #![deny(const_err)]
1111
| ^^^^^^^^^
1212

1313
error: this expression will panic at runtime
14-
--> $DIR/const-err2.rs:20:13
14+
--> $DIR/const-err2.rs:20:18
15+
|
16+
LL | let a_i128 = -std::i128::MIN;
17+
| ^^^^^^^^^^^^^^^ attempt to negate with overflow
18+
19+
error: this expression will panic at runtime
20+
--> $DIR/const-err2.rs:22:13
1521
|
1622
LL | let b = 200u8 + 200u8 + 200u8;
1723
| ^^^^^^^^^^^^^ attempt to add with overflow
1824

1925
error: this expression will panic at runtime
20-
--> $DIR/const-err2.rs:22:13
26+
--> $DIR/const-err2.rs:24:18
27+
|
28+
LL | let b_i128 = std::i128::MIN - std::i128::MAX;
29+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ attempt to subtract with overflow
30+
31+
error: this expression will panic at runtime
32+
--> $DIR/const-err2.rs:26:13
2133
|
2234
LL | let c = 200u8 * 4;
2335
| ^^^^^^^^^ attempt to multiply with overflow
2436

2537
error: this expression will panic at runtime
26-
--> $DIR/const-err2.rs:24:13
38+
--> $DIR/const-err2.rs:28:13
2739
|
2840
LL | let d = 42u8 - (42u8 + 1);
2941
| ^^^^^^^^^^^^^^^^^ attempt to subtract with overflow
3042

3143
error: index out of bounds: the len is 1 but the index is 1
32-
--> $DIR/const-err2.rs:26:14
44+
--> $DIR/const-err2.rs:30:14
3345
|
3446
LL | let _e = [5u8][1];
3547
| ^^^^^^^^
3648

37-
error: aborting due to 5 previous errors
49+
error: aborting due to 7 previous errors
3850

Diff for: src/test/ui/consts/const-err3.rs

+6
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,22 @@ fn black_box<T>(_: T) {
1717
fn main() {
1818
let a = -std::i8::MIN;
1919
//~^ ERROR const_err
20+
let a_i128 = -std::i128::MIN;
21+
//~^ ERROR const_err
2022
let b = 200u8 + 200u8 + 200u8;
2123
//~^ ERROR const_err
24+
let b_i128 = std::i128::MIN - std::i128::MAX;
25+
//~^ ERROR const_err
2226
let c = 200u8 * 4;
2327
//~^ ERROR const_err
2428
let d = 42u8 - (42u8 + 1);
2529
//~^ ERROR const_err
2630
let _e = [5u8][1];
2731
//~^ ERROR const_err
2832
black_box(a);
33+
black_box(a_i128);
2934
black_box(b);
35+
black_box(b_i128);
3036
black_box(c);
3137
black_box(d);
3238
}

Diff for: src/test/ui/consts/const-err3.stderr

+17-5
Original file line numberDiff line numberDiff line change
@@ -10,29 +10,41 @@ note: the lint level is defined here
1010
LL | #![deny(const_err)]
1111
| ^^^^^^^^^
1212

13+
error: attempt to negate with overflow
14+
--> $DIR/const-err3.rs:20:18
15+
|
16+
LL | let a_i128 = -std::i128::MIN;
17+
| ^^^^^^^^^^^^^^^
18+
1319
error: attempt to add with overflow
14-
--> $DIR/const-err3.rs:20:13
20+
--> $DIR/const-err3.rs:22:13
1521
|
1622
LL | let b = 200u8 + 200u8 + 200u8;
1723
| ^^^^^^^^^^^^^
1824

25+
error: attempt to subtract with overflow
26+
--> $DIR/const-err3.rs:24:18
27+
|
28+
LL | let b_i128 = std::i128::MIN - std::i128::MAX;
29+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
30+
1931
error: attempt to multiply with overflow
20-
--> $DIR/const-err3.rs:22:13
32+
--> $DIR/const-err3.rs:26:13
2133
|
2234
LL | let c = 200u8 * 4;
2335
| ^^^^^^^^^
2436

2537
error: attempt to subtract with overflow
26-
--> $DIR/const-err3.rs:24:13
38+
--> $DIR/const-err3.rs:28:13
2739
|
2840
LL | let d = 42u8 - (42u8 + 1);
2941
| ^^^^^^^^^^^^^^^^^
3042

3143
error: index out of bounds: the len is 1 but the index is 1
32-
--> $DIR/const-err3.rs:26:14
44+
--> $DIR/const-err3.rs:30:14
3345
|
3446
LL | let _e = [5u8][1];
3547
| ^^^^^^^^
3648

37-
error: aborting due to 5 previous errors
49+
error: aborting due to 7 previous errors
3850

Diff for: src/test/ui/consts/const-int-arithmetic-overflow.rs

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// run-pass
2+
// compile-flags: -O
3+
#![allow(const_err)]
4+
5+
// Make sure arithmetic unary/binary ops actually return the right result, even when overflowing.
6+
// We have to put them in `const fn` and turn on optimizations to avoid overflow checks.
7+
8+
const fn add(x: i8, y: i8) -> i8 { x+y }
9+
const fn sub(x: i8, y: i8) -> i8 { x-y }
10+
const fn mul(x: i8, y: i8) -> i8 { x*y }
11+
// div and rem are always checked, so we cannot test their result in case of oveflow.
12+
const fn neg(x: i8) -> i8 { -x }
13+
14+
fn main() {
15+
const ADD_OFLOW: i8 = add(100, 100);
16+
assert_eq!(ADD_OFLOW, -56);
17+
18+
const SUB_OFLOW: i8 = sub(100, -100);
19+
assert_eq!(SUB_OFLOW, -56);
20+
21+
const MUL_OFLOW: i8 = mul(-100, -2);
22+
assert_eq!(MUL_OFLOW, -56);
23+
24+
const NEG_OFLOW: i8 = neg(-128);
25+
assert_eq!(NEG_OFLOW, -128);
26+
}

0 commit comments

Comments
 (0)