Skip to content

Commit 378805c

Browse files
emberianAatch
authored andcommitted
rustc: implement arithmatic 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 `arithmatic operation overflowed`. Also adds `overflowing_add`, `overflowing_sub`, and `overflowing_mul` intrinsics for doing unchecked arithmatic. [breaking-change]
1 parent 44a287e commit 378805c

File tree

9 files changed

+294
-6
lines changed

9 files changed

+294
-6
lines changed

src/libcore/intrinsics.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,16 @@ extern "rust-intrinsic" {
547547
pub fn u64_mul_with_overflow(x: u64, y: u64) -> (u64, bool);
548548
}
549549

550+
extern "rust-intrinsic" {
551+
/// Returns (a + b) mod 2^N, where N is the width of N in bits.
552+
pub fn overflowing_add<T>(a: T, b: T) -> T;
553+
/// Returns (a - b) mod 2^N, where N is the width of N in bits.
554+
pub fn overflowing_sub<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_mul<T>(a: T, b: T) -> T;
557+
}
558+
559+
#[cfg(not(stage0))]
550560

551561
/// `TypeId` represents a globally unique identifier for a type
552562
#[lang="type_id"] // This needs to be kept in lockstep with the code in trans/intrinsic.rs and

src/librustc/session/config.rs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,107 @@ pub enum CrateType {
257257
CrateTypeStaticlib,
258258
}
259259

260+
macro_rules! debugging_opts {
261+
([ $opt:ident ] $cnt:expr ) => (
262+
pub const $opt: u64 = 1 << $cnt;
263+
);
264+
([ $opt:ident, $($rest:ident),* ] $cnt:expr ) => (
265+
pub const $opt: u64 = 1 << $cnt;
266+
debugging_opts! { [ $($rest),* ] $cnt + 1 }
267+
)
268+
}
269+
270+
debugging_opts! {
271+
[
272+
VERBOSE,
273+
TIME_PASSES,
274+
COUNT_LLVM_INSNS,
275+
TIME_LLVM_PASSES,
276+
TRANS_STATS,
277+
ASM_COMMENTS,
278+
NO_VERIFY,
279+
BORROWCK_STATS,
280+
NO_LANDING_PADS,
281+
DEBUG_LLVM,
282+
COUNT_TYPE_SIZES,
283+
META_STATS,
284+
GC,
285+
PRINT_LINK_ARGS,
286+
PRINT_LLVM_PASSES,
287+
AST_JSON,
288+
AST_JSON_NOEXPAND,
289+
LS,
290+
SAVE_ANALYSIS,
291+
PRINT_MOVE_FRAGMENTS,
292+
FLOWGRAPH_PRINT_LOANS,
293+
FLOWGRAPH_PRINT_MOVES,
294+
FLOWGRAPH_PRINT_ASSIGNS,
295+
FLOWGRAPH_PRINT_ALL,
296+
PRINT_REGION_GRAPH,
297+
PARSE_ONLY,
298+
NO_TRANS,
299+
NO_ANALYSIS,
300+
UNSTABLE_OPTIONS,
301+
PRINT_ENUM_SIZES,
302+
FORCE_OVERFLOW_CHECKS,
303+
FORCE_NO_OVERFLOW_CHECKS,
304+
]
305+
0
306+
}
307+
308+
pub fn debugging_opts_map() -> Vec<(&'static str, &'static str, u64)> {
309+
vec![("verbose", "in general, enable more debug printouts", VERBOSE),
310+
("time-passes", "measure time of each rustc pass", TIME_PASSES),
311+
("count-llvm-insns", "count where LLVM \
312+
instrs originate", COUNT_LLVM_INSNS),
313+
("time-llvm-passes", "measure time of each LLVM pass",
314+
TIME_LLVM_PASSES),
315+
("trans-stats", "gather trans statistics", TRANS_STATS),
316+
("asm-comments", "generate comments into the assembly (may change behavior)",
317+
ASM_COMMENTS),
318+
("no-verify", "skip LLVM verification", NO_VERIFY),
319+
("borrowck-stats", "gather borrowck statistics", BORROWCK_STATS),
320+
("no-landing-pads", "omit landing pads for unwinding",
321+
NO_LANDING_PADS),
322+
("debug-llvm", "enable debug output from LLVM", DEBUG_LLVM),
323+
("count-type-sizes", "count the sizes of aggregate types",
324+
COUNT_TYPE_SIZES),
325+
("meta-stats", "gather metadata statistics", META_STATS),
326+
("print-link-args", "Print the arguments passed to the linker",
327+
PRINT_LINK_ARGS),
328+
("gc", "Garbage collect shared data (experimental)", GC),
329+
("print-llvm-passes",
330+
"Prints the llvm optimization passes being run",
331+
PRINT_LLVM_PASSES),
332+
("ast-json", "Print the AST as JSON and halt", AST_JSON),
333+
("ast-json-noexpand", "Print the pre-expansion AST as JSON and halt", AST_JSON_NOEXPAND),
334+
("ls", "List the symbols defined by a library crate", LS),
335+
("save-analysis", "Write syntax and type analysis information \
336+
in addition to normal output", SAVE_ANALYSIS),
337+
("print-move-fragments", "Print out move-fragment data for every fn",
338+
PRINT_MOVE_FRAGMENTS),
339+
("flowgraph-print-loans", "Include loan analysis data in \
340+
--pretty flowgraph output", FLOWGRAPH_PRINT_LOANS),
341+
("flowgraph-print-moves", "Include move analysis data in \
342+
--pretty flowgraph output", FLOWGRAPH_PRINT_MOVES),
343+
("flowgraph-print-assigns", "Include assignment analysis data in \
344+
--pretty flowgraph output", FLOWGRAPH_PRINT_ASSIGNS),
345+
("flowgraph-print-all", "Include all dataflow analysis data in \
346+
--pretty flowgraph output", FLOWGRAPH_PRINT_ALL),
347+
("print-region-graph", "Prints region inference graph. \
348+
Use with RUST_REGION_GRAPH=help for more info",
349+
PRINT_REGION_GRAPH),
350+
("parse-only", "Parse only; do not compile, assemble, or link", PARSE_ONLY),
351+
("no-trans", "Run all passes except translation; no output", NO_TRANS),
352+
("no-analysis", "Parse and expand the source, but run no analysis and",
353+
NO_TRANS),
354+
("unstable-options", "Adds unstable command line options to rustc interface",
355+
UNSTABLE_OPTIONS),
356+
("print-enum-sizes", "Print the size of enums and their variants", PRINT_ENUM_SIZES),
357+
("force-overflow-checks", "Force arithmatic overflow checking", FORCE_OVERFLOW_CHECKS),
358+
("force-no-overflow-checks", "Force arithmatic overflow checking", FORCE_NO_OVERFLOW_CHECKS),
359+
]
360+
}
260361

261362
#[derive(Clone)]
262363
pub enum Passes {

src/librustc_trans/trans/base.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3111,6 +3111,9 @@ pub fn trans_crate<'tcx>(analysis: ty::CrateAnalysis<'tcx>)
31113111
let ty::CrateAnalysis { ty_cx: tcx, export_map, reachable, name, .. } = analysis;
31123112
let krate = tcx.map.krate();
31133113

3114+
let check_overflow = tcx.sess.opts.debugging_opts & config::FORCE_OVERFLOW_CHECKS != 0
3115+
|| !attr::contains_name(krate.config[], "ndebug");
3116+
31143117
// Before we touch LLVM, make sure that multithreading is enabled.
31153118
unsafe {
31163119
use std::sync::{Once, ONCE_INIT};
@@ -3138,7 +3141,8 @@ pub fn trans_crate<'tcx>(analysis: ty::CrateAnalysis<'tcx>)
31383141
export_map,
31393142
Sha256::new(),
31403143
link_meta.clone(),
3141-
reachable);
3144+
reachable,
3145+
check_overflow);
31423146

31433147
{
31443148
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
@@ -68,6 +68,7 @@ pub struct SharedCrateContext<'tcx> {
6868
symbol_hasher: RefCell<Sha256>,
6969
tcx: ty::ctxt<'tcx>,
7070
stats: Stats,
71+
check_overflow: bool,
7172

7273
available_monomorphizations: RefCell<FnvHashSet<String>>,
7374
available_drop_glues: RefCell<FnvHashMap<Ty<'tcx>, String>>,
@@ -241,7 +242,8 @@ impl<'tcx> SharedCrateContext<'tcx> {
241242
export_map: ExportMap,
242243
symbol_hasher: Sha256,
243244
link_meta: LinkMeta,
244-
reachable: NodeSet)
245+
reachable: NodeSet,
246+
check_overflow: bool)
245247
-> SharedCrateContext<'tcx> {
246248
let (metadata_llcx, metadata_llmod) = unsafe {
247249
create_context_and_module(&tcx.sess, "metadata")
@@ -270,6 +272,7 @@ impl<'tcx> SharedCrateContext<'tcx> {
270272
llvm_insns: RefCell::new(FnvHashMap::new()),
271273
fn_stats: RefCell::new(Vec::new()),
272274
},
275+
check_overflow: check_overflow,
273276
available_monomorphizations: RefCell::new(FnvHashSet::new()),
274277
available_drop_glues: RefCell::new(FnvHashMap::new()),
275278
};
@@ -733,6 +736,10 @@ impl<'b, 'tcx> CrateContext<'b, 'tcx> {
733736
&format!("the type `{}` is too big for the current architecture",
734737
obj.repr(self.tcx()))[])
735738
}
739+
740+
pub fn check_overflow(&self) -> bool {
741+
self.shared.check_overflow
742+
}
736743
}
737744

738745
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
@@ -65,6 +65,7 @@ use trans::machine::{llsize_of, llsize_of_alloc};
6565
use trans::type_::Type;
6666

6767
use syntax::{ast, ast_util, codemap};
68+
use syntax::parse::token::InternedString;
6869
use syntax::print::pprust::{expr_to_string};
6970
use syntax::ptr::P;
7071
use syntax::parse::token;
@@ -1689,22 +1690,34 @@ fn trans_eager_binop<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
16891690
};
16901691
let is_float = ty::type_is_fp(intype);
16911692
let is_signed = ty::type_is_signed(intype);
1692-
16931693
let rhs = base::cast_shift_expr_rhs(bcx, op, lhs, rhs);
1694+
let sp = binop_expr.span;
16941695

16951696
let mut bcx = bcx;
16961697
let val = match op {
16971698
ast::BiAdd => {
16981699
if is_float { FAdd(bcx, lhs, rhs) }
1699-
else { Add(bcx, lhs, rhs) }
1700+
else {
1701+
let (newbcx, res) = with_overflow_check(bcx, OverflowOp::Add, sp, lhs_t, lhs, rhs);
1702+
bcx = newbcx;
1703+
res
1704+
}
17001705
}
17011706
ast::BiSub => {
17021707
if is_float { FSub(bcx, lhs, rhs) }
1703-
else { Sub(bcx, lhs, rhs) }
1708+
else {
1709+
let (newbcx, res) = with_overflow_check(bcx, OverflowOp::Sub, sp, lhs_t, lhs, rhs);
1710+
bcx = newbcx;
1711+
res
1712+
}
17041713
}
17051714
ast::BiMul => {
17061715
if is_float { FMul(bcx, lhs, rhs) }
1707-
else { Mul(bcx, lhs, rhs) }
1716+
else {
1717+
let (newbcx, res) = with_overflow_check(bcx, OverflowOp::Mul, sp, lhs_t, lhs, rhs);
1718+
bcx = newbcx;
1719+
res
1720+
}
17081721
}
17091722
ast::BiDiv => {
17101723
if is_float {
@@ -2283,3 +2296,107 @@ fn deref_once<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
22832296
DatumBlock { bcx: bcx, datum: datum }
22842297
}
22852298
}
2299+
2300+
enum OverflowOp {
2301+
Add,
2302+
Sub,
2303+
Mul,
2304+
}
2305+
2306+
impl OverflowOp {
2307+
fn to_intrinsic_name(&self, tcx: &ty::ctxt, ty: Ty) -> &'static str {
2308+
use syntax::ast::IntTy::*;
2309+
use syntax::ast::UintTy::*;
2310+
use middle::ty::{ty_int, ty_uint};
2311+
2312+
let new_sty = match ty.sty {
2313+
ty_int(TyI) => match tcx.sess.target.target.target_word_size[] {
2314+
"32" => ty_int(TyI32),
2315+
"64" => ty_int(TyI64),
2316+
_ => panic!("unsupported target word size")
2317+
},
2318+
ty_uint(TyU) => match tcx.sess.target.target.target_word_size[] {
2319+
"32" => ty_uint(TyU32),
2320+
"64" => ty_uint(TyU64),
2321+
_ => panic!("unsupported target word size")
2322+
},
2323+
ref t @ ty_uint(_) | ref t @ ty_int(_) => t.clone(),
2324+
_ => panic!("tried to get overflow intrinsic for non-int type")
2325+
};
2326+
2327+
match *self {
2328+
OverflowOp::Add => match new_sty {
2329+
ty_int(TyI8) => "llvm.sadd.with.overflow.i8",
2330+
ty_int(TyI16) => "llvm.sadd.with.overflow.i16",
2331+
ty_int(TyI32) => "llvm.sadd.with.overflow.i32",
2332+
ty_int(TyI64) => "llvm.sadd.with.overflow.i64",
2333+
2334+
ty_uint(TyU8) => "llvm.uadd.with.overflow.i8",
2335+
ty_uint(TyU16) => "llvm.uadd.with.overflow.i16",
2336+
ty_uint(TyU32) => "llvm.uadd.with.overflow.i32",
2337+
ty_uint(TyU64) => "llvm.uadd.with.overflow.i64",
2338+
2339+
_ => unreachable!(),
2340+
},
2341+
OverflowOp::Sub => match new_sty {
2342+
ty_int(TyI8) => "llvm.ssub.with.overflow.i8",
2343+
ty_int(TyI16) => "llvm.ssub.with.overflow.i16",
2344+
ty_int(TyI32) => "llvm.ssub.with.overflow.i32",
2345+
ty_int(TyI64) => "llvm.ssub.with.overflow.i64",
2346+
2347+
ty_uint(TyU8) => "llvm.usub.with.overflow.i8",
2348+
ty_uint(TyU16) => "llvm.usub.with.overflow.i16",
2349+
ty_uint(TyU32) => "llvm.usub.with.overflow.i32",
2350+
ty_uint(TyU64) => "llvm.usub.with.overflow.i64",
2351+
2352+
_ => unreachable!(),
2353+
},
2354+
OverflowOp::Mul => match new_sty {
2355+
ty_int(TyI8) => "llvm.smul.with.overflow.i8",
2356+
ty_int(TyI16) => "llvm.smul.with.overflow.i16",
2357+
ty_int(TyI32) => "llvm.smul.with.overflow.i32",
2358+
ty_int(TyI64) => "llvm.smul.with.overflow.i64",
2359+
2360+
ty_uint(TyU8) => "llvm.umul.with.overflow.i8",
2361+
ty_uint(TyU16) => "llvm.umul.with.overflow.i16",
2362+
ty_uint(TyU32) => "llvm.umul.with.overflow.i32",
2363+
ty_uint(TyU64) => "llvm.umul.with.overflow.i64",
2364+
2365+
_ => unreachable!(),
2366+
},
2367+
}
2368+
}
2369+
}
2370+
2371+
2372+
fn with_overflow_check<'a, 'b>(bcx: Block<'a, 'b>, oop: OverflowOp, sp: codemap::Span,
2373+
lhs_t: Ty, lhs: ValueRef, rhs: ValueRef) -> (Block<'a, 'b>, ValueRef) {
2374+
if bcx.unreachable.get() { return (bcx, _Undef(lhs)); }
2375+
if bcx.ccx().check_overflow() {
2376+
let name = oop.to_intrinsic_name(bcx.tcx(), lhs_t);
2377+
let llfn = bcx.ccx().get_intrinsic(&name);
2378+
2379+
let val = Call(bcx, llfn, &[lhs, rhs], None);
2380+
let result = ExtractValue(bcx, val, 0); // iN operation result
2381+
let overflow = ExtractValue(bcx, val, 1); // i1 "did it overflow?"
2382+
2383+
let cond = ICmp(bcx, llvm::IntEQ, overflow, C_integral(Type::i1(bcx.ccx()), 1, false));
2384+
2385+
let expect = bcx.ccx().get_intrinsic(&"llvm.expect.i1");
2386+
Call(bcx, expect, &[cond, C_integral(Type::i1(bcx.ccx()), 0, false)], None);
2387+
2388+
let bcx =
2389+
base::with_cond(bcx, cond, |bcx|
2390+
controlflow::trans_fail(bcx, sp,
2391+
InternedString::new("arithmetic operation overflowed")));
2392+
2393+
(bcx, result)
2394+
} else {
2395+
let res = match oop {
2396+
OverflowOp::Add => Add(bcx, lhs, rhs),
2397+
OverflowOp::Sub => Sub(bcx, lhs, rhs),
2398+
OverflowOp::Mul => Mul(bcx, lhs, rhs),
2399+
};
2400+
(bcx, res)
2401+
}
2402+
}

src/librustc_trans/trans/intrinsic.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,10 @@ pub fn trans_intrinsic_call<'a, 'blk, 'tcx>(mut bcx: Block<'blk, 'tcx>,
506506
with_overflow_intrinsic(bcx, "llvm.umul.with.overflow.i64", ret_ty,
507507
llargs[0], llargs[1]),
508508

509+
(_, "overflowing_add") => Add(bcx, llargs[0], llargs[1])
510+
(_, "overflowing_sub") => Sub(bcx, llargs[0], llargs[1])
511+
(_, "overflowing_mul") => Mul(bcx, llargs[0], llargs[1])
512+
509513
(_, "return_address") => {
510514
if !fcx.caller_expects_out_pointer {
511515
tcx.sess.span_err(call_info.span,

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)