diff --git a/clang/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp b/clang/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp --- a/clang/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp @@ -97,7 +97,8 @@ namespace { class NilArgChecker : public Checker, - check::PostStmt > { + check::PostStmt, + EventDispatcher> { mutable std::unique_ptr BT; mutable llvm::SmallDenseMap StringSelectors; @@ -139,12 +140,28 @@ void NilArgChecker::warnIfNilExpr(const Expr *E, const char *Msg, CheckerContext &C) const { - ProgramStateRef State = C.getState(); - if (State->isNull(C.getSVal(E)).isConstrainedTrue()) { - + auto Location = C.getSVal(E).getAs(); + if (!Location) + return; + + auto [NonNull, Null] = C.getState()->assume(*Location); + + // If it's known to be null. + if (!NonNull && Null) { if (ExplodedNode *N = C.generateErrorNode()) { generateBugReport(N, Msg, E->getSourceRange(), E, C); + return; + } + } + + // If it might be null, assume that it cannot after this operation. + if (Null) { + // One needs to make sure the pointer is non-null to be used here. + if (ExplodedNode *N = C.generateSink(Null, C.getPredecessor())) { + dispatchEvent({*Location, /*IsLoad=*/false, N, &C.getBugReporter(), + /*IsDirectDereference=*/false}); } + C.addTransition(NonNull); } } diff --git a/clang/test/Analysis/NSContainers.m b/clang/test/Analysis/NSContainers.m --- a/clang/test/Analysis/NSContainers.m +++ b/clang/test/Analysis/NSContainers.m @@ -1,4 +1,7 @@ -// RUN: %clang_analyze_cc1 -Wno-objc-literal-conversion -analyzer-checker=core,osx.cocoa.NonNilReturnValue,osx.cocoa.NilArg,osx.cocoa.Loops,debug.ExprInspection -verify -Wno-objc-root-class %s +// RUN: %clang_analyze_cc1 -Wno-objc-literal-conversion -Wno-objc-root-class -fobjc-arc \ +// RUN: -analyzer-checker=core,osx.cocoa,nullability \ +// RUN: -analyzer-config eagerly-assume=false \ +// RUN: -analyzer-checker=debug.ExprInspection -verify %s void clang_analyzer_eval(int); @@ -31,13 +34,13 @@ @end typedef struct { - unsigned long state; - id *itemsPtr; - unsigned long *mutationsPtr; - unsigned long extra[5]; + unsigned long state; + id __unsafe_unretained _Nullable * _Nullable itemsPtr; + unsigned long * _Nullable mutationsPtr; + unsigned long extra[5]; } NSFastEnumerationState; @protocol NSFastEnumeration -- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id [])buffer count:(NSUInteger)len; +- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained _Nullable [_Nonnull])buffer count:(NSUInteger)len; @end @interface NSArray : NSObject @@ -323,3 +326,43 @@ // that 'obj' can be nil in this context. dict[obj] = getStringFromString(obj); // no-warning } + +Foo * getMightBeNullFoo(); +Foo * _Nonnull getNonnullFoo(); +Foo * _Nullable getNullableFoo(); + +void testCreateDictionaryLiteralWithNullableArg() { + Foo *p1 = getMightBeNullFoo(); + Foo *p2 = getNonnullFoo(); + Foo *p3 = getNullableFoo(); + + clang_analyzer_eval(p1 == nil); // expected-warning {{UNKNOWN}} + clang_analyzer_eval(p2 == nil); // expected-warning {{UNKNOWN}} + clang_analyzer_eval(p3 == nil); // expected-warning {{UNKNOWN}} + + (void)@{@"abc" : p1}; // no-warning + (void)@{@"abc" : p2}; // no-warning + (void)@{@"abc" : p3}; // expected-warning {{Nullable pointer is passed to a callee that requires a non-null}} + + clang_analyzer_eval(p1 == nil); // expected-warning {{FALSE}} + clang_analyzer_eval(p2 == nil); // expected-warning {{FALSE}} + clang_analyzer_eval(p3 == nil); // expected-warning {{FALSE}} +} + +void testCreateArrayLiteralWithNullableArg() { + Foo *p1 = getMightBeNullFoo(); + Foo *p2 = getNonnullFoo(); + Foo *p3 = getNullableFoo(); + + clang_analyzer_eval(p1 == nil); // expected-warning {{UNKNOWN}} + clang_analyzer_eval(p2 == nil); // expected-warning {{UNKNOWN}} + clang_analyzer_eval(p3 == nil); // expected-warning {{UNKNOWN}} + + (void)@[p1]; // no-warning + (void)@[p2]; // no-warning + (void)@[p3]; // expected-warning {{Nullable pointer is passed to a callee that requires a non-null}} + + clang_analyzer_eval(p1 == nil); // expected-warning {{FALSE}} + clang_analyzer_eval(p2 == nil); // expected-warning {{FALSE}} + clang_analyzer_eval(p3 == nil); // expected-warning {{FALSE}} +} \ No newline at end of file