//==- CheckObjCDealloc.cpp - Check ObjC -dealloc implementation --*- C++ -*-==// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // This checker analyzes Objective-C -dealloc methods and their callees // to warn about improper releasing of instance variables that back synthesized // properties. It warns about missing releases in the following cases: // - When a class has a synthesized instance variable for a 'retain' or 'copy' // property and lacks a -dealloc method in its implementation. // - When a class has a synthesized instance variable for a 'retain'/'copy' // property but the ivar is not released in -dealloc by either -release // or by nilling out the property. // // It warns about extra releases in -dealloc (but not in callees) when a // synthesized instance variable is released in the following cases: // - When the property is 'assign' and is not 'readonly'. // - When the property is 'weak'. // // This checker only warns for instance variables synthesized to back // properties. Handling the more general case would require inferring whether // an instance variable is stored retained or not. For synthesized properties, // this is specified in the property declaration itself. // //===----------------------------------------------------------------------===// #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/Analysis/PathDiagnostic.h" #include "clang/AST/Attr.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/Expr.h" #include "clang/AST/ExprObjC.h" #include "clang/Basic/LangOptions.h" #include "clang/Basic/TargetInfo.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" #include "llvm/Support/raw_ostream.h" #include <optional> usingnamespaceclang; usingnamespaceento; /// Indicates whether an instance variable is required to be released in /// -dealloc. enum class ReleaseRequirement { … }; /// Returns true if the property implementation is synthesized and the /// type of the property is retainable. static bool isSynthesizedRetainableProperty(const ObjCPropertyImplDecl *I, const ObjCIvarDecl **ID, const ObjCPropertyDecl **PD) { … } namespace { class ObjCDeallocChecker : public Checker<check::ASTDecl<ObjCImplementationDecl>, check::PreObjCMessage, check::PostObjCMessage, check::PreCall, check::BeginFunction, check::EndFunction, eval::Assume, check::PointerEscape, check::PreStmt<ReturnStmt>> { … }; } // End anonymous namespace. /// Maps from the symbol for a class instance to the set of /// symbols remaining that must be released in -dealloc. REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(…) REGISTER_MAP_WITH_PROGRAMSTATE(…) /// An AST check that diagnose when the class requires a -dealloc method and /// is missing one. void ObjCDeallocChecker::checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager &Mgr, BugReporter &BR) const { … } /// If this is the beginning of -dealloc, mark the values initially stored in /// instance variables that must be released by the end of -dealloc /// as unreleased in the state. void ObjCDeallocChecker::checkBeginFunction( CheckerContext &C) const { … } /// Given a symbol for an ivar, return the ivar region it was loaded from. /// Returns nullptr if the instance symbol cannot be found. const ObjCIvarRegion * ObjCDeallocChecker::getIvarRegionForIvarSymbol(SymbolRef IvarSym) const { … } /// Given a symbol for an ivar, return a symbol for the instance containing /// the ivar. Returns nullptr if the instance symbol cannot be found. SymbolRef ObjCDeallocChecker::getInstanceSymbolFromIvarSymbol(SymbolRef IvarSym) const { … } /// If we are in -dealloc or -dealloc is on the stack, handle the call if it is /// a release or a nilling-out property setter. void ObjCDeallocChecker::checkPreObjCMessage( const ObjCMethodCall &M, CheckerContext &C) const { … } /// If we are in -dealloc or -dealloc is on the stack, handle the call if it is /// call to Block_release(). void ObjCDeallocChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { … } /// If the message was a call to '[super dealloc]', diagnose any missing /// releases. void ObjCDeallocChecker::checkPostObjCMessage( const ObjCMethodCall &M, CheckerContext &C) const { … } /// Check for missing releases even when -dealloc does not call /// '[super dealloc]'. void ObjCDeallocChecker::checkEndFunction( const ReturnStmt *RS, CheckerContext &C) const { … } /// Check for missing releases on early return. void ObjCDeallocChecker::checkPreStmt( const ReturnStmt *RS, CheckerContext &C) const { … } /// When a symbol is assumed to be nil, remove it from the set of symbols /// require to be nil. ProgramStateRef ObjCDeallocChecker::evalAssume(ProgramStateRef State, SVal Cond, bool Assumption) const { … } /// If a symbol escapes conservatively assume unseen code released it. ProgramStateRef ObjCDeallocChecker::checkPointerEscape( ProgramStateRef State, const InvalidatedSymbols &Escaped, const CallEvent *Call, PointerEscapeKind Kind) const { … } /// Report any unreleased instance variables for the current instance being /// dealloced. void ObjCDeallocChecker::diagnoseMissingReleases(CheckerContext &C) const { … } /// Given a symbol, determine whether the symbol refers to an ivar on /// the top-most deallocating instance. If so, find the property for that /// ivar, if one exists. Otherwise return null. const ObjCPropertyImplDecl * ObjCDeallocChecker::findPropertyOnDeallocatingInstance( SymbolRef IvarSym, CheckerContext &C) const { … } /// Emits a warning if the current context is -dealloc and ReleasedValue /// must not be directly released in a -dealloc. Returns true if a diagnostic /// was emitted. bool ObjCDeallocChecker::diagnoseExtraRelease(SymbolRef ReleasedValue, const ObjCMethodCall &M, CheckerContext &C) const { … } /// Emits a warning if the current context is -dealloc and DeallocedValue /// must not be directly dealloced in a -dealloc. Returns true if a diagnostic /// was emitted. bool ObjCDeallocChecker::diagnoseMistakenDealloc(SymbolRef DeallocedValue, const ObjCMethodCall &M, CheckerContext &C) const { … } void ObjCDeallocChecker::initIdentifierInfoAndSelectors( ASTContext &Ctx) const { … } /// Returns true if M is a call to '[super dealloc]'. bool ObjCDeallocChecker::isSuperDeallocMessage( const ObjCMethodCall &M) const { … } /// Returns the ObjCImplDecl containing the method declaration in LCtx. const ObjCImplDecl * ObjCDeallocChecker::getContainingObjCImpl(const LocationContext *LCtx) const { … } /// Returns the property that shadowed by PropImpl if one exists and /// nullptr otherwise. const ObjCPropertyDecl *ObjCDeallocChecker::findShadowedPropertyDecl( const ObjCPropertyImplDecl *PropImpl) const { … } /// Add a transition noting the release of the given value. void ObjCDeallocChecker::transitionToReleaseValue(CheckerContext &C, SymbolRef Value) const { … } /// Remove the Value requiring a release from the tracked set for /// Instance and return the resultant state. ProgramStateRef ObjCDeallocChecker::removeValueRequiringRelease( ProgramStateRef State, SymbolRef Instance, SymbolRef Value) const { … } /// Determines whether the instance variable for \p PropImpl must or must not be /// released in -dealloc or whether it cannot be determined. ReleaseRequirement ObjCDeallocChecker::getDeallocReleaseRequirement( const ObjCPropertyImplDecl *PropImpl) const { … } /// Returns the released value if M is a call a setter that releases /// and nils out its underlying instance variable. SymbolRef ObjCDeallocChecker::getValueReleasedByNillingOut(const ObjCMethodCall &M, CheckerContext &C) const { … } /// Returns true if the current context is a call to -dealloc and false /// otherwise. If true, it also sets SelfValOut to the value of /// 'self'. bool ObjCDeallocChecker::isInInstanceDealloc(const CheckerContext &C, SVal &SelfValOut) const { … } /// Returns true if LCtx is a call to -dealloc and false /// otherwise. If true, it also sets SelfValOut to the value of /// 'self'. bool ObjCDeallocChecker::isInInstanceDealloc(const CheckerContext &C, const LocationContext *LCtx, SVal &SelfValOut) const { … } /// Returns true if there is a call to -dealloc anywhere on the stack and false /// otherwise. If true, it also sets InstanceValOut to the value of /// 'self' in the frame for -dealloc. bool ObjCDeallocChecker::instanceDeallocIsOnStack(const CheckerContext &C, SVal &InstanceValOut) const { … } /// Returns true if the ID is a class in which is known to have /// a separate teardown lifecycle. In this case, -dealloc warnings /// about missing releases should be suppressed. bool ObjCDeallocChecker::classHasSeparateTeardown( const ObjCInterfaceDecl *ID) const { … } /// The -dealloc method in CIFilter highly unusual in that is will release /// instance variables belonging to its *subclasses* if the variable name /// starts with "input" or backs a property whose name starts with "input". /// Subclasses should not release these ivars in their own -dealloc method -- /// doing so could result in an over release. /// /// This method returns true if the property will be released by /// -[CIFilter dealloc]. bool ObjCDeallocChecker::isReleasedByCIFilterDealloc( const ObjCPropertyImplDecl *PropImpl) const { … } /// Returns whether the ivar backing the property is an IBOutlet that /// has its value set by nib loading code without retaining the value. /// /// On macOS, if there is no setter, the nib-loading code sets the ivar /// directly, without retaining the value, /// /// On iOS and its derivatives, the nib-loading code will call /// -setValue:forKey:, which retains the value before directly setting the ivar. bool ObjCDeallocChecker::isNibLoadedIvarWithoutRetain( const ObjCPropertyImplDecl *PropImpl) const { … } void ento::registerObjCDeallocChecker(CheckerManager &Mgr) { … } bool ento::shouldRegisterObjCDeallocChecker(const CheckerManager &mgr) { … }