diff --git a/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp --- a/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp @@ -81,7 +81,7 @@ : public Checker, check::PostCall, check::PostStmt, check::PostObjCMessage, check::DeadSymbols, - check::Event> { + check::Location, check::Event> { mutable std::unique_ptr BT; public: @@ -101,6 +101,8 @@ void checkPreCall(const CallEvent &Call, CheckerContext &C) const; void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; void checkEvent(ImplicitNullDerefEvent Event) const; + void checkLocation(SVal Location, bool IsLoad, const Stmt *S, + CheckerContext &C) const; void printState(raw_ostream &Out, ProgramStateRef State, const char *NL, const char *Sep) const override; @@ -503,6 +505,52 @@ } } +// Whenever we see a load from a typed memory region that's been annotated as +// 'nonnull', we want to trust the user on that and assume that it is is indeed +// non-null. +// +// We do so even if the value is known to have been assigned to null. +// The user should be warned on assigning the null value to a non-null pointer +// as opposed to warning on the later dereference of this pointer. +// +// \code +// int * _Nonnull var = 0; // we want to warn the user here... +// // . . . +// *var = 42; // ...and not here +// \endcode +void NullabilityChecker::checkLocation(SVal Location, bool IsLoad, + const Stmt *S, + CheckerContext &Context) const { + // We should care only about loads. + // The main idea is to add a constraint whenever we're loading a value from + // an annotated pointer type. + if (!IsLoad) + return; + + // Annotations that we want to consider make sense only for types. + const auto *Region = + dyn_cast_or_null(Location.getAsRegion()); + if (!Region) + return; + + ProgramStateRef State = Context.getState(); + + auto StoredVal = State->getSVal(Region).getAs(); + if (!StoredVal) + return; + + Nullability NullabilityOfTheLoadedValue = + getNullabilityAnnotation(Region->getValueType()); + + if (NullabilityOfTheLoadedValue == Nullability::Nonnull) { + // It doesn't matter what we think about this particular pointer, it should + // be considered non-null as annotated by the developer. + if (ProgramStateRef NewState = State->assume(*StoredVal, true)) { + Context.addTransition(NewState); + } + } +} + /// Find the outermost subexpression of E that is not an implicit cast. /// This looks through the implicit casts to _Nonnull that ARC adds to /// return expressions of ObjC types when the return type of the function or diff --git a/clang/test/Analysis/CheckNSError.m b/clang/test/Analysis/CheckNSError.m --- a/clang/test/Analysis/CheckNSError.m +++ b/clang/test/Analysis/CheckNSError.m @@ -1,5 +1,8 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker=core,osx.cocoa.NSError,osx.coreFoundation.CFError -analyzer-store=region -verify -Wno-objc-root-class %s - +// RUN: %clang_analyze_cc1 -verify -Wno-objc-root-class %s \ +// RUN: -analyzer-checker=core \ +// RUN: -analyzer-checker=nullability \ +// RUN: -analyzer-checker=osx.cocoa.NSError \ +// RUN: -analyzer-checker=osx.coreFoundation.CFError typedef signed char BOOL; typedef int NSInteger; @@ -18,6 +21,7 @@ @interface A - (void)myMethodWhichMayFail:(NSError **)error; - (BOOL)myMethodWhichMayFail2:(NSError **)error; +- (BOOL)myMethodWhichMayFail3:(NSError **_Nonnull)error; @end @implementation A @@ -29,6 +33,11 @@ if (error) *error = [NSError errorWithDomain:@"domain" code:1 userInfo:0]; // no-warning return 0; } + +- (BOOL)myMethodWhichMayFail3:(NSError **_Nonnull)error { // no-warning + *error = [NSError errorWithDomain:@"domain" code:1 userInfo:0]; // no-warning + return 0; +} @end struct __CFError {};