clang 22.0.0git
CheckObjCDealloc.cpp
Go to the documentation of this file.
1//==- CheckObjCDealloc.cpp - Check ObjC -dealloc implementation --*- 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 checker analyzes Objective-C -dealloc methods and their callees
10// to warn about improper releasing of instance variables that back synthesized
11// properties. It warns about missing releases in the following cases:
12// - When a class has a synthesized instance variable for a 'retain' or 'copy'
13// property and lacks a -dealloc method in its implementation.
14// - When a class has a synthesized instance variable for a 'retain'/'copy'
15// property but the ivar is not released in -dealloc by either -release
16// or by nilling out the property.
17//
18// It warns about extra releases in -dealloc (but not in callees) when a
19// synthesized instance variable is released in the following cases:
20// - When the property is 'assign' and is not 'readonly'.
21// - When the property is 'weak'.
22//
23// This checker only warns for instance variables synthesized to back
24// properties. Handling the more general case would require inferring whether
25// an instance variable is stored retained or not. For synthesized properties,
26// this is specified in the property declaration itself.
27//
28//===----------------------------------------------------------------------===//
29
32#include "clang/AST/Attr.h"
33#include "clang/AST/DeclObjC.h"
34#include "clang/AST/Expr.h"
35#include "clang/AST/ExprObjC.h"
47#include "llvm/Support/raw_ostream.h"
48#include <optional>
49
50using namespace clang;
51using namespace ento;
52
53/// Indicates whether an instance variable is required to be released in
54/// -dealloc.
56 /// The instance variable must be released, either by calling
57 /// -release on it directly or by nilling it out with a property setter.
59
60 /// The instance variable must not be directly released with -release.
62
63 /// The requirement for the instance variable could not be determined.
65};
66
67/// Returns true if the property implementation is synthesized and the
68/// type of the property is retainable.
70 const ObjCIvarDecl **ID,
71 const ObjCPropertyDecl **PD) {
72
74 return false;
75
76 (*ID) = I->getPropertyIvarDecl();
77 if (!(*ID))
78 return false;
79
80 QualType T = (*ID)->getType();
81 if (!T->isObjCRetainableType())
82 return false;
83
84 (*PD) = I->getPropertyDecl();
85 // Shouldn't be able to synthesize a property that doesn't exist.
86 assert(*PD);
87
88 return true;
89}
90
91namespace {
92
93class ObjCDeallocChecker
94 : public Checker<check::ASTDecl<ObjCImplementationDecl>,
95 check::PreObjCMessage, check::PostObjCMessage,
96 check::PreCall,
97 check::BeginFunction, check::EndFunction,
98 eval::Assume,
99 check::PointerEscape,
100 check::PreStmt<ReturnStmt>> {
101
102 mutable const IdentifierInfo *NSObjectII = nullptr;
103 mutable const IdentifierInfo *SenTestCaseII = nullptr;
104 mutable const IdentifierInfo *XCTestCaseII = nullptr;
105 mutable const IdentifierInfo *Block_releaseII = nullptr;
106 mutable const IdentifierInfo *CIFilterII = nullptr;
107
108 mutable Selector DeallocSel;
109 mutable Selector ReleaseSel;
110
111 const BugType MissingReleaseBugType{this, "Missing ivar release (leak)",
113 const BugType ExtraReleaseBugType{this, "Extra ivar release",
115 const BugType MistakenDeallocBugType{this, "Mistaken dealloc",
117
118public:
119 void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager& Mgr,
120 BugReporter &BR) const;
121 void checkBeginFunction(CheckerContext &Ctx) const;
122 void checkPreObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;
123 void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
124 void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;
125
126 ProgramStateRef evalAssume(ProgramStateRef State, SVal Cond,
127 bool Assumption) const;
128
129 ProgramStateRef checkPointerEscape(ProgramStateRef State,
130 const InvalidatedSymbols &Escaped,
131 const CallEvent *Call,
132 PointerEscapeKind Kind) const;
133 void checkPreStmt(const ReturnStmt *RS, CheckerContext &C) const;
134 void checkEndFunction(const ReturnStmt *RS, CheckerContext &Ctx) const;
135
136private:
137 void diagnoseMissingReleases(CheckerContext &C) const;
138
139 bool diagnoseExtraRelease(SymbolRef ReleasedValue, const ObjCMethodCall &M,
140 CheckerContext &C) const;
141
142 bool diagnoseMistakenDealloc(SymbolRef DeallocedValue,
143 const ObjCMethodCall &M,
144 CheckerContext &C) const;
145
146 SymbolRef getValueReleasedByNillingOut(const ObjCMethodCall &M,
147 CheckerContext &C) const;
148
149 const ObjCIvarRegion *getIvarRegionForIvarSymbol(SymbolRef IvarSym) const;
150 SymbolRef getInstanceSymbolFromIvarSymbol(SymbolRef IvarSym) const;
151
152 const ObjCPropertyImplDecl*
153 findPropertyOnDeallocatingInstance(SymbolRef IvarSym,
154 CheckerContext &C) const;
155
157 getDeallocReleaseRequirement(const ObjCPropertyImplDecl *PropImpl) const;
158
159 bool isInInstanceDealloc(const CheckerContext &C, SVal &SelfValOut) const;
160 bool isInInstanceDealloc(const CheckerContext &C, const LocationContext *LCtx,
161 SVal &SelfValOut) const;
162 bool instanceDeallocIsOnStack(const CheckerContext &C,
163 SVal &InstanceValOut) const;
164
165 bool isSuperDeallocMessage(const ObjCMethodCall &M) const;
166
167 const ObjCImplDecl *getContainingObjCImpl(const LocationContext *LCtx) const;
168
169 const ObjCPropertyDecl *
170 findShadowedPropertyDecl(const ObjCPropertyImplDecl *PropImpl) const;
171
172 void transitionToReleaseValue(CheckerContext &C, SymbolRef Value) const;
173 ProgramStateRef removeValueRequiringRelease(ProgramStateRef State,
174 SymbolRef InstanceSym,
175 SymbolRef ValueSym) const;
176
177 void initIdentifierInfoAndSelectors(ASTContext &Ctx) const;
178
179 bool classHasSeparateTeardown(const ObjCInterfaceDecl *ID) const;
180
181 bool isReleasedByCIFilterDealloc(const ObjCPropertyImplDecl *PropImpl) const;
182 bool isNibLoadedIvarWithoutRetain(const ObjCPropertyImplDecl *PropImpl) const;
183};
184} // End anonymous namespace.
185
186
187/// Maps from the symbol for a class instance to the set of
188/// symbols remaining that must be released in -dealloc.
191
192
193/// An AST check that diagnose when the class requires a -dealloc method and
194/// is missing one.
195void ObjCDeallocChecker::checkASTDecl(const ObjCImplementationDecl *D,
196 AnalysisManager &Mgr,
197 BugReporter &BR) const {
198 assert(Mgr.getLangOpts().getGC() != LangOptions::GCOnly);
199 assert(!Mgr.getLangOpts().ObjCAutoRefCount);
200 initIdentifierInfoAndSelectors(Mgr.getASTContext());
201
202 const ObjCInterfaceDecl *ID = D->getClassInterface();
203 // If the class is known to have a lifecycle with a separate teardown method
204 // then it may not require a -dealloc method.
205 if (classHasSeparateTeardown(ID))
206 return;
207
208 // Does the class contain any synthesized properties that are retainable?
209 // If not, skip the check entirely.
210 const ObjCPropertyImplDecl *PropImplRequiringRelease = nullptr;
211 bool HasOthers = false;
212 for (const auto *I : D->property_impls()) {
213 if (getDeallocReleaseRequirement(I) == ReleaseRequirement::MustRelease) {
214 if (!PropImplRequiringRelease)
215 PropImplRequiringRelease = I;
216 else {
217 HasOthers = true;
218 break;
219 }
220 }
221 }
222
223 if (!PropImplRequiringRelease)
224 return;
225
226 const ObjCMethodDecl *MD = nullptr;
227
228 // Scan the instance methods for "dealloc".
229 for (const auto *I : D->instance_methods()) {
230 if (I->getSelector() == DeallocSel) {
231 MD = I;
232 break;
233 }
234 }
235
236 if (!MD) { // No dealloc found.
237 const char* Name = "Missing -dealloc";
238
239 std::string Buf;
240 llvm::raw_string_ostream OS(Buf);
241 OS << "'" << *D << "' lacks a 'dealloc' instance method but "
242 << "must release '" << *PropImplRequiringRelease->getPropertyIvarDecl()
243 << "'";
244
245 if (HasOthers)
246 OS << " and others";
247 PathDiagnosticLocation DLoc =
249
251 DLoc);
252 return;
253 }
254}
255
256/// If this is the beginning of -dealloc, mark the values initially stored in
257/// instance variables that must be released by the end of -dealloc
258/// as unreleased in the state.
259void ObjCDeallocChecker::checkBeginFunction(
260 CheckerContext &C) const {
261 initIdentifierInfoAndSelectors(C.getASTContext());
262
263 // Only do this if the current method is -dealloc.
264 SVal SelfVal;
265 if (!isInInstanceDealloc(C, SelfVal))
266 return;
267
268 SymbolRef SelfSymbol = SelfVal.getAsSymbol();
269
270 const LocationContext *LCtx = C.getLocationContext();
271 ProgramStateRef InitialState = C.getState();
272
273 ProgramStateRef State = InitialState;
274
275 SymbolSet::Factory &F = State->getStateManager().get_context<SymbolSet>();
276
277 // Symbols that must be released by the end of the -dealloc;
278 SymbolSet RequiredReleases = F.getEmptySet();
279
280 // If we're an inlined -dealloc, we should add our symbols to the existing
281 // set from our subclass.
282 if (const SymbolSet *CurrSet = State->get<UnreleasedIvarMap>(SelfSymbol))
283 RequiredReleases = *CurrSet;
284
285 for (auto *PropImpl : getContainingObjCImpl(LCtx)->property_impls()) {
286 ReleaseRequirement Requirement = getDeallocReleaseRequirement(PropImpl);
287 if (Requirement != ReleaseRequirement::MustRelease)
288 continue;
289
290 SVal LVal = State->getLValue(PropImpl->getPropertyIvarDecl(), SelfVal);
291 std::optional<Loc> LValLoc = LVal.getAs<Loc>();
292 if (!LValLoc)
293 continue;
294
295 SVal InitialVal = State->getSVal(*LValLoc);
296 SymbolRef Symbol = InitialVal.getAsSymbol();
297 if (!Symbol || !isa<SymbolRegionValue>(Symbol))
298 continue;
299
300 // Mark the value as requiring a release.
301 RequiredReleases = F.add(RequiredReleases, Symbol);
302 }
303
304 if (!RequiredReleases.isEmpty()) {
305 State = State->set<UnreleasedIvarMap>(SelfSymbol, RequiredReleases);
306 }
307
308 if (State != InitialState) {
309 C.addTransition(State);
310 }
311}
312
313/// Given a symbol for an ivar, return the ivar region it was loaded from.
314/// Returns nullptr if the instance symbol cannot be found.
315const ObjCIvarRegion *
316ObjCDeallocChecker::getIvarRegionForIvarSymbol(SymbolRef IvarSym) const {
317 return dyn_cast_or_null<ObjCIvarRegion>(IvarSym->getOriginRegion());
318}
319
320/// Given a symbol for an ivar, return a symbol for the instance containing
321/// the ivar. Returns nullptr if the instance symbol cannot be found.
323ObjCDeallocChecker::getInstanceSymbolFromIvarSymbol(SymbolRef IvarSym) const {
324
325 const ObjCIvarRegion *IvarRegion = getIvarRegionForIvarSymbol(IvarSym);
326 if (!IvarRegion)
327 return nullptr;
328
329 const SymbolicRegion *SR = IvarRegion->getSymbolicBase();
330 assert(SR && "Symbolic base should not be nullptr");
331 return SR->getSymbol();
332}
333
334/// If we are in -dealloc or -dealloc is on the stack, handle the call if it is
335/// a release or a nilling-out property setter.
336void ObjCDeallocChecker::checkPreObjCMessage(
337 const ObjCMethodCall &M, CheckerContext &C) const {
338 // Only run if -dealloc is on the stack.
339 SVal DeallocedInstance;
340 if (!instanceDeallocIsOnStack(C, DeallocedInstance))
341 return;
342
343 SymbolRef ReleasedValue = nullptr;
344
345 if (M.getSelector() == ReleaseSel) {
346 ReleasedValue = M.getReceiverSVal().getAsSymbol();
347 } else if (M.getSelector() == DeallocSel && !M.isReceiverSelfOrSuper()) {
348 if (diagnoseMistakenDealloc(M.getReceiverSVal().getAsSymbol(), M, C))
349 return;
350 }
351
352 if (ReleasedValue) {
353 // An instance variable symbol was released with -release:
354 // [_property release];
355 if (diagnoseExtraRelease(ReleasedValue,M, C))
356 return;
357 } else {
358 // An instance variable symbol was released nilling out its property:
359 // self.property = nil;
360 ReleasedValue = getValueReleasedByNillingOut(M, C);
361 }
362
363 if (!ReleasedValue)
364 return;
365
366 transitionToReleaseValue(C, ReleasedValue);
367}
368
369/// If we are in -dealloc or -dealloc is on the stack, handle the call if it is
370/// call to Block_release().
371void ObjCDeallocChecker::checkPreCall(const CallEvent &Call,
372 CheckerContext &C) const {
373 const IdentifierInfo *II = Call.getCalleeIdentifier();
374 if (II != Block_releaseII)
375 return;
376
377 if (Call.getNumArgs() != 1)
378 return;
379
380 SymbolRef ReleasedValue = Call.getArgSVal(0).getAsSymbol();
381 if (!ReleasedValue)
382 return;
383
384 transitionToReleaseValue(C, ReleasedValue);
385}
386/// If the message was a call to '[super dealloc]', diagnose any missing
387/// releases.
388void ObjCDeallocChecker::checkPostObjCMessage(
389 const ObjCMethodCall &M, CheckerContext &C) const {
390 // We perform this check post-message so that if the super -dealloc
391 // calls a helper method and that this class overrides, any ivars released in
392 // the helper method will be recorded before checking.
393 if (isSuperDeallocMessage(M))
394 diagnoseMissingReleases(C);
395}
396
397/// Check for missing releases even when -dealloc does not call
398/// '[super dealloc]'.
399void ObjCDeallocChecker::checkEndFunction(
400 const ReturnStmt *RS, CheckerContext &C) const {
401 diagnoseMissingReleases(C);
402}
403
404/// Check for missing releases on early return.
405void ObjCDeallocChecker::checkPreStmt(
406 const ReturnStmt *RS, CheckerContext &C) const {
407 diagnoseMissingReleases(C);
408}
409
410/// When a symbol is assumed to be nil, remove it from the set of symbols
411/// require to be nil.
412ProgramStateRef ObjCDeallocChecker::evalAssume(ProgramStateRef State, SVal Cond,
413 bool Assumption) const {
414 if (State->get<UnreleasedIvarMap>().isEmpty())
415 return State;
416
417 auto *CondBSE = dyn_cast_or_null<BinarySymExpr>(Cond.getAsSymbol());
418 if (!CondBSE)
419 return State;
420
421 BinaryOperator::Opcode OpCode = CondBSE->getOpcode();
422 if (Assumption) {
423 if (OpCode != BO_EQ)
424 return State;
425 } else {
426 if (OpCode != BO_NE)
427 return State;
428 }
429
430 SymbolRef NullSymbol = nullptr;
431 if (auto *SIE = dyn_cast<SymIntExpr>(CondBSE)) {
432 const llvm::APInt &RHS = SIE->getRHS();
433 if (RHS != 0)
434 return State;
435 NullSymbol = SIE->getLHS();
436 } else if (auto *SIE = dyn_cast<IntSymExpr>(CondBSE)) {
437 const llvm::APInt &LHS = SIE->getLHS();
438 if (LHS != 0)
439 return State;
440 NullSymbol = SIE->getRHS();
441 } else {
442 return State;
443 }
444
445 SymbolRef InstanceSymbol = getInstanceSymbolFromIvarSymbol(NullSymbol);
446 if (!InstanceSymbol)
447 return State;
448
449 State = removeValueRequiringRelease(State, InstanceSymbol, NullSymbol);
450
451 return State;
452}
453
454/// If a symbol escapes conservatively assume unseen code released it.
455ProgramStateRef ObjCDeallocChecker::checkPointerEscape(
456 ProgramStateRef State, const InvalidatedSymbols &Escaped,
457 const CallEvent *Call, PointerEscapeKind Kind) const {
458
459 if (State->get<UnreleasedIvarMap>().isEmpty())
460 return State;
461
462 // Don't treat calls to '[super dealloc]' as escaping for the purposes
463 // of this checker. Because the checker diagnoses missing releases in the
464 // post-message handler for '[super dealloc], escaping here would cause
465 // the checker to never warn.
466 auto *OMC = dyn_cast_or_null<ObjCMethodCall>(Call);
467 if (OMC && isSuperDeallocMessage(*OMC))
468 return State;
469
470 for (const auto &Sym : Escaped) {
471 if (!Call || (Call && !Call->isInSystemHeader())) {
472 // If Sym is a symbol for an object with instance variables that
473 // must be released, remove these obligations when the object escapes
474 // unless via a call to a system function. System functions are
475 // very unlikely to release instance variables on objects passed to them,
476 // and are frequently called on 'self' in -dealloc (e.g., to remove
477 // observers) -- we want to avoid false negatives from escaping on
478 // them.
479 State = State->remove<UnreleasedIvarMap>(Sym);
480 }
481
482
483 SymbolRef InstanceSymbol = getInstanceSymbolFromIvarSymbol(Sym);
484 if (!InstanceSymbol)
485 continue;
486
487 State = removeValueRequiringRelease(State, InstanceSymbol, Sym);
488 }
489
490 return State;
491}
492
493/// Report any unreleased instance variables for the current instance being
494/// dealloced.
495void ObjCDeallocChecker::diagnoseMissingReleases(CheckerContext &C) const {
496 ProgramStateRef State = C.getState();
497
498 SVal SelfVal;
499 if (!isInInstanceDealloc(C, SelfVal))
500 return;
501
502 const MemRegion *SelfRegion = SelfVal.castAs<loc::MemRegionVal>().getRegion();
503 const LocationContext *LCtx = C.getLocationContext();
504
505 ExplodedNode *ErrNode = nullptr;
506
507 SymbolRef SelfSym = SelfVal.getAsSymbol();
508 if (!SelfSym)
509 return;
510
511 const SymbolSet *OldUnreleased = State->get<UnreleasedIvarMap>(SelfSym);
512 if (!OldUnreleased)
513 return;
514
515 SymbolSet NewUnreleased = *OldUnreleased;
516 SymbolSet::Factory &F = State->getStateManager().get_context<SymbolSet>();
517
518 ProgramStateRef InitialState = State;
519
520 for (auto *IvarSymbol : *OldUnreleased) {
521 const TypedValueRegion *TVR =
522 cast<SymbolRegionValue>(IvarSymbol)->getRegion();
523 const ObjCIvarRegion *IvarRegion = cast<ObjCIvarRegion>(TVR);
524
525 // Don't warn if the ivar is not for this instance.
526 if (SelfRegion != IvarRegion->getSuperRegion())
527 continue;
528
529 const ObjCIvarDecl *IvarDecl = IvarRegion->getDecl();
530 // Prevent an inlined call to -dealloc in a super class from warning
531 // about the values the subclass's -dealloc should release.
532 if (IvarDecl->getContainingInterface() !=
533 cast<ObjCMethodDecl>(LCtx->getDecl())->getClassInterface())
534 continue;
535
536 // Prevents diagnosing multiple times for the same instance variable
537 // at, for example, both a return and at the end of the function.
538 NewUnreleased = F.remove(NewUnreleased, IvarSymbol);
539
540 if (State->getStateManager()
541 .getConstraintManager()
542 .isNull(State, IvarSymbol)
543 .isConstrainedTrue()) {
544 continue;
545 }
546
547 // A missing release manifests as a leak, so treat as a non-fatal error.
548 if (!ErrNode)
549 ErrNode = C.generateNonFatalErrorNode();
550 // If we've already reached this node on another path, return without
551 // diagnosing.
552 if (!ErrNode)
553 return;
554
555 std::string Buf;
556 llvm::raw_string_ostream OS(Buf);
557
558 const ObjCInterfaceDecl *Interface = IvarDecl->getContainingInterface();
559 // If the class is known to have a lifecycle with teardown that is
560 // separate from -dealloc, do not warn about missing releases. We
561 // suppress here (rather than not tracking for instance variables in
562 // such classes) because these classes are rare.
563 if (classHasSeparateTeardown(Interface))
564 return;
565
566 ObjCImplDecl *ImplDecl = Interface->getImplementation();
567
568 const ObjCPropertyImplDecl *PropImpl =
569 ImplDecl->FindPropertyImplIvarDecl(IvarDecl->getIdentifier());
570
571 const ObjCPropertyDecl *PropDecl = PropImpl->getPropertyDecl();
572
573 assert(PropDecl->getSetterKind() == ObjCPropertyDecl::Copy ||
575
576 OS << "The '" << *IvarDecl << "' ivar in '" << *ImplDecl
577 << "' was ";
578
579 if (PropDecl->getSetterKind() == ObjCPropertyDecl::Retain)
580 OS << "retained";
581 else
582 OS << "copied";
583
584 OS << " by a synthesized property but not released"
585 " before '[super dealloc]'";
586
587 auto BR = std::make_unique<PathSensitiveBugReport>(MissingReleaseBugType,
588 Buf, ErrNode);
589 C.emitReport(std::move(BR));
590 }
591
592 if (NewUnreleased.isEmpty()) {
593 State = State->remove<UnreleasedIvarMap>(SelfSym);
594 } else {
595 State = State->set<UnreleasedIvarMap>(SelfSym, NewUnreleased);
596 }
597
598 if (ErrNode) {
599 C.addTransition(State, ErrNode);
600 } else if (State != InitialState) {
601 C.addTransition(State);
602 }
603
604 // Make sure that after checking in the top-most frame the list of
605 // tracked ivars is empty. This is intended to detect accidental leaks in
606 // the UnreleasedIvarMap program state.
607 assert(!LCtx->inTopFrame() || State->get<UnreleasedIvarMap>().isEmpty());
608}
609
610/// Given a symbol, determine whether the symbol refers to an ivar on
611/// the top-most deallocating instance. If so, find the property for that
612/// ivar, if one exists. Otherwise return null.
613const ObjCPropertyImplDecl *
614ObjCDeallocChecker::findPropertyOnDeallocatingInstance(
615 SymbolRef IvarSym, CheckerContext &C) const {
616 SVal DeallocedInstance;
617 if (!isInInstanceDealloc(C, DeallocedInstance))
618 return nullptr;
619
620 // Try to get the region from which the ivar value was loaded.
621 auto *IvarRegion = getIvarRegionForIvarSymbol(IvarSym);
622 if (!IvarRegion)
623 return nullptr;
624
625 // Don't try to find the property if the ivar was not loaded from the
626 // given instance.
627 if (DeallocedInstance.castAs<loc::MemRegionVal>().getRegion() !=
628 IvarRegion->getSuperRegion())
629 return nullptr;
630
631 const LocationContext *LCtx = C.getLocationContext();
632 const ObjCIvarDecl *IvarDecl = IvarRegion->getDecl();
633
634 const ObjCImplDecl *Container = getContainingObjCImpl(LCtx);
635 const ObjCPropertyImplDecl *PropImpl =
636 Container->FindPropertyImplIvarDecl(IvarDecl->getIdentifier());
637 return PropImpl;
638}
639
640/// Emits a warning if the current context is -dealloc and ReleasedValue
641/// must not be directly released in a -dealloc. Returns true if a diagnostic
642/// was emitted.
643bool ObjCDeallocChecker::diagnoseExtraRelease(SymbolRef ReleasedValue,
644 const ObjCMethodCall &M,
645 CheckerContext &C) const {
646 // Try to get the region from which the released value was loaded.
647 // Note that, unlike diagnosing for missing releases, here we don't track
648 // values that must not be released in the state. This is because even if
649 // these values escape, it is still an error under the rules of MRR to
650 // release them in -dealloc.
651 const ObjCPropertyImplDecl *PropImpl =
652 findPropertyOnDeallocatingInstance(ReleasedValue, C);
653
654 if (!PropImpl)
655 return false;
656
657 // If the ivar belongs to a property that must not be released directly
658 // in dealloc, emit a warning.
659 if (getDeallocReleaseRequirement(PropImpl) !=
661 return false;
662 }
663
664 // If the property is readwrite but it shadows a read-only property in its
665 // external interface, treat the property a read-only. If the outside
666 // world cannot write to a property then the internal implementation is free
667 // to make its own convention about whether the value is stored retained
668 // or not. We look up the shadow here rather than in
669 // getDeallocReleaseRequirement() because doing so can be expensive.
670 const ObjCPropertyDecl *PropDecl = findShadowedPropertyDecl(PropImpl);
671 if (PropDecl) {
672 if (PropDecl->isReadOnly())
673 return false;
674 } else {
675 PropDecl = PropImpl->getPropertyDecl();
676 }
677
678 ExplodedNode *ErrNode = C.generateNonFatalErrorNode();
679 if (!ErrNode)
680 return false;
681
682 std::string Buf;
683 llvm::raw_string_ostream OS(Buf);
684
685 assert(PropDecl->getSetterKind() == ObjCPropertyDecl::Weak ||
686 (PropDecl->getSetterKind() == ObjCPropertyDecl::Assign &&
687 !PropDecl->isReadOnly()) ||
688 isReleasedByCIFilterDealloc(PropImpl)
689 );
690
691 const ObjCImplDecl *Container = getContainingObjCImpl(C.getLocationContext());
692 OS << "The '" << *PropImpl->getPropertyIvarDecl()
693 << "' ivar in '" << *Container;
694
695
696 if (isReleasedByCIFilterDealloc(PropImpl)) {
697 OS << "' will be released by '-[CIFilter dealloc]' but also released here";
698 } else {
699 OS << "' was synthesized for ";
700
701 if (PropDecl->getSetterKind() == ObjCPropertyDecl::Weak)
702 OS << "a weak";
703 else
704 OS << "an assign, readwrite";
705
706 OS << " property but was released in 'dealloc'";
707 }
708
709 auto BR = std::make_unique<PathSensitiveBugReport>(ExtraReleaseBugType, Buf,
710 ErrNode);
711 BR->addRange(M.getOriginExpr()->getSourceRange());
712
713 C.emitReport(std::move(BR));
714
715 return true;
716}
717
718/// Emits a warning if the current context is -dealloc and DeallocedValue
719/// must not be directly dealloced in a -dealloc. Returns true if a diagnostic
720/// was emitted.
721bool ObjCDeallocChecker::diagnoseMistakenDealloc(SymbolRef DeallocedValue,
722 const ObjCMethodCall &M,
723 CheckerContext &C) const {
724 // TODO: Apart from unknown/undefined receivers, this may happen when
725 // dealloc is called as a class method. Should we warn?
726 if (!DeallocedValue)
727 return false;
728
729 // Find the property backing the instance variable that M
730 // is dealloc'ing.
731 const ObjCPropertyImplDecl *PropImpl =
732 findPropertyOnDeallocatingInstance(DeallocedValue, C);
733 if (!PropImpl)
734 return false;
735
736 if (getDeallocReleaseRequirement(PropImpl) !=
738 return false;
739 }
740
741 ExplodedNode *ErrNode = C.generateErrorNode();
742 if (!ErrNode)
743 return false;
744
745 std::string Buf;
746 llvm::raw_string_ostream OS(Buf);
747
748 OS << "'" << *PropImpl->getPropertyIvarDecl()
749 << "' should be released rather than deallocated";
750
751 auto BR = std::make_unique<PathSensitiveBugReport>(MistakenDeallocBugType,
752 Buf, ErrNode);
753 BR->addRange(M.getOriginExpr()->getSourceRange());
754
755 C.emitReport(std::move(BR));
756
757 return true;
758}
759
760void ObjCDeallocChecker::initIdentifierInfoAndSelectors(
761 ASTContext &Ctx) const {
762 if (NSObjectII)
763 return;
764
765 NSObjectII = &Ctx.Idents.get("NSObject");
766 SenTestCaseII = &Ctx.Idents.get("SenTestCase");
767 XCTestCaseII = &Ctx.Idents.get("XCTestCase");
768 Block_releaseII = &Ctx.Idents.get("_Block_release");
769 CIFilterII = &Ctx.Idents.get("CIFilter");
770
771 const IdentifierInfo *DeallocII = &Ctx.Idents.get("dealloc");
772 const IdentifierInfo *ReleaseII = &Ctx.Idents.get("release");
773 DeallocSel = Ctx.Selectors.getSelector(0, &DeallocII);
774 ReleaseSel = Ctx.Selectors.getSelector(0, &ReleaseII);
775}
776
777/// Returns true if M is a call to '[super dealloc]'.
778bool ObjCDeallocChecker::isSuperDeallocMessage(
779 const ObjCMethodCall &M) const {
781 return false;
782
783 return M.getSelector() == DeallocSel;
784}
785
786/// Returns the ObjCImplDecl containing the method declaration in LCtx.
787const ObjCImplDecl *
788ObjCDeallocChecker::getContainingObjCImpl(const LocationContext *LCtx) const {
789 auto *MD = cast<ObjCMethodDecl>(LCtx->getDecl());
791}
792
793/// Returns the property that shadowed by PropImpl if one exists and
794/// nullptr otherwise.
795const ObjCPropertyDecl *ObjCDeallocChecker::findShadowedPropertyDecl(
796 const ObjCPropertyImplDecl *PropImpl) const {
797 const ObjCPropertyDecl *PropDecl = PropImpl->getPropertyDecl();
798
799 // Only readwrite properties can shadow.
800 if (PropDecl->isReadOnly())
801 return nullptr;
802
803 auto *CatDecl = dyn_cast<ObjCCategoryDecl>(PropDecl->getDeclContext());
804
805 // Only class extensions can contain shadowing properties.
806 if (!CatDecl || !CatDecl->IsClassExtension())
807 return nullptr;
808
809 IdentifierInfo *ID = PropDecl->getIdentifier();
810 DeclContext::lookup_result R = CatDecl->getClassInterface()->lookup(ID);
811 for (const NamedDecl *D : R) {
812 auto *ShadowedPropDecl = dyn_cast<ObjCPropertyDecl>(D);
813 if (!ShadowedPropDecl)
814 continue;
815
816 if (ShadowedPropDecl->isInstanceProperty()) {
817 assert(ShadowedPropDecl->isReadOnly());
818 return ShadowedPropDecl;
819 }
820 }
821
822 return nullptr;
823}
824
825/// Add a transition noting the release of the given value.
826void ObjCDeallocChecker::transitionToReleaseValue(CheckerContext &C,
827 SymbolRef Value) const {
828 assert(Value);
829 SymbolRef InstanceSym = getInstanceSymbolFromIvarSymbol(Value);
830 if (!InstanceSym)
831 return;
832 ProgramStateRef InitialState = C.getState();
833
834 ProgramStateRef ReleasedState =
835 removeValueRequiringRelease(InitialState, InstanceSym, Value);
836
837 if (ReleasedState != InitialState) {
838 C.addTransition(ReleasedState);
839 }
840}
841
842/// Remove the Value requiring a release from the tracked set for
843/// Instance and return the resultant state.
844ProgramStateRef ObjCDeallocChecker::removeValueRequiringRelease(
845 ProgramStateRef State, SymbolRef Instance, SymbolRef Value) const {
846 assert(Instance);
847 assert(Value);
848 const ObjCIvarRegion *RemovedRegion = getIvarRegionForIvarSymbol(Value);
849 if (!RemovedRegion)
850 return State;
851
852 const SymbolSet *Unreleased = State->get<UnreleasedIvarMap>(Instance);
853 if (!Unreleased)
854 return State;
855
856 // Mark the value as no longer requiring a release.
857 SymbolSet::Factory &F = State->getStateManager().get_context<SymbolSet>();
858 SymbolSet NewUnreleased = *Unreleased;
859 for (auto &Sym : *Unreleased) {
860 const ObjCIvarRegion *UnreleasedRegion = getIvarRegionForIvarSymbol(Sym);
861 assert(UnreleasedRegion);
862 if (RemovedRegion->getDecl() == UnreleasedRegion->getDecl()) {
863 NewUnreleased = F.remove(NewUnreleased, Sym);
864 }
865 }
866
867 if (NewUnreleased.isEmpty()) {
868 return State->remove<UnreleasedIvarMap>(Instance);
869 }
870
871 return State->set<UnreleasedIvarMap>(Instance, NewUnreleased);
872}
873
874/// Determines whether the instance variable for \p PropImpl must or must not be
875/// released in -dealloc or whether it cannot be determined.
876ReleaseRequirement ObjCDeallocChecker::getDeallocReleaseRequirement(
877 const ObjCPropertyImplDecl *PropImpl) const {
878 const ObjCIvarDecl *IvarDecl;
879 const ObjCPropertyDecl *PropDecl;
880 if (!isSynthesizedRetainableProperty(PropImpl, &IvarDecl, &PropDecl))
882
884
885 switch (SK) {
886 // Retain and copy setters retain/copy their values before storing and so
887 // the value in their instance variables must be released in -dealloc.
890 if (isReleasedByCIFilterDealloc(PropImpl))
892
893 if (isNibLoadedIvarWithoutRetain(PropImpl))
895
897
900
902 // It is common for the ivars for read-only assign properties to
903 // always be stored retained, so their release requirement cannot be
904 // be determined.
905 if (PropDecl->isReadOnly())
907
909 }
910 llvm_unreachable("Unrecognized setter kind");
911}
912
913/// Returns the released value if M is a call a setter that releases
914/// and nils out its underlying instance variable.
916ObjCDeallocChecker::getValueReleasedByNillingOut(const ObjCMethodCall &M,
917 CheckerContext &C) const {
918 SVal ReceiverVal = M.getReceiverSVal();
919 if (!ReceiverVal.isValid())
920 return nullptr;
921
922 if (M.getNumArgs() == 0)
923 return nullptr;
924
926 return nullptr;
927
928 // Is the first argument nil?
929 SVal Arg = M.getArgSVal(0);
930 ProgramStateRef notNilState, nilState;
931 std::tie(notNilState, nilState) =
932 M.getState()->assume(Arg.castAs<DefinedOrUnknownSVal>());
933 if (!(nilState && !notNilState))
934 return nullptr;
935
936 const ObjCPropertyDecl *Prop = M.getAccessedProperty();
937 if (!Prop)
938 return nullptr;
939
940 ObjCIvarDecl *PropIvarDecl = Prop->getPropertyIvarDecl();
941 if (!PropIvarDecl)
942 return nullptr;
943
944 ProgramStateRef State = C.getState();
945
946 SVal LVal = State->getLValue(PropIvarDecl, ReceiverVal);
947 std::optional<Loc> LValLoc = LVal.getAs<Loc>();
948 if (!LValLoc)
949 return nullptr;
950
951 SVal CurrentValInIvar = State->getSVal(*LValLoc);
952 return CurrentValInIvar.getAsSymbol();
953}
954
955/// Returns true if the current context is a call to -dealloc and false
956/// otherwise. If true, it also sets SelfValOut to the value of
957/// 'self'.
958bool ObjCDeallocChecker::isInInstanceDealloc(const CheckerContext &C,
959 SVal &SelfValOut) const {
960 return isInInstanceDealloc(C, C.getLocationContext(), SelfValOut);
961}
962
963/// Returns true if LCtx is a call to -dealloc and false
964/// otherwise. If true, it also sets SelfValOut to the value of
965/// 'self'.
966bool ObjCDeallocChecker::isInInstanceDealloc(const CheckerContext &C,
967 const LocationContext *LCtx,
968 SVal &SelfValOut) const {
969 auto *MD = dyn_cast<ObjCMethodDecl>(LCtx->getDecl());
970 if (!MD || !MD->isInstanceMethod() || MD->getSelector() != DeallocSel)
971 return false;
972
973 const ImplicitParamDecl *SelfDecl = LCtx->getSelfDecl();
974 assert(SelfDecl && "No self in -dealloc?");
975
976 ProgramStateRef State = C.getState();
977 SelfValOut = State->getSVal(State->getRegion(SelfDecl, LCtx));
978 return true;
979}
980
981/// Returns true if there is a call to -dealloc anywhere on the stack and false
982/// otherwise. If true, it also sets InstanceValOut to the value of
983/// 'self' in the frame for -dealloc.
984bool ObjCDeallocChecker::instanceDeallocIsOnStack(const CheckerContext &C,
985 SVal &InstanceValOut) const {
986 const LocationContext *LCtx = C.getLocationContext();
987
988 while (LCtx) {
989 if (isInInstanceDealloc(C, LCtx, InstanceValOut))
990 return true;
991
992 LCtx = LCtx->getParent();
993 }
994
995 return false;
996}
997
998/// Returns true if the ID is a class in which is known to have
999/// a separate teardown lifecycle. In this case, -dealloc warnings
1000/// about missing releases should be suppressed.
1001bool ObjCDeallocChecker::classHasSeparateTeardown(
1002 const ObjCInterfaceDecl *ID) const {
1003 // Suppress if the class is not a subclass of NSObject.
1004 for ( ; ID ; ID = ID->getSuperClass()) {
1005 IdentifierInfo *II = ID->getIdentifier();
1006
1007 if (II == NSObjectII)
1008 return false;
1009
1010 // FIXME: For now, ignore classes that subclass SenTestCase and XCTestCase,
1011 // as these don't need to implement -dealloc. They implement tear down in
1012 // another way, which we should try and catch later.
1013 // http://llvm.org/bugs/show_bug.cgi?id=3187
1014 if (II == XCTestCaseII || II == SenTestCaseII)
1015 return true;
1016 }
1017
1018 return true;
1019}
1020
1021/// The -dealloc method in CIFilter highly unusual in that is will release
1022/// instance variables belonging to its *subclasses* if the variable name
1023/// starts with "input" or backs a property whose name starts with "input".
1024/// Subclasses should not release these ivars in their own -dealloc method --
1025/// doing so could result in an over release.
1026///
1027/// This method returns true if the property will be released by
1028/// -[CIFilter dealloc].
1029bool ObjCDeallocChecker::isReleasedByCIFilterDealloc(
1030 const ObjCPropertyImplDecl *PropImpl) const {
1031 assert(PropImpl->getPropertyIvarDecl());
1032 StringRef PropName = PropImpl->getPropertyDecl()->getName();
1033 StringRef IvarName = PropImpl->getPropertyIvarDecl()->getName();
1034
1035 const char *ReleasePrefix = "input";
1036 if (!(PropName.starts_with(ReleasePrefix) ||
1037 IvarName.starts_with(ReleasePrefix))) {
1038 return false;
1039 }
1040
1041 const ObjCInterfaceDecl *ID =
1043 for ( ; ID ; ID = ID->getSuperClass()) {
1044 IdentifierInfo *II = ID->getIdentifier();
1045 if (II == CIFilterII)
1046 return true;
1047 }
1048
1049 return false;
1050}
1051
1052/// Returns whether the ivar backing the property is an IBOutlet that
1053/// has its value set by nib loading code without retaining the value.
1054///
1055/// On macOS, if there is no setter, the nib-loading code sets the ivar
1056/// directly, without retaining the value,
1057///
1058/// On iOS and its derivatives, the nib-loading code will call
1059/// -setValue:forKey:, which retains the value before directly setting the ivar.
1060bool ObjCDeallocChecker::isNibLoadedIvarWithoutRetain(
1061 const ObjCPropertyImplDecl *PropImpl) const {
1062 const ObjCIvarDecl *IvarDecl = PropImpl->getPropertyIvarDecl();
1063 if (!IvarDecl->hasAttr<IBOutletAttr>())
1064 return false;
1065
1066 const llvm::Triple &Target =
1067 IvarDecl->getASTContext().getTargetInfo().getTriple();
1068
1069 if (!Target.isMacOSX())
1070 return false;
1071
1072 if (PropImpl->getPropertyDecl()->getSetterMethodDecl())
1073 return false;
1074
1075 return true;
1076}
1077
1078void ento::registerObjCDeallocChecker(CheckerManager &Mgr) {
1079 Mgr.registerChecker<ObjCDeallocChecker>();
1080}
1081
1082bool ento::shouldRegisterObjCDeallocChecker(const CheckerManager &mgr) {
1083 // These checker only makes sense under MRR.
1084 const LangOptions &LO = mgr.getLangOpts();
1085 return LO.getGC() != LangOptions::GCOnly && !LO.ObjCAutoRefCount;
1086}
static const MemRegion * getRegion(const CallEvent &Call, const MutexDescriptor &Descriptor, bool IsLock)
ReleaseRequirement
Indicates whether an instance variable is required to be released in -dealloc.
@ MustNotReleaseDirectly
The instance variable must not be directly released with -release.
@ Unknown
The requirement for the instance variable could not be determined.
@ MustRelease
The instance variable must be released, either by calling -release on it directly or by nilling it ou...
static bool isSynthesizedRetainableProperty(const ObjCPropertyImplDecl *I, const ObjCIvarDecl **ID, const ObjCPropertyDecl **PD)
Returns true if the property implementation is synthesized and the type of the property is retainable...
Defines the clang::LangOptions interface.
llvm::MachO::SymbolSet SymbolSet
Definition MachO.h:44
llvm::MachO::Target Target
Definition MachO.h:51
#define REGISTER_MAP_WITH_PROGRAMSTATE(Name, Key, Value)
Declares an immutable map of type NameTy, suitable for placement into the ProgramState.
#define REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(Name, Elem)
Declares an immutable set type Name and registers the factory for such sets in the program state,...
IdentifierTable & Idents
Definition ASTContext.h:737
SelectorTable & Selectors
Definition ASTContext.h:738
const TargetInfo & getTargetInfo() const
Definition ASTContext.h:856
BinaryOperatorKind Opcode
Definition Expr.h:3979
DeclContextLookupResult lookup_result
Definition DeclBase.h:2577
ASTContext & getASTContext() const LLVM_READONLY
Definition DeclBase.cpp:524
DeclContext * getDeclContext()
Definition DeclBase.h:448
bool hasAttr() const
Definition DeclBase.h:577
QualType getType() const
Definition Expr.h:144
IdentifierInfo & get(StringRef Name)
Return the identifier token info for the specified named identifier.
const Decl * getDecl() const
const LocationContext * getParent() const
It might return null.
virtual bool inTopFrame() const
const ImplicitParamDecl * getSelfDecl() const
IdentifierInfo * getIdentifier() const
Get the identifier that names this declaration, if there is one.
Definition Decl.h:294
StringRef getName() const
Get the name of identifier for this declaration as a StringRef.
Definition Decl.h:300
instmeth_range instance_methods() const
Definition DeclObjC.h:1033
propimpl_range property_impls() const
Definition DeclObjC.h:2513
const ObjCInterfaceDecl * getClassInterface() const
Definition DeclObjC.h:2486
ObjCPropertyImplDecl * FindPropertyImplIvarDecl(IdentifierInfo *ivarId) const
FindPropertyImplIvarDecl - This method lookup the ivar in the list of properties implemented in this ...
ObjCImplementationDecl - Represents a class definition - this is where method definitions are specifi...
Definition DeclObjC.h:2597
ObjCImplementationDecl * getImplementation() const
ObjCIvarDecl - Represents an ObjC instance variable.
Definition DeclObjC.h:1952
ObjCInterfaceDecl * getContainingInterface()
Return the class interface that this ivar is logically contained in; this is either the interface whe...
@ SuperInstance
The receiver is the instance of the superclass object.
Definition ExprObjC.h:954
ReceiverKind getReceiverKind() const
Determine the kind of receiver that this message is being sent to.
Definition ExprObjC.h:1229
Selector getSelector() const
Definition DeclObjC.h:327
bool isInstanceMethod() const
Definition DeclObjC.h:426
Represents one property declaration in an Objective-C interface.
Definition DeclObjC.h:731
ObjCMethodDecl * getSetterMethodDecl() const
Definition DeclObjC.h:904
bool isReadOnly() const
isReadOnly - Return true iff the property has a setter.
Definition DeclObjC.h:838
ObjCIvarDecl * getPropertyIvarDecl() const
Definition DeclObjC.h:924
SetterKind getSetterKind() const
getSetterKind - Return the method used for doing assignment in the property setter.
Definition DeclObjC.h:873
ObjCPropertyImplDecl - Represents implementation declaration of a property in a class or category imp...
Definition DeclObjC.h:2805
ObjCIvarDecl * getPropertyIvarDecl() const
Definition DeclObjC.h:2879
Kind getPropertyImplementation() const
Definition DeclObjC.h:2875
ObjCPropertyDecl * getPropertyDecl() const
Definition DeclObjC.h:2870
A (possibly-)qualified type.
Definition TypeBase.h:937
Selector getSelector(unsigned NumArgs, const IdentifierInfo **IIV)
Can create any sort of selector.
SourceRange getSourceRange() const LLVM_READONLY
SourceLocation tokens are not useful in isolation - they are low level value objects created/interpre...
Definition Stmt.cpp:334
const llvm::Triple & getTriple() const
Returns the target triple of the primary target.
bool isObjCRetainableType() const
Definition Type.cpp:5291
const LangOptions & getLangOpts() const
ASTContext & getASTContext() override
BugReporter is a utility class for generating PathDiagnostics for analysis.
const SourceManager & getSourceManager()
void EmitBasicReport(const Decl *DeclWithIssue, const CheckerFrontend *Checker, StringRef BugName, StringRef BugCategory, StringRef BugStr, PathDiagnosticLocation Loc, ArrayRef< SourceRange > Ranges={}, ArrayRef< FixItHint > Fixits={})
virtual void emitReport(std::unique_ptr< BugReport > R)
Add the given report to the set of reports tracked by BugReporter.
const ProgramStateRef & getState() const
The state in which the call is being evaluated.
Definition CallEvent.h:235
virtual SVal getArgSVal(unsigned Index) const
Returns the value of a given argument at the time of the call.
CHECKER * registerChecker(AT &&...Args)
Register a single-part checker (derived from Checker): construct its singleton instance,...
const LangOptions & getLangOpts() const
Simple checker classes that implement one frontend (i.e.
Definition Checker.h:553
const SymbolicRegion * getSymbolicBase() const
If this is a symbolic region, returns the region.
LLVM_ATTRIBUTE_RETURNS_NONNULL const ObjCIvarDecl * getDecl() const override
const Expr * getArgExpr(unsigned Index) const override
Returns the expression associated with a given argument.
Definition CallEvent.h:1286
unsigned getNumArgs() const override
Returns the number of arguments (explicit and implicit).
Definition CallEvent.h:1284
const ObjCMessageExpr * getOriginExpr() const override
Returns the expression whose value will be the result of this call.
Definition CallEvent.h:1276
SVal getReceiverSVal() const
Returns the value of the receiver at the time of this call.
bool isReceiverSelfOrSuper() const
Checks if the receiver refers to 'self' or 'super'.
Selector getSelector() const
Definition CallEvent.h:1298
const ObjCPropertyDecl * getAccessedProperty() const
static PathDiagnosticLocation createBegin(const Decl *D, const SourceManager &SM)
Create a location for the beginning of the declaration.
SymbolRef getAsSymbol(bool IncludeBaseRegions=false) const
If this SVal wraps a symbol return that SymbolRef.
Definition SVals.cpp:103
std::optional< T > getAs() const
Convert to the specified SVal type, returning std::nullopt if this SVal is not of the desired type.
Definition SVals.h:87
bool isValid() const
Definition SVals.h:111
T castAs() const
Convert to the specified SVal type, asserting that this SVal is of the desired type.
Definition SVals.h:83
LLVM_ATTRIBUTE_RETURNS_NONNULL const MemRegion * getSuperRegion() const
Definition MemRegion.h:487
virtual const MemRegion * getOriginRegion() const
Find the region from which this symbol originates.
Definition SymExpr.h:124
SymbolRef getSymbol() const
It might return null.
Definition MemRegion.h:827
Defines the clang::TargetInfo interface.
const char *const CoreFoundationObjectiveC
PointerEscapeKind
Describes the different reasons a pointer escapes during analysis.
llvm::DenseSet< SymbolRef > InvalidatedSymbols
Definition Store.h:51
IntrusiveRefCntPtr< const ProgramState > ProgramStateRef
const SymExpr * SymbolRef
Definition SymExpr.h:133
@ OS
Indicates that the tracking object is a descendant of a referenced-counted OSObject,...
The JSON file list parser is used to communicate input to InstallAPI.
bool isa(CodeGen::Address addr)
Definition Address.h:330
Expr * Cond
};
const FunctionProtoType * T
U cast(CodeGen::Address addr)
Definition Address.h:327
@ Interface
The "__interface" keyword introduces the elaborated-type-specifier.
Definition TypeBase.h:5868