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