Skip to content

[NFC][analyzer] Conversion to CheckerFamily: RetainCountChecker #152138

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -840,20 +840,27 @@ ProgramStateRef RetainCountChecker::updateSymbol(ProgramStateRef state,
const RefCountBug &
RetainCountChecker::errorKindToBugKind(RefVal::Kind ErrorKind,
SymbolRef Sym) const {
const RefCountFrontend &FE = getPreferredFrontend();

switch (ErrorKind) {
case RefVal::ErrorUseAfterRelease:
return *UseAfterRelease;
return FE.UseAfterRelease;
case RefVal::ErrorReleaseNotOwned:
return *ReleaseNotOwned;
return FE.ReleaseNotOwned;
case RefVal::ErrorDeallocNotOwned:
if (Sym->getType()->getPointeeCXXRecordDecl())
return *FreeNotOwned;
return *DeallocNotOwned;
return FE.FreeNotOwned;
return FE.DeallocNotOwned;
default:
llvm_unreachable("Unhandled error.");
}
}

bool RetainCountChecker::isReleaseUnownedError(RefVal::Kind ErrorKind) const {
return ErrorKind == RefVal::ErrorReleaseNotOwned ||
ErrorKind == RefVal::ErrorDeallocNotOwned;
}

void RetainCountChecker::processNonLeakError(ProgramStateRef St,
SourceRange ErrorRange,
RefVal::Kind ErrorKind,
Expand All @@ -874,8 +881,8 @@ void RetainCountChecker::processNonLeakError(ProgramStateRef St,
return;

auto report = std::make_unique<RefCountReport>(
errorKindToBugKind(ErrorKind, Sym),
C.getASTContext().getLangOpts(), N, Sym);
errorKindToBugKind(ErrorKind, Sym), C.getASTContext().getLangOpts(), N,
Sym, /*isLeak=*/false, isReleaseUnownedError(ErrorKind));
report->addRange(ErrorRange);
C.emitReport(std::move(report));
}
Expand Down Expand Up @@ -1090,8 +1097,8 @@ ExplodedNode * RetainCountChecker::checkReturnWithRetEffect(const ReturnStmt *S,
ExplodedNode *N = C.addTransition(state, Pred);
if (N) {
const LangOptions &LOpts = C.getASTContext().getLangOpts();
auto R =
std::make_unique<RefLeakReport>(*LeakAtReturn, LOpts, N, Sym, C);
auto R = std::make_unique<RefLeakReport>(
getPreferredFrontend().LeakAtReturn, LOpts, N, Sym, C);
C.emitReport(std::move(R));
}
return N;
Expand All @@ -1113,7 +1120,8 @@ ExplodedNode * RetainCountChecker::checkReturnWithRetEffect(const ReturnStmt *S,
ExplodedNode *N = C.addTransition(state, Pred);
if (N) {
auto R = std::make_unique<RefCountReport>(
*ReturnNotOwnedForOwned, C.getASTContext().getLangOpts(), N, Sym);
getPreferredFrontend().ReturnNotOwnedForOwned,
C.getASTContext().getLangOpts(), N, Sym);
C.emitReport(std::move(R));
}
return N;
Expand Down Expand Up @@ -1261,8 +1269,8 @@ ProgramStateRef RetainCountChecker::handleAutoreleaseCounts(
os << "has a +" << V.getCount() << " retain count";

const LangOptions &LOpts = Ctx.getASTContext().getLangOpts();
auto R = std::make_unique<RefCountReport>(*OverAutorelease, LOpts, N, Sym,
os.str());
auto R = std::make_unique<RefCountReport>(
getPreferredFrontend().OverAutorelease, LOpts, N, Sym, os.str());
Ctx.emitReport(std::move(R));
}

Expand Down Expand Up @@ -1307,8 +1315,10 @@ RetainCountChecker::processLeaks(ProgramStateRef state,
const LangOptions &LOpts = Ctx.getASTContext().getLangOpts();

if (N) {
const RefCountFrontend &FE = getPreferredFrontend();
const RefCountBug &BT = Pred ? FE.LeakWithinFunction : FE.LeakAtReturn;

for (SymbolRef L : Leaked) {
const RefCountBug &BT = Pred ? *LeakWithinFunction : *LeakAtReturn;
Ctx.emitReport(std::make_unique<RefLeakReport>(BT, LOpts, N, L, Ctx));
}
}
Expand Down Expand Up @@ -1463,44 +1473,31 @@ std::unique_ptr<SimpleProgramPointTag> RetainCountChecker::DeallocSentTag;
std::unique_ptr<SimpleProgramPointTag> RetainCountChecker::CastFailTag;

void ento::registerRetainCountBase(CheckerManager &Mgr) {
auto *Chk = Mgr.registerChecker<RetainCountChecker>();
auto *Chk = Mgr.getChecker<RetainCountChecker>();
Chk->DeallocSentTag = std::make_unique<SimpleProgramPointTag>(
"RetainCountChecker", "DeallocSent");
Chk->CastFailTag = std::make_unique<SimpleProgramPointTag>(
"RetainCountChecker", "DynamicCastFail");
}

bool ento::shouldRegisterRetainCountBase(const CheckerManager &mgr) {
bool ento::shouldRegisterRetainCountBase(const CheckerManager &) {
return true;
}

void ento::registerRetainCountChecker(CheckerManager &Mgr) {
auto *Chk = Mgr.getChecker<RetainCountChecker>();
Chk->TrackObjCAndCFObjects = true;
Chk->RetainCount.enable(Mgr);
Chk->TrackNSCFStartParam = Mgr.getAnalyzerOptions().getCheckerBooleanOption(
Mgr.getCurrentCheckerName(), "TrackNSCFStartParam");

#define INIT_BUGTYPE(KIND) \
Chk->KIND = std::make_unique<RefCountBug>(Mgr.getCurrentCheckerName(), \
RefCountBug::KIND);
// TODO: Ideally, we should have a checker for each of these bug types.
INIT_BUGTYPE(UseAfterRelease)
INIT_BUGTYPE(ReleaseNotOwned)
INIT_BUGTYPE(DeallocNotOwned)
INIT_BUGTYPE(FreeNotOwned)
INIT_BUGTYPE(OverAutorelease)
INIT_BUGTYPE(ReturnNotOwnedForOwned)
INIT_BUGTYPE(LeakWithinFunction)
INIT_BUGTYPE(LeakAtReturn)
#undef INIT_BUGTYPE
}

bool ento::shouldRegisterRetainCountChecker(const CheckerManager &mgr) {
bool ento::shouldRegisterRetainCountChecker(const CheckerManager &) {
return true;
}

void ento::registerOSObjectRetainCountChecker(CheckerManager &Mgr) {
auto *Chk = Mgr.getChecker<RetainCountChecker>();
Chk->TrackOSObjects = true;
Chk->OSObjectRetainCount.enable(Mgr);

// FIXME: We want bug reports to always have the same checker name associated
// with them, yet here, if RetainCountChecker is disabled but
Expand All @@ -1511,21 +1508,8 @@ void ento::registerOSObjectRetainCountChecker(CheckerManager &Mgr) {
// diagnostics, and **hidden checker options** with the fine-tuning of
// modeling. Following this logic, OSObjectRetainCountChecker should be the
// latter, but we can't just remove it for backward compatibility reasons.
#define LAZY_INIT_BUGTYPE(KIND) \
if (!Chk->KIND) \
Chk->KIND = std::make_unique<RefCountBug>(Mgr.getCurrentCheckerName(), \
RefCountBug::KIND);
LAZY_INIT_BUGTYPE(UseAfterRelease)
LAZY_INIT_BUGTYPE(ReleaseNotOwned)
LAZY_INIT_BUGTYPE(DeallocNotOwned)
LAZY_INIT_BUGTYPE(FreeNotOwned)
LAZY_INIT_BUGTYPE(OverAutorelease)
LAZY_INIT_BUGTYPE(ReturnNotOwnedForOwned)
LAZY_INIT_BUGTYPE(LeakWithinFunction)
LAZY_INIT_BUGTYPE(LeakAtReturn)
#undef LAZY_INIT_BUGTYPE
}

bool ento::shouldRegisterOSObjectRetainCountChecker(const CheckerManager &mgr) {
bool ento::shouldRegisterOSObjectRetainCountChecker(const CheckerManager &) {
return true;
}
Original file line number Diff line number Diff line change
Expand Up @@ -235,58 +235,48 @@ class RefVal {
};

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 > {
: public CheckerFamily<
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> {

public:
std::unique_ptr<RefCountBug> UseAfterRelease;
std::unique_ptr<RefCountBug> ReleaseNotOwned;
std::unique_ptr<RefCountBug> DeallocNotOwned;
std::unique_ptr<RefCountBug> FreeNotOwned;
std::unique_ptr<RefCountBug> OverAutorelease;
std::unique_ptr<RefCountBug> ReturnNotOwnedForOwned;
std::unique_ptr<RefCountBug> LeakWithinFunction;
std::unique_ptr<RefCountBug> LeakAtReturn;
RefCountFrontend RetainCount;
RefCountFrontend OSObjectRetainCount;

mutable std::unique_ptr<RetainSummaryManager> Summaries;

static std::unique_ptr<SimpleProgramPointTag> DeallocSentTag;
static std::unique_ptr<SimpleProgramPointTag> CastFailTag;

/// 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() {};
StringRef getDebugTag() const override { return "RetainCountChecker"; }

RetainSummaryManager &getSummaryManager(ASTContext &Ctx) const {
if (!Summaries)
Summaries.reset(
new RetainSummaryManager(Ctx, TrackObjCAndCFObjects, TrackOSObjects));
Summaries = std::make_unique<RetainSummaryManager>(
Ctx, RetainCount.isEnabled(), OSObjectRetainCount.isEnabled());
return *Summaries;
}

RetainSummaryManager &getSummaryManager(CheckerContext &C) const {
return getSummaryManager(C.getASTContext());
}

const RefCountFrontend &getPreferredFrontend() const {
// FIXME: The two frontends of this checker family are in an unusual
// relationship: if they are both enabled, then all bug reports are
// reported by RetainCount (i.e. `osx.cocoa.RetainCount`), even the bugs
// that "belong to" OSObjectRetainCount (i.e. `osx.OSObjectRetainCount`).
// This is counter-intuitive and should be fixed to avoid confusion.
return RetainCount.isEnabled() ? RetainCount : OSObjectRetainCount;
}

void printState(raw_ostream &Out, ProgramStateRef State,
const char *NL, const char *Sep) const override;

Expand Down Expand Up @@ -337,6 +327,8 @@ class RetainCountChecker
const RefCountBug &errorKindToBugKind(RefVal::Kind ErrorKind,
SymbolRef Sym) const;

bool isReleaseUnownedError(RefVal::Kind ErrorKind) const;

void processNonLeakError(ProgramStateRef St, SourceRange ErrorRange,
RefVal::Kind ErrorKind, SymbolRef Sym,
CheckerContext &C) const;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,57 +21,6 @@ using namespace clang;
using namespace ento;
using namespace retaincountchecker;

StringRef RefCountBug::bugTypeToName(RefCountBug::RefCountBugKind BT) {
switch (BT) {
case UseAfterRelease:
return "Use-after-release";
case ReleaseNotOwned:
return "Bad release";
case DeallocNotOwned:
return "-dealloc sent to non-exclusively owned object";
case FreeNotOwned:
return "freeing non-exclusively owned object";
case OverAutorelease:
return "Object autoreleased too many times";
case ReturnNotOwnedForOwned:
return "Method should return an owned object";
case LeakWithinFunction:
return "Leak";
case LeakAtReturn:
return "Leak of returned object";
}
llvm_unreachable("Unknown RefCountBugKind");
}

StringRef RefCountBug::getDescription() const {
switch (BT) {
case UseAfterRelease:
return "Reference-counted object is used after it is released";
case ReleaseNotOwned:
return "Incorrect decrement of the reference count of an object that is "
"not owned at this point by the caller";
case DeallocNotOwned:
return "-dealloc sent to object that may be referenced elsewhere";
case FreeNotOwned:
return "'free' called on an object that may be referenced elsewhere";
case OverAutorelease:
return "Object autoreleased too many times";
case ReturnNotOwnedForOwned:
return "Object with a +0 retain count returned to caller where a +1 "
"(owning) retain count is expected";
case LeakWithinFunction:
case LeakAtReturn:
return "";
}
llvm_unreachable("Unknown RefCountBugKind");
}

RefCountBug::RefCountBug(CheckerNameRef Checker, RefCountBugKind BT)
: BugType(Checker, bugTypeToName(BT), categories::MemoryRefCount,
/*SuppressOnSink=*/BT == LeakWithinFunction ||
BT == LeakAtReturn),
BT(BT) {}

static bool isNumericLiteralExpression(const Expr *E) {
// FIXME: This set of cases was copied from SemaExprObjC.
return isa<IntegerLiteral, CharacterLiteral, FloatingLiteral,
Expand Down Expand Up @@ -312,9 +261,11 @@ namespace retaincountchecker {
class RefCountReportVisitor : public BugReporterVisitor {
protected:
SymbolRef Sym;
bool IsReleaseUnowned;

public:
RefCountReportVisitor(SymbolRef sym) : Sym(sym) {}
RefCountReportVisitor(SymbolRef S, bool IRU)
: Sym(S), IsReleaseUnowned(IRU) {}

void Profile(llvm::FoldingSetNodeID &ID) const override {
static int x = 0;
Expand All @@ -334,7 +285,8 @@ class RefCountReportVisitor : public BugReporterVisitor {
class RefLeakReportVisitor : public RefCountReportVisitor {
public:
RefLeakReportVisitor(SymbolRef Sym, const MemRegion *LastBinding)
: RefCountReportVisitor(Sym), LastBinding(LastBinding) {}
: RefCountReportVisitor(Sym, /*IsReleaseUnowned=*/false),
LastBinding(LastBinding) {}

PathDiagnosticPieceRef getEndPath(BugReporterContext &BRC,
const ExplodedNode *N,
Expand Down Expand Up @@ -452,12 +404,6 @@ annotateStartParameter(const ExplodedNode *N, SymbolRef Sym,
PathDiagnosticPieceRef
RefCountReportVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &BRC,
PathSensitiveBugReport &BR) {

const auto &BT = static_cast<const RefCountBug&>(BR.getBugType());

bool IsFreeUnowned = BT.getBugType() == RefCountBug::FreeNotOwned ||
BT.getBugType() == RefCountBug::DeallocNotOwned;

const SourceManager &SM = BRC.getSourceManager();
CallEventManager &CEMgr = BRC.getStateManager().getCallEventManager();
if (auto CE = N->getLocationAs<CallExitBegin>())
Expand Down Expand Up @@ -490,7 +436,7 @@ RefCountReportVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &BRC,
std::string sbuf;
llvm::raw_string_ostream os(sbuf);

if (PrevT && IsFreeUnowned && CurrV.isNotOwned() && PrevT->isOwned()) {
if (PrevT && IsReleaseUnowned && CurrV.isNotOwned() && PrevT->isOwned()) {
os << "Object is now not exclusively owned";
auto Pos = PathDiagnosticLocation::create(N->getLocation(), SM);
return std::make_shared<PathDiagnosticEventPiece>(Pos, sbuf);
Expand Down Expand Up @@ -815,10 +761,8 @@ RefLeakReportVisitor::getEndPath(BugReporterContext &BRC,
if (K == ObjKind::ObjC || K == ObjKind::CF) {
os << "whose name ('" << *FD
<< "') does not contain 'Copy' or 'Create'. This violates the "
"naming"
" convention rules given in the Memory Management Guide for "
"Core"
" Foundation";
"naming convention rules given in the Memory Management Guide "
"for Core Foundation";
} else if (RV->getObjKind() == ObjKind::OS) {
std::string FuncName = FD->getNameAsString();
os << "whose name ('" << FuncName << "') starts with '"
Expand All @@ -836,19 +780,20 @@ RefLeakReportVisitor::getEndPath(BugReporterContext &BRC,
}

RefCountReport::RefCountReport(const RefCountBug &D, const LangOptions &LOpts,
ExplodedNode *n, SymbolRef sym, bool isLeak)
: PathSensitiveBugReport(D, D.getDescription(), n), Sym(sym),
ExplodedNode *n, SymbolRef sym, bool isLeak,
bool IsReleaseUnowned)
: PathSensitiveBugReport(D, D.getReportMessage(), n), Sym(sym),
isLeak(isLeak) {
if (!isLeak)
addVisitor<RefCountReportVisitor>(sym);
addVisitor<RefCountReportVisitor>(sym, IsReleaseUnowned);
}

RefCountReport::RefCountReport(const RefCountBug &D, const LangOptions &LOpts,
ExplodedNode *n, SymbolRef sym,
StringRef endText)
: PathSensitiveBugReport(D, D.getDescription(), endText, n) {
: PathSensitiveBugReport(D, D.getReportMessage(), endText, n) {

addVisitor<RefCountReportVisitor>(sym);
addVisitor<RefCountReportVisitor>(sym, /*IsReleaseUnowned=*/false);
}

void RefLeakReport::deriveParamLocation(CheckerContext &Ctx) {
Expand Down
Loading