diff options
author | Adam Balogh <adam.balogh@ericsson.com> | 2018-07-13 13:09:40 +0000 |
---|---|---|
committer | Adam Balogh <adam.balogh@ericsson.com> | 2018-07-13 13:09:40 +0000 |
commit | e0e5b4cf2e8e2a878fb3d75c32bfda8905431de6 (patch) | |
tree | f0e488e7affa04fc93fcc0b18df91162265d5294 /clang-tools-extra/clang-tidy | |
parent | d47bde0d7a8bff7ef1526cc13b05b1d633550e32 (diff) | |
download | bcm5719-llvm-e0e5b4cf2e8e2a878fb3d75c32bfda8905431de6.tar.gz bcm5719-llvm-e0e5b4cf2e8e2a878fb3d75c32bfda8905431de6.zip |
[clang-tidy] Exception Escape Checker
Finds functions which may throw an exception directly or indirectly, but they
should not: Destructors, move constructors, move assignment operators, the
main() function, swap() functions, functions marked with throw() or noexcept
and functions given as option to the checker.
Differential Revision: https://reviews.llvm.org/D33537
llvm-svn: 336997
Diffstat (limited to 'clang-tools-extra/clang-tidy')
4 files changed, 265 insertions, 0 deletions
diff --git a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp index 86eb9d1ba5e..09a252b30f1 100644 --- a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp @@ -16,6 +16,7 @@ #include "BoolPointerImplicitConversionCheck.h" #include "CopyConstructorInitCheck.h" #include "DanglingHandleCheck.h" +#include "ExceptionEscapeCheck.h" #include "FoldInitTypeCheck.h" #include "ForwardDeclarationNamespaceCheck.h" #include "ForwardingReferenceOverloadCheck.h" @@ -67,6 +68,8 @@ public: "bugprone-copy-constructor-init"); CheckFactories.registerCheck<DanglingHandleCheck>( "bugprone-dangling-handle"); + CheckFactories.registerCheck<ExceptionEscapeCheck>( + "bugprone-exception-escape"); CheckFactories.registerCheck<FoldInitTypeCheck>( "bugprone-fold-init-type"); CheckFactories.registerCheck<ForwardDeclarationNamespaceCheck>( diff --git a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt index 3cce01312ec..b20997cc40b 100644 --- a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt @@ -7,6 +7,7 @@ add_clang_library(clangTidyBugproneModule BugproneTidyModule.cpp CopyConstructorInitCheck.cpp DanglingHandleCheck.cpp + ExceptionEscapeCheck.cpp FoldInitTypeCheck.cpp ForwardDeclarationNamespaceCheck.cpp ForwardingReferenceOverloadCheck.cpp diff --git a/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp new file mode 100644 index 00000000000..a5e54c0a182 --- /dev/null +++ b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp @@ -0,0 +1,214 @@ +//===--- ExceptionEscapeCheck.cpp - clang-tidy-----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ExceptionEscapeCheck.h" + +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +#include "llvm/ADT/SmallSet.h" +#include "llvm/ADT/StringSet.h" + +using namespace clang::ast_matchers; + +namespace { +typedef llvm::SmallVector<const clang::Type *, 8> TypeVec; +} // namespace + +namespace clang { + +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; }); +} + +static const TypeVec +throwsException(const Stmt *St, const TypeVec &Caught, + llvm::SmallSet<const FunctionDecl *, 32> &CallStack); + +static const TypeVec +throwsException(const FunctionDecl *Func, + llvm::SmallSet<const FunctionDecl *, 32> &CallStack) { + if (CallStack.count(Func)) + return TypeVec(); + + if (const Stmt *Body = Func->getBody()) { + CallStack.insert(Func); + const TypeVec Result = throwsException(Body, TypeVec(), CallStack); + CallStack.erase(Func); + return Result; + } + + TypeVec Result; + if (const auto *FPT = Func->getType()->getAs<FunctionProtoType>()) { + for (const QualType Ex : FPT->exceptions()) { + Result.push_back(Ex.getTypePtr()); + } + } + return Result; +} + +static const TypeVec +throwsException(const Stmt *St, const TypeVec &Caught, + llvm::SmallSet<const FunctionDecl *, 32> &CallStack) { + TypeVec Results; + + 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(); + } + if (const auto *TD = ThrownType->getAsTagDecl()) { + if (TD->getDeclName().isIdentifier() && TD->getName() == "bad_alloc" + && TD->isInStdNamespace()) + return Results; + } + Results.push_back(ThrownExpr->getType()->getUnqualifiedDesugaredType()); + } else { + Results.append(Caught.begin(), Caught.end()); + } + } else if (const auto *Try = dyn_cast<CXXTryStmt>(St)) { + TypeVec Uncaught = throwsException(Try->getTryBlock(), Caught, CallStack); + for (unsigned i = 0; i < Try->getNumHandlers(); ++i) { + const CXXCatchStmt *Catch = Try->getHandler(i); + if (!Catch->getExceptionDecl()) { + const TypeVec Rethrown = + throwsException(Catch->getHandlerBlock(), Uncaught, CallStack); + Results.append(Rethrown.begin(), Rethrown.end()); + Uncaught.clear(); + } else { + const auto *CaughtType = + Catch->getCaughtType()->getUnqualifiedDesugaredType(); + if (CaughtType->isReferenceType()) { + CaughtType = CaughtType->castAs<ReferenceType>() + ->getPointeeType() + ->getUnqualifiedDesugaredType(); + } + auto NewEnd = + llvm::remove_if(Uncaught, [&CaughtType](const Type *ThrownType) { + return ThrownType == CaughtType || + isBaseOf(ThrownType, CaughtType); + }); + if (NewEnd != Uncaught.end()) { + Uncaught.erase(NewEnd, Uncaught.end()); + const TypeVec Rethrown = throwsException( + Catch->getHandlerBlock(), TypeVec(1, CaughtType), CallStack); + Results.append(Rethrown.begin(), Rethrown.end()); + } + } + } + Results.append(Uncaught.begin(), Uncaught.end()); + } else if (const auto *Call = dyn_cast<CallExpr>(St)) { + if (const FunctionDecl *Func = Call->getDirectCallee()) { + TypeVec Excs = throwsException(Func, CallStack); + Results.append(Excs.begin(), Excs.end()); + } + } else { + for (const Stmt *Child : St->children()) { + TypeVec Excs = throwsException(Child, Caught, CallStack); + Results.append(Excs.begin(), Excs.end()); + } + } + return Results; +} + +static const TypeVec throwsException(const FunctionDecl *Func) { + llvm::SmallSet<const FunctionDecl *, 32> CallStack; + return throwsException(Func, CallStack); +} + +namespace ast_matchers { +AST_MATCHER_P(FunctionDecl, throws, internal::Matcher<Type>, InnerMatcher) { + TypeVec ExceptionList = throwsException(&Node); + auto NewEnd = llvm::remove_if( + ExceptionList, [this, Finder, Builder](const Type *Exception) { + return !InnerMatcher.matches(*Exception, Finder, Builder); + }); + ExceptionList.erase(NewEnd, ExceptionList.end()); + return ExceptionList.size(); +} + +AST_MATCHER_P(Type, isIgnored, llvm::StringSet<>, IgnoredExceptions) { + if (const auto *TD = Node.getAsTagDecl()) { + if (TD->getDeclName().isIdentifier()) + return IgnoredExceptions.count(TD->getName()) > 0; + } + return false; +} + +AST_MATCHER_P(FunctionDecl, isEnabled, llvm::StringSet<>, + FunctionsThatShouldNotThrow) { + return FunctionsThatShouldNotThrow.count(Node.getNameAsString()) > 0; +} +} // namespace ast_matchers + +namespace tidy { +namespace bugprone { + +ExceptionEscapeCheck::ExceptionEscapeCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), RawFunctionsThatShouldNotThrow(Options.get( + "FunctionsThatShouldNotThrow", "")), + RawIgnoredExceptions(Options.get("IgnoredExceptions", "")) { + llvm::SmallVector<StringRef, 8> FunctionsThatShouldNotThrowVec, + IgnoredExceptionsVec; + StringRef(RawFunctionsThatShouldNotThrow) + .split(FunctionsThatShouldNotThrowVec, ",", -1, false); + FunctionsThatShouldNotThrow.insert(FunctionsThatShouldNotThrowVec.begin(), + FunctionsThatShouldNotThrowVec.end()); + StringRef(RawIgnoredExceptions).split(IgnoredExceptionsVec, ",", -1, false); + IgnoredExceptions.insert(IgnoredExceptionsVec.begin(), + IgnoredExceptionsVec.end()); +} + +void ExceptionEscapeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "FunctionsThatShouldNotThrow", + RawFunctionsThatShouldNotThrow); + Options.store(Opts, "IgnoredExceptions", RawIgnoredExceptions); +} + +void ExceptionEscapeCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + functionDecl(allOf(throws(unless(isIgnored(IgnoredExceptions))), + anyOf(isNoThrow(), cxxDestructorDecl(), + cxxConstructorDecl(isMoveConstructor()), + cxxMethodDecl(isMoveAssignmentOperator()), + hasName("main"), hasName("swap"), + isEnabled(FunctionsThatShouldNotThrow)))) + .bind("thrower"), + this); +} + +void ExceptionEscapeCheck::check(const MatchFinder::MatchResult &Result) { + const FunctionDecl *MatchedDecl = + Result.Nodes.getNodeAs<FunctionDecl>("thrower"); + if (!MatchedDecl) + return; + + // FIXME: We should provide more information about the exact location where + // the exception is thrown, maybe the full path the exception escapes + diag(MatchedDecl->getLocation(), "an exception may be thrown in function %0 " + "which should not throw exceptions") << MatchedDecl; +} + +} // namespace bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.h b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.h new file mode 100644 index 00000000000..d690022aa97 --- /dev/null +++ b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.h @@ -0,0 +1,47 @@ +//===--- ExceptionEscapeCheck.h - clang-tidy---------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_EXCEPTION_ESCAPE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_EXCEPTION_ESCAPE_H + +#include "../ClangTidy.h" + +#include "llvm/ADT/StringSet.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// Finds functions which should not throw exceptions: Destructors, move +/// constructors, move assignment operators, the main() function, +/// swap() functions, functions marked with throw() or noexcept and functions +/// given as option to the checker. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone-exception-escape.html +class ExceptionEscapeCheck : public ClangTidyCheck { +public: + ExceptionEscapeCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + std::string RawFunctionsThatShouldNotThrow; + std::string RawIgnoredExceptions; + + llvm::StringSet<> FunctionsThatShouldNotThrow; + llvm::StringSet<> IgnoredExceptions; +}; + +} // namespace bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_EXCEPTION_ESCAPE_H |