Skip to content

Commit 19fef72

Browse files
committed
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.)
1 parent 7f53b94 commit 19fef72

File tree

3 files changed

+166
-29
lines changed

3 files changed

+166
-29
lines changed

src/librustc_trans/trans/base.rs

+9-9
Original file line numberDiff line numberDiff line change
@@ -756,7 +756,7 @@ pub fn iter_structural_ty<'blk, 'tcx, F>(cx: Block<'blk, 'tcx>,
756756
}
757757

758758
pub fn cast_shift_expr_rhs(cx: Block,
759-
op: ast::BinOp,
759+
op: ast::BinOp_,
760760
lhs: ValueRef,
761761
rhs: ValueRef)
762762
-> ValueRef {
@@ -765,24 +765,24 @@ pub fn cast_shift_expr_rhs(cx: Block,
765765
|a,b| ZExt(cx, a, b))
766766
}
767767

768-
pub fn cast_shift_const_rhs(op: ast::BinOp,
768+
pub fn cast_shift_const_rhs(op: ast::BinOp_,
769769
lhs: ValueRef, rhs: ValueRef) -> ValueRef {
770770
cast_shift_rhs(op, lhs, rhs,
771771
|a, b| unsafe { llvm::LLVMConstTrunc(a, b.to_ref()) },
772772
|a, b| unsafe { llvm::LLVMConstZExt(a, b.to_ref()) })
773773
}
774774

775-
pub fn cast_shift_rhs<F, G>(op: ast::BinOp,
776-
lhs: ValueRef,
777-
rhs: ValueRef,
778-
trunc: F,
779-
zext: G)
780-
-> ValueRef where
775+
fn cast_shift_rhs<F, G>(op: ast::BinOp_,
776+
lhs: ValueRef,
777+
rhs: ValueRef,
778+
trunc: F,
779+
zext: G)
780+
-> ValueRef where
781781
F: FnOnce(ValueRef, Type) -> ValueRef,
782782
G: FnOnce(ValueRef, Type) -> ValueRef,
783783
{
784784
// Shifts may have any size int on the rhs
785-
if ast_util::is_shift_binop(op.node) {
785+
if ast_util::is_shift_binop(op) {
786786
let mut rhs_llty = val_ty(rhs);
787787
let mut lhs_llty = val_ty(lhs);
788788
if rhs_llty.kind() == Vector { rhs_llty = rhs_llty.element_type() }

src/librustc_trans/trans/consts.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,7 @@ fn const_expr_unadjusted<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>,
376376
let signed = ty::type_is_signed(intype);
377377

378378
let (te2, _) = const_expr(cx, &**e2, param_substs);
379-
let te2 = base::cast_shift_const_rhs(b, te1, te2);
379+
let te2 = base::cast_shift_const_rhs(b.node, te1, te2);
380380

381381
match b.node {
382382
ast::BiAdd => {

src/librustc_trans/trans/expr.rs

+156-19
Original file line numberDiff line numberDiff line change
@@ -1765,7 +1765,6 @@ fn trans_eager_binop<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
17651765
};
17661766
let is_float = ty::type_is_fp(intype);
17671767
let is_signed = ty::type_is_signed(intype);
1768-
let rhs = base::cast_shift_expr_rhs(bcx, op, lhs, rhs);
17691768
let info = expr_info(binop_expr);
17701769

17711770
let binop_debug_loc = binop_expr.debug_loc();
@@ -1838,13 +1837,17 @@ fn trans_eager_binop<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
18381837
ast::BiBitOr => Or(bcx, lhs, rhs, binop_debug_loc),
18391838
ast::BiBitAnd => And(bcx, lhs, rhs, binop_debug_loc),
18401839
ast::BiBitXor => Xor(bcx, lhs, rhs, binop_debug_loc),
1841-
ast::BiShl => Shl(bcx, lhs, rhs, binop_debug_loc),
1840+
ast::BiShl => {
1841+
let (newbcx, res) = with_overflow_check(
1842+
bcx, OverflowOp::Shl, info, lhs_t, lhs, rhs, binop_debug_loc);
1843+
bcx = newbcx;
1844+
res
1845+
}
18421846
ast::BiShr => {
1843-
if is_signed {
1844-
AShr(bcx, lhs, rhs, binop_debug_loc)
1845-
} else {
1846-
LShr(bcx, lhs, rhs, binop_debug_loc)
1847-
}
1847+
let (newbcx, res) = with_overflow_check(
1848+
bcx, OverflowOp::Shr, info, lhs_t, lhs, rhs, binop_debug_loc);
1849+
bcx = newbcx;
1850+
res
18481851
}
18491852
ast::BiEq | ast::BiNe | ast::BiLt | ast::BiGe | ast::BiLe | ast::BiGt => {
18501853
if is_simd {
@@ -2384,9 +2387,38 @@ enum OverflowOp {
23842387
Add,
23852388
Sub,
23862389
Mul,
2390+
Shl,
2391+
Shr,
23872392
}
23882393

23892394
impl OverflowOp {
2395+
fn codegen_strategy(&self) -> OverflowCodegen {
2396+
use self::OverflowCodegen::{ViaIntrinsic, ViaInputCheck};
2397+
match *self {
2398+
OverflowOp::Add => ViaIntrinsic(OverflowOpViaIntrinsic::Add),
2399+
OverflowOp::Sub => ViaIntrinsic(OverflowOpViaIntrinsic::Sub),
2400+
OverflowOp::Mul => ViaIntrinsic(OverflowOpViaIntrinsic::Mul),
2401+
2402+
OverflowOp::Shl => ViaInputCheck(OverflowOpViaInputCheck::Shl),
2403+
OverflowOp::Shr => ViaInputCheck(OverflowOpViaInputCheck::Shr),
2404+
}
2405+
}
2406+
}
2407+
2408+
enum OverflowCodegen {
2409+
ViaIntrinsic(OverflowOpViaIntrinsic),
2410+
ViaInputCheck(OverflowOpViaInputCheck),
2411+
}
2412+
2413+
enum OverflowOpViaInputCheck { Shl, Shr, }
2414+
2415+
enum OverflowOpViaIntrinsic { Add, Sub, Mul, }
2416+
2417+
impl OverflowOpViaIntrinsic {
2418+
fn to_intrinsic<'blk, 'tcx>(&self, bcx: Block<'blk, 'tcx>, lhs_ty: Ty) -> ValueRef {
2419+
let name = self.to_intrinsic_name(bcx.tcx(), lhs_ty);
2420+
bcx.ccx().get_intrinsic(&name)
2421+
}
23902422
fn to_intrinsic_name(&self, tcx: &ty::ctxt, ty: Ty) -> &'static str {
23912423
use syntax::ast::IntTy::*;
23922424
use syntax::ast::UintTy::*;
@@ -2408,7 +2440,7 @@ impl OverflowOp {
24082440
};
24092441

24102442
match *self {
2411-
OverflowOp::Add => match new_sty {
2443+
OverflowOpViaIntrinsic::Add => match new_sty {
24122444
ty_int(TyI8) => "llvm.sadd.with.overflow.i8",
24132445
ty_int(TyI16) => "llvm.sadd.with.overflow.i16",
24142446
ty_int(TyI32) => "llvm.sadd.with.overflow.i32",
@@ -2421,7 +2453,7 @@ impl OverflowOp {
24212453

24222454
_ => unreachable!(),
24232455
},
2424-
OverflowOp::Sub => match new_sty {
2456+
OverflowOpViaIntrinsic::Sub => match new_sty {
24252457
ty_int(TyI8) => "llvm.ssub.with.overflow.i8",
24262458
ty_int(TyI16) => "llvm.ssub.with.overflow.i16",
24272459
ty_int(TyI32) => "llvm.ssub.with.overflow.i32",
@@ -2434,7 +2466,7 @@ impl OverflowOp {
24342466

24352467
_ => unreachable!(),
24362468
},
2437-
OverflowOp::Mul => match new_sty {
2469+
OverflowOpViaIntrinsic::Mul => match new_sty {
24382470
ty_int(TyI8) => "llvm.smul.with.overflow.i8",
24392471
ty_int(TyI16) => "llvm.smul.with.overflow.i16",
24402472
ty_int(TyI32) => "llvm.smul.with.overflow.i32",
@@ -2449,16 +2481,14 @@ impl OverflowOp {
24492481
},
24502482
}
24512483
}
2452-
}
24532484

2454-
2455-
fn with_overflow_check<'a, 'b>(bcx: Block<'a, 'b>, oop: OverflowOp, info: NodeIdAndSpan,
2456-
lhs_t: Ty, lhs: ValueRef, rhs: ValueRef, binop_debug_loc: DebugLoc)
2457-
-> (Block<'a, 'b>, ValueRef) {
2458-
if bcx.unreachable.get() { return (bcx, _Undef(lhs)); }
2459-
if bcx.ccx().check_overflow() {
2460-
let name = oop.to_intrinsic_name(bcx.tcx(), lhs_t);
2461-
let llfn = bcx.ccx().get_intrinsic(&name);
2485+
fn build_intrinsic_call<'blk, 'tcx>(&self, bcx: Block<'blk, 'tcx>,
2486+
info: NodeIdAndSpan,
2487+
lhs_t: Ty<'tcx>, lhs: ValueRef,
2488+
rhs: ValueRef,
2489+
binop_debug_loc: DebugLoc)
2490+
-> (Block<'blk, 'tcx>, ValueRef) {
2491+
let llfn = self.to_intrinsic(bcx, lhs_t);
24622492

24632493
let val = Call(bcx, llfn, &[lhs, rhs], None, binop_debug_loc);
24642494
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
24772507
InternedString::new("arithmetic operation overflowed")));
24782508

24792509
(bcx, result)
2510+
}
2511+
}
2512+
2513+
impl OverflowOpViaInputCheck {
2514+
fn build_with_input_check<'blk, 'tcx>(&self,
2515+
bcx: Block<'blk, 'tcx>,
2516+
info: NodeIdAndSpan,
2517+
lhs_t: Ty<'tcx>,
2518+
lhs: ValueRef,
2519+
rhs: ValueRef,
2520+
binop_debug_loc: DebugLoc)
2521+
-> (Block<'blk, 'tcx>, ValueRef)
2522+
{
2523+
let lhs_llty = val_ty(lhs);
2524+
let rhs_llty = val_ty(rhs);
2525+
2526+
// Panic if any bits are set outside of bits that we always
2527+
// mask in.
2528+
//
2529+
// Note that the mask's value is derived from the LHS type
2530+
// (since that is where the 32/64 distinction is relevant) but
2531+
// the mask's type must match the RHS type (since they will
2532+
// both be fed into a and-binop)
2533+
let invert_mask = !shift_mask_val(lhs_llty);
2534+
let invert_mask = C_integral(rhs_llty, invert_mask, true);
2535+
2536+
let outer_bits = And(bcx, rhs, invert_mask, binop_debug_loc);
2537+
let cond = ICmp(bcx, llvm::IntNE, outer_bits,
2538+
C_integral(rhs_llty, 0, false), binop_debug_loc);
2539+
let result = match *self {
2540+
OverflowOpViaInputCheck::Shl =>
2541+
build_unchecked_lshift(bcx, lhs, rhs, binop_debug_loc),
2542+
OverflowOpViaInputCheck::Shr =>
2543+
build_unchecked_rshift(bcx, lhs_t, lhs, rhs, binop_debug_loc),
2544+
};
2545+
let bcx =
2546+
base::with_cond(bcx, cond, |bcx|
2547+
controlflow::trans_fail(bcx, info,
2548+
InternedString::new("shift operation overflowed")));
2549+
2550+
(bcx, result)
2551+
}
2552+
}
2553+
2554+
fn shift_mask_val(llty: Type) -> u64 {
2555+
// i8/u8 can shift by at most 7, i16/u16 by at most 15, etc.
2556+
llty.int_width() - 1
2557+
}
2558+
2559+
// To avoid UB from LLVM, these two functions mask RHS with an
2560+
// appropriate mask unconditionally (i.e. the fallback behavior for
2561+
// all shifts). For 32- and 64-bit types, this matches the semantics
2562+
// of Java. (See related discussion on #1877 and #10183.)
2563+
2564+
fn build_unchecked_lshift<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
2565+
lhs: ValueRef,
2566+
rhs: ValueRef,
2567+
binop_debug_loc: DebugLoc) -> ValueRef {
2568+
let rhs = base::cast_shift_expr_rhs(bcx, ast::BinOp_::BiShl, lhs, rhs);
2569+
// #1877, #10183: Ensure that input is always valid
2570+
let rhs = shift_mask_rhs(bcx, rhs, binop_debug_loc);
2571+
Shl(bcx, lhs, rhs, binop_debug_loc)
2572+
}
2573+
2574+
fn build_unchecked_rshift<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
2575+
lhs_t: Ty<'tcx>,
2576+
lhs: ValueRef,
2577+
rhs: ValueRef,
2578+
binop_debug_loc: DebugLoc) -> ValueRef {
2579+
let rhs = base::cast_shift_expr_rhs(bcx, ast::BinOp_::BiShr, lhs, rhs);
2580+
// #1877, #10183: Ensure that input is always valid
2581+
let rhs = shift_mask_rhs(bcx, rhs, binop_debug_loc);
2582+
let is_signed = ty::type_is_signed(lhs_t);
2583+
if is_signed {
2584+
AShr(bcx, lhs, rhs, binop_debug_loc)
2585+
} else {
2586+
LShr(bcx, lhs, rhs, binop_debug_loc)
2587+
}
2588+
}
2589+
2590+
fn shift_mask_rhs<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
2591+
rhs: ValueRef,
2592+
debug_loc: DebugLoc) -> ValueRef {
2593+
let rhs_llty = val_ty(rhs);
2594+
let mask = shift_mask_val(rhs_llty);
2595+
And(bcx, rhs, C_integral(rhs_llty, mask, false), debug_loc)
2596+
}
2597+
2598+
fn with_overflow_check<'blk, 'tcx>(bcx: Block<'blk, 'tcx>, oop: OverflowOp, info: NodeIdAndSpan,
2599+
lhs_t: Ty<'tcx>, lhs: ValueRef,
2600+
rhs: ValueRef,
2601+
binop_debug_loc: DebugLoc)
2602+
-> (Block<'blk, 'tcx>, ValueRef) {
2603+
if bcx.unreachable.get() { return (bcx, _Undef(lhs)); }
2604+
if bcx.ccx().check_overflow() {
2605+
2606+
match oop.codegen_strategy() {
2607+
OverflowCodegen::ViaIntrinsic(oop) =>
2608+
oop.build_intrinsic_call(bcx, info, lhs_t, lhs, rhs, binop_debug_loc),
2609+
OverflowCodegen::ViaInputCheck(oop) =>
2610+
oop.build_with_input_check(bcx, info, lhs_t, lhs, rhs, binop_debug_loc),
2611+
}
24802612
} else {
24812613
let res = match oop {
24822614
OverflowOp::Add => Add(bcx, lhs, rhs, binop_debug_loc),
24832615
OverflowOp::Sub => Sub(bcx, lhs, rhs, binop_debug_loc),
24842616
OverflowOp::Mul => Mul(bcx, lhs, rhs, binop_debug_loc),
2617+
2618+
OverflowOp::Shl =>
2619+
build_unchecked_lshift(bcx, lhs, rhs, binop_debug_loc),
2620+
OverflowOp::Shr =>
2621+
build_unchecked_rshift(bcx, lhs_t, lhs, rhs, binop_debug_loc),
24852622
};
24862623
(bcx, res)
24872624
}

0 commit comments

Comments
 (0)