forked from OSchip/llvm-project
399 lines
14 KiB
C++
399 lines
14 KiB
C++
//==--- RetainCountChecker.h - Checks for leaks and other issues -*- C++ -*--//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This file defines the methods for RetainCountChecker, which implements
|
|
// a reference count checker for Core Foundation and Cocoa on (Mac OS X).
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_RETAINCOUNTCHECKER_H
|
|
#define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_RETAINCOUNTCHECKER_H
|
|
|
|
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
|
|
#include "RetainCountDiagnostics.h"
|
|
#include "clang/AST/Attr.h"
|
|
#include "clang/AST/DeclCXX.h"
|
|
#include "clang/AST/DeclObjC.h"
|
|
#include "clang/AST/ParentMap.h"
|
|
#include "clang/Analysis/DomainSpecific/CocoaConventions.h"
|
|
#include "clang/Analysis/RetainSummaryManager.h"
|
|
#include "clang/Basic/LangOptions.h"
|
|
#include "clang/Basic/SourceManager.h"
|
|
#include "clang/Analysis/SelectorExtras.h"
|
|
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
|
|
#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h"
|
|
#include "clang/StaticAnalyzer/Core/Checker.h"
|
|
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
|
|
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
|
|
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
|
|
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
|
|
#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
|
|
#include "llvm/ADT/DenseMap.h"
|
|
#include "llvm/ADT/FoldingSet.h"
|
|
#include "llvm/ADT/ImmutableList.h"
|
|
#include "llvm/ADT/ImmutableMap.h"
|
|
#include "llvm/ADT/STLExtras.h"
|
|
#include "llvm/ADT/SmallString.h"
|
|
#include "llvm/ADT/StringExtras.h"
|
|
#include <cstdarg>
|
|
#include <utility>
|
|
|
|
namespace clang {
|
|
namespace ento {
|
|
namespace retaincountchecker {
|
|
|
|
/// Metadata on reference.
|
|
class RefVal {
|
|
public:
|
|
enum Kind {
|
|
Owned = 0, // Owning reference.
|
|
NotOwned, // Reference is not owned by still valid (not freed).
|
|
Released, // Object has been released.
|
|
ReturnedOwned, // Returned object passes ownership to caller.
|
|
ReturnedNotOwned, // Return object does not pass ownership to caller.
|
|
ERROR_START,
|
|
ErrorDeallocNotOwned, // -dealloc called on non-owned object.
|
|
ErrorUseAfterRelease, // Object used after released.
|
|
ErrorReleaseNotOwned, // Release of an object that was not owned.
|
|
ERROR_LEAK_START,
|
|
ErrorLeak, // A memory leak due to excessive reference counts.
|
|
ErrorLeakReturned, // A memory leak due to the returning method not having
|
|
// the correct naming conventions.
|
|
ErrorOverAutorelease,
|
|
ErrorReturnedNotOwned
|
|
};
|
|
|
|
/// Tracks how an object referenced by an ivar has been used.
|
|
///
|
|
/// This accounts for us not knowing if an arbitrary ivar is supposed to be
|
|
/// stored at +0 or +1.
|
|
enum class IvarAccessHistory {
|
|
None,
|
|
AccessedDirectly,
|
|
ReleasedAfterDirectAccess
|
|
};
|
|
|
|
private:
|
|
/// 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;
|
|
|
|
/// 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 or OSObject), if known.
|
|
///
|
|
/// See the ObjKind enum for possible values.
|
|
unsigned RawObjectKind : 3;
|
|
|
|
/// 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 RawIvarAccessHistory : 2;
|
|
|
|
RefVal(Kind k, ObjKind o, unsigned cnt, unsigned acnt, QualType t,
|
|
IvarAccessHistory IvarAccess)
|
|
: Cnt(cnt), ACnt(acnt), T(t), RawKind(static_cast<unsigned>(k)),
|
|
RawObjectKind(static_cast<unsigned>(o)),
|
|
RawIvarAccessHistory(static_cast<unsigned>(IvarAccess)) {
|
|
assert(getKind() == k && "not enough bits for the kind");
|
|
assert(getObjKind() == o && "not enough bits for the object kind");
|
|
assert(getIvarAccessHistory() == IvarAccess && "not enough bits");
|
|
}
|
|
|
|
public:
|
|
Kind getKind() const { return static_cast<Kind>(RawKind); }
|
|
|
|
ObjKind getObjKind() const {
|
|
return static_cast<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;
|
|
}
|
|
|
|
QualType getType() const { return T; }
|
|
|
|
/// Returns what the analyzer knows about direct accesses to a particular
|
|
/// instance variable.
|
|
///
|
|
/// If the object with this refcount wasn't originally from an Objective-C
|
|
/// ivar region, this should always return IvarAccessHistory::None.
|
|
IvarAccessHistory getIvarAccessHistory() const {
|
|
return static_cast<IvarAccessHistory>(RawIvarAccessHistory);
|
|
}
|
|
|
|
bool isOwned() const {
|
|
return getKind() == Owned;
|
|
}
|
|
|
|
bool isNotOwned() const {
|
|
return getKind() == NotOwned;
|
|
}
|
|
|
|
bool isReturnedOwned() const {
|
|
return getKind() == ReturnedOwned;
|
|
}
|
|
|
|
bool isReturnedNotOwned() const {
|
|
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(ObjKind o, QualType t) {
|
|
return RefVal(Owned, o, /*Count=*/1, 0, t, IvarAccessHistory::None);
|
|
}
|
|
|
|
/// 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(ObjKind o, QualType t) {
|
|
return RefVal(NotOwned, o, /*Count=*/0, 0, t, IvarAccessHistory::None);
|
|
}
|
|
|
|
RefVal operator-(size_t i) const {
|
|
return RefVal(getKind(), getObjKind(), getCount() - i,
|
|
getAutoreleaseCount(), getType(), getIvarAccessHistory());
|
|
}
|
|
|
|
RefVal operator+(size_t i) const {
|
|
return RefVal(getKind(), getObjKind(), getCount() + i,
|
|
getAutoreleaseCount(), getType(), getIvarAccessHistory());
|
|
}
|
|
|
|
RefVal operator^(Kind k) const {
|
|
return RefVal(k, getObjKind(), getCount(), getAutoreleaseCount(),
|
|
getType(), getIvarAccessHistory());
|
|
}
|
|
|
|
RefVal autorelease() const {
|
|
return RefVal(getKind(), getObjKind(), getCount(), getAutoreleaseCount()+1,
|
|
getType(), getIvarAccessHistory());
|
|
}
|
|
|
|
RefVal withIvarAccess() const {
|
|
assert(getIvarAccessHistory() == IvarAccessHistory::None);
|
|
return RefVal(getKind(), getObjKind(), getCount(), getAutoreleaseCount(),
|
|
getType(), IvarAccessHistory::AccessedDirectly);
|
|
}
|
|
|
|
RefVal releaseViaIvar() const {
|
|
assert(getIvarAccessHistory() == IvarAccessHistory::AccessedDirectly);
|
|
return RefVal(getKind(), getObjKind(), getCount(), getAutoreleaseCount(),
|
|
getType(), IvarAccessHistory::ReleasedAfterDirectAccess);
|
|
}
|
|
|
|
// Comparison, profiling, and pretty-printing.
|
|
bool hasSameState(const RefVal &X) const {
|
|
return getKind() == X.getKind() && Cnt == X.Cnt && ACnt == X.ACnt &&
|
|
getIvarAccessHistory() == X.getIvarAccessHistory();
|
|
}
|
|
|
|
bool operator==(const RefVal& X) const {
|
|
return T == X.T && hasSameState(X) && getObjKind() == X.getObjKind();
|
|
}
|
|
|
|
void Profile(llvm::FoldingSetNodeID& ID) const {
|
|
ID.Add(T);
|
|
ID.AddInteger(RawKind);
|
|
ID.AddInteger(Cnt);
|
|
ID.AddInteger(ACnt);
|
|
ID.AddInteger(RawObjectKind);
|
|
ID.AddInteger(RawIvarAccessHistory);
|
|
}
|
|
|
|
void print(raw_ostream &Out) const;
|
|
};
|
|
|
|
class RetainCountChecker
|
|
: public Checker< check::Bind,
|
|
check::DeadSymbols,
|
|
check::BeginFunction,
|
|
check::EndFunction,
|
|
check::PostStmt<BlockExpr>,
|
|
check::PostStmt<CastExpr>,
|
|
check::PostStmt<ObjCArrayLiteral>,
|
|
check::PostStmt<ObjCDictionaryLiteral>,
|
|
check::PostStmt<ObjCBoxedExpr>,
|
|
check::PostStmt<ObjCIvarRefExpr>,
|
|
check::PostCall,
|
|
check::RegionChanges,
|
|
eval::Assume,
|
|
eval::Call > {
|
|
|
|
RefCountBug useAfterRelease{this, RefCountBug::UseAfterRelease};
|
|
RefCountBug releaseNotOwned{this, RefCountBug::ReleaseNotOwned};
|
|
RefCountBug deallocNotOwned{this, RefCountBug::DeallocNotOwned};
|
|
RefCountBug freeNotOwned{this, RefCountBug::FreeNotOwned};
|
|
RefCountBug overAutorelease{this, RefCountBug::OverAutorelease};
|
|
RefCountBug returnNotOwnedForOwned{this, RefCountBug::ReturnNotOwnedForOwned};
|
|
RefCountBug leakWithinFunction{this, RefCountBug::LeakWithinFunction};
|
|
RefCountBug leakAtReturn{this, RefCountBug::LeakAtReturn};
|
|
|
|
CheckerProgramPointTag DeallocSentTag{this, "DeallocSent"};
|
|
CheckerProgramPointTag CastFailTag{this, "DynamicCastFail"};
|
|
|
|
mutable std::unique_ptr<RetainSummaryManager> Summaries;
|
|
public:
|
|
|
|
/// Track Objective-C and CoreFoundation objects.
|
|
bool TrackObjCAndCFObjects = false;
|
|
|
|
/// Track sublcasses of OSObject.
|
|
bool TrackOSObjects = false;
|
|
|
|
/// Track initial parameters (for the entry point) for NS/CF objects.
|
|
bool TrackNSCFStartParam = false;
|
|
|
|
RetainCountChecker() {};
|
|
|
|
RetainSummaryManager &getSummaryManager(ASTContext &Ctx) const {
|
|
if (!Summaries)
|
|
Summaries.reset(
|
|
new RetainSummaryManager(Ctx, TrackObjCAndCFObjects, TrackOSObjects));
|
|
return *Summaries;
|
|
}
|
|
|
|
RetainSummaryManager &getSummaryManager(CheckerContext &C) const {
|
|
return getSummaryManager(C.getASTContext());
|
|
}
|
|
|
|
void printState(raw_ostream &Out, ProgramStateRef State,
|
|
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;
|
|
void checkPostStmt(const CastExpr *CE, CheckerContext &C) const;
|
|
|
|
void checkPostStmt(const ObjCArrayLiteral *AL, CheckerContext &C) const;
|
|
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,
|
|
CheckerContext &C) const;
|
|
|
|
void processSummaryOfInlined(const RetainSummary &Summ,
|
|
const CallEvent &Call,
|
|
CheckerContext &C) const;
|
|
|
|
bool evalCall(const CallExpr *CE, CheckerContext &C) const;
|
|
|
|
ProgramStateRef evalAssume(ProgramStateRef state, SVal Cond,
|
|
bool Assumption) const;
|
|
|
|
ProgramStateRef
|
|
checkRegionChanges(ProgramStateRef state,
|
|
const InvalidatedSymbols *invalidated,
|
|
ArrayRef<const MemRegion *> ExplicitRegions,
|
|
ArrayRef<const MemRegion *> Regions,
|
|
const LocationContext* LCtx,
|
|
const CallEvent *Call) const;
|
|
|
|
ExplodedNode* checkReturnWithRetEffect(const ReturnStmt *S, CheckerContext &C,
|
|
ExplodedNode *Pred, RetEffect RE, RefVal X,
|
|
SymbolRef Sym, ProgramStateRef state) const;
|
|
|
|
void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
|
|
void checkBeginFunction(CheckerContext &C) const;
|
|
void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const;
|
|
|
|
ProgramStateRef updateSymbol(ProgramStateRef state, SymbolRef sym,
|
|
RefVal V, ArgEffect E, RefVal::Kind &hasErr,
|
|
CheckerContext &C) const;
|
|
|
|
const RefCountBug &errorKindToBugKind(RefVal::Kind ErrorKind,
|
|
SymbolRef Sym) const;
|
|
|
|
void processNonLeakError(ProgramStateRef St, SourceRange ErrorRange,
|
|
RefVal::Kind ErrorKind, SymbolRef Sym,
|
|
CheckerContext &C) const;
|
|
|
|
void processObjCLiterals(CheckerContext &C, const Expr *Ex) const;
|
|
|
|
ProgramStateRef handleSymbolDeath(ProgramStateRef state,
|
|
SymbolRef sid, RefVal V,
|
|
SmallVectorImpl<SymbolRef> &Leaked) const;
|
|
|
|
ProgramStateRef
|
|
handleAutoreleaseCounts(ProgramStateRef state, ExplodedNode *Pred,
|
|
const ProgramPointTag *Tag, CheckerContext &Ctx,
|
|
SymbolRef Sym,
|
|
RefVal V,
|
|
const ReturnStmt *S=nullptr) const;
|
|
|
|
ExplodedNode *processLeaks(ProgramStateRef state,
|
|
SmallVectorImpl<SymbolRef> &Leaked,
|
|
CheckerContext &Ctx,
|
|
ExplodedNode *Pred = nullptr) const;
|
|
|
|
const CheckerProgramPointTag &getDeallocSentTag() const {
|
|
return DeallocSentTag;
|
|
}
|
|
|
|
const CheckerProgramPointTag &getCastFailTag() const {
|
|
return CastFailTag;
|
|
}
|
|
|
|
private:
|
|
/// Perform the necessary checks and state adjustments at the end of the
|
|
/// function.
|
|
/// \p S Return statement, may be null.
|
|
ExplodedNode * processReturn(const ReturnStmt *S, CheckerContext &C) const;
|
|
};
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// RefBindings - State used to track object reference counts.
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
const RefVal *getRefBinding(ProgramStateRef State, SymbolRef Sym);
|
|
|
|
/// Returns true if this stack frame is for an Objective-C method that is a
|
|
/// property getter or setter whose body has been synthesized by the analyzer.
|
|
inline bool isSynthesizedAccessor(const StackFrameContext *SFC) {
|
|
auto Method = dyn_cast_or_null<ObjCMethodDecl>(SFC->getDecl());
|
|
if (!Method || !Method->isPropertyAccessor())
|
|
return false;
|
|
|
|
return SFC->getAnalysisDeclContext()->isBodyAutosynthesized();
|
|
}
|
|
|
|
} // end namespace retaincountchecker
|
|
} // end namespace ento
|
|
} // end namespace clang
|
|
|
|
#endif
|