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