Skip to content

CONTRACTS: Several fixes for loop contracts #6701

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
merged 2 commits into from
Mar 8, 2022

Conversation

SaswatPadhi
Copy link
Contributor

@SaswatPadhi SaswatPadhi commented Feb 28, 2022

The main change in this PR is that loop contract instrumentation is now disabled along program paths that result in vacuous loop (i.e., the loop guard doesn't hold initially and the loop never runs).

At a high level, this is equivalent to instrumenting a:

while (G) do S;

loop as:

if (G) {
  while (G) do S;
}

instead. The instrumented code would not be triggered if G was initially false and we never enter the loop.

This allows for significantly simpler loop contracts. A simple example is attached as a regression test in this PR.

Things get a little tricky when G can have side effects, and the instrumented code now jumps back and forth to avoid replicating the entire G. I have added several comments to clarify the instrumentation process. We check all side effects in loop guards as well against the loop assigns clauses (see attached regression tests).

Finally, this also allows us to move the instrumentation for decreases clauses to before the loop head (i.e., out of the loop "body"), so we no longer require the source_location hack to figure out where the loop guard ends and body starts.

  • 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.
  • n/a 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).
  • n/a 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.

@SaswatPadhi SaswatPadhi added enhancement aws Bugs or features of importance to AWS CBMC users aws-high Code Contracts Function and loop contracts labels Feb 28, 2022
@SaswatPadhi SaswatPadhi force-pushed the loop-contracts-simplify branch from 8cd75d5 to 973319d Compare February 28, 2022 17:25
@codecov
Copy link

codecov bot commented Feb 28, 2022

Codecov Report

Merging #6701 (af24259) into develop (6594cbb) will increase coverage by 0.00%.
The diff coverage is 90.78%.

Impacted file tree graph

@@            Coverage Diff            @@
##           develop    #6701    +/-   ##
=========================================
  Coverage    77.15%   77.15%            
=========================================
  Files         1582     1582            
  Lines       182628   182759   +131     
=========================================
+ Hits        140900   141013   +113     
- Misses       41728    41746    +18     
Impacted Files Coverage Δ
src/goto-instrument/contracts/contracts.h 100.00% <ø> (ø)
src/goto-instrument/contracts/contracts.cpp 88.31% <90.58%> (-0.90%) ⬇️
src/goto-checker/properties.cpp 80.53% <100.00%> (+0.35%) ⬆️
...oto-instrument/contracts/instrument_spec_assigns.h 94.73% <100.00%> (+0.21%) ⬆️
src/util/signal_catcher.cpp 84.61% <0.00%> (-11.54%) ⬇️
src/util/simplify_expr_int.cpp 85.50% <0.00%> (+0.42%) ⬆️
src/util/xml_irep.cpp 25.92% <0.00%> (+1.85%) ⬆️

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 de9fa65...af24259. Read the comment docs.

Copy link
Collaborator

@remi-delmas-3000 remi-delmas-3000 left a comment

Choose a reason for hiding this comment

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

Declare all instrumentation DEAD after the loop, add doc on the invariant_expr() closure, use distinct variables for generated_code before and after loop to make it easier to understand what is going on. Keep comments in sync with the implementation.

@@ -187,26 +187,7 @@ void code_contractst::check_apply_loop_contracts(

Copy link
Collaborator

Choose a reason for hiding this comment

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

Random place for comment:

At line 150-154 is this method to detect the loop end really reliable reliable ? It seems that the result returned by natural_loops already contains for each loop the list of instructions forming the loop.

  goto_programt::targett loop_end = loop_head;
  for(const auto &t : loop)
    if(
      t->is_goto() && t->get_target() == loop_head &&
      t->location_number > loop_end->location_number)
      loop_end = t;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes it captures a "set" of all the instructions (not "list"), but also, we need to find the "loop_end" instruction where we have a back edge (so that we can remove it).

As to whether this loop is reliable for finding the "loop_end", I think it is? Do you see a case where this may not work?

@remi-delmas-3000
Copy link
Collaborator

Other comment: the havoc statements do not contain the local static variables

@SaswatPadhi SaswatPadhi force-pushed the loop-contracts-simplify branch 3 times, most recently from b3de959 to 4df78a2 Compare March 3, 2022 01:15
@SaswatPadhi SaswatPadhi self-assigned this Mar 3, 2022
@SaswatPadhi SaswatPadhi force-pushed the loop-contracts-simplify branch 2 times, most recently from ffa64b2 to 75e9ab8 Compare March 3, 2022 17:08
Comment on lines +361 to +334
// FIXME: this is not true for write set targets that
// might depend on other write set targets.
Copy link
Collaborator

@remi-delmas-3000 remi-delmas-3000 Mar 3, 2022

Choose a reason for hiding this comment

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

Is there a way to check if we are in this case with the specified or inferred targets ?
And is widening now applied right after the targets have been inferred or still during havocing ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The widening part is not yet fixed.

I could add a separate commit to fix this PR, but is this still high priority given that we double check all inferred targets anyway by instrumenting the loop?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Right now loop contracts still use the havoc_assigns_targetst class because it contains the widening logic.

Widening right after the inference would allow to use the new class havoc_assigns_clause_targetst which shares the CAR snapshotting logic with assigns clause checking.

@SaswatPadhi SaswatPadhi force-pushed the loop-contracts-simplify branch 6 times, most recently from 8f543a2 to 901a006 Compare March 5, 2022 02:32
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.

LGTM, no blocking comments.

Comment on lines 1107 to 1141
log.error() << "Cannot enforce contracts on irregular loop at: "
<< loop_head->source_location()
<< ".\nLoop body must be a contiguous set of instructions."
<< messaget::eom;
log.debug() << "Out-of-loop-body instruction:" << messaget::eom;
goto_function.body.output_instruction(ns, function_name, log.debug(), *i);
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.

This is not covered by regression tests because is design to catch inconsistent GOTO programs? I'd add a comment if that's true.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes exactly. I tried to, but couldn't write loops in C that would trigger this.

I will add a comment.

Comment on lines +1127 to +1165
if(!loop_nesting_graph[outer].content.contains(
loop_nesting_graph[inner].end_target))
{
log.error()
<< "Overlapping loops at:\n"
<< loop_nesting_graph[outer].head_target->source_location()
<< "\nand\n"
<< loop_nesting_graph[inner].head_target->source_location()
<< "\nLoops must be nested or sequential for contracts to be "
"enforced."
<< messaget::eom;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why not covered by regression tests?

Copy link
Contributor Author

@SaswatPadhi SaswatPadhi Mar 7, 2022

Choose a reason for hiding this comment

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

I don't think we can write loops in C which would trigger this check. It can only be triggered if someone messes with the goto instructions directly and introduces some statements in the middle of a loop.

We should have this check in place though because our instrumentation code assumes this property and would be unsound if two loop bodies somehow overlap.

to_check_contracts_on_children.push_back(idx);

// If the loop has contracts to be enforced, then
// reject irregular loops that have "holes" within their body,
Copy link
Collaborator

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

Choose a reason for hiding this comment

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

We don't really have a notion of 'irregular' loop. Could we rephrase the comment and move this check in a separate method ?

We could add a doc like this for the method :

By definition the loop body is the set of instructions computed by `natural_loops` based on the CFG, and here we check that straight line code or instructions from different loops do not mix and overlap in the serialized representation.
This check is needed because we perform the instrumentation by traversing the sequence of instructions and we want to make sure we are only going to instrument loop instructions or function exit instructions.

So we check that all instructions found in sequential order between loop_head and loop_end belong to the set of instructions computed by the natural_loop analysis, while allowing the presence of:
- `assume(false)` instructions which are introduced by function pointer or nested loop transformations, and have no successor instructions
- `SET_RETURN_VALUE` instructions followed by an uninterrupted sequence of `DEAD` instructions and a `GOTO` instruction that jumps out of the loop, which model return statements in GOTO.

If in addition we could check that instructions are in topological order this would prove that this is equivalent to traversing the CFG to do the instrumentation.

Copy link
Collaborator

@remi-delmas-3000 remi-delmas-3000 left a comment

Choose a reason for hiding this comment

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

move the loop dependency graph construction and loop instruction checking to a dedicated method with documentation, and add clarification on the case when a loop does not have a contract and we skip instrumentation (L1167)

+ Disabled loop contract instrumentation along program paths that result in vacuous loops,
  i.e., when the loop guard doesn't hold initially and the loop never runs.
  This allows for significantly simpler loop contracts in some cases.
+ Fixed the instrumentation for decreases clauses.
  It no longer requires the `source_location` hack to find the beginning of loop "body".
+ Added some regression tests for loops that have side effects within guards
Our loop contracts instrumentation routine makes some assumptions regarding the structure of loops.
In this commit, I have added checks to verify them before we start instrumenting,
so as to avoid potentially unsound instrumentation.
@SaswatPadhi SaswatPadhi force-pushed the loop-contracts-simplify branch from 901a006 to af24259 Compare March 7, 2022 23:28
@SaswatPadhi SaswatPadhi merged commit ae4f1d6 into diffblue:develop Mar 8, 2022
SaswatPadhi added a commit to padhi-forks/cbmc that referenced this pull request Apr 15, 2022
Our assigns clause instrumentation works over a sequence of instructions ignoring control flow edges. In diffblue#6701 a safety check was added to bail out on attempting to instrument loops that contain "holes" -- instructions that appear within the [head, end] sequence for the loop, but are not part of the computed "natural loop" because of control flow.
In this PR, we remove this restriction and allow instrumenting such loops by masking out instructions outside of the computed natural loop.
SaswatPadhi added a commit to padhi-forks/cbmc that referenced this pull request Apr 21, 2022
Our assigns clause instrumentation works over a sequence of instructions ignoring control flow edges. In diffblue#6701 a safety check was added to bail out on attempting to instrument loops that contain "holes" -- instructions that appear within the [head, end] sequence for the loop, but are not part of the computed "natural loop" because of control flow.
In this PR, we remove this restriction and allow instrumenting such loops by masking out instructions outside of the computed natural loop.
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 aws-high Code Contracts Function and loop contracts enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants