//===--- ExceptionAnalyzer.cpp - clang-tidy -------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "ExceptionAnalyzer.h" namespace clang { namespace tidy { namespace utils { void ExceptionAnalyzer::ExceptionInfo::registerException( const Type *ExceptionType) { assert(ExceptionType != nullptr && "Only valid types are accepted"); Behaviour = State::Throwing; ThrownExceptions.insert(ExceptionType); } void ExceptionAnalyzer::ExceptionInfo::registerExceptions( const Throwables &Exceptions) { if (Exceptions.size() == 0) return; Behaviour = State::Throwing; ThrownExceptions.insert(Exceptions.begin(), Exceptions.end()); } ExceptionAnalyzer::ExceptionInfo &ExceptionAnalyzer::ExceptionInfo::merge( const ExceptionAnalyzer::ExceptionInfo &Other) { // Only the following two cases require an update to the local // 'Behaviour'. If the local entity is already throwing there will be no // change and if the other entity is throwing the merged entity will throw // as well. // If one of both entities is 'Unknown' and the other one does not throw // the merged entity is 'Unknown' as well. if (Other.Behaviour == State::Throwing) Behaviour = State::Throwing; else if (Other.Behaviour == State::Unknown && Behaviour == State::NotThrowing) Behaviour = State::Unknown; ContainsUnknown = ContainsUnknown || Other.ContainsUnknown; ThrownExceptions.insert(Other.ThrownExceptions.begin(), Other.ThrownExceptions.end()); return *this; } static bool isBaseOf(const Type *DerivedType, const Type *BaseType) { const auto *DerivedClass = DerivedType->getAsCXXRecordDecl(); const auto *BaseClass = BaseType->getAsCXXRecordDecl(); if (!DerivedClass || !BaseClass) return false; return !DerivedClass->forallBases( [BaseClass](const CXXRecordDecl *Cur) { return Cur != BaseClass; }); } bool ExceptionAnalyzer::ExceptionInfo::filterByCatch(const Type *BaseClass) { llvm::SmallVector TypesToDelete; for (const Type *T : ThrownExceptions) { if (T == BaseClass || isBaseOf(T, BaseClass)) TypesToDelete.push_back(T); } for (const Type *T : TypesToDelete) ThrownExceptions.erase(T); reevaluateBehaviour(); return TypesToDelete.size() > 0; } ExceptionAnalyzer::ExceptionInfo & ExceptionAnalyzer::ExceptionInfo::filterIgnoredExceptions( const llvm::StringSet<> &IgnoredTypes, bool IgnoreBadAlloc) { llvm::SmallVector TypesToDelete; // Note: Using a 'SmallSet' with 'llvm::remove_if()' is not possible. // Therefore this slightly hacky implementation is required. for (const Type *T : ThrownExceptions) { if (const auto *TD = T->getAsTagDecl()) { if (TD->getDeclName().isIdentifier()) { if ((IgnoreBadAlloc && (TD->getName() == "bad_alloc" && TD->isInStdNamespace())) || (IgnoredTypes.count(TD->getName()) > 0)) TypesToDelete.push_back(T); } } } for (const Type *T : TypesToDelete) ThrownExceptions.erase(T); reevaluateBehaviour(); return *this; } void ExceptionAnalyzer::ExceptionInfo::clear() { Behaviour = State::NotThrowing; ContainsUnknown = false; ThrownExceptions.clear(); } void ExceptionAnalyzer::ExceptionInfo::reevaluateBehaviour() { if (ThrownExceptions.size() == 0) if (ContainsUnknown) Behaviour = State::Unknown; else Behaviour = State::NotThrowing; else Behaviour = State::Throwing; } ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException( const FunctionDecl *Func, llvm::SmallSet &CallStack) { if (CallStack.count(Func)) return ExceptionInfo::createNonThrowing(); if (const Stmt *Body = Func->getBody()) { CallStack.insert(Func); ExceptionInfo Result = throwsException(Body, ExceptionInfo::Throwables(), CallStack); CallStack.erase(Func); return Result; } auto Result = ExceptionInfo::createUnknown(); if (const auto *FPT = Func->getType()->getAs()) { for (const QualType &Ex : FPT->exceptions()) Result.registerException(Ex.getTypePtr()); } return Result; } /// Analyzes a single statement on it's throwing behaviour. This is in principle /// possible except some 'Unknown' functions are called. ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException( const Stmt *St, const ExceptionInfo::Throwables &Caught, llvm::SmallSet &CallStack) { auto Results = ExceptionInfo::createNonThrowing(); if (!St) return Results; if (const auto *Throw = dyn_cast(St)) { if (const auto *ThrownExpr = Throw->getSubExpr()) { const auto *ThrownType = ThrownExpr->getType()->getUnqualifiedDesugaredType(); if (ThrownType->isReferenceType()) ThrownType = ThrownType->castAs() ->getPointeeType() ->getUnqualifiedDesugaredType(); Results.registerException( ThrownExpr->getType()->getUnqualifiedDesugaredType()); } else // A rethrow of a caught exception happens which makes it possible // to throw all exception that are caught in the 'catch' clause of // the parent try-catch block. Results.registerExceptions(Caught); } else if (const auto *Try = dyn_cast(St)) { ExceptionInfo Uncaught = throwsException(Try->getTryBlock(), Caught, CallStack); for (unsigned I = 0; I < Try->getNumHandlers(); ++I) { const CXXCatchStmt *Catch = Try->getHandler(I); // Everything is catched through 'catch(...)'. if (!Catch->getExceptionDecl()) { ExceptionInfo Rethrown = throwsException( Catch->getHandlerBlock(), Uncaught.getExceptionTypes(), CallStack); Results.merge(Rethrown); Uncaught.clear(); } else { const auto *CaughtType = Catch->getCaughtType()->getUnqualifiedDesugaredType(); if (CaughtType->isReferenceType()) { CaughtType = CaughtType->castAs() ->getPointeeType() ->getUnqualifiedDesugaredType(); } // If the caught exception will catch multiple previously potential // thrown types (because it's sensitive to inheritance) the throwing // situation changes. First of all filter the exception types and // analyze if the baseclass-exception is rethrown. if (Uncaught.filterByCatch(CaughtType)) { ExceptionInfo::Throwables CaughtExceptions; CaughtExceptions.insert(CaughtType); ExceptionInfo Rethrown = throwsException(Catch->getHandlerBlock(), CaughtExceptions, CallStack); Results.merge(Rethrown); } } } Results.merge(Uncaught); } else if (const auto *Call = dyn_cast(St)) { if (const FunctionDecl *Func = Call->getDirectCallee()) { ExceptionInfo Excs = throwsException(Func, CallStack); Results.merge(Excs); } } else { for (const Stmt *Child : St->children()) { ExceptionInfo Excs = throwsException(Child, Caught, CallStack); Results.merge(Excs); } } return Results; } ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::analyzeImpl(const FunctionDecl *Func) { ExceptionInfo ExceptionList; // Check if the function has already been analyzed and reuse that result. if (FunctionCache.count(Func) == 0) { llvm::SmallSet CallStack; ExceptionList = throwsException(Func, CallStack); // Cache the result of the analysis. This is done prior to filtering // because it is best to keep as much information as possible. // The results here might be relevant to different analysis passes // with different needs as well. FunctionCache.insert(std::make_pair(Func, ExceptionList)); } else ExceptionList = FunctionCache[Func]; return ExceptionList; } ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::analyzeImpl(const Stmt *Stmt) { llvm::SmallSet CallStack; return throwsException(Stmt, ExceptionInfo::Throwables(), CallStack); } template ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::analyzeDispatch(const T *Node) { ExceptionInfo ExceptionList = analyzeImpl(Node); if (ExceptionList.getBehaviour() == State::NotThrowing || ExceptionList.getBehaviour() == State::Unknown) return ExceptionList; // Remove all ignored exceptions from the list of exceptions that can be // thrown. ExceptionList.filterIgnoredExceptions(IgnoredExceptions, IgnoreBadAlloc); return ExceptionList; } ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::analyze(const FunctionDecl *Func) { return analyzeDispatch(Func); } ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::analyze(const Stmt *Stmt) { return analyzeDispatch(Stmt); } } // namespace utils } // namespace tidy } // namespace clang