Path: blob/main/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp
35269 views
//==- CheckObjCDealloc.cpp - Check ObjC -dealloc implementation --*- C++ -*-==//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7//8// This checker analyzes Objective-C -dealloc methods and their callees9// to warn about improper releasing of instance variables that back synthesized10// properties. It warns about missing releases in the following cases:11// - When a class has a synthesized instance variable for a 'retain' or 'copy'12// property and lacks a -dealloc method in its implementation.13// - When a class has a synthesized instance variable for a 'retain'/'copy'14// property but the ivar is not released in -dealloc by either -release15// or by nilling out the property.16//17// It warns about extra releases in -dealloc (but not in callees) when a18// synthesized instance variable is released in the following cases:19// - When the property is 'assign' and is not 'readonly'.20// - When the property is 'weak'.21//22// This checker only warns for instance variables synthesized to back23// properties. Handling the more general case would require inferring whether24// an instance variable is stored retained or not. For synthesized properties,25// this is specified in the property declaration itself.26//27//===----------------------------------------------------------------------===//2829#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"30#include "clang/Analysis/PathDiagnostic.h"31#include "clang/AST/Attr.h"32#include "clang/AST/DeclObjC.h"33#include "clang/AST/Expr.h"34#include "clang/AST/ExprObjC.h"35#include "clang/Basic/LangOptions.h"36#include "clang/Basic/TargetInfo.h"37#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"38#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"39#include "clang/StaticAnalyzer/Core/Checker.h"40#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"41#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"42#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"43#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"44#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"45#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"46#include "llvm/Support/raw_ostream.h"47#include <optional>4849using namespace clang;50using namespace ento;5152/// Indicates whether an instance variable is required to be released in53/// -dealloc.54enum class ReleaseRequirement {55/// The instance variable must be released, either by calling56/// -release on it directly or by nilling it out with a property setter.57MustRelease,5859/// The instance variable must not be directly released with -release.60MustNotReleaseDirectly,6162/// The requirement for the instance variable could not be determined.63Unknown64};6566/// Returns true if the property implementation is synthesized and the67/// type of the property is retainable.68static bool isSynthesizedRetainableProperty(const ObjCPropertyImplDecl *I,69const ObjCIvarDecl **ID,70const ObjCPropertyDecl **PD) {7172if (I->getPropertyImplementation() != ObjCPropertyImplDecl::Synthesize)73return false;7475(*ID) = I->getPropertyIvarDecl();76if (!(*ID))77return false;7879QualType T = (*ID)->getType();80if (!T->isObjCRetainableType())81return false;8283(*PD) = I->getPropertyDecl();84// Shouldn't be able to synthesize a property that doesn't exist.85assert(*PD);8687return true;88}8990namespace {9192class ObjCDeallocChecker93: public Checker<check::ASTDecl<ObjCImplementationDecl>,94check::PreObjCMessage, check::PostObjCMessage,95check::PreCall,96check::BeginFunction, check::EndFunction,97eval::Assume,98check::PointerEscape,99check::PreStmt<ReturnStmt>> {100101mutable const IdentifierInfo *NSObjectII = nullptr;102mutable const IdentifierInfo *SenTestCaseII = nullptr;103mutable const IdentifierInfo *XCTestCaseII = nullptr;104mutable const IdentifierInfo *Block_releaseII = nullptr;105mutable const IdentifierInfo *CIFilterII = nullptr;106107mutable Selector DeallocSel;108mutable Selector ReleaseSel;109110const BugType MissingReleaseBugType{this, "Missing ivar release (leak)",111categories::MemoryRefCount};112const BugType ExtraReleaseBugType{this, "Extra ivar release",113categories::MemoryRefCount};114const BugType MistakenDeallocBugType{this, "Mistaken dealloc",115categories::MemoryRefCount};116117public:118void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager& Mgr,119BugReporter &BR) const;120void checkBeginFunction(CheckerContext &Ctx) const;121void checkPreObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;122void checkPreCall(const CallEvent &Call, CheckerContext &C) const;123void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;124125ProgramStateRef evalAssume(ProgramStateRef State, SVal Cond,126bool Assumption) const;127128ProgramStateRef checkPointerEscape(ProgramStateRef State,129const InvalidatedSymbols &Escaped,130const CallEvent *Call,131PointerEscapeKind Kind) const;132void checkPreStmt(const ReturnStmt *RS, CheckerContext &C) const;133void checkEndFunction(const ReturnStmt *RS, CheckerContext &Ctx) const;134135private:136void diagnoseMissingReleases(CheckerContext &C) const;137138bool diagnoseExtraRelease(SymbolRef ReleasedValue, const ObjCMethodCall &M,139CheckerContext &C) const;140141bool diagnoseMistakenDealloc(SymbolRef DeallocedValue,142const ObjCMethodCall &M,143CheckerContext &C) const;144145SymbolRef getValueReleasedByNillingOut(const ObjCMethodCall &M,146CheckerContext &C) const;147148const ObjCIvarRegion *getIvarRegionForIvarSymbol(SymbolRef IvarSym) const;149SymbolRef getInstanceSymbolFromIvarSymbol(SymbolRef IvarSym) const;150151const ObjCPropertyImplDecl*152findPropertyOnDeallocatingInstance(SymbolRef IvarSym,153CheckerContext &C) const;154155ReleaseRequirement156getDeallocReleaseRequirement(const ObjCPropertyImplDecl *PropImpl) const;157158bool isInInstanceDealloc(const CheckerContext &C, SVal &SelfValOut) const;159bool isInInstanceDealloc(const CheckerContext &C, const LocationContext *LCtx,160SVal &SelfValOut) const;161bool instanceDeallocIsOnStack(const CheckerContext &C,162SVal &InstanceValOut) const;163164bool isSuperDeallocMessage(const ObjCMethodCall &M) const;165166const ObjCImplDecl *getContainingObjCImpl(const LocationContext *LCtx) const;167168const ObjCPropertyDecl *169findShadowedPropertyDecl(const ObjCPropertyImplDecl *PropImpl) const;170171void transitionToReleaseValue(CheckerContext &C, SymbolRef Value) const;172ProgramStateRef removeValueRequiringRelease(ProgramStateRef State,173SymbolRef InstanceSym,174SymbolRef ValueSym) const;175176void initIdentifierInfoAndSelectors(ASTContext &Ctx) const;177178bool classHasSeparateTeardown(const ObjCInterfaceDecl *ID) const;179180bool isReleasedByCIFilterDealloc(const ObjCPropertyImplDecl *PropImpl) const;181bool isNibLoadedIvarWithoutRetain(const ObjCPropertyImplDecl *PropImpl) const;182};183} // End anonymous namespace.184185186/// Maps from the symbol for a class instance to the set of187/// symbols remaining that must be released in -dealloc.188REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(SymbolSet, SymbolRef)189REGISTER_MAP_WITH_PROGRAMSTATE(UnreleasedIvarMap, SymbolRef, SymbolSet)190191192/// An AST check that diagnose when the class requires a -dealloc method and193/// is missing one.194void ObjCDeallocChecker::checkASTDecl(const ObjCImplementationDecl *D,195AnalysisManager &Mgr,196BugReporter &BR) const {197assert(Mgr.getLangOpts().getGC() != LangOptions::GCOnly);198assert(!Mgr.getLangOpts().ObjCAutoRefCount);199initIdentifierInfoAndSelectors(Mgr.getASTContext());200201const ObjCInterfaceDecl *ID = D->getClassInterface();202// If the class is known to have a lifecycle with a separate teardown method203// then it may not require a -dealloc method.204if (classHasSeparateTeardown(ID))205return;206207// Does the class contain any synthesized properties that are retainable?208// If not, skip the check entirely.209const ObjCPropertyImplDecl *PropImplRequiringRelease = nullptr;210bool HasOthers = false;211for (const auto *I : D->property_impls()) {212if (getDeallocReleaseRequirement(I) == ReleaseRequirement::MustRelease) {213if (!PropImplRequiringRelease)214PropImplRequiringRelease = I;215else {216HasOthers = true;217break;218}219}220}221222if (!PropImplRequiringRelease)223return;224225const ObjCMethodDecl *MD = nullptr;226227// Scan the instance methods for "dealloc".228for (const auto *I : D->instance_methods()) {229if (I->getSelector() == DeallocSel) {230MD = I;231break;232}233}234235if (!MD) { // No dealloc found.236const char* Name = "Missing -dealloc";237238std::string Buf;239llvm::raw_string_ostream OS(Buf);240OS << "'" << *D << "' lacks a 'dealloc' instance method but "241<< "must release '" << *PropImplRequiringRelease->getPropertyIvarDecl()242<< "'";243244if (HasOthers)245OS << " and others";246PathDiagnosticLocation DLoc =247PathDiagnosticLocation::createBegin(D, BR.getSourceManager());248249BR.EmitBasicReport(D, this, Name, categories::CoreFoundationObjectiveC, Buf,250DLoc);251return;252}253}254255/// If this is the beginning of -dealloc, mark the values initially stored in256/// instance variables that must be released by the end of -dealloc257/// as unreleased in the state.258void ObjCDeallocChecker::checkBeginFunction(259CheckerContext &C) const {260initIdentifierInfoAndSelectors(C.getASTContext());261262// Only do this if the current method is -dealloc.263SVal SelfVal;264if (!isInInstanceDealloc(C, SelfVal))265return;266267SymbolRef SelfSymbol = SelfVal.getAsSymbol();268269const LocationContext *LCtx = C.getLocationContext();270ProgramStateRef InitialState = C.getState();271272ProgramStateRef State = InitialState;273274SymbolSet::Factory &F = State->getStateManager().get_context<SymbolSet>();275276// Symbols that must be released by the end of the -dealloc;277SymbolSet RequiredReleases = F.getEmptySet();278279// If we're an inlined -dealloc, we should add our symbols to the existing280// set from our subclass.281if (const SymbolSet *CurrSet = State->get<UnreleasedIvarMap>(SelfSymbol))282RequiredReleases = *CurrSet;283284for (auto *PropImpl : getContainingObjCImpl(LCtx)->property_impls()) {285ReleaseRequirement Requirement = getDeallocReleaseRequirement(PropImpl);286if (Requirement != ReleaseRequirement::MustRelease)287continue;288289SVal LVal = State->getLValue(PropImpl->getPropertyIvarDecl(), SelfVal);290std::optional<Loc> LValLoc = LVal.getAs<Loc>();291if (!LValLoc)292continue;293294SVal InitialVal = State->getSVal(*LValLoc);295SymbolRef Symbol = InitialVal.getAsSymbol();296if (!Symbol || !isa<SymbolRegionValue>(Symbol))297continue;298299// Mark the value as requiring a release.300RequiredReleases = F.add(RequiredReleases, Symbol);301}302303if (!RequiredReleases.isEmpty()) {304State = State->set<UnreleasedIvarMap>(SelfSymbol, RequiredReleases);305}306307if (State != InitialState) {308C.addTransition(State);309}310}311312/// Given a symbol for an ivar, return the ivar region it was loaded from.313/// Returns nullptr if the instance symbol cannot be found.314const ObjCIvarRegion *315ObjCDeallocChecker::getIvarRegionForIvarSymbol(SymbolRef IvarSym) const {316return dyn_cast_or_null<ObjCIvarRegion>(IvarSym->getOriginRegion());317}318319/// Given a symbol for an ivar, return a symbol for the instance containing320/// the ivar. Returns nullptr if the instance symbol cannot be found.321SymbolRef322ObjCDeallocChecker::getInstanceSymbolFromIvarSymbol(SymbolRef IvarSym) const {323324const ObjCIvarRegion *IvarRegion = getIvarRegionForIvarSymbol(IvarSym);325if (!IvarRegion)326return nullptr;327328const SymbolicRegion *SR = IvarRegion->getSymbolicBase();329assert(SR && "Symbolic base should not be nullptr");330return SR->getSymbol();331}332333/// If we are in -dealloc or -dealloc is on the stack, handle the call if it is334/// a release or a nilling-out property setter.335void ObjCDeallocChecker::checkPreObjCMessage(336const ObjCMethodCall &M, CheckerContext &C) const {337// Only run if -dealloc is on the stack.338SVal DeallocedInstance;339if (!instanceDeallocIsOnStack(C, DeallocedInstance))340return;341342SymbolRef ReleasedValue = nullptr;343344if (M.getSelector() == ReleaseSel) {345ReleasedValue = M.getReceiverSVal().getAsSymbol();346} else if (M.getSelector() == DeallocSel && !M.isReceiverSelfOrSuper()) {347if (diagnoseMistakenDealloc(M.getReceiverSVal().getAsSymbol(), M, C))348return;349}350351if (ReleasedValue) {352// An instance variable symbol was released with -release:353// [_property release];354if (diagnoseExtraRelease(ReleasedValue,M, C))355return;356} else {357// An instance variable symbol was released nilling out its property:358// self.property = nil;359ReleasedValue = getValueReleasedByNillingOut(M, C);360}361362if (!ReleasedValue)363return;364365transitionToReleaseValue(C, ReleasedValue);366}367368/// If we are in -dealloc or -dealloc is on the stack, handle the call if it is369/// call to Block_release().370void ObjCDeallocChecker::checkPreCall(const CallEvent &Call,371CheckerContext &C) const {372const IdentifierInfo *II = Call.getCalleeIdentifier();373if (II != Block_releaseII)374return;375376if (Call.getNumArgs() != 1)377return;378379SymbolRef ReleasedValue = Call.getArgSVal(0).getAsSymbol();380if (!ReleasedValue)381return;382383transitionToReleaseValue(C, ReleasedValue);384}385/// If the message was a call to '[super dealloc]', diagnose any missing386/// releases.387void ObjCDeallocChecker::checkPostObjCMessage(388const ObjCMethodCall &M, CheckerContext &C) const {389// We perform this check post-message so that if the super -dealloc390// calls a helper method and that this class overrides, any ivars released in391// the helper method will be recorded before checking.392if (isSuperDeallocMessage(M))393diagnoseMissingReleases(C);394}395396/// Check for missing releases even when -dealloc does not call397/// '[super dealloc]'.398void ObjCDeallocChecker::checkEndFunction(399const ReturnStmt *RS, CheckerContext &C) const {400diagnoseMissingReleases(C);401}402403/// Check for missing releases on early return.404void ObjCDeallocChecker::checkPreStmt(405const ReturnStmt *RS, CheckerContext &C) const {406diagnoseMissingReleases(C);407}408409/// When a symbol is assumed to be nil, remove it from the set of symbols410/// require to be nil.411ProgramStateRef ObjCDeallocChecker::evalAssume(ProgramStateRef State, SVal Cond,412bool Assumption) const {413if (State->get<UnreleasedIvarMap>().isEmpty())414return State;415416auto *CondBSE = dyn_cast_or_null<BinarySymExpr>(Cond.getAsSymbol());417if (!CondBSE)418return State;419420BinaryOperator::Opcode OpCode = CondBSE->getOpcode();421if (Assumption) {422if (OpCode != BO_EQ)423return State;424} else {425if (OpCode != BO_NE)426return State;427}428429SymbolRef NullSymbol = nullptr;430if (auto *SIE = dyn_cast<SymIntExpr>(CondBSE)) {431const llvm::APInt &RHS = SIE->getRHS();432if (RHS != 0)433return State;434NullSymbol = SIE->getLHS();435} else if (auto *SIE = dyn_cast<IntSymExpr>(CondBSE)) {436const llvm::APInt &LHS = SIE->getLHS();437if (LHS != 0)438return State;439NullSymbol = SIE->getRHS();440} else {441return State;442}443444SymbolRef InstanceSymbol = getInstanceSymbolFromIvarSymbol(NullSymbol);445if (!InstanceSymbol)446return State;447448State = removeValueRequiringRelease(State, InstanceSymbol, NullSymbol);449450return State;451}452453/// If a symbol escapes conservatively assume unseen code released it.454ProgramStateRef ObjCDeallocChecker::checkPointerEscape(455ProgramStateRef State, const InvalidatedSymbols &Escaped,456const CallEvent *Call, PointerEscapeKind Kind) const {457458if (State->get<UnreleasedIvarMap>().isEmpty())459return State;460461// Don't treat calls to '[super dealloc]' as escaping for the purposes462// of this checker. Because the checker diagnoses missing releases in the463// post-message handler for '[super dealloc], escaping here would cause464// the checker to never warn.465auto *OMC = dyn_cast_or_null<ObjCMethodCall>(Call);466if (OMC && isSuperDeallocMessage(*OMC))467return State;468469for (const auto &Sym : Escaped) {470if (!Call || (Call && !Call->isInSystemHeader())) {471// If Sym is a symbol for an object with instance variables that472// must be released, remove these obligations when the object escapes473// unless via a call to a system function. System functions are474// very unlikely to release instance variables on objects passed to them,475// and are frequently called on 'self' in -dealloc (e.g., to remove476// observers) -- we want to avoid false negatives from escaping on477// them.478State = State->remove<UnreleasedIvarMap>(Sym);479}480481482SymbolRef InstanceSymbol = getInstanceSymbolFromIvarSymbol(Sym);483if (!InstanceSymbol)484continue;485486State = removeValueRequiringRelease(State, InstanceSymbol, Sym);487}488489return State;490}491492/// Report any unreleased instance variables for the current instance being493/// dealloced.494void ObjCDeallocChecker::diagnoseMissingReleases(CheckerContext &C) const {495ProgramStateRef State = C.getState();496497SVal SelfVal;498if (!isInInstanceDealloc(C, SelfVal))499return;500501const MemRegion *SelfRegion = SelfVal.castAs<loc::MemRegionVal>().getRegion();502const LocationContext *LCtx = C.getLocationContext();503504ExplodedNode *ErrNode = nullptr;505506SymbolRef SelfSym = SelfVal.getAsSymbol();507if (!SelfSym)508return;509510const SymbolSet *OldUnreleased = State->get<UnreleasedIvarMap>(SelfSym);511if (!OldUnreleased)512return;513514SymbolSet NewUnreleased = *OldUnreleased;515SymbolSet::Factory &F = State->getStateManager().get_context<SymbolSet>();516517ProgramStateRef InitialState = State;518519for (auto *IvarSymbol : *OldUnreleased) {520const TypedValueRegion *TVR =521cast<SymbolRegionValue>(IvarSymbol)->getRegion();522const ObjCIvarRegion *IvarRegion = cast<ObjCIvarRegion>(TVR);523524// Don't warn if the ivar is not for this instance.525if (SelfRegion != IvarRegion->getSuperRegion())526continue;527528const ObjCIvarDecl *IvarDecl = IvarRegion->getDecl();529// Prevent an inlined call to -dealloc in a super class from warning530// about the values the subclass's -dealloc should release.531if (IvarDecl->getContainingInterface() !=532cast<ObjCMethodDecl>(LCtx->getDecl())->getClassInterface())533continue;534535// Prevents diagnosing multiple times for the same instance variable536// at, for example, both a return and at the end of the function.537NewUnreleased = F.remove(NewUnreleased, IvarSymbol);538539if (State->getStateManager()540.getConstraintManager()541.isNull(State, IvarSymbol)542.isConstrainedTrue()) {543continue;544}545546// A missing release manifests as a leak, so treat as a non-fatal error.547if (!ErrNode)548ErrNode = C.generateNonFatalErrorNode();549// If we've already reached this node on another path, return without550// diagnosing.551if (!ErrNode)552return;553554std::string Buf;555llvm::raw_string_ostream OS(Buf);556557const ObjCInterfaceDecl *Interface = IvarDecl->getContainingInterface();558// If the class is known to have a lifecycle with teardown that is559// separate from -dealloc, do not warn about missing releases. We560// suppress here (rather than not tracking for instance variables in561// such classes) because these classes are rare.562if (classHasSeparateTeardown(Interface))563return;564565ObjCImplDecl *ImplDecl = Interface->getImplementation();566567const ObjCPropertyImplDecl *PropImpl =568ImplDecl->FindPropertyImplIvarDecl(IvarDecl->getIdentifier());569570const ObjCPropertyDecl *PropDecl = PropImpl->getPropertyDecl();571572assert(PropDecl->getSetterKind() == ObjCPropertyDecl::Copy ||573PropDecl->getSetterKind() == ObjCPropertyDecl::Retain);574575OS << "The '" << *IvarDecl << "' ivar in '" << *ImplDecl576<< "' was ";577578if (PropDecl->getSetterKind() == ObjCPropertyDecl::Retain)579OS << "retained";580else581OS << "copied";582583OS << " by a synthesized property but not released"584" before '[super dealloc]'";585586auto BR = std::make_unique<PathSensitiveBugReport>(MissingReleaseBugType,587Buf, ErrNode);588C.emitReport(std::move(BR));589}590591if (NewUnreleased.isEmpty()) {592State = State->remove<UnreleasedIvarMap>(SelfSym);593} else {594State = State->set<UnreleasedIvarMap>(SelfSym, NewUnreleased);595}596597if (ErrNode) {598C.addTransition(State, ErrNode);599} else if (State != InitialState) {600C.addTransition(State);601}602603// Make sure that after checking in the top-most frame the list of604// tracked ivars is empty. This is intended to detect accidental leaks in605// the UnreleasedIvarMap program state.606assert(!LCtx->inTopFrame() || State->get<UnreleasedIvarMap>().isEmpty());607}608609/// Given a symbol, determine whether the symbol refers to an ivar on610/// the top-most deallocating instance. If so, find the property for that611/// ivar, if one exists. Otherwise return null.612const ObjCPropertyImplDecl *613ObjCDeallocChecker::findPropertyOnDeallocatingInstance(614SymbolRef IvarSym, CheckerContext &C) const {615SVal DeallocedInstance;616if (!isInInstanceDealloc(C, DeallocedInstance))617return nullptr;618619// Try to get the region from which the ivar value was loaded.620auto *IvarRegion = getIvarRegionForIvarSymbol(IvarSym);621if (!IvarRegion)622return nullptr;623624// Don't try to find the property if the ivar was not loaded from the625// given instance.626if (DeallocedInstance.castAs<loc::MemRegionVal>().getRegion() !=627IvarRegion->getSuperRegion())628return nullptr;629630const LocationContext *LCtx = C.getLocationContext();631const ObjCIvarDecl *IvarDecl = IvarRegion->getDecl();632633const ObjCImplDecl *Container = getContainingObjCImpl(LCtx);634const ObjCPropertyImplDecl *PropImpl =635Container->FindPropertyImplIvarDecl(IvarDecl->getIdentifier());636return PropImpl;637}638639/// Emits a warning if the current context is -dealloc and ReleasedValue640/// must not be directly released in a -dealloc. Returns true if a diagnostic641/// was emitted.642bool ObjCDeallocChecker::diagnoseExtraRelease(SymbolRef ReleasedValue,643const ObjCMethodCall &M,644CheckerContext &C) const {645// Try to get the region from which the released value was loaded.646// Note that, unlike diagnosing for missing releases, here we don't track647// values that must not be released in the state. This is because even if648// these values escape, it is still an error under the rules of MRR to649// release them in -dealloc.650const ObjCPropertyImplDecl *PropImpl =651findPropertyOnDeallocatingInstance(ReleasedValue, C);652653if (!PropImpl)654return false;655656// If the ivar belongs to a property that must not be released directly657// in dealloc, emit a warning.658if (getDeallocReleaseRequirement(PropImpl) !=659ReleaseRequirement::MustNotReleaseDirectly) {660return false;661}662663// If the property is readwrite but it shadows a read-only property in its664// external interface, treat the property a read-only. If the outside665// world cannot write to a property then the internal implementation is free666// to make its own convention about whether the value is stored retained667// or not. We look up the shadow here rather than in668// getDeallocReleaseRequirement() because doing so can be expensive.669const ObjCPropertyDecl *PropDecl = findShadowedPropertyDecl(PropImpl);670if (PropDecl) {671if (PropDecl->isReadOnly())672return false;673} else {674PropDecl = PropImpl->getPropertyDecl();675}676677ExplodedNode *ErrNode = C.generateNonFatalErrorNode();678if (!ErrNode)679return false;680681std::string Buf;682llvm::raw_string_ostream OS(Buf);683684assert(PropDecl->getSetterKind() == ObjCPropertyDecl::Weak ||685(PropDecl->getSetterKind() == ObjCPropertyDecl::Assign &&686!PropDecl->isReadOnly()) ||687isReleasedByCIFilterDealloc(PropImpl)688);689690const ObjCImplDecl *Container = getContainingObjCImpl(C.getLocationContext());691OS << "The '" << *PropImpl->getPropertyIvarDecl()692<< "' ivar in '" << *Container;693694695if (isReleasedByCIFilterDealloc(PropImpl)) {696OS << "' will be released by '-[CIFilter dealloc]' but also released here";697} else {698OS << "' was synthesized for ";699700if (PropDecl->getSetterKind() == ObjCPropertyDecl::Weak)701OS << "a weak";702else703OS << "an assign, readwrite";704705OS << " property but was released in 'dealloc'";706}707708auto BR = std::make_unique<PathSensitiveBugReport>(ExtraReleaseBugType, Buf,709ErrNode);710BR->addRange(M.getOriginExpr()->getSourceRange());711712C.emitReport(std::move(BR));713714return true;715}716717/// Emits a warning if the current context is -dealloc and DeallocedValue718/// must not be directly dealloced in a -dealloc. Returns true if a diagnostic719/// was emitted.720bool ObjCDeallocChecker::diagnoseMistakenDealloc(SymbolRef DeallocedValue,721const ObjCMethodCall &M,722CheckerContext &C) const {723// TODO: Apart from unknown/undefined receivers, this may happen when724// dealloc is called as a class method. Should we warn?725if (!DeallocedValue)726return false;727728// Find the property backing the instance variable that M729// is dealloc'ing.730const ObjCPropertyImplDecl *PropImpl =731findPropertyOnDeallocatingInstance(DeallocedValue, C);732if (!PropImpl)733return false;734735if (getDeallocReleaseRequirement(PropImpl) !=736ReleaseRequirement::MustRelease) {737return false;738}739740ExplodedNode *ErrNode = C.generateErrorNode();741if (!ErrNode)742return false;743744std::string Buf;745llvm::raw_string_ostream OS(Buf);746747OS << "'" << *PropImpl->getPropertyIvarDecl()748<< "' should be released rather than deallocated";749750auto BR = std::make_unique<PathSensitiveBugReport>(MistakenDeallocBugType,751Buf, ErrNode);752BR->addRange(M.getOriginExpr()->getSourceRange());753754C.emitReport(std::move(BR));755756return true;757}758759void ObjCDeallocChecker::initIdentifierInfoAndSelectors(760ASTContext &Ctx) const {761if (NSObjectII)762return;763764NSObjectII = &Ctx.Idents.get("NSObject");765SenTestCaseII = &Ctx.Idents.get("SenTestCase");766XCTestCaseII = &Ctx.Idents.get("XCTestCase");767Block_releaseII = &Ctx.Idents.get("_Block_release");768CIFilterII = &Ctx.Idents.get("CIFilter");769770const IdentifierInfo *DeallocII = &Ctx.Idents.get("dealloc");771const IdentifierInfo *ReleaseII = &Ctx.Idents.get("release");772DeallocSel = Ctx.Selectors.getSelector(0, &DeallocII);773ReleaseSel = Ctx.Selectors.getSelector(0, &ReleaseII);774}775776/// Returns true if M is a call to '[super dealloc]'.777bool ObjCDeallocChecker::isSuperDeallocMessage(778const ObjCMethodCall &M) const {779if (M.getOriginExpr()->getReceiverKind() != ObjCMessageExpr::SuperInstance)780return false;781782return M.getSelector() == DeallocSel;783}784785/// Returns the ObjCImplDecl containing the method declaration in LCtx.786const ObjCImplDecl *787ObjCDeallocChecker::getContainingObjCImpl(const LocationContext *LCtx) const {788auto *MD = cast<ObjCMethodDecl>(LCtx->getDecl());789return cast<ObjCImplDecl>(MD->getDeclContext());790}791792/// Returns the property that shadowed by PropImpl if one exists and793/// nullptr otherwise.794const ObjCPropertyDecl *ObjCDeallocChecker::findShadowedPropertyDecl(795const ObjCPropertyImplDecl *PropImpl) const {796const ObjCPropertyDecl *PropDecl = PropImpl->getPropertyDecl();797798// Only readwrite properties can shadow.799if (PropDecl->isReadOnly())800return nullptr;801802auto *CatDecl = dyn_cast<ObjCCategoryDecl>(PropDecl->getDeclContext());803804// Only class extensions can contain shadowing properties.805if (!CatDecl || !CatDecl->IsClassExtension())806return nullptr;807808IdentifierInfo *ID = PropDecl->getIdentifier();809DeclContext::lookup_result R = CatDecl->getClassInterface()->lookup(ID);810for (const NamedDecl *D : R) {811auto *ShadowedPropDecl = dyn_cast<ObjCPropertyDecl>(D);812if (!ShadowedPropDecl)813continue;814815if (ShadowedPropDecl->isInstanceProperty()) {816assert(ShadowedPropDecl->isReadOnly());817return ShadowedPropDecl;818}819}820821return nullptr;822}823824/// Add a transition noting the release of the given value.825void ObjCDeallocChecker::transitionToReleaseValue(CheckerContext &C,826SymbolRef Value) const {827assert(Value);828SymbolRef InstanceSym = getInstanceSymbolFromIvarSymbol(Value);829if (!InstanceSym)830return;831ProgramStateRef InitialState = C.getState();832833ProgramStateRef ReleasedState =834removeValueRequiringRelease(InitialState, InstanceSym, Value);835836if (ReleasedState != InitialState) {837C.addTransition(ReleasedState);838}839}840841/// Remove the Value requiring a release from the tracked set for842/// Instance and return the resultant state.843ProgramStateRef ObjCDeallocChecker::removeValueRequiringRelease(844ProgramStateRef State, SymbolRef Instance, SymbolRef Value) const {845assert(Instance);846assert(Value);847const ObjCIvarRegion *RemovedRegion = getIvarRegionForIvarSymbol(Value);848if (!RemovedRegion)849return State;850851const SymbolSet *Unreleased = State->get<UnreleasedIvarMap>(Instance);852if (!Unreleased)853return State;854855// Mark the value as no longer requiring a release.856SymbolSet::Factory &F = State->getStateManager().get_context<SymbolSet>();857SymbolSet NewUnreleased = *Unreleased;858for (auto &Sym : *Unreleased) {859const ObjCIvarRegion *UnreleasedRegion = getIvarRegionForIvarSymbol(Sym);860assert(UnreleasedRegion);861if (RemovedRegion->getDecl() == UnreleasedRegion->getDecl()) {862NewUnreleased = F.remove(NewUnreleased, Sym);863}864}865866if (NewUnreleased.isEmpty()) {867return State->remove<UnreleasedIvarMap>(Instance);868}869870return State->set<UnreleasedIvarMap>(Instance, NewUnreleased);871}872873/// Determines whether the instance variable for \p PropImpl must or must not be874/// released in -dealloc or whether it cannot be determined.875ReleaseRequirement ObjCDeallocChecker::getDeallocReleaseRequirement(876const ObjCPropertyImplDecl *PropImpl) const {877const ObjCIvarDecl *IvarDecl;878const ObjCPropertyDecl *PropDecl;879if (!isSynthesizedRetainableProperty(PropImpl, &IvarDecl, &PropDecl))880return ReleaseRequirement::Unknown;881882ObjCPropertyDecl::SetterKind SK = PropDecl->getSetterKind();883884switch (SK) {885// Retain and copy setters retain/copy their values before storing and so886// the value in their instance variables must be released in -dealloc.887case ObjCPropertyDecl::Retain:888case ObjCPropertyDecl::Copy:889if (isReleasedByCIFilterDealloc(PropImpl))890return ReleaseRequirement::MustNotReleaseDirectly;891892if (isNibLoadedIvarWithoutRetain(PropImpl))893return ReleaseRequirement::Unknown;894895return ReleaseRequirement::MustRelease;896897case ObjCPropertyDecl::Weak:898return ReleaseRequirement::MustNotReleaseDirectly;899900case ObjCPropertyDecl::Assign:901// It is common for the ivars for read-only assign properties to902// always be stored retained, so their release requirement cannot be903// be determined.904if (PropDecl->isReadOnly())905return ReleaseRequirement::Unknown;906907return ReleaseRequirement::MustNotReleaseDirectly;908}909llvm_unreachable("Unrecognized setter kind");910}911912/// Returns the released value if M is a call a setter that releases913/// and nils out its underlying instance variable.914SymbolRef915ObjCDeallocChecker::getValueReleasedByNillingOut(const ObjCMethodCall &M,916CheckerContext &C) const {917SVal ReceiverVal = M.getReceiverSVal();918if (!ReceiverVal.isValid())919return nullptr;920921if (M.getNumArgs() == 0)922return nullptr;923924if (!M.getArgExpr(0)->getType()->isObjCRetainableType())925return nullptr;926927// Is the first argument nil?928SVal Arg = M.getArgSVal(0);929ProgramStateRef notNilState, nilState;930std::tie(notNilState, nilState) =931M.getState()->assume(Arg.castAs<DefinedOrUnknownSVal>());932if (!(nilState && !notNilState))933return nullptr;934935const ObjCPropertyDecl *Prop = M.getAccessedProperty();936if (!Prop)937return nullptr;938939ObjCIvarDecl *PropIvarDecl = Prop->getPropertyIvarDecl();940if (!PropIvarDecl)941return nullptr;942943ProgramStateRef State = C.getState();944945SVal LVal = State->getLValue(PropIvarDecl, ReceiverVal);946std::optional<Loc> LValLoc = LVal.getAs<Loc>();947if (!LValLoc)948return nullptr;949950SVal CurrentValInIvar = State->getSVal(*LValLoc);951return CurrentValInIvar.getAsSymbol();952}953954/// Returns true if the current context is a call to -dealloc and false955/// otherwise. If true, it also sets SelfValOut to the value of956/// 'self'.957bool ObjCDeallocChecker::isInInstanceDealloc(const CheckerContext &C,958SVal &SelfValOut) const {959return isInInstanceDealloc(C, C.getLocationContext(), SelfValOut);960}961962/// Returns true if LCtx is a call to -dealloc and false963/// otherwise. If true, it also sets SelfValOut to the value of964/// 'self'.965bool ObjCDeallocChecker::isInInstanceDealloc(const CheckerContext &C,966const LocationContext *LCtx,967SVal &SelfValOut) const {968auto *MD = dyn_cast<ObjCMethodDecl>(LCtx->getDecl());969if (!MD || !MD->isInstanceMethod() || MD->getSelector() != DeallocSel)970return false;971972const ImplicitParamDecl *SelfDecl = LCtx->getSelfDecl();973assert(SelfDecl && "No self in -dealloc?");974975ProgramStateRef State = C.getState();976SelfValOut = State->getSVal(State->getRegion(SelfDecl, LCtx));977return true;978}979980/// Returns true if there is a call to -dealloc anywhere on the stack and false981/// otherwise. If true, it also sets InstanceValOut to the value of982/// 'self' in the frame for -dealloc.983bool ObjCDeallocChecker::instanceDeallocIsOnStack(const CheckerContext &C,984SVal &InstanceValOut) const {985const LocationContext *LCtx = C.getLocationContext();986987while (LCtx) {988if (isInInstanceDealloc(C, LCtx, InstanceValOut))989return true;990991LCtx = LCtx->getParent();992}993994return false;995}996997/// Returns true if the ID is a class in which is known to have998/// a separate teardown lifecycle. In this case, -dealloc warnings999/// about missing releases should be suppressed.1000bool ObjCDeallocChecker::classHasSeparateTeardown(1001const ObjCInterfaceDecl *ID) const {1002// Suppress if the class is not a subclass of NSObject.1003for ( ; ID ; ID = ID->getSuperClass()) {1004IdentifierInfo *II = ID->getIdentifier();10051006if (II == NSObjectII)1007return false;10081009// FIXME: For now, ignore classes that subclass SenTestCase and XCTestCase,1010// as these don't need to implement -dealloc. They implement tear down in1011// another way, which we should try and catch later.1012// http://llvm.org/bugs/show_bug.cgi?id=31871013if (II == XCTestCaseII || II == SenTestCaseII)1014return true;1015}10161017return true;1018}10191020/// The -dealloc method in CIFilter highly unusual in that is will release1021/// instance variables belonging to its *subclasses* if the variable name1022/// starts with "input" or backs a property whose name starts with "input".1023/// Subclasses should not release these ivars in their own -dealloc method --1024/// doing so could result in an over release.1025///1026/// This method returns true if the property will be released by1027/// -[CIFilter dealloc].1028bool ObjCDeallocChecker::isReleasedByCIFilterDealloc(1029const ObjCPropertyImplDecl *PropImpl) const {1030assert(PropImpl->getPropertyIvarDecl());1031StringRef PropName = PropImpl->getPropertyDecl()->getName();1032StringRef IvarName = PropImpl->getPropertyIvarDecl()->getName();10331034const char *ReleasePrefix = "input";1035if (!(PropName.starts_with(ReleasePrefix) ||1036IvarName.starts_with(ReleasePrefix))) {1037return false;1038}10391040const ObjCInterfaceDecl *ID =1041PropImpl->getPropertyIvarDecl()->getContainingInterface();1042for ( ; ID ; ID = ID->getSuperClass()) {1043IdentifierInfo *II = ID->getIdentifier();1044if (II == CIFilterII)1045return true;1046}10471048return false;1049}10501051/// Returns whether the ivar backing the property is an IBOutlet that1052/// has its value set by nib loading code without retaining the value.1053///1054/// On macOS, if there is no setter, the nib-loading code sets the ivar1055/// directly, without retaining the value,1056///1057/// On iOS and its derivatives, the nib-loading code will call1058/// -setValue:forKey:, which retains the value before directly setting the ivar.1059bool ObjCDeallocChecker::isNibLoadedIvarWithoutRetain(1060const ObjCPropertyImplDecl *PropImpl) const {1061const ObjCIvarDecl *IvarDecl = PropImpl->getPropertyIvarDecl();1062if (!IvarDecl->hasAttr<IBOutletAttr>())1063return false;10641065const llvm::Triple &Target =1066IvarDecl->getASTContext().getTargetInfo().getTriple();10671068if (!Target.isMacOSX())1069return false;10701071if (PropImpl->getPropertyDecl()->getSetterMethodDecl())1072return false;10731074return true;1075}10761077void ento::registerObjCDeallocChecker(CheckerManager &Mgr) {1078Mgr.registerChecker<ObjCDeallocChecker>();1079}10801081bool ento::shouldRegisterObjCDeallocChecker(const CheckerManager &mgr) {1082// These checker only makes sense under MRR.1083const LangOptions &LO = mgr.getLangOpts();1084return LO.getGC() != LangOptions::GCOnly && !LO.ObjCAutoRefCount;1085}108610871088