-
Notifications
You must be signed in to change notification settings - Fork 284
CONTRACTS: New class instrument_spec_assignst encapsulating assigns clause checking logic.
#6575
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
CONTRACTS: New class instrument_spec_assignst encapsulating assigns clause checking logic.
#6575
Conversation
b2384c8 to
f8b5c50
Compare
tautschnig
left a comment
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.
Just some preliminary comments below. Overall, this is really hard to review as it's hard to tell what is a change in behaviour (if any?) and what is just refactoring. It it truly were refactoring-only then surely no tests should change. Having this split up in several commits would have helped a lot.
| // ^^^ FIXME ^^^ we should only allow assignments to static locals | ||
| // declared in functions that are called in the loop body, not from the whole | ||
| // function... |
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 we tracking this task somewhere?
| auto payload = instrument_spec_assigns.track_spec_target(target); | ||
| snapshot_instructions.destructive_append(payload); |
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.
Would it perhaps make for a better interface if snapshot_instructions were passed as an argument?
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 refactored the API to use destination passing style, but since sometimes we have to append instructions, or to insert them before a target, or after a target, with or without swapping, we often end up having to declare a temporary goto_programt destination anyway.
| insert_before_swap_and_advance( | ||
| goto_function.body, loop_head, snapshot_instructions); | ||
| }; | ||
| // ^^^ FIXME this is not true ^^^ |
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.
Can we just fix this 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.
this actually depends on wether
- a loop assigns clause is meant to represent the whole set of locations ever touched by the loop until completion, and hence
- no target expression should depend on other expressions also touched by the loop (i.e. we do not allow array slice expressions a[0..i]),
- it can be snapshotted before havoc+invariants
or
- it should summarize an arbitrary execution prefix of the loop, in which case:
- a target expressions could depend on each other like
(i, a[0..i]) - targets should be snapshotted after havocking and assuming the invariant.
- the whole clause should be proved to be included in itself post loop-step
and we have not really decided on the semantics yet.
| } | ||
| } | ||
|
|
||
| #if 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.
At least a comment would be really helpful 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.
this was leftover from refactoring, now removed
| #include <util/pointer_expr.h> | ||
|
|
||
| #include "assigns.h" | ||
| // #include "assigns.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.
Just remove it. It's very easy to re-add if needed...
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
| skipt skip_parameter_assigns, | ||
| optionalt<cfg_infot> &cfg_info_opt); | ||
|
|
||
| #if 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.
Comment, 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.
removed
dfe63b9 to
62cca11
Compare
Codecov Report
@@ Coverage Diff @@
## develop #6575 +/- ##
===========================================
- Coverage 76.65% 76.64% -0.01%
===========================================
Files 1579 1580 +1
Lines 181294 181542 +248
===========================================
+ Hits 138973 139148 +175
- Misses 42321 42394 +73
Continue to review full report at Codecov.
|
SaswatPadhi
left a comment
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.
Below are some minor initial comments based on a quick look at the changes.
I share the same concern as @tautschnig, that the change is huge (even ignoring the regression tests) and so is kind of hard to review. The new assigns clause code is MUCH larger than the previous assigns.cpp code and to me it seems quite complex (but I am also looking at it after a long time, and after the conditional assigns clause stuff was added).
My requests are to split the PR to multiple smaller commits if possible, and also add some description to the PR and/or the commit message as to why having these exprt subclasses is desirable.
| // set of tracked conditional targets that come from heap-allocated objects | ||
| conditional_target_sett from_heap_alloc; | ||
|
|
||
| using car_mapt = std::map<const conditional_target_exprt, const car_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.
Why not:
| using car_mapt = std::map<const conditional_target_exprt, const car_exprt>; | |
| using car_mapt = std::unordered_map<const conditional_target_exprt, const car_exprt, irep_hash>; |
| void | ||
| invalidate_aliases(const car_exprt &freed_car, goto_programt &dest) const; | ||
|
|
||
| using conditional_target_sett = std::set<conditional_target_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.
Why not:
| using conditional_target_sett = std::set<conditional_target_exprt>; | |
| using conditional_target_sett = std::unorederd_set<conditional_target_exprt, irep_hash>; |
| add_pragma_disable_assigns_check(*it); | ||
| return prog; | ||
| } | ||
|
|
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.
Am I missing something or have these two functions (this one + the one above) just been moved to a different location in the same file? If so, can we move those back to reduce the diff?
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 change was made so that the order of definitions in the cpp file follows the order of declarations in the header file, after moving havoc_assigns_targett from the now defunct assigns to utils.
355d3e4 to
b6dfda0
Compare
b6dfda0 to
ced8795
Compare
07ba00a to
22893ba
Compare
tautschnig
left a comment
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 ok in general, but a whole bunch of comments on individual bits of code.
src/ansi-c/c_typecheck_code.cpp
Outdated
| // now each target is either a simple side-effect-free unconditional lvalue | ||
| // expression conditional target group expression with a non-trivial condition |
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.
Missing "or?"
| #include "utils.h" | ||
|
|
||
| #include <analyses/call_graph.h> | ||
|
|
||
| #include <langapi/language_util.h> | ||
|
|
||
| #include "havoc_assigns_clause_targets.h" | ||
| #include <util/arith_tools.h> | ||
| #include <util/c_types.h> | ||
| #include <util/expr_util.h> | ||
| #include <util/pointer_offset_size.h> | ||
| #include <util/pointer_predicates.h> | ||
| #include <util/simplify_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.
Nit pick for consistency with other places: use the following order
- Include the header file corresponding to the implementation (as done here)
- Includes from
util - Includes from other directories
- Includes from the current directory
| if(can_cast_expr<conditional_target_group_exprt>(expr)) | ||
| track_spec_target_group(to_conditional_target_group_expr(expr), dest); |
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.
How about
if(auto target = expr_try_dynamic_cast<conditional_target_group_exprt>(expr))
track_spec_target_group(*target, dest);
| conditional_target_exprt target{ | ||
| true_exprt{}, symbol_expr, symbol_expr.source_location()}; | ||
|
|
||
| return from_stack_alloc.find(target) != from_stack_alloc.end(); |
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 it really necessary to construct an expression this complicated just for the sake of a lookup? I'd expect the keys to be much more succinct (I'm particularly worried about the source location seemingly being required).
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 specialized the lookup tables to have more specific key types depending on context, which caused some code inflation.
| // ensure it is tracked | ||
| PRECONDITION_WITH_DIAGNOSTICS( | ||
| stack_allocated_is_tracked(symbol_expr), | ||
| "symbol is not tracked :" + from_expr(ns, "", symbol_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.
I'd prefer the use of format (from util/format_expr.h) over from_expr. Ideally, we'd restrict from_expr to output that has to be language-specific.
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.
All I see in format_expr.h is std::ostream & format_rec(std::ostream &os, const expr &src) which is barely used in the whole codebase (28 uses), however I see a lot more uses of from_expr() (139 occurences).
Also format_rec renders directly to ostreams so from_expr seems necessary when rendering expressions to irep_ids to generate comments, or precondition/invariant message strings. What should I use to render to a string in a language agnostic way ?
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.
Ah, my apologies for the lack of clarity. It's: stream << format(expr); or string = format_to_string(expr);
| side_effect_expr_nondett nondet(target_type, source_location); | ||
| const auto &inst = dest.add(goto_programt::make_assignment( | ||
| dereference_exprt{target_snapshot_var}, nondet, source_location)); | ||
| dereference_exprt{typecast_exprt::conditional_cast( | ||
| car.lower_bound_var(), pointer_type(target_type))}, | ||
| nondet, | ||
| source_location)); |
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 think we might be better off using nondet_initializer here for that would produce nondets for individual fields (based on the type). goto-symex could then take each of these in its field-sensitive encoding.
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 is the relation between side_effect_expr_nondett (which also carries type information) and this nondet_initializer class ? It seemed to me that the latter was used to lower the former to basic GOTO before symex.
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 latter will use side_effect_expr_nondett by either returning exactly such (for all basic types), or by creating a compound object that in turn contains side_effect_expr_nondett (for example a struct expression with a nondet for each member).
| } | ||
| dest.add( | ||
| goto_programt::make_dead(car.upper_bound_var(), source_location_no_checks)); | ||
| } |
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.
Github says there's no newline at end-of-file.
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
| namespacet &_ns, | ||
| symbol_tablet &_st, |
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 said in another comment: don't take both a namespacet and a symbol_tablet (and essentially never take a non-const namespacet).
| havoc_argument, | ||
| allow_null_lhst::NO, | ||
| cfg_info_opt); | ||
| auto havoc_argument = instruction_it->get_other().operands().front(); |
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 probably want to_unary_expr(instruction_it->get_other()).op()
| call_sequences.cpp \ | ||
| contracts/assigns.cpp \ | ||
| contracts/contracts.cpp \ | ||
| contracts/havoc_assigns_clause_targets.cpp \ | ||
| contracts/instrument_spec_assigns.cpp \ |
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 should be part of the commit that removed the old file so as not to break git bisect applications.
22893ba to
1d326d8
Compare
remi-delmas-3000
left a comment
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 addressed all comments except the one regarding from_expr and the use of nondet_initializer.
| insert_before_swap_and_advance( | ||
| goto_function.body, loop_head, snapshot_instructions); | ||
| }; | ||
| // ^^^ FIXME this is not true ^^^ |
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 actually depends on wether
- a loop assigns clause is meant to represent the whole set of locations ever touched by the loop until completion, and hence
- no target expression should depend on other expressions also touched by the loop (i.e. we do not allow array slice expressions a[0..i]),
- it can be snapshotted before havoc+invariants
or
- it should summarize an arbitrary execution prefix of the loop, in which case:
- a target expressions could depend on each other like
(i, a[0..i]) - targets should be snapshotted after havocking and assuming the invariant.
- the whole clause should be proved to be included in itself post loop-step
and we have not really decided on the semantics yet.
| auto payload = instrument_spec_assigns.track_spec_target(target); | ||
| snapshot_instructions.destructive_append(payload); |
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 refactored the API to use destination passing style, but since sometimes we have to append instructions, or to insert them before a target, or after a target, with or without swapping, we often end up having to declare a temporary goto_programt destination anyway.
| } | ||
| } | ||
|
|
||
| #if 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.
this was leftover from refactoring, now removed
| #include <util/pointer_expr.h> | ||
|
|
||
| #include "assigns.h" | ||
| // #include "assigns.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.
yes
| skipt skip_parameter_assigns, | ||
| optionalt<cfg_infot> &cfg_info_opt); | ||
|
|
||
| #if 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.
removed
| const namespacet &_ns, | ||
| symbol_tablet &_st, |
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.
Good point.
| /// Message handler | ||
| message_handlert &message_handler; | ||
|
|
||
| /// Logger | ||
| messaget log; |
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 did not realise the message_handler was not used elsewhere.
| conditional_target_sett from_heap_alloc; | ||
|
|
||
| using car_mapt = std:: | ||
| unordered_map<const conditional_target_exprt, const car_exprt, irep_hash>; |
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 need to make sure that things stored in this map will never get modified.
| side_effect_expr_nondett nondet(target_type, source_location); | ||
| const auto &inst = dest.add(goto_programt::make_assignment( | ||
| dereference_exprt{target_snapshot_var}, nondet, source_location)); | ||
| dereference_exprt{typecast_exprt::conditional_cast( | ||
| car.lower_bound_var(), pointer_type(target_type))}, | ||
| nondet, | ||
| source_location)); |
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 is the relation between side_effect_expr_nondett (which also carries type information) and this nondet_initializer class ? It seemed to me that the latter was used to lower the former to basic GOTO before symex.
| } | ||
| dest.add( | ||
| goto_programt::make_dead(car.upper_bound_var(), source_location_no_checks)); | ||
| } |
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
1d326d8 to
da0b0dc
Compare
|
The change to the |
chrisr-diffblue
left a comment
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've only reviewed the Makefile changes - so consider my approval as mainly for that.
da0b0dc to
19fe124
Compare
…the front-end (defer to actual instrumentation).
- new class conditional_target_exprt derived from exprt representing conditional targets - new class car_exprt derived from exprt representing normalised conditional targets extended with their conditional address ranges and snapshot variables - new class instrument_assigns_clauset encapsulating assigns clause checking logic - simplified interface with 5 main methods to generate CARs snapshots for different types of targets, generate inclusion checks, invalidate CARs - Add the new intrument_spec_assigns.cpp to the Makefile
from instrument_assigns_clauset. Eliminate code duplication for CAR calculation, target validity assertions properly handle condition cleaning, local statics when havocking assigns clauses.
- Move havoc_assigns_clauset from contract/assigns to utils - Moving functions for tracking assigns clause replacement to utils to break circular dependency - Unified interface for adding "disable:check" pragmas on locations, instructions and programs
- Remove the old assigns clause checking implementation - Remove assigns.cpp from the Makefile.
…ment_assigns_clauset.
19fe124 to
63d03bc
Compare
The previous
assigns_clausetclass leaked its internal representations to the outside world and important control logic was spread betweenassigns.cppandcontracts.cpp.New classes:
conditional_target_exprtderivingexprt: represents a single conditional target from the source programconditional_address_range_exprtderivingexprt: represents a CAR derived from a conditional targetinstrument_spec_assignstencapsulating assigns clause checking algos:Changes:
havoc_assigns_clause_targetsis now a classhavoc_assigns_clause_targetstthat inherits frominstrument_spec_assignstand reuses all the snapshotting and target validity checking conditionsinstrument_spec_assignstNo performance changes expected.