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