Search code examples
objective-ccocoa-touchclangclang-static-analyzer

How to compile-time check objects being passed to an NSArray constructor for nil


NSArray rather dislikes being passed a nil object as part of its constructor:

UIView *aView;
UIView *aSecondView = [[UIView alloc] init];
NSArray *array = @[aView, aSecondView];

will throw an exception at runtime when array is created.

Does clang have any facilities to try and detect this kind of error? For some trivial cases (like the one above: a stack-local variable that's never assigned to), it seems like the sort of problem the static analyzer would hit out of the park.


Solution

  • TL;DR: -Wuninitialized catches that specific example, __attribute__((nonnull)) for parameters to functions/methods would help catch nil as argument in general.

    For this specific example (variable not specifically initialized), you can use -Wuninitialized to catch those uses:

    ○ xcrun clang a.m -F/System/Library/Frameworks -c -o a.o -Wuninitialized
    a.m:6:22: warning: variable 'aView' is uninitialized when used here [-Wuninitialized]
      NSArray *array = @[aView, aSecondView];
                         ^~~~~
    a.m:4:16: note: initialize the variable 'aView' to silence this warning
      NSView *aView;// = nil;
                   ^
                    = nil
    1 warning generated.
    

    For passing nullptr/NULL/nil to functions/methods, __attribute__((nonnull)) should work for most cases. Since this is an API provided by Apple, you would need to file a radar and hope they add it.

    P.S: Actually, __attribute__((nonnull)) wouldn't “simply work”, in this case (if you initialized aView to nil, for example. The @[...] sequence seems to be creating an array, and calling +[NSArray arrayWithObjects: count:], where you would be able to mark the first argument as nonnull, but not the things pointed to by it. In any case, it should be relatively simple to write a clang pass for the analysis you mention. You should file a radar, since such a pass could avoid lots of time losses.

    P.P.S: It seems __attribute__((nonnull)) doesn't propagate much information, which is sad. :-( If you have a function f(type arg __attribute__((nonnull))) and you pass it a variable which you initialized to nil and never touched again, it doesn't warn.