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 ObjCImplementationDecl *D, 101 const LangOptions& LOpts, BugReporter& BR) { 102 103 assert (LOpts.getGC() != LangOptions::GCOnly); 104 105 ASTContext &Ctx = BR.getContext(); 106 const ObjCInterfaceDecl *ID = D->getClassInterface(); 107 108 // Does the class contain any ivars that are pointers (or id<...>)? 109 // If not, skip the check entirely. 110 // NOTE: This is motivated by PR 2517: 111 // http://llvm.org/bugs/show_bug.cgi?id=2517 112 113 bool containsPointerIvar = false; 114 115 for (ObjCInterfaceDecl::ivar_iterator I=ID->ivar_begin(), E=ID->ivar_end(); 116 I!=E; ++I) { 117 118 ObjCIvarDecl *ID = *I; 119 QualType T = ID->getType(); 120 121 if (!T->isObjCObjectPointerType() || 122 ID->getAttr<IBOutletAttr>() || // Skip IBOutlets. 123 ID->getAttr<IBOutletCollectionAttr>()) // Skip IBOutletCollections. 124 continue; 125 126 containsPointerIvar = true; 127 break; 128 } 129 130 if (!containsPointerIvar) 131 return; 132 133 // Determine if the class subclasses NSObject. 134 IdentifierInfo* NSObjectII = &Ctx.Idents.get("NSObject"); 135 IdentifierInfo* SenTestCaseII = &Ctx.Idents.get("SenTestCase"); 136 137 138 for ( ; ID ; ID = ID->getSuperClass()) { 139 IdentifierInfo *II = ID->getIdentifier(); 140 141 if (II == NSObjectII) 142 break; 143 144 // FIXME: For now, ignore classes that subclass SenTestCase, as these don't 145 // need to implement -dealloc. They implement tear down in another way, 146 // which we should try and catch later. 147 // http://llvm.org/bugs/show_bug.cgi?id=3187 148 if (II == SenTestCaseII) 149 return; 150 } 151 152 if (!ID) 153 return; 154 155 // Get the "dealloc" selector. 156 IdentifierInfo* II = &Ctx.Idents.get("dealloc"); 157 Selector S = Ctx.Selectors.getSelector(0, &II); 158 ObjCMethodDecl *MD = 0; 159 160 // Scan the instance methods for "dealloc". 161 for (ObjCImplementationDecl::instmeth_iterator I = D->instmeth_begin(), 162 E = D->instmeth_end(); I!=E; ++I) { 163 164 if ((*I)->getSelector() == S) { 165 MD = *I; 166 break; 167 } 168 } 169 170 PathDiagnosticLocation DLoc = 171 PathDiagnosticLocation::createBegin(D, BR.getSourceManager()); 172 173 if (!MD) { // No dealloc found. 174 175 const char* name = LOpts.getGC() == LangOptions::NonGC 176 ? "missing -dealloc" 177 : "missing -dealloc (Hybrid MM, non-GC)"; 178 179 std::string buf; 180 llvm::raw_string_ostream os(buf); 181 os << "Objective-C class '" << *D << "' lacks a 'dealloc' instance method"; 182 183 BR.EmitBasicReport(D, name, categories::CoreFoundationObjectiveC, 184 os.str(), DLoc); 185 return; 186 } 187 188 // dealloc found. Scan for missing [super dealloc]. 189 if (MD->getBody() && !scan_dealloc(MD->getBody(), S)) { 190 191 const char* name = LOpts.getGC() == LangOptions::NonGC 192 ? "missing [super dealloc]" 193 : "missing [super dealloc] (Hybrid MM, non-GC)"; 194 195 std::string buf; 196 llvm::raw_string_ostream os(buf); 197 os << "The 'dealloc' instance method in Objective-C class '" << *D 198 << "' does not send a 'dealloc' message to its super class" 199 " (missing [super dealloc])"; 200 201 BR.EmitBasicReport(MD, name, categories::CoreFoundationObjectiveC, 202 os.str(), DLoc); 203 return; 204 } 205 206 // Get the "release" selector. 207 IdentifierInfo* RII = &Ctx.Idents.get("release"); 208 Selector RS = Ctx.Selectors.getSelector(0, &RII); 209 210 // Get the "self" identifier 211 IdentifierInfo* SelfII = &Ctx.Idents.get("self"); 212 213 // Scan for missing and extra releases of ivars used by implementations 214 // of synthesized properties 215 for (ObjCImplementationDecl::propimpl_iterator I = D->propimpl_begin(), 216 E = D->propimpl_end(); I!=E; ++I) { 217 218 // We can only check the synthesized properties 219 if (I->getPropertyImplementation() != ObjCPropertyImplDecl::Synthesize) 220 continue; 221 222 ObjCIvarDecl *ID = I->getPropertyIvarDecl(); 223 if (!ID) 224 continue; 225 226 QualType T = ID->getType(); 227 if (!T->isObjCObjectPointerType()) // Skip non-pointer ivars 228 continue; 229 230 const ObjCPropertyDecl *PD = I->getPropertyDecl(); 231 if (!PD) 232 continue; 233 234 // ivars cannot be set via read-only properties, so we'll skip them 235 if (PD->isReadOnly()) 236 continue; 237 238 // ivar must be released if and only if the kind of setter was not 'assign' 239 bool requiresRelease = PD->getSetterKind() != ObjCPropertyDecl::Assign; 240 if (scan_ivar_release(MD->getBody(), ID, PD, RS, SelfII, Ctx) 241 != requiresRelease) { 242 const char *name = 0; 243 std::string buf; 244 llvm::raw_string_ostream os(buf); 245 246 if (requiresRelease) { 247 name = LOpts.getGC() == LangOptions::NonGC 248 ? "missing ivar release (leak)" 249 : "missing ivar release (Hybrid MM, non-GC)"; 250 251 os << "The '" << *ID 252 << "' instance variable was retained by a synthesized property but " 253 "wasn't released in 'dealloc'"; 254 } else { 255 name = LOpts.getGC() == LangOptions::NonGC 256 ? "extra ivar release (use-after-release)" 257 : "extra ivar release (Hybrid MM, non-GC)"; 258 259 os << "The '" << *ID 260 << "' instance variable was not retained by a synthesized property " 261 "but was released in 'dealloc'"; 262 } 263 264 PathDiagnosticLocation SDLoc = 265 PathDiagnosticLocation::createBegin(*I, BR.getSourceManager()); 266 267 BR.EmitBasicReport(MD, name, categories::CoreFoundationObjectiveC, 268 os.str(), SDLoc); 269 } 270 } 271 } 272 273 //===----------------------------------------------------------------------===// 274 // ObjCDeallocChecker 275 //===----------------------------------------------------------------------===// 276 277 namespace { 278 class ObjCDeallocChecker : public Checker< 279 check::ASTDecl<ObjCImplementationDecl> > { 280 public: 281 void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager& mgr, 282 BugReporter &BR) const { 283 if (mgr.getLangOpts().getGC() == LangOptions::GCOnly) 284 return; 285 checkObjCDealloc(cast<ObjCImplementationDecl>(D), mgr.getLangOpts(), BR); 286 } 287 }; 288 } 289 290 void ento::registerObjCDeallocChecker(CheckerManager &mgr) { 291 mgr.registerChecker<ObjCDeallocChecker>(); 292 } 293