1 //==- CheckObjCDealloc.cpp - Check ObjC -dealloc implementation --*- C++ -*-==// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 // 10 // This file defines a CheckObjCDealloc, a checker that 11 // analyzes an Objective-C class's implementation to determine if it 12 // correctly implements -dealloc. 13 // 14 //===----------------------------------------------------------------------===// 15 16 #include "ClangSACheckers.h" 17 #include "clang/AST/Attr.h" 18 #include "clang/AST/DeclObjC.h" 19 #include "clang/AST/Expr.h" 20 #include "clang/AST/ExprObjC.h" 21 #include "clang/Basic/LangOptions.h" 22 #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" 23 #include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" 24 #include "clang/StaticAnalyzer/Core/Checker.h" 25 #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" 26 #include "llvm/Support/raw_ostream.h" 27 28 using namespace clang; 29 using namespace ento; 30 31 static bool scan_ivar_release(Stmt *S, ObjCIvarDecl *ID, 32 const ObjCPropertyDecl *PD, 33 Selector Release, 34 IdentifierInfo* SelfII, 35 ASTContext &Ctx) { 36 37 // [mMyIvar release] 38 if (ObjCMessageExpr *ME = dyn_cast<ObjCMessageExpr>(S)) 39 if (ME->getSelector() == Release) 40 if (ME->getInstanceReceiver()) 41 if (Expr *Receiver = ME->getInstanceReceiver()->IgnoreParenCasts()) 42 if (ObjCIvarRefExpr *E = dyn_cast<ObjCIvarRefExpr>(Receiver)) 43 if (E->getDecl() == ID) 44 return true; 45 46 // [self setMyIvar:nil]; 47 if (ObjCMessageExpr *ME = dyn_cast<ObjCMessageExpr>(S)) 48 if (ME->getInstanceReceiver()) 49 if (Expr *Receiver = ME->getInstanceReceiver()->IgnoreParenCasts()) 50 if (DeclRefExpr *E = dyn_cast<DeclRefExpr>(Receiver)) 51 if (E->getDecl()->getIdentifier() == SelfII) 52 if (ME->getMethodDecl() == PD->getSetterMethodDecl() && 53 ME->getNumArgs() == 1 && 54 ME->getArg(0)->isNullPointerConstant(Ctx, 55 Expr::NPC_ValueDependentIsNull)) 56 return true; 57 58 // self.myIvar = nil; 59 if (BinaryOperator* BO = dyn_cast<BinaryOperator>(S)) 60 if (BO->isAssignmentOp()) 61 if (ObjCPropertyRefExpr *PRE = 62 dyn_cast<ObjCPropertyRefExpr>(BO->getLHS()->IgnoreParenCasts())) 63 if (PRE->isExplicitProperty() && PRE->getExplicitProperty() == PD) 64 if (BO->getRHS()->isNullPointerConstant(Ctx, 65 Expr::NPC_ValueDependentIsNull)) { 66 // This is only a 'release' if the property kind is not 67 // 'assign'. 68 return PD->getSetterKind() != ObjCPropertyDecl::Assign; 69 } 70 71 // Recurse to children. 72 for (Stmt *SubStmt : S->children()) 73 if (SubStmt && scan_ivar_release(SubStmt, ID, PD, Release, SelfII, Ctx)) 74 return true; 75 76 return false; 77 } 78 79 static void checkObjCDealloc(const CheckerBase *Checker, 80 const ObjCImplementationDecl *D, 81 const LangOptions &LOpts, BugReporter &BR) { 82 83 assert (LOpts.getGC() != LangOptions::GCOnly); 84 85 ASTContext &Ctx = BR.getContext(); 86 const ObjCInterfaceDecl *ID = D->getClassInterface(); 87 88 // Does the class contain any ivars that are pointers (or id<...>)? 89 // If not, skip the check entirely. 90 // NOTE: This is motivated by PR 2517: 91 // http://llvm.org/bugs/show_bug.cgi?id=2517 92 93 bool containsPointerIvar = false; 94 95 for (const auto *Ivar : ID->ivars()) { 96 QualType T = Ivar->getType(); 97 98 if (!T->isObjCObjectPointerType() || 99 Ivar->hasAttr<IBOutletAttr>() || // Skip IBOutlets. 100 Ivar->hasAttr<IBOutletCollectionAttr>()) // Skip IBOutletCollections. 101 continue; 102 103 containsPointerIvar = true; 104 break; 105 } 106 107 if (!containsPointerIvar) 108 return; 109 110 // Determine if the class subclasses NSObject. 111 IdentifierInfo* NSObjectII = &Ctx.Idents.get("NSObject"); 112 IdentifierInfo* SenTestCaseII = &Ctx.Idents.get("SenTestCase"); 113 114 115 for ( ; ID ; ID = ID->getSuperClass()) { 116 IdentifierInfo *II = ID->getIdentifier(); 117 118 if (II == NSObjectII) 119 break; 120 121 // FIXME: For now, ignore classes that subclass SenTestCase, as these don't 122 // need to implement -dealloc. They implement tear down in another way, 123 // which we should try and catch later. 124 // http://llvm.org/bugs/show_bug.cgi?id=3187 125 if (II == SenTestCaseII) 126 return; 127 } 128 129 if (!ID) 130 return; 131 132 // Get the "dealloc" selector. 133 IdentifierInfo* II = &Ctx.Idents.get("dealloc"); 134 Selector S = Ctx.Selectors.getSelector(0, &II); 135 const ObjCMethodDecl *MD = nullptr; 136 137 // Scan the instance methods for "dealloc". 138 for (const auto *I : D->instance_methods()) { 139 if (I->getSelector() == S) { 140 MD = I; 141 break; 142 } 143 } 144 145 PathDiagnosticLocation DLoc = 146 PathDiagnosticLocation::createBegin(D, BR.getSourceManager()); 147 148 if (!MD) { // No dealloc found. 149 150 const char* name = LOpts.getGC() == LangOptions::NonGC 151 ? "missing -dealloc" 152 : "missing -dealloc (Hybrid MM, non-GC)"; 153 154 std::string buf; 155 llvm::raw_string_ostream os(buf); 156 os << "Objective-C class '" << *D << "' lacks a 'dealloc' instance method"; 157 158 BR.EmitBasicReport(D, Checker, name, categories::CoreFoundationObjectiveC, 159 os.str(), DLoc); 160 return; 161 } 162 163 // Get the "release" selector. 164 IdentifierInfo* RII = &Ctx.Idents.get("release"); 165 Selector RS = Ctx.Selectors.getSelector(0, &RII); 166 167 // Get the "self" identifier 168 IdentifierInfo* SelfII = &Ctx.Idents.get("self"); 169 170 // Scan for missing and extra releases of ivars used by implementations 171 // of synthesized properties 172 for (const auto *I : D->property_impls()) { 173 // We can only check the synthesized properties 174 if (I->getPropertyImplementation() != ObjCPropertyImplDecl::Synthesize) 175 continue; 176 177 ObjCIvarDecl *ID = I->getPropertyIvarDecl(); 178 if (!ID) 179 continue; 180 181 QualType T = ID->getType(); 182 if (!T->isObjCObjectPointerType()) // Skip non-pointer ivars 183 continue; 184 185 const ObjCPropertyDecl *PD = I->getPropertyDecl(); 186 if (!PD) 187 continue; 188 189 // ivars cannot be set via read-only properties, so we'll skip them 190 if (PD->isReadOnly()) 191 continue; 192 193 // ivar must be released if and only if the kind of setter was not 'assign' 194 bool requiresRelease = PD->getSetterKind() != ObjCPropertyDecl::Assign; 195 if (scan_ivar_release(MD->getBody(), ID, PD, RS, SelfII, Ctx) 196 != requiresRelease) { 197 const char *name = nullptr; 198 std::string buf; 199 llvm::raw_string_ostream os(buf); 200 201 if (requiresRelease) { 202 name = LOpts.getGC() == LangOptions::NonGC 203 ? "missing ivar release (leak)" 204 : "missing ivar release (Hybrid MM, non-GC)"; 205 206 os << "The '" << *ID 207 << "' instance variable was retained by a synthesized property but " 208 "wasn't released in 'dealloc'"; 209 } else { 210 name = LOpts.getGC() == LangOptions::NonGC 211 ? "extra ivar release (use-after-release)" 212 : "extra ivar release (Hybrid MM, non-GC)"; 213 214 os << "The '" << *ID 215 << "' instance variable was not retained by a synthesized property " 216 "but was released in 'dealloc'"; 217 } 218 219 PathDiagnosticLocation SDLoc = 220 PathDiagnosticLocation::createBegin(I, BR.getSourceManager()); 221 222 BR.EmitBasicReport(MD, Checker, name, 223 categories::CoreFoundationObjectiveC, os.str(), SDLoc); 224 } 225 } 226 } 227 228 //===----------------------------------------------------------------------===// 229 // ObjCDeallocChecker 230 //===----------------------------------------------------------------------===// 231 232 namespace { 233 class ObjCDeallocChecker : public Checker< 234 check::ASTDecl<ObjCImplementationDecl> > { 235 public: 236 void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager& mgr, 237 BugReporter &BR) const { 238 if (mgr.getLangOpts().getGC() == LangOptions::GCOnly) 239 return; 240 checkObjCDealloc(this, cast<ObjCImplementationDecl>(D), mgr.getLangOpts(), 241 BR); 242 } 243 }; 244 } 245 246 void ento::registerObjCDeallocChecker(CheckerManager &mgr) { 247 mgr.registerChecker<ObjCDeallocChecker>(); 248 } 249