xref: /netbsd-src/external/apache2/llvm/dist/clang/docs/analyzer/developer-docs/nullability.rst (revision 7330f729ccf0bd976a06f95fad452fe774fc7fd1)
1*7330f729Sjoerg==================
2*7330f729SjoergNullability Checks
3*7330f729Sjoerg==================
4*7330f729Sjoerg
5*7330f729SjoergThis document is a high level description of the nullablility checks.
6*7330f729SjoergThese checks intended to use the annotations that is described in this
7*7330f729SjoergRFC: http://lists.cs.uiuc.edu/pipermail/cfe-dev/2015-March/041798.html.
8*7330f729Sjoerg
9*7330f729SjoergLet's consider the following 2 categories:
10*7330f729Sjoerg
11*7330f729Sjoerg**1) nullable**
12*7330f729Sjoerg
13*7330f729SjoergIf a pointer ``p`` has a nullable annotation and no explicit null check or assert, we should warn in the following cases:
14*7330f729Sjoerg
15*7330f729Sjoerg* ``p`` gets implicitly converted into nonnull pointer, for example, we are passing it to a function that takes a nonnull parameter.
16*7330f729Sjoerg* ``p`` gets dereferenced
17*7330f729Sjoerg
18*7330f729SjoergTaking a branch on nullable pointers are the same like taking branch on null unspecified pointers.
19*7330f729Sjoerg
20*7330f729SjoergExplicit cast from nullable to nonnul:
21*7330f729Sjoerg
22*7330f729Sjoerg.. code-block:: cpp
23*7330f729Sjoerg
24*7330f729Sjoerg  __nullable id foo;
25*7330f729Sjoerg  id bar = foo;
26*7330f729Sjoerg  takesNonNull((_nonnull) bar); // should not warn here (backward compatibility hack)
27*7330f729Sjoerg  anotherTakesNonNull(bar); // would be great to warn here, but not necessary(*)
28*7330f729Sjoerg
29*7330f729SjoergBecause bar corresponds to the same symbol all the time it is not easy to implement the checker that way the cast only suppress the first call but not the second. For this reason in the first implementation after a contradictory cast happens, I will treat bar as nullable unspecified, this way all of the warnings will be suppressed. Treating the symbol as nullable unspecified also has an advantage that in case the takesNonNull function body is being inlined, the will be no warning, when the symbol is dereferenced. In case I have time after the initial version I might spend additional time to try to find a more sophisticated solution, in which we would produce the second warning (*).
30*7330f729Sjoerg
31*7330f729Sjoerg**2) nonnull**
32*7330f729Sjoerg
33*7330f729Sjoerg* Dereferencing a nonnull, or sending message to it is ok.
34*7330f729Sjoerg* Converting nonnull to nullable is Ok.
35*7330f729Sjoerg* When there is an explicit cast from nonnull to nullable I will trust the cast (it is probable there for a reason, because this cast does not suppress any warnings or errors).
36*7330f729Sjoerg* But what should we do about null checks?:
37*7330f729Sjoerg
38*7330f729Sjoerg.. code-block:: cpp
39*7330f729Sjoerg
40*7330f729Sjoerg  __nonnull id takesNonnull(__nonnull id x) {
41*7330f729Sjoerg      if (x == nil) {
42*7330f729Sjoerg          // Defensive backward compatible code:
43*7330f729Sjoerg          ....
44*7330f729Sjoerg          return nil; // Should the analyzer cover this piece of code? Should we require the cast (__nonnull)nil?
45*7330f729Sjoerg      }
46*7330f729Sjoerg      ....
47*7330f729Sjoerg  }
48*7330f729Sjoerg
49*7330f729SjoergThere are these directions:
50*7330f729Sjoerg
51*7330f729Sjoerg* We can either take the branch; this way the branch is analyzed
52*7330f729Sjoerg* Should we not warn about any nullability issues in that branch? Probably not, it is ok to break the nullability postconditions when the nullability preconditions are violated.
53*7330f729Sjoerg* We can assume that these pointers are not null and we lose coverage with the analyzer. (This can be implemented either in constraint solver or in the checker itself.)
54*7330f729Sjoerg
55*7330f729SjoergOther Issues to keep in mind/take care of:
56*7330f729Sjoerg
57*7330f729Sjoerg* Messaging:
58*7330f729Sjoerg
59*7330f729Sjoerg  * Sending a message to a nullable pointer
60*7330f729Sjoerg
61*7330f729Sjoerg    * Even though the method might return a nonnull pointer, when it was sent to a nullable pointer the return type will be nullable.
62*7330f729Sjoerg  	* The result is nullable unless the receiver is known to be non null.
63*7330f729Sjoerg
64*7330f729Sjoerg  * Sending a message to a unspecified or nonnull pointer
65*7330f729Sjoerg
66*7330f729Sjoerg    * If the pointer is not assumed to be nil, we should be optimistic and use the nullability implied by the method.
67*7330f729Sjoerg
68*7330f729Sjoerg      * This will not happen automatically, since the AST will have null unspecified in this case.
69*7330f729Sjoerg
70*7330f729SjoergInlining
71*7330f729Sjoerg--------
72*7330f729Sjoerg
73*7330f729SjoergA symbol may need to be treated differently inside an inlined body. For example, consider these conversions from nonnull to nullable in presence of inlining:
74*7330f729Sjoerg
75*7330f729Sjoerg.. code-block:: cpp
76*7330f729Sjoerg
77*7330f729Sjoerg  id obj = getNonnull();
78*7330f729Sjoerg  takesNullable(obj);
79*7330f729Sjoerg  takesNonnull(obj);
80*7330f729Sjoerg
81*7330f729Sjoerg  void takesNullable(nullable id obj) {
82*7330f729Sjoerg     obj->ivar // we should assume obj is nullable and warn here
83*7330f729Sjoerg  }
84*7330f729Sjoerg
85*7330f729SjoergWith no special treatment, when the takesNullable is inlined the analyzer will not warn when the obj symbol is dereferenced. One solution for this is to reanalyze takesNullable as a top level function to get possible violations. The alternative method, deducing nullability information from the arguments after inlining is not robust enough (for example there might be more parameters with different nullability, but in the given path the two parameters might end up being the same symbol or there can be nested functions that take different view of the nullability of the same symbol). So the symbol will remain nonnull to avoid false positives but the functions that takes nullable parameters will be analyzed separately as well without inlining.
86*7330f729Sjoerg
87*7330f729SjoergAnnotations on multi level pointers
88*7330f729Sjoerg-----------------------------------
89*7330f729Sjoerg
90*7330f729SjoergTracking multiple levels of annotations for pointers pointing to pointers would make the checker more complicated, because this way a vector of nullability qualifiers would be needed to be tracked for each symbol. This is not a big caveat, since once the top level pointer is dereferenced, the symvol for the inner pointer will have the nullability information. The lack of multi level annotation tracking only observable, when multiple levels of pointers are passed to a function which has a parameter with multiple levels of annotations. So for now the checker support the top level nullability qualifiers only.:
91*7330f729Sjoerg
92*7330f729Sjoerg.. code-block:: cpp
93*7330f729Sjoerg
94*7330f729Sjoerg  int * __nonnull * __nullable p;
95*7330f729Sjoerg  int ** q = p;
96*7330f729Sjoerg  takesStarNullableStarNullable(q);
97*7330f729Sjoerg
98*7330f729SjoergImplementation notes
99*7330f729Sjoerg--------------------
100*7330f729Sjoerg
101*7330f729SjoergWhat to track?
102*7330f729Sjoerg
103*7330f729Sjoerg* The checker would track memory regions, and to each relevant region a qualifier information would be attached which is either nullable, nonnull or null unspecified (or contradicted to suppress warnings for a specific region).
104*7330f729Sjoerg* On a branch, where a nullable pointer is known to be non null, the checker treat it as a same way as a pointer annotated as nonnull.
105*7330f729Sjoerg* When there is an explicit cast from a null unspecified to either nonnull or nullable I will trust the cast.
106*7330f729Sjoerg* Unannotated pointers are treated the same way as pointers annotated with nullability unspecified qualifier, unless the region is wrapped in ASSUME_NONNULL macros.
107*7330f729Sjoerg* We might want to implement a callback for entry points to top level functions, where the pointer nullability assumptions would be made.
108