Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix assert!() and panic!() code generation #687

Merged
merged 5 commits into from
Dec 14, 2021

Conversation

celinval
Copy link
Contributor

@celinval celinval commented Dec 10, 2021

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

  • Each commit message has a non-empty body, explaining why the change was made
  • Methods or procedures are documented
  • Regression or unit tests are included, or existing tests cover the modified code
  • My PR is restricted to a single feature or bugfix

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.

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.
@celinval celinval requested a review from a team as a code owner December 10, 2021 00:04
@@ -207,6 +207,10 @@ impl Stmt {
)
}

pub fn assume_false(loc: Location) -> Self {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doc comment

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For sure!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm actually removing this since I am adding a builtin abort()

}
}
} else {
tracing::error!(?values, "Oh no");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More informative message please?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ooops... That was a debug statement. =)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll remove it!

);
if let Some(values) = fargs[0].struct_expr_values() {
if !values.is_empty() {
if let ExprValue::AddressOf(msg_value) = values[0].value() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any other times we might want to do this? Could be a utility function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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())],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we assume(false) or abort()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good question. What do you think? I can try it out with abort and see how CBMC behaves.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines 201 to 203
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()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

loc,
)
let msg = format!(
"a panicking function {} is invoked",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we can give a clearer message here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@@ -56,19 +56,22 @@ fn ibxor_test(a: i32, b: i32, correct: i32, wrong: i32) {
}

fn main() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IS this to prevent one failure from hiding others?

Copy link
Contributor Author

@celinval celinval Dec 10, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Otherwise everything passed after iadd_test() because the checks become unreachable.

Comment on lines +9 to +21
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),
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it make sense to have a macro for this pattern

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm cool with a different name (brainstorming: rmc::probe? check also seems good) and in a follow up PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created #695 to capture the work.


/* The function dynamic object has no associated data */
rmc::expect_fail(size_from_vtable(vtable!(fun)) != 0, "Wrong size");
}

pub fn unit_to_u32() -> u32 {
assert!(false);
if rmc::nondet::<bool>() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need the turbofish here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh... that's a good point. I guess because it's inside an if, we probably don't. Let me check.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was it necessary?

);
if let Some(values) = fargs[0].struct_expr_values() {
if !values.is_empty() {
if let ExprValue::AddressOf(msg_value) = values[0].value() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Jai had a utility function that does this in his first PR (that's still open). Maybe just steal it from there?

@@ -4,7 +4,7 @@
// Check that we can verify test harnesses using the --tests argument.
// 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me double check. I think you are correct though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was it necessary?

Comment on lines +9 to +21
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),
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
Copy link
Contributor

@tedinski tedinski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.


/* The function dynamic object has no associated data */
rmc::expect_fail(size_from_vtable(vtable!(fun)) != 0, "Wrong size");
}

pub fn unit_to_u32() -> u32 {
assert!(false);
if rmc::nondet::<bool>() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was it necessary?

@@ -4,7 +4,7 @@
// Check that we can verify test harnesses using the --tests argument.
// 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was it necessary?

@celinval
Copy link
Contributor Author

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.

My bad. I had missed them. Thanks for pointing it out.

@celinval celinval merged commit 625990c into model-checking:main Dec 14, 2021
@celinval celinval deleted the issue-636 branch December 14, 2021 21:04
tedinski pushed a commit to tedinski/rmc that referenced this pull request Apr 22, 2022
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.
tedinski pushed a commit to tedinski/rmc that referenced this pull request Apr 25, 2022
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.
tedinski pushed a commit to tedinski/rmc that referenced this pull request Apr 26, 2022
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.
tedinski pushed a commit that referenced this pull request Apr 27, 2022
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants