Repository navigation
B5: calls of functions without contracts verified through their inlined bodies - #121
Merged
Merged
Conversation
…efined for its argument.
…on check's own place as its focus.
…builtin-argument as definedness.
…ions without a type, as a static_assert message.
…ic_assert with a message.
…e C++ evaluates them, each once.
…h integer type in cppverify.h.
… be inlined, and inlined extents.
…the helpers, and inline them.
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 B5 (helpers without contracts), decision Q10 (a): a call of a function without a contract stopped its caller, the most common first error users hit. Stacked on #118 (B13, on #117 and #116), whose commits it shows until those merge.
On the base
Both callers stop, and the helper, verified on its own because it has a loop contract, fails at
n = -1, which no caller passes.With this PR
A call of a function with a body and no contract runs that body: the caller's proof covers it at the arguments the caller passes, its checks are the caller's, and it reads and writes the caller's objects within the caller's frame.
count_zeroshas no verdict of its own: a function without a contract is verified where verified code calls it, and on its own only when none does.Design, from the references
SpecInlinerdoes for specs. The converter lowers a call of a helper as it lowers a contracted call (lowersToCall,hoistExecCall, the call statements), so the arguments are evaluated in C++'s order (B9, B11), andatCallmarks itInlinesHelper. After the main loop it converts each helper once (convertFunction(FD, /*AsInlineHelper=*/true),IsInlineHelper).inlineHelperCalls(Transform/InlineHelpers.cpp, new) then runs in the converter, before the call-graph analyses, so recursion groups, clusters, spec dependencies, origins, UB instrumentation, and passivization all see the inlined code, and no pass after it needed a change.__inline1_find$i); each parameter becomes a local assigned its argument. Eachreturn ebecomesresult = e; returned = true; outside loops what follows a statement that may return is guarded by!returned, inside a loop the return also breaks, a loop that may return is followed byif (returned) break;in an enclosing loop, and each such loop gets the generated invariant!returned, proved like any other (a path that sets it leaves the loop at once). The body's result is a local of the helper (__inline1_find$__result), given a value of its type before the body and copied to the call's target after it, so that a value assigned on a loop's return path reaches the loop's exit (the passivizer treats a variable first assigned in a loop as the loop's own; without itindex_ofbelow failed with an arbitrary result). Havocking it instead had no type in the passive program (invalid sortwith nested loops).count_zeros::i), bounded pointers, spec dependencies,decreases(*)(the caller is[partial]), an unmeasured loop (decreases.missing), and its extent, so that a failure's focus inside the helper is shown (x + 1, not thereturn).Fail closed
Each case below is
Unresolvedwithconstruct.unsupported, at the call, with a message that names the helper and asks for a contract; the path ends there, so no other check of the caller can fail on a value the call would have produced:down has no contract and calls itself, so its body cannot be inlined; give it a contract, and through others,the functions without contracts even -> odd -> even call each other, ...;thisincluded, addresses the caller's object);cppverify::oldorcppverify::resultin the body, whose entry would be the caller's;with_goto has no contract, and its body cannot be inlined (with_goto: goto is unsupported at 331:5); give it a contract;give h10 a contract), since a partly inlined body only grows the queries (with the budget at 10000, a chain of helpers that call two helpers each reached the 4-million-node query limit first, with a message that does not say why).Unchanged: a
constexprfunction without a contract is still lifted as a spec, a constructor without a contract is not inlined, and a function without a body, of the standard library, or of a system header is stillcall to function without a verification contract.Tests
suite/inline_helpers.cpp(new), 28 functions: a scalar helper verified under one caller and failing under another (overflowat the helper'sx + 1, withadd_one::xin the counterexample); early returns (sign, its exact value, and a false claim); writes through a pointer within the caller's frame, outside it (frame), and past its extent (bounds); a loop with its invariant at the caller's extent and one past it; a return inside a loop (find) and a false claim about it; helpers calling helpers and contracted functions, whose precondition fails at a helper's call (precondition); member functions reading and writing their object; a helper'scppverify::check; a record parameter; a helper's local named like the caller's; every fail-closed case above, with its message; the budget; and a Layer-1 dump of the inlined loop (the renamed locals, the flag, its invariant, the break).QUIET-NOTchecks that no helper gets a verdict of its own.suite/unused_template.cpp: a call of a function template without contracts now verifies through its body.On the base the test's file gives
1 verified, 4 failed, 27 unresolved (construct.unsupported 27).On a release build of this branch, the whole Verify suite passes (509 of 510, one unsupported).
Mutants
Each mutant below, applied alone to the converter or the inliner and rebuilt, is caught by
inline_helpers.cpporunused_template.cpp(17 of 17):cppverify::oldis inlinedM9 survived a first run: every caller in the test that keeps a failed call is
Unresolvedeither way, so verdicts alone do not show whether its path ends. The test now dumpsuse_mid, whose call of a helper returning a record is not inlined, and checks theassume falseafter itsassert unsupported.Docs
check-doc-examples.pypasses on every changed page.Not done
cppverify::oldin a helper's invariant (its entry state would need a snapshot at the call), constructors, andconstexprhelpers with loops.(its precondition is assumed, not checked, at calls from unverified h), still names helpers, which is true of their calls from code the verifier does not verify, but not of the calls inlined into verified code.