Skip to content

Commit d77daf9

Browse files
celinvaltedinski
authored andcommitted
Fix assert!() and panic!() code generation (rust-lang#687)
I refactored how we codegen panic statements so it now it terminate the program per its spec. I have also removed the hack we had to try to get the assert location. Since we currently do not support stack unwinding, the panic codegen will always terminate immediately and it will not try to unwind the stack. I added an option to RMC to force the compiler to use abort as the panic strategy and a check to rmc codegen that will fail if the user tries to override that. Another note is that we currently do not support `#[track_caller]` and I have not changed that. This change fixes rust-lang#67, fixes rust-lang#466, fixes rust-lang#543, and fixes rust-lang#636. This change also mitigates rust-lang#545.
1 parent 3cdc9a9 commit d77daf9

File tree

30 files changed

+332
-155
lines changed

30 files changed

+332
-155
lines changed

compiler/cbmc/src/goto_program/builtin.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use super::{Expr, Location, Symbol, Type};
66

77
#[derive(Debug, Clone, Copy)]
88
pub enum BuiltinFn {
9+
Abort,
910
CProverAssert,
1011
CProverAssume,
1112
Calloc,
@@ -63,6 +64,7 @@ pub enum BuiltinFn {
6364
impl ToString for BuiltinFn {
6465
fn to_string(&self) -> String {
6566
match self {
67+
Abort => "abort",
6668
CProverAssert => "__CPROVER_assert",
6769
CProverAssume => "__CPROVER_assume",
6870
Calloc => "calloc",
@@ -124,6 +126,7 @@ impl ToString for BuiltinFn {
124126
impl BuiltinFn {
125127
pub fn param_types(&self) -> Vec<Type> {
126128
match self {
129+
Abort => vec![],
127130
CProverAssert => vec![Type::bool(), Type::c_char().to_pointer()],
128131
CProverAssume => vec![Type::bool()],
129132
Calloc => vec![Type::size_t(), Type::size_t()],
@@ -178,6 +181,7 @@ impl BuiltinFn {
178181

179182
pub fn return_type(&self) -> Type {
180183
match self {
184+
Abort => Type::empty(),
181185
CProverAssert => Type::empty(),
182186
CProverAssume => Type::empty(),
183187
Calloc => Type::void_pointer(),
@@ -235,6 +239,7 @@ impl BuiltinFn {
235239

236240
pub fn list_all() -> Vec<BuiltinFn> {
237241
vec![
242+
Abort,
238243
CProverAssert,
239244
CProverAssume,
240245
Calloc,

compiler/rustc_codegen_rmc/src/codegen/span.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,19 @@ impl<'tcx> GotocCtx<'tcx> {
2727
)
2828
}
2929

30+
/// Get the location of the caller. This will attempt to reach the macro caller.
31+
/// This function uses rustc_span methods designed to returns span for the macro which
32+
/// originally caused the expansion to happen.
33+
/// Note: The API stops backtracing at include! boundary.
34+
pub fn codegen_caller_span(&self, sp: &Option<Span>) -> Location {
35+
if let Some(span) = sp {
36+
let topmost = span.ctxt().outer_expn().expansion_cause().unwrap_or(*span);
37+
self.codegen_span(&topmost)
38+
} else {
39+
Location::none()
40+
}
41+
}
42+
3043
pub fn codegen_span_option(&self, sp: Option<Span>) -> Location {
3144
sp.map_or(Location::none(), |x| self.codegen_span(&x))
3245
}

compiler/rustc_codegen_rmc/src/codegen/statement.rs

Lines changed: 37 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use super::typ::TypeExt;
44
use super::typ::FN_RETURN_VOID_VAR_NAME;
55
use crate::{GotocCtx, VtableCtx};
6-
use cbmc::goto_program::{BuiltinFn, Expr, Location, Stmt, Type};
6+
use cbmc::goto_program::{BuiltinFn, Expr, ExprValue, Location, Stmt, Type};
77
use rustc_hir::def_id::DefId;
88
use rustc_middle::mir;
99
use rustc_middle::mir::{
@@ -482,54 +482,28 @@ impl<'tcx> GotocCtx<'tcx> {
482482
}
483483
}
484484

485-
pub fn codegen_panic(&mut self, span: Option<Span>, fargs: Vec<Expr>) -> Stmt {
485+
pub fn codegen_panic(&self, span: Option<Span>, fargs: Vec<Expr>) -> Stmt {
486486
// CBMC requires that the argument to the assertion must be a string constant.
487487
// If there is one in the MIR, use it; otherwise, explain that we can't.
488-
// TODO: give a better message here.
489-
let arg = match fargs[0].struct_expr_values() {
490-
Some(values) => values[0].clone(),
491-
_ => Expr::string_constant(
492-
"This is a placeholder assertion message; the rust message requires dynamic string formatting, which is not supported by CBMC",
493-
),
494-
};
488+
assert!(!fargs.is_empty(), "Panic requires a string message");
489+
let msg = extract_const_message(&fargs[0]).unwrap_or(String::from(
490+
"This is a placeholder message; RMC doesn't support message formatted at runtime",
491+
));
495492

496-
let loc = self.codegen_span_option(span);
497-
let cbb = self.current_fn().current_bb();
498-
499-
// TODO: is it proper?
500-
//
501-
// [assert!(expr)] generates code like
502-
// if !expr { panic() }
503-
// thus when we compile [panic], we would like to continue with
504-
// the code following [assert!(expr)] as well as display the panic
505-
// location using the assert's location.
506-
let preds = &self.current_fn().mir().predecessors()[cbb];
507-
if preds.len() == 1 {
508-
let pred: &BasicBlock = preds.first().unwrap();
509-
let pred_bbd = &self.current_fn().mir()[*pred];
510-
let pterm = pred_bbd.terminator();
511-
match pterm.successors().find(|bb| **bb != cbb) {
512-
None => self.codegen_assert_false(arg, loc),
513-
Some(alt) => {
514-
let loc = self.codegen_span(&pterm.source_info.span);
515-
Stmt::block(
516-
vec![
517-
self.codegen_assert_false(arg, loc.clone()),
518-
Stmt::goto(self.current_fn().find_label(alt), Location::none()),
519-
],
520-
loc,
521-
)
522-
}
523-
}
524-
} else {
525-
self.codegen_assert_false(arg, loc)
526-
}
493+
self.codegen_fatal_error(&msg, span)
527494
}
528495

529-
// By the time we get this, the string constant has been codegenned.
530-
// TODO: make this case also use the Stmt::assert_false() constructor
531-
pub fn codegen_assert_false(&mut self, err: Expr, loc: Location) -> Stmt {
532-
BuiltinFn::CProverAssert.call(vec![Expr::bool_false(), err], loc.clone()).as_stmt(loc)
496+
// Generate code for fatal error which should trigger an assertion failure and abort the
497+
// execution.
498+
pub fn codegen_fatal_error(&self, msg: &str, span: Option<Span>) -> Stmt {
499+
let loc = self.codegen_caller_span(&span);
500+
Stmt::block(
501+
vec![
502+
Stmt::assert_false(msg, loc.clone()),
503+
BuiltinFn::Abort.call(vec![], loc.clone()).as_stmt(loc.clone()),
504+
],
505+
loc,
506+
)
533507
}
534508

535509
pub fn codegen_statement(&mut self, stmt: &Statement<'tcx>) -> Stmt {
@@ -661,3 +635,22 @@ impl<'tcx> GotocCtx<'tcx> {
661635
.with_location(self.codegen_span(&stmt.source_info.span))
662636
}
663637
}
638+
639+
/// Tries to extract a string message from an `Expr`.
640+
/// If the expression represents a pointer to a string constant, this will return the string
641+
/// constant. Otherwise, return `None`.
642+
fn extract_const_message(arg: &Expr) -> Option<String> {
643+
match arg.value() {
644+
ExprValue::Struct { values } => match &values[0].value() {
645+
ExprValue::AddressOf(address) => match address.value() {
646+
ExprValue::Index { array, .. } => match array.value() {
647+
ExprValue::StringConstant { s } => Some(s.to_string()),
648+
_ => None,
649+
},
650+
_ => None,
651+
},
652+
_ => None,
653+
},
654+
_ => None,
655+
}
656+
}

compiler/rustc_codegen_rmc/src/compiler_interface.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use rustc_middle::ty::{self, TyCtxt};
2121
use rustc_session::config::{OutputFilenames, OutputType};
2222
use rustc_session::cstore::MetadataLoaderDyn;
2323
use rustc_session::Session;
24+
use rustc_target::spec::PanicStrategy;
2425
use std::collections::BTreeMap;
2526
use std::io::BufWriter;
2627
use std::iter::FromIterator;
@@ -62,12 +63,10 @@ impl CodegenBackend for GotocCodegenBackend {
6263
) -> Box<dyn Any> {
6364
use rustc_hir::def_id::LOCAL_CRATE;
6465

65-
if !tcx.sess.overflow_checks() {
66-
tcx.sess.err("RMC requires overflow checks in order to provide a sound analysis.")
67-
}
68-
6966
super::utils::init();
7067

68+
check_options(&tcx.sess);
69+
7170
let codegen_units: &'tcx [CodegenUnit<'_>] = tcx.collect_and_partition_mono_items(()).1;
7271
let mut c = GotocCtx::new(tcx);
7372

@@ -191,6 +190,21 @@ impl CodegenBackend for GotocCodegenBackend {
191190
}
192191
}
193192

193+
fn check_options(session: &Session) {
194+
if !session.overflow_checks() {
195+
session.err("RMC requires overflow checks in order to provide a sound analysis.");
196+
}
197+
198+
if session.panic_strategy() != PanicStrategy::Abort {
199+
session.err(
200+
"RMC can only handle abort panic strategy (-C panic=abort). See for more details \
201+
https://github.com/model-checking/rmc/issues/692",
202+
);
203+
}
204+
205+
session.abort_if_errors();
206+
}
207+
194208
fn write_file<T>(base_filename: &PathBuf, extension: &str, source: &T)
195209
where
196210
T: serde::Serialize,

compiler/rustc_codegen_rmc/src/overrides/hooks.rs

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
//! It would be too nasty if we spread around these sort of undocumented hooks in place, so
99
//! this module addresses this issue.
1010
11-
use crate::utils::{instance_name_is, instance_name_starts_with};
11+
use crate::utils::instance_name_starts_with;
1212
use crate::GotocCtx;
1313
use cbmc::goto_program::{BuiltinFn, Expr, Location, Stmt, Symbol, Type};
1414
use cbmc::NO_PRETTY_NAME;
@@ -197,9 +197,11 @@ struct Panic;
197197

198198
impl<'tcx> GotocHook<'tcx> for Panic {
199199
fn hook_applies(&self, tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) -> bool {
200-
output_of_instance_is_never(tcx, instance)
201-
&& (instance_name_is(tcx, instance, "begin_panic")
202-
|| instance_name_is(tcx, instance, "panic"))
200+
let def_id = instance.def.def_id();
201+
Some(def_id) == tcx.lang_items().panic_fn()
202+
|| Some(def_id) == tcx.lang_items().panic_display()
203+
|| Some(def_id) == tcx.lang_items().panic_fmt()
204+
|| Some(def_id) == tcx.lang_items().begin_panic_fn()
203205
}
204206

205207
fn handle(
@@ -231,15 +233,11 @@ impl<'tcx> GotocHook<'tcx> for Nevers {
231233
_target: Option<BasicBlock>,
232234
span: Option<Span>,
233235
) -> Stmt {
234-
let loc = tcx.codegen_span_option(span);
235-
// _target must be None due to how rust compiler considers it
236-
Stmt::assert_false(
237-
&format!(
238-
"a panicking function {} is invoked",
239-
with_no_trimmed_paths(|| tcx.tcx.def_path_str(instance.def_id()))
240-
),
241-
loc,
242-
)
236+
let msg = format!(
237+
"a panicking function {} is invoked",
238+
with_no_trimmed_paths(|| tcx.tcx.def_path_str(instance.def_id()))
239+
);
240+
tcx.codegen_fatal_error(&msg, span)
243241
}
244242
}
245243

compiler/rustc_codegen_rmc/src/utils/names.rs

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -144,16 +144,3 @@ pub fn instance_name_starts_with(
144144
}
145145
false
146146
}
147-
148-
/// Helper function to determine if the function is exactly `expected`
149-
// TODO: rationalize how we match the hooks https://github.com/model-checking/rmc/issues/130
150-
pub fn instance_name_is(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>, expected: &str) -> bool {
151-
let def_path = tcx.def_path(instance.def.def_id());
152-
if let Some(data) = def_path.data.last() {
153-
match data.data.name() {
154-
DefPathDataName::Named(name) => return name.to_string() == expected,
155-
DefPathDataName::Anon { .. } => (),
156-
}
157-
}
158-
false
159-
}

scripts/rmc-rustc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ else
6565
set_rmc_lib_path
6666
RMC_FLAGS="-Z codegen-backend=gotoc \
6767
-C overflow-checks=on \
68+
-C panic=abort \
69+
-Z panic_abort_tests=yes \
6870
-Z trim-diagnostic-paths=no \
6971
-Z human_readable_cgu_names \
7072
--cfg=rmc \

src/test/expected/abort/expected

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
line 12 a panicking function std::process::abort is invoked: FAILURE
2+
line 16 a panicking function std::process::abort is invoked: SUCCESS
3+

src/test/expected/abort/main.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
//
4+
// Test that the abort() function is respected and nothing beyond it will execute.
5+
6+
use std::process;
7+
8+
pub fn main() {
9+
for i in 0..4 {
10+
if i == 1 {
11+
// This comes first and it should be reachable.
12+
process::abort();
13+
}
14+
if i == 2 {
15+
// This should never happen.
16+
process::abort();
17+
}
18+
}
19+
assert!(false, "This is unreachable");
20+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
line 12 assertion failed: false: FAILURE
2+
line 17 This is a placeholder message; RMC doesn't support message formatted at runtime: FAILURE
3+
line 21 Fail with custom static message: FAILURE
4+
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+
// Check the location of the assert statement when using assert!(false);
5+
6+
fn any_bool() -> bool {
7+
rmc::nondet()
8+
}
9+
10+
pub fn main() {
11+
if any_bool() {
12+
assert!(false);
13+
}
14+
15+
if any_bool() {
16+
let s = "Fail with custom runtime message";
17+
assert!(false, "{}", s);
18+
}
19+
20+
if any_bool() {
21+
assert!(false, "Fail with custom static message");
22+
}
23+
}
24+
25+
#[inline(always)]
26+
#[track_caller]
27+
fn check_caller(b: bool) {
28+
assert!(b);
29+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
line 6 This should fail and stop the execution: FAILURE
2+
line 7 This should be unreachable: SUCCESS
3+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
pub fn main() {
5+
for i in 0..4 {
6+
debug_assert!(i > 0, "This should fail and stop the execution");
7+
assert!(i == 0, "This should be unreachable");
8+
}
9+
}

src/test/expected/binop/main.rs

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -56,19 +56,22 @@ fn ibxor_test(a: i32, b: i32, correct: i32, wrong: i32) {
5656
}
5757

5858
fn main() {
59-
iadd_test(1, 2, 3, 4);
60-
isub_test(3, 4, -1, 0);
61-
imul_test(5, 6, 30, 60);
62-
idiv_test(8, 2, 4, 5);
63-
idiv_test(9, 2, 4, 5);
64-
imod_test(9, 3, 0, 1);
65-
imod_test(10, 3, 1, 2);
66-
ishl_test(2, 3, 16, 8);
67-
ishr_test(8, 3, 1, 2);
68-
ishr_test(-1, 2, -1, 1073741823);
69-
ushr_test(4294967292, 2, 1073741823, 2);
70-
iband_test(0, 2389034, 0, 2389034);
71-
iband_test(3, 10, 2, 3);
72-
ibor_test(0, 2389034, 2389034, 0);
73-
ibxor_test(0, 2389034, 2389034, 0);
59+
match rmc::nondet::<u8>() {
60+
0 => iadd_test(1, 2, 3, 4),
61+
1 => isub_test(3, 4, -1, 0),
62+
2 => imul_test(5, 6, 30, 60),
63+
3 => idiv_test(8, 2, 4, 5),
64+
4 => idiv_test(9, 2, 4, 5),
65+
5 => imod_test(9, 3, 0, 1),
66+
6 => imod_test(10, 3, 1, 2),
67+
7 => ishl_test(2, 3, 16, 8),
68+
8 => ishr_test(8, 3, 1, 2),
69+
9 => ishr_test(-1, 2, -1, 1073741823),
70+
10 => ushr_test(4294967292, 2, 1073741823, 2),
71+
11 => iband_test(0, 2389034, 0, 2389034),
72+
12 => iband_test(3, 10, 2, 3),
73+
13 => ibor_test(0, 2389034, 2389034, 0),
74+
14 => ibxor_test(0, 2389034, 2389034, 0),
75+
_ => {}
76+
}
7477
}

src/test/expected/closure/expected

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
resume instruction: SUCCESS
21
line 18 attempt to compute `move _8 + move _9`, which would overflow: SUCCESS
32
line 18 attempt to compute `move _5 + move _6`, which would overflow: SUCCESS
43
line 18 attempt to compute `(*((*_1).0: &mut i32)) + move _4`, which would overflow: SUCCESS

0 commit comments

Comments
 (0)