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 "clang/StaticAnalyzer/Checkers/LocalCheckers.h" 17 #include "clang/StaticAnalyzer/BugReporter/PathDiagnostic.h" 18 #include "clang/StaticAnalyzer/BugReporter/BugReporter.h" 19 #include "clang/AST/ExprObjC.h" 20 #include "clang/AST/Expr.h" 21 #include "clang/AST/DeclObjC.h" 22 #include "clang/Basic/LangOptions.h" 23 #include "llvm/Support/raw_ostream.h" 24 25 using namespace clang; 26 using namespace ento; 27 28 static bool scan_dealloc(Stmt* S, Selector Dealloc) { 29 30 if (ObjCMessageExpr* ME = dyn_cast<ObjCMessageExpr>(S)) 31 if (ME->getSelector() == Dealloc) { 32 switch (ME->getReceiverKind()) { 33 case ObjCMessageExpr::Instance: return false; 34 case ObjCMessageExpr::SuperInstance: return true; 35 case ObjCMessageExpr::Class: break; 36 case ObjCMessageExpr::SuperClass: break; 37 } 38 } 39 40 // Recurse to children. 41 42 for (Stmt::child_iterator I = S->child_begin(), E= S->child_end(); I!=E; ++I) 43 if (*I && scan_dealloc(*I, Dealloc)) 44 return true; 45 46 return false; 47 } 48 49 static bool scan_ivar_release(Stmt* S, ObjCIvarDecl* ID, 50 const ObjCPropertyDecl* PD, 51 Selector Release, 52 IdentifierInfo* SelfII, 53 ASTContext& Ctx) { 54 55 // [mMyIvar release] 56 if (ObjCMessageExpr* ME = dyn_cast<ObjCMessageExpr>(S)) 57 if (ME->getSelector() == Release) 58 if (ME->getInstanceReceiver()) 59 if (Expr* Receiver = ME->getInstanceReceiver()->IgnoreParenCasts()) 60 if (ObjCIvarRefExpr* E = dyn_cast<ObjCIvarRefExpr>(Receiver)) 61 if (E->getDecl() == ID) 62 return true; 63 64 // [self setMyIvar:nil]; 65 if (ObjCMessageExpr* ME = dyn_cast<ObjCMessageExpr>(S)) 66 if (ME->getInstanceReceiver()) 67 if (Expr* Receiver = ME->getInstanceReceiver()->IgnoreParenCasts()) 68 if (DeclRefExpr* E = dyn_cast<DeclRefExpr>(Receiver)) 69 if (E->getDecl()->getIdentifier() == SelfII) 70 if (ME->getMethodDecl() == PD->getSetterMethodDecl() && 71 ME->getNumArgs() == 1 && 72 ME->getArg(0)->isNullPointerConstant(Ctx, 73 Expr::NPC_ValueDependentIsNull)) 74 return true; 75 76 // self.myIvar = nil; 77 if (BinaryOperator* BO = dyn_cast<BinaryOperator>(S)) 78 if (BO->isAssignmentOp()) 79 if (ObjCPropertyRefExpr* PRE = 80 dyn_cast<ObjCPropertyRefExpr>(BO->getLHS()->IgnoreParenCasts())) 81 if (PRE->isExplicitProperty() && PRE->getExplicitProperty() == PD) 82 if (BO->getRHS()->isNullPointerConstant(Ctx, 83 Expr::NPC_ValueDependentIsNull)) { 84 // This is only a 'release' if the property kind is not 85 // 'assign'. 86 return PD->getSetterKind() != ObjCPropertyDecl::Assign;; 87 } 88 89 // Recurse to children. 90 for (Stmt::child_iterator I = S->child_begin(), E= S->child_end(); I!=E; ++I) 91 if (*I && scan_ivar_release(*I, ID, PD, Release, SelfII, Ctx)) 92 return true; 93 94 return false; 95 } 96 97 void ento::CheckObjCDealloc(const ObjCImplementationDecl* D, 98 const LangOptions& LOpts, BugReporter& BR) { 99 100 assert (LOpts.getGCMode() != LangOptions::GCOnly); 101 102 ASTContext& Ctx = BR.getContext(); 103 const ObjCInterfaceDecl* ID = D->getClassInterface(); 104 105 // Does the class contain any ivars that are pointers (or id<...>)? 106 // If not, skip the check entirely. 107 // NOTE: This is motivated by PR 2517: 108 // http://llvm.org/bugs/show_bug.cgi?id=2517 109 110 bool containsPointerIvar = false; 111 112 for (ObjCInterfaceDecl::ivar_iterator I=ID->ivar_begin(), E=ID->ivar_end(); 113 I!=E; ++I) { 114 115 ObjCIvarDecl* ID = *I; 116 QualType T = ID->getType(); 117 118 if (!T->isObjCObjectPointerType() || 119 ID->getAttr<IBOutletAttr>() || // Skip IBOutlets. 120 ID->getAttr<IBOutletCollectionAttr>()) // Skip IBOutletCollections. 121 continue; 122 123 containsPointerIvar = true; 124 break; 125 } 126 127 if (!containsPointerIvar) 128 return; 129 130 // Determine if the class subclasses NSObject. 131 IdentifierInfo* NSObjectII = &Ctx.Idents.get("NSObject"); 132 IdentifierInfo* SenTestCaseII = &Ctx.Idents.get("SenTestCase"); 133 134 135 for ( ; ID ; ID = ID->getSuperClass()) { 136 IdentifierInfo *II = ID->getIdentifier(); 137 138 if (II == NSObjectII) 139 break; 140 141 // FIXME: For now, ignore classes that subclass SenTestCase, as these don't 142 // need to implement -dealloc. They implement tear down in another way, 143 // which we should try and catch later. 144 // http://llvm.org/bugs/show_bug.cgi?id=3187 145 if (II == SenTestCaseII) 146 return; 147 } 148 149 if (!ID) 150 return; 151 152 // Get the "dealloc" selector. 153 IdentifierInfo* II = &Ctx.Idents.get("dealloc"); 154 Selector S = Ctx.Selectors.getSelector(0, &II); 155 ObjCMethodDecl* MD = 0; 156 157 // Scan the instance methods for "dealloc". 158 for (ObjCImplementationDecl::instmeth_iterator I = D->instmeth_begin(), 159 E = D->instmeth_end(); I!=E; ++I) { 160 161 if ((*I)->getSelector() == S) { 162 MD = *I; 163 break; 164 } 165 } 166 167 if (!MD) { // No dealloc found. 168 169 const char* name = LOpts.getGCMode() == LangOptions::NonGC 170 ? "missing -dealloc" 171 : "missing -dealloc (Hybrid MM, non-GC)"; 172 173 std::string buf; 174 llvm::raw_string_ostream os(buf); 175 os << "Objective-C class '" << D << "' lacks a 'dealloc' instance method"; 176 177 BR.EmitBasicReport(name, os.str(), D->getLocStart()); 178 return; 179 } 180 181 // dealloc found. Scan for missing [super dealloc]. 182 if (MD->getBody() && !scan_dealloc(MD->getBody(), S)) { 183 184 const char* name = LOpts.getGCMode() == LangOptions::NonGC 185 ? "missing [super dealloc]" 186 : "missing [super dealloc] (Hybrid MM, non-GC)"; 187 188 std::string buf; 189 llvm::raw_string_ostream os(buf); 190 os << "The 'dealloc' instance method in Objective-C class '" << D 191 << "' does not send a 'dealloc' message to its super class" 192 " (missing [super dealloc])"; 193 194 BR.EmitBasicReport(name, os.str(), D->getLocStart()); 195 return; 196 } 197 198 // Get the "release" selector. 199 IdentifierInfo* RII = &Ctx.Idents.get("release"); 200 Selector RS = Ctx.Selectors.getSelector(0, &RII); 201 202 // Get the "self" identifier 203 IdentifierInfo* SelfII = &Ctx.Idents.get("self"); 204 205 // Scan for missing and extra releases of ivars used by implementations 206 // of synthesized properties 207 for (ObjCImplementationDecl::propimpl_iterator I = D->propimpl_begin(), 208 E = D->propimpl_end(); I!=E; ++I) { 209 210 // We can only check the synthesized properties 211 if ((*I)->getPropertyImplementation() != ObjCPropertyImplDecl::Synthesize) 212 continue; 213 214 ObjCIvarDecl* ID = (*I)->getPropertyIvarDecl(); 215 if (!ID) 216 continue; 217 218 QualType T = ID->getType(); 219 if (!T->isObjCObjectPointerType()) // Skip non-pointer ivars 220 continue; 221 222 const ObjCPropertyDecl* PD = (*I)->getPropertyDecl(); 223 if (!PD) 224 continue; 225 226 // ivars cannot be set via read-only properties, so we'll skip them 227 if (PD->isReadOnly()) 228 continue; 229 230 // ivar must be released if and only if the kind of setter was not 'assign' 231 bool requiresRelease = PD->getSetterKind() != ObjCPropertyDecl::Assign; 232 if (scan_ivar_release(MD->getBody(), ID, PD, RS, SelfII, Ctx) 233 != requiresRelease) { 234 const char *name; 235 const char* category = "Memory (Core Foundation/Objective-C)"; 236 237 std::string buf; 238 llvm::raw_string_ostream os(buf); 239 240 if (requiresRelease) { 241 name = LOpts.getGCMode() == LangOptions::NonGC 242 ? "missing ivar release (leak)" 243 : "missing ivar release (Hybrid MM, non-GC)"; 244 245 os << "The '" << ID 246 << "' instance variable was retained by a synthesized property but " 247 "wasn't released in 'dealloc'"; 248 } else { 249 name = LOpts.getGCMode() == LangOptions::NonGC 250 ? "extra ivar release (use-after-release)" 251 : "extra ivar release (Hybrid MM, non-GC)"; 252 253 os << "The '" << ID 254 << "' instance variable was not retained by a synthesized property " 255 "but was released in 'dealloc'"; 256 } 257 258 BR.EmitBasicReport(name, category, os.str(), (*I)->getLocation()); 259 } 260 } 261 } 262 263