Skip to content

Allow instrumentation of loops with "holes" #6808

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 1 commit into from
Apr 21, 2022

Conversation

SaswatPadhi
Copy link
Contributor

Our assigns clause instrumentation currently works over a sequence of instructions, ignoring control flow edges.
In #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.

  • 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 Apr 15, 2022
@SaswatPadhi SaswatPadhi self-assigned this Apr 15, 2022
@codecov
Copy link

codecov bot commented Apr 15, 2022

Codecov Report

Merging #6808 (7aa1044) into develop (aed93c8) will increase coverage by 0.01%.
The diff coverage is 97.87%.

@@             Coverage Diff             @@
##           develop    #6808      +/-   ##
===========================================
+ Coverage    76.99%   77.00%   +0.01%     
===========================================
  Files         1594     1594              
  Lines       184263   184345      +82     
===========================================
+ Hits        141872   141957      +85     
+ Misses       42391    42388       -3     
Impacted Files Coverage Δ
...oto-instrument/contracts/instrument_spec_assigns.h 94.73% <ø> (ø)
src/solvers/smt2/smt2_conv.h 100.00% <ø> (ø)
...c/solvers/smt2_incremental/convert_expr_to_smt.cpp 84.53% <0.00%> (-0.35%) ⬇️
src/util/simplify_expr_class.h 90.47% <ø> (ø)
src/ansi-c/c_typecheck_expr.cpp 77.05% <100.00%> (-0.02%) ⬇️
src/ansi-c/goto_check_c.cpp 91.47% <100.00%> (ø)
src/goto-instrument/contracts/contracts.cpp 94.49% <100.00%> (+0.67%) ⬆️
...o-instrument/contracts/instrument_spec_assigns.cpp 97.14% <100.00%> (-0.23%) ⬇️
src/solvers/flattening/bv_pointers.cpp 83.09% <100.00%> (ø)
src/solvers/smt2/smt2_conv.cpp 67.47% <100.00%> (-0.02%) ⬇️
... and 12 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 67f5d09...7aa1044. Read the comment docs.

if(has_disable_assigns_check_pragma(instruction_it))
if(
has_disable_assigns_check_pragma(instruction_it) ||
(filter && !filter(instruction_it)))
Copy link
Collaborator

Choose a reason for hiding this comment

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

See below: I'm not convinced we should permit an empty std::function here.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Or at least we should make it the default to check all instructions.

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 is the default.

@@ -541,7 +544,7 @@ exprt instrument_spec_assignst::target_validity_expr(
// the target's `start_address` pointer satisfies w_ok with the expected size
// (or is NULL if we allow it explicitly).
// This assertion will be falsified whenever `start_address` is invalid or
// not of the right size (or is NULL if we dot not allow it expliclitly).
// not of the right size (or is NULL if we dot not allow it explicitly).
Copy link
Collaborator

Choose a reason for hiding this comment

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

Since you're fixing typos - can you also change "dot" to "do" in 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.

Yes sure, will do.

@@ -449,7 +449,8 @@ class instrument_spec_assignst
goto_programt::targett instruction_it,
const goto_programt::targett &instruction_end,
skip_function_paramst skip_function_params,
optionalt<cfg_infot> &cfg_info_opt);
optionalt<cfg_infot> &cfg_info_opt,
const std::function<bool(const goto_programt::targett &)> &filter = {});
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 not convinced that default-arguments are a good thing here, and also I'm wondering about the semantics: if the function returns true, is the instruction filtered out, or is it only to be included when the filter returns true?

Copy link
Contributor Author

@SaswatPadhi SaswatPadhi Apr 17, 2022

Choose a reason for hiding this comment

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

I'm not convinced that default-arguments are a good thing here

Hmm, what exactly is the issue that you see here? I think default arguments actually very nicely capture the right semantics for a "filter" here:
"if there is a filter and it rejects an instruction, then don't instrument it"
So in the default case when there is no filter, nothing is filtered.

I'm wondering about the semantics: if the function returns true, is the instruction filtered out, or is it only to be included when the filter returns true?

In like 325, I check filter && !filter(instruction_it). So instructions are filtered out only if a filter is provided, and it rejects the instruction. std::function has an operator bool overload that makes this check, IMO, readable and succinct.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I shall accept the use of a default argument, but I'm afraid you're evading my question when it comes to the semantics: what is the desired result when a filter "rejects the instruction?" You can probably address this concern by changing the name of the parameter to something that a human expects a true/false (or yes/no) response to (such as "skip_instruction" or "should_be_instrumented").

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 think I evaded your question. I think explained the desired result. Quoting from my answer again:

"if there is a filter and it rejects an instruction, then don't instrument it"

Are you asking something else?

As to the name of the parameter, sure, I could change it but a "filter" has pretty standard semantics across all languages, okay at least those I know of, e.g. List.filter in OCaml / Haskell, Stream.filter in Java, and even the new views::filter in C++20. They all return the set of things that satisfy the given predicate (and that set of thigs is instrumented in our case).

Copy link
Collaborator

Choose a reason for hiding this comment

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

If you want to use the filter analogy, isn't what you are expecting the user to provide here the "predicate," and not the "filter" itself?

We are talking about a method that does not have any documentation, so the parameter names are all that a user can go by. RTFS is not an acceptable answer. The parameter you added requires answers to two questions, which the user has to be able to figure out just from the signature of this parameter and its name in the context of the name of the method:

  1. What are the semantics of this function to be provided?
  2. [Because of the default argument:] When does a function need to be provided?

It may well be that the right answer is to write documentation instead of trying to come up with a parameter name that is self explanatory, but the current status is not good enough.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If you want to use the filter analogy, isn't what you are expecting the user to provide here the "predicate," and not the "filter" itself?

The analogy holds because each predicate can only result in a single filter, i.e. one cannot derive two distinct filters from the same predicate.
So, the user gives gives a predicate, which defines the unique filter under discussion.

In any case, I will add some doxygen doc.
What would you like this parameter to be named?

Copy link
Collaborator

Choose a reason for hiding this comment

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

If you add comments then I won't care all that much about the name, because I expect to be able to construct a suitable function just by reading those comments. Else I'd have gone with needs_instrumentation.

Copy link
Collaborator

Choose a reason for hiding this comment

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

in other languages you'll find filter(collection, predicate) and filter_not(collection, predicate)

I'd name this pred, and precise that the semantics is that checks are applied when it returns true, and no arguments means "all instruction checked".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

precise that the semantics is that checks are applied when it returns true, and no arguments means "all instruction checked".

Yes, I will specify this in the doc comment.

Thanks for taking a look.

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.

Just needs documentation for the predicate !

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 SaswatPadhi merged commit ac6b8a9 into diffblue:develop Apr 21, 2022
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.

3 participants