Index: clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp =================================================================== --- clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp +++ clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp @@ -22,6 +22,7 @@ #include "clang/AST/ExprObjC.h" #include "clang/AST/Stmt.h" #include "clang/AST/Type.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/Analysis/AnalysisDeclContext.h" #include "clang/Analysis/CFG.h" #include "clang/Analysis/CFGStmtMap.h" @@ -306,9 +307,33 @@ CallEventRef<> Call = BRC.getStateManager().getCallEventManager().getCaller(SCtx, State); - const PrintingPolicy &PP = BRC.getASTContext().getPrintingPolicy(); const SourceManager &SM = BRC.getSourceManager(); + + // Region of interest corresponds to an IVar, exiting a method + // which could have written into that IVar, but did not. + if (const auto *MC = dyn_cast(Call)) { + if (const ObjCIvarDecl *IVD = ivarForRegionOfInterest(MC, State)) { + + // Find declaration associated with the runtime definition. + const Decl *PD = Call->getRuntimeDefinition().getDecl(); + if (!PD) + PD = MC->getDecl(); + + if (potentiallyWritesIntoIvar(PD, IVD) && + !isRegionOfInterestModifiedInFrame(N)) { + + PathDiagnosticLocation L = getPathDiagnosticLocation( + CallExitLoc->getReturnStmt(), SM, Ctx, Call); + SmallString<256> sbuf; + llvm::raw_svector_ostream os(sbuf); + os << DiagnosticsMsg << "self." << IVD->getName() << "'"; + return std::make_shared(L, os.str()); + } + } + } + + if (const auto *CCall = dyn_cast(Call)) { const MemRegion *ThisRegion = CCall->getCXXThisVal().getAsRegion(); if (RegionOfInterest->isSubRegionOf(ThisRegion) @@ -346,6 +371,46 @@ } private: + + /// For a method declaration and a program state, + /// find an ivar corresponding to the region of interest. + ObjCIvarDecl *ivarForRegionOfInterest(const ObjCMethodCall *MC, + ProgramStateRef State) { + auto *MD = MC->getDecl(); + auto *P = MD->getParent(); + auto *ID = dyn_cast(P); + + if (!ID) + return nullptr; + + const ObjCImplementationDecl *ImplDecl = ID->getImplementation(); + if (!ImplDecl) + return nullptr; + + SVal Receiver = MC->getReceiverSVal(); + for (ObjCIvarDecl *IVD : ImplDecl->ivars()) { + SVal LValue = State->getLValue(IVD, Receiver); + const MemRegion *LR = LValue.getAsRegion(); + if (LR == RegionOfInterest) + return IVD; // found + } + + return nullptr; + } + + /// \return Whether the method declaration \p Parent + /// syntactically has a binary operation writing into the ivar \p Ivar. + bool potentiallyWritesIntoIvar(const Decl *Parent, + const ObjCIvarDecl *Ivar) { + using namespace ast_matchers; + StatementMatcher WriteIntoIvarM = binaryOperator( + hasOperatorName("="), hasLHS(ignoringParenImpCasts(objcIvarRefExpr( + hasDeclaration(equalsNode(Ivar)))))); + DeclarationMatcher ParentM = decl(hasDescendant(WriteIntoIvarM)); + auto Matches = match(ParentM, *Parent, Parent->getASTContext()); + return !Matches.empty(); + } + /// Check and lazily calculate whether the region of interest is /// modified in the stack frame to which \p N belongs. /// The calculation is cached in FramesModifyingRegion. @@ -419,7 +484,7 @@ const SourceManager &SM, const PrintingPolicy &PP, CallExitBegin &CallExitLoc, - const CXXConstructorCall *Call, + CallEventRef<> Call, const MemRegion *ArgRegion) { SmallString<256> sbuf; llvm::raw_svector_ostream os(sbuf); Index: clang/test/Analysis/diagnostics/no-store-func-path-notes.m =================================================================== --- clang/test/Analysis/diagnostics/no-store-func-path-notes.m +++ clang/test/Analysis/diagnostics/no-store-func-path-notes.m @@ -1,6 +1,10 @@ -// RUN: %clang_analyze_cc1 -x objective-c -analyzer-checker=core -analyzer-output=text -Wno-objc-root-class -fblocks -verify %s +// RUN: %clang_analyze_cc1 -x objective-c -analyzer-checker=core,nullability -analyzer-output=text -Wno-objc-root-class -fblocks -verify %s -@interface I +#include "../Inputs/system-header-simulator-for-nullability.h" + +extern int coin(); + +@interface I : NSObject - (int)initVar:(int *)var param:(int)param; @end @@ -44,3 +48,29 @@ }(); return z; } + +extern void expectNonNull(NSString * _Nonnull a); + +@interface A : NSObject +- (void) func; +- (void) initAMaybe; +@end + +@implementation A { + NSString * a; +} + +- (void) func { + a = nil; // expected-note{{nil object reference stored to 'a'}} + [self initAMaybe]; // expected-note{{Calling 'initAMaybe'}} + // expected-note@-1{{Returning from 'initAMaybe'}} + expectNonNull(a); // expected-warning{{nil passed to a callee that requires a non-null 1st parameter}} + // expected-note@-1{{nil passed to a callee that requires a non-null 1st parameter}} +} + +- (void) initAMaybe { + if (coin()) // expected-note{{Assuming the condition is false}} + // expected-note@-1{{Taking false branch}} + a = @"string"; +} // expected-note{{Returning without writing to 'self.a'}} +@end