void clang::CheckDeadStores(CFG &cfg, LiveVariables &L, ParentMap &pmap, BugReporter& BR) { FindEscaped FS(&cfg); FS.getCFG().VisitBlockStmts(FS); DeadStoreObs A(BR.getContext(), BR, pmap, FS.Escaped); L.runOnAllBlocks(cfg, &A); }
// OutputPossibleOverflows - We've found a possible overflow earlier, // now check whether Body might contain a comparison which might be // preventing the overflow. // This doesn't do flow analysis, range analysis, or points-to analysis; it's // just a dumb "is there a comparison" scan. The aim here is to // detect the most blatent cases of overflow and educate the // programmer. void MallocOverflowSecurityChecker::OutputPossibleOverflows( llvm::SmallVectorImpl<MallocOverflowCheck> &PossibleMallocOverflows, const Decl *D, BugReporter &BR, AnalysisManager &mgr) const { // By far the most common case: nothing to check. if (PossibleMallocOverflows.empty()) return; // Delete any possible overflows which have a comparison. CheckOverflowOps c(PossibleMallocOverflows, BR.getContext()); c.Visit(mgr.getAnalysisContext(D)->getBody()); // Output warnings for all overflows that are left. for (CheckOverflowOps::theVecType::iterator i = PossibleMallocOverflows.begin(), e = PossibleMallocOverflows.end(); i != e; ++i) { SourceRange R = i->mulop->getSourceRange(); BR.EmitBasicReport("MallocOverflowSecurityChecker", "the computation of the size of the memory allocation may overflow", PathDiagnosticLocation::createOperatorLoc(i->mulop, BR.getSourceManager()), &R, 1); } }
void NSErrorChecker::FlushReports(BugReporter& BR) { // Get the analysis engine and the exploded analysis graph. ExplodedGraph& G = Eng.getGraph(); // Get the ASTContext, which is useful for querying type information. ASTContext &Ctx = BR.getContext(); QualType ResultTy; llvm::SmallVector<VarDecl*, 5> ErrorParams; if (const ObjCMethodDecl* MD = dyn_cast<ObjCMethodDecl>(&CodeDecl)) CheckSignature(*MD, ResultTy, ErrorParams); else if (const FunctionDecl* FD = dyn_cast<FunctionDecl>(&CodeDecl)) CheckSignature(*FD, ResultTy, ErrorParams); else return; if (ErrorParams.empty()) return; if (ResultTy == Ctx.VoidTy) EmitRetTyWarning(BR, CodeDecl); for (ExplodedGraph::roots_iterator RI=G.roots_begin(), RE=G.roots_end(); RI!=RE; ++RI) { // Scan the parameters for an implicit null dereference. for (llvm::SmallVectorImpl<VarDecl*>::iterator I=ErrorParams.begin(), E=ErrorParams.end(); I!=E; ++I) CheckParamDeref(*I, (*RI)->getLocationContext(), (*RI)->getState(), BR); } }
void iOSAppSecAbusingURLSchemesChecker::checkASTDecl(const ObjCImplementationDecl *pImplDecl, AnalysisManager &rMgr, BugReporter &rBR) const { MSEC_DEBUG_FUNC( "redwud: ", "ENTER" ) ; ASTContext &Ctx = rBR.getContext() ; do { initIdentifierInfo( Ctx ) ; const ObjCInterfaceDecl *pIfDecl = CMSecCommon::isSupportedProtocol( pImplDecl ->getClassInterface(), m_piiUIApplicationDelegate ) ; if ( !pIfDecl ) { MSEC_DEBUG( "redwud: ", "!pIfDecl" ) ; break ; } //Pitfall: This should be instance method for ( ObjCImplementationDecl::instmeth_iterator pItem = pImplDecl ->instmeth_begin() , pEndItem = pImplDecl ->instmeth_end(); pItem != pEndItem; ++pItem ) { checkASTDecl( (*pItem), rMgr, rBR ) ; } } while (_PASSING_) ; MSEC_DEBUG_FUNC( "redwud: ", "EXIT" ) ; }
void iOSAppSecLeakingWebCachesChecker::checkASTDecl(const ObjCMethodDecl *pMD, AnalysisManager &rMgr, BugReporter &rBR) const { MSEC_DEBUG_FUNC( "redwud: ", "ENTER (Method)" ) ; do { if ( !pMD ) { break ; } Selector S = pMD ->getSelector() ; //There is no point of checking if you can't report it if ( !m_pInsecureInstBugType ) { MSEC_DEBUG("redwud: ","Reporting will fail!" ) ; break ; } // connection: if ( S.getIdentifierInfoForSlot( 0 ) != m_piiConnection) { //MSEC_DEBUG("redwud: ","!connection: " << S.getAsString() ) ; break ; } ASTContext &Ctx = rBR.getContext() ; // Two arguments if ( S.getNumArgs() != 2 ) { break ; } // willCacheResponse: if ( S.getIdentifierInfoForSlot(1) != m_piiWillCacheResponse ) { break ; } //Note: Intentionally made it common for both if ( !checkWillCacheResponse( pMD, Ctx ) ) { break ; } CMSecCommon::reportInsecureInstance( *m_pInsecureInstBugType , m_szReportDesc, rBR, pMD ) ; } while(_PASSING_) ; MSEC_DEBUG_FUNC( "redwud: ", "EXIT (Method)" ) ; }
void clang::CheckObjCUnusedIvar(ObjCImplementationDecl* D, BugReporter& BR) { ObjCInterfaceDecl* ID = D->getClassInterface(); IvarUsageMap M; ASTContext &Ctx = BR.getContext(); // Iterate over the ivars. for (ObjCInterfaceDecl::ivar_iterator I=ID->ivar_begin(), E=ID->ivar_end(); I!=E; ++I) { ObjCIvarDecl* ID = *I; // Ignore ivars that aren't private. if (ID->getAccessControl() != ObjCIvarDecl::Private) continue; // Skip IB Outlets. if (ID->getAttr<IBOutletAttr>()) continue; M[ID] = Unused; } if (M.empty()) return; // Now scan the methods for accesses. for (ObjCImplementationDecl::instmeth_iterator I = D->instmeth_begin(Ctx), E = D->instmeth_end(Ctx); I!=E; ++I) Scan(M, (*I)->getBody(Ctx)); // Scan for @synthesized property methods that act as setters/getters // to an ivar. for (ObjCImplementationDecl::propimpl_iterator I = D->propimpl_begin(Ctx), E = D->propimpl_end(Ctx); I!=E; ++I) Scan(M, *I); // Find ivars that are unused. for (IvarUsageMap::iterator I = M.begin(), E = M.end(); I!=E; ++I) if (I->second == Unused) { std::ostringstream os; os << "Instance variable '" << I->first->getNameAsString() << "' in class '" << ID->getNameAsString() << "' is never used by the methods in its @implementation " "(although it may be used by category methods)."; BR.EmitBasicReport("Unused instance variable", "Optimization", os.str().c_str(), I->first->getLocation()); } }
void ObjCSuperCallChecker::checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager &Mgr, BugReporter &BR) const { ASTContext &Ctx = BR.getContext(); // We need to initialize the selector table once. if (!IsInitialized) initializeSelectors(Ctx); // Find out whether this class has a superclass that we are supposed to check. StringRef SuperclassName; if (!isCheckableClass(D, SuperclassName)) return; // Iterate over all instance methods. for (auto *MD : D->instance_methods()) { Selector S = MD->getSelector(); // Find out whether this is a selector that we want to check. if (!SelectorsForClass[SuperclassName].count(S)) continue; // Check if the method calls its superclass implementation. if (MD->getBody()) { FindSuperCallVisitor Visitor(S); Visitor.TraverseDecl(MD); // It doesn't call super, emit a diagnostic. if (!Visitor.DoesCallSuper) { PathDiagnosticLocation DLoc = PathDiagnosticLocation::createEnd(MD->getBody(), BR.getSourceManager(), Mgr.getAnalysisDeclContext(D)); const char *Name = "Missing call to superclass"; SmallString<320> Buf; llvm::raw_svector_ostream os(Buf); os << "The '" << S.getAsString() << "' instance method in " << SuperclassName.str() << " subclass '" << *D << "' is missing a [super " << S.getAsString() << "] call"; BR.EmitBasicReport(MD, this, Name, categories::CoreFoundationObjectiveC, os.str(), DLoc); } } } }
void ento::CheckObjCInstMethSignature(const ObjCImplementationDecl* ID, BugReporter& BR) { const ObjCInterfaceDecl* D = ID->getClassInterface(); const ObjCInterfaceDecl* C = D->getSuperClass(); if (!C) return; ASTContext& Ctx = BR.getContext(); // Build a DenseMap of the methods for quick querying. typedef llvm::DenseMap<Selector,ObjCMethodDecl*> MapTy; MapTy IMeths; unsigned NumMethods = 0; for (ObjCImplementationDecl::instmeth_iterator I=ID->instmeth_begin(), E=ID->instmeth_end(); I!=E; ++I) { ObjCMethodDecl* M = *I; IMeths[M->getSelector()] = M; ++NumMethods; } // Now recurse the class hierarchy chain looking for methods with the // same signatures. while (C && NumMethods) { for (ObjCInterfaceDecl::instmeth_iterator I=C->instmeth_begin(), E=C->instmeth_end(); I!=E; ++I) { ObjCMethodDecl* M = *I; Selector S = M->getSelector(); MapTy::iterator MI = IMeths.find(S); if (MI == IMeths.end() || MI->second == 0) continue; --NumMethods; ObjCMethodDecl* MethDerived = MI->second; MI->second = 0; CompareReturnTypes(MethDerived, M, BR, Ctx, ID); } C = C->getSuperClass(); } }
void UnreachableCodeChecker::checkEndAnalysis(ExplodedGraph &G, BugReporter &B, ExprEngine &Eng) const { CFGBlocksSet reachable, visited; if (Eng.hasWorkRemaining()) return; CFG *C = 0; ParentMap *PM = 0; // Iterate over ExplodedGraph for (ExplodedGraph::node_iterator I = G.nodes_begin(), E = G.nodes_end(); I != E; ++I) { const ProgramPoint &P = I->getLocation(); const LocationContext *LC = P.getLocationContext(); // Save the CFG if we don't have it already if (!C) C = LC->getAnalysisContext()->getUnoptimizedCFG(); if (!PM) PM = &LC->getParentMap(); if (const BlockEntrance *BE = dyn_cast<BlockEntrance>(&P)) { const CFGBlock *CB = BE->getBlock(); reachable.insert(CB->getBlockID()); } } // Bail out if we didn't get the CFG or the ParentMap. if (!C || !PM) return; ASTContext &Ctx = B.getContext(); // Find CFGBlocks that were not covered by any node for (CFG::const_iterator I = C->begin(), E = C->end(); I != E; ++I) { const CFGBlock *CB = *I; // Check if the block is unreachable if (reachable.count(CB->getBlockID())) continue; // Check if the block is empty (an artificial block) if (isEmptyCFGBlock(CB)) continue; // Find the entry points for this block if (!visited.count(CB->getBlockID())) FindUnreachableEntryPoints(CB, reachable, visited); // This block may have been pruned; check if we still want to report it if (reachable.count(CB->getBlockID())) continue; // Check for false positives if (CB->size() > 0 && isInvalidPath(CB, *PM)) continue; // Special case for __builtin_unreachable. // FIXME: This should be extended to include other unreachable markers, // such as llvm_unreachable. if (!CB->empty()) { CFGElement First = CB->front(); if (const CFGStmt *S = First.getAs<CFGStmt>()) { if (const CallExpr *CE = dyn_cast<CallExpr>(S->getStmt())) { if (CE->isBuiltinCall(Ctx) == Builtin::BI__builtin_unreachable) continue; } } } // We found a block that wasn't covered - find the statement to report SourceRange SR; SourceLocation SL; if (const Stmt *S = getUnreachableStmt(CB)) { SR = S->getSourceRange(); SL = S->getLocStart(); if (SR.isInvalid() || SL.isInvalid()) continue; } else continue; // Check if the SourceLocation is in a system header const SourceManager &SM = B.getSourceManager(); if (SM.isInSystemHeader(SL) || SM.isInExternCSystemHeader(SL)) continue; B.EmitBasicReport("Unreachable code", "Dead code", "This statement is never" " executed", SL, SR); } }
static void checkObjCDealloc(const ObjCImplementationDecl *D, const LangOptions& LOpts, BugReporter& BR) { assert (LOpts.getGC() != LangOptions::GCOnly); ASTContext &Ctx = BR.getContext(); const ObjCInterfaceDecl *ID = D->getClassInterface(); // Does the class contain any ivars that are pointers (or id<...>)? // If not, skip the check entirely. // NOTE: This is motivated by PR 2517: // http://llvm.org/bugs/show_bug.cgi?id=2517 bool containsPointerIvar = false; for (ObjCInterfaceDecl::ivar_iterator I=ID->ivar_begin(), E=ID->ivar_end(); I!=E; ++I) { ObjCIvarDecl *ID = *I; QualType T = ID->getType(); if (!T->isObjCObjectPointerType() || ID->getAttr<IBOutletAttr>() || // Skip IBOutlets. ID->getAttr<IBOutletCollectionAttr>()) // Skip IBOutletCollections. continue; containsPointerIvar = true; break; } if (!containsPointerIvar) return; // Determine if the class subclasses NSObject. IdentifierInfo* NSObjectII = &Ctx.Idents.get("NSObject"); IdentifierInfo* SenTestCaseII = &Ctx.Idents.get("SenTestCase"); for ( ; ID ; ID = ID->getSuperClass()) { IdentifierInfo *II = ID->getIdentifier(); if (II == NSObjectII) break; // FIXME: For now, ignore classes that subclass SenTestCase, as these don't // need to implement -dealloc. They implement tear down in another way, // which we should try and catch later. // http://llvm.org/bugs/show_bug.cgi?id=3187 if (II == SenTestCaseII) return; } if (!ID) return; // Get the "dealloc" selector. IdentifierInfo* II = &Ctx.Idents.get("dealloc"); Selector S = Ctx.Selectors.getSelector(0, &II); ObjCMethodDecl *MD = 0; // Scan the instance methods for "dealloc". for (ObjCImplementationDecl::instmeth_iterator I = D->instmeth_begin(), E = D->instmeth_end(); I!=E; ++I) { if ((*I)->getSelector() == S) { MD = *I; break; } } PathDiagnosticLocation DLoc = PathDiagnosticLocation::createBegin(D, BR.getSourceManager()); if (!MD) { // No dealloc found. const char* name = LOpts.getGC() == LangOptions::NonGC ? "missing -dealloc" : "missing -dealloc (Hybrid MM, non-GC)"; std::string buf; llvm::raw_string_ostream os(buf); os << "Objective-C class '" << *D << "' lacks a 'dealloc' instance method"; BR.EmitBasicReport(D, name, categories::CoreFoundationObjectiveC, os.str(), DLoc); return; } // dealloc found. Scan for missing [super dealloc]. if (MD->getBody() && !scan_dealloc(MD->getBody(), S)) { const char* name = LOpts.getGC() == LangOptions::NonGC ? "missing [super dealloc]" : "missing [super dealloc] (Hybrid MM, non-GC)"; std::string buf; llvm::raw_string_ostream os(buf); os << "The 'dealloc' instance method in Objective-C class '" << *D << "' does not send a 'dealloc' message to its super class" " (missing [super dealloc])"; BR.EmitBasicReport(MD, name, categories::CoreFoundationObjectiveC, os.str(), DLoc); return; } // Get the "release" selector. IdentifierInfo* RII = &Ctx.Idents.get("release"); Selector RS = Ctx.Selectors.getSelector(0, &RII); // Get the "self" identifier IdentifierInfo* SelfII = &Ctx.Idents.get("self"); // Scan for missing and extra releases of ivars used by implementations // of synthesized properties for (ObjCImplementationDecl::propimpl_iterator I = D->propimpl_begin(), E = D->propimpl_end(); I!=E; ++I) { // We can only check the synthesized properties if (I->getPropertyImplementation() != ObjCPropertyImplDecl::Synthesize) continue; ObjCIvarDecl *ID = I->getPropertyIvarDecl(); if (!ID) continue; QualType T = ID->getType(); if (!T->isObjCObjectPointerType()) // Skip non-pointer ivars continue; const ObjCPropertyDecl *PD = I->getPropertyDecl(); if (!PD) continue; // ivars cannot be set via read-only properties, so we'll skip them if (PD->isReadOnly()) continue; // ivar must be released if and only if the kind of setter was not 'assign' bool requiresRelease = PD->getSetterKind() != ObjCPropertyDecl::Assign; if (scan_ivar_release(MD->getBody(), ID, PD, RS, SelfII, Ctx) != requiresRelease) { const char *name = 0; std::string buf; llvm::raw_string_ostream os(buf); if (requiresRelease) { name = LOpts.getGC() == LangOptions::NonGC ? "missing ivar release (leak)" : "missing ivar release (Hybrid MM, non-GC)"; os << "The '" << *ID << "' instance variable was retained by a synthesized property but " "wasn't released in 'dealloc'"; } else { name = LOpts.getGC() == LangOptions::NonGC ? "extra ivar release (use-after-release)" : "extra ivar release (Hybrid MM, non-GC)"; os << "The '" << *ID << "' instance variable was not retained by a synthesized property " "but was released in 'dealloc'"; } PathDiagnosticLocation SDLoc = PathDiagnosticLocation::createBegin(*I, BR.getSourceManager()); BR.EmitBasicReport(MD, name, categories::CoreFoundationObjectiveC, os.str(), SDLoc); } } }
void ObjCSuperCallChecker::checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager &Mgr, BugReporter &BR) const { ASTContext &Ctx = BR.getContext(); if (!isUIViewControllerSubclass(Ctx, D)) return; const char *SelectorNames[] = {"addChildViewController", "viewDidAppear", "viewDidDisappear", "viewWillAppear", "viewWillDisappear", "removeFromParentViewController", "didReceiveMemoryWarning", "viewDidUnload", "viewWillUnload", "viewDidLoad"}; const unsigned SelectorArgumentCounts[] = {1, 1, 1, 1, 1, 0, 0, 0, 0, 0}; const size_t SelectorCount = llvm::array_lengthof(SelectorNames); assert(llvm::array_lengthof(SelectorArgumentCounts) == SelectorCount); // Fill the Selectors SmallSet with all selectors we want to check. llvm::SmallSet<Selector, 16> Selectors; for (size_t i = 0; i < SelectorCount; i++) { unsigned ArgumentCount = SelectorArgumentCounts[i]; const char *SelectorCString = SelectorNames[i]; // Get the selector. IdentifierInfo *II = &Ctx.Idents.get(SelectorCString); Selectors.insert(Ctx.Selectors.getSelector(ArgumentCount, &II)); } // Iterate over all instance methods. for (ObjCImplementationDecl::instmeth_iterator I = D->instmeth_begin(), E = D->instmeth_end(); I != E; ++I) { Selector S = (*I)->getSelector(); // Find out whether this is a selector that we want to check. if (!Selectors.count(S)) continue; ObjCMethodDecl *MD = *I; // Check if the method calls its superclass implementation. if (MD->getBody()) { FindSuperCallVisitor Visitor(S); Visitor.TraverseDecl(MD); // It doesn't call super, emit a diagnostic. if (!Visitor.DoesCallSuper) { PathDiagnosticLocation DLoc = PathDiagnosticLocation::createEnd(MD->getBody(), BR.getSourceManager(), Mgr.getAnalysisDeclContext(D)); const char *Name = "Missing call to superclass"; SmallString<256> Buf; llvm::raw_svector_ostream os(Buf); os << "The '" << S.getAsString() << "' instance method in UIViewController subclass '" << *D << "' is missing a [super " << S.getAsString() << "] call"; BR.EmitBasicReport(MD, Name, categories::CoreFoundationObjectiveC, os.str(), DLoc); } } } }
void CloneChecker::reportSuspiciousClones( BugReporter &BR, AnalysisManager &Mgr, std::vector<CloneDetector::CloneGroup> &CloneGroups) const { std::vector<VariablePattern::SuspiciousClonePair> Pairs; for (const CloneDetector::CloneGroup &Group : CloneGroups) { for (unsigned i = 0; i < Group.size(); ++i) { VariablePattern PatternA(Group[i]); for (unsigned j = i + 1; j < Group.size(); ++j) { VariablePattern PatternB(Group[j]); VariablePattern::SuspiciousClonePair ClonePair; // For now, we only report clones which break the variable pattern just // once because multiple differences in a pattern are an indicator that // those differences are maybe intended (e.g. because it's actually a // different algorithm). // FIXME: In very big clones even multiple variables can be unintended, // so replacing this number with a percentage could better handle such // cases. On the other hand it could increase the false-positive rate // for all clones if the percentage is too high. if (PatternA.countPatternDifferences(PatternB, &ClonePair) == 1) { Pairs.push_back(ClonePair); break; } } } } if (!BT_Suspicious) BT_Suspicious.reset( new BugType(this, "Suspicious code clone", "Code clone")); ASTContext &ACtx = BR.getContext(); SourceManager &SM = ACtx.getSourceManager(); AnalysisDeclContext *ADC = Mgr.getAnalysisDeclContext(ACtx.getTranslationUnitDecl()); for (VariablePattern::SuspiciousClonePair &Pair : Pairs) { // FIXME: We are ignoring the suggestions currently, because they are // only 50% accurate (even if the second suggestion is unavailable), // which may confuse the user. // Think how to perform more accurate suggestions? auto R = llvm::make_unique<BugReport>( *BT_Suspicious, "Potential copy-paste error; did you really mean to use '" + Pair.FirstCloneInfo.Variable->getNameAsString() + "' here?", PathDiagnosticLocation::createBegin(Pair.FirstCloneInfo.Mention, SM, ADC)); R->addRange(Pair.FirstCloneInfo.Mention->getSourceRange()); R->addNote("Similar code using '" + Pair.SecondCloneInfo.Variable->getNameAsString() + "' here", PathDiagnosticLocation::createBegin(Pair.SecondCloneInfo.Mention, SM, ADC), Pair.SecondCloneInfo.Mention->getSourceRange()); BR.emitReport(std::move(R)); } }
void iOSAppSecAbusingURLSchemesChecker::checkASTDecl(const ObjCMethodDecl *pMD, AnalysisManager &rMgr, BugReporter &rBR) const { MSEC_DEBUG_FUNC( "redwud: ", "ENTER (Method)" ) ; do { if ( !pMD ) { break ; } Selector S = pMD ->getSelector() ; //There is no point of checking if you can't report it if ( !m_pInsecureInstBugType ) { MSEC_DEBUG("redwud: ","Reporting will fail!" ) ; break ; } // application: if ( S.getIdentifierInfoForSlot( 0 ) != m_piiApplication ) { //MSEC_DEBUG("redwud: ","!application: " << S.getAsString() ) ; break ; } unsigned iArgs = S.getNumArgs() ; ASTContext &Ctx = rBR.getContext() ; // //openURL: // do { if ( iArgs != 4 ) { break ; } if ( (S.getIdentifierInfoForSlot(1) != m_piiOpenURL) || (S.getIdentifierInfoForSlot(2) != m_piiSourceApplication) || (S.getIdentifierInfoForSlot(3) != m_piiAnnotation ) ) { break ; } if ( !checkOpenURL( pMD, Ctx ) ) { break ; } CMSecCommon::reportInsecureInstance( *m_pInsecureInstBugType , m_szReportDesc , rBR, pMD ) ; } while(_PASSING_) ; // //handleOpenURL: // do { if ( iArgs != 2 ) { break ; } if ( S.getIdentifierInfoForSlot(1) != m_piiHandleOpenURL ) { break ; } //Note: Intentionally made it common for both if ( !checkOpenURL( pMD, Ctx ) ) { break ; } CMSecCommon::reportInsecureInstance( *m_pInsecureInstBugType , m_szReportDesc, rBR, pMD ) ; } while(_PASSING_) ; //handleOpenURL: } while(_PASSING_) ; MSEC_DEBUG_FUNC( "redwud: ", "EXIT (Method)" ) ; }
static void checkObjCDealloc(const CheckerBase *Checker, const ObjCImplementationDecl *D, const LangOptions &LOpts, BugReporter &BR) { assert(LOpts.getGC() != LangOptions::GCOnly); assert(!LOpts.ObjCAutoRefCount); ASTContext &Ctx = BR.getContext(); const ObjCInterfaceDecl *ID = D->getClassInterface(); // Does the class contain any synthesized properties that are retainable? // If not, skip the check entirely. bool containsRetainedSynthesizedProperty = false; for (const auto *I : D->property_impls()) { const ObjCIvarDecl *ID = nullptr; const ObjCPropertyDecl *PD = nullptr; if (!isSynthesizedRetainableProperty(I, &ID, &PD)) continue; if (synthesizedPropertyRequiresRelease(PD)) { containsRetainedSynthesizedProperty = true; break; } } if (!containsRetainedSynthesizedProperty) return; // Determine if the class subclasses NSObject. IdentifierInfo* NSObjectII = &Ctx.Idents.get("NSObject"); IdentifierInfo* SenTestCaseII = &Ctx.Idents.get("SenTestCase"); for ( ; ID ; ID = ID->getSuperClass()) { IdentifierInfo *II = ID->getIdentifier(); if (II == NSObjectII) break; // FIXME: For now, ignore classes that subclass SenTestCase, as these don't // need to implement -dealloc. They implement tear down in another way, // which we should try and catch later. // http://llvm.org/bugs/show_bug.cgi?id=3187 if (II == SenTestCaseII) return; } if (!ID) return; // Get the "dealloc" selector. IdentifierInfo* II = &Ctx.Idents.get("dealloc"); Selector S = Ctx.Selectors.getSelector(0, &II); const ObjCMethodDecl *MD = nullptr; // Scan the instance methods for "dealloc". for (const auto *I : D->instance_methods()) { if (I->getSelector() == S) { MD = I; break; } } if (!MD) { // No dealloc found. const char* name = LOpts.getGC() == LangOptions::NonGC ? "missing -dealloc" : "missing -dealloc (Hybrid MM, non-GC)"; std::string buf; llvm::raw_string_ostream os(buf); os << "Objective-C class '" << *D << "' lacks a 'dealloc' instance method"; PathDiagnosticLocation DLoc = PathDiagnosticLocation::createBegin(D, BR.getSourceManager()); BR.EmitBasicReport(D, Checker, name, categories::CoreFoundationObjectiveC, os.str(), DLoc); return; } // Get the "release" selector. IdentifierInfo* RII = &Ctx.Idents.get("release"); Selector RS = Ctx.Selectors.getSelector(0, &RII); // Get the "self" identifier IdentifierInfo* SelfII = &Ctx.Idents.get("self"); // Scan for missing and extra releases of ivars used by implementations // of synthesized properties for (const auto *I : D->property_impls()) { const ObjCIvarDecl *ID = nullptr; const ObjCPropertyDecl *PD = nullptr; if (!isSynthesizedRetainableProperty(I, &ID, &PD)) continue; bool requiresRelease = synthesizedPropertyRequiresRelease(PD); if (scan_ivar_release(MD->getBody(), ID, PD, RS, SelfII, Ctx) != requiresRelease) { const char *name = nullptr; std::string buf; llvm::raw_string_ostream os(buf); if (requiresRelease) { name = LOpts.getGC() == LangOptions::NonGC ? "missing ivar release (leak)" : "missing ivar release (Hybrid MM, non-GC)"; os << "The '" << *ID << "' instance variable in '" << *D << "' was retained by a synthesized property " "but was not released in 'dealloc'"; } else { // It is common for the ivars for read-only assign properties to // always be stored retained, so don't warn for a release in // dealloc for the ivar backing these properties. if (PD->isReadOnly()) continue; name = LOpts.getGC() == LangOptions::NonGC ? "extra ivar release (use-after-release)" : "extra ivar release (Hybrid MM, non-GC)"; os << "The '" << *ID << "' instance variable in '" << *D << "' was not retained by a synthesized property " "but was released in 'dealloc'"; } // If @synthesize statement is missing, fall back to @property statement. const Decl *SPDecl = I->getLocation().isValid() ? static_cast<const Decl *>(I) : static_cast<const Decl *>(PD); PathDiagnosticLocation SPLoc = PathDiagnosticLocation::createBegin(SPDecl, BR.getSourceManager()); BR.EmitBasicReport(MD, Checker, name, categories::CoreFoundationObjectiveC, os.str(), SPLoc); } } }