-
Notifications
You must be signed in to change notification settings - Fork 134
Override assert macro #797
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
Conversation
library/std/src/lib.rs
Outdated
| kani::assert($cond, concat!("assertion: ", stringify!($cond))); | ||
| }; | ||
| ($cond:expr, $($arg:tt)+) => { | ||
| kani::assert($cond, concat!("assertion: ", stringify!($($arg)+))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess in the future we should ensure that any variable access is valid for message arguments. Can you please add a fixme comment and an issue capturing that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean that the variable implements the Display trait for example?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, something like that. However, I was just checking, it looks like the message side of the expression only executes if the condition fails. So I don't think it's that important.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. In some sense, kani would be more lenient than rustc. For example, for
fn main() {
assert!(1 + 1 == 3, "Bad arithmetic: {}", foo);
}
rustc reports:
$ rustc no_display.rs
error[E0425]: cannot find value `foo` in this scope
--> no_display.rs:2:47
|
2 | assert!(1 + 1 == 3, "Bad arithmetic: {}", foo);
| ^^^ not found in this scope
whereas with this PR, kani doesn't complain:
$ kani no_display.rs
CBMC version 5.48.0 (cbmc-5.48.0) 64-bit x86_64 linux
Reading GOTO program from file no_display.goto
Generating GOTO Program
Adding CPROVER library (x86_64)
Removal of function pointers and virtual functions
Generic Property Instrumentation
Running with 16 object bits, 48 offset bits (user-specified)
Starting Bounded Model Checking
Runtime Symex: 0.00102581s
size of program expression: 75 steps
simple slicing removed 3 assignments
Generated 3 VCC(s), 1 remaining after simplification
Runtime Postprocess Equation: 7.25e-06s
Passing problem to propositional reduction
converting SSA
Runtime Convert SSA: 0.000213004s
Running propositional reduction
Post-processing
Runtime Post-process: 2.594e-05s
Solving with MiniSAT 2.2.1 with simplifier
100 variables, 33 clauses
SAT checker: instance is SATISFIABLE
Runtime Solver: 9.1981e-05s
Runtime decision procedure: 0.000343287s
** Results:
[assertion.1] Code generation sanity check: Correct CBMC vtable size for StructTag("tag-_4317141983261250885") (MIR type Closure(DefId(2:9147 ~ std[2f3c]::rt::lang_start::{closure#0}), [(), i8, extern "rust-call" fn(()) -> i32, (fn(),)])). Please report failures:
https://github.com/model-checking/kani/issues/new?template=bug_report.md: SUCCESS
[pointer_primitives.1] dead object in OBJECT_SIZE(&temp_0): SUCCESS
/home/ubuntu/examples/no_display.rs function main
[main.assertion.1] line 2 assertion: "Bad arithmetic: {}", foo: FAILURE
** 1 of 3 failed (2 iterations)
VERIFICATION FAILED
I'll file an issue and add a comment.
| vec![ | ||
| Stmt::decl(tmp.clone(), Some(cond), loc.clone()), | ||
| Stmt::assert(tmp.clone(), &msg, caller_loc.clone()), | ||
| Stmt::assume(tmp, loc.clone()), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm assuming the assumption here is to abort when the assertion fails, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this is to maintain the current semantics of assert, which are currently roughly implemented as:
if !cond {
assert(false);
abort();
}
// cond must be true hereThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you use the original method codegen_fatal_error() instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I understand, codegen_fatal_error is meant for a panic statement, which unconditionally fails, whereas here, there is a condition involved.
The alternative I was considering is to do something similar to how rustc expands the assert macro, e.g.
cond.not().if_then_else(codegen_fatal_error(...), None)However, based on @danielsn's feedback, with an "if", we lose the structure of the original assert.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha. I was just hoping that we could encapsulate and formalized what an assert in the rust / kani world means, which is different from the goto-c semantics. But we can do that in a follow up PR.
Maybe we can do that when we fix codegen TerminatorKind::Assert().
library/std/src/lib.rs
Outdated
| /// assert!(a + b == c, "The sum of {} and {} is {}", a, b, c); | ||
| /// ``` | ||
| /// the assert message will be: | ||
| /// assertion "The sum of {} and {} is {}", 1, 1, 2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not keep it compatible with the std message?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean remove the assertion: prefix? If so, the reason I added this prefix is to make it easy to match a reachability check with its assert. If not, can you clarify what you mean?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The std library adds "assertion failed:" prefix to things that don't have a message. So I was wondering if we should match that.
Not sure I understand how you are planning to match the check with its assert though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense. The matching should be doable without the assertion: prefix. I'll remove it.
| let msg = fargs.remove(0); | ||
| let msg = utils::extract_const_message(&msg).unwrap(); | ||
| let target = target.unwrap(); | ||
| let loc = tcx.codegen_span_option(span); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you checked how the trace looks like in viewer? I'm wondering if this will look weird since we are using two different locations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The trace does look a bit weird in viewer (notice the missing files/lines):
Step 38: Function foo, File reach.rs, Line 4
assert!(x < 4);
var_5 = FALSE
Step 39: Function MISSING, File MISSING, Line 0
temp_0 = FALSE
Step 40: Function MISSING, File MISSING, Line 0
temp_0 = FALSE
Step 41: Function foo, File reach.rs, Line 4
assert!(x < 4);
failure: foo.assertion.1: assertion: x < 4
I'll see if changing all the locations to the "caller" location fixes it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool. BTW, the MISSING is a different problem. We should chat with @markrtuttle about this. Viewer doesn't seem to handle files that are not inside the current folder.
db21be9 to
f7e360c
Compare
f7e360c to
97ae95e
Compare
Description of changes:
Override the
assertmacro via calling a newkani::assertfunction that is implemented in codegen (via a hook). This gives us control over the implementation of the macro and allows us to inject any additional checks (e.g. reachability).For assertions with a message, e.g.
assert!(a == b, "a and b have the values {} and {}, respectively", a, b), it produces"a and b have the values {} and {}, respectively", a, binstead of the currentThis is a placeholder message; Kani doesn't support message formatted at runtime.Resolved issues:
Call-outs:
The handling of assertions with messages results in bypassing some compile-time checks. See #803 for details.
Testing:
How is this change tested? Current regressions
Is this a refactor change? No
Checklist
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.