This checker implements the lifetime rules presented in the paper
by Herb Sutter and Neil MacIntosh paper [1].
Basically, for each "Pointer" we track a set of possible "Owners" to which it
it directly or transitivly points, called its pset (points-to set).
If an Owner is invalidated, all Pointers that contain that Owner
in their pset get an "invalid" pset. For an in-depth explanation, please refer to the paper.
"Pointers" are not only variable with pointer type, but also references,
and objects that contain Pointers (e.g. iterators).
A pset can contain "null", "invalid", "static" (for static duration Owners that cannot
be invalidated), and tuples (Owner, order). Order 0 means that the
Pointer points to the object, e.g. int* p = &i => pset(p) = {(i,0)}.
Order 1 means that the Pointer points to something owned by the object,
e.g. int* p = o.getp() => pset(p) = {(o,1)}.
The paper, and thus the checker, are implemented in a path-independent
way. That leads to some false positives, but increases speed and
allows e.g. to reason about loops without unrolling.
Function bodies are analyzed separately. When calling a function,
the caller can assume how the psets of the Pointers change.
Those assumptions are checked for each analyzed function. (Just like for
const methods; callers assume that the object does not change; bodies of
const methods are not allowed to change the object).
The checker assumes that the code to be checked is valid
according to the C++ Core Guidelines type and bounds profile.
If "forbidden" expressions (such as pointer arithmetic) are used,
or if an unimplemented expression is encountered, the resulting pset
will be "unknown". Psets containing "unknown" will not cause any
warnings by the checker.
The checker will emit the pset of an variable if it finds a call to
"clang_analyzer_pset" in the C++ code. This is used by the tests to
display the pset of a Pointer.
For now, the checker is split into ProLifetimeCheck.cpp and
ProLifetimeVisitor.cpp. The only reason is that it speeds up the
compile time for me. ProLifetimeCheck.cpp is quite slow to compile
(I guess due to ASTMatchers.h). I can happily merge both cpp files before
the commiting, if required.
This checker is not complete. I would like to hear your comments
about high level stuff (e.g. architecture) and what is the best way
forward to get this upstream.
Currently state:
- Pointers are only variables of pointer type or reference type (no
objects containing Pointers, like iterators).
- Psets are computed through all expressions and conditionals.
- "(null)" will be removed from a pset in a branch
if the pointer appears in a conditional guarding that branch. It does
not handle all types of conditionals yet.
- Pointers are invalidated when the Owner they point to goes out of scope.
- Dereferencing invalid pointers is flagged.
- Pointers with static duration will be checked on assignment to only
have a pset of (null) and/or (static). Anything else is flagged.
- CFGs with loops are supported and lead to an iterative refinement
- I have run this on the llvm code base, and the diagnosed issues are mostly related to null pointer dereferencing. I would not call them
false-positives, because this checker assumes that pointer
arguments can be null, and otherwise gsl::not_null should be used.
If you like to see what it can diagnose, look at
test/clang-tidy/cppcoreguidelines-pro-lifetime.cpp
For the internal pset propagation, look at
test/clang-tidy/cppcoreguidelines-pro-lifetime-psets.cpp
(incomplete) TODO:
- track psets for objects containing Pointers
- compute pset of return values and out-parameters in function calls
- check correct pset in 'return' statments
- support annotations for non-standard lifetime behavior
Depends on D15031
Should write that short description. :-)