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