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-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file defines diagnostics for RetainCountChecker, which implements
10// 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>(Val: 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(X: 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 else
81 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 S
117/// 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(Index: Idx).getAsRegion())
127 if (const auto *TR = dyn_cast<TypedValueRegion>(Val: MR))
128 if (CurrSt->getSVal(R: MR, T: 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>(Val: 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>(Val: 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>(Val: 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>(Val: S))
154 if (auto Out = findMetaClassAlloc(Callee: 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>(Val: S)) {
166 // Get the name of the callee (if it is available)
167 // from the tracked SVal.
168 SVal X = CurrSt->getSValAsScalarOrLoc(S: 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>(Val: CE->getCalleeDecl());
174
175 if (const auto *MD = dyn_cast<CXXMethodDecl>(Val: 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>(Val: S)) {
183 os << "Operator 'new'";
184 } else {
185 assert(isa<ObjCMessageExpr>(S));
186 CallEventRef<ObjCMethodCall> Call = Mgr.getObjCMethodCall(
187 E: cast<ObjCMessageExpr>(Val: S), State: CurrSt, LCtx, ElemRef: {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, State: CurrSt, LC: LCtx, ElemRef: {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, QT: 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>(Val: T)) {
223 os << "an Objective-C object with a ";
224 } else {
225 const ObjCObjectPointerType *PT = cast<ObjCObjectPointerType>(Val&: 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&: os, Policy: 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(V: RV).isConstrainedTrue()) {
248 os << " (assuming the call returns zero)";
249 } else if (CurrSt->isNonNull(V: 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(Ptr: &x);
273 ID.AddPointer(Ptr: 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 retaincountchecker
300} // end namespace ento
301} // end namespace clang
302
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 exit
322/// 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(Pred: N);
331 if (!CN)
332 return nullptr;
333
334 CallEventRef<> Call = CEMgr.getCaller(CalleeCtx: N->getStackFrame(), State: 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(Index: I).getAsLocSymbol()) {
346 const RefVal *CountBeforeCall = getRefBinding(State: CN->getState(), Sym: SR);
347 const RefVal *CountAtExit = getRefBinding(State: N->getState(), Sym: 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&: os, Policy: 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(P: CallExitLoc, SMng: SM);
370 return std::make_shared<PathDiagnosticEventPiece>(args&: L, args&: 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(State: N->getState(), Sym);
383
384 if (&Src->getParent()->getEntry() != Src || !CurrT ||
385 getRefBinding(State: N->getFirstPred()->getState(), Sym))
386 return nullptr;
387
388 const auto *VR = cast<VarRegion>(Val: cast<SymbolRegionValue>(Val: Sym)->getRegion());
389 const auto *PVD = cast<ParmVarDecl>(Val: 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>(args&: L, args&: s);
402}
403
404PathDiagnosticPieceRef
405RefCountReportVisitor::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, CallExitLoc&: *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 events
417 // (__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(State: CurrSt, Sym);
428 if (!CurrT)
429 return nullptr;
430
431 const RefVal &CurrV = *CurrT;
432 const RefVal *PrevT = getRefBinding(State: PrevSt, Sym);
433
434 // Create a string buffer to constain all the useful things we want
435 // 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(P: N->getLocation(), SMng: SM);
442 return std::make_shared<PathDiagnosticEventPiece>(args&: Pos, args&: sbuf);
443 }
444
445 // This is the allocation site since the previous node had no bindings
446 // for this symbol.
447 if (!PrevT) {
448 const Stmt *S = N->getLocation().castAs<StmtPoint>().getStmt();
449
450 if (isa<ObjCIvarRefExpr>(Val: S) &&
451 isSynthesizedAccessor(SFC: LCtx->getStackFrame())) {
452 S = LCtx->getStackFrame()->getCallSite();
453 }
454
455 if (isa<ObjCArrayLiteral>(Val: S)) {
456 os << "NSArray literal is an object with a +0 retain count";
457 } else if (isa<ObjCDictionaryLiteral>(Val: S)) {
458 os << "NSDictionary literal is an object with a +0 retain count";
459 } else if (const ObjCBoxedExpr *BL = dyn_cast<ObjCBoxedExpr>(Val: S)) {
460 if (isNumericLiteralExpression(E: 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>(Val: 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>(args&: Pos, args&: sbuf);
485 }
486
487 // Gather up the effects that were performed on the object at this
488 // program point
489 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 and
499 // ObjCMessageExprs.
500 const Stmt *S = N->getLocation().castAs<StmtPoint>().getStmt();
501
502 if (const CallExpr *CE = dyn_cast<CallExpr>(Val: S)) {
503 // Iterate through the parameter expressions and see if the symbol
504 // 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 symbol
510 // we are interested in?
511 if (CurrSt->getSValAsScalarOrLoc(S: *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>(Val: S)) {
518 if (const Expr *receiver = ME->getInstanceReceiver()) {
519 if (CurrSt->getSValAsScalarOrLoc(S: 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>(args&: Pos, args&: sbuf);
538
539 // Add the range by scanning the children of the statement for any bindings
540 // to Sym.
541 for (const Stmt *Child : S->children())
542 if (const Expr *Exp = dyn_cast_or_null<Expr>(Val: Child))
543 if (CurrSt->getSValAsScalarOrLoc(S: Exp, LCtx).getAsLocSymbol() == Sym) {
544 P->addRange(R: 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>(Val: 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>(Val: R))
577 Result.emplace_back(Args&: R, Args&: Val);
578
579 return true;
580 }
581};
582} // namespace
583
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(state: Node->getState(), F&: 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 the
599// tracked symbol and the memory location that value was stored to. Note, the
600// value is only reported if the allocation occurred in the same function as
601// the leak. The function can also return a location context, which should be
602// 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 namespace
613
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, if
622 // 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(State: St, Sym))
630 break;
631
632 StoreManager::FindUniqueBinding FB(Sym);
633 StateMgr.iterBindings(state: St, F&: FB);
634
635 if (FB) {
636 const MemRegion *R = FB.getRegion();
637 // Do not show local variables belonging to a function other than
638 // where the error is reported.
639 if (const auto *MR = R->getMemorySpaceAs<StackSpaceRegion>(State: 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 or
648 // 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 reference
652 // to it and that reference is overwritten/dropped by another call to
653 // the block.
654 if (NContext == LeakContext || NContext->isParentOf(LC: LeakContext))
655 AllocationNodeInCurrentOrParentContext = N;
656
657 // Find the last init that was called on the given symbol and store the
658 // 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>(Val: CE)) {
663 const Stmt *RecExpr = ME->getInstanceReceiver();
664 if (RecExpr) {
665 SVal RecV = St->getSVal(Ex: RecExpr, LCtx: 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
699PathDiagnosticPieceRef
700RefCountReportVisitor::getEndPath(BugReporterContext &BRC,
701 const ExplodedNode *EndN,
702 PathSensitiveBugReport &BR) {
703 BR.markInteresting(sym: Sym);
704 return BugReporterVisitor::getDefaultEndPath(BRC, N: EndN, BR);
705}
706
707PathDiagnosticPieceRef
708RefLeakReportVisitor::getEndPath(BugReporterContext &BRC,
709 const ExplodedNode *EndN,
710 PathSensitiveBugReport &BR) {
711
712 // Tell the BugReporterContext to report cases when the tracked symbol is
713 // assigned to different variables, etc.
714 BR.markInteresting(sym: Sym);
715
716 PathDiagnosticLocation L = cast<RefLeakReport>(Val&: 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(MR: LastBinding);
724 if (RegionDescription) {
725 os << "object allocated and stored into '" << *RegionDescription << '\'';
726 } else {
727 os << "allocated object of type '" << getPrettyTypeName(QT: Sym->getType())
728 << "'";
729 }
730
731 // Get the retain count.
732 const RefVal *RV = getRefBinding(State: EndN->getState(), Sym);
733 assert(RV);
734
735 if (RV->getKind() == RefVal::ErrorLeakReturned) {
736 const Decl *D = &EndN->getCodeDecl();
737
738 os << (isa<ObjCMethodDecl>(Val: 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>(Val: 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>(Val: D);
760 ObjKind K = RV->getObjKind();
761 if (K == ObjKind::ObjC || K == ObjKind::CF) {
762 os << "whose name ('" << *FD
763 << "') 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(Start: 0, N: 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>(args&: L, args&: 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>(ConstructorArgs&: sym, ConstructorArgs&: 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>(ConstructorArgs&: sym, /*IsReleaseUnowned=*/ConstructorArgs: 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>(Val: Sym->getOriginRegion());
806 if (Region) {
807 const Decl *PDecl = Region->getDecl();
808 if (isa_and_nonnull<ParmVarDecl>(Val: PDecl)) {
809 PathDiagnosticLocation ParamLocation =
810 PathDiagnosticLocation::create(D: PDecl, SM: 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 were
821 // allocated, and only report a single path. To do this, we need to find
822 // the allocation site of a piece of tracked memory, which we do via a
823 // 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 the
826 // same SourceLocation.
827 const ExplodedNode *AllocNode = nullptr;
828
829 const SourceManager &SMgr = Ctx.getSourceManager();
830
831 AllocationInfo AllocI =
832 GetAllocationSite(StateMgr&: Ctx.getStateManager(), N: getErrorNode(), Sym);
833
834 AllocNode = AllocI.N;
835 AllocFirstBinding = AllocI.R;
836 markInteresting(LC: AllocI.InterestingMethodContext);
837
838 // Get the SourceLocation for the allocation site.
839 // FIXME: This will crash the analyzer if an allocation comes from an
840 // 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 S: AllocStmt, SM: SMgr, LAC: AllocNode->getLocationContext());
851 Location = AllocLocation;
852
853 // Set uniqieing info, which will be used for unique the bug reports. The
854 // 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(MR: 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(QT: 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 any
880 // better binding to report.
881 return;
882
883 // If the original region still contains the leaking symbol...
884 if (Node->getState()->getSVal(R: 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 leaking
891 // when the actual leak happens. It means that it can be confusing for the
892 // 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 a
901 // rightful confusion because 'Original' is actually released.
902 // We should complain about 'New' instead.
903 Bindings AllVarBindings =
904 getAllVarBindingsForSymbol(Manager&: Ctx.getStateManager(), Node, Sym);
905
906 // While looking for the last var bindings, we can still find
907 // `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(Range: llvm::make_first_range(c&: AllVarBindings),
911 Element: 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 as
916 // 'AllocFirstBinding', we need to explain how the leaking object
917 // got from one to another.
918 //
919 // NOTE: We use the actual SVal stored in AllocBindingToReport here because
920 // trackStoredValue compares SVal's and it can get trickier for
921 // something like derived regions if we want to construct SVal from
922 // Sym. Instead, we take the value that is definitely stored in that
923 // region, thus guaranteeing that trackStoredValue will work.
924 bugreporter::trackStoredValue(V: AllVarBindings[0].second,
925 R: AllocBindingToReport, Report&: *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, Node: N);
938
939 if (!AllocFirstBinding)
940 deriveParamLocation(Ctx);
941
942 createDescription(Ctx);
943
944 addVisitor<RefLeakReportVisitor>(ConstructorArgs&: Sym, ConstructorArgs&: AllocBindingToReport);
945}
946