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

Lines changed: 8 additions & 3 deletions
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

Lines changed: 66 additions & 11 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 4 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 6 additions & 3 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 0 additions & 7 deletions
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

Lines changed: 22 additions & 0 deletions
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+
}
Lines changed: 13 additions & 0 deletions
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+
}

0 commit comments

Comments
 (0)