Skip to content

Commit f35328c

Browse files
committed
rustc: Avoid UB with signed division/remainder
Division and remainder by 0 are undefined behavior, and are detected at runtime. This commit adds support for ensuring that MIN / -1 is also checked for at runtime, as this would cause signed overflow, or undefined behvaior. Closes #8460
1 parent 9fd075f commit f35328c

File tree

3 files changed

+102
-24
lines changed

3 files changed

+102
-24
lines changed

src/librustc/middle/trans/base.rs

+63-20
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ use libc::{c_uint, uint64_t};
8080
use std::c_str::ToCStr;
8181
use std::cell::{Cell, RefCell};
8282
use std::rc::Rc;
83+
use std::{i8, i16, i32, i64};
8384
use syntax::abi::{X86, X86_64, Arm, Mips, Rust, RustIntrinsic};
8485
use syntax::ast_util::{local_def, is_local};
8586
use syntax::attr::AttrMetaMethods;
@@ -777,35 +778,77 @@ pub fn cast_shift_rhs(op: ast::BinOp,
777778
}
778779
}
779780

780-
pub fn fail_if_zero<'a>(
781+
pub fn fail_if_zero_or_overflows<'a>(
781782
cx: &'a Block<'a>,
782783
span: Span,
783784
divrem: ast::BinOp,
785+
lhs: ValueRef,
784786
rhs: ValueRef,
785787
rhs_t: ty::t)
786788
-> &'a Block<'a> {
787-
let text = if divrem == ast::BiDiv {
788-
"attempted to divide by zero"
789+
let (zero_text, overflow_text) = if divrem == ast::BiDiv {
790+
("attempted to divide by zero",
791+
"attempted to divide with overflow")
789792
} else {
790-
"attempted remainder with a divisor of zero"
793+
("attempted remainder with a divisor of zero",
794+
"attempted remainder with overflow")
791795
};
792-
let is_zero = match ty::get(rhs_t).sty {
793-
ty::ty_int(t) => {
794-
let zero = C_integral(Type::int_from_ty(cx.ccx(), t), 0u64, false);
795-
ICmp(cx, lib::llvm::IntEQ, rhs, zero)
796-
}
797-
ty::ty_uint(t) => {
798-
let zero = C_integral(Type::uint_from_ty(cx.ccx(), t), 0u64, false);
799-
ICmp(cx, lib::llvm::IntEQ, rhs, zero)
800-
}
801-
_ => {
802-
cx.sess().bug(format!("fail-if-zero on unexpected type: {}",
803-
ty_to_str(cx.tcx(), rhs_t)).as_slice());
804-
}
796+
let (is_zero, is_signed) = match ty::get(rhs_t).sty {
797+
ty::ty_int(t) => {
798+
let zero = C_integral(Type::int_from_ty(cx.ccx(), t), 0u64, false);
799+
(ICmp(cx, lib::llvm::IntEQ, rhs, zero), true)
800+
}
801+
ty::ty_uint(t) => {
802+
let zero = C_integral(Type::uint_from_ty(cx.ccx(), t), 0u64, false);
803+
(ICmp(cx, lib::llvm::IntEQ, rhs, zero), false)
804+
}
805+
_ => {
806+
cx.sess().bug(format!("fail-if-zero on unexpected type: {}",
807+
ty_to_str(cx.tcx(), rhs_t)).as_slice());
808+
}
805809
};
806-
with_cond(cx, is_zero, |bcx| {
807-
controlflow::trans_fail(bcx, span, InternedString::new(text))
808-
})
810+
let bcx = with_cond(cx, is_zero, |bcx| {
811+
controlflow::trans_fail(bcx, span, InternedString::new(zero_text))
812+
});
813+
814+
// To quote LLVM's documentation for the sdiv instruction:
815+
//
816+
// Division by zero leads to undefined behavior. Overflow also leads
817+
// to undefined behavior; this is a rare case, but can occur, for
818+
// example, by doing a 32-bit division of -2147483648 by -1.
819+
//
820+
// In order to avoid undefined behavior, we perform runtime checks for
821+
// signed division/remainder which would trigger overflow. For unsigned
822+
// integers, no action beyond checking for zero need be taken.
823+
if is_signed {
824+
let (llty, min) = match ty::get(rhs_t).sty {
825+
ty::ty_int(t) => {
826+
let llty = Type::int_from_ty(cx.ccx(), t);
827+
let min = match t {
828+
ast::TyI if llty == Type::i32(cx.ccx()) => i32::MIN as u64,
829+
ast::TyI => i64::MIN as u64,
830+
ast::TyI8 => i8::MIN as u64,
831+
ast::TyI16 => i16::MIN as u64,
832+
ast::TyI32 => i32::MIN as u64,
833+
ast::TyI64 => i64::MIN as u64,
834+
};
835+
(llty, min)
836+
}
837+
_ => unreachable!(),
838+
};
839+
let minus_one = ICmp(bcx, lib::llvm::IntEQ, rhs,
840+
C_integral(llty, -1, false));
841+
with_cond(bcx, minus_one, |bcx| {
842+
let is_min = ICmp(bcx, lib::llvm::IntEQ, lhs,
843+
C_integral(llty, min, true));
844+
with_cond(bcx, is_min, |bcx| {
845+
controlflow::trans_fail(bcx, span,
846+
InternedString::new(overflow_text))
847+
})
848+
})
849+
} else {
850+
bcx
851+
}
809852
}
810853

811854
pub fn trans_external_path(ccx: &CrateContext, did: ast::DefId, t: ty::t) -> ValueRef {

src/librustc/middle/trans/expr.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -1297,8 +1297,8 @@ fn trans_eager_binop<'a>(
12971297
FDiv(bcx, lhs, rhs)
12981298
} else {
12991299
// Only zero-check integers; fp /0 is NaN
1300-
bcx = base::fail_if_zero(bcx, binop_expr.span,
1301-
op, rhs, rhs_t);
1300+
bcx = base::fail_if_zero_or_overflows(bcx, binop_expr.span,
1301+
op, lhs, rhs, rhs_t);
13021302
if is_signed {
13031303
SDiv(bcx, lhs, rhs)
13041304
} else {
@@ -1311,8 +1311,8 @@ fn trans_eager_binop<'a>(
13111311
FRem(bcx, lhs, rhs)
13121312
} else {
13131313
// Only zero-check integers; fp %0 is NaN
1314-
bcx = base::fail_if_zero(bcx, binop_expr.span,
1315-
op, rhs, rhs_t);
1314+
bcx = base::fail_if_zero_or_overflows(bcx, binop_expr.span,
1315+
op, lhs, rhs, rhs_t);
13161316
if is_signed {
13171317
SRem(bcx, lhs, rhs)
13181318
} else {

src/test/run-pass/issue-8460.rs

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
use std::{int, i8, i16, i32, i64};
12+
use std::task;
13+
14+
fn main() {
15+
assert!(task::try(proc() int::MIN / -1).is_err());
16+
assert!(task::try(proc() i8::MIN / -1).is_err());
17+
assert!(task::try(proc() i16::MIN / -1).is_err());
18+
assert!(task::try(proc() i32::MIN / -1).is_err());
19+
assert!(task::try(proc() i64::MIN / -1).is_err());
20+
assert!(task::try(proc() 1i / 0).is_err());
21+
assert!(task::try(proc() 1i8 / 0).is_err());
22+
assert!(task::try(proc() 1i16 / 0).is_err());
23+
assert!(task::try(proc() 1i32 / 0).is_err());
24+
assert!(task::try(proc() 1i64 / 0).is_err());
25+
assert!(task::try(proc() int::MIN % -1).is_err());
26+
assert!(task::try(proc() i8::MIN % -1).is_err());
27+
assert!(task::try(proc() i16::MIN % -1).is_err());
28+
assert!(task::try(proc() i32::MIN % -1).is_err());
29+
assert!(task::try(proc() i64::MIN % -1).is_err());
30+
assert!(task::try(proc() 1i % 0).is_err());
31+
assert!(task::try(proc() 1i8 % 0).is_err());
32+
assert!(task::try(proc() 1i16 % 0).is_err());
33+
assert!(task::try(proc() 1i32 % 0).is_err());
34+
assert!(task::try(proc() 1i64 % 0).is_err());
35+
}

0 commit comments

Comments
 (0)