Repository navigation
B13: memcpy, memmove, memset, memcmp, and abs through models, and quantifiers over shifted slices - #118
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.
…'s quantifier for each slice offset it reads.
… abs to its definition.
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 B13 (library primitives):
memcpy,memmove,memset,memcmp, andabsstopped their functions. Stacked on #117 (B11, on #116), whose commits it shows until those merge.On the base
The example below (with this PR's output further down):
abswas the same error. The coverage investigation counted about 92 uses of these and the string functions in 29 files of LLVM'slib/Support.With this PR
Design, from the references
share/libc/string.h) states these functions in ACSL over bytes, through(char *)casts:valid_or_empty(dest, n),\separatedranges formemcpy,assigns dest[0..n-1] \from src[0..n-1], and logic functionsmemcmp,memset,strlen. That needs a memory of bytes, which WP's Typed+Cast model and Eva have. CBMC's memory is bytes too.void *cannot speak of the objects behind it, and an overload in<cppverify.h>would change overload resolution and need a definition at link time.So the models are contracted declarations in
<cppverify.h>, incppverify::model, one per type of object (every integer type, andboolfor copies and comparisons), readable as C++:ASTConverter::convertLibraryCallrecognizes the four functions by builtin ID (memcpy,std::memcpy,__builtin_memcpy, ...), reads the objects each pointer addresses as written, before its conversion tovoid *(an enumeration as its underlying integer), and lowers the call to a call of that type's model withk = n / sizeof(T). The modular call then does the rest, as for any callee: each extent lies in the caller's object,memcpy's two ranges are separate (two pointer parameters of which one is mutable;memmove's model hasaliases), the frame isd[0 : k], and the postconditions hold after it.memset(d, c, n): the model takes the object every byte of which is(unsigned char)c,(unsigned char)c * 0x0101...reinterpreted asT, exact for any width and byte order.memcmp: 0 exactly when the objects are equal; of bytes, negative exactly when the first difference is smaller asunsigned char. Of wider objects the sign depends on the byte order, which is not modeled.memcpy(d, s, 6)ofint) is defined C++ that this memory cannot represent: anunsupportedobligation thatn % sizeof(T) == 0, with a note (VAssertStmt::Note, new, passed to the passive statement's).void *, and objects of two sizes are an error naming the types:memcpy of 'Pt *' and 'const Pt *' is unsupported: it is modeled for pointers to integers of one size.The models are trusted, as the C library is, so every proof that uses one says so (
[trusts=memcpy]), and through its callers too. They are converted only where a call uses one (UsedLibraryModels, after the main loop, which skipscppverify::model) and markedIsBuiltin: not verified, not reported, and still trusted for the fixpoint and[trusts=...]. Without that, every file would print oneTrusted:line per model.abs,labs,llabs, andstd::absof an integer are their definition,x < 0 ? -x : xin the parameter's type in the function's integer mode (convertAbs):abs(INT_MIN)is anoverflowin executable code, in a contract too, and in a spec, whose integers are mathematical,absis the absolute value.Shifted ranges, found here
memmove(a, a + 1, ...)timed out: no quantified claim about a copy between shifted ranges was proved, with any callee. A callee's quantifier that reads through a slice actualbase + cis restated overbase's indices (rebaseSliceBinders, j := k - c), so that the caller's reads trigger it. It took the first offset it found, though, and a quantifier that reads one pointer directly and another throughb + 1was restated forb + 1, which the reads of the other no longer match. It also did not look under a cast, which every Boolean result has (cv::result == cv::forall(...)).Now a quantifier is restated when all the loads its binder indexes go through one slice offset (
collectSliceOffsets,SliceUse). With several offsets, or a direct load beside a slice, an assumed postcondition keeps the quantifier and adds its restatement over each slice's base: equivalent formulas, each with the triggers of its own form, so the caller's reads trigger one of them. A check (a precondition at a call) is left as it is, since the solver would have to prove each form.memmove(a, a + 1, ...)andmemmove(a + 1, a, ...)now verify at once, and so does a user's copy between shifted arrays.Mutants
Each mutant below, applied alone and rebuilt, is caught by
library_models.cpporslice_quantifier_offsets.cpp(18 of 18). M15 first survived as "abs of a contract is mathematical": inside an executable function the integer mode is machine, contracts included, so it changed only specs, where the absolute value is the right meaning;absnow takes the function's mode, and a spec test catches the machine reading.memcpyis not loweredvoid *boolis copied from acharmemsetstores its byte unrepeatedmemsetdoes not reducecto a byteabsis the argumentabsin a spec is of machine integersTests
suite/library_models.cpp(new):memcpyofint(and through a caller, withstd::memcpy), of an enumeration overunsigned char;memmoveto both sides;memsetto 0, to0xff(eachintis -1), and to a byte ofunsigned char;memcmpas equality ofints and as order of bytes; failures: a fill claimed 1 where eachintis0x01010101, an overlappingmemcpy(aliasing), one past the end (bounds); a size that splits an object (construct.unsupported); records (an error);absandstd::absverified, at the minimum (overflow), and in a contract.suite/slice_quantifier_offsets.cpp(new): copies between shifted ranges both ways and a Boolean callee result over a shifted slice, with--timeout=5000; the passive dumps show the quantifier restated (one offset) and kept beside its restatement (two).cases/library.py(new suitelibrary, 36 cases) runs each function of the real C library on 200 random buffers, guard pages around them, under contracts verified with the models:memcpy,memset, andmemcmpfor nine integer types,memmoveboth ways, byte order,abs,labs. No run breaks a verified contract, and the false claims (a copy that changes nothing, a fill that storesc, a comparison always 0,absof the minimum) fail with real counterexamples:36 cases, 0 problems. cvc5 leaves most of the memory casesunknown.On the base all 23 functions of
library_models.cppare unresolved (call to function without a verification contract), and inslice_quantifier_offsets.cppshiftedandboth_shiftedtime out (shifted_targetverifies there: the first offset the old rule found was the destination's, which its reads match).The whole Verify suite runs in this PR's CI (Linux release and assertions, macOS, Windows).
Docs
Not done
strlenand the other string functions: a terminator must lie in the string's object, whose end the converter does not know.memset(&s, 0, sizeof s)), field by field.Stacked on #117.
Found by macOS CI
library_models.cppfailed on macOS:distance(std::absof along) wasconstruct.unsupported. libc++ definesabs(long)andabs(long long)in the global namespace of its<stdlib.h>, and its<cstdlib>brings them intostdwithusing ::abs, where libstdc++ defines them instditself; the model recognized only the latter. A globalabsdeclared in a system header is nowstd::abstoo, and anabsof the user's own is still a function like any other.library_abs_global.cpp(new) mimics libc++'s header (Inputs/global_abs.h, a system header): it fails on the previous head, and fails when the system-header condition is dropped (a user'sabs(long)returningxwould be taken as the absolute value). The other macOS failure,confirmation_invariants.cpp, is the flake #115 fixes.