Skip to content

Commit cdfff9d

Browse files
emberianpnkfelix
authored andcommitted
rustc: implement arithmetic overflow checking
Adds overflow checking to integer addition, multiplication, and subtraction when `-Z force-overflow-checks` is true, or if `--cfg ndebug` is not passed to the compiler. On overflow, it panics with `arithmetic operation overflowed`. Also adds `overflowing_add`, `overflowing_sub`, and `overflowing_mul` intrinsics for doing unchecked arithmetic. [breaking-change]
1 parent 00ccc7a commit cdfff9d

File tree

10 files changed

+203
-7
lines changed

10 files changed

+203
-7
lines changed

src/libcore/intrinsics.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,3 +546,14 @@ extern "rust-intrinsic" {
546546
/// Performs checked `u64` multiplication.
547547
pub fn u64_mul_with_overflow(x: u64, y: u64) -> (u64, bool);
548548
}
549+
550+
// SNAP 880fb89
551+
#[cfg(not(stage0))]
552+
extern "rust-intrinsic" {
553+
/// Returns (a + b) mod 2^N, where N is the width of N in bits.
554+
pub fn overflowing_add<T>(a: T, b: T) -> T;
555+
/// Returns (a - b) mod 2^N, where N is the width of N in bits.
556+
pub fn overflowing_sub<T>(a: T, b: T) -> T;
557+
/// Returns (a * b) mod 2^N, where N is the width of N in bits.
558+
pub fn overflowing_mul<T>(a: T, b: T) -> T;
559+
}

src/librustc/session/config.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,6 @@ pub enum CrateType {
259259
CrateTypeStaticlib,
260260
}
261261

262-
263262
#[derive(Clone)]
264263
pub enum Passes {
265264
SomePasses(Vec<String>),
@@ -585,6 +584,8 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options,
585584
"Adds unstable command line options to rustc interface"),
586585
print_enum_sizes: bool = (false, parse_bool,
587586
"Print the size of enums and their variants"),
587+
force_overflow_checks: Option<bool> = (None, parse_opt_bool,
588+
"Force overflow checks on or off"),
588589
}
589590

590591
pub fn default_lib_output() -> CrateType {

src/librustc_trans/trans/base.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3102,6 +3102,12 @@ pub fn trans_crate<'tcx>(analysis: ty::CrateAnalysis<'tcx>)
31023102
let ty::CrateAnalysis { ty_cx: tcx, export_map, reachable, name, .. } = analysis;
31033103
let krate = tcx.map.krate();
31043104

3105+
let check_overflow = if let Some(v) = tcx.sess.opts.debugging_opts.force_overflow_checks {
3106+
v
3107+
} else {
3108+
!attr::contains_name(&krate.config, "ndebug")
3109+
};
3110+
31053111
// Before we touch LLVM, make sure that multithreading is enabled.
31063112
unsafe {
31073113
use std::sync::{Once, ONCE_INIT};
@@ -3129,7 +3135,8 @@ pub fn trans_crate<'tcx>(analysis: ty::CrateAnalysis<'tcx>)
31293135
export_map,
31303136
Sha256::new(),
31313137
link_meta.clone(),
3132-
reachable);
3138+
reachable,
3139+
check_overflow);
31333140

31343141
{
31353142
let ccx = shared_ccx.get_ccx(0);

src/librustc_trans/trans/context.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ pub struct SharedCrateContext<'tcx> {
6969
symbol_hasher: RefCell<Sha256>,
7070
tcx: ty::ctxt<'tcx>,
7171
stats: Stats,
72+
check_overflow: bool,
7273

7374
available_monomorphizations: RefCell<FnvHashSet<String>>,
7475
available_drop_glues: RefCell<FnvHashMap<Ty<'tcx>, String>>,
@@ -245,7 +246,8 @@ impl<'tcx> SharedCrateContext<'tcx> {
245246
export_map: ExportMap,
246247
symbol_hasher: Sha256,
247248
link_meta: LinkMeta,
248-
reachable: NodeSet)
249+
reachable: NodeSet,
250+
check_overflow: bool)
249251
-> SharedCrateContext<'tcx> {
250252
let (metadata_llcx, metadata_llmod) = unsafe {
251253
create_context_and_module(&tcx.sess, "metadata")
@@ -274,6 +276,7 @@ impl<'tcx> SharedCrateContext<'tcx> {
274276
llvm_insns: RefCell::new(FnvHashMap()),
275277
fn_stats: RefCell::new(Vec::new()),
276278
},
279+
check_overflow: check_overflow,
277280
available_monomorphizations: RefCell::new(FnvHashSet()),
278281
available_drop_glues: RefCell::new(FnvHashMap()),
279282
};
@@ -743,6 +746,10 @@ impl<'b, 'tcx> CrateContext<'b, 'tcx> {
743746
&format!("the type `{}` is too big for the current architecture",
744747
obj.repr(self.tcx())))
745748
}
749+
750+
pub fn check_overflow(&self) -> bool {
751+
self.shared.check_overflow
752+
}
746753
}
747754

748755
fn declare_intrinsic(ccx: &CrateContext, key: & &'static str) -> Option<ValueRef> {

src/librustc_trans/trans/expr.rs

Lines changed: 121 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ use trans::machine::{llsize_of, llsize_of_alloc};
8282
use trans::type_::Type;
8383

8484
use syntax::{ast, ast_util, codemap};
85+
use syntax::parse::token::InternedString;
8586
use syntax::ptr::P;
8687
use syntax::parse::token;
8788
use std::iter::repeat;
@@ -1709,8 +1710,8 @@ fn trans_eager_binop<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
17091710
};
17101711
let is_float = ty::type_is_fp(intype);
17111712
let is_signed = ty::type_is_signed(intype);
1712-
17131713
let rhs = base::cast_shift_expr_rhs(bcx, op, lhs, rhs);
1714+
let info = expr_info(binop_expr);
17141715

17151716
let binop_debug_loc = binop_expr.debug_loc();
17161717

@@ -1720,21 +1721,30 @@ fn trans_eager_binop<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
17201721
if is_float {
17211722
FAdd(bcx, lhs, rhs, binop_debug_loc)
17221723
} else {
1723-
Add(bcx, lhs, rhs, binop_debug_loc)
1724+
let (newbcx, res) = with_overflow_check(
1725+
bcx, OverflowOp::Add, info, lhs_t, lhs, rhs, binop_debug_loc);
1726+
bcx = newbcx;
1727+
res
17241728
}
17251729
}
17261730
ast::BiSub => {
17271731
if is_float {
17281732
FSub(bcx, lhs, rhs, binop_debug_loc)
17291733
} else {
1730-
Sub(bcx, lhs, rhs, binop_debug_loc)
1734+
let (newbcx, res) = with_overflow_check(
1735+
bcx, OverflowOp::Sub, info, lhs_t, lhs, rhs, binop_debug_loc);
1736+
bcx = newbcx;
1737+
res
17311738
}
17321739
}
17331740
ast::BiMul => {
17341741
if is_float {
17351742
FMul(bcx, lhs, rhs, binop_debug_loc)
17361743
} else {
1737-
Mul(bcx, lhs, rhs, binop_debug_loc)
1744+
let (newbcx, res) = with_overflow_check(
1745+
bcx, OverflowOp::Mul, info, lhs_t, lhs, rhs, binop_debug_loc);
1746+
bcx = newbcx;
1747+
res
17381748
}
17391749
}
17401750
ast::BiDiv => {
@@ -2314,3 +2324,110 @@ fn deref_once<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
23142324
DatumBlock { bcx: bcx, datum: datum }
23152325
}
23162326
}
2327+
2328+
enum OverflowOp {
2329+
Add,
2330+
Sub,
2331+
Mul,
2332+
}
2333+
2334+
impl OverflowOp {
2335+
fn to_intrinsic_name(&self, tcx: &ty::ctxt, ty: Ty) -> &'static str {
2336+
use syntax::ast::IntTy::*;
2337+
use syntax::ast::UintTy::*;
2338+
use middle::ty::{ty_int, ty_uint};
2339+
2340+
let new_sty = match ty.sty {
2341+
ty_int(TyIs(_)) => match &tcx.sess.target.target.target_pointer_width[..] {
2342+
"32" => ty_int(TyI32),
2343+
"64" => ty_int(TyI64),
2344+
_ => panic!("unsupported target word size")
2345+
},
2346+
ty_uint(TyUs(_)) => match &tcx.sess.target.target.target_pointer_width[..] {
2347+
"32" => ty_uint(TyU32),
2348+
"64" => ty_uint(TyU64),
2349+
_ => panic!("unsupported target word size")
2350+
},
2351+
ref t @ ty_uint(_) | ref t @ ty_int(_) => t.clone(),
2352+
_ => panic!("tried to get overflow intrinsic for non-int type")
2353+
};
2354+
2355+
match *self {
2356+
OverflowOp::Add => match new_sty {
2357+
ty_int(TyI8) => "llvm.sadd.with.overflow.i8",
2358+
ty_int(TyI16) => "llvm.sadd.with.overflow.i16",
2359+
ty_int(TyI32) => "llvm.sadd.with.overflow.i32",
2360+
ty_int(TyI64) => "llvm.sadd.with.overflow.i64",
2361+
2362+
ty_uint(TyU8) => "llvm.uadd.with.overflow.i8",
2363+
ty_uint(TyU16) => "llvm.uadd.with.overflow.i16",
2364+
ty_uint(TyU32) => "llvm.uadd.with.overflow.i32",
2365+
ty_uint(TyU64) => "llvm.uadd.with.overflow.i64",
2366+
2367+
_ => unreachable!(),
2368+
},
2369+
OverflowOp::Sub => match new_sty {
2370+
ty_int(TyI8) => "llvm.ssub.with.overflow.i8",
2371+
ty_int(TyI16) => "llvm.ssub.with.overflow.i16",
2372+
ty_int(TyI32) => "llvm.ssub.with.overflow.i32",
2373+
ty_int(TyI64) => "llvm.ssub.with.overflow.i64",
2374+
2375+
ty_uint(TyU8) => "llvm.usub.with.overflow.i8",
2376+
ty_uint(TyU16) => "llvm.usub.with.overflow.i16",
2377+
ty_uint(TyU32) => "llvm.usub.with.overflow.i32",
2378+
ty_uint(TyU64) => "llvm.usub.with.overflow.i64",
2379+
2380+
_ => unreachable!(),
2381+
},
2382+
OverflowOp::Mul => match new_sty {
2383+
ty_int(TyI8) => "llvm.smul.with.overflow.i8",
2384+
ty_int(TyI16) => "llvm.smul.with.overflow.i16",
2385+
ty_int(TyI32) => "llvm.smul.with.overflow.i32",
2386+
ty_int(TyI64) => "llvm.smul.with.overflow.i64",
2387+
2388+
ty_uint(TyU8) => "llvm.umul.with.overflow.i8",
2389+
ty_uint(TyU16) => "llvm.umul.with.overflow.i16",
2390+
ty_uint(TyU32) => "llvm.umul.with.overflow.i32",
2391+
ty_uint(TyU64) => "llvm.umul.with.overflow.i64",
2392+
2393+
_ => unreachable!(),
2394+
},
2395+
}
2396+
}
2397+
}
2398+
2399+
2400+
fn with_overflow_check<'a, 'b>(bcx: Block<'a, 'b>, oop: OverflowOp, info: NodeIdAndSpan,
2401+
lhs_t: Ty, lhs: ValueRef, rhs: ValueRef, binop_debug_loc: DebugLoc)
2402+
-> (Block<'a, 'b>, ValueRef) {
2403+
if bcx.unreachable.get() { return (bcx, _Undef(lhs)); }
2404+
if bcx.ccx().check_overflow() {
2405+
let name = oop.to_intrinsic_name(bcx.tcx(), lhs_t);
2406+
let llfn = bcx.ccx().get_intrinsic(&name);
2407+
2408+
let val = Call(bcx, llfn, &[lhs, rhs], None, binop_debug_loc);
2409+
let result = ExtractValue(bcx, val, 0); // iN operation result
2410+
let overflow = ExtractValue(bcx, val, 1); // i1 "did it overflow?"
2411+
2412+
let cond = ICmp(bcx, llvm::IntEQ, overflow, C_integral(Type::i1(bcx.ccx()), 1, false),
2413+
binop_debug_loc);
2414+
2415+
let expect = bcx.ccx().get_intrinsic(&"llvm.expect.i1");
2416+
Call(bcx, expect, &[cond, C_integral(Type::i1(bcx.ccx()), 0, false)],
2417+
None, binop_debug_loc);
2418+
2419+
let bcx =
2420+
base::with_cond(bcx, cond, |bcx|
2421+
controlflow::trans_fail(bcx, info,
2422+
InternedString::new("arithmetic operation overflowed")));
2423+
2424+
(bcx, result)
2425+
} else {
2426+
let res = match oop {
2427+
OverflowOp::Add => Add(bcx, lhs, rhs, binop_debug_loc),
2428+
OverflowOp::Sub => Sub(bcx, lhs, rhs, binop_debug_loc),
2429+
OverflowOp::Mul => Mul(bcx, lhs, rhs, binop_debug_loc),
2430+
};
2431+
(bcx, res)
2432+
}
2433+
}

src/librustc_trans/trans/intrinsic.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,11 @@ pub fn trans_intrinsic_call<'a, 'blk, 'tcx>(mut bcx: Block<'blk, 'tcx>,
660660
llargs[0],
661661
llargs[1],
662662
call_debug_location),
663+
664+
(_, "overflowing_add") => Add(bcx, llargs[0], llargs[1], call_debug_location),
665+
(_, "overflowing_sub") => Sub(bcx, llargs[0], llargs[1], call_debug_location),
666+
(_, "overflowing_mul") => Mul(bcx, llargs[0], llargs[1], call_debug_location),
667+
663668
(_, "return_address") => {
664669
if !fcx.caller_expects_out_pointer {
665670
tcx.sess.span_err(call_info.span,

src/librustc_typeck/check/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5491,6 +5491,9 @@ pub fn check_intrinsic_type(ccx: &CrateCtxt, it: &ast::ForeignItem) {
54915491
(0, vec!(tcx.types.u64, tcx.types.u64),
54925492
ty::mk_tup(tcx, vec!(tcx.types.u64, tcx.types.bool))),
54935493

5494+
"overflowing_add" | "overflowing_sub" | "overflowing_mul" =>
5495+
(1, vec![param(ccx, 0), param(ccx, 0)], param(ccx, 0)),
5496+
54945497
"return_address" => (0, vec![], ty::mk_imm_ptr(tcx, tcx.types.u8)),
54955498

54965499
"assume" => (0, vec![tcx.types.bool], ty::mk_nil(tcx)),

src/test/run-fail/overflowing-add.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright 2015 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+
// error-pattern:thread '<main>' panicked at 'arithmatic operation overflowed'
12+
13+
fn main() {
14+
let x = 200u8 + 200u8 + 200u8;
15+
}

src/test/run-fail/overflowing-mul.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright 2015 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+
// error-pattern:thread '<main>' panicked at 'arithmatic operation overflowed'
12+
13+
fn main() {
14+
let x = 200u8 + 4u8;
15+
}

src/test/run-fail/overflowing-sub.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright 2015 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+
// error-pattern:thread '<main>' panicked at 'arithmatic operation overflowed'
12+
13+
fn main() {
14+
let x = 42u8 - 43u8;
15+
}

0 commit comments

Comments
 (0)