Implicitly recover missing subexpr nullability by synthesizing it; log details.
Up to now each expr transfer function that depends on the nullability of its
subexpr must deal with this being missing, and (typically) recovers by making
its own nullability "unspecified".
Instead, we can populate the *subexpr's* nullability as unspecified, and run the
usual logic. This makes the code simpler, and occasionally more precise
e.g. `&unhandled_expr` is [nonnull, unspecified, unspecified, ...].
Centralizing this logic gives us some space to produce actionable debug logs:
[ RUN ] PointerNullabilityTest.DerefNullPtr
=== Missing child nullability: ===
CXXNullPtrLiteralExpr 0x5babf7487d8 'nullptr_t'
==================================
=== Missing child nullability: ===
IntegerLiteral 0x5babfde82d8 'int' 0
==================================
[ OK ] PointerNullabilityTest.DerefNullPtr (9 ms)
PiperOrigin-RevId: 502700687
diff --git a/nullability_verification/pointer_nullability_lattice.h b/nullability_verification/pointer_nullability_lattice.h
index 0249c8a..f4829aa 100644
--- a/nullability_verification/pointer_nullability_lattice.h
+++ b/nullability_verification/pointer_nullability_lattice.h
@@ -8,6 +8,7 @@
#include <ostream>
#include "absl/container/flat_hash_map.h"
+#include "absl/log/check.h"
#include "clang/Analysis/FlowSensitive/DataflowAnalysis.h"
#include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h"
#include "clang/Analysis/FlowSensitive/DataflowLattice.h"
@@ -30,6 +31,7 @@
: ExprToNullability(ExprToNullability) {}
Optional<ArrayRef<NullabilityKind>> getExprNullability(const Expr *E) const {
+ E = &dataflow::ignoreCFGOmittedNodes(*E);
auto I = ExprToNullability->find(&dataflow::ignoreCFGOmittedNodes(*E));
return I == ExprToNullability->end()
? std::nullopt
@@ -39,14 +41,19 @@
// If the `ExprToNullability` map already contains an entry for `E`, does
// nothing. Otherwise, inserts a new entry with key `E` and value computed by
// the provided GetNullability.
- void insertExprNullabilityIfAbsent(
+ // Returns the (cached or computed) nullability.
+ ArrayRef<NullabilityKind> insertExprNullabilityIfAbsent(
const Expr *E,
const std::function<std::vector<NullabilityKind>()> &GetNullability) {
- auto [Iterator, Inserted] = ExprToNullability->insert(
- {&dataflow::ignoreCFGOmittedNodes(*E), std::vector<NullabilityKind>()});
- if (Inserted) {
- Iterator->second = GetNullability();
- }
+ E = &dataflow::ignoreCFGOmittedNodes(*E);
+ if (auto It = ExprToNullability->find(E); It != ExprToNullability->end())
+ return It->second;
+ // Deliberately perform a separate lookup after calling GetNullability.
+ // It may invalidate iterators, e.g. inserting missing vectors for children.
+ auto [Iterator, Inserted] =
+ ExprToNullability->insert({E, GetNullability()});
+ CHECK(Inserted) << "GetNullability inserted same " << E->getStmtClassName();
+ return Iterator->second;
}
bool operator==(const PointerNullabilityLattice &Other) const { return true; }