blob: 0be37ff65c7f5a2dd20137997ebdb09843f964cf [file] [log] [blame]
George Karpenkov70c2ee32018-08-17 21:41:07 +00001// RetainCountDiagnostics.cpp - Checks for leaks and other issues -*- C++ -*--//
2//
3// The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9//
10// This file defines diagnostics for RetainCountChecker, which implements
11// a reference count checker for Core Foundation and Cocoa on (Mac OS X).
12//
13//===----------------------------------------------------------------------===//
14
15#include "RetainCountDiagnostics.h"
16#include "RetainCountChecker.h"
17
18using namespace clang;
19using namespace ento;
20using namespace retaincountchecker;
21
22static bool isNumericLiteralExpression(const Expr *E) {
23 // FIXME: This set of cases was copied from SemaExprObjC.
24 return isa<IntegerLiteral>(E) ||
25 isa<CharacterLiteral>(E) ||
26 isa<FloatingLiteral>(E) ||
27 isa<ObjCBoolLiteralExpr>(E) ||
28 isa<CXXBoolLiteralExpr>(E);
29}
30
31std::shared_ptr<PathDiagnosticPiece>
George Karpenkovc82d4572018-09-28 18:49:41 +000032CFRefReportVisitor::VisitNode(const ExplodedNode *N,
George Karpenkov70c2ee32018-08-17 21:41:07 +000033 BugReporterContext &BRC, BugReport &BR) {
34 // FIXME: We will eventually need to handle non-statement-based events
35 // (__attribute__((cleanup))).
36 if (!N->getLocation().getAs<StmtPoint>())
37 return nullptr;
38
39 // Check if the type state has changed.
George Karpenkovc82d4572018-09-28 18:49:41 +000040 ProgramStateRef PrevSt = N->getFirstPred()->getState();
George Karpenkov70c2ee32018-08-17 21:41:07 +000041 ProgramStateRef CurrSt = N->getState();
42 const LocationContext *LCtx = N->getLocationContext();
43
44 const RefVal* CurrT = getRefBinding(CurrSt, Sym);
45 if (!CurrT) return nullptr;
46
47 const RefVal &CurrV = *CurrT;
48 const RefVal *PrevT = getRefBinding(PrevSt, Sym);
49
50 // Create a string buffer to constain all the useful things we want
51 // to tell the user.
52 std::string sbuf;
53 llvm::raw_string_ostream os(sbuf);
54
55 // This is the allocation site since the previous node had no bindings
56 // for this symbol.
57 if (!PrevT) {
58 const Stmt *S = N->getLocation().castAs<StmtPoint>().getStmt();
59
60 if (isa<ObjCIvarRefExpr>(S) &&
61 isSynthesizedAccessor(LCtx->getStackFrame())) {
62 S = LCtx->getStackFrame()->getCallSite();
63 }
64
65 if (isa<ObjCArrayLiteral>(S)) {
66 os << "NSArray literal is an object with a +0 retain count";
67 }
68 else if (isa<ObjCDictionaryLiteral>(S)) {
69 os << "NSDictionary literal is an object with a +0 retain count";
70 }
71 else if (const ObjCBoxedExpr *BL = dyn_cast<ObjCBoxedExpr>(S)) {
72 if (isNumericLiteralExpression(BL->getSubExpr()))
73 os << "NSNumber literal is an object with a +0 retain count";
74 else {
75 const ObjCInterfaceDecl *BoxClass = nullptr;
76 if (const ObjCMethodDecl *Method = BL->getBoxingMethod())
77 BoxClass = Method->getClassInterface();
78
79 // We should always be able to find the boxing class interface,
80 // but consider this future-proofing.
81 if (BoxClass)
82 os << *BoxClass << " b";
83 else
84 os << "B";
85
86 os << "oxed expression produces an object with a +0 retain count";
87 }
88 }
89 else if (isa<ObjCIvarRefExpr>(S)) {
90 os << "Object loaded from instance variable";
91 }
92 else {
93 if (const CallExpr *CE = dyn_cast<CallExpr>(S)) {
94 // Get the name of the callee (if it is available).
95 SVal X = CurrSt->getSValAsScalarOrLoc(CE->getCallee(), LCtx);
96 if (const FunctionDecl *FD = X.getAsFunctionDecl())
97 os << "Call to function '" << *FD << '\'';
98 else
99 os << "function call";
100 }
101 else {
102 assert(isa<ObjCMessageExpr>(S));
103 CallEventManager &Mgr = CurrSt->getStateManager().getCallEventManager();
104 CallEventRef<ObjCMethodCall> Call
105 = Mgr.getObjCMethodCall(cast<ObjCMessageExpr>(S), CurrSt, LCtx);
106
107 switch (Call->getMessageKind()) {
108 case OCM_Message:
109 os << "Method";
110 break;
111 case OCM_PropertyAccess:
112 os << "Property";
113 break;
114 case OCM_Subscript:
115 os << "Subscript";
116 break;
117 }
118 }
119
120 if (CurrV.getObjKind() == RetEffect::CF) {
121 os << " returns a Core Foundation object of type "
122 << Sym->getType().getAsString() << " with a ";
George Karpenkovab0011e2018-08-23 00:26:59 +0000123 } else if (CurrV.getObjKind() == RetEffect::OS) {
124 os << " returns an OSObject of type "
125 << Sym->getType().getAsString() << " with a ";
George Karpenkov70c2ee32018-08-17 21:41:07 +0000126 } else if (CurrV.getObjKind() == RetEffect::Generalized) {
127 os << " returns an object of type " << Sym->getType().getAsString()
128 << " with a ";
129 } else {
130 assert (CurrV.getObjKind() == RetEffect::ObjC);
131 QualType T = Sym->getType();
132 if (!isa<ObjCObjectPointerType>(T)) {
133 os << " returns an Objective-C object with a ";
134 } else {
135 const ObjCObjectPointerType *PT = cast<ObjCObjectPointerType>(T);
136 os << " returns an instance of "
137 << PT->getPointeeType().getAsString() << " with a ";
138 }
139 }
140
141 if (CurrV.isOwned()) {
142 os << "+1 retain count";
143 } else {
144 assert (CurrV.isNotOwned());
145 os << "+0 retain count";
146 }
147 }
148
149 PathDiagnosticLocation Pos(S, BRC.getSourceManager(),
150 N->getLocationContext());
151 return std::make_shared<PathDiagnosticEventPiece>(Pos, os.str());
152 }
153
154 // Gather up the effects that were performed on the object at this
155 // program point
156 SmallVector<ArgEffect, 2> AEffects;
157
158 const ExplodedNode *OrigNode = BRC.getNodeResolver().getOriginalNode(N);
159 if (const RetainSummary *Summ = SummaryLog.lookup(OrigNode)) {
160 // We only have summaries attached to nodes after evaluating CallExpr and
161 // ObjCMessageExprs.
162 const Stmt *S = N->getLocation().castAs<StmtPoint>().getStmt();
163
164 if (const CallExpr *CE = dyn_cast<CallExpr>(S)) {
165 // Iterate through the parameter expressions and see if the symbol
166 // was ever passed as an argument.
167 unsigned i = 0;
168
169 for (CallExpr::const_arg_iterator AI=CE->arg_begin(), AE=CE->arg_end();
170 AI!=AE; ++AI, ++i) {
171
172 // Retrieve the value of the argument. Is it the symbol
173 // we are interested in?
174 if (CurrSt->getSValAsScalarOrLoc(*AI, LCtx).getAsLocSymbol() != Sym)
175 continue;
176
177 // We have an argument. Get the effect!
178 AEffects.push_back(Summ->getArg(i));
179 }
180 } else if (const ObjCMessageExpr *ME = dyn_cast<ObjCMessageExpr>(S)) {
181 if (const Expr *receiver = ME->getInstanceReceiver()) {
182 if (CurrSt->getSValAsScalarOrLoc(receiver, LCtx)
183 .getAsLocSymbol() == Sym) {
184 // The symbol we are tracking is the receiver.
185 AEffects.push_back(Summ->getReceiverEffect());
186 }
187 }
188 }
189 }
190
191 do {
192 // Get the previous type state.
193 RefVal PrevV = *PrevT;
194
195 // Specially handle -dealloc.
196 if (std::find(AEffects.begin(), AEffects.end(), Dealloc) !=
197 AEffects.end()) {
198 // Determine if the object's reference count was pushed to zero.
199 assert(!PrevV.hasSameState(CurrV) && "The state should have changed.");
200 // We may not have transitioned to 'release' if we hit an error.
201 // This case is handled elsewhere.
202 if (CurrV.getKind() == RefVal::Released) {
203 assert(CurrV.getCombinedCounts() == 0);
204 os << "Object released by directly sending the '-dealloc' message";
205 break;
206 }
207 }
208
209 // Determine if the typestate has changed.
210 if (!PrevV.hasSameState(CurrV))
211 switch (CurrV.getKind()) {
212 case RefVal::Owned:
213 case RefVal::NotOwned:
214 if (PrevV.getCount() == CurrV.getCount()) {
215 // Did an autorelease message get sent?
216 if (PrevV.getAutoreleaseCount() == CurrV.getAutoreleaseCount())
217 return nullptr;
218
219 assert(PrevV.getAutoreleaseCount() < CurrV.getAutoreleaseCount());
220 os << "Object autoreleased";
221 break;
222 }
223
224 if (PrevV.getCount() > CurrV.getCount())
225 os << "Reference count decremented.";
226 else
227 os << "Reference count incremented.";
228
229 if (unsigned Count = CurrV.getCount())
230 os << " The object now has a +" << Count << " retain count.";
231
232 break;
233
234 case RefVal::Released:
235 if (CurrV.getIvarAccessHistory() ==
236 RefVal::IvarAccessHistory::ReleasedAfterDirectAccess &&
237 CurrV.getIvarAccessHistory() != PrevV.getIvarAccessHistory()) {
238 os << "Strong instance variable relinquished. ";
239 }
240 os << "Object released.";
241 break;
242
243 case RefVal::ReturnedOwned:
244 // Autoreleases can be applied after marking a node ReturnedOwned.
245 if (CurrV.getAutoreleaseCount())
246 return nullptr;
247
248 os << "Object returned to caller as an owning reference (single "
249 "retain count transferred to caller)";
250 break;
251
252 case RefVal::ReturnedNotOwned:
253 os << "Object returned to caller with a +0 retain count";
254 break;
255
256 default:
257 return nullptr;
258 }
259 } while (0);
260
261 if (os.str().empty())
262 return nullptr; // We have nothing to say!
263
264 const Stmt *S = N->getLocation().castAs<StmtPoint>().getStmt();
265 PathDiagnosticLocation Pos(S, BRC.getSourceManager(),
266 N->getLocationContext());
267 auto P = std::make_shared<PathDiagnosticEventPiece>(Pos, os.str());
268
269 // Add the range by scanning the children of the statement for any bindings
270 // to Sym.
271 for (const Stmt *Child : S->children())
272 if (const Expr *Exp = dyn_cast_or_null<Expr>(Child))
273 if (CurrSt->getSValAsScalarOrLoc(Exp, LCtx).getAsLocSymbol() == Sym) {
274 P->addRange(Exp->getSourceRange());
275 break;
276 }
277
278 return std::move(P);
279}
280
281static Optional<std::string> describeRegion(const MemRegion *MR) {
282 if (const auto *VR = dyn_cast_or_null<VarRegion>(MR))
283 return std::string(VR->getDecl()->getName());
284 // Once we support more storage locations for bindings,
285 // this would need to be improved.
286 return None;
287}
288
289namespace {
290// Find the first node in the current function context that referred to the
291// tracked symbol and the memory location that value was stored to. Note, the
292// value is only reported if the allocation occurred in the same function as
293// the leak. The function can also return a location context, which should be
294// treated as interesting.
295struct AllocationInfo {
296 const ExplodedNode* N;
297 const MemRegion *R;
298 const LocationContext *InterestingMethodContext;
299 AllocationInfo(const ExplodedNode *InN,
300 const MemRegion *InR,
301 const LocationContext *InInterestingMethodContext) :
302 N(InN), R(InR), InterestingMethodContext(InInterestingMethodContext) {}
303};
304} // end anonymous namespace
305
306static AllocationInfo
307GetAllocationSite(ProgramStateManager& StateMgr, const ExplodedNode *N,
308 SymbolRef Sym) {
309 const ExplodedNode *AllocationNode = N;
310 const ExplodedNode *AllocationNodeInCurrentOrParentContext = N;
311 const MemRegion *FirstBinding = nullptr;
312 const LocationContext *LeakContext = N->getLocationContext();
313
314 // The location context of the init method called on the leaked object, if
315 // available.
316 const LocationContext *InitMethodContext = nullptr;
317
318 while (N) {
319 ProgramStateRef St = N->getState();
320 const LocationContext *NContext = N->getLocationContext();
321
322 if (!getRefBinding(St, Sym))
323 break;
324
325 StoreManager::FindUniqueBinding FB(Sym);
326 StateMgr.iterBindings(St, FB);
327
328 if (FB) {
329 const MemRegion *R = FB.getRegion();
330 const VarRegion *VR = R->getBaseRegion()->getAs<VarRegion>();
331 // Do not show local variables belonging to a function other than
332 // where the error is reported.
333 if (!VR || VR->getStackFrame() == LeakContext->getStackFrame())
334 FirstBinding = R;
335 }
336
337 // AllocationNode is the last node in which the symbol was tracked.
338 AllocationNode = N;
339
340 // AllocationNodeInCurrentContext, is the last node in the current or
341 // parent context in which the symbol was tracked.
342 //
343 // Note that the allocation site might be in the parent conext. For example,
344 // the case where an allocation happens in a block that captures a reference
345 // to it and that reference is overwritten/dropped by another call to
346 // the block.
347 if (NContext == LeakContext || NContext->isParentOf(LeakContext))
348 AllocationNodeInCurrentOrParentContext = N;
349
350 // Find the last init that was called on the given symbol and store the
351 // init method's location context.
352 if (!InitMethodContext)
353 if (Optional<CallEnter> CEP = N->getLocation().getAs<CallEnter>()) {
354 const Stmt *CE = CEP->getCallExpr();
355 if (const ObjCMessageExpr *ME = dyn_cast_or_null<ObjCMessageExpr>(CE)) {
356 const Stmt *RecExpr = ME->getInstanceReceiver();
357 if (RecExpr) {
358 SVal RecV = St->getSVal(RecExpr, NContext);
359 if (ME->getMethodFamily() == OMF_init && RecV.getAsSymbol() == Sym)
360 InitMethodContext = CEP->getCalleeContext();
361 }
362 }
363 }
364
365 N = N->pred_empty() ? nullptr : *(N->pred_begin());
366 }
367
368 // If we are reporting a leak of the object that was allocated with alloc,
369 // mark its init method as interesting.
370 const LocationContext *InterestingMethodContext = nullptr;
371 if (InitMethodContext) {
372 const ProgramPoint AllocPP = AllocationNode->getLocation();
373 if (Optional<StmtPoint> SP = AllocPP.getAs<StmtPoint>())
374 if (const ObjCMessageExpr *ME = SP->getStmtAs<ObjCMessageExpr>())
375 if (ME->getMethodFamily() == OMF_alloc)
376 InterestingMethodContext = InitMethodContext;
377 }
378
379 // If allocation happened in a function different from the leak node context,
380 // do not report the binding.
381 assert(N && "Could not find allocation node");
382 if (N->getLocationContext() != LeakContext) {
383 FirstBinding = nullptr;
384 }
385
386 return AllocationInfo(AllocationNodeInCurrentOrParentContext,
387 FirstBinding,
388 InterestingMethodContext);
389}
390
391std::shared_ptr<PathDiagnosticPiece>
392CFRefReportVisitor::getEndPath(BugReporterContext &BRC,
393 const ExplodedNode *EndN, BugReport &BR) {
394 BR.markInteresting(Sym);
395 return BugReporterVisitor::getDefaultEndPath(BRC, EndN, BR);
396}
397
398std::shared_ptr<PathDiagnosticPiece>
399CFRefLeakReportVisitor::getEndPath(BugReporterContext &BRC,
400 const ExplodedNode *EndN, BugReport &BR) {
401
402 // Tell the BugReporterContext to report cases when the tracked symbol is
403 // assigned to different variables, etc.
404 BR.markInteresting(Sym);
405
406 // We are reporting a leak. Walk up the graph to get to the first node where
407 // the symbol appeared, and also get the first VarDecl that tracked object
408 // is stored to.
409 AllocationInfo AllocI =
410 GetAllocationSite(BRC.getStateManager(), EndN, Sym);
411
412 const MemRegion* FirstBinding = AllocI.R;
413 BR.markInteresting(AllocI.InterestingMethodContext);
414
415 SourceManager& SM = BRC.getSourceManager();
416
417 // Compute an actual location for the leak. Sometimes a leak doesn't
418 // occur at an actual statement (e.g., transition between blocks; end
419 // of function) so we need to walk the graph and compute a real location.
420 const ExplodedNode *LeakN = EndN;
421 PathDiagnosticLocation L = PathDiagnosticLocation::createEndOfPath(LeakN, SM);
422
423 std::string sbuf;
424 llvm::raw_string_ostream os(sbuf);
425
426 os << "Object leaked: ";
427
428 Optional<std::string> RegionDescription = describeRegion(FirstBinding);
429 if (RegionDescription) {
430 os << "object allocated and stored into '" << *RegionDescription << '\'';
431 }
432 else
433 os << "allocated object";
434
435 // Get the retain count.
436 const RefVal* RV = getRefBinding(EndN->getState(), Sym);
437 assert(RV);
438
439 if (RV->getKind() == RefVal::ErrorLeakReturned) {
440 // FIXME: Per comments in rdar://6320065, "create" only applies to CF
441 // objects. Only "copy", "alloc", "retain" and "new" transfer ownership
442 // to the caller for NS objects.
443 const Decl *D = &EndN->getCodeDecl();
444
445 os << (isa<ObjCMethodDecl>(D) ? " is returned from a method "
446 : " is returned from a function ");
447
448 if (D->hasAttr<CFReturnsNotRetainedAttr>())
449 os << "that is annotated as CF_RETURNS_NOT_RETAINED";
450 else if (D->hasAttr<NSReturnsNotRetainedAttr>())
451 os << "that is annotated as NS_RETURNS_NOT_RETAINED";
452 else {
453 if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(D)) {
454 if (BRC.getASTContext().getLangOpts().ObjCAutoRefCount) {
455 os << "managed by Automatic Reference Counting";
456 } else {
457 os << "whose name ('" << MD->getSelector().getAsString()
458 << "') does not start with "
459 "'copy', 'mutableCopy', 'alloc' or 'new'."
460 " This violates the naming convention rules"
461 " given in the Memory Management Guide for Cocoa";
462 }
George Karpenkov6e9fd132018-08-22 01:17:09 +0000463 } else {
George Karpenkov70c2ee32018-08-17 21:41:07 +0000464 const FunctionDecl *FD = cast<FunctionDecl>(D);
465 os << "whose name ('" << *FD
466 << "') does not contain 'Copy' or 'Create'. This violates the naming"
467 " convention rules given in the Memory Management Guide for Core"
468 " Foundation";
469 }
470 }
471 }
472 else
473 os << " is not referenced later in this execution path and has a retain "
474 "count of +" << RV->getCount();
475
476 return std::make_shared<PathDiagnosticEventPiece>(L, os.str());
477}
478
479void CFRefLeakReport::deriveParamLocation(CheckerContext &Ctx, SymbolRef sym) {
480 const SourceManager& SMgr = Ctx.getSourceManager();
481
482 if (!sym->getOriginRegion())
483 return;
484
485 auto *Region = dyn_cast<DeclRegion>(sym->getOriginRegion());
486 if (Region) {
487 const Decl *PDecl = Region->getDecl();
488 if (PDecl && isa<ParmVarDecl>(PDecl)) {
489 PathDiagnosticLocation ParamLocation = PathDiagnosticLocation::create(PDecl, SMgr);
490 Location = ParamLocation;
491 UniqueingLocation = ParamLocation;
492 UniqueingDecl = Ctx.getLocationContext()->getDecl();
493 }
494 }
495}
496
497void CFRefLeakReport::deriveAllocLocation(CheckerContext &Ctx,SymbolRef sym) {
498 // Most bug reports are cached at the location where they occurred.
499 // With leaks, we want to unique them by the location where they were
500 // allocated, and only report a single path. To do this, we need to find
501 // the allocation site of a piece of tracked memory, which we do via a
502 // call to GetAllocationSite. This will walk the ExplodedGraph backwards.
503 // Note that this is *not* the trimmed graph; we are guaranteed, however,
504 // that all ancestor nodes that represent the allocation site have the
505 // same SourceLocation.
506 const ExplodedNode *AllocNode = nullptr;
507
508 const SourceManager& SMgr = Ctx.getSourceManager();
509
510 AllocationInfo AllocI =
511 GetAllocationSite(Ctx.getStateManager(), getErrorNode(), sym);
512
513 AllocNode = AllocI.N;
514 AllocBinding = AllocI.R;
515 markInteresting(AllocI.InterestingMethodContext);
516
517 // Get the SourceLocation for the allocation site.
518 // FIXME: This will crash the analyzer if an allocation comes from an
519 // implicit call (ex: a destructor call).
520 // (Currently there are no such allocations in Cocoa, though.)
521 AllocStmt = PathDiagnosticLocation::getStmt(AllocNode);
522
523 if (!AllocStmt) {
524 AllocBinding = nullptr;
525 return;
526 }
527
528 PathDiagnosticLocation AllocLocation =
529 PathDiagnosticLocation::createBegin(AllocStmt, SMgr,
530 AllocNode->getLocationContext());
531 Location = AllocLocation;
532
533 // Set uniqieing info, which will be used for unique the bug reports. The
534 // leaks should be uniqued on the allocation site.
535 UniqueingLocation = AllocLocation;
536 UniqueingDecl = AllocNode->getLocationContext()->getDecl();
537}
538
539void CFRefLeakReport::createDescription(CheckerContext &Ctx,
540 bool IncludeAllocationLine) {
541 assert(Location.isValid() && UniqueingDecl && UniqueingLocation.isValid());
542 Description.clear();
543 llvm::raw_string_ostream os(Description);
544 os << "Potential leak of an object";
545
546 Optional<std::string> RegionDescription = describeRegion(AllocBinding);
547 if (RegionDescription) {
548 os << " stored into '" << *RegionDescription << '\'';
549 if (IncludeAllocationLine) {
550 FullSourceLoc SL(AllocStmt->getBeginLoc(), Ctx.getSourceManager());
551 os << " (allocated on line " << SL.getSpellingLineNumber() << ")";
552 }
553 }
554}
555
556CFRefLeakReport::CFRefLeakReport(CFRefBug &D, const LangOptions &LOpts,
557 const SummaryLogTy &Log,
558 ExplodedNode *n, SymbolRef sym,
559 CheckerContext &Ctx,
560 bool IncludeAllocationLine)
561 : CFRefReport(D, LOpts, Log, n, sym, false) {
562
563 deriveAllocLocation(Ctx, sym);
564 if (!AllocBinding)
565 deriveParamLocation(Ctx, sym);
566
567 createDescription(Ctx, IncludeAllocationLine);
568
569 addVisitor(llvm::make_unique<CFRefLeakReportVisitor>(sym, Log));
570}