946 lines · cpp
1// RetainCountDiagnostics.cpp - Checks for leaks and other issues -*- C++ -*--//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8//9// This file defines diagnostics for RetainCountChecker, which implements10// a reference count checker for Core Foundation and Cocoa on (Mac OS X).11//12//===----------------------------------------------------------------------===//13 14#include "RetainCountDiagnostics.h"15#include "RetainCountChecker.h"16#include "llvm/ADT/STLExtras.h"17#include "llvm/ADT/SmallVector.h"18#include <optional>19 20using namespace clang;21using namespace ento;22using namespace retaincountchecker;23 24static bool isNumericLiteralExpression(const Expr *E) {25 // FIXME: This set of cases was copied from SemaExprObjC.26 return isa<IntegerLiteral, CharacterLiteral, FloatingLiteral,27 ObjCBoolLiteralExpr, CXXBoolLiteralExpr>(E);28}29 30/// If type represents a pointer to CXXRecordDecl,31/// and is not a typedef, return the decl name.32/// Otherwise, return the serialization of type.33static std::string getPrettyTypeName(QualType QT) {34 QualType PT = QT->getPointeeType();35 if (!PT.isNull() && !QT->getAs<TypedefType>())36 if (const auto *RD = PT->getAsCXXRecordDecl())37 return std::string(RD->getName());38 return QT.getAsString();39}40 41/// Write information about the type state change to @c os,42/// return whether the note should be generated.43static bool shouldGenerateNote(llvm::raw_string_ostream &os,44 const RefVal *PrevT,45 const RefVal &CurrV,46 bool DeallocSent) {47 // Get the previous type state.48 RefVal PrevV = *PrevT;49 50 // Specially handle -dealloc.51 if (DeallocSent) {52 // Determine if the object's reference count was pushed to zero.53 assert(!PrevV.hasSameState(CurrV) && "The state should have changed.");54 // We may not have transitioned to 'release' if we hit an error.55 // This case is handled elsewhere.56 if (CurrV.getKind() == RefVal::Released) {57 assert(CurrV.getCombinedCounts() == 0);58 os << "Object released by directly sending the '-dealloc' message";59 return true;60 }61 }62 63 // Determine if the typestate has changed.64 if (!PrevV.hasSameState(CurrV))65 switch (CurrV.getKind()) {66 case RefVal::Owned:67 case RefVal::NotOwned:68 if (PrevV.getCount() == CurrV.getCount()) {69 // Did an autorelease message get sent?70 if (PrevV.getAutoreleaseCount() == CurrV.getAutoreleaseCount())71 return false;72 73 assert(PrevV.getAutoreleaseCount() < CurrV.getAutoreleaseCount());74 os << "Object autoreleased";75 return true;76 }77 78 if (PrevV.getCount() > CurrV.getCount())79 os << "Reference count decremented.";80 else81 os << "Reference count incremented.";82 83 if (unsigned Count = CurrV.getCount())84 os << " The object now has a +" << Count << " retain count.";85 86 return true;87 88 case RefVal::Released:89 if (CurrV.getIvarAccessHistory() ==90 RefVal::IvarAccessHistory::ReleasedAfterDirectAccess &&91 CurrV.getIvarAccessHistory() != PrevV.getIvarAccessHistory()) {92 os << "Strong instance variable relinquished. ";93 }94 os << "Object released.";95 return true;96 97 case RefVal::ReturnedOwned:98 // Autoreleases can be applied after marking a node ReturnedOwned.99 if (CurrV.getAutoreleaseCount())100 return false;101 102 os << "Object returned to caller as an owning reference (single "103 "retain count transferred to caller)";104 return true;105 106 case RefVal::ReturnedNotOwned:107 os << "Object returned to caller with a +0 retain count";108 return true;109 110 default:111 return false;112 }113 return true;114}115 116/// Finds argument index of the out paramter in the call @c S117/// corresponding to the symbol @c Sym.118/// If none found, returns std::nullopt.119static std::optional<unsigned>120findArgIdxOfSymbol(ProgramStateRef CurrSt, const LocationContext *LCtx,121 SymbolRef &Sym, std::optional<CallEventRef<>> CE) {122 if (!CE)123 return std::nullopt;124 125 for (unsigned Idx = 0; Idx < (*CE)->getNumArgs(); Idx++)126 if (const MemRegion *MR = (*CE)->getArgSVal(Idx).getAsRegion())127 if (const auto *TR = dyn_cast<TypedValueRegion>(MR))128 if (CurrSt->getSVal(MR, TR->getValueType()).getAsSymbol() == Sym)129 return Idx;130 131 return std::nullopt;132}133 134static std::optional<std::string> findMetaClassAlloc(const Expr *Callee) {135 if (const auto *ME = dyn_cast<MemberExpr>(Callee)) {136 if (ME->getMemberDecl()->getNameAsString() != "alloc")137 return std::nullopt;138 const Expr *This = ME->getBase()->IgnoreParenImpCasts();139 if (const auto *DRE = dyn_cast<DeclRefExpr>(This)) {140 const ValueDecl *VD = DRE->getDecl();141 if (VD->getNameAsString() != "metaClass")142 return std::nullopt;143 144 if (const auto *RD = dyn_cast<CXXRecordDecl>(VD->getDeclContext()))145 return RD->getNameAsString();146 147 }148 }149 return std::nullopt;150}151 152static std::string findAllocatedObjectName(const Stmt *S, QualType QT) {153 if (const auto *CE = dyn_cast<CallExpr>(S))154 if (auto Out = findMetaClassAlloc(CE->getCallee()))155 return *Out;156 return getPrettyTypeName(QT);157}158 159static void generateDiagnosticsForCallLike(ProgramStateRef CurrSt,160 const LocationContext *LCtx,161 const RefVal &CurrV, SymbolRef &Sym,162 const Stmt *S,163 llvm::raw_string_ostream &os) {164 CallEventManager &Mgr = CurrSt->getStateManager().getCallEventManager();165 if (const CallExpr *CE = dyn_cast<CallExpr>(S)) {166 // Get the name of the callee (if it is available)167 // from the tracked SVal.168 SVal X = CurrSt->getSValAsScalarOrLoc(CE->getCallee(), LCtx);169 const FunctionDecl *FD = X.getAsFunctionDecl();170 171 // If failed, try to get it from AST.172 if (!FD)173 FD = dyn_cast<FunctionDecl>(CE->getCalleeDecl());174 175 if (const auto *MD = dyn_cast<CXXMethodDecl>(CE->getCalleeDecl())) {176 os << "Call to method '" << MD->getQualifiedNameAsString() << '\'';177 } else if (FD) {178 os << "Call to function '" << FD->getQualifiedNameAsString() << '\'';179 } else {180 os << "function call";181 }182 } else if (isa<CXXNewExpr>(S)) {183 os << "Operator 'new'";184 } else {185 assert(isa<ObjCMessageExpr>(S));186 CallEventRef<ObjCMethodCall> Call = Mgr.getObjCMethodCall(187 cast<ObjCMessageExpr>(S), CurrSt, LCtx, {nullptr, 0});188 189 switch (Call->getMessageKind()) {190 case OCM_Message:191 os << "Method";192 break;193 case OCM_PropertyAccess:194 os << "Property";195 break;196 case OCM_Subscript:197 os << "Subscript";198 break;199 }200 }201 202 std::optional<CallEventRef<>> CE = Mgr.getCall(S, CurrSt, LCtx, {nullptr, 0});203 auto Idx = findArgIdxOfSymbol(CurrSt, LCtx, Sym, CE);204 205 // If index is not found, we assume that the symbol was returned.206 if (!Idx) {207 os << " returns ";208 } else {209 os << " writes ";210 }211 212 if (CurrV.getObjKind() == ObjKind::CF) {213 os << "a Core Foundation object of type '" << Sym->getType() << "' with a ";214 } else if (CurrV.getObjKind() == ObjKind::OS) {215 os << "an OSObject of type '" << findAllocatedObjectName(S, Sym->getType())216 << "' with a ";217 } else if (CurrV.getObjKind() == ObjKind::Generalized) {218 os << "an object of type '" << Sym->getType() << "' with a ";219 } else {220 assert(CurrV.getObjKind() == ObjKind::ObjC);221 QualType T = Sym->getType();222 if (!isa<ObjCObjectPointerType>(T)) {223 os << "an Objective-C object with a ";224 } else {225 const ObjCObjectPointerType *PT = cast<ObjCObjectPointerType>(T);226 os << "an instance of " << PT->getPointeeType() << " with a ";227 }228 }229 230 if (CurrV.isOwned()) {231 os << "+1 retain count";232 } else {233 assert(CurrV.isNotOwned());234 os << "+0 retain count";235 }236 237 if (Idx) {238 os << " into an out parameter '";239 const ParmVarDecl *PVD = (*CE)->parameters()[*Idx];240 PVD->getNameForDiagnostic(os, PVD->getASTContext().getPrintingPolicy(),241 /*Qualified=*/false);242 os << "'";243 244 QualType RT = (*CE)->getResultType();245 if (!RT.isNull() && !RT->isVoidType()) {246 SVal RV = (*CE)->getReturnValue();247 if (CurrSt->isNull(RV).isConstrainedTrue()) {248 os << " (assuming the call returns zero)";249 } else if (CurrSt->isNonNull(RV).isConstrainedTrue()) {250 os << " (assuming the call returns non-zero)";251 }252 253 }254 }255}256 257namespace clang {258namespace ento {259namespace retaincountchecker {260 261class RefCountReportVisitor : public BugReporterVisitor {262protected:263 SymbolRef Sym;264 bool IsReleaseUnowned;265 266public:267 RefCountReportVisitor(SymbolRef S, bool IRU)268 : Sym(S), IsReleaseUnowned(IRU) {}269 270 void Profile(llvm::FoldingSetNodeID &ID) const override {271 static int x = 0;272 ID.AddPointer(&x);273 ID.AddPointer(Sym);274 }275 276 PathDiagnosticPieceRef VisitNode(const ExplodedNode *N,277 BugReporterContext &BRC,278 PathSensitiveBugReport &BR) override;279 280 PathDiagnosticPieceRef getEndPath(BugReporterContext &BRC,281 const ExplodedNode *N,282 PathSensitiveBugReport &BR) override;283};284 285class RefLeakReportVisitor : public RefCountReportVisitor {286public:287 RefLeakReportVisitor(SymbolRef Sym, const MemRegion *LastBinding)288 : RefCountReportVisitor(Sym, /*IsReleaseUnowned=*/false),289 LastBinding(LastBinding) {}290 291 PathDiagnosticPieceRef getEndPath(BugReporterContext &BRC,292 const ExplodedNode *N,293 PathSensitiveBugReport &BR) override;294 295private:296 const MemRegion *LastBinding;297};298 299} // end namespace retaincountchecker300} // end namespace ento301} // end namespace clang302 303 304/// Find the first node with the parent stack frame.305static const ExplodedNode *getCalleeNode(const ExplodedNode *Pred) {306 const StackFrameContext *SC = Pred->getStackFrame();307 if (SC->inTopFrame())308 return nullptr;309 const StackFrameContext *PC = SC->getParent()->getStackFrame();310 if (!PC)311 return nullptr;312 313 const ExplodedNode *N = Pred;314 while (N && N->getStackFrame() != PC) {315 N = N->getFirstPred();316 }317 return N;318}319 320 321/// Insert a diagnostic piece at function exit322/// if a function parameter is annotated as "os_consumed",323/// but it does not actually consume the reference.324static std::shared_ptr<PathDiagnosticEventPiece>325annotateConsumedSummaryMismatch(const ExplodedNode *N,326 CallExitBegin &CallExitLoc,327 const SourceManager &SM,328 CallEventManager &CEMgr) {329 330 const ExplodedNode *CN = getCalleeNode(N);331 if (!CN)332 return nullptr;333 334 CallEventRef<> Call = CEMgr.getCaller(N->getStackFrame(), N->getState());335 336 std::string sbuf;337 llvm::raw_string_ostream os(sbuf);338 ArrayRef<const ParmVarDecl *> Parameters = Call->parameters();339 for (unsigned I=0; I < Call->getNumArgs() && I < Parameters.size(); ++I) {340 const ParmVarDecl *PVD = Parameters[I];341 342 if (!PVD->hasAttr<OSConsumedAttr>())343 continue;344 345 if (SymbolRef SR = Call->getArgSVal(I).getAsLocSymbol()) {346 const RefVal *CountBeforeCall = getRefBinding(CN->getState(), SR);347 const RefVal *CountAtExit = getRefBinding(N->getState(), SR);348 349 if (!CountBeforeCall || !CountAtExit)350 continue;351 352 unsigned CountBefore = CountBeforeCall->getCount();353 unsigned CountAfter = CountAtExit->getCount();354 355 bool AsExpected = CountBefore > 0 && CountAfter == CountBefore - 1;356 if (!AsExpected) {357 os << "Parameter '";358 PVD->getNameForDiagnostic(os, PVD->getASTContext().getPrintingPolicy(),359 /*Qualified=*/false);360 os << "' is marked as consuming, but the function did not consume "361 << "the reference\n";362 }363 }364 }365 366 if (sbuf.empty())367 return nullptr;368 369 PathDiagnosticLocation L = PathDiagnosticLocation::create(CallExitLoc, SM);370 return std::make_shared<PathDiagnosticEventPiece>(L, sbuf);371}372 373/// Annotate the parameter at the analysis entry point.374static std::shared_ptr<PathDiagnosticEventPiece>375annotateStartParameter(const ExplodedNode *N, SymbolRef Sym,376 const SourceManager &SM) {377 auto PP = N->getLocationAs<BlockEdge>();378 if (!PP)379 return nullptr;380 381 const CFGBlock *Src = PP->getSrc();382 const RefVal *CurrT = getRefBinding(N->getState(), Sym);383 384 if (&Src->getParent()->getEntry() != Src || !CurrT ||385 getRefBinding(N->getFirstPred()->getState(), Sym))386 return nullptr;387 388 const auto *VR = cast<VarRegion>(cast<SymbolRegionValue>(Sym)->getRegion());389 const auto *PVD = cast<ParmVarDecl>(VR->getDecl());390 PathDiagnosticLocation L = PathDiagnosticLocation(PVD, SM);391 392 std::string s;393 llvm::raw_string_ostream os(s);394 os << "Parameter '" << PVD->getDeclName() << "' starts at +";395 if (CurrT->getCount() == 1) {396 os << "1, as it is marked as consuming";397 } else {398 assert(CurrT->getCount() == 0);399 os << "0";400 }401 return std::make_shared<PathDiagnosticEventPiece>(L, s);402}403 404PathDiagnosticPieceRef405RefCountReportVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &BRC,406 PathSensitiveBugReport &BR) {407 const SourceManager &SM = BRC.getSourceManager();408 CallEventManager &CEMgr = BRC.getStateManager().getCallEventManager();409 if (auto CE = N->getLocationAs<CallExitBegin>())410 if (auto PD = annotateConsumedSummaryMismatch(N, *CE, SM, CEMgr))411 return PD;412 413 if (auto PD = annotateStartParameter(N, Sym, SM))414 return PD;415 416 // FIXME: We will eventually need to handle non-statement-based events417 // (__attribute__((cleanup))).418 if (!N->getLocation().getAs<StmtPoint>())419 return nullptr;420 421 // Check if the type state has changed.422 const ExplodedNode *PrevNode = N->getFirstPred();423 ProgramStateRef PrevSt = PrevNode->getState();424 ProgramStateRef CurrSt = N->getState();425 const LocationContext *LCtx = N->getLocationContext();426 427 const RefVal* CurrT = getRefBinding(CurrSt, Sym);428 if (!CurrT)429 return nullptr;430 431 const RefVal &CurrV = *CurrT;432 const RefVal *PrevT = getRefBinding(PrevSt, Sym);433 434 // Create a string buffer to constain all the useful things we want435 // to tell the user.436 std::string sbuf;437 llvm::raw_string_ostream os(sbuf);438 439 if (PrevT && IsReleaseUnowned && CurrV.isNotOwned() && PrevT->isOwned()) {440 os << "Object is now not exclusively owned";441 auto Pos = PathDiagnosticLocation::create(N->getLocation(), SM);442 return std::make_shared<PathDiagnosticEventPiece>(Pos, sbuf);443 }444 445 // This is the allocation site since the previous node had no bindings446 // for this symbol.447 if (!PrevT) {448 const Stmt *S = N->getLocation().castAs<StmtPoint>().getStmt();449 450 if (isa<ObjCIvarRefExpr>(S) &&451 isSynthesizedAccessor(LCtx->getStackFrame())) {452 S = LCtx->getStackFrame()->getCallSite();453 }454 455 if (isa<ObjCArrayLiteral>(S)) {456 os << "NSArray literal is an object with a +0 retain count";457 } else if (isa<ObjCDictionaryLiteral>(S)) {458 os << "NSDictionary literal is an object with a +0 retain count";459 } else if (const ObjCBoxedExpr *BL = dyn_cast<ObjCBoxedExpr>(S)) {460 if (isNumericLiteralExpression(BL->getSubExpr()))461 os << "NSNumber literal is an object with a +0 retain count";462 else {463 const ObjCInterfaceDecl *BoxClass = nullptr;464 if (const ObjCMethodDecl *Method = BL->getBoxingMethod())465 BoxClass = Method->getClassInterface();466 467 // We should always be able to find the boxing class interface,468 // but consider this future-proofing.469 if (BoxClass) {470 os << *BoxClass << " b";471 } else {472 os << "B";473 }474 475 os << "oxed expression produces an object with a +0 retain count";476 }477 } else if (isa<ObjCIvarRefExpr>(S)) {478 os << "Object loaded from instance variable";479 } else {480 generateDiagnosticsForCallLike(CurrSt, LCtx, CurrV, Sym, S, os);481 }482 483 PathDiagnosticLocation Pos(S, SM, N->getLocationContext());484 return std::make_shared<PathDiagnosticEventPiece>(Pos, sbuf);485 }486 487 // Gather up the effects that were performed on the object at this488 // program point489 bool DeallocSent = false;490 491 const ProgramPointTag *Tag = N->getLocation().getTag();492 493 if (Tag == &RetainCountChecker::getCastFailTag()) {494 os << "Assuming dynamic cast returns null due to type mismatch";495 }496 497 if (Tag == &RetainCountChecker::getDeallocSentTag()) {498 // We only have summaries attached to nodes after evaluating CallExpr and499 // ObjCMessageExprs.500 const Stmt *S = N->getLocation().castAs<StmtPoint>().getStmt();501 502 if (const CallExpr *CE = dyn_cast<CallExpr>(S)) {503 // Iterate through the parameter expressions and see if the symbol504 // was ever passed as an argument.505 unsigned i = 0;506 507 for (auto AI=CE->arg_begin(), AE=CE->arg_end(); AI!=AE; ++AI, ++i) {508 509 // Retrieve the value of the argument. Is it the symbol510 // we are interested in?511 if (CurrSt->getSValAsScalarOrLoc(*AI, LCtx).getAsLocSymbol() != Sym)512 continue;513 514 // We have an argument. Get the effect!515 DeallocSent = true;516 }517 } else if (const ObjCMessageExpr *ME = dyn_cast<ObjCMessageExpr>(S)) {518 if (const Expr *receiver = ME->getInstanceReceiver()) {519 if (CurrSt->getSValAsScalarOrLoc(receiver, LCtx)520 .getAsLocSymbol() == Sym) {521 // The symbol we are tracking is the receiver.522 DeallocSent = true;523 }524 }525 }526 }527 528 if (!shouldGenerateNote(os, PrevT, CurrV, DeallocSent))529 return nullptr;530 531 if (sbuf.empty())532 return nullptr; // We have nothing to say!533 534 const Stmt *S = N->getLocation().castAs<StmtPoint>().getStmt();535 PathDiagnosticLocation Pos(S, BRC.getSourceManager(),536 N->getLocationContext());537 auto P = std::make_shared<PathDiagnosticEventPiece>(Pos, sbuf);538 539 // Add the range by scanning the children of the statement for any bindings540 // to Sym.541 for (const Stmt *Child : S->children())542 if (const Expr *Exp = dyn_cast_or_null<Expr>(Child))543 if (CurrSt->getSValAsScalarOrLoc(Exp, LCtx).getAsLocSymbol() == Sym) {544 P->addRange(Exp->getSourceRange());545 break;546 }547 548 return std::move(P);549}550 551static std::optional<std::string> describeRegion(const MemRegion *MR) {552 if (const auto *VR = dyn_cast_or_null<VarRegion>(MR))553 return std::string(VR->getDecl()->getName());554 // Once we support more storage locations for bindings,555 // this would need to be improved.556 return std::nullopt;557}558 559using Bindings = llvm::SmallVector<std::pair<const MemRegion *, SVal>, 4>;560 561namespace {562class VarBindingsCollector : public StoreManager::BindingsHandler {563 SymbolRef Sym;564 Bindings &Result;565 566public:567 VarBindingsCollector(SymbolRef Sym, Bindings &ToFill)568 : Sym(Sym), Result(ToFill) {}569 570 bool HandleBinding(StoreManager &SMgr, Store Store, const MemRegion *R,571 SVal Val) override {572 SymbolRef SymV = Val.getAsLocSymbol();573 if (!SymV || SymV != Sym)574 return true;575 576 if (isa<NonParamVarRegion>(R))577 Result.emplace_back(R, Val);578 579 return true;580 }581};582} // namespace583 584static Bindings getAllVarBindingsForSymbol(ProgramStateManager &Manager,585 const ExplodedNode *Node,586 SymbolRef Sym) {587 Bindings Result;588 VarBindingsCollector Collector{Sym, Result};589 while (Result.empty() && Node) {590 Manager.iterBindings(Node->getState(), Collector);591 Node = Node->getFirstPred();592 }593 594 return Result;595}596 597namespace {598// Find the first node in the current function context that referred to the599// tracked symbol and the memory location that value was stored to. Note, the600// value is only reported if the allocation occurred in the same function as601// the leak. The function can also return a location context, which should be602// treated as interesting.603struct AllocationInfo {604 const ExplodedNode* N;605 const MemRegion *R;606 const LocationContext *InterestingMethodContext;607 AllocationInfo(const ExplodedNode *InN,608 const MemRegion *InR,609 const LocationContext *InInterestingMethodContext) :610 N(InN), R(InR), InterestingMethodContext(InInterestingMethodContext) {}611};612} // end anonymous namespace613 614static AllocationInfo GetAllocationSite(ProgramStateManager &StateMgr,615 const ExplodedNode *N, SymbolRef Sym) {616 const ExplodedNode *AllocationNode = N;617 const ExplodedNode *AllocationNodeInCurrentOrParentContext = N;618 const MemRegion *FirstBinding = nullptr;619 const LocationContext *LeakContext = N->getLocationContext();620 621 // The location context of the init method called on the leaked object, if622 // available.623 const LocationContext *InitMethodContext = nullptr;624 625 while (N) {626 ProgramStateRef St = N->getState();627 const LocationContext *NContext = N->getLocationContext();628 629 if (!getRefBinding(St, Sym))630 break;631 632 StoreManager::FindUniqueBinding FB(Sym);633 StateMgr.iterBindings(St, FB);634 635 if (FB) {636 const MemRegion *R = FB.getRegion();637 // Do not show local variables belonging to a function other than638 // where the error is reported.639 if (const auto *MR = R->getMemorySpaceAs<StackSpaceRegion>(St))640 if (MR->getStackFrame() == LeakContext->getStackFrame())641 FirstBinding = R;642 }643 644 // AllocationNode is the last node in which the symbol was tracked.645 AllocationNode = N;646 647 // AllocationNodeInCurrentContext, is the last node in the current or648 // parent context in which the symbol was tracked.649 //650 // Note that the allocation site might be in the parent context. For example,651 // the case where an allocation happens in a block that captures a reference652 // to it and that reference is overwritten/dropped by another call to653 // the block.654 if (NContext == LeakContext || NContext->isParentOf(LeakContext))655 AllocationNodeInCurrentOrParentContext = N;656 657 // Find the last init that was called on the given symbol and store the658 // init method's location context.659 if (!InitMethodContext)660 if (auto CEP = N->getLocation().getAs<CallEnter>()) {661 const Stmt *CE = CEP->getCallExpr();662 if (const auto *ME = dyn_cast_or_null<ObjCMessageExpr>(CE)) {663 const Stmt *RecExpr = ME->getInstanceReceiver();664 if (RecExpr) {665 SVal RecV = St->getSVal(RecExpr, NContext);666 if (ME->getMethodFamily() == OMF_init && RecV.getAsSymbol() == Sym)667 InitMethodContext = CEP->getCalleeContext();668 }669 }670 }671 672 N = N->getFirstPred();673 }674 675 // If we are reporting a leak of the object that was allocated with alloc,676 // mark its init method as interesting.677 const LocationContext *InterestingMethodContext = nullptr;678 if (InitMethodContext) {679 const ProgramPoint AllocPP = AllocationNode->getLocation();680 if (std::optional<StmtPoint> SP = AllocPP.getAs<StmtPoint>())681 if (const ObjCMessageExpr *ME = SP->getStmtAs<ObjCMessageExpr>())682 if (ME->getMethodFamily() == OMF_alloc)683 InterestingMethodContext = InitMethodContext;684 }685 686 // If allocation happened in a function different from the leak node context,687 // do not report the binding.688 assert(N && "Could not find allocation node");689 690 if (AllocationNodeInCurrentOrParentContext &&691 AllocationNodeInCurrentOrParentContext->getLocationContext() !=692 LeakContext)693 FirstBinding = nullptr;694 695 return AllocationInfo(AllocationNodeInCurrentOrParentContext, FirstBinding,696 InterestingMethodContext);697}698 699PathDiagnosticPieceRef700RefCountReportVisitor::getEndPath(BugReporterContext &BRC,701 const ExplodedNode *EndN,702 PathSensitiveBugReport &BR) {703 BR.markInteresting(Sym);704 return BugReporterVisitor::getDefaultEndPath(BRC, EndN, BR);705}706 707PathDiagnosticPieceRef708RefLeakReportVisitor::getEndPath(BugReporterContext &BRC,709 const ExplodedNode *EndN,710 PathSensitiveBugReport &BR) {711 712 // Tell the BugReporterContext to report cases when the tracked symbol is713 // assigned to different variables, etc.714 BR.markInteresting(Sym);715 716 PathDiagnosticLocation L = cast<RefLeakReport>(BR).getEndOfPath();717 718 std::string sbuf;719 llvm::raw_string_ostream os(sbuf);720 721 os << "Object leaked: ";722 723 std::optional<std::string> RegionDescription = describeRegion(LastBinding);724 if (RegionDescription) {725 os << "object allocated and stored into '" << *RegionDescription << '\'';726 } else {727 os << "allocated object of type '" << getPrettyTypeName(Sym->getType())728 << "'";729 }730 731 // Get the retain count.732 const RefVal *RV = getRefBinding(EndN->getState(), Sym);733 assert(RV);734 735 if (RV->getKind() == RefVal::ErrorLeakReturned) {736 const Decl *D = &EndN->getCodeDecl();737 738 os << (isa<ObjCMethodDecl>(D) ? " is returned from a method "739 : " is returned from a function ");740 741 if (D->hasAttr<CFReturnsNotRetainedAttr>()) {742 os << "that is annotated as CF_RETURNS_NOT_RETAINED";743 } else if (D->hasAttr<NSReturnsNotRetainedAttr>()) {744 os << "that is annotated as NS_RETURNS_NOT_RETAINED";745 } else if (D->hasAttr<OSReturnsNotRetainedAttr>()) {746 os << "that is annotated as OS_RETURNS_NOT_RETAINED";747 } else {748 if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(D)) {749 if (BRC.getASTContext().getLangOpts().ObjCAutoRefCount) {750 os << "managed by Automatic Reference Counting";751 } else {752 os << "whose name ('" << MD->getSelector().getAsString()753 << "') does not start with "754 "'copy', 'mutableCopy', 'alloc' or 'new'."755 " This violates the naming convention rules"756 " given in the Memory Management Guide for Cocoa";757 }758 } else {759 const FunctionDecl *FD = cast<FunctionDecl>(D);760 ObjKind K = RV->getObjKind();761 if (K == ObjKind::ObjC || K == ObjKind::CF) {762 os << "whose name ('" << *FD763 << "') does not contain 'Copy' or 'Create'. This violates the "764 "naming convention rules given in the Memory Management Guide "765 "for Core Foundation";766 } else if (RV->getObjKind() == ObjKind::OS) {767 std::string FuncName = FD->getNameAsString();768 os << "whose name ('" << FuncName << "') starts with '"769 << StringRef(FuncName).substr(0, 3) << "'";770 }771 }772 }773 } else {774 os << " is not referenced later in this execution path and has a retain "775 "count of +"776 << RV->getCount();777 }778 779 return std::make_shared<PathDiagnosticEventPiece>(L, sbuf);780}781 782RefCountReport::RefCountReport(const RefCountBug &D, const LangOptions &LOpts,783 ExplodedNode *n, SymbolRef sym, bool isLeak,784 bool IsReleaseUnowned)785 : PathSensitiveBugReport(D, D.getReportMessage(), n), Sym(sym),786 isLeak(isLeak) {787 if (!isLeak)788 addVisitor<RefCountReportVisitor>(sym, IsReleaseUnowned);789}790 791RefCountReport::RefCountReport(const RefCountBug &D, const LangOptions &LOpts,792 ExplodedNode *n, SymbolRef sym,793 StringRef endText)794 : PathSensitiveBugReport(D, D.getReportMessage(), endText, n) {795 796 addVisitor<RefCountReportVisitor>(sym, /*IsReleaseUnowned=*/false);797}798 799void RefLeakReport::deriveParamLocation(CheckerContext &Ctx) {800 const SourceManager &SMgr = Ctx.getSourceManager();801 802 if (!Sym->getOriginRegion())803 return;804 805 auto *Region = dyn_cast<DeclRegion>(Sym->getOriginRegion());806 if (Region) {807 const Decl *PDecl = Region->getDecl();808 if (isa_and_nonnull<ParmVarDecl>(PDecl)) {809 PathDiagnosticLocation ParamLocation =810 PathDiagnosticLocation::create(PDecl, SMgr);811 Location = ParamLocation;812 UniqueingLocation = ParamLocation;813 UniqueingDecl = Ctx.getLocationContext()->getDecl();814 }815 }816}817 818void RefLeakReport::deriveAllocLocation(CheckerContext &Ctx) {819 // Most bug reports are cached at the location where they occurred.820 // With leaks, we want to unique them by the location where they were821 // allocated, and only report a single path. To do this, we need to find822 // the allocation site of a piece of tracked memory, which we do via a823 // call to GetAllocationSite. This will walk the ExplodedGraph backwards.824 // Note that this is *not* the trimmed graph; we are guaranteed, however,825 // that all ancestor nodes that represent the allocation site have the826 // same SourceLocation.827 const ExplodedNode *AllocNode = nullptr;828 829 const SourceManager &SMgr = Ctx.getSourceManager();830 831 AllocationInfo AllocI =832 GetAllocationSite(Ctx.getStateManager(), getErrorNode(), Sym);833 834 AllocNode = AllocI.N;835 AllocFirstBinding = AllocI.R;836 markInteresting(AllocI.InterestingMethodContext);837 838 // Get the SourceLocation for the allocation site.839 // FIXME: This will crash the analyzer if an allocation comes from an840 // implicit call (ex: a destructor call).841 // (Currently there are no such allocations in Cocoa, though.)842 AllocStmt = AllocNode->getStmtForDiagnostics();843 844 if (!AllocStmt) {845 AllocFirstBinding = nullptr;846 return;847 }848 849 PathDiagnosticLocation AllocLocation = PathDiagnosticLocation::createBegin(850 AllocStmt, SMgr, AllocNode->getLocationContext());851 Location = AllocLocation;852 853 // Set uniqieing info, which will be used for unique the bug reports. The854 // leaks should be uniqued on the allocation site.855 UniqueingLocation = AllocLocation;856 UniqueingDecl = AllocNode->getLocationContext()->getDecl();857}858 859void RefLeakReport::createDescription(CheckerContext &Ctx) {860 assert(Location.isValid() && UniqueingDecl && UniqueingLocation.isValid());861 Description.clear();862 llvm::raw_string_ostream os(Description);863 os << "Potential leak of an object";864 865 std::optional<std::string> RegionDescription =866 describeRegion(AllocBindingToReport);867 if (RegionDescription) {868 os << " stored into '" << *RegionDescription << '\'';869 } else {870 871 // If we can't figure out the name, just supply the type information.872 os << " of type '" << getPrettyTypeName(Sym->getType()) << "'";873 }874}875 876void RefLeakReport::findBindingToReport(CheckerContext &Ctx,877 ExplodedNode *Node) {878 if (!AllocFirstBinding)879 // If we don't have any bindings, we won't be able to find any880 // better binding to report.881 return;882 883 // If the original region still contains the leaking symbol...884 if (Node->getState()->getSVal(AllocFirstBinding).getAsSymbol() == Sym) {885 // ...it is the best binding to report.886 AllocBindingToReport = AllocFirstBinding;887 return;888 }889 890 // At this point, we know that the original region doesn't contain the leaking891 // when the actual leak happens. It means that it can be confusing for the892 // user to see such description in the message.893 //894 // Let's consider the following example:895 // Object *Original = allocate(...);896 // Object *New = Original;897 // Original = allocate(...);898 // Original->release();899 //900 // Complaining about a leaking object "stored into Original" might cause a901 // rightful confusion because 'Original' is actually released.902 // We should complain about 'New' instead.903 Bindings AllVarBindings =904 getAllVarBindingsForSymbol(Ctx.getStateManager(), Node, Sym);905 906 // While looking for the last var bindings, we can still find907 // `AllocFirstBinding` to be one of them. In situations like this,908 // it would still be the easiest case to explain to our users.909 if (!AllVarBindings.empty() &&910 !llvm::is_contained(llvm::make_first_range(AllVarBindings),911 AllocFirstBinding)) {912 // Let's pick one of them at random (if there is something to pick from).913 AllocBindingToReport = AllVarBindings[0].first;914 915 // Because 'AllocBindingToReport' is not the same as916 // 'AllocFirstBinding', we need to explain how the leaking object917 // got from one to another.918 //919 // NOTE: We use the actual SVal stored in AllocBindingToReport here because920 // trackStoredValue compares SVal's and it can get trickier for921 // something like derived regions if we want to construct SVal from922 // Sym. Instead, we take the value that is definitely stored in that923 // region, thus guaranteeing that trackStoredValue will work.924 bugreporter::trackStoredValue(AllVarBindings[0].second,925 AllocBindingToReport, *this);926 } else {927 AllocBindingToReport = AllocFirstBinding;928 }929}930 931RefLeakReport::RefLeakReport(const RefCountBug &D, const LangOptions &LOpts,932 ExplodedNode *N, SymbolRef Sym,933 CheckerContext &Ctx)934 : RefCountReport(D, LOpts, N, Sym, /*isLeak=*/true) {935 936 deriveAllocLocation(Ctx);937 findBindingToReport(Ctx, N);938 939 if (!AllocFirstBinding)940 deriveParamLocation(Ctx);941 942 createDescription(Ctx);943 944 addVisitor<RefLeakReportVisitor>(Sym, AllocBindingToReport);945}946