annotate clang/docs/analyzer/developer-docs/nullability.rst @ 173:0572611fdcc8 llvm10 llvm12

reorgnization done
author Shinji KONO <kono@ie.u-ryukyu.ac.jp>
date Mon, 25 May 2020 11:55:54 +0900
parents 1d019706d866
children c4bab56944e8
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
150
anatofuz
parents:
diff changeset
1 ==================
anatofuz
parents:
diff changeset
2 Nullability Checks
anatofuz
parents:
diff changeset
3 ==================
anatofuz
parents:
diff changeset
4
anatofuz
parents:
diff changeset
5 This document is a high level description of the nullablility checks.
anatofuz
parents:
diff changeset
6 These checks intended to use the annotations that is described in this
anatofuz
parents:
diff changeset
7 RFC: http://lists.cs.uiuc.edu/pipermail/cfe-dev/2015-March/041798.html.
anatofuz
parents:
diff changeset
8
anatofuz
parents:
diff changeset
9 Let's consider the following 2 categories:
anatofuz
parents:
diff changeset
10
anatofuz
parents:
diff changeset
11 **1) nullable**
anatofuz
parents:
diff changeset
12
anatofuz
parents:
diff changeset
13 If a pointer ``p`` has a nullable annotation and no explicit null check or assert, we should warn in the following cases:
anatofuz
parents:
diff changeset
14
anatofuz
parents:
diff changeset
15 * ``p`` gets implicitly converted into nonnull pointer, for example, we are passing it to a function that takes a nonnull parameter.
anatofuz
parents:
diff changeset
16 * ``p`` gets dereferenced
anatofuz
parents:
diff changeset
17
anatofuz
parents:
diff changeset
18 Taking a branch on nullable pointers are the same like taking branch on null unspecified pointers.
anatofuz
parents:
diff changeset
19
anatofuz
parents:
diff changeset
20 Explicit cast from nullable to nonnul:
anatofuz
parents:
diff changeset
21
anatofuz
parents:
diff changeset
22 .. code-block:: cpp
anatofuz
parents:
diff changeset
23
anatofuz
parents:
diff changeset
24 __nullable id foo;
anatofuz
parents:
diff changeset
25 id bar = foo;
anatofuz
parents:
diff changeset
26 takesNonNull((_nonnull) bar); // should not warn here (backward compatibility hack)
anatofuz
parents:
diff changeset
27 anotherTakesNonNull(bar); // would be great to warn here, but not necessary(*)
anatofuz
parents:
diff changeset
28
anatofuz
parents:
diff changeset
29 Because 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 (*).
anatofuz
parents:
diff changeset
30
anatofuz
parents:
diff changeset
31 **2) nonnull**
anatofuz
parents:
diff changeset
32
anatofuz
parents:
diff changeset
33 * Dereferencing a nonnull, or sending message to it is ok.
anatofuz
parents:
diff changeset
34 * Converting nonnull to nullable is Ok.
anatofuz
parents:
diff changeset
35 * 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).
anatofuz
parents:
diff changeset
36 * But what should we do about null checks?:
anatofuz
parents:
diff changeset
37
anatofuz
parents:
diff changeset
38 .. code-block:: cpp
anatofuz
parents:
diff changeset
39
anatofuz
parents:
diff changeset
40 __nonnull id takesNonnull(__nonnull id x) {
anatofuz
parents:
diff changeset
41 if (x == nil) {
anatofuz
parents:
diff changeset
42 // Defensive backward compatible code:
anatofuz
parents:
diff changeset
43 ....
anatofuz
parents:
diff changeset
44 return nil; // Should the analyzer cover this piece of code? Should we require the cast (__nonnull)nil?
anatofuz
parents:
diff changeset
45 }
anatofuz
parents:
diff changeset
46 ....
anatofuz
parents:
diff changeset
47 }
anatofuz
parents:
diff changeset
48
anatofuz
parents:
diff changeset
49 There are these directions:
anatofuz
parents:
diff changeset
50
anatofuz
parents:
diff changeset
51 * We can either take the branch; this way the branch is analyzed
anatofuz
parents:
diff changeset
52 * 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.
anatofuz
parents:
diff changeset
53 * 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.)
anatofuz
parents:
diff changeset
54
anatofuz
parents:
diff changeset
55 Other Issues to keep in mind/take care of:
anatofuz
parents:
diff changeset
56
anatofuz
parents:
diff changeset
57 * Messaging:
anatofuz
parents:
diff changeset
58
anatofuz
parents:
diff changeset
59 * Sending a message to a nullable pointer
anatofuz
parents:
diff changeset
60
anatofuz
parents:
diff changeset
61 * Even though the method might return a nonnull pointer, when it was sent to a nullable pointer the return type will be nullable.
anatofuz
parents:
diff changeset
62 * The result is nullable unless the receiver is known to be non null.
anatofuz
parents:
diff changeset
63
anatofuz
parents:
diff changeset
64 * Sending a message to a unspecified or nonnull pointer
anatofuz
parents:
diff changeset
65
anatofuz
parents:
diff changeset
66 * If the pointer is not assumed to be nil, we should be optimistic and use the nullability implied by the method.
anatofuz
parents:
diff changeset
67
anatofuz
parents:
diff changeset
68 * This will not happen automatically, since the AST will have null unspecified in this case.
anatofuz
parents:
diff changeset
69
anatofuz
parents:
diff changeset
70 Inlining
anatofuz
parents:
diff changeset
71 --------
anatofuz
parents:
diff changeset
72
anatofuz
parents:
diff changeset
73 A symbol may need to be treated differently inside an inlined body. For example, consider these conversions from nonnull to nullable in presence of inlining:
anatofuz
parents:
diff changeset
74
anatofuz
parents:
diff changeset
75 .. code-block:: cpp
anatofuz
parents:
diff changeset
76
anatofuz
parents:
diff changeset
77 id obj = getNonnull();
anatofuz
parents:
diff changeset
78 takesNullable(obj);
anatofuz
parents:
diff changeset
79 takesNonnull(obj);
anatofuz
parents:
diff changeset
80
anatofuz
parents:
diff changeset
81 void takesNullable(nullable id obj) {
anatofuz
parents:
diff changeset
82 obj->ivar // we should assume obj is nullable and warn here
anatofuz
parents:
diff changeset
83 }
anatofuz
parents:
diff changeset
84
anatofuz
parents:
diff changeset
85 With 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.
anatofuz
parents:
diff changeset
86
anatofuz
parents:
diff changeset
87 Annotations on multi level pointers
anatofuz
parents:
diff changeset
88 -----------------------------------
anatofuz
parents:
diff changeset
89
anatofuz
parents:
diff changeset
90 Tracking 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.:
anatofuz
parents:
diff changeset
91
anatofuz
parents:
diff changeset
92 .. code-block:: cpp
anatofuz
parents:
diff changeset
93
anatofuz
parents:
diff changeset
94 int * __nonnull * __nullable p;
anatofuz
parents:
diff changeset
95 int ** q = p;
anatofuz
parents:
diff changeset
96 takesStarNullableStarNullable(q);
anatofuz
parents:
diff changeset
97
anatofuz
parents:
diff changeset
98 Implementation notes
anatofuz
parents:
diff changeset
99 --------------------
anatofuz
parents:
diff changeset
100
anatofuz
parents:
diff changeset
101 What to track?
anatofuz
parents:
diff changeset
102
anatofuz
parents:
diff changeset
103 * 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).
anatofuz
parents:
diff changeset
104 * 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.
anatofuz
parents:
diff changeset
105 * When there is an explicit cast from a null unspecified to either nonnull or nullable I will trust the cast.
anatofuz
parents:
diff changeset
106 * Unannotated pointers are treated the same way as pointers annotated with nullability unspecified qualifier, unless the region is wrapped in ASSUME_NONNULL macros.
anatofuz
parents:
diff changeset
107 * We might want to implement a callback for entry points to top level functions, where the pointer nullability assumptions would be made.