Repository navigation
A25: warn where the facts about specs close a path the code alone reaches - #122
Merged
Merged
Conversation
…th that continues.
… ARCHITECTURE.md.
# Conflicts: # website/source/release-notes.rst
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 A25 (path-level vacuity): the vacuity checks asked only whether anything reaches a verified function's end, its preconditions, its behaviors, and its trusted calls. An assumption that removes some paths and leaves others went unnoticed. That is how F38 verified a false claim without a mark: the contradictory definition of a spec recursive through
cppverify::choosemadex == 7impossible, andforty_twoverified althoughforty_two(7) == 0.On the base
checkedhasif (t < 0) return -1;undert == tri(n), whichtri's postcondition (result >= 0) makes dead. Nothing says so, and with a wrong fact in that place nothing would.With this PR
The result stays
Verifiedwithout[vacuous], since the verifier cannot tell dead code from a wrong fact. Acppverify::check(t >= 0);before the branch states the fact in the code and silences the warning.What it would have caught
F38 itself, reopened: with A19's fix reverted (the call-graph edges through a choose removed, so
fbelow again counts as non-recursive and needs no measure), the base reportsVerified: forty_twoand nothing else; this PR, on the same reverted converter, points at the branch the bad definition removed:With A19 in place,
fneeds a measure, as main reports.Design
readsframe, a choice's axiom. Each is proved before use, so each should be a theorem, and a wrong one is a verifier bug that cuts paths silently. Those are the facts this check withholds: passivization already labels them by origin (spec:,reads:,choose). The builtin specs (validity, a sequence'supdateandreverse, a constant table) are the language's, so their definitions and facts stay, and a function that applies only them is not checked.Uninterpretedcopy with its signature and nothing else, so each application is a function of its arguments and no more. Hiding the specs is not enough: the counterexample check holds a model's choices to their axioms, and its refinement gives them back (a branch closed by a choice's axiom went unreported that way).return, a loop's body (the iteration chosen and its condition true), and the path after a loop. For each, a smoke query asks whether all the statements before it refute its guard; only then does a second query, without the spec facts and with every spec hidden, ask whether the code reaches it. The first point found is reported, since the points after it share its cause; a function that applies no spec gets no queries.counterexample.unchecked), which with the specs' values free is a path of the code. A timeout orunknownreports nothing.exittrace event before the exit assumption, with the guard of the path before the loop, so it could not say whether anything continues after the loop. It is now emitted after the exit assumption with the path that continues; event order, every obligation's trace prefix, and the text of traces are unchanged.--warn-contradictory-assumptionswarns where assumptions contradict a path.Tests
suite/contradicted_paths.cpp(new): warnings at a branch closed by a spec's postcondition (checked_tri), by a recursive spec's definition (checked_steps), inside a loop (first_negative), at a loop's body (drain), after a loop left only by its return (climb), by a choice's axiom (picked), and by a cluster's induction hypothesis (total_nonneg); none for a branch the precondition closes (defensive,no_spec), one the code reaches (plain), one acppverify::checkstates (stated), one closed by a builtin spec's fact or definition (rev_len,rev_empty), or one whose verdict a failed postcondition demotes (uses_wrong). The warnings are checked in order in a stream of their own, with--implicit-check-not=warning:, which does not look inside a span ofCHECK-DAGmatches; the JSON record carries the warning.The test's warnings do not appear on the base.
On a release build, the whole Verify suite passes (505 of 506, one unsupported).
Noise. Over all 454 programs of
clang/test/Verify, run with this PR, the warning appears incontradicted_paths.cppalone (its 7 expected warnings): no other test program has a path that the facts about specs close and its code reaches.Cost. Each of the 184 test programs that apply a spec, verified with this PR and with the check switched off (alternating which runs first, 12 at a time): 1452.8 s against 1428.8 s, 1.7%; without the exhaustive ULEB128 decoder, a stress test lit does not run, within noise (1316.8 s against 1335.4 s). A first version, which checked every point of a function, cost 18% (the decoder took 332 s instead of 90 s), so a function's points now share the time of one vacuity check, no query starts after it, and the search ends at the first point whose query is undecided; the decoder then takes 73.1 s against 65.8 s alone.
Mutants
Each mutant below, applied alone and rebuilt, is run against
contradicted_paths.cppandbackend_counterexample_trace.cpp; 11 of 13 are caught:spec:stay in the code alonechoose) stayspec:); a fact of originchooseis the axiom of a choice inside another fact, which is withheld itself, so keeping the axiom alone constrains nothing the code names. It stays classified as a fact about a spec, since it is one.--implicit-check-not=warning:amongCHECK-DAGlines, which does not look between the matches of a group, so an extra warning there passed. The warnings are now checked in a stream of their own.Theorem; every such fact has the originspec:orreads:, so the test was redundant and is gone.Docs
check-doc-examples.py), and how to keep a dead branch;Not done
--warn-contradictory-assumptions); cpp-verify's backends do not extract cores yet, so the check runs queries of its own.check-doc-examples.pycompares only the first line of a wrapped warning; the rest of this one was compared by hand.