This diff adds FixItHints to clang's NullabilityChecker where a return type is erroneously marked as nonnull.
- A CmdLineOption called ShowFixIts to the all of nullability checks. (We add to all of them because of the way NullabilityChecker.cpp registers all of the checkers.)
- For each of the two *ReturnedFromNonnull methods, attaches a FixItHint to the output.
Why
So we can use NS_ASSUME_NONNULL and then apply the fixits to automatically and quickly annotate large amounts of source code.
What It Does
The checker will emit a FixIt the following cases for unparameterized pointer return types, like NSObject *:
Declaration | Annotation | Within NS_ASSUME_NONNULL | FixItHint |
---|---|---|---|
ObjCMethodDecl | _Nonnull after * | No | Replace with _Nullable. |
ObjCMethodDecl | nonnull before type name | No | Replace with nullable. |
ObjCMethodDecl | implicitly unspecified | No | No hint emitted. |
ObjCMethodDecl | implicitly unspecified | Yes | Insert nullable |
FunctionDecl | _Nonnull after * | No | Replace with _Nullable. |
FunctionDecl | _Nonnull after * | Yes | Replace with _Nullable. |
FunctionDecl | implicitly unspecified | Yes | Insert _Nullable. |
How It Works
- If we're not inside an audited region, and there's no existing annotation, we'll insert an annotation, and choose the correct style, depending if we're annotating a function or an Obj-C method decl.
- We check for existing annotations without our accessing source directly by using TypeLoc's findNullabilityLoc.
- An invalid loc means no prior annotations.
- A valid loc tells us both that there's an existing annotation, and that we need to replace it.
Handling NS_ASSUME... macros
- There's one edge case where a valid nullability location is a false positive - inside of an NS_ASSUME_NONNULL pair of pragmas.
- The Lexer can tell us if we're inside of such an audited region, but we probably shouldn't use it.
- Luckily, there's a subtle nuance in findNullabilityLoc: If we are inside of an "assume region", and the annotation isn't really written out in source, then the location returned is going to be right *before* the asterisk/pointer symbol. Otherwise, if the code actually has _Nonnull written out, we're going to see the location of the nullability annotation correctly after the asterisk.
False Positives
Here's why I think this is safe:
- This is an addition to the existing NullabilityChecker, which is already quite conservative in what it diagnoses, so I'm fairly confident that this won't break anything.
- The fixits are only emitted if a ShowFixits flag is enabled on the two nullability return-type checkers, so the default behavior does not change.
Edge Cases
- instancetype and id inside of "audited regions" are problematic, since they don't have the same difference in findNullability. a. We'll skip these for now. Once we have a better way to properly find macro expansions we might be able to enable this.
- There are potentially edge cases here with parameterized types, such as NSArray and NSDictionary. a. I added test cases using a protocol to verify that angle brackets don't interfere with the logic, because the fake headers we use for testing don't properly support the collection types mentioned above.
Running Tests
LIT_FILTER=nullability-fixits ninja check-clang
The tests have four checks.
- Verify that the fixits are not emitted by default.
- Verify that the fixits are not emitted for their respective checkers if -analyzer-config explicitly disables either nullability.NullReturnedFromNonnull:ShowFixIts and/or nullability.NullableReturnedFromNonnull:ShowFixIts.
- Verify that the fixits are emitted for their respective checkers if -analyzer-config explicitly enables either nullability.NullReturnedFromNonnull:ShowFixIts and/or nullability.NullableReturnedFromNonnull:ShowFixIts.
- Validates that the fixes are applied correctly and appear as we expect.
You could pass the owning Decl to this function and directly figure out if it's an objc method or not.