xref: /openbsd-src/gnu/llvm/clang/docs/analyzer/developer-docs/nullability.rst (revision 12c855180aad702bbcca06e0398d774beeafb155)
1e5dd7070Spatrick==================
2e5dd7070SpatrickNullability Checks
3e5dd7070Spatrick==================
4e5dd7070Spatrick
5e5dd7070SpatrickThis document is a high level description of the nullablility checks.
6e5dd7070SpatrickThese checks intended to use the annotations that is described in this
7*12c85518SrobertRFC: https://discourse.llvm.org/t/rfc-nullability-qualifiers/35672
8*12c85518Srobert(`Mailman <https://lists.llvm.org/pipermail/cfe-dev/2015-March/041779.html>`_)
9e5dd7070Spatrick
10e5dd7070SpatrickLet's consider the following 2 categories:
11e5dd7070Spatrick
12e5dd7070Spatrick**1) nullable**
13e5dd7070Spatrick
14e5dd7070SpatrickIf a pointer ``p`` has a nullable annotation and no explicit null check or assert, we should warn in the following cases:
15e5dd7070Spatrick
16e5dd7070Spatrick* ``p`` gets implicitly converted into nonnull pointer, for example, we are passing it to a function that takes a nonnull parameter.
17e5dd7070Spatrick* ``p`` gets dereferenced
18e5dd7070Spatrick
19e5dd7070SpatrickTaking a branch on nullable pointers are the same like taking branch on null unspecified pointers.
20e5dd7070Spatrick
21e5dd7070SpatrickExplicit cast from nullable to nonnul:
22e5dd7070Spatrick
23e5dd7070Spatrick.. code-block:: cpp
24e5dd7070Spatrick
25e5dd7070Spatrick  __nullable id foo;
26e5dd7070Spatrick  id bar = foo;
27e5dd7070Spatrick  takesNonNull((_nonnull) bar); // should not warn here (backward compatibility hack)
28e5dd7070Spatrick  anotherTakesNonNull(bar); // would be great to warn here, but not necessary(*)
29e5dd7070Spatrick
30e5dd7070SpatrickBecause 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 (*).
31e5dd7070Spatrick
32e5dd7070Spatrick**2) nonnull**
33e5dd7070Spatrick
34e5dd7070Spatrick* Dereferencing a nonnull, or sending message to it is ok.
35e5dd7070Spatrick* Converting nonnull to nullable is Ok.
36e5dd7070Spatrick* 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).
37e5dd7070Spatrick* But what should we do about null checks?:
38e5dd7070Spatrick
39e5dd7070Spatrick.. code-block:: cpp
40e5dd7070Spatrick
41e5dd7070Spatrick  __nonnull id takesNonnull(__nonnull id x) {
42e5dd7070Spatrick      if (x == nil) {
43e5dd7070Spatrick          // Defensive backward compatible code:
44e5dd7070Spatrick          ....
45e5dd7070Spatrick          return nil; // Should the analyzer cover this piece of code? Should we require the cast (__nonnull)nil?
46e5dd7070Spatrick      }
47e5dd7070Spatrick      ....
48e5dd7070Spatrick  }
49e5dd7070Spatrick
50e5dd7070SpatrickThere are these directions:
51e5dd7070Spatrick
52e5dd7070Spatrick* We can either take the branch; this way the branch is analyzed
53e5dd7070Spatrick* 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.
54e5dd7070Spatrick* 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.)
55e5dd7070Spatrick
56e5dd7070SpatrickOther Issues to keep in mind/take care of:
57e5dd7070Spatrick
58e5dd7070Spatrick* Messaging:
59e5dd7070Spatrick
60e5dd7070Spatrick  * Sending a message to a nullable pointer
61e5dd7070Spatrick
62e5dd7070Spatrick    * Even though the method might return a nonnull pointer, when it was sent to a nullable pointer the return type will be nullable.
63e5dd7070Spatrick  	* The result is nullable unless the receiver is known to be non null.
64e5dd7070Spatrick
65e5dd7070Spatrick  * Sending a message to a unspecified or nonnull pointer
66e5dd7070Spatrick
67e5dd7070Spatrick    * If the pointer is not assumed to be nil, we should be optimistic and use the nullability implied by the method.
68e5dd7070Spatrick
69e5dd7070Spatrick      * This will not happen automatically, since the AST will have null unspecified in this case.
70e5dd7070Spatrick
71e5dd7070SpatrickInlining
72e5dd7070Spatrick--------
73e5dd7070Spatrick
74e5dd7070SpatrickA symbol may need to be treated differently inside an inlined body. For example, consider these conversions from nonnull to nullable in presence of inlining:
75e5dd7070Spatrick
76e5dd7070Spatrick.. code-block:: cpp
77e5dd7070Spatrick
78e5dd7070Spatrick  id obj = getNonnull();
79e5dd7070Spatrick  takesNullable(obj);
80e5dd7070Spatrick  takesNonnull(obj);
81e5dd7070Spatrick
82e5dd7070Spatrick  void takesNullable(nullable id obj) {
83e5dd7070Spatrick     obj->ivar // we should assume obj is nullable and warn here
84e5dd7070Spatrick  }
85e5dd7070Spatrick
86e5dd7070SpatrickWith 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.
87e5dd7070Spatrick
88e5dd7070SpatrickAnnotations on multi level pointers
89e5dd7070Spatrick-----------------------------------
90e5dd7070Spatrick
91e5dd7070SpatrickTracking 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.:
92e5dd7070Spatrick
93e5dd7070Spatrick.. code-block:: cpp
94e5dd7070Spatrick
95e5dd7070Spatrick  int * __nonnull * __nullable p;
96e5dd7070Spatrick  int ** q = p;
97e5dd7070Spatrick  takesStarNullableStarNullable(q);
98e5dd7070Spatrick
99e5dd7070SpatrickImplementation notes
100e5dd7070Spatrick--------------------
101e5dd7070Spatrick
102e5dd7070SpatrickWhat to track?
103e5dd7070Spatrick
104e5dd7070Spatrick* 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).
105e5dd7070Spatrick* 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.
106e5dd7070Spatrick* When there is an explicit cast from a null unspecified to either nonnull or nullable I will trust the cast.
107e5dd7070Spatrick* Unannotated pointers are treated the same way as pointers annotated with nullability unspecified qualifier, unless the region is wrapped in ASSUME_NONNULL macros.
108e5dd7070Spatrick* We might want to implement a callback for entry points to top level functions, where the pointer nullability assumptions would be made.
109