Use the dataflow analysis framework to propagate the nullability of expressions.
Consider the following code:
```
template<typename T0, typename T1>
struct Pair{
T0 f;
T1 s;
};
void target(Pair<int*, int * _Nullable> p) {
*p.f;
}
```
We want to know whether `*p.f` is a null-safe operation. If `p` were a concrete structure, it would suffice to look at its type. However, since p is not concrete, its type signature has no nullability annotations: all sugar has been stripped away during template instantiation.
The base expression of `p.f` is `p`. If we look at the AST of `p.f`, we will find a `DeclRefExpr` of `p`, which looks roughly like this:
```DeclRefExpr 0x55... <col:x> 'Pair<int *, int * _Nullable>':'Pair<int *, int *>' lvalue ParmVar 0x55... 'p' 'Pair<int *, int * _Nullable>':'Pair<int *, int *>'```
Note that the nullability annotation that we wrote when declaring `p` is present in the `DeclRefExpr`. What is the nullability of `p`? Well, we can go over all the pointer types “inside” `p`'s `DeclRefExpr` and keep track of each of their nullabilities. We could even go ahead and store this information in some kind of data structure. Here, `p` is a `Pair` specialization with one non-annotated pointer and one pointer annotated as nullable. We can thus construct a *nullability vector* for `p` that looks like this: `{Unknown, _Nullable}`.
If we know that `p` has nullability `{Unknown, _Nullable}`, and if we know that the return type of `.f` is the first type argument of `p`, we can find that the nullability of `p.f` is `Unknown`, so the dereference above is safe.
Up until now, we have been uncovering the nullability of class template specializations as described above: we would find the expression's "base type" (in the case above, a `DeclRefExpr`), and use that to find the expression's nullability.
Now, consider a slightly more complex member access:
```
template<typename T0, typename T1>
struct Pair{
T0 f;
T1 s;
};
void target(Pair<Pair<int*, int * _Nullable>, int* _Nonnull> p) {
*p.f.s;
}
```
Again, we would want to look into the base expression of `p.f.s` to find its nullability. However, the base of `p.f.s` is `p.f`, which is itself also a `MemberExpr`. Like `.s`, `.f` does not contain type sugar. We would thus have to go one level further to find the `DeclRefExpr` of `p`. From there, we can compute the nullability of `p.f`, and use that to compute the nullability of `p.f.s`.
To search for the nullability of a templated expression as described above, we could recursively pattern match over different expression shapes. From a given expression, we would dive all the way down to its `Decl`, read its nullability, and then move up the AST, at each stage using the nullability of the "previous" expression to compute that of the one being visited. This is what we have been doing for a limited range of expression shapes. However, this process of traversing an AST in post-order is already done by our static analysis. Therefore, we could add this construction of nullability annotations as a step in relevant transfer functions. This is what this CL introduces.
Now, when processing a CFG element, we do the following:
1) If the element is a `DeclRefExpr` (e.g., `p`), construct its nullability vector from the type annotations (in this case, `{Unknown, _Nullable, _Nonnull}`), and store that in the `ExprToNullability` map in the dataflow lattice.
2) If the element is a `MemberExpr` (e.g., `.f` or `.s`), find the element’s base (in this case, `p` and `.f`, respectively), search for its nullability in the lattice maps, and use information about its type and the base type to construct its own nullability vector (`{Unknown, _Nullable}` and `{_Nullable}`, respectively). Store this vector in the `ExprToNullability` map.
This allows us to find the nullability of expressions whose base types have been desugared, without having to pattern match over specific expression shapes.
We currently process `MemberExpr` and `DeclRefExpr` CFG elements as defined above, but such a construction would also be useful for other expression types. For example, adding support for `MemberCallExpr`s would allow us to correctly assess the nullability of concatenated member *function* calls, such as `x.getArg0().getArg1()`.
PiperOrigin-RevId: 496120804
diff --git a/nullability_verification/BUILD b/nullability_verification/BUILD
index d15de47..e28cfaf 100644
--- a/nullability_verification/BUILD
+++ b/nullability_verification/BUILD
@@ -3,7 +3,10 @@
cc_library(
name = "pointer_nullability_lattice",
hdrs = ["pointer_nullability_lattice.h"],
- deps = ["@llvm-project//clang:analysis"],
+ deps = [
+ "@absl//absl/container:flat_hash_map",
+ "@llvm-project//clang:analysis",
+ ],
)
cc_library(