| //==- CheckObjCDealloc.cpp - Check ObjC -dealloc implementation --*- C++ -*-==// |
| // |
| // The LLVM Compiler Infrastructure |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // This file defines a CheckObjCDealloc, a checker that |
| // analyzes an Objective-C class's implementation to determine if it |
| // correctly implements -dealloc. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "ClangSACheckers.h" |
| #include "clang/StaticAnalyzer/Core/Checker.h" |
| #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" |
| #include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" |
| #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" |
| #include "clang/AST/ExprObjC.h" |
| #include "clang/AST/Expr.h" |
| #include "clang/AST/DeclObjC.h" |
| #include "clang/Basic/LangOptions.h" |
| #include "llvm/Support/raw_ostream.h" |
| |
| using namespace clang; |
| using namespace ento; |
| |
| static bool scan_dealloc(Stmt *S, Selector Dealloc) { |
| |
| if (ObjCMessageExpr *ME = dyn_cast<ObjCMessageExpr>(S)) |
| if (ME->getSelector() == Dealloc) { |
| switch (ME->getReceiverKind()) { |
| case ObjCMessageExpr::Instance: return false; |
| case ObjCMessageExpr::SuperInstance: return true; |
| case ObjCMessageExpr::Class: break; |
| case ObjCMessageExpr::SuperClass: break; |
| } |
| } |
| |
| // Recurse to children. |
| |
| for (Stmt::child_iterator I = S->child_begin(), E= S->child_end(); I!=E; ++I) |
| if (*I && scan_dealloc(*I, Dealloc)) |
| return true; |
| |
| return false; |
| } |
| |
| static bool scan_ivar_release(Stmt *S, ObjCIvarDecl *ID, |
| const ObjCPropertyDecl *PD, |
| Selector Release, |
| IdentifierInfo* SelfII, |
| ASTContext &Ctx) { |
| |
| // [mMyIvar release] |
| if (ObjCMessageExpr *ME = dyn_cast<ObjCMessageExpr>(S)) |
| if (ME->getSelector() == Release) |
| if (ME->getInstanceReceiver()) |
| if (Expr *Receiver = ME->getInstanceReceiver()->IgnoreParenCasts()) |
| if (ObjCIvarRefExpr *E = dyn_cast<ObjCIvarRefExpr>(Receiver)) |
| if (E->getDecl() == ID) |
| return true; |
| |
| // [self setMyIvar:nil]; |
| if (ObjCMessageExpr *ME = dyn_cast<ObjCMessageExpr>(S)) |
| if (ME->getInstanceReceiver()) |
| if (Expr *Receiver = ME->getInstanceReceiver()->IgnoreParenCasts()) |
| if (DeclRefExpr *E = dyn_cast<DeclRefExpr>(Receiver)) |
| if (E->getDecl()->getIdentifier() == SelfII) |
| if (ME->getMethodDecl() == PD->getSetterMethodDecl() && |
| ME->getNumArgs() == 1 && |
| ME->getArg(0)->isNullPointerConstant(Ctx, |
| Expr::NPC_ValueDependentIsNull)) |
| return true; |
| |
| // self.myIvar = nil; |
| if (BinaryOperator* BO = dyn_cast<BinaryOperator>(S)) |
| if (BO->isAssignmentOp()) |
| if (ObjCPropertyRefExpr *PRE = |
| dyn_cast<ObjCPropertyRefExpr>(BO->getLHS()->IgnoreParenCasts())) |
| if (PRE->isExplicitProperty() && PRE->getExplicitProperty() == PD) |
| if (BO->getRHS()->isNullPointerConstant(Ctx, |
| Expr::NPC_ValueDependentIsNull)) { |
| // This is only a 'release' if the property kind is not |
| // 'assign'. |
| return PD->getSetterKind() != ObjCPropertyDecl::Assign;; |
| } |
| |
| // Recurse to children. |
| for (Stmt::child_iterator I = S->child_begin(), E= S->child_end(); I!=E; ++I) |
| if (*I && scan_ivar_release(*I, ID, PD, Release, SelfII, Ctx)) |
| return true; |
| |
| return false; |
| } |
| |
| static void checkObjCDealloc(const ObjCImplementationDecl *D, |
| const LangOptions& LOpts, BugReporter& BR) { |
| |
| assert (LOpts.getGCMode() != LangOptions::GCOnly); |
| |
| ASTContext &Ctx = BR.getContext(); |
| const ObjCInterfaceDecl *ID = D->getClassInterface(); |
| |
| // Does the class contain any ivars that are pointers (or id<...>)? |
| // If not, skip the check entirely. |
| // NOTE: This is motivated by PR 2517: |
| // http://llvm.org/bugs/show_bug.cgi?id=2517 |
| |
| bool containsPointerIvar = false; |
| |
| for (ObjCInterfaceDecl::ivar_iterator I=ID->ivar_begin(), E=ID->ivar_end(); |
| I!=E; ++I) { |
| |
| ObjCIvarDecl *ID = *I; |
| QualType T = ID->getType(); |
| |
| if (!T->isObjCObjectPointerType() || |
| ID->getAttr<IBOutletAttr>() || // Skip IBOutlets. |
| ID->getAttr<IBOutletCollectionAttr>()) // Skip IBOutletCollections. |
| continue; |
| |
| containsPointerIvar = true; |
| break; |
| } |
| |
| if (!containsPointerIvar) |
| return; |
| |
| // Determine if the class subclasses NSObject. |
| IdentifierInfo* NSObjectII = &Ctx.Idents.get("NSObject"); |
| IdentifierInfo* SenTestCaseII = &Ctx.Idents.get("SenTestCase"); |
| |
| |
| for ( ; ID ; ID = ID->getSuperClass()) { |
| IdentifierInfo *II = ID->getIdentifier(); |
| |
| if (II == NSObjectII) |
| break; |
| |
| // FIXME: For now, ignore classes that subclass SenTestCase, as these don't |
| // need to implement -dealloc. They implement tear down in another way, |
| // which we should try and catch later. |
| // http://llvm.org/bugs/show_bug.cgi?id=3187 |
| if (II == SenTestCaseII) |
| return; |
| } |
| |
| if (!ID) |
| return; |
| |
| // Get the "dealloc" selector. |
| IdentifierInfo* II = &Ctx.Idents.get("dealloc"); |
| Selector S = Ctx.Selectors.getSelector(0, &II); |
| ObjCMethodDecl *MD = 0; |
| |
| // Scan the instance methods for "dealloc". |
| for (ObjCImplementationDecl::instmeth_iterator I = D->instmeth_begin(), |
| E = D->instmeth_end(); I!=E; ++I) { |
| |
| if ((*I)->getSelector() == S) { |
| MD = *I; |
| break; |
| } |
| } |
| |
| if (!MD) { // No dealloc found. |
| |
| const char* name = LOpts.getGCMode() == LangOptions::NonGC |
| ? "missing -dealloc" |
| : "missing -dealloc (Hybrid MM, non-GC)"; |
| |
| std::string buf; |
| llvm::raw_string_ostream os(buf); |
| os << "Objective-C class '" << D << "' lacks a 'dealloc' instance method"; |
| |
| BR.EmitBasicReport(name, os.str(), D->getLocStart()); |
| return; |
| } |
| |
| // dealloc found. Scan for missing [super dealloc]. |
| if (MD->getBody() && !scan_dealloc(MD->getBody(), S)) { |
| |
| const char* name = LOpts.getGCMode() == LangOptions::NonGC |
| ? "missing [super dealloc]" |
| : "missing [super dealloc] (Hybrid MM, non-GC)"; |
| |
| std::string buf; |
| llvm::raw_string_ostream os(buf); |
| os << "The 'dealloc' instance method in Objective-C class '" << D |
| << "' does not send a 'dealloc' message to its super class" |
| " (missing [super dealloc])"; |
| |
| BR.EmitBasicReport(name, os.str(), D->getLocStart()); |
| return; |
| } |
| |
| // Get the "release" selector. |
| IdentifierInfo* RII = &Ctx.Idents.get("release"); |
| Selector RS = Ctx.Selectors.getSelector(0, &RII); |
| |
| // Get the "self" identifier |
| IdentifierInfo* SelfII = &Ctx.Idents.get("self"); |
| |
| // Scan for missing and extra releases of ivars used by implementations |
| // of synthesized properties |
| for (ObjCImplementationDecl::propimpl_iterator I = D->propimpl_begin(), |
| E = D->propimpl_end(); I!=E; ++I) { |
| |
| // We can only check the synthesized properties |
| if ((*I)->getPropertyImplementation() != ObjCPropertyImplDecl::Synthesize) |
| continue; |
| |
| ObjCIvarDecl *ID = (*I)->getPropertyIvarDecl(); |
| if (!ID) |
| continue; |
| |
| QualType T = ID->getType(); |
| if (!T->isObjCObjectPointerType()) // Skip non-pointer ivars |
| continue; |
| |
| const ObjCPropertyDecl *PD = (*I)->getPropertyDecl(); |
| if (!PD) |
| continue; |
| |
| // ivars cannot be set via read-only properties, so we'll skip them |
| if (PD->isReadOnly()) |
| continue; |
| |
| // ivar must be released if and only if the kind of setter was not 'assign' |
| bool requiresRelease = PD->getSetterKind() != ObjCPropertyDecl::Assign; |
| if (scan_ivar_release(MD->getBody(), ID, PD, RS, SelfII, Ctx) |
| != requiresRelease) { |
| const char *name; |
| const char* category = "Memory (Core Foundation/Objective-C)"; |
| |
| std::string buf; |
| llvm::raw_string_ostream os(buf); |
| |
| if (requiresRelease) { |
| name = LOpts.getGCMode() == LangOptions::NonGC |
| ? "missing ivar release (leak)" |
| : "missing ivar release (Hybrid MM, non-GC)"; |
| |
| os << "The '" << ID |
| << "' instance variable was retained by a synthesized property but " |
| "wasn't released in 'dealloc'"; |
| } else { |
| name = LOpts.getGCMode() == LangOptions::NonGC |
| ? "extra ivar release (use-after-release)" |
| : "extra ivar release (Hybrid MM, non-GC)"; |
| |
| os << "The '" << ID |
| << "' instance variable was not retained by a synthesized property " |
| "but was released in 'dealloc'"; |
| } |
| |
| BR.EmitBasicReport(name, category, os.str(), (*I)->getLocation()); |
| } |
| } |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // ObjCDeallocChecker |
| //===----------------------------------------------------------------------===// |
| |
| namespace { |
| class ObjCDeallocChecker : public Checker< |
| check::ASTDecl<ObjCImplementationDecl> > { |
| public: |
| void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager& mgr, |
| BugReporter &BR) const { |
| if (mgr.getLangOptions().getGCMode() == LangOptions::GCOnly) |
| return; |
| checkObjCDealloc(cast<ObjCImplementationDecl>(D), mgr.getLangOptions(), BR); |
| } |
| }; |
| } |
| |
| void ento::registerObjCDeallocChecker(CheckerManager &mgr) { |
| mgr.registerChecker<ObjCDeallocChecker>(); |
| } |