1*67e74705SXin Li============ 2*67e74705SXin LiNullability Checks 3*67e74705SXin Li============ 4*67e74705SXin Li 5*67e74705SXin LiThis document is a high level description of the nullablility checks. 6*67e74705SXin LiThese checks intended to use the annotations that is described in this 7*67e74705SXin LiRFC: http://lists.cs.uiuc.edu/pipermail/cfe-dev/2015-March/041798.html. 8*67e74705SXin Li 9*67e74705SXin LiLet's consider the following 2 categories: 10*67e74705SXin Li 11*67e74705SXin Li1) nullable 12*67e74705SXin Li============ 13*67e74705SXin Li 14*67e74705SXin LiIf a pointer 'p' has a nullable annotation and no explicit null check or assert, we should warn in the following cases: 15*67e74705SXin Li- 'p' gets implicitly converted into nonnull pointer, for example, we are passing it to a function that takes a nonnull parameter. 16*67e74705SXin Li- 'p' gets dereferenced 17*67e74705SXin Li 18*67e74705SXin LiTaking a branch on nullable pointers are the same like taking branch on null unspecified pointers. 19*67e74705SXin Li 20*67e74705SXin LiExplicit cast from nullable to nonnul:: 21*67e74705SXin Li 22*67e74705SXin Li __nullable id foo; 23*67e74705SXin Li id bar = foo; 24*67e74705SXin Li takesNonNull((_nonnull) bar); <— should not warn here (backward compatibility hack) 25*67e74705SXin Li anotherTakesNonNull(bar); <— would be great to warn here, but not necessary(*) 26*67e74705SXin Li 27*67e74705SXin LiBecause 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 (*). 28*67e74705SXin Li 29*67e74705SXin Li2) nonnull 30*67e74705SXin Li============ 31*67e74705SXin Li 32*67e74705SXin Li- Dereferencing a nonnull, or sending message to it is ok. 33*67e74705SXin Li- Converting nonnull to nullable is Ok. 34*67e74705SXin Li- 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). 35*67e74705SXin Li- But what should we do about null checks?:: 36*67e74705SXin Li 37*67e74705SXin Li __nonnull id takesNonnull(__nonnull id x) { 38*67e74705SXin Li if (x == nil) { 39*67e74705SXin Li // Defensive backward compatible code: 40*67e74705SXin Li .... 41*67e74705SXin Li return nil; <- Should the analyzer cover this piece of code? Should we require the cast (__nonnull)nil? 42*67e74705SXin Li } 43*67e74705SXin Li .... 44*67e74705SXin Li } 45*67e74705SXin Li 46*67e74705SXin LiThere are these directions: 47*67e74705SXin Li- We can either take the branch; this way the branch is analyzed 48*67e74705SXin Li - 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. 49*67e74705SXin Li- 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.) 50*67e74705SXin Li 51*67e74705SXin LiOther Issues to keep in mind/take care of: 52*67e74705SXin LiMessaging: 53*67e74705SXin Li- Sending a message to a nullable pointer 54*67e74705SXin Li - Even though the method might return a nonnull pointer, when it was sent to a nullable pointer the return type will be nullable. 55*67e74705SXin Li - The result is nullable unless the receiver is known to be non null. 56*67e74705SXin Li- Sending a message to a unspecified or nonnull pointer 57*67e74705SXin Li - If the pointer is not assumed to be nil, we should be optimistic and use the nullability implied by the method. 58*67e74705SXin Li - This will not happen automatically, since the AST will have null unspecified in this case. 59*67e74705SXin Li 60*67e74705SXin LiInlining 61*67e74705SXin Li============ 62*67e74705SXin Li 63*67e74705SXin LiA symbol may need to be treated differently inside an inlined body. For example, consider these conversions from nonnull to nullable in presence of inlining:: 64*67e74705SXin Li 65*67e74705SXin Li id obj = getNonnull(); 66*67e74705SXin Li takesNullable(obj); 67*67e74705SXin Li takesNonnull(obj); 68*67e74705SXin Li 69*67e74705SXin Li void takesNullable(nullable id obj) { 70*67e74705SXin Li obj->ivar // we should assume obj is nullable and warn here 71*67e74705SXin Li } 72*67e74705SXin Li 73*67e74705SXin LiWith 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. 74*67e74705SXin Li 75*67e74705SXin LiAnnotations on multi level pointers 76*67e74705SXin Li============ 77*67e74705SXin Li 78*67e74705SXin LiTracking 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.:: 79*67e74705SXin Li 80*67e74705SXin Li int * __nonnull * __nullable p; 81*67e74705SXin Li int ** q = p; 82*67e74705SXin Li takesStarNullableStarNullable(q); 83*67e74705SXin Li 84*67e74705SXin LiImplementation notes 85*67e74705SXin Li============ 86*67e74705SXin Li 87*67e74705SXin LiWhat to track? 88*67e74705SXin Li- 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). 89*67e74705SXin Li- 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. 90*67e74705SXin Li- When there is an explicit cast from a null unspecified to either nonnull or nullable I will trust the cast. 91*67e74705SXin Li- Unannotated pointers are treated the same way as pointers annotated with nullability unspecified qualifier, unless the region is wrapped in ASSUME_NONNULL macros. 92*67e74705SXin Li- We might want to implement a callback for entry points to top level functions, where the pointer nullability assumptions would be made. 93