Fix assert!() and panic!() code generation#687
Conversation
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 #67, fixes model-checking#466, fixes model-checking#543, and fixes model-checking#636. This change also mitigates model-checking#545.
| ) | ||
| } | ||
|
|
||
| pub fn assume_false(loc: Location) -> Self { |
There was a problem hiding this comment.
I'm actually removing this since I am adding a builtin abort()
| } | ||
| } | ||
| } else { | ||
| tracing::error!(?values, "Oh no"); |
There was a problem hiding this comment.
More informative message please?
There was a problem hiding this comment.
ooops... That was a debug statement. =)
| ); | ||
| if let Some(values) = fargs[0].struct_expr_values() { | ||
| if !values.is_empty() { | ||
| if let ExprValue::AddressOf(msg_value) = values[0].value() { |
There was a problem hiding this comment.
Are there any other times we might want to do this? Could be a utility function?
There was a problem hiding this comment.
I don't know. I figured that this code has a lot of assumptions about the format of the input, so I was thinking about leaving here for now and pulled it out if we start seeing this pattern in other places.
There was a problem hiding this comment.
I think Jai had a utility function that does this in his first PR (that's still open). Maybe just steal it from there?
| pub fn codegen_fatal_error(&self, msg: &str, span: Option<Span>) -> Stmt { | ||
| let loc = self.codegen_caller_span(&span); | ||
| Stmt::block( | ||
| vec![Stmt::assert_false(msg, loc.clone()), Stmt::assume_false(loc.clone())], |
There was a problem hiding this comment.
should we assume(false) or abort()
There was a problem hiding this comment.
That's a good question. What do you think? I can try it out with abort and see how CBMC behaves.
There was a problem hiding this comment.
I replaced this by calling abort() instead of assume(false), but I still see this message on CBMC output:
aborting path on assume(false) at file <builtin-library-abort> line 6 function abort thread 0
So I don't think it will make much difference. I'll use abort() to improve readability.
| Some(def_id) == tcx.lang_items().panic_fn() | ||
| || Some(def_id) == tcx.lang_items().panic_display() | ||
| || Some(def_id) == tcx.lang_items().begin_panic_fn() |
| loc, | ||
| ) | ||
| let msg = format!( | ||
| "a panicking function {} is invoked", |
There was a problem hiding this comment.
I wonder if we can give a clearer message here
There was a problem hiding this comment.
Sure. Can I do it in a follow up PR since this will likely impact a lot of expected tests. I think this PR is already big enough.
| @@ -56,19 +56,22 @@ fn ibxor_test(a: i32, b: i32, correct: i32, wrong: i32) { | |||
| } | |||
|
|
|||
| fn main() { | |||
There was a problem hiding this comment.
IS this to prevent one failure from hiding others?
There was a problem hiding this comment.
Yes. Otherwise everything passed after iadd_test() because the checks become unreachable.
| match rmc::nondet::<u8>() { | ||
| // at this point, f == 0.0 | ||
| // should succeed | ||
| 0 => assert!(1.0 / f != 0.0 / f), | ||
| // should succeed | ||
| 1 => assert!(!(1.0 / f == 0.0 / f)), | ||
| // should fail | ||
| 2 => assert!(1.0 / f == 0.0 / f), | ||
| // should fail | ||
| 3 => assert!(0.0 / f == 0.0 / f), | ||
| // should suceed | ||
| _ => assert!(0.0 / f != 0.0 / f), | ||
| } |
There was a problem hiding this comment.
would it make sense to have a macro for this pattern
There was a problem hiding this comment.
I figured it wasn't worth the effort. There are a few differences in them and a macro would probably make it harder to read.
There was a problem hiding this comment.
I wonder if we should have the original, primitive cbmc assert available still.
Then our tests could still be straight-line code, just using rmc::assert() instead of assert!
There was a problem hiding this comment.
I was thinking about adding a function rmc::check() that had the behaviour you mentioned. What do you think? I would prefer not having two types of assert.
That said, I would prefer adding it in a different PR.
There was a problem hiding this comment.
Also, once we have the #[proof] attribute integrated, we should be able to easily split tests like this into multiple harnesses in the same file.
There was a problem hiding this comment.
I'm cool with a different name (brainstorming: rmc::probe? check also seems good) and in a follow up PR.
|
|
||
| pub fn unit_to_u32() -> u32 { | ||
| assert!(false); | ||
| if rmc::nondet::<bool>() { |
There was a problem hiding this comment.
do we need the turbofish here
There was a problem hiding this comment.
Oh... that's a good point. I guess because it's inside an if, we probably don't. Let me check.
| ); | ||
| if let Some(values) = fargs[0].struct_expr_values() { | ||
| if !values.is_empty() { | ||
| if let ExprValue::AddressOf(msg_value) = values[0].value() { |
There was a problem hiding this comment.
I think Jai had a utility function that does this in his first PR (that's still open). Maybe just steal it from there?
src/test/rmc/Options/check_tests.rs
Outdated
| // Note: We need to provide the compile-flags because compile test runs rustc directly and via rmc. | ||
|
|
||
| // compile-flags: --test | ||
| // compile-flags: --test --C panic=abort |
There was a problem hiding this comment.
Let me double check. I think you are correct though.
| match rmc::nondet::<u8>() { | ||
| // at this point, f == 0.0 | ||
| // should succeed | ||
| 0 => assert!(1.0 / f != 0.0 / f), | ||
| // should succeed | ||
| 1 => assert!(!(1.0 / f == 0.0 / f)), | ||
| // should fail | ||
| 2 => assert!(1.0 / f == 0.0 / f), | ||
| // should fail | ||
| 3 => assert!(0.0 / f == 0.0 / f), | ||
| // should suceed | ||
| _ => assert!(0.0 / f != 0.0 / f), | ||
| } |
There was a problem hiding this comment.
I wonder if we should have the original, primitive cbmc assert available still.
Then our tests could still be straight-line code, just using rmc::assert() instead of assert!
- Use abort instead of assume(false). - Add better method doc. - Create utility function to extract string constant. - Add fixme test and link to issue to add support to stack unwind.
tedinski
left a comment
There was a problem hiding this comment.
I think this is good to merge. I really want rmc::check or whatever we call it, but whenever we get to it I guess. :)
There were two very minor things I didn't see addressed that I added comments to, also.
|
|
||
| pub fn unit_to_u32() -> u32 { | ||
| assert!(false); | ||
| if rmc::nondet::<bool>() { |
src/test/rmc/Options/check_tests.rs
Outdated
| // Note: We need to provide the compile-flags because compile test runs rustc directly and via rmc. | ||
|
|
||
| // compile-flags: --test | ||
| // compile-flags: --test --C panic=abort |
My bad. I had missed them. Thanks for pointing it out. |
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 model-checking#67, fixes model-checking#466, fixes model-checking#543, and fixes model-checking#636. This change also mitigates model-checking#545.
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 #67, fixes #466, fixes #543, and fixes #636. This change also mitigates #545.
Description of changes:
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 #67, fixes #466, fixes #543, and fixes #636. This change also mitigates #545.
Resolved issues:
Resolves #67
Resolves #466
Resolves #543
Resolves #636
Call-outs:
I split this into two commits. One with the changes to the compiler and new test cases. The second commit is adjustments to our existing tests.
Testing:
How is this change tested? New and old test cases.
Is this a refactor change? Yes
Checklist
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.