1
Fork 0

Auto merge of #94512 - RalfJung:sdiv-ub, r=oli-obk

Miri/CTFE: properly treat overflow in (signed) division/rem as UB

To my surprise, it looks like LLVM treats overflow of signed div/rem as UB. From what I can tell, MIR `Div`/`Rem` directly lowers to the corresponding LLVM operation, so to make that correct we also have to consider these overflows UB in the CTFE/Miri interpreter engine.

r? `@oli-obk`
This commit is contained in:
bors 2022-03-03 12:56:24 +00:00
commit 4566094913
11 changed files with 97 additions and 80 deletions

View file

@ -500,15 +500,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
// `x % y != 0` or `y == 0` or `x == T::MIN && y == -1`.
// First, check x % y != 0 (or if that computation overflows).
let (res, overflow, _ty) = self.overflowing_binary_op(BinOp::Rem, &a, &b)?;
if overflow || res.assert_bits(a.layout.size) != 0 {
// Then, check if `b` is -1, which is the "MIN / -1" case.
let minus1 = Scalar::from_int(-1, dest.layout.size);
let b_scalar = b.to_scalar().unwrap();
if b_scalar == minus1 {
throw_ub_format!("exact_div: result of dividing MIN by -1 cannot be represented")
} else {
throw_ub_format!("exact_div: {} cannot be divided by {} without remainder", a, b,)
}
assert!(!overflow); // All overflow is UB, so this should never return on overflow.
if res.assert_bits(a.layout.size) != 0 {
throw_ub_format!("exact_div: {} cannot be divided by {} without remainder", a, b)
}
// `Rem` says this is all right, so we can let `Div` do its job.
self.binop_ignore_overflow(BinOp::Div, &a, &b, dest)

View file

@ -196,16 +196,20 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
_ => None,
};
if let Some(op) = op {
let l = self.sign_extend(l, left_layout) as i128;
let r = self.sign_extend(r, right_layout) as i128;
// We need a special check for overflowing remainder:
// "int_min % -1" overflows and returns 0, but after casting things to a larger int
// type it does *not* overflow nor give an unrepresentable result!
if bin_op == Rem {
if r == -1 && l == (1 << (size.bits() - 1)) {
return Ok((Scalar::from_int(0, size), true, left_layout.ty));
// We need a special check for overflowing Rem and Div since they are *UB*
// on overflow, which can happen with "int_min $OP -1".
if matches!(bin_op, Rem | Div) {
if l == size.signed_int_min() && r == -1 {
if bin_op == Rem {
throw_ub!(RemainderOverflow)
} else {
throw_ub!(DivisionOverflow)
}
}
}
let l = self.sign_extend(l, left_layout) as i128;
let (result, oflo) = op(l, r);
// This may be out-of-bounds for the result type, so we have to truncate ourselves.