Skip to content

Commit 8719a89

Browse files
zhassan-awstedinski
authored andcommitted
Use 2's complement when computing the relative discriminant to avoid signed to unsigned overflow failures (rust-lang#995)
1 parent 361dc6e commit 8719a89

File tree

3 files changed

+70
-4
lines changed

3 files changed

+70
-4
lines changed

src/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,8 @@ impl<'tcx> GotocCtx<'tcx> {
447447
e.member("case", &self.symbol_table).cast_to(self.codegen_ty(res_ty))
448448
}
449449
TagEncoding::Niche { dataful_variant, niche_variants, niche_start } => {
450+
// This code follows the logic in the cranelift codegen backend:
451+
// https://github.com/rust-lang/rust/blob/05d22212e89588e7c443cc6b9bc0e4e02fdfbc8d/compiler/rustc_codegen_cranelift/src/discriminant.rs#L116
450452
let offset = match &layout.fields {
451453
FieldsShape::Arbitrary { offsets, .. } => offsets[0].bytes_usize(),
452454
_ => unreachable!("niche encoding must have arbitrary fields"),
@@ -457,8 +459,7 @@ impl<'tcx> GotocCtx<'tcx> {
457459
let relative_discr = if *niche_start == 0 {
458460
niche_val
459461
} else {
460-
// This should be a wrapping sub.
461-
niche_val.sub(Expr::int_constant(*niche_start, discr_ty.clone()))
462+
wrapping_sub(&niche_val, *niche_start)
462463
};
463464
let relative_max =
464465
niche_variants.end().as_u32() - niche_variants.start().as_u32();
@@ -467,8 +468,7 @@ impl<'tcx> GotocCtx<'tcx> {
467468
} else {
468469
relative_discr
469470
.clone()
470-
.cast_to(Type::unsigned_int(64))
471-
.le(Expr::int_constant(relative_max, Type::unsigned_int(64)))
471+
.le(Expr::int_constant(relative_max, relative_discr.typ().clone()))
472472
};
473473
let niche_discr = {
474474
let relative_discr = if relative_max == 0 {
@@ -1223,3 +1223,30 @@ impl<'tcx> GotocCtx<'tcx> {
12231223
}
12241224
}
12251225
}
1226+
1227+
/// Perform a wrapping subtraction of an Expr with a constant "expr - constant"
1228+
/// where "-" is wrapping subtraction, i.e., the result should be interpreted as
1229+
/// an unsigned value (2's complement).
1230+
fn wrapping_sub(expr: &Expr, constant: u128) -> Expr {
1231+
// While the wrapping subtraction can be done through a regular subtraction
1232+
// and then casting the result to an unsigned, doing so may result in CBMC
1233+
// flagging the operation with a signed to unsigned overflow failure (see
1234+
// https://github.com/model-checking/kani/issues/356).
1235+
// To avoid those overflow failures, the wrapping subtraction operation is
1236+
// computed as:
1237+
// if expr >= constant {
1238+
// // result is positive, so overflow may not occur
1239+
// expr - constant
1240+
// } else {
1241+
// // compute the 2's complement to avoid overflow
1242+
// expr - constant + 2^32
1243+
// }
1244+
let s64 = Type::signed_int(64);
1245+
let expr = expr.clone().cast_to(s64.clone());
1246+
let twos_complement: i64 = u32::MAX as i64 + 1 - i64::try_from(constant).unwrap();
1247+
let constant = Expr::int_constant(constant, s64.clone());
1248+
expr.clone().ge(constant.clone()).ternary(
1249+
expr.clone().sub(constant),
1250+
expr.plus(Expr::int_constant(twos_complement, s64.clone())),
1251+
)
1252+
}

tests/kani/Enum/neg_discriminant.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// This test checks that enums with negative discriminants are handled correctly
5+
6+
enum Foo {
7+
A = -500,
8+
B = -200,
9+
C = -100,
10+
D = 0,
11+
E = 1,
12+
F = 256,
13+
}
14+
15+
#[kani::proof]
16+
fn check_negative_discriminant() {
17+
let a = Some(Foo::A);
18+
let b = Some(Foo::B);
19+
let c = Some(Foo::C);
20+
let d = Some(Foo::D);
21+
let e = Some(Foo::E);
22+
let f = Some(Foo::F);
23+
let _ = assert!(matches!(a, Some(Foo::A)));
24+
let _ = assert!(matches!(b, Some(Foo::B)));
25+
let _ = assert!(matches!(c, Some(Foo::C)));
26+
let _ = assert!(matches!(d, Some(Foo::D)));
27+
let _ = assert!(matches!(e, Some(Foo::E)));
28+
let _ = assert!(matches!(f, Some(Foo::F)));
29+
}

tests/kani/LexicographicCmp/main.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// This test checks that lexicographic comparison is handled correctly
5+
6+
#[kani::proof]
7+
fn check_lexicographic_cmp() {
8+
assert!([1, 2, 3] < [1, 3, 4]);
9+
assert!("World" >= "Hello");
10+
}

0 commit comments

Comments
 (0)