Skip to content

Commit 60e56a9

Browse files
celinvaltedinski
authored andcommitted
Fix spurious overflow counter examples. (rust-lang#558) (rust-lang#647)
We cannot easily specify which arithmetic operations are wrapping ones to CBMC. Thus, CBMC was generating overflow checks for wrapping operations which were generating spurious failures. This change disables some of those CBMC checks. We replace them by using rustc overflow checks for regular operations and by explicitly adding checks to unchecked operations.
1 parent 326b217 commit 60e56a9

File tree

25 files changed

+305
-65
lines changed

25 files changed

+305
-65
lines changed

compiler/cbmc/src/goto_program/expr.rs

+8-3
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ pub enum UnaryOperand {
225225
UnaryMinus,
226226
}
227227

228-
/// The return type for `__builtin_op_overflow` operations
228+
/// The return type for `__CPROVER_overflow_op` operations
229229
pub struct ArithmeticOverflowResult {
230230
/// If overflow did not occur, the result of the operation. Otherwise undefined.
231231
pub result: Expr,
@@ -849,8 +849,13 @@ impl Expr {
849849
// Floating Point Equalities
850850
IeeeFloatEqual | IeeeFloatNotequal => lhs.typ == rhs.typ && lhs.typ.is_floating_point(),
851851
// Overflow flags
852-
OverflowMinus | OverflowMult | OverflowPlus => {
853-
lhs.typ == rhs.typ && lhs.typ.is_integer()
852+
OverflowMinus => {
853+
(lhs.typ == rhs.typ && (lhs.typ.is_pointer() || lhs.typ.is_numeric()))
854+
|| (lhs.typ.is_pointer() && rhs.typ.is_integer())
855+
}
856+
OverflowMult | OverflowPlus => {
857+
(lhs.typ == rhs.typ && lhs.typ.is_integer())
858+
|| (lhs.typ.is_pointer() && rhs.typ.is_integer())
854859
}
855860
}
856861
}

compiler/rustc_codegen_rmc/src/codegen/intrinsic.rs

+66-11
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,60 @@ impl<'tcx> GotocCtx<'tcx> {
130130
}};
131131
}
132132

133+
// Intrinsics which encode a simple arithmetic operation with overflow check
134+
macro_rules! codegen_op_with_overflow_check {
135+
($f:ident) => {{
136+
let a = fargs.remove(0);
137+
let b = fargs.remove(0);
138+
let res = a.$f(b);
139+
let check = Stmt::assert(
140+
res.overflowed.not(),
141+
format!("attempt to compute {} which would overflow", intrinsic).as_str(),
142+
loc,
143+
);
144+
let expr_place = self.codegen_expr_to_place(p, res.result);
145+
Stmt::block(vec![expr_place, check], loc)
146+
}};
147+
}
148+
149+
// Intrinsics which encode a SIMD arithmetic operation with overflow check.
150+
// We expand the overflow check because CBMC overflow operations don't accept array as
151+
// argument.
152+
macro_rules! codegen_simd_with_overflow_check {
153+
($op:ident, $overflow:ident) => {{
154+
let a = fargs.remove(0);
155+
let b = fargs.remove(0);
156+
let mut check = Expr::bool_false();
157+
if let Type::Vector { size, .. } = a.typ() {
158+
let a_size = size;
159+
if let Type::Vector { size, .. } = b.typ() {
160+
let b_size = size;
161+
assert_eq!(a_size, b_size, "Expected same length vectors",);
162+
for i in 0..*a_size {
163+
// create expression
164+
let index = Expr::int_constant(i, Type::ssize_t());
165+
let v_a = a.clone().index_array(index.clone());
166+
let v_b = b.clone().index_array(index);
167+
check = check.or(v_a.$overflow(v_b));
168+
}
169+
}
170+
}
171+
let check_stmt = Stmt::assert(
172+
check.not(),
173+
format!("attempt to compute {} which would overflow", intrinsic).as_str(),
174+
loc,
175+
);
176+
let res = a.$op(b);
177+
let expr_place = self.codegen_expr_to_place(p, res);
178+
Stmt::block(vec![expr_place, check_stmt], loc)
179+
}};
180+
}
181+
182+
// Intrinsics which encode a simple wrapping arithmetic operation
183+
macro_rules! codegen_wrapping_op {
184+
($f:ident) => {{ codegen_intrinsic_binop!($f) }};
185+
}
186+
133187
// Intrinsics which encode a simple binary operation
134188
macro_rules! codegen_intrinsic_boolean_binop {
135189
($f:ident) => {{ self.binop(p, fargs, |a, b| a.$f(b).cast_to(Type::c_bool())) }};
@@ -203,6 +257,7 @@ impl<'tcx> GotocCtx<'tcx> {
203257
// *var1 = op(*var1, var2);
204258
// var = tmp;
205259
// -------------------------
260+
// Note: Atomic arithmetic operations wrap around on overflow.
206261
macro_rules! codegen_atomic_binop {
207262
($op: ident) => {{
208263
warn!("RMC does not support concurrency for now. {} treated as a sequential operation.", intrinsic);
@@ -227,7 +282,7 @@ impl<'tcx> GotocCtx<'tcx> {
227282
match intrinsic {
228283
"abort" => Stmt::assert_false("abort intrinsic", loc),
229284
"add_with_overflow" => codegen_op_with_overflow!(add_overflow),
230-
"arith_offset" => codegen_intrinsic_binop!(plus),
285+
"arith_offset" => codegen_wrapping_op!(plus),
231286
"assert_inhabited" => {
232287
let ty = instance.substs.type_at(0);
233288
let layout = self.layout_of(ty);
@@ -356,7 +411,7 @@ impl<'tcx> GotocCtx<'tcx> {
356411
"nearbyintf32" => codegen_simple_intrinsic!(Nearbyintf),
357412
"nearbyintf64" => codegen_simple_intrinsic!(Nearbyint),
358413
"needs_drop" => codegen_intrinsic_const!(),
359-
"offset" => codegen_intrinsic_binop!(plus),
414+
"offset" => codegen_op_with_overflow_check!(add_overflow),
360415
"powf32" => codegen_simple_intrinsic!(Powf),
361416
"powf64" => codegen_simple_intrinsic!(Pow),
362417
"powif32" => codegen_simple_intrinsic!(Powif),
@@ -376,7 +431,7 @@ impl<'tcx> GotocCtx<'tcx> {
376431
"saturating_sub" => codegen_intrinsic_binop_with_mm!(saturating_sub),
377432
"sinf32" => codegen_simple_intrinsic!(Sinf),
378433
"sinf64" => codegen_simple_intrinsic!(Sin),
379-
"simd_add" => codegen_intrinsic_binop!(plus),
434+
"simd_add" => codegen_simd_with_overflow_check!(plus, add_overflow_p),
380435
"simd_and" => codegen_intrinsic_binop!(bitand),
381436
"simd_div" => codegen_intrinsic_binop!(div),
382437
"simd_eq" => codegen_intrinsic_binop!(eq),
@@ -390,7 +445,7 @@ impl<'tcx> GotocCtx<'tcx> {
390445
"simd_insert" => self.codegen_intrinsic_simd_insert(fargs, p, cbmc_ret_ty, loc),
391446
"simd_le" => codegen_intrinsic_binop!(le),
392447
"simd_lt" => codegen_intrinsic_binop!(lt),
393-
"simd_mul" => codegen_intrinsic_binop!(mul),
448+
"simd_mul" => codegen_simd_with_overflow_check!(mul, mul_overflow_p),
394449
"simd_ne" => codegen_intrinsic_binop!(neq),
395450
"simd_or" => codegen_intrinsic_binop!(bitor),
396451
"simd_rem" => codegen_intrinsic_binop!(rem),
@@ -404,7 +459,7 @@ impl<'tcx> GotocCtx<'tcx> {
404459
}
405460
}
406461
// "simd_shuffle#" => handled in an `if` preceding this match
407-
"simd_sub" => codegen_intrinsic_binop!(sub),
462+
"simd_sub" => codegen_simd_with_overflow_check!(sub, sub_overflow_p),
408463
"simd_xor" => codegen_intrinsic_binop!(bitxor),
409464
"size_of" => codegen_intrinsic_const!(),
410465
"size_of_val" => codegen_size_align!(size),
@@ -422,9 +477,9 @@ impl<'tcx> GotocCtx<'tcx> {
422477
"unaligned_volatile_load" => {
423478
self.codegen_expr_to_place(p, fargs.remove(0).dereference())
424479
}
425-
"unchecked_add" => codegen_intrinsic_binop!(plus),
480+
"unchecked_add" => codegen_op_with_overflow_check!(add_overflow),
426481
"unchecked_div" => codegen_intrinsic_binop!(div),
427-
"unchecked_mul" => codegen_intrinsic_binop!(mul),
482+
"unchecked_mul" => codegen_op_with_overflow_check!(mul_overflow),
428483
"unchecked_rem" => codegen_intrinsic_binop!(rem),
429484
"unchecked_shl" => codegen_intrinsic_binop!(shl),
430485
"unchecked_shr" => {
@@ -434,15 +489,15 @@ impl<'tcx> GotocCtx<'tcx> {
434489
codegen_intrinsic_binop!(lshr)
435490
}
436491
}
437-
"unchecked_sub" => codegen_intrinsic_binop!(sub),
492+
"unchecked_sub" => codegen_op_with_overflow_check!(sub_overflow),
438493
"unlikely" => self.codegen_expr_to_place(p, fargs.remove(0)),
439494
"unreachable" => Stmt::assert_false("unreachable", loc),
440495
"volatile_copy_memory" => codegen_intrinsic_copy!(Memmove),
441496
"volatile_copy_nonoverlapping_memory" => codegen_intrinsic_copy!(Memcpy),
442497
"volatile_load" => self.codegen_expr_to_place(p, fargs.remove(0).dereference()),
443-
"wrapping_add" => codegen_intrinsic_binop!(plus),
444-
"wrapping_mul" => codegen_intrinsic_binop!(mul),
445-
"wrapping_sub" => codegen_intrinsic_binop!(sub),
498+
"wrapping_add" => codegen_wrapping_op!(plus),
499+
"wrapping_mul" => codegen_wrapping_op!(mul),
500+
"wrapping_sub" => codegen_wrapping_op!(sub),
446501
"write_bytes" => {
447502
let dst = fargs.remove(0).cast_to(Type::void_pointer());
448503
let val = fargs.remove(0).cast_to(Type::c_int());

compiler/rustc_codegen_rmc/src/codegen/rvalue.rs

+1
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,7 @@ impl<'tcx> GotocCtx<'tcx> {
470470
let relative_discr = if *niche_start == 0 {
471471
niche_val
472472
} else {
473+
// This should be a wrapping sub.
473474
niche_val.sub(Expr::int_constant(*niche_start, discr_ty.clone()))
474475
};
475476
let relative_max =

compiler/rustc_codegen_rmc/src/compiler_interface.rs

+4
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ impl CodegenBackend for GotocCodegenBackend {
5858
) -> Box<dyn Any> {
5959
use rustc_hir::def_id::LOCAL_CRATE;
6060

61+
if !tcx.sess.overflow_checks() {
62+
tcx.sess.err("RMC requires overflow checks in order to provide a sound analysis.")
63+
}
64+
6165
super::utils::init();
6266

6367
let codegen_units: &'tcx [CodegenUnit<'_>] = tcx.collect_and_partition_mono_items(()).1;

scripts/rmc-rustc

+1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ then
5454
else
5555
set_rmc_lib_path
5656
RMC_FLAGS="-Z codegen-backend=gotoc \
57+
-C overflow-checks=on \
5758
-Z trim-diagnostic-paths=no \
5859
-Z human_readable_cgu_names \
5960
--cfg=rmc \

scripts/rmc.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,15 @@
2121
MEMORY_SAFETY_CHECKS = ["--bounds-check",
2222
"--pointer-check",
2323
"--pointer-primitive-check"]
24+
25+
# We no longer use --(un)signed-overflow-check" by default since rust already add assertions for places where wrapping
26+
# is an error.
2427
OVERFLOW_CHECKS = ["--conversion-check",
2528
"--div-by-zero-check",
2629
"--float-overflow-check",
2730
"--nan-check",
2831
"--pointer-overflow-check",
29-
"--signed-overflow-check",
30-
"--undefined-shift-check",
31-
"--unsigned-overflow-check"]
32+
"--undefined-shift-check"]
3233
UNWINDING_CHECKS = ["--unwinding-assertions"]
3334

3435

@@ -172,6 +173,8 @@ def run_cmd(
172173

173174
process = subprocess.CompletedProcess(None, EXIT_CODE_SUCCESS, stdout=cmd_line)
174175
else:
176+
if verbose:
177+
print(' '.join(cmd))
175178
process = subprocess.run(
176179
cmd, universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
177180
env=env, cwd=cwd)

src/test/expected/dry-run/expected

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
rmc-rustc -Z symbol-mangling-version=v0 -Z symbol_table_passes=
22
symtab2gb
33
goto-cc --function main
4-
cbmc --bounds-check --pointer-check --pointer-primitive-check --conversion-check --div-by-zero-check --float-overflow-check --nan-check --pointer-overflow-check --signed-overflow-check --undefined-shift-check --unsigned-overflow-check --unwinding-assertions --object-bits 16 --function main
4+
cbmc --bounds-check --pointer-check --pointer-primitive-check --conversion-check --div-by-zero-check --float-overflow-check --nan-check --pointer-overflow-check --undefined-shift-check --unwinding-assertions --object-bits 16 --function main

src/test/firecracker/virtio-block-parse/main.rs

-7
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0 OR MIT
33

4-
// rmc-flags: --no-overflow-checks
5-
6-
// We use `--no-overflow-checks` in this test to avoid getting
7-
// a verification failure:
8-
// [overflow.1] arithmetic overflow on unsigned + in var_6 + var_7: FAILURE
9-
// Tracking issue: https://github.com/model-checking/rmc/issues/307
10-
114
// Example from Firecracker virtio block device
125
// We test the parse function against an arbitrary guest memory
136

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
//
4+
// Check that none of these operations trigger spurious overflow checks.
5+
#![feature(unchecked_math)]
6+
7+
macro_rules! verify_no_overflow {
8+
($cf: ident, $uf: ident) => {{
9+
let a: u8 = rmc::nondet();
10+
let b: u8 = rmc::nondet();
11+
let checked = a.$cf(b);
12+
rmc::assume(checked.is_some());
13+
let unchecked = unsafe { a.$uf(b) };
14+
assert!(checked.unwrap() == unchecked);
15+
}};
16+
}
17+
18+
fn main() {
19+
verify_no_overflow!(checked_add, unchecked_add);
20+
verify_no_overflow!(checked_sub, unchecked_sub);
21+
verify_no_overflow!(checked_mul, unchecked_mul);
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
//
4+
// Check that unchecked add trigger overflow checks.
5+
// rmc-verify-fail
6+
7+
#![feature(unchecked_math)]
8+
9+
pub fn main() {
10+
let a: u8 = rmc::nondet();
11+
let b: u8 = rmc::nondet();
12+
unsafe { a.unchecked_add(b) };
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
//
4+
// Check that unchecked mul trigger overflow checks.
5+
// rmc-verify-fail
6+
7+
#![feature(unchecked_math)]
8+
9+
pub fn main() {
10+
let a: u8 = rmc::nondet();
11+
let b: u8 = rmc::nondet();
12+
unsafe { a.unchecked_mul(b) };
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
//
4+
// Check that unchecked sub trigger overflow checks.
5+
// rmc-verify-fail
6+
7+
#![feature(unchecked_math)]
8+
9+
fn main() {
10+
let a: u8 = rmc::nondet();
11+
let b: u8 = rmc::nondet();
12+
unsafe { a.unchecked_sub(b) };
13+
}

src/test/rmc/ArithOperators/unsafe.rs

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
//
4+
// Check that none of these operations trigger spurious overflow checks.
5+
6+
macro_rules! verify_no_overflow {
7+
($cf: ident, $uf: tt) => {{
8+
let a: u8 = rmc::nondet();
9+
let b: u8 = rmc::nondet();
10+
let checked = a.$cf(b);
11+
rmc::assume(checked.is_some());
12+
let unchecked = unsafe { a $uf b };
13+
assert!(checked.unwrap() == unchecked);
14+
}};
15+
}
16+
17+
fn main() {
18+
verify_no_overflow!(checked_add, +);
19+
verify_no_overflow!(checked_sub, -);
20+
verify_no_overflow!(checked_mul, *);
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
//
4+
// Check that regular arithmetic operations in unsafe blocks still trigger overflow checks.
5+
// rmc-verify-fail
6+
// rmc-flags: --function check_add
7+
// compile-flags: --crate-type lib
8+
9+
pub fn check_add(a: u8, b: u8) {
10+
unsafe {
11+
a + b;
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
//
4+
// Check that regular arithmetic operations in unsafe blocks still trigger overflow checks.
5+
// rmc-verify-fail
6+
// rmc-flags: --function check_mul
7+
// compile-flags: --crate-type lib
8+
9+
pub fn check_add(a: u8, b: u8) {
10+
unsafe {
11+
a * b;
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
//
4+
// Check that regular arithmetic operations in unsafe blocks still trigger overflow checks.
5+
// rmc-verify-fail
6+
// rmc-flags: --function check_sub
7+
// compile-flags: --crate-type lib
8+
9+
pub fn check_sub(a: u8, b: u8) {
10+
unsafe {
11+
a - b;
12+
}
13+
}
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
//
4+
// Check that none of these operations trigger spurious overflow checks.
5+
#![feature(core_intrinsics)]
6+
7+
fn main() {
8+
let a: u8 = rmc::nondet();
9+
let b: u8 = rmc::nondet();
10+
let sum0 = core::intrinsics::wrapping_add(a, b);
11+
let sum1 = a.wrapping_add(b);
12+
let sum2 = a.checked_add(b);
13+
assert!(sum0 == sum1);
14+
assert!(sum1 >= b || sum2.is_none());
15+
assert!(sum1 >= a || sum2.is_none());
16+
}

0 commit comments

Comments
 (0)