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