Skip to content

Commit 4178958

Browse files
authored
Work around loop identification issues with CBMC (#2181)
CBMC has a heuristic to identify loops that identifies any jump to a previously declared basic block as the back edge of a loop. For rust, this happens often due to drop elaboration. See https://rustc-dev-guide.rust-lang.org/mir/drop-elaboration.html#drop-elaboration-1 for more details. To avoid that issue, we now codegen basic blocks in topological order.
1 parent 2b2c5f8 commit 4178958

File tree

7 files changed

+107
-16
lines changed

7 files changed

+107
-16
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
use crate::codegen_cprover_gotoc::GotocCtx;
55
use rustc_middle::mir::{BasicBlock, BasicBlockData};
6+
use tracing::debug;
67

78
impl<'tcx> GotocCtx<'tcx> {
89
/// Generates Goto-C for a basic block.
@@ -12,6 +13,7 @@ impl<'tcx> GotocCtx<'tcx> {
1213
/// This function does not return a value, but mutates state with
1314
/// `self.current_fn_mut().push_onto_block(...)`
1415
pub fn codegen_block(&mut self, bb: BasicBlock, bbd: &BasicBlockData<'tcx>) {
16+
debug!(?bb, "Codegen basicblock");
1517
self.current_fn_mut().set_current_bb(bb);
1618
let label: String = self.current_fn().find_label(&bb);
1719
// the first statement should be labelled. if there is no statements, then the

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use kani_queries::UserInput;
1212
use rustc_ast::Attribute;
1313
use rustc_hir::def::DefKind;
1414
use rustc_hir::def_id::DefId;
15+
use rustc_middle::mir::traversal::reverse_postorder;
1516
use rustc_middle::mir::{Body, HasLocalDecls, Local};
1617
use rustc_middle::ty::layout::FnAbiOf;
1718
use rustc_middle::ty::{self, Instance};
@@ -88,7 +89,7 @@ impl<'tcx> GotocCtx<'tcx> {
8889
self.codegen_function_prelude();
8990
self.codegen_declare_variables();
9091

91-
mir.basic_blocks.iter_enumerated().for_each(|(bb, bbd)| self.codegen_block(bb, bbd));
92+
reverse_postorder(mir).for_each(|(bb, bbd)| self.codegen_block(bb, bbd));
9293

9394
let loc = self.codegen_span(&mir.span);
9495
let stmts = self.current_fn_mut().extract_block();

tests/cargo-kani/small-vec/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
use smallvec::{smallvec, SmallVec};
55

66
#[kani::proof]
7+
#[kani::unwind(4)]
78
pub fn check_vec() {
89
// Create small vec with three elements.
910
let chars: SmallVec<[char; 3]> = smallvec![kani::any(), kani::any(), kani::any()];
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
//! This test checks whether dropping objects passed through
5+
//! std::sync::mpsc::channel is handled.
6+
//! This test only passes on MacOS today, so we duplicate the test for now.
7+
#![cfg(target_os = "macos")]
8+
9+
use std::sync::mpsc::*;
10+
11+
static mut CELL: i32 = 0;
12+
13+
struct DropSetCELLToOne {}
14+
15+
impl Drop for DropSetCELLToOne {
16+
fn drop(&mut self) {
17+
unsafe {
18+
CELL = 1;
19+
}
20+
}
21+
}
22+
23+
#[kani::unwind(1)]
24+
#[kani::proof]
25+
fn main() {
26+
{
27+
let (send, recv) = channel::<DropSetCELLToOne>();
28+
send.send(DropSetCELLToOne {}).unwrap();
29+
let _to_drop: DropSetCELLToOne = recv.recv().unwrap();
30+
}
31+
assert_eq!(unsafe { CELL }, 1, "Drop should be called");
32+
}

tests/kani/Drop/fixme_drop_after_moving_across_channel.rs

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,27 +9,38 @@
99
// kani::unwind(2) takes longer than 10m on a M1 Mac. For details,
1010
// please see: https://github.com/model-checking/kani/issues/1286
1111

12-
use std::sync::mpsc::*;
12+
#[cfg(target_os = "linux")]
13+
mod fixme_harness {
14+
use std::sync::mpsc::*;
1315

14-
static mut CELL: i32 = 0;
16+
static mut CELL: i32 = 0;
1517

16-
struct DropSetCELLToOne {}
18+
struct DropSetCELLToOne {}
1719

18-
impl Drop for DropSetCELLToOne {
19-
fn drop(&mut self) {
20-
unsafe {
21-
CELL = 1;
20+
impl Drop for DropSetCELLToOne {
21+
fn drop(&mut self) {
22+
unsafe {
23+
CELL = 1;
24+
}
2225
}
2326
}
27+
28+
#[kani::unwind(1)]
29+
#[kani::proof]
30+
fn main() {
31+
{
32+
let (send, recv) = channel::<DropSetCELLToOne>();
33+
send.send(DropSetCELLToOne {}).unwrap();
34+
let _to_drop: DropSetCELLToOne = recv.recv().unwrap();
35+
}
36+
assert_eq!(unsafe { CELL }, 1, "Drop should be called");
37+
}
2438
}
2539

26-
#[kani::unwind(1)]
27-
#[kani::proof]
28-
fn main() {
29-
{
30-
let (send, recv) = channel::<DropSetCELLToOne>();
31-
send.send(DropSetCELLToOne {}).unwrap();
32-
let _to_drop: DropSetCELLToOne = recv.recv().unwrap();
40+
#[cfg(target_os = "macos")]
41+
mod forced_failure {
42+
#[kani::proof]
43+
fn just_panic() {
44+
panic!("This test only fails on linux");
3345
}
34-
assert_eq!(unsafe { CELL }, 1, "Drop should be called");
3546
}

tests/kani/Loops/loop_free.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
//
4+
//! Ensure that Kani identifies that there is not loop in this code.
5+
//! This was related to https://github.com/model-checking/kani/issues/2164
6+
fn loop_free<T: Default>(b: bool, other: T) -> T {
7+
match b {
8+
true => T::default(),
9+
false => other,
10+
}
11+
}
12+
13+
/// Set the unwind to 1 so this test will fail instead of running forever.
14+
#[kani::proof]
15+
#[kani::unwind(1)]
16+
fn check_no_loop() {
17+
let b: bool = kani::any();
18+
let result = loop_free(b, 5);
19+
assert!(result == 5 || (b && result == 0))
20+
}

tests/kani/Loops/loop_with_drop.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
//
4+
//! Ensure that Kani correctly unwinds the loop with drop instructions.
5+
//! This was related to https://github.com/model-checking/kani/issues/2164
6+
7+
/// Dummy function with a for loop that only runs 2 iterations.
8+
fn bounded_loop<T: Default>(b: bool, other: T) -> T {
9+
let mut ret = other;
10+
for i in 0..2 {
11+
ret = match b {
12+
true => T::default(),
13+
false => ret,
14+
};
15+
}
16+
return ret;
17+
}
18+
19+
/// Harness that should succeed. We add a conservative loop bound.
20+
#[kani::proof]
21+
#[kani::unwind(3)]
22+
fn harness() {
23+
let _ = bounded_loop(kani::any(), ());
24+
}

0 commit comments

Comments
 (0)