Repository navigation
A23: out-parameters, inferred from what a body writes before it reads - #83
Open
SwayamInSync wants to merge 34 commits into
Open
SwayamInSync wants to merge 34 commits into
SwayamInSync wants to merge 34 commits into
Conversation
…ces of lifted specs.
…ec's const references as values, and name a helper that cannot be lifted.
…texpr helpers taking references.
…nwritten fails initialization.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Passing storage that is not initialized yet to a function that writes it before reading it was
Unresolved [construct.unsupported](#35): every pointer and reference parameter required initialized storage. C++ requires no such thing, since binding a reference or passing a pointer reads nothing ([dcl.init.ref], [basic.indet]). Now a parameter whose storage the body writes before it reads it is an out-parameter, and callers may pass it storage not yet initialized:On the base (#82):
Stacked on #82. Plan item A23 (F42, R33), the 0.2 form after #35's fail-closed one.
Design
The contract follows from the body, as
freshOwnedReturndoes (C#outparameters by definite assignment are the model), and the body is verified against it, so a wrong classification can make a proof fail but never make one wrong.Transform/OutParameters.cpp,inferOutParameters, run on the converted functions afterinferFreshOwnedReturns). For each pointer or reference parameter of a function with a verified body (not a constructor'sthis, never assigned), a walk keeps the byte offsets from the parameter written on every path: a branch joins by intersection, a loop adds nothing after it, areturnrecords the exit state. The parameter is an input when the walk finds a read of an offset not written, a read at a computed offset (p[i]), any use of the pointer other than an access, a null test,valid_ptr, or an address step (a copy, a store, a return, a comparison), a call passing it to a callee parameter that is not an out-parameter whose leaves are not all written (or that has an extent, or whose callee has no verified body), or a read of the entry state in a clause: preconditions (a class invariant is one), recommends, the measure, behaviors,old. Postconditions, invariants, andcppverify::checkdescribe what the body wrote, so the verifier checks their reads instead; but if the function never writes the storage they read the caller's values, and the parameter is an input (max_indexreads its array only in its postcondition).initialized_ptr; the converter's subobject binding pair (assert the object initialized, assume the part initialized) is removed for out-parameters, since binding reads nothing; each leaf written at every exit becomes a generated postconditioninitialized_ptr(p + k)of kindinitialization, which generalizes a constructor's exit checks.--dump-ir=1printsparam p out.tracksInitialization: provenance, or based on an out-parameter (source name or entry version). Stores set the initialization bit, loops count the initialization heap as modified (so the head keeps what was initialized and assumes the invariants' definedness), loads checkinitialized_ptrat the exact address (also under--no-check-ub), and so do the reads of postconditions at exit. Validity, frames, and origins stay those of any parameter.initializationchecks of each postcondition's reads, with the call's origin, sopost(!result || out == 5)gives initialized storage where the result is true. A call in a loop counts as modifying the initialization heap.emitRecordActualChecksskips the per-leaf check for an out record parameter.abstractValiditykeepsinitialized_ptron tracked storage, and a caller's own out-parameter passed to an input record gets the per-leaf checks.constructors.cpp). A constructor calling a member function that writes its object failed with a certified counterexample on main once the member was initialized (Resets() : v(1) { reset(); }:pointer-validity@6:5#2, and behind itframe): the record argument's last-byte check had no fact about the constructor's own object, and the entry state'sthislost its lifetime companion, so the frame*thisand the argumentthiswere different objects. The constructor's precondition now includes its last byte (which its callers already prove), and the entry state'sthiskeeps the companion (VConstructedObjectLifetime, now in the IR). Calling such a member before the members are initialized is fine now when its object is an out-parameter.Not changed: external and trusted callees (their parameters are inputs), returned pointers (
UninitializedResultNote), constructors'this.Tests
suite/out_parameters.cpp(Z3, with a check that no proof is vacuous; Layer 1 dumps;--no-check-ub), rewritten: local, array-element, and heap storage passed to out-parameters; a function written on every path with no postcondition (set_some); written on one path with a postcondition saying where (try_get), and a read after a miss that failsinitialization; write then read (write_then_update); inputs: read on a path first, read by the precondition, underold, passed to a reading callee first, through a trusted callee, through a pointer copy (alias_reads_garbagestaysUnresolved); pass-through to another function's out-parameter; records written field by field, through field references, and partly written (reads_unmadefailsinitialization); a postcondition about storage never written (an input) and one written on a path (set_iffails); loops in the callee and in the caller, across iterations; recursion; null tests; array filling and pass-through (fill_threefails); a member function writing*this.local_record_arguments.cpp:out_parameterverifies; a reading callee with a partly initialized record staysUnresolved.constructors.cpp:Resets::Resetsverifies; a member function that reads its object first, called before initialization, staysUnresolved.region_frames.cpp:claims_too_much, whosepis written only through a callee, failsinitializationat the cell its postcondition reads.local_reference_lowering.cpp,shared_logic_terms.test: the passive form and node count of a call to an out-parameter.The six fail on the base (each run with the base verifier):
scalar_outand the other new verdicts,out_parameter,Resets::Resets,claims_too_much'sinitialization, the passiveforall __init_address, and the node count.Mutants, each built on an assertions build and run on the six tests and
record_references.cpp,nested_quantifier_counterexamples.cpp, andpointer_subobjects.cpp: 24 of 26 killed; the two guards survive alone, as the classification never lets them decide, and kill their combinations.out_parameters,constructors,local_record_arguments,local_reference_lowering,region_framesout_parameters,record_referencesout_parameters(alias_reads_garbage)out_parameters(read_then_write: the call-site guard),record_referencesout_parameters,region_framesoldreads nothingout_parameters,region_frames,record_references,pointer_subobjectsout_parameters,pointer_subobjects,record_references,nested_quantifier_counterexamplesout_parametersout_parameters(try_get)out_parameters(zero_each)out_parameters(set_twice)out_parameters(make_by_parts),record_referencesout_parameters,constructorsconstructors,local_record_arguments,out_parameters,record_references,region_frames,local_reference_lowering,pointer_subobjectsout_parameters,region_framesout_parameters,local_record_arguments,region_framesout_parameters,local_reference_loweringout_parameters(got)out_parameters(init_once_in_loop)local_record_arguments,out_parameters,record_references,constructorsconstructors(Resets)thiswithout its lifetimeconstructors(Resets:frame)out_parameters(write_a_then_read, by the other guard)out_parameters(read_then_write, by the other guard)Docs
init,try_get,copy_to), what stays unsupported.counter.cppexample now verifieshalf_initializedand shows a reading calleeUnresolved.inferOutParameters, the passivizer's tracking, record calls, the constructor'sthis.Checked
docs_examples.testanddocs_coverage.testpass; the edited pages' 60 examples check, 20 against the verdicts they show; the site builds with warnings as errors.