summaryrefslogtreecommitdiffstats
path: root/clang-tidy/utils/ExceptionAnalyzer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'clang-tidy/utils/ExceptionAnalyzer.cpp')
-rw-r--r--clang-tidy/utils/ExceptionAnalyzer.cpp262
1 files changed, 262 insertions, 0 deletions
diff --git a/clang-tidy/utils/ExceptionAnalyzer.cpp b/clang-tidy/utils/ExceptionAnalyzer.cpp
new file mode 100644
index 00000000..09fce323
--- /dev/null
+++ b/clang-tidy/utils/ExceptionAnalyzer.cpp
@@ -0,0 +1,262 @@
+//===--- 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<const Type *, 8> 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<const Type *, 8> 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<const FunctionDecl *, 32> &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<FunctionProtoType>()) {
+ for (const QualType Ex : FPT->exceptions())
+ Result.registerException(Ex.getTypePtr());
+ }
+ return Result;
+}
+
+/// Analyzes a single statment 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<const FunctionDecl *, 32> &CallStack) {
+ auto Results = ExceptionInfo::createNonThrowing();
+ if (!St)
+ return Results;
+
+ if (const auto *Throw = dyn_cast<CXXThrowExpr>(St)) {
+ if (const auto *ThrownExpr = Throw->getSubExpr()) {
+ const auto *ThrownType =
+ ThrownExpr->getType()->getUnqualifiedDesugaredType();
+ if (ThrownType->isReferenceType())
+ ThrownType = ThrownType->castAs<ReferenceType>()
+ ->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<CXXTryStmt>(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<ReferenceType>()
+ ->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<CallExpr>(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<const FunctionDecl *, 32> 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<const FunctionDecl *, 32> CallStack;
+ return throwsException(Stmt, ExceptionInfo::Throwables(), CallStack);
+}
+
+template <typename T>
+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