-
Notifications
You must be signed in to change notification settings - Fork 274
Function contracts: preliminary support for conditional targets in assigns clause checking #6546
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
Function contracts: preliminary support for conditional targets in assigns clause checking #6546
Conversation
Codecov Report
@@ Coverage Diff @@
## develop #6546 +/- ##
===========================================
+ Coverage 75.99% 76.01% +0.02%
===========================================
Files 1579 1579
Lines 181124 181240 +116
===========================================
+ Hits 137643 137774 +131
+ Misses 43481 43466 -15
Continue to review full report at Codecov.
|
28ae8ce
to
d80695b
Compare
regression/contracts/aaa-assigns_enforce_guarded_assigns_targets/main.c
Outdated
Show resolved
Hide resolved
d80695b
to
6916359
Compare
e3d694d
to
cd1ca88
Compare
cd1ca88
to
865b827
Compare
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 believe the changes to the C front-end require some more work as detailed in my comments.
return true; | ||
} | ||
|
||
int foo(int a, int *x, int *y) __CPROVER_assigns(a && cond() ? *x) |
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 am not entirely convinced this is the best syntax to use: it seems that ?
now has a very difference precedence?
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.
Frama-C has gone that way, with different precedence rules depending on the context of an expression. I would much like to avoid anything the like.
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.
Do you have a suggestion for the operator syntax ?
cond ==> {target, .. }
was considered at some point but there would be similar issues in case the condition contains an operator like <==>
.
The more verbose if(cond) target
or if(cond) {target, .., target }
?
Maybe a completely new digraph or trigraph operator like??
or ?=>
?
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.
What about :
(colon)?
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 that could work !
How about this:
assigns_clause: '__CPROVER_assigns' '(' [ cond_targets (';' cond_targets)* [ ';' ] ] ')'
cond_targets: [ logical_equiv_expr ':' ] unary_expr (',' unary_expr)*
This is retro compatible with ,
-separated lists of unconditional targets
__CPROVER_assigns(t1, t2, ... )
And we can have ;
-separated lists of lists of conditional targets:
__CPROVER_assigns(t1, t2; cond: t3, t4; other_cond: t5, t6, t7)
with an optional ';' at the end you can indent like this:
__CPROVER_assigns(
t1, t2;
cond: t3, t4;
other_cond: t5, t6, t7;
)
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 happy with that if you can come up with a grammar that doesn't require more lookahead!
865b827
to
3671e58
Compare
@remi-delmas-3000 (nitpic) is there any particular reason to add "Function contracts: " as a suffix for every commit (and title) of this PR? I think this pollutes the message more than inform anything. I'd suggest removing it and keep the remaining text. |
@feliperodri, it was a request from @tautschnig to make it clear in the log history that a given commit is scoped to function contracts, and make it easier to filter the git history when there are regressions in CBMC-core. |
3671e58
to
b01be89
Compare
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.
Looks much better! I'm holding off on approving until we've concluded the syntax discussion.
src/ansi-c/c_typecheck_code.cpp
Outdated
@@ -11,6 +11,8 @@ Author: Daniel Kroening, [email protected] | |||
|
|||
#include "c_typecheck_base.h" | |||
|
|||
#include <ansi-c/c_expr.h> |
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.
Move this further down, underneath #include "ansi_c_declaration.h"
, and use #include "c_expr.h"
for consistency.
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.
fixed
if(condition.type().id() == ID_empty) | ||
{ | ||
must_throw = true; | ||
error().source_location = condition.source_location(); | ||
error() << "void-typed expressions not allowed as assigns clause conditions" | ||
<< eom; | ||
} |
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.
Isn't this caught by implicit_typecast_bool
already?
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.
Since neiter implicit_typecast_bool
or implicit_typecast
have documentation I wasn't sure what to expect.
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.
Fair enough! I was actually hoping for your test cases to confirm whether it is/isn't caught...
b01be89
to
a65f3c1
Compare
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.
Several comments need addressing, but I'm generally ok with this.
^\[update.\d+\] line 90 Check that state->digest.high_level.second.ctx->flags is assignable: FAILURE$ | ||
^\[update.\d+\] line 96 Check that POINTER_OBJECT\(\(void \*\)state->digest.high_level.first.ctx->digest\) is assignable: FAILURE$ | ||
^\[update.\d+\] line 97 Check that state->digest.high_level.first.ctx->flags is assignable: FAILURE$ | ||
^\[update.\d+\] line 100 Check that POINTER_OBJECT\(\(void \*\)state->digest.high_level.second.ctx->digest\) is assignable: SUCCESS$ |
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.
This one is failing in regression tests right now.
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 tool is right and the test case is badly specified, fixing it in a new update.
src/ansi-c/c_expr.h
Outdated
return ret; | ||
} | ||
|
||
/// \copydoc to_conditional_target_expr(exprt &expr) |
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.
const exprt &expr
to fix the Doxygen error
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.
fixed
src/ansi-c/c_typecheck_base.h
Outdated
/// targets of the form `c ? {t1, .. , tn}`, which get expanded to a | ||
/// collection `{c ? t1, .. , c ? tn }` by distributing the condition `c` |
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.
Syntax needs updating
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.
+1
src/ansi-c/c_expr.h
Outdated
explicit conditional_target_exprt(exprt _condition, exprt _target_list) | ||
: exprt(ID_conditional_target, empty_typet{}) | ||
{ | ||
add_to_operands(std::move(_condition)); | ||
add_to_operands(std::move(_target_list)); |
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 did you choose to no longer make it a binary_exprt
?
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 was't really sure what a binary_exprt
is supposed to represent, but from reading the code it seemed to be meant for binary operators such as <
, +
, where both left and right subtrees are homogenous.
This one being asymmetric and only meant to occur at a specify place in the AST seemed like a special case, it did not seem like reusing the binary_exprt
interface would bring much.
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.
Ok! (Adding/subtracting pointers is an existing example of binary_exprt
with non-matching types.)
src/ansi-c/c_typecheck_code.cpp
Outdated
exprt ops{ID_expression_list}; | ||
ops.add_to_operands(actual_target); | ||
conditional_target_exprt result{condition, ops}; | ||
result.add_source_location() = conditional_target.source_location(); | ||
tmp.push_back(std::move(result)); |
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.
This appears to be the only place that constructs a conditional_target_exprt
. I would, therefore, expect that conditional_target_exprt
has a constructor that permit creating the desired expression with just a single statement, like this:
tmp.push_back(conditional_target_exprt{condition, exprt{ID_expression_list, typet{}, {actual_target}}, conditional_target.source_location()});
@@ -18,8 +18,10 @@ Date: July 2021 | |||
|
|||
#include <langapi/language_util.h> | |||
|
|||
#include <ansi-c/c_expr.h> |
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'd appreciate a blank line after this one.
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.
fixed
@@ -15,6 +15,7 @@ Author: Remi Delmas, [email protected] | |||
|
|||
#include <langapi/language_util.h> | |||
|
|||
#include <ansi-c/c_expr.h> |
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.
Blank line after this one, please.
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.
fixed
#include <iostream> | ||
|
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.
Debugging left-over?
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.
fixed
|
||
std::cout << target.pretty() << std::endl; |
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.
Debugging left-over?
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.
fixed
const auto &cond_target = to_conditional_target_expr(target); | ||
|
||
// clean the condition if needed | ||
null_message_handlert null_handler; |
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.
Here and in the other place that introduces a null_message_handlert
: should you perhaps use log.get_message_handler()
from code_contractst?
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.
fixed
d25f258
to
828fc8f
Compare
@remi-delmas-3000 We should have "labels" to make it easier to filter contributions in the git history, similarly as we see in the linux project. For example, take a look at this commit message:
We can adopt the tag
I can add a README file to our |
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.
Only minor comments. Thanks for implementing this new feature and the improved doc/descriptions!
#include <assert.h> | ||
#include <stdbool.h> | ||
#include <stdlib.h> |
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.
We only need #include <stdbool.h>
.
The next step must be to check that the called functions have a contract | ||
with an empty assigns clause and that they indeed satisfy their contract | ||
using a CI check. |
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.
Is there a issue to track this?
#include <assert.h> | ||
#include <stdbool.h> | ||
#include <stdlib.h> |
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.
We only need #include <stdbool.h>
here.
^SIGNAL=0$ | ||
-- | ||
-- | ||
Check that conditional assigns clause `c ? lvalue` |
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.
We don't use this pattern anymore.
@@ -2,12 +2,21 @@ | |||
#include <stdbool.h> | |||
#include <stdlib.h> |
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.
We don't need stdlib.h
here.
void c_typecheck_baset::typecheck_spec_assigns(exprt::operandst &targets) | ||
{ | ||
exprt::operandst tmp; | ||
bool must_throw = false; |
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.
Same as above.
Why don't you throw 0
on each if branch instead? You're now evaluating every if condition even if there is a previous error. Is that intentional?
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.
very much intentional, in order to report all errors triggered by the expression instead of reporting them one by one.
void c_typecheck_baset::typecheck_spec_assigns(exprt::operandst &targets) | ||
{ | ||
exprt::operandst tmp; |
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.
Are those temporary targets? I'd give a more descriptive name for this temporary variable.
// at least one target was not as expected | ||
if(must_throw) | ||
throw 0; |
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.
So why not throw 0
as soon as we find this target?
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.
in order to report all errors found in the target list instead of stopping on the first one.
const auto &conditional_target = to_conditional_target_expr(expr); | ||
INVARIANT( | ||
conditional_target.targets().size() == 1, | ||
"expecting only a single target at this stage"); |
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.
What stage? Could we be more descriptive?
@@ -22,6 +22,27 @@ Date: July 2021 | |||
|
|||
typedef std::pair<const exprt, const exprt> slicet; | |||
|
|||
/// A guarded slice of memory |
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.
\brief
? Why struct and not a class here?
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.
there are no methods associated with this. Why would it need to be a class ?
828fc8f
to
79b5289
Compare
…ses. Conditional targets specify memory locations that can be assigned if and only if a condition holds when a function is invoked. *does not yet work for loops*. - Add a new `conditional_target_group_exprt` class and associated irep_id `ID_conditional_target_group` - Update `expr2ct` to print the new expression - Modify `ansi-c/parser.y` to accept the new target syntax - Modify `ansi-c/c_typecheck_code.cpp` for the new expression type - Update `conditional_address_ranget` with explicit condition - Change the validity condition of a CAR to `condition && all_deref_valid(target) && rw_ok(target, size)` for both contract checking and contract replacement logic. - Add new tests for the feature - Update existing tests
79b5289
to
4a10dd4
Compare
This PR adds support for conditional targets in assigns clauses.
A conditional target specifies a memory location that can be assigned by a function only when a condition holds at the call site.
When a function call is replaced by its contract, conditional targets are havocked only if the condition holds at the call site.
Syntax
comma separated list of targets can now be preceded by
condition :
, and multiple target lists with or without conditions are separated by;
:Use cases
These conditional targets allow the user to specify frame conditions for functions that have distinct modes of operation and assign different locations depending on the mode.
They also allow the user to specify validity conditions for the assigned locations, in case they are accessed through chains of struct/union member selections, pointer dereferences, can are not always valid. For instance in the example below,
cond
would be a condition which guarantees that the target expression corresponds to a valid memory location:Changes
In the ansi-c front-end
irep_id
for the new construct;expr2ct
expression formatter class to print the new expression type;side_effect_function_callt
but no otherside_effect_exprt
subtypes.Remark: In a future PR we will check that any function used in a condition is side-effect-free, i.e. has a contract with an empty assigns clause. For now we trust the user not to use side-effecting functions in conditions.
In the
contracts/assigns.{h|c}
code:guard
expression to the internal representation of assigns clause targets;guard && all_deref_valid(target) && rw_ok(target, size)
;