Repository navigation
verify: Lend local records and addresses to callees that keep no address of them - #71
Open
SwayamInSync wants to merge 85 commits into
Open
SwayamInSync wants to merge 85 commits into
SwayamInSync wants to merge 85 commits into
Conversation
…ded integer quantifiers in the prelude.
…d or not reported.
…r does not represent.
…d the parameters that address a record.
…checking the whole record and its fields at the call.
… verdict and not an abort.
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.
A local record could not be lent to a callee, so a function over
Counter &orCounter *could not be called on a local, which is what a method call on a local object will be. On the base (with #70),bump(c)on a localCounter cwasand
reset(&c)an error (raw address-of is unsupported until lexical lifetime and escape effects are modeled). Now:Stacked on #70 (B3a-4 of plan item B3a).
Design
The scalar rule lends a local to a callee whose scan shows it only dereferences the parameter directly, because the caller frames the callee's whole-object footprint as the one cell at the address. A record has more than one cell, and the callee reaches them by offsets, which that scan rejects. But a callee verified in the object model already proves that every access through its parameter, at any field or element, lies in its object (the UB checks against the parameter's entry object). So what a record needs from the scan is only that no address of the local outlives the call: a borrow in Rust's sense, which Rust's borrow checker (and so Verus) enforces by types, and which here is checked on the callee's body.
keepsNoAddress(Transform/Passivize.cpp) closes the parameter's aliases over the pointer variables assigned an address derived from one (copies, steps, subobject pointers, local references), to a fixpoint, and rejects: storing such an address in memory, returning it, freeing it, passing it to a callee that keeps one (scanned the same way; itsvalidextent is allowed there, since the function passing it proved the extent inside its own object) or that returns a pointer, and a callee without a verified body (an external contract, a proof, or a function that allocates). A scan that reaches itself again through recursion assumes the callee keeps none: an escape is a finite path to a statement that keeps an address. Converting an address to an integer is no escape, since the frontend rejects every cast back to a pointer; the generated subobject invariantq % S == q.__lo % Sdoes exactly that.VFunction::RecordParamsnames the pointer and reference parameters to a record, with the identity of its layout. A provenance-backed actual for one takeskeepsNoAddress(only when the caller is in the object model); every other provenance-backed actual keeps the scalar scan.emitRecordActualChecks): the generated validity clause names the record's first byte; the call also asserts the validity of its last byte, which puts the whole record in the first byte's live allocation (an allocation's bytes are contiguous), and the initialization of every leaf of the callee's layout, as anunsupportedobligation with the out-parameter note, as a scalar out-parameter already is. Before, only the first leaf was checked, so a callee would have read a partly initialized record as initialized.havocFootprints): a footprint rooted at a record parameter is framed by its bytes (footprintBytes, aHeapFrameor cell stores), a region as the whole record and never only the exact first cell of the scalar rule. The caller's frame check accepts footprints in its own allocations; its alternative for an empty range replaced that disjunction instead of joining it, so a callee's range in the caller's own record failedframe(clear(r)withmodifies(r.buf[0 : 4])). On the base no actual reached it: the scalar scan rejects such a callee.&placerooted at a promoted local, not an array, passed to a pointer parameter, as it lowers a reference argument: the address with its provenance, the element access condition, and the binding check.&xof a scalar local takes the scalar rule. Stored in a variable,&xis still an error.What stays out
construct.unsupported, as for scalars).first(local.buf, 4)): the scalar scan rejects its offsets. Inside a lent record it works, since nested calls takekeepsNoAddress.--no-check-ub: nothing proves the callee's accesses inside its object, so a record local passed to a callee isconstruct.unsupported.Tests
suite/local_record_arguments.cpp, with Z3, BMC,--no-check-ub, and a Layer 2 dump:bump(p.left)), an element (bump(a[1])),&c.nand&xto a scalar pointer parameter; the other field and other locals keep their values; a wrong result fails;twice), walk a member array with a pointer (for (int *q = r.buf; ...)), pass a decayed member to a buffer callee, and recurse;clear), and a caller whose ownmodifiesnames only its out-parameter;aliasing;construct.unsupportedfor a partly initialized record; a callee that stores the address in the record, stores a copy of it, forwards it to one that stores it, or returns it; a callee with avalidextent (it could write past the local); and a callee without a body, called directly or through another;record_references.cppnow expects its local passed by reference to verify.Mutants, each built and run on
local_record_arguments.cppandrecord_references.cpp(the first sweep also on the seven local, reference, and dynamic-storage tests beside them): 14 of 14 killed.local_record_argumentslocal_record_argumentslocal_record_argumentslocal_record_argumentslocal_record_argumentslocal_record_argumentslocal_record_argumentslocal_record_arguments,record_referenceslocal_record_argumentslocal_record_argumentslocal_record_argumentslocal_record_argumentslocal_record_arguments&localrejected (the base)local_record_argumentsThe first sweep left two rules surviving because they were redundant, and both are gone: rejecting a callee that returns a pointer (the callee's own scan rejects returning the address), and a scan of
deleteandnewstatements (a function with either isUsesDynamicStorage, rejected before its statements are scanned, which the scan now says).Docs
construct.unsupported), what the call checks, what stays out.&xas arguments.Checked
local_record_arguments.cppfails under each mutant that restores a rule of the base: the scalar scan for records,&localrejected, and the empty-range alternative replacing owned storage.