diff --git a/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp index e096e2047f5e..31560f2687c6 100644 --- a/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp @@ -100,6 +100,14 @@ class NullabilityChecker mutable std::unique_ptr BT; public: + // If true, the checker will not diagnose nullabilility issues for calls + // to system headers. This option is motivated by the observation that large + // projects may have many nullability warnings. These projects may + // find warnings about nullability annotations that they have explicitly + // added themselves higher priority to fix than warnings on calls to system + // libraries. + DefaultBool NoDiagnoseCallsToSystemHeaders; + void checkBind(SVal L, SVal V, const Stmt *S, CheckerContext &C) const; void checkPostStmt(const ExplicitCastExpr *CE, CheckerContext &C) const; void checkPreStmt(const ReturnStmt *S, CheckerContext &C) const; @@ -191,6 +199,15 @@ private: /// to the wrapped region. Otherwise it will return a nullptr. const SymbolicRegion *getTrackRegion(SVal Val, bool CheckSuperRegion = false) const; + + /// Returns true if the call is diagnosable in the currrent analyzer + /// configuration. + bool isDiagnosableCall(const CallEvent &Call) const { + if (NoDiagnoseCallsToSystemHeaders && Call.isInSystemHeader()) + return false; + + return true; + } }; class NullabilityState { @@ -620,7 +637,8 @@ void NullabilityChecker::checkPreCall(const CallEvent &Call, if (Filter.CheckNullPassedToNonnull && Nullness == NullConstraint::IsNull && ArgExprTypeLevelNullability != Nullability::Nonnull && - RequiredNullability == Nullability::Nonnull) { + RequiredNullability == Nullability::Nonnull && + isDiagnosableCall(Call)) { ExplodedNode *N = C.generateErrorNode(State); if (!N) return; @@ -647,7 +665,8 @@ void NullabilityChecker::checkPreCall(const CallEvent &Call, continue; if (Filter.CheckNullablePassedToNonnull && - RequiredNullability == Nullability::Nonnull) { + RequiredNullability == Nullability::Nonnull && + isDiagnosableCall(Call)) { ExplodedNode *N = C.addTransition(State); SmallString<256> SBuf; llvm::raw_svector_ostream OS(SBuf); @@ -1106,6 +1125,10 @@ void NullabilityChecker::printState(raw_ostream &Out, ProgramStateRef State, checker->Filter.Check##name = true; \ checker->Filter.CheckName##name = mgr.getCurrentCheckName(); \ checker->NeedTracking = checker->NeedTracking || trackingRequired; \ + checker->NoDiagnoseCallsToSystemHeaders = \ + checker->NoDiagnoseCallsToSystemHeaders || \ + mgr.getAnalyzerOptions().getBooleanOption( \ + "NoDiagnoseCallsToSystemHeaders", false, checker, true); \ } // The checks are likely to be turned on by default and it is possible to do diff --git a/clang/test/Analysis/Inputs/system-header-simulator-for-nullability.h b/clang/test/Analysis/Inputs/system-header-simulator-for-nullability.h new file mode 100644 index 000000000000..9bb2786bd7f1 --- /dev/null +++ b/clang/test/Analysis/Inputs/system-header-simulator-for-nullability.h @@ -0,0 +1,42 @@ +#pragma clang system_header + +#define nil 0 +#define BOOL int + +#define NS_ASSUME_NONNULL_BEGIN _Pragma("clang assume_nonnull begin") +#define NS_ASSUME_NONNULL_END _Pragma("clang assume_nonnull end") + +NS_ASSUME_NONNULL_BEGIN + +typedef struct _NSZone NSZone; + +@protocol NSObject ++ (id)alloc; +- (id)init; +@end + +@protocol NSCopying +- (id)copyWithZone:(nullable NSZone *)zone; +@end + +@protocol NSMutableCopying +- (id)mutableCopyWithZone:(nullable NSZone *)zone; +@end + +__attribute__((objc_root_class)) +@interface +NSObject +@end + +@interface NSString : NSObject +- (BOOL)isEqualToString : (NSString *)aString; +- (NSString *)stringByAppendingString:(NSString *)aString; +@end + +void NSSystemFunctionTakingNonnull(NSString *s); + +@interface NSSystemClass : NSObject +- (void) takesNonnull:(NSString *)s; +@end + +NS_ASSUME_NONNULL_END diff --git a/clang/test/Analysis/nullability.mm b/clang/test/Analysis/nullability.mm index 220a38118aa2..0a3ae7a1968c 100644 --- a/clang/test/Analysis/nullability.mm +++ b/clang/test/Analysis/nullability.mm @@ -1,38 +1,7 @@ -// RUN: %clang_cc1 -fblocks -analyze -analyzer-checker=core,nullability -verify %s +// RUN: %clang_cc1 -fblocks -analyze -analyzer-checker=core,nullability -DNOSYSTEMHEADERS=0 -verify %s +// RUN: %clang_cc1 -fblocks -analyze -analyzer-checker=core,nullability -analyzer-config nullability:NoDiagnoseCallsToSystemHeaders=true -DNOSYSTEMHEADERS=1 -verify %s -#define nil 0 -#define BOOL int - -#define NS_ASSUME_NONNULL_BEGIN _Pragma("clang assume_nonnull begin") -#define NS_ASSUME_NONNULL_END _Pragma("clang assume_nonnull end") - -typedef struct _NSZone NSZone; - -@protocol NSObject -+ (id)alloc; -- (id)init; -@end - -NS_ASSUME_NONNULL_BEGIN -@protocol NSCopying -- (id)copyWithZone:(nullable NSZone *)zone; - -@end - -@protocol NSMutableCopying -- (id)mutableCopyWithZone:(nullable NSZone *)zone; -@end -NS_ASSUME_NONNULL_END - -__attribute__((objc_root_class)) -@interface -NSObject -@end - -@interface NSString : NSObject -- (BOOL)isEqualToString : (NSString *_Nonnull)aString; -- (NSString *)stringByAppendingString:(NSString *_Nonnull)aString; -@end +#include "Inputs/system-header-simulator-for-nullability.h" @interface TestObject : NSObject - (int *_Nonnull)returnsNonnull; @@ -419,3 +388,24 @@ Dummy *_Nonnull testDefensiveInlineChecks(Dummy * p) { return newInstance; } @end + +NSString * _Nullable returnsNullableString(); + +void callFunctionInSystemHeader() { + NSString *s = returnsNullableString(); + + NSSystemFunctionTakingNonnull(s); + #if !NOSYSTEMHEADERS + // expected-warning@-2{{Nullable pointer is passed to a callee that requires a non-null 1st parameter}} + #endif +} + +void callMethodInSystemHeader() { + NSString *s = returnsNullableString(); + + NSSystemClass *sc = [[NSSystemClass alloc] init]; + [sc takesNonnull:s]; + #if !NOSYSTEMHEADERS + // expected-warning@-2{{Nullable pointer is passed to a callee that requires a non-null 1st parameter}} + #endif +} diff --git a/clang/test/Analysis/nullability_nullonly.mm b/clang/test/Analysis/nullability_nullonly.mm index d82105cf5b55..9671877719f3 100644 --- a/clang/test/Analysis/nullability_nullonly.mm +++ b/clang/test/Analysis/nullability_nullonly.mm @@ -1,20 +1,7 @@ -// RUN: %clang_cc1 -analyze -fobjc-arc -analyzer-checker=core,nullability.NullPassedToNonnull,nullability.NullReturnedFromNonnull -verify %s +// RUN: %clang_cc1 -analyze -fobjc-arc -analyzer-checker=core,nullability.NullPassedToNonnull,nullability.NullReturnedFromNonnull -DNOSYSTEMHEADERS=0 -verify %s +// RUN: %clang_cc1 -analyze -fobjc-arc -analyzer-checker=core,nullability.NullPassedToNonnull,nullability.NullReturnedFromNonnull -analyzer-config nullability:NoDiagnoseCallsToSystemHeaders=true -DNOSYSTEMHEADERS=1 -verify %s -#define nil 0 -#define BOOL int - -@protocol NSObject -+ (id)alloc; -- (id)init; -@end - -@protocol NSCopying -@end - -__attribute__((objc_root_class)) -@interface -NSObject -@end +#include "Inputs/system-header-simulator-for-nullability.h" int getRandom(); @@ -159,3 +146,25 @@ TestObject * _Nonnull returnsNilObjCInstanceDirectlyWithSuppressingCast() { return p; // no-warning } @end + + +void callFunctionInSystemHeader() { + NSString *s; + s = nil; + + NSSystemFunctionTakingNonnull(s); + #if !NOSYSTEMHEADERS + // expected-warning@-2{{Null passed to a callee that requires a non-null 1st parameter}} + #endif +} + +void callMethodInSystemHeader() { + NSString *s; + s = nil; + + NSSystemClass *sc = [[NSSystemClass alloc] init]; + [sc takesNonnull:s]; + #if !NOSYSTEMHEADERS + // expected-warning@-2{{Null passed to a callee that requires a non-null 1st parameter}} + #endif +}