aboutsummaryrefslogtreecommitdiff
path: root/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp')
-rw-r--r--lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp434
1 files changed, 276 insertions, 158 deletions
diff --git a/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp b/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp
index c474e78310fa..eb699d694087 100644
--- a/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp
+++ b/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;
}