IvarInvalidationChecker.cpp revision bbff82f302a1dd67589f65912351978905f0c5a7
1//=- IvarInvalidationChecker.cpp - -*- 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 checker implements annotation driven invalidation checking. If a class 11// contains a method annotated with 'objc_instance_variable_invalidator', 12// - (void) foo 13// __attribute__((annotate("objc_instance_variable_invalidator"))); 14// all the "ivalidatable" instance variables of this class should be 15// invalidated. We call an instance variable ivalidatable if it is an object of 16// a class which contains an invalidation method. There could be multiple 17// methods annotated with such annotations per class, either one can be used 18// to invalidate the ivar. An ivar or property are considered to be 19// invalidated if they are being assigned 'nil' or an invalidation method has 20// been called on them. An invalidation method should either invalidate all 21// the ivars or call another invalidation method (on self). 22// 23//===----------------------------------------------------------------------===// 24 25#include "ClangSACheckers.h" 26#include "clang/StaticAnalyzer/Core/Checker.h" 27#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" 28#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" 29#include "clang/AST/DeclObjC.h" 30#include "clang/AST/StmtVisitor.h" 31#include "llvm/ADT/DenseMap.h" 32#include "llvm/ADT/SmallString.h" 33 34using namespace clang; 35using namespace ento; 36 37namespace { 38class IvarInvalidationChecker : 39 public Checker<check::ASTDecl<ObjCMethodDecl> > { 40 41 typedef llvm::DenseSet<const ObjCMethodDecl*> MethodSet; 42 typedef llvm::DenseMap<const ObjCMethodDecl*, 43 const ObjCIvarDecl*> MethToIvarMapTy; 44 typedef llvm::DenseMap<const ObjCPropertyDecl*, 45 const ObjCIvarDecl*> PropToIvarMapTy; 46 typedef llvm::DenseMap<const ObjCIvarDecl*, 47 const ObjCPropertyDecl*> IvarToPropMapTy; 48 49 50 struct IvarInfo { 51 /// Has the ivar been invalidated? 52 bool IsInvalidated; 53 54 /// The methods which can be used to invalidate the ivar. 55 MethodSet InvalidationMethods; 56 57 IvarInfo() : IsInvalidated(false) {} 58 void addInvalidationMethod(const ObjCMethodDecl *MD) { 59 InvalidationMethods.insert(MD); 60 } 61 62 bool needsInvalidation() const { 63 return !InvalidationMethods.empty(); 64 } 65 66 void markInvalidated() { 67 IsInvalidated = true; 68 } 69 70 bool markInvalidated(const ObjCMethodDecl *MD) { 71 if (IsInvalidated) 72 return true; 73 for (MethodSet::iterator I = InvalidationMethods.begin(), 74 E = InvalidationMethods.end(); I != E; ++I) { 75 if (*I == MD) { 76 IsInvalidated = true; 77 return true; 78 } 79 } 80 return false; 81 } 82 83 bool isInvalidated() const { 84 return IsInvalidated; 85 } 86 }; 87 88 typedef llvm::DenseMap<const ObjCIvarDecl*, IvarInfo> IvarSet; 89 90 /// Statement visitor, which walks the method body and flags the ivars 91 /// referenced in it (either directly or via property). 92 class MethodCrawler : public ConstStmtVisitor<MethodCrawler> { 93 /// The set of Ivars which need to be invalidated. 94 IvarSet &IVars; 95 96 /// Flag is set as the result of a message send to another 97 /// invalidation method. 98 bool &CalledAnotherInvalidationMethod; 99 100 /// Property setter to ivar mapping. 101 const MethToIvarMapTy &PropertySetterToIvarMap; 102 103 /// Property getter to ivar mapping. 104 const MethToIvarMapTy &PropertyGetterToIvarMap; 105 106 /// Property to ivar mapping. 107 const PropToIvarMapTy &PropertyToIvarMap; 108 109 /// The invalidation method being currently processed. 110 const ObjCMethodDecl *InvalidationMethod; 111 112 ASTContext &Ctx; 113 114 /// Peel off parens, casts, OpaqueValueExpr, and PseudoObjectExpr. 115 const Expr *peel(const Expr *E) const; 116 117 /// Does this expression represent zero: '0'? 118 bool isZero(const Expr *E) const; 119 120 /// Mark the given ivar as invalidated. 121 void markInvalidated(const ObjCIvarDecl *Iv); 122 123 /// Checks if IvarRef refers to the tracked IVar, if yes, marks it as 124 /// invalidated. 125 void checkObjCIvarRefExpr(const ObjCIvarRefExpr *IvarRef); 126 127 /// Checks if ObjCPropertyRefExpr refers to the tracked IVar, if yes, marks 128 /// it as invalidated. 129 void checkObjCPropertyRefExpr(const ObjCPropertyRefExpr *PA); 130 131 /// Checks if ObjCMessageExpr refers to (is a getter for) the tracked IVar, 132 /// if yes, marks it as invalidated. 133 void checkObjCMessageExpr(const ObjCMessageExpr *ME); 134 135 /// Checks if the Expr refers to an ivar, if yes, marks it as invalidated. 136 void check(const Expr *E); 137 138 public: 139 MethodCrawler(IvarSet &InIVars, 140 bool &InCalledAnotherInvalidationMethod, 141 const MethToIvarMapTy &InPropertySetterToIvarMap, 142 const MethToIvarMapTy &InPropertyGetterToIvarMap, 143 const PropToIvarMapTy &InPropertyToIvarMap, 144 ASTContext &InCtx) 145 : IVars(InIVars), 146 CalledAnotherInvalidationMethod(InCalledAnotherInvalidationMethod), 147 PropertySetterToIvarMap(InPropertySetterToIvarMap), 148 PropertyGetterToIvarMap(InPropertyGetterToIvarMap), 149 PropertyToIvarMap(InPropertyToIvarMap), 150 InvalidationMethod(0), 151 Ctx(InCtx) {} 152 153 void VisitStmt(const Stmt *S) { VisitChildren(S); } 154 155 void VisitBinaryOperator(const BinaryOperator *BO); 156 157 void VisitObjCMessageExpr(const ObjCMessageExpr *ME); 158 159 void VisitChildren(const Stmt *S) { 160 for (Stmt::const_child_range I = S->children(); I; ++I) { 161 if (*I) 162 this->Visit(*I); 163 if (CalledAnotherInvalidationMethod) 164 return; 165 } 166 } 167 }; 168 169 /// Check if the any of the methods inside the interface are annotated with 170 /// the invalidation annotation, update the IvarInfo accordingly. 171 static void containsInvalidationMethod(const ObjCContainerDecl *D, 172 IvarInfo &Out); 173 174 /// Check if ivar should be tracked and add to TrackedIvars if positive. 175 /// Returns true if ivar should be tracked. 176 static bool trackIvar(const ObjCIvarDecl *Iv, IvarSet &TrackedIvars); 177 178 /// Given the property declaration, and the list of tracked ivars, finds 179 /// the ivar backing the property when possible. Returns '0' when no such 180 /// ivar could be found. 181 static const ObjCIvarDecl *findPropertyBackingIvar( 182 const ObjCPropertyDecl *Prop, 183 const ObjCInterfaceDecl *InterfaceD, 184 IvarSet &TrackedIvars); 185 186public: 187 void checkASTDecl(const ObjCMethodDecl *D, AnalysisManager& Mgr, 188 BugReporter &BR) const; 189 190 // TODO: We are currently ignoring the ivars coming from class extensions. 191}; 192 193static bool isInvalidationMethod(const ObjCMethodDecl *M) { 194 for (specific_attr_iterator<AnnotateAttr> 195 AI = M->specific_attr_begin<AnnotateAttr>(), 196 AE = M->specific_attr_end<AnnotateAttr>(); AI != AE; ++AI) { 197 const AnnotateAttr *Ann = *AI; 198 if (Ann->getAnnotation() == "objc_instance_variable_invalidator") 199 return true; 200 } 201 return false; 202} 203 204void IvarInvalidationChecker::containsInvalidationMethod( 205 const ObjCContainerDecl *D, IvarInfo &OutInfo) { 206 207 // TODO: Cache the results. 208 209 if (!D) 210 return; 211 212 // Check all methods. 213 for (ObjCContainerDecl::method_iterator 214 I = D->meth_begin(), 215 E = D->meth_end(); I != E; ++I) { 216 const ObjCMethodDecl *MDI = *I; 217 if (isInvalidationMethod(MDI)) 218 OutInfo.addInvalidationMethod( 219 cast<ObjCMethodDecl>(MDI->getCanonicalDecl())); 220 } 221 222 // If interface, check all parent protocols and super. 223 // TODO: Visit all categories in case the invalidation method is declared in 224 // a category. 225 if (const ObjCInterfaceDecl *InterfaceD = dyn_cast<ObjCInterfaceDecl>(D)) { 226 for (ObjCInterfaceDecl::protocol_iterator 227 I = InterfaceD->protocol_begin(), 228 E = InterfaceD->protocol_end(); I != E; ++I) { 229 containsInvalidationMethod(*I, OutInfo); 230 } 231 containsInvalidationMethod(InterfaceD->getSuperClass(), OutInfo); 232 return; 233 } 234 235 // If protocol, check all parent protocols. 236 if (const ObjCProtocolDecl *ProtD = dyn_cast<ObjCProtocolDecl>(D)) { 237 for (ObjCInterfaceDecl::protocol_iterator 238 I = ProtD->protocol_begin(), 239 E = ProtD->protocol_end(); I != E; ++I) { 240 containsInvalidationMethod(*I, OutInfo); 241 } 242 return; 243 } 244 245 llvm_unreachable("One of the casts above should have succeeded."); 246} 247 248bool IvarInvalidationChecker::trackIvar(const ObjCIvarDecl *Iv, 249 IvarSet &TrackedIvars) { 250 QualType IvQTy = Iv->getType(); 251 const ObjCObjectPointerType *IvTy = IvQTy->getAs<ObjCObjectPointerType>(); 252 if (!IvTy) 253 return false; 254 const ObjCInterfaceDecl *IvInterf = IvTy->getInterfaceDecl(); 255 256 IvarInfo Info; 257 containsInvalidationMethod(IvInterf, Info); 258 if (Info.needsInvalidation()) { 259 TrackedIvars[cast<ObjCIvarDecl>(Iv->getCanonicalDecl())] = Info; 260 return true; 261 } 262 return false; 263} 264 265const ObjCIvarDecl *IvarInvalidationChecker::findPropertyBackingIvar( 266 const ObjCPropertyDecl *Prop, 267 const ObjCInterfaceDecl *InterfaceD, 268 IvarSet &TrackedIvars) { 269 const ObjCIvarDecl *IvarD = 0; 270 271 // Lookup for the synthesized case. 272 IvarD = Prop->getPropertyIvarDecl(); 273 if (IvarD) { 274 if (TrackedIvars.count(IvarD)) { 275 return IvarD; 276 } 277 // If the ivar is synthesized we still want to track it. 278 if (trackIvar(IvarD, TrackedIvars)) 279 return IvarD; 280 } 281 282 // Lookup IVars named "_PropName"or "PropName" among the tracked Ivars. 283 StringRef PropName = Prop->getIdentifier()->getName(); 284 for (IvarSet::const_iterator I = TrackedIvars.begin(), 285 E = TrackedIvars.end(); I != E; ++I) { 286 const ObjCIvarDecl *Iv = I->first; 287 StringRef IvarName = Iv->getName(); 288 289 if (IvarName == PropName) 290 return Iv; 291 292 SmallString<128> PropNameWithUnderscore; 293 { 294 llvm::raw_svector_ostream os(PropNameWithUnderscore); 295 os << '_' << PropName; 296 } 297 if (IvarName == PropNameWithUnderscore.str()) 298 return Iv; 299 } 300 301 // Note, this is a possible source of false positives. We could look at the 302 // getter implementation to find the ivar when its name is not derived from 303 // the property name. 304 return 0; 305} 306 307void IvarInvalidationChecker::checkASTDecl(const ObjCMethodDecl *D, 308 AnalysisManager& Mgr, 309 BugReporter &BR) const { 310 // We are only interested in checking the cleanup methods. 311 if (!D->hasBody() || !isInvalidationMethod(D)) 312 return; 313 314 // Collect all ivars that need cleanup. 315 IvarSet Ivars; 316 const ObjCInterfaceDecl *InterfaceD = D->getClassInterface(); 317 for (ObjCInterfaceDecl::ivar_iterator 318 II = InterfaceD->ivar_begin(), 319 IE = InterfaceD->ivar_end(); II != IE; ++II) { 320 const ObjCIvarDecl *Iv = *II; 321 trackIvar(Iv, Ivars); 322 } 323 324 // Construct Property/Property Accessor to Ivar maps to assist checking if an 325 // ivar which is backing a property has been reset. 326 MethToIvarMapTy PropSetterToIvarMap; 327 MethToIvarMapTy PropGetterToIvarMap; 328 PropToIvarMapTy PropertyToIvarMap; 329 IvarToPropMapTy IvarToPopertyMap; 330 for (ObjCInterfaceDecl::prop_iterator 331 I = InterfaceD->prop_begin(), 332 E = InterfaceD->prop_end(); I != E; ++I) { 333 const ObjCPropertyDecl *PD = *I; 334 335 const ObjCIvarDecl *ID = findPropertyBackingIvar(PD, InterfaceD, Ivars); 336 if (!ID) { 337 continue; 338 } 339 340 // Store the mappings. 341 PD = cast<ObjCPropertyDecl>(PD->getCanonicalDecl()); 342 PropertyToIvarMap[PD] = ID; 343 IvarToPopertyMap[ID] = PD; 344 345 // Find the setter and the getter. 346 const ObjCMethodDecl *SetterD = PD->getSetterMethodDecl(); 347 if (SetterD) { 348 SetterD = cast<ObjCMethodDecl>(SetterD->getCanonicalDecl()); 349 PropSetterToIvarMap[SetterD] = ID; 350 } 351 352 const ObjCMethodDecl *GetterD = PD->getGetterMethodDecl(); 353 if (GetterD) { 354 GetterD = cast<ObjCMethodDecl>(GetterD->getCanonicalDecl()); 355 PropGetterToIvarMap[GetterD] = ID; 356 } 357 } 358 359 360 // Check which ivars have been invalidated in the method body. 361 bool CalledAnotherInvalidationMethod = false; 362 MethodCrawler(Ivars, 363 CalledAnotherInvalidationMethod, 364 PropSetterToIvarMap, 365 PropGetterToIvarMap, 366 PropertyToIvarMap, 367 BR.getContext()).VisitStmt(D->getBody()); 368 369 if (CalledAnotherInvalidationMethod) 370 return; 371 372 // Warn on the ivars that were not accessed by the method. 373 for (IvarSet::const_iterator I = Ivars.begin(), E = Ivars.end(); I != E; ++I){ 374 if (!I->second.isInvalidated()) { 375 const ObjCIvarDecl *IvarDecl = I->first; 376 377 PathDiagnosticLocation IvarDecLocation = 378 PathDiagnosticLocation::createEnd(D->getBody(), BR.getSourceManager(), 379 Mgr.getAnalysisDeclContext(D)); 380 381 SmallString<128> sbuf; 382 llvm::raw_svector_ostream os(sbuf); 383 384 // Construct the warning message. 385 if (IvarDecl->getSynthesize()) { 386 const ObjCPropertyDecl *PD = IvarToPopertyMap[IvarDecl]; 387 assert(PD && 388 "Do we synthesize ivars for something other than properties?"); 389 os << "Property "<< PD->getName() << " needs to be invalidated"; 390 } else { 391 os << "Instance variable "<< IvarDecl->getName() 392 << " needs to be invalidated"; 393 } 394 395 BR.EmitBasicReport(D, 396 "Incomplete invalidation", 397 categories::CoreFoundationObjectiveC, os.str(), 398 IvarDecLocation); 399 } 400 } 401} 402 403void IvarInvalidationChecker::MethodCrawler::markInvalidated( 404 const ObjCIvarDecl *Iv) { 405 IvarSet::iterator I = IVars.find(Iv); 406 if (I != IVars.end()) { 407 // If InvalidationMethod is present, we are processing the message send and 408 // should ensure we are invalidating with the appropriate method, 409 // otherwise, we are processing setting to 'nil'. 410 if (InvalidationMethod) 411 I->second.markInvalidated(InvalidationMethod); 412 else 413 I->second.markInvalidated(); 414 } 415} 416 417const Expr *IvarInvalidationChecker::MethodCrawler::peel(const Expr *E) const { 418 E = E->IgnoreParenCasts(); 419 if (const PseudoObjectExpr *POE = dyn_cast<PseudoObjectExpr>(E)) 420 E = POE->getSyntacticForm()->IgnoreParenCasts(); 421 if (const OpaqueValueExpr *OVE = dyn_cast<OpaqueValueExpr>(E)) 422 E = OVE->getSourceExpr()->IgnoreParenCasts(); 423 return E; 424} 425 426void IvarInvalidationChecker::MethodCrawler::checkObjCIvarRefExpr( 427 const ObjCIvarRefExpr *IvarRef) { 428 if (const Decl *D = IvarRef->getDecl()) 429 markInvalidated(cast<ObjCIvarDecl>(D->getCanonicalDecl())); 430} 431 432void IvarInvalidationChecker::MethodCrawler::checkObjCMessageExpr( 433 const ObjCMessageExpr *ME) { 434 const ObjCMethodDecl *MD = ME->getMethodDecl(); 435 if (MD) { 436 MD = cast<ObjCMethodDecl>(MD->getCanonicalDecl()); 437 MethToIvarMapTy::const_iterator IvI = PropertyGetterToIvarMap.find(MD); 438 if (IvI != PropertyGetterToIvarMap.end()) 439 markInvalidated(IvI->second); 440 } 441} 442 443void IvarInvalidationChecker::MethodCrawler::checkObjCPropertyRefExpr( 444 const ObjCPropertyRefExpr *PA) { 445 446 if (PA->isExplicitProperty()) { 447 const ObjCPropertyDecl *PD = PA->getExplicitProperty(); 448 if (PD) { 449 PD = cast<ObjCPropertyDecl>(PD->getCanonicalDecl()); 450 PropToIvarMapTy::const_iterator IvI = PropertyToIvarMap.find(PD); 451 if (IvI != PropertyToIvarMap.end()) 452 markInvalidated(IvI->second); 453 return; 454 } 455 } 456 457 if (PA->isImplicitProperty()) { 458 const ObjCMethodDecl *MD = PA->getImplicitPropertySetter(); 459 if (MD) { 460 MD = cast<ObjCMethodDecl>(MD->getCanonicalDecl()); 461 MethToIvarMapTy::const_iterator IvI =PropertyGetterToIvarMap.find(MD); 462 if (IvI != PropertyGetterToIvarMap.end()) 463 markInvalidated(IvI->second); 464 return; 465 } 466 } 467} 468 469bool IvarInvalidationChecker::MethodCrawler::isZero(const Expr *E) const { 470 E = peel(E); 471 472 return (E->isNullPointerConstant(Ctx, Expr::NPC_ValueDependentIsNotNull) 473 != Expr::NPCK_NotNull); 474} 475 476void IvarInvalidationChecker::MethodCrawler::check(const Expr *E) { 477 E = peel(E); 478 479 if (const ObjCIvarRefExpr *IvarRef = dyn_cast<ObjCIvarRefExpr>(E)) { 480 checkObjCIvarRefExpr(IvarRef); 481 return; 482 } 483 484 if (const ObjCPropertyRefExpr *PropRef = dyn_cast<ObjCPropertyRefExpr>(E)) { 485 checkObjCPropertyRefExpr(PropRef); 486 return; 487 } 488 489 if (const ObjCMessageExpr *MsgExpr = dyn_cast<ObjCMessageExpr>(E)) { 490 checkObjCMessageExpr(MsgExpr); 491 return; 492 } 493} 494 495void IvarInvalidationChecker::MethodCrawler::VisitBinaryOperator( 496 const BinaryOperator *BO) { 497 VisitStmt(BO); 498 499 if (BO->getOpcode() != BO_Assign) 500 return; 501 502 // Do we assign zero? 503 if (!isZero(BO->getRHS())) 504 return; 505 506 // Check the variable we are assigning to. 507 check(BO->getLHS()); 508} 509 510void IvarInvalidationChecker::MethodCrawler::VisitObjCMessageExpr( 511 const ObjCMessageExpr *ME) { 512 const ObjCMethodDecl *MD = ME->getMethodDecl(); 513 const Expr *Receiver = ME->getInstanceReceiver(); 514 515 // Stop if we are calling '[self invalidate]'. 516 if (Receiver && isInvalidationMethod(MD)) 517 if (Receiver->isObjCSelfExpr()) { 518 CalledAnotherInvalidationMethod = true; 519 return; 520 } 521 522 // Check if we call a setter and set the property to 'nil'. 523 if (MD && (ME->getNumArgs() == 1) && isZero(ME->getArg(0))) { 524 MD = cast<ObjCMethodDecl>(MD->getCanonicalDecl()); 525 MethToIvarMapTy::const_iterator IvI = PropertySetterToIvarMap.find(MD); 526 if (IvI != PropertySetterToIvarMap.end()) { 527 markInvalidated(IvI->second); 528 return; 529 } 530 } 531 532 // Check if we call the 'invalidation' routine on the ivar. 533 if (Receiver) { 534 InvalidationMethod = MD; 535 check(Receiver->IgnoreParenCasts()); 536 InvalidationMethod = 0; 537 } 538 539 VisitStmt(ME); 540} 541} 542 543// Register the checker. 544void ento::registerIvarInvalidationChecker(CheckerManager &mgr) { 545 mgr.registerChecker<IvarInvalidationChecker>(); 546} 547