diff options
Diffstat (limited to 'contrib/llvm/tools/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp')
-rw-r--r-- | contrib/llvm/tools/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp | 434 |
1 files changed, 276 insertions, 158 deletions
diff --git a/contrib/llvm/tools/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp b/contrib/llvm/tools/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp index c474e78..eb699d6 100644 --- a/contrib/llvm/tools/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp +++ b/contrib/llvm/tools/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp @@ -13,6 +13,8 @@ //===----------------------------------------------------------------------===// #include "ClangSACheckers.h" +#include "AllocationDiagnostics.h" +#include "SelectorExtras.h" #include "clang/AST/Attr.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclObjC.h" @@ -20,6 +22,7 @@ #include "clang/Analysis/DomainSpecific/CocoaConventions.h" #include "clang/Basic/LangOptions.h" #include "clang/Basic/SourceManager.h" +#include "clang/StaticAnalyzer/Checkers/ObjCRetainCount.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/Checker.h" @@ -28,7 +31,6 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" -#include "clang/StaticAnalyzer/Checkers/ObjCRetainCount.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/FoldingSet.h" #include "llvm/ADT/ImmutableList.h" @@ -38,8 +40,6 @@ #include "llvm/ADT/StringExtras.h" #include <cstdarg> -#include "AllocationDiagnostics.h" - using namespace clang; using namespace ento; using namespace objc_retain; @@ -95,29 +95,70 @@ public: }; private: - Kind kind; - RetEffect::ObjKind okind; + /// The number of outstanding retains. unsigned Cnt; + /// The number of outstanding autoreleases. unsigned ACnt; + /// The (static) type of the object at the time we started tracking it. QualType T; - RefVal(Kind k, RetEffect::ObjKind o, unsigned cnt, unsigned acnt, QualType t) - : kind(k), okind(o), Cnt(cnt), ACnt(acnt), T(t) {} + /// The current state of the object. + /// + /// See the RefVal::Kind enum for possible values. + unsigned RawKind : 5; + + /// The kind of object being tracked (CF or ObjC), if known. + /// + /// See the RetEffect::ObjKind enum for possible values. + unsigned RawObjectKind : 2; + + /// True if the current state and/or retain count may turn out to not be the + /// best possible approximation of the reference counting state. + /// + /// If true, the checker may decide to throw away ("override") this state + /// in favor of something else when it sees the object being used in new ways. + /// + /// This setting should not be propagated to state derived from this state. + /// Once we start deriving new states, it would be inconsistent to override + /// them. + unsigned IsOverridable : 1; + + RefVal(Kind k, RetEffect::ObjKind o, unsigned cnt, unsigned acnt, QualType t, + bool Overridable = false) + : Cnt(cnt), ACnt(acnt), T(t), RawKind(static_cast<unsigned>(k)), + RawObjectKind(static_cast<unsigned>(o)), IsOverridable(Overridable) { + assert(getKind() == k && "not enough bits for the kind"); + assert(getObjKind() == o && "not enough bits for the object kind"); + } public: - Kind getKind() const { return kind; } + Kind getKind() const { return static_cast<Kind>(RawKind); } - RetEffect::ObjKind getObjKind() const { return okind; } + RetEffect::ObjKind getObjKind() const { + return static_cast<RetEffect::ObjKind>(RawObjectKind); + } unsigned getCount() const { return Cnt; } unsigned getAutoreleaseCount() const { return ACnt; } unsigned getCombinedCounts() const { return Cnt + ACnt; } - void clearCounts() { Cnt = 0; ACnt = 0; } - void setCount(unsigned i) { Cnt = i; } - void setAutoreleaseCount(unsigned i) { ACnt = i; } + void clearCounts() { + Cnt = 0; + ACnt = 0; + IsOverridable = false; + } + void setCount(unsigned i) { + Cnt = i; + IsOverridable = false; + } + void setAutoreleaseCount(unsigned i) { + ACnt = i; + IsOverridable = false; + } QualType getType() const { return T; } + bool isOverridable() const { return IsOverridable; } + bool isOwned() const { return getKind() == Owned; } @@ -134,20 +175,31 @@ public: return getKind() == ReturnedNotOwned; } + /// Create a state for an object whose lifetime is the responsibility of the + /// current function, at least partially. + /// + /// Most commonly, this is an owned object with a retain count of +1. static RefVal makeOwned(RetEffect::ObjKind o, QualType t, unsigned Count = 1) { return RefVal(Owned, o, Count, 0, t); } + /// Create a state for an object whose lifetime is not the responsibility of + /// the current function. + /// + /// Most commonly, this is an unowned object with a retain count of +0. static RefVal makeNotOwned(RetEffect::ObjKind o, QualType t, unsigned Count = 0) { return RefVal(NotOwned, o, Count, 0, t); } - // Comparison, profiling, and pretty-printing. - - bool operator==(const RefVal& X) const { - return kind == X.kind && Cnt == X.Cnt && T == X.T && ACnt == X.ACnt; + /// Create an "overridable" state for an unowned object at +0. + /// + /// An overridable state is one that provides a good approximation of the + /// reference counting state now, but which may be discarded later if the + /// checker sees the object being used in new ways. + static RefVal makeOverridableNotOwned(RetEffect::ObjKind o, QualType t) { + return RefVal(NotOwned, o, 0, 0, t, /*Overridable=*/true); } RefVal operator-(size_t i) const { @@ -170,11 +222,24 @@ public: getType()); } + // Comparison, profiling, and pretty-printing. + + bool hasSameState(const RefVal &X) const { + return getKind() == X.getKind() && Cnt == X.Cnt && ACnt == X.ACnt; + } + + bool operator==(const RefVal& X) const { + return T == X.T && hasSameState(X) && getObjKind() == X.getObjKind() && + IsOverridable == X.IsOverridable; + } + void Profile(llvm::FoldingSetNodeID& ID) const { - ID.AddInteger((unsigned) kind); + ID.Add(T); + ID.AddInteger(RawKind); ID.AddInteger(Cnt); ID.AddInteger(ACnt); - ID.Add(T); + ID.AddInteger(RawObjectKind); + ID.AddBoolean(IsOverridable); } void print(raw_ostream &Out) const; @@ -184,6 +249,9 @@ void RefVal::print(raw_ostream &Out) const { if (!T.isNull()) Out << "Tracked " << T.getAsString() << '/'; + if (isOverridable()) + Out << "(overridable) "; + switch (getKind()) { default: llvm_unreachable("Invalid RefVal kind"); case Owned: { @@ -383,10 +451,10 @@ public: : II(ii), S(s) {} ObjCSummaryKey(const ObjCInterfaceDecl *d, Selector s) - : II(d ? d->getIdentifier() : 0), S(s) {} + : II(d ? d->getIdentifier() : nullptr), S(s) {} ObjCSummaryKey(Selector s) - : II(0), S(s) {} + : II(nullptr), S(s) {} IdentifierInfo *getIdentifier() const { return II; } Selector getSelector() const { return S; } @@ -435,7 +503,7 @@ public: if (I != M.end()) return I->second; if (!D) - return NULL; + return nullptr; // Walk the super chain. If we find a hit with a parent, we'll end // up returning that summary. We actually allow that key (null,S), as @@ -448,7 +516,7 @@ public: break; if (!C) - return NULL; + return nullptr; } // Cache the summary with original key to make the next lookup faster @@ -466,7 +534,7 @@ public: if (I == M.end()) I = M.find(ObjCSummaryKey(S)); - return I == M.end() ? NULL : I->second; + return I == M.end() ? nullptr : I->second; } const RetainSummary *& operator[](ObjCSummaryKey K) { @@ -608,18 +676,9 @@ private: ObjCMethodSummaries[ObjCSummaryKey(ClsII, S)] = Summ; } - Selector generateSelector(va_list argp) { - SmallVector<IdentifierInfo*, 10> II; - - while (const char* s = va_arg(argp, const char*)) - II.push_back(&Ctx.Idents.get(s)); - - return Ctx.Selectors.getSelector(II.size(), &II[0]); - } - - void addMethodSummary(IdentifierInfo *ClsII, ObjCMethodSummariesTy& Summaries, - const RetainSummary * Summ, va_list argp) { - Selector S = generateSelector(argp); + void addMethodSummary(IdentifierInfo *ClsII, ObjCMethodSummariesTy &Summaries, + const RetainSummary *Summ, va_list argp) { + Selector S = getKeywordSelector(Ctx, argp); Summaries[ObjCSummaryKey(ClsII, S)] = Summ; } @@ -653,18 +712,18 @@ public: AF(BPAlloc), ScratchArgs(AF.getEmptyMap()), ObjCAllocRetE(gcenabled ? RetEffect::MakeGCNotOwned() - : (usesARC ? RetEffect::MakeARCNotOwned() + : (usesARC ? RetEffect::MakeNotOwned(RetEffect::ObjC) : RetEffect::MakeOwned(RetEffect::ObjC, true))), ObjCInitRetE(gcenabled ? RetEffect::MakeGCNotOwned() - : (usesARC ? RetEffect::MakeARCNotOwned() + : (usesARC ? RetEffect::MakeNotOwned(RetEffect::ObjC) : RetEffect::MakeOwnedWhenTrackedReceiver())) { InitializeClassMethodSummaries(); InitializeMethodSummaries(); } const RetainSummary *getSummary(const CallEvent &Call, - ProgramStateRef State = 0); + ProgramStateRef State = nullptr); const RetainSummary *getFunctionSummary(const FunctionDecl *FD); @@ -689,7 +748,7 @@ public: const RetainSummary *getMethodSummary(const ObjCMethodDecl *MD) { const ObjCInterfaceDecl *ID = MD->getClassInterface(); Selector S = MD->getSelector(); - QualType ResultTy = MD->getResultType(); + QualType ResultTy = MD->getReturnType(); ObjCMethodSummariesTy *CachedSummaries; if (MD->isInstanceMethod()) @@ -861,7 +920,7 @@ void RetainSummaryManager::updateSummaryForCall(const RetainSummary *&S, // Special cases where the callback argument CANNOT free the return value. // This can generally only happen if we know that the callback will only be // called when the return value is already being deallocated. - if (const FunctionCall *FC = dyn_cast<FunctionCall>(&Call)) { + if (const SimpleFunctionCall *FC = dyn_cast<SimpleFunctionCall>(&Call)) { if (IdentifierInfo *Name = FC->getDecl()->getIdentifier()) { // When the CGBitmapContext is deallocated, the callback here will free // the associated data buffer. @@ -909,7 +968,7 @@ RetainSummaryManager::getSummary(const CallEvent &Call, const RetainSummary *Summ; switch (Call.getKind()) { case CE_Function: - Summ = getFunctionSummary(cast<FunctionCall>(Call).getDecl()); + Summ = getFunctionSummary(cast<SimpleFunctionCall>(Call).getDecl()); break; case CE_CXXMember: case CE_CXXMemberOperator: @@ -947,7 +1006,7 @@ RetainSummaryManager::getFunctionSummary(const FunctionDecl *FD) { return I->second; // No summary? Generate one. - const RetainSummary *S = 0; + const RetainSummary *S = nullptr; bool AllowAnnotations = true; do { @@ -971,7 +1030,7 @@ RetainSummaryManager::getFunctionSummary(const FunctionDecl *FD) { FName = FName.substr(FName.find_first_not_of('_')); // Inspect the result type. - QualType RetTy = FT->getResultType(); + QualType RetTy = FT->getReturnType(); // FIXME: This should all be refactored into a chain of "summary lookup" // filters. @@ -1102,7 +1161,7 @@ RetainSummaryManager::getFunctionSummary(const FunctionDecl *FD) { break; } - if (FD->getAttr<CFAuditedTransferAttr>()) { + if (FD->hasAttr<CFAuditedTransferAttr>()) { S = getCFCreateGetRuleSummary(FD); break; } @@ -1175,7 +1234,7 @@ RetainSummaryManager::getUnarySummary(const FunctionType* FT, // Sanity check that this is *really* a unary function. This can // happen if people do weird things. const FunctionProtoType* FTP = dyn_cast<FunctionProtoType>(FT); - if (!FTP || FTP->getNumArgs() != 1) + if (!FTP || FTP->getNumParams() != 1) return getPersistentStopSummary(); assert (ScratchArgs.isEmpty()); @@ -1214,21 +1273,21 @@ Optional<RetEffect> RetainSummaryManager::getRetEffectFromAnnotations(QualType RetTy, const Decl *D) { if (cocoa::isCocoaObjectRef(RetTy)) { - if (D->getAttr<NSReturnsRetainedAttr>()) + if (D->hasAttr<NSReturnsRetainedAttr>()) return ObjCAllocRetE; - if (D->getAttr<NSReturnsNotRetainedAttr>() || - D->getAttr<NSReturnsAutoreleasedAttr>()) + if (D->hasAttr<NSReturnsNotRetainedAttr>() || + D->hasAttr<NSReturnsAutoreleasedAttr>()) return RetEffect::MakeNotOwned(RetEffect::ObjC); } else if (!RetTy->isPointerType()) { return None; } - if (D->getAttr<CFReturnsRetainedAttr>()) + if (D->hasAttr<CFReturnsRetainedAttr>()) return RetEffect::MakeOwned(RetEffect::CF, true); - if (D->getAttr<CFReturnsNotRetainedAttr>()) + if (D->hasAttr<CFReturnsNotRetainedAttr>()) return RetEffect::MakeNotOwned(RetEffect::CF); return None; @@ -1248,13 +1307,13 @@ RetainSummaryManager::updateSummaryFromAnnotations(const RetainSummary *&Summ, for (FunctionDecl::param_const_iterator pi = FD->param_begin(), pe = FD->param_end(); pi != pe; ++pi, ++parm_idx) { const ParmVarDecl *pd = *pi; - if (pd->getAttr<NSConsumedAttr>()) + if (pd->hasAttr<NSConsumedAttr>()) Template->addArg(AF, parm_idx, DecRefMsg); - else if (pd->getAttr<CFConsumedAttr>()) + else if (pd->hasAttr<CFConsumedAttr>()) Template->addArg(AF, parm_idx, DecRef); } - - QualType RetTy = FD->getResultType(); + + QualType RetTy = FD->getReturnType(); if (Optional<RetEffect> RetE = getRetEffectFromAnnotations(RetTy, FD)) Template->setRetEffect(*RetE); } @@ -1269,7 +1328,7 @@ RetainSummaryManager::updateSummaryFromAnnotations(const RetainSummary *&Summ, RetainSummaryTemplate Template(Summ, *this); // Effects on the receiver. - if (MD->getAttr<NSConsumesSelfAttr>()) + if (MD->hasAttr<NSConsumesSelfAttr>()) Template->setReceiverEffect(DecRefMsg); // Effects on the parameters. @@ -1278,14 +1337,14 @@ RetainSummaryManager::updateSummaryFromAnnotations(const RetainSummary *&Summ, pi=MD->param_begin(), pe=MD->param_end(); pi != pe; ++pi, ++parm_idx) { const ParmVarDecl *pd = *pi; - if (pd->getAttr<NSConsumedAttr>()) + if (pd->hasAttr<NSConsumedAttr>()) Template->addArg(AF, parm_idx, DecRefMsg); - else if (pd->getAttr<CFConsumedAttr>()) { + else if (pd->hasAttr<CFConsumedAttr>()) { Template->addArg(AF, parm_idx, DecRef); } } - - QualType RetTy = MD->getResultType(); + + QualType RetTy = MD->getReturnType(); if (Optional<RetEffect> RetE = getRetEffectFromAnnotations(RetTy, MD)) Template->setRetEffect(*RetE); } @@ -1388,7 +1447,7 @@ RetainSummaryManager::getStandardMethodSummary(const ObjCMethodDecl *MD, const RetainSummary * RetainSummaryManager::getInstanceMethodSummary(const ObjCMethodCall &Msg, ProgramStateRef State) { - const ObjCInterfaceDecl *ReceiverClass = 0; + const ObjCInterfaceDecl *ReceiverClass = nullptr; // We do better tracking of the type of the object than the core ExprEngine. // See if we have its type in our private state. @@ -1507,6 +1566,11 @@ void RetainSummaryManager::InitializeMethodSummaries() { // as for NSWindow objects. addClassMethSummary("NSPanel", "alloc", NoTrackYet); + // For NSNull, objects returned by +null are singletons that ignore + // retain/release semantics. Just don't track them. + // <rdar://problem/12858915> + addClassMethSummary("NSNull", "null", NoTrackYet); + // Don't track allocated autorelease pools, as it is okay to prematurely // exit a method. addClassMethSummary("NSAutoreleasePool", "alloc", NoTrackYet); @@ -1543,8 +1607,9 @@ namespace { class CFRefBug : public BugType { protected: - CFRefBug(StringRef name) - : BugType(name, categories::MemoryCoreFoundationObjectiveC) {} + CFRefBug(const CheckerBase *checker, StringRef name) + : BugType(checker, name, categories::MemoryCoreFoundationObjectiveC) {} + public: // FIXME: Eventually remove. @@ -1555,18 +1620,19 @@ namespace { class UseAfterRelease : public CFRefBug { public: - UseAfterRelease() : CFRefBug("Use-after-release") {} + UseAfterRelease(const CheckerBase *checker) + : CFRefBug(checker, "Use-after-release") {} - const char *getDescription() const { + const char *getDescription() const override { return "Reference-counted object is used after it is released"; } }; class BadRelease : public CFRefBug { public: - BadRelease() : CFRefBug("Bad release") {} + BadRelease(const CheckerBase *checker) : CFRefBug(checker, "Bad release") {} - const char *getDescription() const { + const char *getDescription() const override { return "Incorrect decrement of the reference count of an object that is " "not owned at this point by the caller"; } @@ -1574,40 +1640,40 @@ namespace { class DeallocGC : public CFRefBug { public: - DeallocGC() - : CFRefBug("-dealloc called while using garbage collection") {} + DeallocGC(const CheckerBase *checker) + : CFRefBug(checker, "-dealloc called while using garbage collection") {} - const char *getDescription() const { + const char *getDescription() const override { return "-dealloc called while using garbage collection"; } }; class DeallocNotOwned : public CFRefBug { public: - DeallocNotOwned() - : CFRefBug("-dealloc sent to non-exclusively owned object") {} + DeallocNotOwned(const CheckerBase *checker) + : CFRefBug(checker, "-dealloc sent to non-exclusively owned object") {} - const char *getDescription() const { + const char *getDescription() const override { return "-dealloc sent to object that may be referenced elsewhere"; } }; class OverAutorelease : public CFRefBug { public: - OverAutorelease() - : CFRefBug("Object autoreleased too many times") {} + OverAutorelease(const CheckerBase *checker) + : CFRefBug(checker, "Object autoreleased too many times") {} - const char *getDescription() const { + const char *getDescription() const override { return "Object autoreleased too many times"; } }; class ReturnedNotOwnedForOwned : public CFRefBug { public: - ReturnedNotOwnedForOwned() - : CFRefBug("Method should return an owned object") {} + ReturnedNotOwnedForOwned(const CheckerBase *checker) + : CFRefBug(checker, "Method should return an owned object") {} - const char *getDescription() const { + const char *getDescription() const override { return "Object with a +0 retain count returned to caller where a +1 " "(owning) retain count is expected"; } @@ -1615,15 +1681,14 @@ namespace { class Leak : public CFRefBug { public: - Leak(StringRef name) - : CFRefBug(name) { + Leak(const CheckerBase *checker, StringRef name) : CFRefBug(checker, name) { // Leaks should not be reported if they are post-dominated by a sink. setSuppressOnSink(true); } - const char *getDescription() const { return ""; } + const char *getDescription() const override { return ""; } - bool isLeak() const { return true; } + bool isLeak() const override { return true; } }; //===---------===// @@ -1640,20 +1705,20 @@ namespace { CFRefReportVisitor(SymbolRef sym, bool gcEnabled, const SummaryLogTy &log) : Sym(sym), SummaryLog(log), GCEnabled(gcEnabled) {} - virtual void Profile(llvm::FoldingSetNodeID &ID) const { + void Profile(llvm::FoldingSetNodeID &ID) const override { static int x = 0; ID.AddPointer(&x); ID.AddPointer(Sym); } - virtual PathDiagnosticPiece *VisitNode(const ExplodedNode *N, - const ExplodedNode *PrevN, - BugReporterContext &BRC, - BugReport &BR); + PathDiagnosticPiece *VisitNode(const ExplodedNode *N, + const ExplodedNode *PrevN, + BugReporterContext &BRC, + BugReport &BR) override; - virtual PathDiagnosticPiece *getEndPath(BugReporterContext &BRC, - const ExplodedNode *N, - BugReport &BR); + PathDiagnosticPiece *getEndPath(BugReporterContext &BRC, + const ExplodedNode *N, + BugReport &BR) override; }; class CFRefLeakReportVisitor : public CFRefReportVisitor { @@ -1664,9 +1729,9 @@ namespace { PathDiagnosticPiece *getEndPath(BugReporterContext &BRC, const ExplodedNode *N, - BugReport &BR); + BugReport &BR) override; - virtual BugReporterVisitor *clone() const { + BugReporterVisitor *clone() const override { // The curiously-recurring template pattern only works for one level of // subclassing. Rather than make a new template base for // CFRefReportVisitor, we simply override clone() to do the right thing. @@ -1697,7 +1762,7 @@ namespace { addGCModeDescription(LOpts, GCEnabled); } - virtual std::pair<ranges_iterator, ranges_iterator> getRanges() { + std::pair<ranges_iterator, ranges_iterator> getRanges() override { const CFRefBug& BugTy = static_cast<CFRefBug&>(getBugType()); if (!BugTy.isLeak()) return BugReport::getRanges(); @@ -1714,7 +1779,7 @@ namespace { CheckerContext &Ctx, bool IncludeAllocationLine); - PathDiagnosticLocation getLocation(const SourceManager &SM) const { + PathDiagnosticLocation getLocation(const SourceManager &SM) const override { assert(Location.isValid()); return Location; } @@ -1723,7 +1788,7 @@ namespace { void CFRefReport::addGCModeDescription(const LangOptions &LOpts, bool GCEnabled) { - const char *GCModeDescription = 0; + const char *GCModeDescription = nullptr; switch (LOpts.getGC()) { case LangOptions::GCOnly: @@ -1770,7 +1835,7 @@ PathDiagnosticPiece *CFRefReportVisitor::VisitNode(const ExplodedNode *N, // FIXME: We will eventually need to handle non-statement-based events // (__attribute__((cleanup))). if (!N->getLocation().getAs<StmtPoint>()) - return NULL; + return nullptr; // Check if the type state has changed. ProgramStateRef PrevSt = PrevN->getState(); @@ -1778,7 +1843,7 @@ PathDiagnosticPiece *CFRefReportVisitor::VisitNode(const ExplodedNode *N, const LocationContext *LCtx = N->getLocationContext(); const RefVal* CurrT = getRefBinding(CurrSt, Sym); - if (!CurrT) return NULL; + if (!CurrT) return nullptr; const RefVal &CurrV = *CurrT; const RefVal *PrevT = getRefBinding(PrevSt, Sym); @@ -1803,7 +1868,7 @@ PathDiagnosticPiece *CFRefReportVisitor::VisitNode(const ExplodedNode *N, if (isNumericLiteralExpression(BL->getSubExpr())) os << "NSNumber literal is an object with a +0 retain count"; else { - const ObjCInterfaceDecl *BoxClass = 0; + const ObjCInterfaceDecl *BoxClass = nullptr; if (const ObjCMethodDecl *Method = BL->getBoxingMethod()) BoxClass = Method->getClassInterface(); @@ -1918,7 +1983,7 @@ PathDiagnosticPiece *CFRefReportVisitor::VisitNode(const ExplodedNode *N, if (!GCEnabled && std::find(AEffects.begin(), AEffects.end(), Dealloc) != AEffects.end()) { // Determine if the object's reference count was pushed to zero. - assert(!(PrevV == CurrV) && "The typestate *must* have changed."); + assert(!PrevV.hasSameState(CurrV) && "The state should have changed."); // We may not have transitioned to 'release' if we hit an error. // This case is handled elsewhere. if (CurrV.getKind() == RefVal::Released) { @@ -1939,7 +2004,7 @@ PathDiagnosticPiece *CFRefReportVisitor::VisitNode(const ExplodedNode *N, if (GCEnabled) { // Determine if the object's reference count was pushed to zero. - assert(!(PrevV == CurrV) && "The typestate *must* have changed."); + assert(!PrevV.hasSameState(CurrV) && "The state should have changed."); os << "In GC mode a call to '" << *FD << "' decrements an object's retain count and registers the " @@ -1964,7 +2029,7 @@ PathDiagnosticPiece *CFRefReportVisitor::VisitNode(const ExplodedNode *N, } // Determine if the typestate has changed. - if (!(PrevV == CurrV)) + if (!PrevV.hasSameState(CurrV)) switch (CurrV.getKind()) { case RefVal::Owned: case RefVal::NotOwned: @@ -1972,7 +2037,7 @@ PathDiagnosticPiece *CFRefReportVisitor::VisitNode(const ExplodedNode *N, if (PrevV.getCount() == CurrV.getCount()) { // Did an autorelease message get sent? if (PrevV.getAutoreleaseCount() == CurrV.getAutoreleaseCount()) - return 0; + return nullptr; assert(PrevV.getAutoreleaseCount() < CurrV.getAutoreleaseCount()); os << "Object autoreleased"; @@ -2002,7 +2067,7 @@ PathDiagnosticPiece *CFRefReportVisitor::VisitNode(const ExplodedNode *N, case RefVal::ReturnedOwned: // Autoreleases can be applied after marking a node ReturnedOwned. if (CurrV.getAutoreleaseCount()) - return NULL; + return nullptr; os << "Object returned to caller as an owning reference (single " "retain count transferred to caller)"; @@ -2013,7 +2078,7 @@ PathDiagnosticPiece *CFRefReportVisitor::VisitNode(const ExplodedNode *N, break; default: - return NULL; + return nullptr; } // Emit any remaining diagnostics for the argument effects (if any). @@ -2038,7 +2103,7 @@ PathDiagnosticPiece *CFRefReportVisitor::VisitNode(const ExplodedNode *N, } while (0); if (os.str().empty()) - return 0; // We have nothing to say! + return nullptr; // We have nothing to say! const Stmt *S = N->getLocation().castAs<StmtPoint>().getStmt(); PathDiagnosticLocation Pos(S, BRC.getSourceManager(), @@ -2078,12 +2143,12 @@ GetAllocationSite(ProgramStateManager& StateMgr, const ExplodedNode *N, SymbolRef Sym) { const ExplodedNode *AllocationNode = N; const ExplodedNode *AllocationNodeInCurrentContext = N; - const MemRegion* FirstBinding = 0; + const MemRegion *FirstBinding = nullptr; const LocationContext *LeakContext = N->getLocationContext(); // The location context of the init method called on the leaked object, if // available. - const LocationContext *InitMethodContext = 0; + const LocationContext *InitMethodContext = nullptr; while (N) { ProgramStateRef St = N->getState(); @@ -2127,12 +2192,12 @@ GetAllocationSite(ProgramStateManager& StateMgr, const ExplodedNode *N, } } - N = N->pred_empty() ? NULL : *(N->pred_begin()); + N = N->pred_empty() ? nullptr : *(N->pred_begin()); } // If we are reporting a leak of the object that was allocated with alloc, // mark its init method as interesting. - const LocationContext *InterestingMethodContext = 0; + const LocationContext *InterestingMethodContext = nullptr; if (InitMethodContext) { const ProgramPoint AllocPP = AllocationNode->getLocation(); if (Optional<StmtPoint> SP = AllocPP.getAs<StmtPoint>()) @@ -2145,7 +2210,7 @@ GetAllocationSite(ProgramStateManager& StateMgr, const ExplodedNode *N, // do not report the binding. assert(N && "Could not find allocation node"); if (N->getLocationContext() != LeakContext) { - FirstBinding = 0; + FirstBinding = nullptr; } return AllocationInfo(AllocationNodeInCurrentContext, @@ -2212,9 +2277,9 @@ CFRefLeakReportVisitor::getEndPath(BugReporterContext &BRC, os << (isa<ObjCMethodDecl>(D) ? " is returned from a method " : " is returned from a function "); - if (D->getAttr<CFReturnsNotRetainedAttr>()) + if (D->hasAttr<CFReturnsNotRetainedAttr>()) os << "that is annotated as CF_RETURNS_NOT_RETAINED"; - else if (D->getAttr<NSReturnsNotRetainedAttr>()) + else if (D->hasAttr<NSReturnsNotRetainedAttr>()) os << "that is annotated as NS_RETURNS_NOT_RETAINED"; else { if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(D)) { @@ -2262,7 +2327,7 @@ CFRefLeakReport::CFRefLeakReport(CFRefBug &D, const LangOptions &LOpts, // Note that this is *not* the trimmed graph; we are guaranteed, however, // that all ancestor nodes that represent the allocation site have the // same SourceLocation. - const ExplodedNode *AllocNode = 0; + const ExplodedNode *AllocNode = nullptr; const SourceManager& SMgr = Ctx.getSourceManager(); @@ -2275,14 +2340,27 @@ CFRefLeakReport::CFRefLeakReport(CFRefBug &D, const LangOptions &LOpts, // Get the SourceLocation for the allocation site. // FIXME: This will crash the analyzer if an allocation comes from an - // implicit call. (Currently there are no such allocations in Cocoa, though.) - const Stmt *AllocStmt; + // implicit call (ex: a destructor call). + // (Currently there are no such allocations in Cocoa, though.) + const Stmt *AllocStmt = 0; ProgramPoint P = AllocNode->getLocation(); if (Optional<CallExitEnd> Exit = P.getAs<CallExitEnd>()) AllocStmt = Exit->getCalleeContext()->getCallSite(); - else - AllocStmt = P.castAs<PostStmt>().getStmt(); - assert(AllocStmt && "All allocations must come from explicit calls"); + else { + // We are going to get a BlockEdge when the leak and allocation happen in + // different, non-nested frames (contexts). For example, the case where an + // allocation happens in a block that captures a reference to it and + // that reference is overwritten/dropped by another call to the block. + if (Optional<BlockEdge> Edge = P.getAs<BlockEdge>()) { + if (Optional<CFGStmt> St = Edge->getDst()->front().getAs<CFGStmt>()) { + AllocStmt = St->getStmt(); + } + } + else { + AllocStmt = P.castAs<PostStmt>().getStmt(); + } + } + assert(AllocStmt && "Cannot find allocation statement"); PathDiagnosticLocation AllocLocation = PathDiagnosticLocation::createBegin(AllocStmt, SMgr, @@ -2328,24 +2406,25 @@ class RetainCountChecker check::PostStmt<ObjCArrayLiteral>, check::PostStmt<ObjCDictionaryLiteral>, check::PostStmt<ObjCBoxedExpr>, + check::PostStmt<ObjCIvarRefExpr>, check::PostCall, check::PreStmt<ReturnStmt>, check::RegionChanges, eval::Assume, eval::Call > { - mutable OwningPtr<CFRefBug> useAfterRelease, releaseNotOwned; - mutable OwningPtr<CFRefBug> deallocGC, deallocNotOwned; - mutable OwningPtr<CFRefBug> overAutorelease, returnNotOwnedForOwned; - mutable OwningPtr<CFRefBug> leakWithinFunction, leakAtReturn; - mutable OwningPtr<CFRefBug> leakWithinFunctionGC, leakAtReturnGC; + mutable std::unique_ptr<CFRefBug> useAfterRelease, releaseNotOwned; + mutable std::unique_ptr<CFRefBug> deallocGC, deallocNotOwned; + mutable std::unique_ptr<CFRefBug> overAutorelease, returnNotOwnedForOwned; + mutable std::unique_ptr<CFRefBug> leakWithinFunction, leakAtReturn; + mutable std::unique_ptr<CFRefBug> leakWithinFunctionGC, leakAtReturnGC; - typedef llvm::DenseMap<SymbolRef, const SimpleProgramPointTag *> SymbolTagMap; + typedef llvm::DenseMap<SymbolRef, const CheckerProgramPointTag *> SymbolTagMap; // This map is only used to ensure proper deletion of any allocated tags. mutable SymbolTagMap DeadSymbolTags; - mutable OwningPtr<RetainSummaryManager> Summaries; - mutable OwningPtr<RetainSummaryManager> SummariesGC; + mutable std::unique_ptr<RetainSummaryManager> Summaries; + mutable std::unique_ptr<RetainSummaryManager> SummariesGC; mutable SummaryLogTy SummaryLog; mutable bool ShouldResetSummaryLog; @@ -2402,17 +2481,18 @@ public: bool GCEnabled) const { if (GCEnabled) { if (!leakWithinFunctionGC) - leakWithinFunctionGC.reset(new Leak("Leak of object when using " - "garbage collection")); + leakWithinFunctionGC.reset(new Leak(this, "Leak of object when using " + "garbage collection")); return leakWithinFunctionGC.get(); } else { if (!leakWithinFunction) { if (LOpts.getGC() == LangOptions::HybridGC) { - leakWithinFunction.reset(new Leak("Leak of object when not using " + leakWithinFunction.reset(new Leak(this, + "Leak of object when not using " "garbage collection (GC) in " "dual GC/non-GC code")); } else { - leakWithinFunction.reset(new Leak("Leak")); + leakWithinFunction.reset(new Leak(this, "Leak")); } } return leakWithinFunction.get(); @@ -2422,17 +2502,19 @@ public: CFRefBug *getLeakAtReturnBug(const LangOptions &LOpts, bool GCEnabled) const { if (GCEnabled) { if (!leakAtReturnGC) - leakAtReturnGC.reset(new Leak("Leak of returned object when using " + leakAtReturnGC.reset(new Leak(this, + "Leak of returned object when using " "garbage collection")); return leakAtReturnGC.get(); } else { if (!leakAtReturn) { if (LOpts.getGC() == LangOptions::HybridGC) { - leakAtReturn.reset(new Leak("Leak of returned object when not using " + leakAtReturn.reset(new Leak(this, + "Leak of returned object when not using " "garbage collection (GC) in dual " "GC/non-GC code")); } else { - leakAtReturn.reset(new Leak("Leak of returned object")); + leakAtReturn.reset(new Leak(this, "Leak of returned object")); } } return leakAtReturn.get(); @@ -2464,7 +2546,7 @@ public: } void printState(raw_ostream &Out, ProgramStateRef State, - const char *NL, const char *Sep) const; + const char *NL, const char *Sep) const override; void checkBind(SVal loc, SVal val, const Stmt *S, CheckerContext &C) const; void checkPostStmt(const BlockExpr *BE, CheckerContext &C) const; @@ -2474,6 +2556,8 @@ public: void checkPostStmt(const ObjCDictionaryLiteral *DL, CheckerContext &C) const; void checkPostStmt(const ObjCBoxedExpr *BE, CheckerContext &C) const; + void checkPostStmt(const ObjCIvarRefExpr *IRE, CheckerContext &C) const; + void checkPostCall(const CallEvent &Call, CheckerContext &C) const; void checkSummary(const RetainSummary &Summ, const CallEvent &Call, @@ -2531,7 +2615,7 @@ public: ExplodedNode *processLeaks(ProgramStateRef state, SmallVectorImpl<SymbolRef> &Leaked, CheckerContext &Ctx, - ExplodedNode *Pred = 0) const; + ExplodedNode *Pred = nullptr) const; }; } // end anonymous namespace @@ -2542,7 +2626,7 @@ public: StopTrackingCallback(ProgramStateRef st) : state(st) {} ProgramStateRef getState() const { return state; } - bool VisitSymbol(SymbolRef sym) { + bool VisitSymbol(SymbolRef sym) override { state = state->remove<RefBindings>(sym); return true; } @@ -2691,6 +2775,20 @@ void RetainCountChecker::checkPostStmt(const ObjCBoxedExpr *Ex, C.addTransition(State); } +void RetainCountChecker::checkPostStmt(const ObjCIvarRefExpr *IRE, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + // If an instance variable was previously accessed through a property, + // it may have a synthesized refcount of +0. Override right now that we're + // doing direct access. + if (Optional<Loc> IVarLoc = C.getSVal(IRE).getAs<Loc>()) + if (SymbolRef Sym = State->getSVal(*IVarLoc).getAsSymbol()) + if (const RefVal *RV = getRefBinding(State, Sym)) + if (RV->isOverridable()) + State = removeRefBinding(State, Sym); + C.addTransition(State); +} + void RetainCountChecker::checkPostCall(const CallEvent &Call, CheckerContext &C) const { RetainSummaryManager &Summaries = getSummaryManager(C); @@ -2731,6 +2829,16 @@ static QualType GetReturnType(const Expr *RetE, ASTContext &Ctx) { return RetTy; } +static bool wasSynthesizedProperty(const ObjCMethodCall *Call, + ExplodedNode *N) { + if (!Call || !Call->getDecl()->isPropertyAccessor()) + return false; + + CallExitEnd PP = N->getLocation().castAs<CallExitEnd>(); + const StackFrameContext *Frame = PP.getCalleeContext(); + return Frame->getAnalysisDeclContext()->isBodyAutosynthesized(); +} + // We don't always get the exact modeling of the function with regards to the // retain count checker even when the function is inlined. For example, we need // to stop tracking the symbols which were marked with StopTrackingHard. @@ -2765,6 +2873,19 @@ void RetainCountChecker::processSummaryOfInlined(const RetainSummary &Summ, SymbolRef Sym = CallOrMsg.getReturnValue().getAsSymbol(); if (Sym) state = removeRefBinding(state, Sym); + } else if (RE.getKind() == RetEffect::NotOwnedSymbol) { + if (wasSynthesizedProperty(MsgInvocation, C.getPredecessor())) { + // Believe the summary if we synthesized the body of a property getter + // and the return value is currently untracked. If the corresponding + // instance variable is later accessed directly, however, we're going to + // want to override this state, so that the owning object can perform + // reference counting operations on its own ivars. + SymbolRef Sym = CallOrMsg.getReturnValue().getAsSymbol(); + if (Sym && !getRefBinding(state, Sym)) + state = setRefBinding(state, Sym, + RefVal::makeOverridableNotOwned(RE.getObjKind(), + Sym->getType())); + } } C.addTransition(state); @@ -2778,7 +2899,7 @@ void RetainCountChecker::checkSummary(const RetainSummary &Summ, // Evaluate the effect of the arguments. RefVal::Kind hasErr = (RefVal::Kind) 0; SourceRange ErrorRange; - SymbolRef ErrorSym = 0; + SymbolRef ErrorSym = nullptr; for (unsigned idx = 0, e = CallOrMsg.getNumArgs(); idx != e; ++idx) { SVal V = CallOrMsg.getArgSVal(idx); @@ -2857,7 +2978,6 @@ void RetainCountChecker::checkSummary(const RetainSummary &Summ, } case RetEffect::GCNotOwnedSymbol: - case RetEffect::ARCNotOwnedSymbol: case RetEffect::NotOwnedSymbol: { const Expr *Ex = CallOrMsg.getOriginExpr(); SymbolRef Sym = CallOrMsg.getReturnValue().getAsSymbol(); @@ -3053,22 +3173,22 @@ void RetainCountChecker::processNonLeakError(ProgramStateRef St, llvm_unreachable("Unhandled error."); case RefVal::ErrorUseAfterRelease: if (!useAfterRelease) - useAfterRelease.reset(new UseAfterRelease()); + useAfterRelease.reset(new UseAfterRelease(this)); BT = &*useAfterRelease; break; case RefVal::ErrorReleaseNotOwned: if (!releaseNotOwned) - releaseNotOwned.reset(new BadRelease()); + releaseNotOwned.reset(new BadRelease(this)); BT = &*releaseNotOwned; break; case RefVal::ErrorDeallocGC: if (!deallocGC) - deallocGC.reset(new DeallocGC()); + deallocGC.reset(new DeallocGC(this)); BT = &*deallocGC; break; case RefVal::ErrorDeallocNotOwned: if (!deallocNotOwned) - deallocNotOwned.reset(new DeallocNotOwned()); + deallocNotOwned.reset(new DeallocNotOwned(this)); BT = &*deallocNotOwned; break; } @@ -3135,7 +3255,7 @@ bool RetainCountChecker::evalCall(const CallExpr *CE, CheckerContext &C) const { if (RetVal.isUnknown()) { // If the receiver is unknown, conjure a return value. SValBuilder &SVB = C.getSValBuilder(); - RetVal = SVB.conjureSymbolVal(0, CE, LCtx, ResultTy, C.blockCount()); + RetVal = SVB.conjureSymbolVal(nullptr, CE, LCtx, ResultTy, C.blockCount()); } state = state->BindExpr(CE, LCtx, RetVal, false); @@ -3144,7 +3264,7 @@ bool RetainCountChecker::evalCall(const CallExpr *CE, CheckerContext &C) const { if (const MemRegion *ArgRegion = RetVal.getAsRegion()) { // Save the refcount status of the argument. SymbolRef Sym = RetVal.getAsLocSymbol(); - const RefVal *Binding = 0; + const RefVal *Binding = nullptr; if (Sym) Binding = getRefBinding(state, Sym); @@ -3232,8 +3352,7 @@ void RetainCountChecker::checkPreStmt(const ReturnStmt *S, return; // Update the autorelease counts. - static SimpleProgramPointTag - AutoreleaseTag("RetainCountChecker : Autorelease"); + static CheckerProgramPointTag AutoreleaseTag(this, "Autorelease"); state = handleAutoreleaseCounts(state, Pred, &AutoreleaseTag, C, Sym, X); // Did we cache out? @@ -3294,8 +3413,7 @@ void RetainCountChecker::checkReturnWithRetEffect(const ReturnStmt *S, // Generate an error node. state = setRefBinding(state, Sym, X); - static SimpleProgramPointTag - ReturnOwnLeakTag("RetainCountChecker : ReturnsOwnLeak"); + static CheckerProgramPointTag ReturnOwnLeakTag(this, "ReturnsOwnLeak"); ExplodedNode *N = C.addTransition(state, Pred, &ReturnOwnLeakTag); if (N) { const LangOptions &LOpts = C.getASTContext().getLangOpts(); @@ -3315,12 +3433,12 @@ void RetainCountChecker::checkReturnWithRetEffect(const ReturnStmt *S, // owned object. state = setRefBinding(state, Sym, X ^ RefVal::ErrorReturnedNotOwned); - static SimpleProgramPointTag - ReturnNotOwnedTag("RetainCountChecker : ReturnNotOwnedForOwned"); + static CheckerProgramPointTag ReturnNotOwnedTag(this, + "ReturnNotOwnedForOwned"); ExplodedNode *N = C.addTransition(state, Pred, &ReturnNotOwnedTag); if (N) { if (!returnNotOwnedForOwned) - returnNotOwnedForOwned.reset(new ReturnedNotOwnedForOwned()); + returnNotOwnedForOwned.reset(new ReturnedNotOwnedForOwned(this)); CFRefReport *report = new CFRefReport(*returnNotOwnedForOwned, @@ -3375,7 +3493,7 @@ void RetainCountChecker::checkBind(SVal loc, SVal val, const Stmt *S, // false positives. if (const VarRegion *LVR = dyn_cast_or_null<VarRegion>(loc.getAsRegion())) { const VarDecl *VD = LVR->getDecl(); - if (VD->getAttr<CleanupAttr>()) { + if (VD->hasAttr<CleanupAttr>()) { escapes = true; } } @@ -3508,7 +3626,7 @@ RetainCountChecker::handleAutoreleaseCounts(ProgramStateRef state, os << "has a +" << V.getCount() << " retain count"; if (!overAutorelease) - overAutorelease.reset(new OverAutorelease()); + overAutorelease.reset(new OverAutorelease(this)); const LangOptions &LOpts = Ctx.getASTContext().getLangOpts(); CFRefReport *report = @@ -3517,7 +3635,7 @@ RetainCountChecker::handleAutoreleaseCounts(ProgramStateRef state, Ctx.emitReport(report); } - return 0; + return nullptr; } ProgramStateRef @@ -3578,7 +3696,7 @@ void RetainCountChecker::checkEndFunction(CheckerContext &Ctx) const { } for (RefBindingsTy::iterator I = B.begin(), E = B.end(); I != E; ++I) { - state = handleAutoreleaseCounts(state, Pred, /*Tag=*/0, Ctx, + state = handleAutoreleaseCounts(state, Pred, /*Tag=*/nullptr, Ctx, I->first, I->second); if (!state) return; @@ -3602,13 +3720,13 @@ void RetainCountChecker::checkEndFunction(CheckerContext &Ctx) const { const ProgramPointTag * RetainCountChecker::getDeadSymbolTag(SymbolRef sym) const { - const SimpleProgramPointTag *&tag = DeadSymbolTags[sym]; + const CheckerProgramPointTag *&tag = DeadSymbolTags[sym]; if (!tag) { SmallString<64> buf; llvm::raw_svector_ostream out(buf); - out << "RetainCountChecker : Dead Symbol : "; + out << "Dead Symbol : "; sym->dumpToStream(out); - tag = new SimpleProgramPointTag(out.str()); + tag = new CheckerProgramPointTag(this, out.str()); } return tag; } |