Repository navigation
H5: a ring buffer case study, and the four things it needed - #111
Merged
Merged
Conversation
…ps none of its invariants.
…S runner can meet.
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.
Plan item H5: a case study for 0.2. It is a class-based component that exercises the class and template support (B1 to B4) and the diagnostics (E3): a ring buffer. Writing it found four things the verifier did not support, and this PR adds them. It is stacked on #110 (D7).
The case study
Ring<N>keeps a queue of at mostNintegers in a fixed array, as embedded code keeps a UART's received bytes:buf,head,count) are private, with a class invariant over them;pushandpopare specified through two spec functions, friends of the class:at(r, i), the elementiplaces after the oldest, andsize(r). So the contract says what the class is, a FIFO queue, and not how it stores it;pushassertscount < Nwith C'sassert.Two clients use only the contract:
first_twoloads samples in a range-basedfor, adds one if the ring is not full, and shows that the two oldest are 1 and 2;applyruns a command, chosen by aswitchover a scoped enumeration, on a ring a reference names.With
-DFORGET_WRAP,popforgets the wraparound (head = head + 1;). The class invariant fails atpop's exit, athead < N, and the path shows the store of 4 intohead(address 17, after the 16 bytes ofbuf). The page,case-studies/ring-buffer.rst, shows both runs and explains them.What it took
1. Class invariants at loops. On
main, a loop that calls member functions of an object outside its class forgets the object's class invariant: at the loop head the verifier knows only the loop's invariants. With private fields, the client cannot restate the invariant there. Sofirst_two'sr.push(s)failed with[type-invariant], and the verifier's own note saidno execution fails this check ... the loops' invariants are too weak.The converter now appends to each loop's invariants the class invariants of the objects the loop reaches (
ASTConverter::appendObjectInvariants): the local objects alive at it, and the objects its function's reference and pointer parameters address (guarded byp == nullptr ||).This is sound because each one is checked at entry and preserved like the user's invariants, and is never assumed. Only the class's members, and code that writes a public field, change what an invariant states, and a public member keeps it. So the invariant holds at every iteration of a loop outside the class. The design follows Spec#'s and Dafny's discipline that an object's invariant holds whenever no method of its class is running on it, but proves it instead of assuming it.
A member function's own loops get none of its class's invariants, since a member may break one between calls.
Meter::recountin the test breaks its invariant inside its loop and verifies.2. The address of a local in a contract.
size(&r)in a loop invariant was an error (raw address-of is unsupported until lexical lifetime and escape effects are modeled). In a contract, the address of a local object in memory is now its address. A spec it is passed to cannot keep it, and nothing else in a contract can. In ghost code, which could keep it in a ghost variable, it is still an error.3.
hideandrevealof a spec template's specialization.cv::hide(at<4>)stopped the function (unsupported C++ type in verification: <overloaded function type>). Clang leaves a template-id that is not called as an overload set, so the parser now resolves it with Sema's own placeholder resolution (CheckPlaceholderExpr), as C++ resolves&at<4>. A template's name alone is now Clang's error (reference to overloaded function could not be resolved; did you mean to call it?). A first version passedComplaintoResolveAndFixSingleFunctionTemplateSpecializationwithout a diagnostic ID, which crashes in the diagnostic printer on an unresolvable name; the test covers that name.4.
first_twohidesat. Its proof needs only the contracts ofpushandpop, which speak ofat. Visible, the definition (buf[(head + i) % N]) puts%inside every quantifier, and the loop invariant's preservation timed out at 30 s. Hidden, the whole file verifies in 1.9 s.Tests
suite/ring_buffer.cpp: the case study, verified, and with-DFORGET_WRAPfailed with the store that breaks the invariant.suite/class_invariants_in_loops.cpp:count_to), an object a reference names (fill), one a pointer addresses in adoloop (drain), and a pointer that may be null (idle);this(recount), and on another object of the class, which a reference names (empty);spread), which fails[invariant-preserved]at the class's invariant: the generated invariant is checked, not assumed;&xof a local in ghost code, which stays an error.suite/hide_template_specs.cpp:hideof a specialization (spec.hidden, namingscaled<3>);reveal_with_fuelof a recursive one;On the base:
ring_buffer.cppstops atcv::hide(at<4>);class_invariants_in_loops.cpprejects&m, failsfill([type-invariant]), and times out ondrain;hide_template_specs.cppstops at everyhide.Each mutant below, applied alone and rebuilt, is caught by these tests (8 of 8). H2 survived a first run, since
recount's loop reaches no object of its class other thanthis, which is no parameter;emptywas added for it:On a release build of this branch, the whole Verify suite (503 tests) passed except
docs_examples.test: the ring buffer page showed the one-line bug in a code block that does not compile alone. The block is now marked as a fragment, and the docs check passes (267 examples, 0 failed);class_invariants_in_loops.cpppasses with its added case.Docs
structs.rst: class invariants at loops, with an example and its output, and the address of a local in a contract;ghost-proofs.rst: specializations inhideandreveal;limitations.rst: spec member functions and spec parameters of class reference type, still unsupported, and an address taken in ghost code;Not done
cv::spec int at(int i) const) and spec parameters of class reference type (const Ring<N> &). A view is a free function taking a pointer, a friend when it reads private fields. Both are follow-ups.q!frame). A query with such a quantifier is solved by Z3'ssmttactic. Inclass_invariants_in_loops.cpp's first twin, that tactic gave up after 20 s on a check that the default solver refutes in 24 ms;--dump-smt2(D7: every solver query as a standalone SMT-LIB script (--dump-smt2) #110) showed it. The twin now uses a local object, and the solver choice is a follow-up.Stacked on #110.