| // RetainCountDiagnostics.cpp - 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 diagnostics for RetainCountChecker, which implements |
| // a reference count checker for Core Foundation and Cocoa on (Mac OS X). |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "RetainCountDiagnostics.h" |
| #include "RetainCountChecker.h" |
| |
| 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>(E) || |
| isa<CharacterLiteral>(E) || |
| isa<FloatingLiteral>(E) || |
| isa<ObjCBoolLiteralExpr>(E) || |
| isa<CXXBoolLiteralExpr>(E); |
| } |
| |
| /// If type represents a pointer to CXXRecordDecl, |
| /// and is not a typedef, return the decl name. |
| /// Otherwise, return the serialization of type. |
| static std::string getPrettyTypeName(QualType QT) { |
| QualType PT = QT->getPointeeType(); |
| if (!PT.isNull() && !QT->getAs<TypedefType>()) |
| if (const auto *RD = PT->getAsCXXRecordDecl()) |
| return std::string(RD->getName()); |
| return QT.getAsString(); |
| } |
| |
| /// Write information about the type state change to {@code os}, |
| /// return whether the note should be generated. |
| static bool shouldGenerateNote(llvm::raw_string_ostream &os, |
| const RefVal *PrevT, |
| const RefVal &CurrV, |
| bool DeallocSent) { |
| // Get the previous type state. |
| RefVal PrevV = *PrevT; |
| |
| // Specially handle -dealloc. |
| if (DeallocSent) { |
| // Determine if the object's reference count was pushed to zero. |
| 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) { |
| assert(CurrV.getCombinedCounts() == 0); |
| os << "Object released by directly sending the '-dealloc' message"; |
| return true; |
| } |
| } |
| |
| // Determine if the typestate has changed. |
| if (!PrevV.hasSameState(CurrV)) |
| switch (CurrV.getKind()) { |
| case RefVal::Owned: |
| case RefVal::NotOwned: |
| if (PrevV.getCount() == CurrV.getCount()) { |
| // Did an autorelease message get sent? |
| if (PrevV.getAutoreleaseCount() == CurrV.getAutoreleaseCount()) |
| return false; |
| |
| assert(PrevV.getAutoreleaseCount() < CurrV.getAutoreleaseCount()); |
| os << "Object autoreleased"; |
| return true; |
| } |
| |
| if (PrevV.getCount() > CurrV.getCount()) |
| os << "Reference count decremented."; |
| else |
| os << "Reference count incremented."; |
| |
| if (unsigned Count = CurrV.getCount()) |
| os << " The object now has a +" << Count << " retain count."; |
| |
| return true; |
| |
| case RefVal::Released: |
| if (CurrV.getIvarAccessHistory() == |
| RefVal::IvarAccessHistory::ReleasedAfterDirectAccess && |
| CurrV.getIvarAccessHistory() != PrevV.getIvarAccessHistory()) { |
| os << "Strong instance variable relinquished. "; |
| } |
| os << "Object released."; |
| return true; |
| |
| case RefVal::ReturnedOwned: |
| // Autoreleases can be applied after marking a node ReturnedOwned. |
| if (CurrV.getAutoreleaseCount()) |
| return false; |
| |
| os << "Object returned to caller as an owning reference (single " |
| "retain count transferred to caller)"; |
| return true; |
| |
| case RefVal::ReturnedNotOwned: |
| os << "Object returned to caller with a +0 retain count"; |
| return true; |
| |
| default: |
| return false; |
| } |
| return true; |
| } |
| |
| /// Finds argument index of the out paramter in the call {@code S} |
| /// corresponding to the symbol {@code Sym}. |
| /// If none found, returns None. |
| static Optional<unsigned> findArgIdxOfSymbol(ProgramStateRef CurrSt, |
| const LocationContext *LCtx, |
| SymbolRef &Sym, |
| Optional<CallEventRef<>> CE) { |
| if (!CE) |
| return None; |
| |
| for (unsigned Idx = 0; Idx < (*CE)->getNumArgs(); Idx++) |
| if (const MemRegion *MR = (*CE)->getArgSVal(Idx).getAsRegion()) |
| if (const auto *TR = dyn_cast<TypedValueRegion>(MR)) |
| if (CurrSt->getSVal(MR, TR->getValueType()).getAsSymbol() == Sym) |
| return Idx; |
| |
| return None; |
| } |
| |
| static Optional<std::string> findMetaClassAlloc(const Expr *Callee) { |
| if (const auto *ME = dyn_cast<MemberExpr>(Callee)) { |
| if (ME->getMemberDecl()->getNameAsString() != "alloc") |
| return None; |
| const Expr *This = ME->getBase()->IgnoreParenImpCasts(); |
| if (const auto *DRE = dyn_cast<DeclRefExpr>(This)) { |
| const ValueDecl *VD = DRE->getDecl(); |
| if (VD->getNameAsString() != "metaClass") |
| return None; |
| |
| if (const auto *RD = dyn_cast<CXXRecordDecl>(VD->getDeclContext())) |
| return RD->getNameAsString(); |
| |
| } |
| } |
| return None; |
| } |
| |
| static std::string findAllocatedObjectName(const Stmt *S, QualType QT) { |
| if (const auto *CE = dyn_cast<CallExpr>(S)) |
| if (auto Out = findMetaClassAlloc(CE->getCallee())) |
| return *Out; |
| return getPrettyTypeName(QT); |
| } |
| |
| static void generateDiagnosticsForCallLike(ProgramStateRef CurrSt, |
| const LocationContext *LCtx, |
| const RefVal &CurrV, SymbolRef &Sym, |
| const Stmt *S, |
| llvm::raw_string_ostream &os) { |
| CallEventManager &Mgr = CurrSt->getStateManager().getCallEventManager(); |
| if (const CallExpr *CE = dyn_cast<CallExpr>(S)) { |
| // Get the name of the callee (if it is available) |
| // from the tracked SVal. |
| SVal X = CurrSt->getSValAsScalarOrLoc(CE->getCallee(), LCtx); |
| const FunctionDecl *FD = X.getAsFunctionDecl(); |
| |
| // If failed, try to get it from AST. |
| if (!FD) |
| FD = dyn_cast<FunctionDecl>(CE->getCalleeDecl()); |
| |
| if (const auto *MD = dyn_cast<CXXMethodDecl>(CE->getCalleeDecl())) { |
| os << "Call to method '" << MD->getQualifiedNameAsString() << '\''; |
| } else if (FD) { |
| os << "Call to function '" << FD->getQualifiedNameAsString() << '\''; |
| } else { |
| os << "function call"; |
| } |
| } else if (isa<CXXNewExpr>(S)) { |
| os << "Operator 'new'"; |
| } else { |
| assert(isa<ObjCMessageExpr>(S)); |
| CallEventRef<ObjCMethodCall> Call = |
| Mgr.getObjCMethodCall(cast<ObjCMessageExpr>(S), CurrSt, LCtx); |
| |
| switch (Call->getMessageKind()) { |
| case OCM_Message: |
| os << "Method"; |
| break; |
| case OCM_PropertyAccess: |
| os << "Property"; |
| break; |
| case OCM_Subscript: |
| os << "Subscript"; |
| break; |
| } |
| } |
| |
| Optional<CallEventRef<>> CE = Mgr.getCall(S, CurrSt, LCtx); |
| auto Idx = findArgIdxOfSymbol(CurrSt, LCtx, Sym, CE); |
| |
| // If index is not found, we assume that the symbol was returned. |
| if (!Idx) { |
| os << " returns "; |
| } else { |
| os << " writes "; |
| } |
| |
| if (CurrV.getObjKind() == ObjKind::CF) { |
| os << "a Core Foundation object of type '" |
| << Sym->getType().getAsString() << "' with a "; |
| } else if (CurrV.getObjKind() == ObjKind::OS) { |
| os << "an OSObject of type '" << findAllocatedObjectName(S, Sym->getType()) |
| << "' with a "; |
| } else if (CurrV.getObjKind() == ObjKind::Generalized) { |
| os << "an object of type '" << Sym->getType().getAsString() |
| << "' with a "; |
| } else { |
| assert(CurrV.getObjKind() == ObjKind::ObjC); |
| QualType T = Sym->getType(); |
| if (!isa<ObjCObjectPointerType>(T)) { |
| os << "an Objective-C object with a "; |
| } else { |
| const ObjCObjectPointerType *PT = cast<ObjCObjectPointerType>(T); |
| os << "an instance of " << PT->getPointeeType().getAsString() |
| << " with a "; |
| } |
| } |
| |
| if (CurrV.isOwned()) { |
| os << "+1 retain count"; |
| } else { |
| assert(CurrV.isNotOwned()); |
| os << "+0 retain count"; |
| } |
| |
| if (Idx) { |
| os << " into an out parameter '"; |
| const ParmVarDecl *PVD = (*CE)->parameters()[*Idx]; |
| PVD->getNameForDiagnostic(os, PVD->getASTContext().getPrintingPolicy(), |
| /*Qualified=*/false); |
| os << "'"; |
| |
| QualType RT = (*CE)->getResultType(); |
| if (!RT.isNull() && !RT->isVoidType()) { |
| SVal RV = (*CE)->getReturnValue(); |
| if (CurrSt->isNull(RV).isConstrainedTrue()) { |
| os << " (assuming the call returns zero)"; |
| } else if (CurrSt->isNonNull(RV).isConstrainedTrue()) { |
| os << " (assuming the call returns non-zero)"; |
| } |
| |
| } |
| } |
| } |
| |
| namespace clang { |
| namespace ento { |
| namespace retaincountchecker { |
| |
| class RefCountReportVisitor : public BugReporterVisitor { |
| protected: |
| SymbolRef Sym; |
| |
| public: |
| RefCountReportVisitor(SymbolRef sym) : Sym(sym) {} |
| |
| void Profile(llvm::FoldingSetNodeID &ID) const override { |
| static int x = 0; |
| ID.AddPointer(&x); |
| ID.AddPointer(Sym); |
| } |
| |
| PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, |
| BugReporterContext &BRC, |
| PathSensitiveBugReport &BR) override; |
| |
| PathDiagnosticPieceRef getEndPath(BugReporterContext &BRC, |
| const ExplodedNode *N, |
| PathSensitiveBugReport &BR) override; |
| }; |
| |
| class RefLeakReportVisitor : public RefCountReportVisitor { |
| public: |
| RefLeakReportVisitor(SymbolRef sym) : RefCountReportVisitor(sym) {} |
| |
| PathDiagnosticPieceRef getEndPath(BugReporterContext &BRC, |
| const ExplodedNode *N, |
| PathSensitiveBugReport &BR) override; |
| }; |
| |
| } // end namespace retaincountchecker |
| } // end namespace ento |
| } // end namespace clang |
| |
| |
| /// Find the first node with the parent stack frame. |
| static const ExplodedNode *getCalleeNode(const ExplodedNode *Pred) { |
| const StackFrameContext *SC = Pred->getStackFrame(); |
| if (SC->inTopFrame()) |
| return nullptr; |
| const StackFrameContext *PC = SC->getParent()->getStackFrame(); |
| if (!PC) |
| return nullptr; |
| |
| const ExplodedNode *N = Pred; |
| while (N && N->getStackFrame() != PC) { |
| N = N->getFirstPred(); |
| } |
| return N; |
| } |
| |
| |
| /// Insert a diagnostic piece at function exit |
| /// if a function parameter is annotated as "os_consumed", |
| /// but it does not actually consume the reference. |
| static std::shared_ptr<PathDiagnosticEventPiece> |
| annotateConsumedSummaryMismatch(const ExplodedNode *N, |
| CallExitBegin &CallExitLoc, |
| const SourceManager &SM, |
| CallEventManager &CEMgr) { |
| |
| const ExplodedNode *CN = getCalleeNode(N); |
| if (!CN) |
| return nullptr; |
| |
| CallEventRef<> Call = CEMgr.getCaller(N->getStackFrame(), N->getState()); |
| |
| std::string sbuf; |
| llvm::raw_string_ostream os(sbuf); |
| ArrayRef<const ParmVarDecl *> Parameters = Call->parameters(); |
| for (unsigned I=0; I < Call->getNumArgs() && I < Parameters.size(); ++I) { |
| const ParmVarDecl *PVD = Parameters[I]; |
| |
| if (!PVD->hasAttr<OSConsumedAttr>()) |
| continue; |
| |
| if (SymbolRef SR = Call->getArgSVal(I).getAsLocSymbol()) { |
| const RefVal *CountBeforeCall = getRefBinding(CN->getState(), SR); |
| const RefVal *CountAtExit = getRefBinding(N->getState(), SR); |
| |
| if (!CountBeforeCall || !CountAtExit) |
| continue; |
| |
| unsigned CountBefore = CountBeforeCall->getCount(); |
| unsigned CountAfter = CountAtExit->getCount(); |
| |
| bool AsExpected = CountBefore > 0 && CountAfter == CountBefore - 1; |
| if (!AsExpected) { |
| os << "Parameter '"; |
| PVD->getNameForDiagnostic(os, PVD->getASTContext().getPrintingPolicy(), |
| /*Qualified=*/false); |
| os << "' is marked as consuming, but the function did not consume " |
| << "the reference\n"; |
| } |
| } |
| } |
| |
| if (os.str().empty()) |
| return nullptr; |
| |
| PathDiagnosticLocation L = PathDiagnosticLocation::create(CallExitLoc, SM); |
| return std::make_shared<PathDiagnosticEventPiece>(L, os.str()); |
| } |
| |
| /// Annotate the parameter at the analysis entry point. |
| static std::shared_ptr<PathDiagnosticEventPiece> |
| annotateStartParameter(const ExplodedNode *N, SymbolRef Sym, |
| const SourceManager &SM) { |
| auto PP = N->getLocationAs<BlockEdge>(); |
| if (!PP) |
| return nullptr; |
| |
| const CFGBlock *Src = PP->getSrc(); |
| const RefVal *CurrT = getRefBinding(N->getState(), Sym); |
| |
| if (&Src->getParent()->getEntry() != Src || !CurrT || |
| getRefBinding(N->getFirstPred()->getState(), Sym)) |
| return nullptr; |
| |
| const auto *VR = cast<VarRegion>(cast<SymbolRegionValue>(Sym)->getRegion()); |
| const auto *PVD = cast<ParmVarDecl>(VR->getDecl()); |
| PathDiagnosticLocation L = PathDiagnosticLocation(PVD, SM); |
| |
| std::string s; |
| llvm::raw_string_ostream os(s); |
| os << "Parameter '" << PVD->getDeclName() << "' starts at +"; |
| if (CurrT->getCount() == 1) { |
| os << "1, as it is marked as consuming"; |
| } else { |
| assert(CurrT->getCount() == 0); |
| os << "0"; |
| } |
| return std::make_shared<PathDiagnosticEventPiece>(L, os.str()); |
| } |
| |
| 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>()) |
| if (auto PD = annotateConsumedSummaryMismatch(N, *CE, SM, CEMgr)) |
| return PD; |
| |
| if (auto PD = annotateStartParameter(N, Sym, SM)) |
| return PD; |
| |
| // FIXME: We will eventually need to handle non-statement-based events |
| // (__attribute__((cleanup))). |
| if (!N->getLocation().getAs<StmtPoint>()) |
| return nullptr; |
| |
| // Check if the type state has changed. |
| const ExplodedNode *PrevNode = N->getFirstPred(); |
| ProgramStateRef PrevSt = PrevNode->getState(); |
| ProgramStateRef CurrSt = N->getState(); |
| const LocationContext *LCtx = N->getLocationContext(); |
| |
| const RefVal* CurrT = getRefBinding(CurrSt, Sym); |
| if (!CurrT) |
| return nullptr; |
| |
| const RefVal &CurrV = *CurrT; |
| const RefVal *PrevT = getRefBinding(PrevSt, Sym); |
| |
| // Create a string buffer to constain all the useful things we want |
| // to tell the user. |
| std::string sbuf; |
| llvm::raw_string_ostream os(sbuf); |
| |
| if (PrevT && IsFreeUnowned && CurrV.isNotOwned() && PrevT->isOwned()) { |
| os << "Object is now not exclusively owned"; |
| auto Pos = PathDiagnosticLocation::create(N->getLocation(), SM); |
| return std::make_shared<PathDiagnosticEventPiece>(Pos, os.str()); |
| } |
| |
| // This is the allocation site since the previous node had no bindings |
| // for this symbol. |
| if (!PrevT) { |
| const Stmt *S = N->getLocation().castAs<StmtPoint>().getStmt(); |
| |
| if (isa<ObjCIvarRefExpr>(S) && |
| isSynthesizedAccessor(LCtx->getStackFrame())) { |
| S = LCtx->getStackFrame()->getCallSite(); |
| } |
| |
| if (isa<ObjCArrayLiteral>(S)) { |
| os << "NSArray literal is an object with a +0 retain count"; |
| } else if (isa<ObjCDictionaryLiteral>(S)) { |
| os << "NSDictionary literal is an object with a +0 retain count"; |
| } else if (const ObjCBoxedExpr *BL = dyn_cast<ObjCBoxedExpr>(S)) { |
| if (isNumericLiteralExpression(BL->getSubExpr())) |
| os << "NSNumber literal is an object with a +0 retain count"; |
| else { |
| const ObjCInterfaceDecl *BoxClass = nullptr; |
| if (const ObjCMethodDecl *Method = BL->getBoxingMethod()) |
| BoxClass = Method->getClassInterface(); |
| |
| // We should always be able to find the boxing class interface, |
| // but consider this future-proofing. |
| if (BoxClass) { |
| os << *BoxClass << " b"; |
| } else { |
| os << "B"; |
| } |
| |
| os << "oxed expression produces an object with a +0 retain count"; |
| } |
| } else if (isa<ObjCIvarRefExpr>(S)) { |
| os << "Object loaded from instance variable"; |
| } else { |
| generateDiagnosticsForCallLike(CurrSt, LCtx, CurrV, Sym, S, os); |
| } |
| |
| PathDiagnosticLocation Pos(S, SM, N->getLocationContext()); |
| return std::make_shared<PathDiagnosticEventPiece>(Pos, os.str()); |
| } |
| |
| // Gather up the effects that were performed on the object at this |
| // program point |
| bool DeallocSent = false; |
| |
| const ProgramPointTag *Tag = N->getLocation().getTag(); |
| |
| if (Tag == &RetainCountChecker::getCastFailTag()) { |
| os << "Assuming dynamic cast returns null due to type mismatch"; |
| } |
| |
| if (Tag == &RetainCountChecker::getDeallocSentTag()) { |
| // We only have summaries attached to nodes after evaluating CallExpr and |
| // ObjCMessageExprs. |
| const Stmt *S = N->getLocation().castAs<StmtPoint>().getStmt(); |
| |
| if (const CallExpr *CE = dyn_cast<CallExpr>(S)) { |
| // Iterate through the parameter expressions and see if the symbol |
| // was ever passed as an argument. |
| unsigned i = 0; |
| |
| for (auto AI=CE->arg_begin(), AE=CE->arg_end(); AI!=AE; ++AI, ++i) { |
| |
| // Retrieve the value of the argument. Is it the symbol |
| // we are interested in? |
| if (CurrSt->getSValAsScalarOrLoc(*AI, LCtx).getAsLocSymbol() != Sym) |
| continue; |
| |
| // We have an argument. Get the effect! |
| DeallocSent = true; |
| } |
| } else if (const ObjCMessageExpr *ME = dyn_cast<ObjCMessageExpr>(S)) { |
| if (const Expr *receiver = ME->getInstanceReceiver()) { |
| if (CurrSt->getSValAsScalarOrLoc(receiver, LCtx) |
| .getAsLocSymbol() == Sym) { |
| // The symbol we are tracking is the receiver. |
| DeallocSent = true; |
| } |
| } |
| } |
| } |
| |
| if (!shouldGenerateNote(os, PrevT, CurrV, DeallocSent)) |
| return nullptr; |
| |
| if (os.str().empty()) |
| return nullptr; // We have nothing to say! |
| |
| const Stmt *S = N->getLocation().castAs<StmtPoint>().getStmt(); |
| PathDiagnosticLocation Pos(S, BRC.getSourceManager(), |
| N->getLocationContext()); |
| auto P = std::make_shared<PathDiagnosticEventPiece>(Pos, os.str()); |
| |
| // Add the range by scanning the children of the statement for any bindings |
| // to Sym. |
| for (const Stmt *Child : S->children()) |
| if (const Expr *Exp = dyn_cast_or_null<Expr>(Child)) |
| if (CurrSt->getSValAsScalarOrLoc(Exp, LCtx).getAsLocSymbol() == Sym) { |
| P->addRange(Exp->getSourceRange()); |
| break; |
| } |
| |
| return std::move(P); |
| } |
| |
| static Optional<std::string> describeRegion(const MemRegion *MR) { |
| if (const auto *VR = dyn_cast_or_null<VarRegion>(MR)) |
| return std::string(VR->getDecl()->getName()); |
| // Once we support more storage locations for bindings, |
| // this would need to be improved. |
| return None; |
| } |
| |
| namespace { |
| // Find the first node in the current function context that referred to the |
| // tracked symbol and the memory location that value was stored to. Note, the |
| // value is only reported if the allocation occurred in the same function as |
| // the leak. The function can also return a location context, which should be |
| // treated as interesting. |
| struct AllocationInfo { |
| const ExplodedNode* N; |
| const MemRegion *R; |
| const LocationContext *InterestingMethodContext; |
| AllocationInfo(const ExplodedNode *InN, |
| const MemRegion *InR, |
| const LocationContext *InInterestingMethodContext) : |
| N(InN), R(InR), InterestingMethodContext(InInterestingMethodContext) {} |
| }; |
| } // end anonymous namespace |
| |
| static AllocationInfo GetAllocationSite(ProgramStateManager &StateMgr, |
| const ExplodedNode *N, SymbolRef Sym) { |
| const ExplodedNode *AllocationNode = N; |
| const ExplodedNode *AllocationNodeInCurrentOrParentContext = N; |
| 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 = nullptr; |
| |
| while (N) { |
| ProgramStateRef St = N->getState(); |
| const LocationContext *NContext = N->getLocationContext(); |
| |
| if (!getRefBinding(St, Sym)) |
| break; |
| |
| StoreManager::FindUniqueBinding FB(Sym); |
| StateMgr.iterBindings(St, FB); |
| |
| if (FB) { |
| const MemRegion *R = FB.getRegion(); |
| // Do not show local variables belonging to a function other than |
| // where the error is reported. |
| if (auto MR = dyn_cast<StackSpaceRegion>(R->getMemorySpace())) |
| if (MR->getStackFrame() == LeakContext->getStackFrame()) |
| FirstBinding = R; |
| } |
| |
| // AllocationNode is the last node in which the symbol was tracked. |
| AllocationNode = N; |
| |
| // AllocationNodeInCurrentContext, is the last node in the current or |
| // parent context in which the symbol was tracked. |
| // |
| // Note that the allocation site might be in the parent context. 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 (NContext == LeakContext || NContext->isParentOf(LeakContext)) |
| AllocationNodeInCurrentOrParentContext = N; |
| |
| // Find the last init that was called on the given symbol and store the |
| // init method's location context. |
| if (!InitMethodContext) |
| if (auto CEP = N->getLocation().getAs<CallEnter>()) { |
| const Stmt *CE = CEP->getCallExpr(); |
| if (const auto *ME = dyn_cast_or_null<ObjCMessageExpr>(CE)) { |
| const Stmt *RecExpr = ME->getInstanceReceiver(); |
| if (RecExpr) { |
| SVal RecV = St->getSVal(RecExpr, NContext); |
| if (ME->getMethodFamily() == OMF_init && RecV.getAsSymbol() == Sym) |
| InitMethodContext = CEP->getCalleeContext(); |
| } |
| } |
| } |
| |
| N = N->getFirstPred(); |
| } |
| |
| // If we are reporting a leak of the object that was allocated with alloc, |
| // mark its init method as interesting. |
| const LocationContext *InterestingMethodContext = nullptr; |
| if (InitMethodContext) { |
| const ProgramPoint AllocPP = AllocationNode->getLocation(); |
| if (Optional<StmtPoint> SP = AllocPP.getAs<StmtPoint>()) |
| if (const ObjCMessageExpr *ME = SP->getStmtAs<ObjCMessageExpr>()) |
| if (ME->getMethodFamily() == OMF_alloc) |
| InterestingMethodContext = InitMethodContext; |
| } |
| |
| // If allocation happened in a function different from the leak node context, |
| // do not report the binding. |
| assert(N && "Could not find allocation node"); |
| |
| if (AllocationNodeInCurrentOrParentContext && |
| AllocationNodeInCurrentOrParentContext->getLocationContext() != |
| LeakContext) |
| FirstBinding = nullptr; |
| |
| return AllocationInfo(AllocationNodeInCurrentOrParentContext, FirstBinding, |
| InterestingMethodContext); |
| } |
| |
| PathDiagnosticPieceRef |
| RefCountReportVisitor::getEndPath(BugReporterContext &BRC, |
| const ExplodedNode *EndN, |
| PathSensitiveBugReport &BR) { |
| BR.markInteresting(Sym); |
| return BugReporterVisitor::getDefaultEndPath(BRC, EndN, BR); |
| } |
| |
| PathDiagnosticPieceRef |
| RefLeakReportVisitor::getEndPath(BugReporterContext &BRC, |
| const ExplodedNode *EndN, |
| PathSensitiveBugReport &BR) { |
| |
| // Tell the BugReporterContext to report cases when the tracked symbol is |
| // assigned to different variables, etc. |
| BR.markInteresting(Sym); |
| |
| // We are reporting a leak. Walk up the graph to get to the first node where |
| // the symbol appeared, and also get the first VarDecl that tracked object |
| // is stored to. |
| AllocationInfo AllocI = GetAllocationSite(BRC.getStateManager(), EndN, Sym); |
| |
| const MemRegion* FirstBinding = AllocI.R; |
| BR.markInteresting(AllocI.InterestingMethodContext); |
| |
| PathDiagnosticLocation L = cast<RefLeakReport>(BR).getEndOfPath(); |
| |
| std::string sbuf; |
| llvm::raw_string_ostream os(sbuf); |
| |
| os << "Object leaked: "; |
| |
| Optional<std::string> RegionDescription = describeRegion(FirstBinding); |
| if (RegionDescription) { |
| os << "object allocated and stored into '" << *RegionDescription << '\''; |
| } else { |
| os << "allocated object of type '" << getPrettyTypeName(Sym->getType()) |
| << "'"; |
| } |
| |
| // Get the retain count. |
| const RefVal* RV = getRefBinding(EndN->getState(), Sym); |
| assert(RV); |
| |
| if (RV->getKind() == RefVal::ErrorLeakReturned) { |
| // FIXME: Per comments in rdar://6320065, "create" only applies to CF |
| // objects. Only "copy", "alloc", "retain" and "new" transfer ownership |
| // to the caller for NS objects. |
| const Decl *D = &EndN->getCodeDecl(); |
| |
| os << (isa<ObjCMethodDecl>(D) ? " is returned from a method " |
| : " is returned from a function "); |
| |
| if (D->hasAttr<CFReturnsNotRetainedAttr>()) { |
| os << "that is annotated as CF_RETURNS_NOT_RETAINED"; |
| } else if (D->hasAttr<NSReturnsNotRetainedAttr>()) { |
| os << "that is annotated as NS_RETURNS_NOT_RETAINED"; |
| } else if (D->hasAttr<OSReturnsNotRetainedAttr>()) { |
| os << "that is annotated as OS_RETURNS_NOT_RETAINED"; |
| } else { |
| if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(D)) { |
| if (BRC.getASTContext().getLangOpts().ObjCAutoRefCount) { |
| os << "managed by Automatic Reference Counting"; |
| } else { |
| os << "whose name ('" << MD->getSelector().getAsString() |
| << "') does not start with " |
| "'copy', 'mutableCopy', 'alloc' or 'new'." |
| " This violates the naming convention rules" |
| " given in the Memory Management Guide for Cocoa"; |
| } |
| } else { |
| const FunctionDecl *FD = cast<FunctionDecl>(D); |
| ObjKind K = RV->getObjKind(); |
| 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"; |
| } else if (RV->getObjKind() == ObjKind::OS) { |
| std::string FuncName = FD->getNameAsString(); |
| os << "whose name ('" << FuncName |
| << "') starts with '" << StringRef(FuncName).substr(0, 3) << "'"; |
| } |
| } |
| } |
| } else { |
| os << " is not referenced later in this execution path and has a retain " |
| "count of +" << RV->getCount(); |
| } |
| |
| return std::make_shared<PathDiagnosticEventPiece>(L, os.str()); |
| } |
| |
| RefCountReport::RefCountReport(const RefCountBug &D, const LangOptions &LOpts, |
| ExplodedNode *n, SymbolRef sym, bool isLeak) |
| : PathSensitiveBugReport(D, D.getDescription(), n), Sym(sym), |
| isLeak(isLeak) { |
| if (!isLeak) |
| addVisitor(std::make_unique<RefCountReportVisitor>(sym)); |
| } |
| |
| RefCountReport::RefCountReport(const RefCountBug &D, const LangOptions &LOpts, |
| ExplodedNode *n, SymbolRef sym, |
| StringRef endText) |
| : PathSensitiveBugReport(D, D.getDescription(), endText, n) { |
| |
| addVisitor(std::make_unique<RefCountReportVisitor>(sym)); |
| } |
| |
| void RefLeakReport::deriveParamLocation(CheckerContext &Ctx, SymbolRef sym) { |
| const SourceManager& SMgr = Ctx.getSourceManager(); |
| |
| if (!sym->getOriginRegion()) |
| return; |
| |
| auto *Region = dyn_cast<DeclRegion>(sym->getOriginRegion()); |
| if (Region) { |
| const Decl *PDecl = Region->getDecl(); |
| if (PDecl && isa<ParmVarDecl>(PDecl)) { |
| PathDiagnosticLocation ParamLocation = |
| PathDiagnosticLocation::create(PDecl, SMgr); |
| Location = ParamLocation; |
| UniqueingLocation = ParamLocation; |
| UniqueingDecl = Ctx.getLocationContext()->getDecl(); |
| } |
| } |
| } |
| |
| void RefLeakReport::deriveAllocLocation(CheckerContext &Ctx, |
| SymbolRef sym) { |
| // Most bug reports are cached at the location where they occurred. |
| // With leaks, we want to unique them by the location where they were |
| // allocated, and only report a single path. To do this, we need to find |
| // the allocation site of a piece of tracked memory, which we do via a |
| // call to GetAllocationSite. This will walk the ExplodedGraph backwards. |
| // 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 = nullptr; |
| |
| const SourceManager& SMgr = Ctx.getSourceManager(); |
| |
| AllocationInfo AllocI = |
| GetAllocationSite(Ctx.getStateManager(), getErrorNode(), sym); |
| |
| AllocNode = AllocI.N; |
| AllocBinding = AllocI.R; |
| markInteresting(AllocI.InterestingMethodContext); |
| |
| // Get the SourceLocation for the allocation site. |
| // FIXME: This will crash the analyzer if an allocation comes from an |
| // implicit call (ex: a destructor call). |
| // (Currently there are no such allocations in Cocoa, though.) |
| AllocStmt = AllocNode->getStmtForDiagnostics(); |
| |
| if (!AllocStmt) { |
| AllocBinding = nullptr; |
| return; |
| } |
| |
| PathDiagnosticLocation AllocLocation = |
| PathDiagnosticLocation::createBegin(AllocStmt, SMgr, |
| AllocNode->getLocationContext()); |
| Location = AllocLocation; |
| |
| // Set uniqieing info, which will be used for unique the bug reports. The |
| // leaks should be uniqued on the allocation site. |
| UniqueingLocation = AllocLocation; |
| UniqueingDecl = AllocNode->getLocationContext()->getDecl(); |
| } |
| |
| void RefLeakReport::createDescription(CheckerContext &Ctx) { |
| assert(Location.isValid() && UniqueingDecl && UniqueingLocation.isValid()); |
| Description.clear(); |
| llvm::raw_string_ostream os(Description); |
| os << "Potential leak of an object"; |
| |
| Optional<std::string> RegionDescription = describeRegion(AllocBinding); |
| if (RegionDescription) { |
| os << " stored into '" << *RegionDescription << '\''; |
| } else { |
| |
| // If we can't figure out the name, just supply the type information. |
| os << " of type '" << getPrettyTypeName(Sym->getType()) << "'"; |
| } |
| } |
| |
| RefLeakReport::RefLeakReport(const RefCountBug &D, const LangOptions &LOpts, |
| ExplodedNode *n, SymbolRef sym, |
| CheckerContext &Ctx) |
| : RefCountReport(D, LOpts, n, sym, /*isLeak=*/true) { |
| |
| deriveAllocLocation(Ctx, sym); |
| if (!AllocBinding) |
| deriveParamLocation(Ctx, sym); |
| |
| createDescription(Ctx); |
| |
| addVisitor(std::make_unique<RefLeakReportVisitor>(sym)); |
| } |