From 19fef72a8c14100029e5ce39cbc5472ab091dad8 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Thu, 19 Mar 2015 19:52:08 +0100 Subject: [PATCH 1/5] Added panic-on-overflow for rhs of left and right shift expressions. This includes a slight refactoring of the `cast_shift_rhs` and related functions in `trans::base`, so that I can call them from much later in the compiler's control flow (so that we can clearly dilineate where automatic conversions of the RHS occur, versus where we check it). The rhs-checking and fallback-masking is generalized to 8- and 16-bit values, and the fallback-masking is turned on unconditionally. Fix #10183. Is this a [breaking-change]? I would argue it is not; it only adds a strict definition to what was previously undefined behavior; however, there might be code that was e.g. assuming that `1_i8 << 17` yields 0. (This happens in certain contexts and at certain optimization levels.) --- src/librustc_trans/trans/base.rs | 18 +-- src/librustc_trans/trans/consts.rs | 2 +- src/librustc_trans/trans/expr.rs | 175 +++++++++++++++++++++++++---- 3 files changed, 166 insertions(+), 29 deletions(-) diff --git a/src/librustc_trans/trans/base.rs b/src/librustc_trans/trans/base.rs index f584de7c47f..cea3adccda4 100644 --- a/src/librustc_trans/trans/base.rs +++ b/src/librustc_trans/trans/base.rs @@ -756,7 +756,7 @@ pub fn iter_structural_ty<'blk, 'tcx, F>(cx: Block<'blk, 'tcx>, } pub fn cast_shift_expr_rhs(cx: Block, - op: ast::BinOp, + op: ast::BinOp_, lhs: ValueRef, rhs: ValueRef) -> ValueRef { @@ -765,24 +765,24 @@ pub fn cast_shift_expr_rhs(cx: Block, |a,b| ZExt(cx, a, b)) } -pub fn cast_shift_const_rhs(op: ast::BinOp, +pub fn cast_shift_const_rhs(op: ast::BinOp_, lhs: ValueRef, rhs: ValueRef) -> ValueRef { cast_shift_rhs(op, lhs, rhs, |a, b| unsafe { llvm::LLVMConstTrunc(a, b.to_ref()) }, |a, b| unsafe { llvm::LLVMConstZExt(a, b.to_ref()) }) } -pub fn cast_shift_rhs(op: ast::BinOp, - lhs: ValueRef, - rhs: ValueRef, - trunc: F, - zext: G) - -> ValueRef where +fn cast_shift_rhs(op: ast::BinOp_, + lhs: ValueRef, + rhs: ValueRef, + trunc: F, + zext: G) + -> ValueRef where F: FnOnce(ValueRef, Type) -> ValueRef, G: FnOnce(ValueRef, Type) -> ValueRef, { // Shifts may have any size int on the rhs - if ast_util::is_shift_binop(op.node) { + if ast_util::is_shift_binop(op) { let mut rhs_llty = val_ty(rhs); let mut lhs_llty = val_ty(lhs); if rhs_llty.kind() == Vector { rhs_llty = rhs_llty.element_type() } diff --git a/src/librustc_trans/trans/consts.rs b/src/librustc_trans/trans/consts.rs index 2a3fcd66195..c95b29f4e7a 100644 --- a/src/librustc_trans/trans/consts.rs +++ b/src/librustc_trans/trans/consts.rs @@ -376,7 +376,7 @@ fn const_expr_unadjusted<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>, let signed = ty::type_is_signed(intype); let (te2, _) = const_expr(cx, &**e2, param_substs); - let te2 = base::cast_shift_const_rhs(b, te1, te2); + let te2 = base::cast_shift_const_rhs(b.node, te1, te2); match b.node { ast::BiAdd => { diff --git a/src/librustc_trans/trans/expr.rs b/src/librustc_trans/trans/expr.rs index c316308c618..9dd3f60ec4f 100644 --- a/src/librustc_trans/trans/expr.rs +++ b/src/librustc_trans/trans/expr.rs @@ -1765,7 +1765,6 @@ fn trans_eager_binop<'blk, 'tcx>(bcx: Block<'blk, 'tcx>, }; let is_float = ty::type_is_fp(intype); let is_signed = ty::type_is_signed(intype); - let rhs = base::cast_shift_expr_rhs(bcx, op, lhs, rhs); let info = expr_info(binop_expr); let binop_debug_loc = binop_expr.debug_loc(); @@ -1838,13 +1837,17 @@ fn trans_eager_binop<'blk, 'tcx>(bcx: Block<'blk, 'tcx>, ast::BiBitOr => Or(bcx, lhs, rhs, binop_debug_loc), ast::BiBitAnd => And(bcx, lhs, rhs, binop_debug_loc), ast::BiBitXor => Xor(bcx, lhs, rhs, binop_debug_loc), - ast::BiShl => Shl(bcx, lhs, rhs, binop_debug_loc), + ast::BiShl => { + let (newbcx, res) = with_overflow_check( + bcx, OverflowOp::Shl, info, lhs_t, lhs, rhs, binop_debug_loc); + bcx = newbcx; + res + } ast::BiShr => { - if is_signed { - AShr(bcx, lhs, rhs, binop_debug_loc) - } else { - LShr(bcx, lhs, rhs, binop_debug_loc) - } + let (newbcx, res) = with_overflow_check( + bcx, OverflowOp::Shr, info, lhs_t, lhs, rhs, binop_debug_loc); + bcx = newbcx; + res } ast::BiEq | ast::BiNe | ast::BiLt | ast::BiGe | ast::BiLe | ast::BiGt => { if is_simd { @@ -2384,9 +2387,38 @@ enum OverflowOp { Add, Sub, Mul, + Shl, + Shr, } impl OverflowOp { + fn codegen_strategy(&self) -> OverflowCodegen { + use self::OverflowCodegen::{ViaIntrinsic, ViaInputCheck}; + match *self { + OverflowOp::Add => ViaIntrinsic(OverflowOpViaIntrinsic::Add), + OverflowOp::Sub => ViaIntrinsic(OverflowOpViaIntrinsic::Sub), + OverflowOp::Mul => ViaIntrinsic(OverflowOpViaIntrinsic::Mul), + + OverflowOp::Shl => ViaInputCheck(OverflowOpViaInputCheck::Shl), + OverflowOp::Shr => ViaInputCheck(OverflowOpViaInputCheck::Shr), + } + } +} + +enum OverflowCodegen { + ViaIntrinsic(OverflowOpViaIntrinsic), + ViaInputCheck(OverflowOpViaInputCheck), +} + +enum OverflowOpViaInputCheck { Shl, Shr, } + +enum OverflowOpViaIntrinsic { Add, Sub, Mul, } + +impl OverflowOpViaIntrinsic { + fn to_intrinsic<'blk, 'tcx>(&self, bcx: Block<'blk, 'tcx>, lhs_ty: Ty) -> ValueRef { + let name = self.to_intrinsic_name(bcx.tcx(), lhs_ty); + bcx.ccx().get_intrinsic(&name) + } fn to_intrinsic_name(&self, tcx: &ty::ctxt, ty: Ty) -> &'static str { use syntax::ast::IntTy::*; use syntax::ast::UintTy::*; @@ -2408,7 +2440,7 @@ impl OverflowOp { }; match *self { - OverflowOp::Add => match new_sty { + OverflowOpViaIntrinsic::Add => match new_sty { ty_int(TyI8) => "llvm.sadd.with.overflow.i8", ty_int(TyI16) => "llvm.sadd.with.overflow.i16", ty_int(TyI32) => "llvm.sadd.with.overflow.i32", @@ -2421,7 +2453,7 @@ impl OverflowOp { _ => unreachable!(), }, - OverflowOp::Sub => match new_sty { + OverflowOpViaIntrinsic::Sub => match new_sty { ty_int(TyI8) => "llvm.ssub.with.overflow.i8", ty_int(TyI16) => "llvm.ssub.with.overflow.i16", ty_int(TyI32) => "llvm.ssub.with.overflow.i32", @@ -2434,7 +2466,7 @@ impl OverflowOp { _ => unreachable!(), }, - OverflowOp::Mul => match new_sty { + OverflowOpViaIntrinsic::Mul => match new_sty { ty_int(TyI8) => "llvm.smul.with.overflow.i8", ty_int(TyI16) => "llvm.smul.with.overflow.i16", ty_int(TyI32) => "llvm.smul.with.overflow.i32", @@ -2449,16 +2481,14 @@ impl OverflowOp { }, } } -} - -fn with_overflow_check<'a, 'b>(bcx: Block<'a, 'b>, oop: OverflowOp, info: NodeIdAndSpan, - lhs_t: Ty, lhs: ValueRef, rhs: ValueRef, binop_debug_loc: DebugLoc) - -> (Block<'a, 'b>, ValueRef) { - if bcx.unreachable.get() { return (bcx, _Undef(lhs)); } - if bcx.ccx().check_overflow() { - let name = oop.to_intrinsic_name(bcx.tcx(), lhs_t); - let llfn = bcx.ccx().get_intrinsic(&name); + fn build_intrinsic_call<'blk, 'tcx>(&self, bcx: Block<'blk, 'tcx>, + info: NodeIdAndSpan, + lhs_t: Ty<'tcx>, lhs: ValueRef, + rhs: ValueRef, + binop_debug_loc: DebugLoc) + -> (Block<'blk, 'tcx>, ValueRef) { + let llfn = self.to_intrinsic(bcx, lhs_t); let val = Call(bcx, llfn, &[lhs, rhs], None, binop_debug_loc); let result = ExtractValue(bcx, val, 0); // iN operation result @@ -2477,11 +2507,118 @@ fn with_overflow_check<'a, 'b>(bcx: Block<'a, 'b>, oop: OverflowOp, info: NodeId InternedString::new("arithmetic operation overflowed"))); (bcx, result) + } +} + +impl OverflowOpViaInputCheck { + fn build_with_input_check<'blk, 'tcx>(&self, + bcx: Block<'blk, 'tcx>, + info: NodeIdAndSpan, + lhs_t: Ty<'tcx>, + lhs: ValueRef, + rhs: ValueRef, + binop_debug_loc: DebugLoc) + -> (Block<'blk, 'tcx>, ValueRef) + { + let lhs_llty = val_ty(lhs); + let rhs_llty = val_ty(rhs); + + // Panic if any bits are set outside of bits that we always + // mask in. + // + // Note that the mask's value is derived from the LHS type + // (since that is where the 32/64 distinction is relevant) but + // the mask's type must match the RHS type (since they will + // both be fed into a and-binop) + let invert_mask = !shift_mask_val(lhs_llty); + let invert_mask = C_integral(rhs_llty, invert_mask, true); + + let outer_bits = And(bcx, rhs, invert_mask, binop_debug_loc); + let cond = ICmp(bcx, llvm::IntNE, outer_bits, + C_integral(rhs_llty, 0, false), binop_debug_loc); + let result = match *self { + OverflowOpViaInputCheck::Shl => + build_unchecked_lshift(bcx, lhs, rhs, binop_debug_loc), + OverflowOpViaInputCheck::Shr => + build_unchecked_rshift(bcx, lhs_t, lhs, rhs, binop_debug_loc), + }; + let bcx = + base::with_cond(bcx, cond, |bcx| + controlflow::trans_fail(bcx, info, + InternedString::new("shift operation overflowed"))); + + (bcx, result) + } +} + +fn shift_mask_val(llty: Type) -> u64 { + // i8/u8 can shift by at most 7, i16/u16 by at most 15, etc. + llty.int_width() - 1 +} + +// To avoid UB from LLVM, these two functions mask RHS with an +// appropriate mask unconditionally (i.e. the fallback behavior for +// all shifts). For 32- and 64-bit types, this matches the semantics +// of Java. (See related discussion on #1877 and #10183.) + +fn build_unchecked_lshift<'blk, 'tcx>(bcx: Block<'blk, 'tcx>, + lhs: ValueRef, + rhs: ValueRef, + binop_debug_loc: DebugLoc) -> ValueRef { + let rhs = base::cast_shift_expr_rhs(bcx, ast::BinOp_::BiShl, lhs, rhs); + // #1877, #10183: Ensure that input is always valid + let rhs = shift_mask_rhs(bcx, rhs, binop_debug_loc); + Shl(bcx, lhs, rhs, binop_debug_loc) +} + +fn build_unchecked_rshift<'blk, 'tcx>(bcx: Block<'blk, 'tcx>, + lhs_t: Ty<'tcx>, + lhs: ValueRef, + rhs: ValueRef, + binop_debug_loc: DebugLoc) -> ValueRef { + let rhs = base::cast_shift_expr_rhs(bcx, ast::BinOp_::BiShr, lhs, rhs); + // #1877, #10183: Ensure that input is always valid + let rhs = shift_mask_rhs(bcx, rhs, binop_debug_loc); + let is_signed = ty::type_is_signed(lhs_t); + if is_signed { + AShr(bcx, lhs, rhs, binop_debug_loc) + } else { + LShr(bcx, lhs, rhs, binop_debug_loc) + } +} + +fn shift_mask_rhs<'blk, 'tcx>(bcx: Block<'blk, 'tcx>, + rhs: ValueRef, + debug_loc: DebugLoc) -> ValueRef { + let rhs_llty = val_ty(rhs); + let mask = shift_mask_val(rhs_llty); + And(bcx, rhs, C_integral(rhs_llty, mask, false), debug_loc) +} + +fn with_overflow_check<'blk, 'tcx>(bcx: Block<'blk, 'tcx>, oop: OverflowOp, info: NodeIdAndSpan, + lhs_t: Ty<'tcx>, lhs: ValueRef, + rhs: ValueRef, + binop_debug_loc: DebugLoc) + -> (Block<'blk, 'tcx>, ValueRef) { + if bcx.unreachable.get() { return (bcx, _Undef(lhs)); } + if bcx.ccx().check_overflow() { + + match oop.codegen_strategy() { + OverflowCodegen::ViaIntrinsic(oop) => + oop.build_intrinsic_call(bcx, info, lhs_t, lhs, rhs, binop_debug_loc), + OverflowCodegen::ViaInputCheck(oop) => + oop.build_with_input_check(bcx, info, lhs_t, lhs, rhs, binop_debug_loc), + } } else { let res = match oop { OverflowOp::Add => Add(bcx, lhs, rhs, binop_debug_loc), OverflowOp::Sub => Sub(bcx, lhs, rhs, binop_debug_loc), OverflowOp::Mul => Mul(bcx, lhs, rhs, binop_debug_loc), + + OverflowOp::Shl => + build_unchecked_lshift(bcx, lhs, rhs, binop_debug_loc), + OverflowOp::Shr => + build_unchecked_rshift(bcx, lhs_t, lhs, rhs, binop_debug_loc), }; (bcx, res) } From 61ff823c63d90f323872862053e928b5a9c874e4 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Thu, 19 Mar 2015 23:20:21 +0100 Subject: [PATCH 2/5] Test suite for overflowing shift operators. Note the tests have been revised to match new semantics for 8- and 16-bit values. --- src/test/run-fail/overflowing-lsh-1.rs | 19 +++++ src/test/run-fail/overflowing-lsh-2.rs | 19 +++++ src/test/run-fail/overflowing-lsh-3.rs | 19 +++++ src/test/run-fail/overflowing-lsh-4.rs | 33 ++++++++ src/test/run-fail/overflowing-rsh-1.rs | 19 +++++ src/test/run-fail/overflowing-rsh-2.rs | 19 +++++ src/test/run-fail/overflowing-rsh-3.rs | 19 +++++ src/test/run-fail/overflowing-rsh-4.rs | 33 ++++++++ src/test/run-pass/shift-near-oflo.rs | 100 +++++++++++++++++++++++++ 9 files changed, 280 insertions(+) create mode 100644 src/test/run-fail/overflowing-lsh-1.rs create mode 100644 src/test/run-fail/overflowing-lsh-2.rs create mode 100644 src/test/run-fail/overflowing-lsh-3.rs create mode 100644 src/test/run-fail/overflowing-lsh-4.rs create mode 100644 src/test/run-fail/overflowing-rsh-1.rs create mode 100644 src/test/run-fail/overflowing-rsh-2.rs create mode 100644 src/test/run-fail/overflowing-rsh-3.rs create mode 100644 src/test/run-fail/overflowing-rsh-4.rs create mode 100644 src/test/run-pass/shift-near-oflo.rs diff --git a/src/test/run-fail/overflowing-lsh-1.rs b/src/test/run-fail/overflowing-lsh-1.rs new file mode 100644 index 00000000000..54159153382 --- /dev/null +++ b/src/test/run-fail/overflowing-lsh-1.rs @@ -0,0 +1,19 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// error-pattern:thread '
' panicked at 'shift operation overflowed' +// compile-flags: -C debug-assertions + +// (Work around constant-evaluation) +fn id(x: T) -> T { x } + +fn main() { + let _x = 1_i32 << id(32); +} diff --git a/src/test/run-fail/overflowing-lsh-2.rs b/src/test/run-fail/overflowing-lsh-2.rs new file mode 100644 index 00000000000..fd3e801457c --- /dev/null +++ b/src/test/run-fail/overflowing-lsh-2.rs @@ -0,0 +1,19 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// error-pattern:thread '
' panicked at 'shift operation overflowed' +// compile-flags: -C debug-assertions + +// (Work around constant-evaluation) +fn id(x: T) -> T { x } + +fn main() { + let _x = 1 << id(-1); +} diff --git a/src/test/run-fail/overflowing-lsh-3.rs b/src/test/run-fail/overflowing-lsh-3.rs new file mode 100644 index 00000000000..58914bab3fb --- /dev/null +++ b/src/test/run-fail/overflowing-lsh-3.rs @@ -0,0 +1,19 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// error-pattern:thread '
' panicked at 'shift operation overflowed' +// compile-flags: -C debug-assertions + +// (Work around constant-evaluation) +fn id(x: T) -> T { x } + +fn main() { + let _x = 1_u64 << id(64); +} diff --git a/src/test/run-fail/overflowing-lsh-4.rs b/src/test/run-fail/overflowing-lsh-4.rs new file mode 100644 index 00000000000..e7e5e6e6b9b --- /dev/null +++ b/src/test/run-fail/overflowing-lsh-4.rs @@ -0,0 +1,33 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// error-pattern:thread '
' panicked at 'shift operation overflowed' +// compile-flags: -C debug-assertions + +// This function is checking that our automatic truncation does not +// sidestep the overflow checking. + +// (Work around constant-evaluation) +fn id(x: T) -> T { x } + +fn main() { + let x = 1_i8 << id(17); // signals overflow when checking is on + + // ... but when checking is off, the fallback will truncate the + // input to its lower three bits (= 1). Note that this is *not* + // the behavior of the x86 processor for 8- and 16-bit types, + // but it is necessary to avoid undefined behavior from LLVM. + // + // We check that here, by ensuring the result has only been + // shifted by one place; if overflow checking is turned off, then + // this assertion will pass (and the compiletest driver will + // report that the test did not produce the error expected above). + assert_eq!(x, 2_i8); +} diff --git a/src/test/run-fail/overflowing-rsh-1.rs b/src/test/run-fail/overflowing-rsh-1.rs new file mode 100644 index 00000000000..c36a16f18f8 --- /dev/null +++ b/src/test/run-fail/overflowing-rsh-1.rs @@ -0,0 +1,19 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// error-pattern:thread '
' panicked at 'shift operation overflowed' +// compile-flags: -C debug-assertions + +// (Work around constant-evaluation) +fn id(x: T) -> T { x } + +fn main() { + let _x = -1_i32 >> id(32); +} diff --git a/src/test/run-fail/overflowing-rsh-2.rs b/src/test/run-fail/overflowing-rsh-2.rs new file mode 100644 index 00000000000..f619ebe9fb4 --- /dev/null +++ b/src/test/run-fail/overflowing-rsh-2.rs @@ -0,0 +1,19 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// error-pattern:thread '
' panicked at 'shift operation overflowed' +// compile-flags: -C debug-assertions + +// (Work around constant-evaluation) +fn id(x: T) -> T { x } + +fn main() { + let _x = -1_i32 >> id(-1); +} diff --git a/src/test/run-fail/overflowing-rsh-3.rs b/src/test/run-fail/overflowing-rsh-3.rs new file mode 100644 index 00000000000..c261e195fd7 --- /dev/null +++ b/src/test/run-fail/overflowing-rsh-3.rs @@ -0,0 +1,19 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// error-pattern:thread '
' panicked at 'shift operation overflowed' +// compile-flags: -C debug-assertions + +// (Work around constant-evaluation) +fn id(x: T) -> T { x } + +fn main() { + let _x = -1_i64 >> id(64); +} diff --git a/src/test/run-fail/overflowing-rsh-4.rs b/src/test/run-fail/overflowing-rsh-4.rs new file mode 100644 index 00000000000..c8e6c918e99 --- /dev/null +++ b/src/test/run-fail/overflowing-rsh-4.rs @@ -0,0 +1,33 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// error-pattern:thread '
' panicked at 'shift operation overflowed' +// compile-flags: -C debug-assertions + +// This function is checking that our (type-based) automatic +// truncation does not sidestep the overflow checking. + +// (Work around constant-evaluation) +fn id(x: T) -> T { x } + +fn main() { + let x = 2_i8 >> id(17); // signals overflow when checking is on + + // ... but when checking is off, the fallback will truncate the + // input to its lower three bits (= 1). Note that this is *not* + // the behavior of the x86 processor for 8- and 16-bit types, + // but it is necessary to avoid undefined behavior from LLVM. + // + // We check that here, by ensuring the result is not zero; if + // overflow checking is turned off, then this assertion will pass + // (and the compiletest driver will report that the test did not + // produce the error expected above). + assert_eq!(x, 1_i8); +} diff --git a/src/test/run-pass/shift-near-oflo.rs b/src/test/run-pass/shift-near-oflo.rs new file mode 100644 index 00000000000..c656fc00fc2 --- /dev/null +++ b/src/test/run-pass/shift-near-oflo.rs @@ -0,0 +1,100 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// compile-flags: -C debug-assertions + +// Check that we do *not* overflow on a number of edge cases. +// (compare with test/run-fail/overflowing-{lsh,rsh}*.rs) + +// (Work around constant-evaluation) +fn id(x: T) -> T { x } + +fn main() { + test_left_shift(); + test_right_shift(); +} + +fn test_left_shift() { + // negative rhs can panic, but values in [0,N-1] are okay for iN + + macro_rules! tests { + ($iN:ty, $uN:ty, $max_rhs:expr, $expect_i:expr, $expect_u:expr) => { { + let x = 1 as $iN << id(0); + assert_eq!(x, 1); + let x = 1 as $uN << id(0); + assert_eq!(x, 1); + let x = 1 as $iN << id($max_rhs); + assert_eq!(x, $expect_i); + let x = 1 as $uN << id($max_rhs); + assert_eq!(x, $expect_u); + // high-order bits on LHS are silently discarded without panic. + let x = 3 as $iN << id($max_rhs); + assert_eq!(x, $expect_i); + let x = 3 as $uN << id($max_rhs); + assert_eq!(x, $expect_u); + } } + } + + let x = 1_i8 << id(0); + assert_eq!(x, 1); + let x = 1_u8 << id(0); + assert_eq!(x, 1); + let x = 1_i8 << id(7); + assert_eq!(x, std::i8::MIN); + let x = 1_u8 << id(7); + assert_eq!(x, 0x80); + // high-order bits on LHS are silently discarded without panic. + let x = 3_i8 << id(7); + assert_eq!(x, std::i8::MIN); + let x = 3_u8 << id(7); + assert_eq!(x, 0x80); + + // above is (approximately) expanded from: + tests!(i8, u8, 7, std::i8::MIN, 0x80_u8); + + tests!(i16, u16, 15, std::i16::MIN, 0x8000_u16); + tests!(i32, u32, 31, std::i32::MIN, 0x8000_0000_u32); + tests!(i64, u64, 63, std::i64::MIN, 0x8000_0000_0000_0000_u64); +} + +fn test_right_shift() { + // negative rhs can panic, but values in [0,N-1] are okay for iN + + macro_rules! tests { + ($iN:ty, $uN:ty, $max_rhs:expr, + $signbit_i:expr, $highbit_i:expr, $highbit_u:expr) => + { { + let x = 1 as $iN >> id(0); + assert_eq!(x, 1); + let x = 1 as $uN >> id(0); + assert_eq!(x, 1); + let x = $highbit_i >> id($max_rhs-1); + assert_eq!(x, 1); + let x = $highbit_u >> id($max_rhs); + assert_eq!(x, 1); + // sign-bit is carried by arithmetic right shift + let x = $signbit_i >> id($max_rhs); + assert_eq!(x, -1); + // low-order bits on LHS are silently discarded without panic. + let x = $highbit_i + 1 >> id($max_rhs-1); + assert_eq!(x, 1); + let x = $highbit_u + 1 >> id($max_rhs); + assert_eq!(x, 1); + let x = $signbit_i + 1 >> id($max_rhs); + assert_eq!(x, -1); + } } + } + + tests!(i8, u8, 7, std::i8::MIN, 0x40_i8, 0x80_u8); + tests!(i16, u16, 15, std::i16::MIN, 0x4000_u16, 0x8000_u16); + tests!(i32, u32, 31, std::i32::MIN, 0x4000_0000_u32, 0x8000_0000_u32); + tests!(i64, u64, 63, std::i64::MIN, + 0x4000_0000_0000_0000_u64, 0x8000_0000_0000_0000_u64); +} From 5e47c6655b41a1bbabb2b2f8891e0a41c9c60b5c Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Sat, 21 Mar 2015 17:57:22 +0100 Subject: [PATCH 3/5] workaround bugs in pretty-printer so that we can pass check-stage2-pretty-rpass. --- src/test/run-pass/shift-near-oflo.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/test/run-pass/shift-near-oflo.rs b/src/test/run-pass/shift-near-oflo.rs index c656fc00fc2..4ff058f3366 100644 --- a/src/test/run-pass/shift-near-oflo.rs +++ b/src/test/run-pass/shift-near-oflo.rs @@ -26,18 +26,18 @@ fn test_left_shift() { macro_rules! tests { ($iN:ty, $uN:ty, $max_rhs:expr, $expect_i:expr, $expect_u:expr) => { { - let x = 1 as $iN << id(0); + let x = (1 as $iN) << id(0); assert_eq!(x, 1); - let x = 1 as $uN << id(0); + let x = (1 as $uN) << id(0); assert_eq!(x, 1); - let x = 1 as $iN << id($max_rhs); + let x = (1 as $iN) << id($max_rhs); assert_eq!(x, $expect_i); - let x = 1 as $uN << id($max_rhs); + let x = (1 as $uN) << id($max_rhs); assert_eq!(x, $expect_u); // high-order bits on LHS are silently discarded without panic. - let x = 3 as $iN << id($max_rhs); + let x = (3 as $iN) << id($max_rhs); assert_eq!(x, $expect_i); - let x = 3 as $uN << id($max_rhs); + let x = (3 as $uN) << id($max_rhs); assert_eq!(x, $expect_u); } } } @@ -71,23 +71,23 @@ fn test_right_shift() { ($iN:ty, $uN:ty, $max_rhs:expr, $signbit_i:expr, $highbit_i:expr, $highbit_u:expr) => { { - let x = 1 as $iN >> id(0); + let x = (1 as $iN) >> id(0); assert_eq!(x, 1); - let x = 1 as $uN >> id(0); + let x = (1 as $uN) >> id(0); assert_eq!(x, 1); - let x = $highbit_i >> id($max_rhs-1); + let x = ($highbit_i) >> id($max_rhs-1); assert_eq!(x, 1); - let x = $highbit_u >> id($max_rhs); + let x = ($highbit_u) >> id($max_rhs); assert_eq!(x, 1); // sign-bit is carried by arithmetic right shift - let x = $signbit_i >> id($max_rhs); + let x = ($signbit_i) >> id($max_rhs); assert_eq!(x, -1); // low-order bits on LHS are silently discarded without panic. - let x = $highbit_i + 1 >> id($max_rhs-1); + let x = ($highbit_i + 1) >> id($max_rhs-1); assert_eq!(x, 1); - let x = $highbit_u + 1 >> id($max_rhs); + let x = ($highbit_u + 1) >> id($max_rhs); assert_eq!(x, 1); - let x = $signbit_i + 1 >> id($max_rhs); + let x = ($signbit_i + 1) >> id($max_rhs); assert_eq!(x, -1); } } } From 4dfec6cab51619b98eacc27d4521d2e7162f2d50 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Sun, 22 Mar 2015 23:53:06 +0100 Subject: [PATCH 4/5] placate check-pretty and pretty-printer bug; see also issue 23623. --- src/test/run-fail/overflowing-lsh-4.rs | 3 ++- src/test/run-fail/overflowing-rsh-4.rs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/test/run-fail/overflowing-lsh-4.rs b/src/test/run-fail/overflowing-lsh-4.rs index e7e5e6e6b9b..ed25876cec4 100644 --- a/src/test/run-fail/overflowing-lsh-4.rs +++ b/src/test/run-fail/overflowing-lsh-4.rs @@ -18,7 +18,8 @@ fn id(x: T) -> T { x } fn main() { - let x = 1_i8 << id(17); // signals overflow when checking is on + // this signals overflow when checking is on + let x = 1_i8 << id(17); // ... but when checking is off, the fallback will truncate the // input to its lower three bits (= 1). Note that this is *not* diff --git a/src/test/run-fail/overflowing-rsh-4.rs b/src/test/run-fail/overflowing-rsh-4.rs index c8e6c918e99..6e79a13d4e1 100644 --- a/src/test/run-fail/overflowing-rsh-4.rs +++ b/src/test/run-fail/overflowing-rsh-4.rs @@ -18,7 +18,8 @@ fn id(x: T) -> T { x } fn main() { - let x = 2_i8 >> id(17); // signals overflow when checking is on + // this signals overflow when checking is on + let x = 2_i8 >> id(17); // ... but when checking is off, the fallback will truncate the // input to its lower three bits (= 1). Note that this is *not* From bb9d210c99ab248e81598d70c39c3968ab9d09eb Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Mon, 23 Mar 2015 07:04:15 -0700 Subject: [PATCH 5/5] Fix shift-overflow in very old run-pass test. --- src/test/run-pass/over-constrained-vregs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/run-pass/over-constrained-vregs.rs b/src/test/run-pass/over-constrained-vregs.rs index 1118638fe07..c2b42ac1c81 100644 --- a/src/test/run-pass/over-constrained-vregs.rs +++ b/src/test/run-pass/over-constrained-vregs.rs @@ -11,7 +11,7 @@ // Regression test for issue #152. pub fn main() { let mut b: uint = 1_usize; - while b <= 32_usize { + while b < std::mem::size_of::() { 0_usize << b; b <<= 1_usize; println!("{}", b);