Check nullability consistency in your Objective-C implementation.
Recent Objective-C allows programmers to annotate variables and methods with nullability, as nonnull
(_Nonnull
) and nullable
(_Nullable
).
However the compiler does not check if they are implemented correctly or not.
This tool checks nullability consistency on:
- Assignment on variables
- Method calls
- Returns
It helps you to find out nullability mismatch and prevent from having unexpected nil
in your code.
The following is an example of programs and warnings.
NS_ASSUME_NONNULL_BEGIN
@implementation SomeClass
- (void)example {
NSNumber * _Nullable a = @123;
NSString * _Nonnull b = [self doSomethingWithString:a]; // Warning: parameter of doSomethingWithString: is _Nonnull
}
- (NSString *)doSomethingWithString:(NSNumber *)x {
NSString * _Nullable y = x.stringValue;
return y; // Warning: returning _Nonnull value is expected
}
NS_ASSUME_NONNULL_END
Install via brew tap.
$ brew tap soutaro/nullarihyon
$ brew install nullarihyon
nullarihyon check
command allows checking single .m
file.
$ nullarihyon check sample.m
You may have to specify --sdk
option for Foundation
, UIKit
, or AppKit
classes.
- For iOS apps try with
--sdk iphonesimulator
- For Mac apps try with
--sdk macosx
You can find available options for --sdk
by
$ nullarihyon help check
nullarihyon xcode
command is for Xcode integration.
Add Run Script Phase
with:
if which nullarihyon >/dev/null; then
nullarihyon xcode
fi
Run build from Xcode and you will see tons of warnings ;-)
Thousand of warnings would not make you feel happy; try --only-latest
option if you like.
nullarihyon xcode --only-latest
With --only-latest
option, Xcode warnings will be flushed every time you run build.
Warnings for files which is compiled during last build will be shown.
Nullarihyon checks timestamp of compiled objects to see if the source code is updated or not.
Add the new phase after Compile Sources
phase.
If you consider using Nullarihyon to existing project, the workflow would be like the following.
- Find working set of classes
- Setup filtering for the classes
- Add
NS_ASSUME_NONNULL_BEGIN
/NS_ASSUME_NONNULL_END
to its.h
and.m
- Run the tool
- Fix warnings
Nullarihyon would report thouthands of warnings. It should be better to have a working set with small number of classes. Focus tens of warnings and fix them. And, go next classes.
Filtering is explained at wiki page. Take a look at it.
To fix nullability warnings, there are things you can do.
This is the last resort.
When you are sure the value cannot be nil
, add explicit cast.
NSString * _Nullable x = ...;
// Warning here
NSString * _Nonnull y = x;
// Add explicit cast
NSString * _Nonnull z = (NSString * _Nonnull)x;
The tool makes you write many casts. To prevent from writing wrong casts, Nullarihyon reports warning if the cast changes both nullability and type.
NSObject * _Nullable x;
NSObject * _Nonnull y = (NSObject * _Nonnull)x; // This is ok
NSString * _Nonnull z = (NSString * _Nonnull)x; // This reports warning
Casting from id
is still allowed.
id _Nullable x;
NSString * _Nonnull y = (NSString * _Nonnull)x; // This is ok because x is id
?:
operator is supported.
NSString * _Nullable x;
NSString * _Nonnull y = x ?: @""; // This is okay
Nullability of local variables declared with initial value will be propagated from the initial value.
NSString * _Nonnull x;
NSString * y = x; // This is _Nonnull because initial value is _Nonnull
When you declare variable without initial value, its nullability is _Nullable
.
This rule discourages the following programming style.
NSString *x = nil;
if (a == 1) {
x = @"one";
} else {
x = @"other";
}
NSString * _Nonnull y = x; // x cannot be nil here
Clearly the value of x
after if
statement cannot be nil
.
However, Nullarihyon reports warning on the assignment to y
since x
is _Nullable
because of its declaration.
For this, annotate variable declaration explicitly, and stop assigning nil
.
NSString * _Nonnull x;
if (a == 1) {
x = @"one";
} else {
x = @"other";
}
NSString * _Nonnull y = x;
Nothing will go wrong.
Variable without initial value will be nil
anyway, if you enables ARC.
Nullarihyon supports super simple form of flow-sensitive-type.
When condition clause of if
is a variable reference, the variable will be treated as _Nonnull
in then clause.
NSString * _Nullable x;
if (x) {
NSString * _Nonnull y = x; // This is okay because x is tested by if
}
The analyzer consider binary &&
with variable reference.
NSString * _Nullable x;
T a = x && [self nonnullMethod:x]; // x is nonnull on method call
NSString * _Nullable y;
if (x && y) {
// x and y are nonnull in true clause
}
The nullability modification works only with variable reference.
Take a look at wiki page to see the rules of Nullarihyon. It has some assumptions to minimize number of trivial false positives.
As of 1.6, Nullarihyon can check if initializers assign all nonnull instance variables. This is experimental and totally optional.
Add __attribute__((annotate("nlh_initializer")))
attribute to method you want Nullarihyon to check.
- (instancetype)init __attribute__((annotate("nlh_initializer"))) {
...
}
In nlh_initializer
methods,
- You have to assign all nonnull instance variables/properties, or
- You have to call other
nlh_initializer
methods
This check would prevent you from leaving nonnull instance variables and properties nil
.
Checking if super
is initialized correctly is out of scope of Nullarihyon.
You are responsible to call call initialization method of super classes correctly.
- It does not support per-file build setting in Xcode
- It does not support arm architectures (just skip checking)
[self alloc]
does not returninstancetype
butid
(analysis by Clang compiler)