Skip to content

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

Merged

Conversation

remi-delmas-3000
Copy link
Collaborator

@remi-delmas-3000 remi-delmas-3000 commented Dec 23, 2021

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 ;:

__CPROVER_assigns(
a, b, c; // unconditional targets
condition: x, y, z; // conditional targets
!condition && other_condition: q, r, t; // conditional targets
)

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:

__CPROVER_assigns(cond: p->union.member->array[idx]->member) 

Changes

In the ansi-c front-end

  • In the parser, modify the target rule to accept the new syntax;
  • Add an new expression class and irep_id for the new construct;
  • Update the expr2ct expression formatter class to print the new expression type;
  • Add typechecking rules:
    • targets must be lvalues, without ternary operators or side-effects;
    • conditions are still allowed to contain side_effect_function_callt but no other side_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:

  1. Add an explicit guard expression to the internal representation of assigns clause targets;
  2. Update the CAR validity condition to guard && all_deref_valid(target) && rw_ok(target, size);
  3. Propagate these changes to the instrumentation of assigns clause checking;
  4. Propagate these changes to the havocking of targets for contract replacement.
  • Each commit message has a non-empty body, explaining why the change was made.
  • Methods or procedures I have added are documented, following the guidelines provided in CODING_STANDARD.md.
  • The feature or user visible behaviour I have added or modified has been documented in the User Guide in doc/cprover-manual/
  • Regression or unit tests are included, or existing tests cover the modified code (in this case I have detailed which ones those are in the commit message).
  • My commit message includes data points confirming performance improvements (if claimed).
  • My PR is restricted to a single feature or bugfix.
  • White-space or formatting changes outside the feature-related changed lines are in commits of their own.

@codecov
Copy link

codecov bot commented Dec 23, 2021

Codecov Report

Merging #6546 (e3d694d) into develop (bd1401f) will increase coverage by 0.02%.
The diff coverage is 91.53%.

❗ Current head e3d694d differs from pull request most recent head 4a10dd4. Consider uploading reports for the commit 4a10dd4 to get more accurate results
Impacted file tree graph

@@             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     
Impacted Files Coverage Δ
src/ansi-c/c_typecheck_base.h 100.00% <ø> (ø)
src/ansi-c/c_typecheck_code.cpp 80.70% <86.84%> (+1.04%) ⬆️
src/goto-instrument/contracts/assigns.cpp 95.68% <96.42%> (-0.15%) ⬇️
src/ansi-c/parser.y 79.60% <100.00%> (+0.31%) ⬆️
src/goto-instrument/contracts/assigns.h 100.00% <100.00%> (ø)
src/util/std_code.h 94.06% <0.00%> (+0.01%) ⬆️
src/solvers/lowering/byte_operators.cpp 92.25% <0.00%> (+0.09%) ⬆️
src/pointer-analysis/value_set.cpp 82.84% <0.00%> (+0.10%) ⬆️
build/src/ansi-c/ansi_c_y.tab.cpp 78.37% <0.00%> (+0.22%) ⬆️
src/goto-programs/rewrite_union.cpp 100.00% <0.00%> (+5.88%) ⬆️
... and 1 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 27b02b3...4a10dd4. Read the comment docs.

@remi-delmas-3000 remi-delmas-3000 force-pushed the guarded-assigns-targets branch from d80695b to 6916359 Compare January 3, 2022 16:15
@feliperodri feliperodri added aws Bugs or features of importance to AWS CBMC users Code Contracts Function and loop contracts new feature labels Jan 4, 2022
@remi-delmas-3000 remi-delmas-3000 force-pushed the guarded-assigns-targets branch from e3d694d to cd1ca88 Compare January 4, 2022 23:33
@remi-delmas-3000 remi-delmas-3000 changed the title Function contracts: preliminary support for guarded assigns clause targets Function contracts: preliminary support for conditional targets in assigns clause checking Jan 4, 2022
@remi-delmas-3000 remi-delmas-3000 force-pushed the guarded-assigns-targets branch from cd1ca88 to 865b827 Compare January 4, 2022 23:59
Copy link
Collaborator

@tautschnig tautschnig left a 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)
Copy link
Collaborator

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?

Copy link
Member

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.

Copy link
Collaborator Author

@remi-delmas-3000 remi-delmas-3000 Jan 5, 2022

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 ?=> ?

Copy link
Collaborator

Choose a reason for hiding this comment

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

What about : (colon)?

Copy link
Collaborator Author

@remi-delmas-3000 remi-delmas-3000 Jan 6, 2022

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;
)

Copy link
Collaborator

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!

@remi-delmas-3000 remi-delmas-3000 force-pushed the guarded-assigns-targets branch from 865b827 to 3671e58 Compare January 5, 2022 18:31
@feliperodri
Copy link
Collaborator

@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.

@remi-delmas-3000
Copy link
Collaborator Author

@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.

@remi-delmas-3000 remi-delmas-3000 force-pushed the guarded-assigns-targets branch from 3671e58 to b01be89 Compare January 5, 2022 23:06
Copy link
Collaborator

@tautschnig tautschnig left a 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.

@@ -11,6 +11,8 @@ Author: Daniel Kroening, [email protected]

#include "c_typecheck_base.h"

#include <ansi-c/c_expr.h>
Copy link
Collaborator

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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed

Comment on lines +872 to +878
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;
}
Copy link
Collaborator

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?

Copy link
Collaborator Author

@remi-delmas-3000 remi-delmas-3000 Jan 7, 2022

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.

Copy link
Collaborator

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...

@remi-delmas-3000 remi-delmas-3000 force-pushed the guarded-assigns-targets branch from b01be89 to a65f3c1 Compare January 7, 2022 00:45
Copy link
Collaborator

@tautschnig tautschnig left a 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$
Copy link
Collaborator

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.

Copy link
Collaborator Author

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.

return ret;
}

/// \copydoc to_conditional_target_expr(exprt &expr)
Copy link
Collaborator

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

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed

Comment on lines 153 to 154
/// targets of the form `c ? {t1, .. , tn}`, which get expanded to a
/// collection `{c ? t1, .. , c ? tn }` by distributing the condition `c`
Copy link
Collaborator

Choose a reason for hiding this comment

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

Syntax needs updating

Copy link
Collaborator

Choose a reason for hiding this comment

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

+1

Comment on lines 234 to 238
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));
Copy link
Collaborator

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?

Copy link
Collaborator Author

@remi-delmas-3000 remi-delmas-3000 Jan 7, 2022

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.

Copy link
Collaborator

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.)

Comment on lines 990 to 995
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));
Copy link
Collaborator

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>
Copy link
Collaborator

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.

Copy link
Collaborator Author

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>
Copy link
Collaborator

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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed

Comment on lines 27 to 28
#include <iostream>

Copy link
Collaborator

Choose a reason for hiding this comment

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

Debugging left-over?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed

Comment on lines 77 to 78

std::cout << target.pretty() << std::endl;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Debugging left-over?

Copy link
Collaborator Author

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;
Copy link
Collaborator

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?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed

@remi-delmas-3000 remi-delmas-3000 force-pushed the guarded-assigns-targets branch 2 times, most recently from d25f258 to 828fc8f Compare January 7, 2022 15:40
@feliperodri
Copy link
Collaborator

@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.

@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:

tracing: Tag trace_percpu_buffer as a percpu pointer

Tag trace_percpu_buffer as a percpu pointer to resolve warnings
reported by sparse:
...

We can adopt the tag CONTRACT to all work related to function and loop contracts (I don't think there is a need to distinct between them) and adopt these guidelines for commit messages. Linux also offers some guidelines about describing changes, but the one from cbea it's clear IMHO. Following these guidelines, your current commit message could be

CONTRACTS: Preliminary support for conditional assigns clauses

Conditional targets specify memory locations that can be assigned if and
only if a condition holds when a function is invoked. It does not yet
work for loops.

- Add a new `conditional_target_exprt` class and associated `irep_ID`;
- Modify ansi-c parser and typechecker to accept the new target syntax
  `cond: t1, ..., tn`;
- Update the CAR class `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;
- Update some tests, add new tests for the feature.

I can add a README file to our src/goto-instrument/contracts module with a reference for these guidelines and start a "contributors guide". (cc. @tautschnig)

Copy link
Collaborator

@feliperodri feliperodri left a 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!

Comment on lines 1 to 3
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
Copy link
Collaborator

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>.

Comment on lines +15 to +17
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.
Copy link
Collaborator

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?

Comment on lines 1 to 3
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
Copy link
Collaborator

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`
Copy link
Collaborator

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>
Copy link
Collaborator

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;
Copy link
Collaborator

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?

Copy link
Collaborator Author

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;
Copy link
Collaborator

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.

Comment on lines +999 to +1002
// at least one target was not as expected
if(must_throw)
throw 0;
Copy link
Collaborator

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?

Copy link
Collaborator Author

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");
Copy link
Collaborator

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
Copy link
Collaborator

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?

Copy link
Collaborator Author

@remi-delmas-3000 remi-delmas-3000 Jan 10, 2022

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 ?

@remi-delmas-3000 remi-delmas-3000 force-pushed the guarded-assigns-targets branch from 828fc8f to 79b5289 Compare January 7, 2022 21:54
…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
@remi-delmas-3000 remi-delmas-3000 merged commit 169dc18 into diffblue:develop Jan 10, 2022
@remi-delmas-3000 remi-delmas-3000 deleted the guarded-assigns-targets branch January 10, 2022 20:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
aws Bugs or features of importance to AWS CBMC users Code Contracts Function and loop contracts new feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants