This is an archive of the discontinued LLVM Phabricator instance.

[analyzer] Add FixItHint to `nullability.NullReturnedFromNonnull` and `nullability.NullableReturnedFromNonnull`
AbandonedPublic

Authored by MosheBerman on Apr 7 2022, 6:43 PM.

Details

Summary

This diff adds FixItHints to clang's NullabilityChecker where a return type is erroneously marked as nonnull.

  1. 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.)
  2. 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 *:

DeclarationAnnotationWithin NS_ASSUME_NONNULLFixItHint
ObjCMethodDecl_Nonnull after *NoReplace with _Nullable.
ObjCMethodDeclnonnull before type nameNoReplace with nullable.
ObjCMethodDeclimplicitly unspecifiedNoNo hint emitted.
ObjCMethodDeclimplicitly unspecifiedYesInsert nullable
FunctionDecl_Nonnull after *NoReplace with _Nullable.
FunctionDecl_Nonnull after *YesReplace with _Nullable.
FunctionDeclimplicitly unspecifiedYesInsert _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:

  1. 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.
  2. 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

  1. 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.
  2. 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.

  1. Verify that the fixits are not emitted by default.
  2. 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.
  3. Verify that the fixits are emitted for their respective checkers if -analyzer-config explicitly enables either nullability.NullReturnedFromNonnull:ShowFixIts and/or nullability.NullableReturnedFromNonnull:ShowFixIts.
  4. Validates that the fixes are applied correctly and appear as we expect.

Diff Detail

Event Timeline

MosheBerman created this revision.Apr 7 2022, 6:43 PM
Herald added a project: Restricted Project. · View Herald TranscriptApr 7 2022, 6:43 PM
MosheBerman requested review of this revision.Apr 7 2022, 6:43 PM
Herald added a project: Restricted Project. · View Herald TranscriptApr 7 2022, 6:43 PM
Herald added a subscriber: cfe-commits. · View Herald Transcript

You have a single bool property, yet you allow to enable/disable this by each sub-checker. It feels like the last checker in the registration process will rule them all.

That being said, in the fixit creation scope, you check for both this flag and the presence of the fixit location - which you only set if the flag is active.

IMO you should have this flag per-subchecker, and check for the presence of that and either pass the fixit location if you actually need to insert the fixit or pass it unconditionally and emit the fixit only if the flag of the given sub-checker is set.

Also, make sure the tests pass.

MosheBerman added a comment.EditedApr 8 2022, 8:31 AM

You have a single bool property, yet you allow to enable/disable this by each sub-checker. It feels like the last checker in the registration process will rule them all.

That being said, in the fixit creation scope, you check for both this flag and the presence of the fixit location - which you only set if the flag is active.

IMO you should have this flag per-subchecker, and check for the presence of that and either pass the fixit location if you actually need to insert the fixit or pass it unconditionally and emit the fixit only if the flag of the given sub-checker is set.

That’s a helpful observation. Besides for the code hygiene, I am completely new to LLVM and C++, so I wasn’t aware that the flags will override. I’ll think more about this and address this feedback in an update to this patch.

Also, make sure the tests pass.

Yep - the reason I posted this diff in the current state was because I’m unsure why the fixit isn’t appearing in the test output. I posted about it on Discourse.

Is there something I need to do besides for attaching the hint to the bug report?

I also found a really old post where @ted_kremenek says that FixItHints were - at the time - not implemented on BugReporter. I don’t know if that’s changed since then, or if the approach has been to use clang-tidy exclusive to supporting fixits in checkers.

Do you have any insight?

Thanks so much for taking the time to look at this and provide thoughtful feedback!

tldr; static-analyzer fixits are not completely implemented. We don't even have tests for it, which is a shame.
When I passed the apply-fixits, it modified the input source file - as I expected.
Then I tried the -analyzer-output=text and suddenly it inserted the fixit 2 times xD, which is less than ideal and we should fix this.
And I'm expecting many more bugs with this feature.

There is a clang/test/Analysis/check-analyzer-fixit.py script which could be invoked by a RUN line like this:

// RUN: %check-analyzer-fixit %s %t -analyzer-checker=core

At least, the comment of that script says so.


I'm quite skeptical about inserting such fixits in general - regarding path-sensitive analysis.
For fixits, we should be confident that some property holds, but the static analyzer might conclude erroneously that a given pointer is null resulting in a bad fixit.
Thus, relying on this might be dangerous.
You can read more about false-positive cases regarding null pointers in the clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.def, namely suppress-null-return-paths, avoid-suppressing-null-argument-paths, suppress-inlined-defensive-checks. These options were introduced for mitigating such false-positive null pointer deref reports.

Let me invite @Szelethus for his expertise in the null pointer checker.

tldr; static-analyzer fixits are not completely implemented.

Where can I learn more about this? Would it be possible and idiomatically/architecturally sounds to write a clang-tidy that processes output from this checker?

When I passed the apply-fixits, it modified the input source file - as I expected.

Did you test this diff, or an existing checker? Would you please share the command you used to test?

Then I tried the -analyzer-output=text and suddenly it inserted the fixit 2 times xD, which is less than ideal and we should fix this.
And I'm expecting many more bugs with this feature.

This is why it's gated. xD.

There is a clang/test/Analysis/check-analyzer-fixit.py script which could be invoked by a RUN line like this:

// RUN: %check-analyzer-fixit %s %t -analyzer-checker=core

At least, the comment of that script says so.


I'm quite skeptical about inserting such fixits in general - regarding path-sensitive analysis.
For fixits, we should be confident that some property holds, but the static analyzer might conclude erroneously that a given pointer is null resulting in a bad fixit.
Thus, relying on this might be dangerous.

Understood, and I don't disagree (both because of lack of expertise, and because false-positives is a logical concern.) The intention here is to enable us to add NS_ASSUME macros to a bunch of files, then use the nullability return checkers to catch the functions/methods which violate the new contract. This assumes that the existing code is the source of truth, as opposed to, say, callsites.

You can read more about false-positive cases regarding null pointers in the clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.def, namely suppress-null-return-paths, avoid-suppressing-null-argument-paths, suppress-inlined-defensive-checks. These options were introduced for mitigating such false-positive null pointer deref reports.

I'll take a look at that, thanks! Would those suppressions interact with the nullability checkers? They seem to have a lot of defenses against false positives.

Let me invite @Szelethus for his expertise in the null pointer checker.

Thanks!

  • Changed the ShowFixIts flag to be per-checker.
  • Added support for syntax sugar (nullable vs _Nullable)
    • Pass FixItHint through, so we can do that.
  • Changed tests to use -fdiagnostics-parseable-fixits.

This doesn't yet account for removing existing _Nonnull local annotations, but I want to make sure we can get tests working first and/or determine if a tidy is a better approach.

Hey, bumping for feedback, please. :D

The tests now pass and actually verify the behavior.

tldr; static-analyzer fixits are not completely implemented.

Where can I learn more about this?

Grep through the code and look for references to the variable. It's not used widely.
In fact, there are no checkers facilitating fixits.

Would it be possible and idiomatically/architecturally sounds to write a clang-tidy that processes output from this checker?

I don't believe that clang-tidy should be involved in this. It would be better to keep it in-house (in the analyzer).

When I passed the apply-fixits, it modified the input source file - as I expected.

Did you test this diff, or an existing checker? Would you please share the command you used to test?

Sort of. I tried to apply some parts of it by hand, and check the output difference in my command.

I cannot immediately recall, but I cannot remember any major obstacles, which suggests that I had to set the apply-fixits=true analyzer config option and that's it.
Since there were no checkers emitting fixit hints, I modified an existing one to emit a dummy fixithint. Nothing fancy there.

Then I tried the -analyzer-output=text and suddenly it inserted the fixit 2 times xD, which is less than ideal and we should fix this.
And I'm expecting many more bugs with this feature.

This is why it's gated. xD.

What do you mean by 'gated'? It seems to be a bug, which we should fix prior to this.

PS: I'm also inviting @whisperity since he is more experienced with fixits and that sort of stuff.

clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp
160–162

You could pass the owning Decl to this function and directly figure out if it's an objc method or not.

693

?

MosheBerman added inline comments.Apr 20 2022, 1:34 PM
clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp
160–162

We do this at the callsite. Does it matter where we do the check?

MosheBerman edited the summary of this revision. (Show Details)
  • Improved handling of NS_ASSUME and other edge cases/
  • Added test cases, and changed the test to actually validate the output (not just the presence of fixits.)
  • Cleaned up the diff.
  • Updated the summary to include more detail and address some of the comments.

tldr; static-analyzer fixits are not completely implemented.

Where can I learn more about this?

Grep through the code and look for references to the variable. It's not used widely.
In fact, there are no checkers facilitating fixits.

There's a DeadStore checker that does have some fixit code, which is where I found the FixItHint class. I did notice it isn't used in too many other places.

Would it be possible and idiomatically/architecturally sounds to write a clang-tidy that processes output from this checker?

I don't believe that clang-tidy should be involved in this. It would be better to keep it in-house (in the analyzer).

When I passed the apply-fixits, it modified the input source file - as I expected.

Did you test this diff, or an existing checker? Would you please share the command you used to test?

Sort of. I tried to apply some parts of it by hand, and check the output difference in my command.

I cannot immediately recall, but I cannot remember any major obstacles, which suggests that I had to set the apply-fixits=true analyzer config option and that's it.
Since there were no checkers emitting fixit hints, I modified an existing one to emit a dummy fixithint. Nothing fancy there.

Then I tried the -analyzer-output=text and suddenly it inserted the fixit 2 times xD, which is less than ideal and we should fix this.
And I'm expecting many more bugs with this feature.

This is why it's gated. xD.

What do you mean by 'gated'? It seems to be a bug, which we should fix prior to this.

The -analyzer-output=text comes from a frontend. (I misplaced the source right now, but I did see it recently on the LLVM github.) The reasons I'm not too concerned are:

  1. It looks like a pre-existing behavior. (The text frontend consumer appeared to be printing fixits independently of whatever other diagnostic flags)
  2. When I say gated, I mean that the default behavior of the checker hasn't changed. These fixits are opt-in. If you're opting in, you can also choose to not use the text output.

PS: I'm also inviting @whisperity since he is more experienced with fixits and that sort of stuff.

Thanks! More reviewers means I learn more and can be more accurate.

MosheBerman marked an inline comment as done.Apr 20 2022, 2:07 PM
steakhal added a subscriber: NoQ.Apr 25 2022, 1:17 AM
steakhal added inline comments.
clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp
578

You should avoid std::string comparisons. llvm::StringRefs are a better alternative.

581–591

Uh, this is really ugly. I don't believe this is the preferred way of detecting this. @NoQ WDYT?

679–680
clang/test/Analysis/nullability-fixits.mm
6–11

Why don't you use the %clang_analyze_cc1 ... form instead or even better the %check_analyzer_fixit tool-subst pattern?
See the clang/test/Analysis/dead-stores.c as an example.

MosheBerman planned changes to this revision.Apr 25 2022, 5:59 AM

Thanks for all the thoughtful and fantastic feedback!

clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp
581–591

Uh, this is really ugly.

It is really ugly. And a more correct implementation would probably handle some of the edge cases highlighted in the diff summary. It’s on me - being a n00b at llvm stuff.

By “_this_” do you mean IsInsideOfAssume or UseSyntaxSugar in general?

I don't believe this is the preferred way of detecting this. @NoQ WDYT?

I tried isMacroExpansion for assume nonnull, passing the end loc of the return type without success.

A colleague suggested SourceManager::isMacroBodyExpansion. I’ll look at it again later today.

Thank you so much for your patience and direction with reviews as I work on this. I really appreciate you making time for me!

clang/test/Analysis/nullability-fixits.mm
6–11

That’s a good call out. I looked at that exact test and was having trouble testing. Instead, I implemented the RUN commands from the most basic invocation I could find.

I’m happy to update the tests that verify that fixits are emitted. For the one that checks that fixits are applied, is there a better way? The -verify flag doesn’t do that, from what I saw in the docs.

Regarding FixIts... FixIts are implemented in the "Diagnostic" library, which is non-specific to neither Clang-Tidy nor Sema whatsoever, they use the same infrastructure under the hood. Why the apply logic in CSA might do the same FixIt multiple times is beyond me, but I know that both clang-apply-replacements and clang-tidy go to length to ensure that in case multiple checkers report to the same location with potentially conflicting FixIts, then none gets applied, because applying all of them would result in ridiculously broken source code. They internally become an object in the clang::tooling namespace which is implemented as a core Clang library. The relevant entrypoint to this logic, at least in Clang-Tidy, should be this one: http://github.com/llvm/llvm-project/blob/8f9dd5e608c0ac201ab682ccc89ac3be2dfd0d29/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp#L115-L134

FixIts are very rudimentary under the hood. Essentially, they allow you to, in a file at a given position, delete N characters and insert M characters. Removal fixits only delete, insertion fixits only insert (duh), and just like in version control, a modification is a "removal" and an "insertion" grouped into one action...

If FixIts are so broken in CSA's infrastructure as @steakhal observed then it might very well be a good thing to hold off on them and fix the infrastructure first. Or at least remove the ability to auto-apply them. You don't have to apply the fixes, but you can always get the neat little green-coloured notes as to what could the user should insert.

There's a DeadStore checker that does have some fixit code, which is where I found the FixItHint class. I did notice it isn't used in too many other places.

For fixits, we should be confident that some property holds, but the static analyzer might conclude erroneously that a given pointer is null resulting in a bad fixit.

Dead stores and dead code are, for the most part, rather easy to reason about and not make dangerous fixits by their removal. (I'd go far enough to say the DeadStore checker should be a run-of-the-mill warning in Sema, and not some extra fluff only available in CSA...)

Where can I learn more about this? Would it be possible and idiomatically/architecturally sounds to write a clang-tidy that processes output from this checker?

Very unlikely. While Clang-Tidy can execute CSA checkers (because CSA is a library within Clang, but AFAIK calling CSA from Tidy doesn't handle things like CTU), Clang-Tidy does not contain any notion of dependency between checks and they are meant to be independent. Moreover, you would need to enforce a sequential execution by the user across binaries (not to mention the fact that the entire project needs to be parsed twice, which might not be a trivial cost!), which seems error-prone...


In general, why you are trying to achieve this with the static analyser? It looks to me as if what you want is to mechanically add an annotation to some types if they are in between specific macros / annotations. More specifically, the table that is in your original post doesn't seem to involve path-sensitive information. So could this be a purely (platform-specific?) Tidy check? Your test code also doesn't contain anything that seems to depend on path-sensitive information.

  1. Where is this NS_ annotation coming from? Is this a thing of a specific platform, or specific to your company's products?
  2. If you are sure you will not depend on any path-sensitive symbolic execution information (which you currently appear you do not), and the answer to 1. is meaningful enough to be useful for a wider community (that is, not just you or your company?), then I would suggest rewriting the whole thing within Clang-Tidy. Clang-Tidy works off of AST matchers directly. The nullability annotations are attributes, which are also AST nodes, so you can handle them similarly to matching types or Decls.

Regarding FixIts... FixIts are implemented in the "Diagnostic" library, which is non-specific to neither Clang-Tidy nor Sema whatsoever, they use the same infrastructure under the hood. Why the apply logic in CSA might do the same FixIt multiple times is beyond me, but I know that both clang-apply-replacements and clang-tidy go to length to ensure that in case multiple checkers report to the same location with potentially conflicting FixIts, then none gets applied, because applying all of them would result in ridiculously broken source code. They internally become an object in the clang::tooling namespace which is implemented as a core Clang library. The relevant entrypoint to this logic, at least in Clang-Tidy, should be this one: http://github.com/llvm/llvm-project/blob/8f9dd5e608c0ac201ab682ccc89ac3be2dfd0d29/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp#L115-L134

FixIts are very rudimentary under the hood. Essentially, they allow you to, in a file at a given position, delete N characters and insert M characters. Removal fixits only delete, insertion fixits only insert (duh), and just like in version control, a modification is a "removal" and an "insertion" grouped into one action...

If FixIts are so broken in CSA's infrastructure as @steakhal observed then it might very well be a good thing to hold off on them and fix the infrastructure first. Or at least remove the ability to auto-apply them. You don't have to apply the fixes, but you can always get the neat little green-coloured notes as to what could the user should insert.

The default behavior _does not change_ with this diff. These FixIts are gated by the flags`nullability.NilReturnedToNonnull:ShowFixIts` and nullability.NilReturnedToNonnull:ShowFixIts. Even then -apply-fixits is required to actually apply them.

There's a DeadStore checker that does have some fixit code, which is where I found the FixItHint class. I did notice it isn't used in too many other places.

For fixits, we should be confident that some property holds, but the static analyzer might conclude erroneously that a given pointer is null resulting in a bad fixit.

In this case, the consequence of a bad fixit is pretty harmless from the perspective of a developer compiling their code. These annotations are not detemining if a pointer is nullptr, but if it can be. The code will still compile, and callsites might have an extra check. (Swift or Obj-C/Cxx) would have an extra check, but it would always pass. The NullabilityChecker has lots of conservative logic to prevent false positives. If we trust the existing checker, we are already pretty low-risk to begin with.

Dead stores and dead code are, for the most part, rather easy to reason about and not make dangerous fixits by their removal. (I'd go far enough to say the DeadStore checker should be a run-of-the-mill warning in Sema, and not some extra fluff only available in CSA...)

Where can I learn more about this? Would it be possible and idiomatically/architecturally sounds to write a clang-tidy that processes output from this checker?

Very unlikely. While Clang-Tidy can execute CSA checkers (because CSA is a library within Clang, but AFAIK calling CSA from Tidy doesn't handle things like CTU), Clang-Tidy does not contain any notion of dependency between checks and they are meant to be independent. Moreover, you would need to enforce a sequential execution by the user across binaries (not to mention the fact that the entire project needs to be parsed twice, which might not be a trivial cost!), which seems error-prone...

Ok, thanks!


In general, why you are trying to achieve this with the static analyser? It looks to me as if what you want is to mechanically add an annotation to some types _if they are in between specific macros / annotations._ More specifically, the table that is in your original post doesn't seem to involve path-sensitive information. So could this be a purely (platform-specific?) Tidy check? Your test code also doesn't contain anything that seems to depend on path-sensitive information.

We want is to mechanically add a _nullable_ annotation to some types _when they are incorrectly annotated as nonnull_. A nonnull annotation is "incorrect" when a return value is a nil literal, or the result of a nullable expression. (Macros are only one way to specify a return type as nonnull.) The Nullability Checker emits warnings for exactly this, but doesn't go as far as displaying or applying fixits, and that's what this diff is about.

  1. Where is this NS_ annotation coming from? Is this a thing of a specific platform, or specific to your company's products?

You can disregard the NS_ comment. It refers to Apple's NS_ prefixed Objective-C collection classes which the checker appears to special-case no-op on. I was thinking if we could leverage it for those cases, but this is completely auxiliary to what I'm trying to do here, though. I'll delete the comment about that.

  1. If you are sure you will not depend on any path-sensitive symbolic execution information (which you currently appear you do not), and the answer to 1. is meaningful enough to be useful for a wider community (that is, not just you or your company?), then I would suggest rewriting the whole thing within Clang-Tidy. Clang-Tidy works off of AST matchers directly.

I've looked at clang-tidy before, and found that matching on return types gets complex once you account for all of the various cases like:
a) implicit cast nodes generated by arc vs no different AST trees when arc is disabled
b) returns of calls to other functions or methods.

While working on this, I referenced NullabilityChecker for a better way to reliably detect these cases and realized I was looking right at what I wanted.

is meaningful enough to be useful for a wider community (that is, not just you or your company

This change will make it easier for everyone to annotate codebases, although has greater impact for larger codebases. Annotating a smaller codebase manually might manageable for a single engineer through the following process:

  1. Check the implementations for nil/nullable returns.
  2. Write annotations for nullable return types.
  3. We can optionally add NS_ASSUME_NONNULL macros to cover the rest.

This is tedious, and with the CSA, we can do better. Today, the CSA already does step 1. With our changes here, we get:

  1. CSA checks the implementations for nil/nullable returns.
  2. CSA optionally writes annotations for nullable return types.
  3. We can optionally add NS_ASSUME_NONNULL macros to cover the rest.

The nullability annotations are attributes, which are also AST nodes, so you can handle them similarly to matching types or Decls.

This raises another issue, which is that inferred nullability Attributes do not carry any information (from what I can tell) that tells us if they are explicitly spelled out or are macro expansions.

They are added in SemaTypes and don't currently track that they've been generated. I think that we can use IsImplicit, but I'm not sure if that is in the spirit of the original diff where it was introduced.

I'm eager to hear your thoughts, and happy to answer any more questions.

NoQ added a comment.Apr 26 2022, 4:59 PM

I'm worried that even if the warning is correct, the suggested fix is not necessarily the right solution.

The very nature of path-sensitive warnings requires multiple events to happen on the path in order for the problem to manifest. Eliminating even one of those critical events from the path would eliminate the warning even if all other events are still present. The fix-it hint you're adding targets only one of those events. So it'll eliminate the warning, but so would 2-3 other potential fixes, and there's no way to know which one is actually preferred.

For example, you can eliminate a null-pointer-to-nonnull warning by either changing the "to-nonnull" part (changing the attribute) or by changing the "null-pointer" part (eg., adding a null check before the call). Both fixes make sense depending on the circumstances. Say, if the callee function always crashes on null pointer parameter, annotating the parameter as nullable is a totally wrong thing to do.

So I generally advice against fix-it hints for path-sensitive warnings. I'm curious how you plan to deal with this problem in your specific workflow.

MosheBerman added a comment.EditedApr 26 2022, 6:09 PM

I'm worried that even if the warning is correct, the suggested fix is not necessarily the right solution.

The very nature of path-sensitive warnings requires multiple events to happen on the path in order for the problem to manifest. Eliminating even one of those critical events from the path would eliminate the warning even if all other events are still present. The fix-it hint you're adding targets only one of those events. So it'll eliminate the warning, but so would 2-3 other potential fixes, and there's no way to know which one is actually preferred.

For example, you can eliminate a null-pointer-to-nonnull warning by either changing the "to-nonnull" part (changing the attribute) or by changing the "null-pointer" part (eg., adding a null check before the call). Both fixes make sense depending on the circumstances. Say, if the callee function always crashes on null pointer parameter, annotating the parameter as nullable is a totally wrong thing to do.

So I generally advice against fix-it hints for path-sensitive warnings. I'm curious how you plan to deal with this problem in your specific workflow.

That's a great point. The FixItHints are only emitted for nil/null-returned, not NTNC or null dereference checks, and this is why. They also only cover return type annotations, and not arguments.

Annotating arguments is more complex, because such an annotation changes the contract between callers to the function/method and also changes -as you noted - uses of the argument inside of the function/method.

(I actually meant to include tests for when arguments are directly returned by a code path. I'll add them.)

MosheBerman marked 3 inline comments as done.May 12 2022, 1:28 PM
MosheBerman added inline comments.
clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp
581–591

Hello again! I tried a few things and looked at some of the clang source and I think this is the only way to check for this right now.

The type system treats nullability attributes as Objective-C syntax sugar. It adds attributes on in SemaType.cpp, but does not track if an attribute is inferred by NS_ASSUME macros or if it's spelled-out. Consequentially, clang also does not preserve any information about the macro ID for the type. The reason this works right now is because the pointer character isn't included in NullabilityLoc.

We can fix this by setting isImplicit on the inferred Attrs in SemaType.cpp, but I'm unsure if that's the correct approach. For now, this continues to work, and the tests will catch any breaking changes.

Incorporated feedback:

  • Removed confusing comment about NS-prefixed classes
  • Use StringRef instead of str
  • Fixed case where isa was more appropriate
MosheBerman added inline comments.May 12 2022, 2:03 PM
clang/test/Analysis/nullability-fixits.mm
6–11

Why don't you use the %clang_analyze_cc1 ... form instead or even better the %check_analyzer_fixit tool-subst pattern?
See the clang/test/Analysis/dead-stores.c as an example.

I tried both, and %clang_analyze_cc1 will work. The check_analyzer_fixit pattern fails because the test imports a fake header, which exists at input, but not in the output location.

Update tests to use %clang_analyze_cc1.

kyulee added a subscriber: kyulee.May 12 2022, 10:15 PM
MosheBerman marked an inline comment as done and 2 inline comments as not done.

Addressed feedback.

  • Removed comment about NS-classes
  • Changed some of the LIT test invocations
  • Added StringRef where appropriate.
MosheBerman marked an inline comment as not done.

My IDE auto-formatted away some whitespace, causing a merge conflict for the test CI.

Missed a whitespace. (Looks like there may be another merge conflict, in NullabilityChecker.cpp:117, but not sure if that's the issue. Let's try this just in case it's not.)

MosheBerman abandoned this revision.Oct 31 2022, 3:47 AM

In the interest of not leaving detritus on the internet, I'm going to abandon this diff. I believe there's an alternate approach worth pursuing, with clang tidy. Thanks for your reviews and constructive feedback!