Index: clang-tidy/bugprone/BugproneTidyModule.cpp =================================================================== --- clang-tidy/bugprone/BugproneTidyModule.cpp +++ clang-tidy/bugprone/BugproneTidyModule.cpp @@ -15,6 +15,7 @@ #include "BoolPointerImplicitConversionCheck.h" #include "CopyConstructorInitCheck.h" #include "DanglingHandleCheck.h" +#include "ExceptionEscapeCheck.h" #include "FoldInitTypeCheck.h" #include "ForwardDeclarationNamespaceCheck.h" #include "ForwardingReferenceOverloadCheck.h" @@ -64,6 +65,8 @@ "bugprone-copy-constructor-init"); CheckFactories.registerCheck( "bugprone-dangling-handle"); + CheckFactories.registerCheck( + "bugprone-exception-escape"); CheckFactories.registerCheck( "bugprone-fold-init-type"); CheckFactories.registerCheck( Index: clang-tidy/bugprone/CMakeLists.txt =================================================================== --- clang-tidy/bugprone/CMakeLists.txt +++ clang-tidy/bugprone/CMakeLists.txt @@ -7,6 +7,7 @@ BugproneTidyModule.cpp CopyConstructorInitCheck.cpp DanglingHandleCheck.cpp + ExceptionEscapeCheck.cpp FoldInitTypeCheck.cpp ForwardDeclarationNamespaceCheck.cpp ForwardingReferenceOverloadCheck.cpp Index: clang-tidy/bugprone/ExceptionEscapeCheck.h =================================================================== --- /dev/null +++ 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/SmallSet.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 RawEnabledFunctions; + std::string RawIgnoredExceptions; + + llvm::SmallSet EnabledFunctions; + llvm::SmallSet IgnoredExceptions; +}; + +} // namespace bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_EXCEPTION_ESCAPE_H Index: clang-tidy/bugprone/ExceptionEscapeCheck.cpp =================================================================== --- /dev/null +++ clang-tidy/bugprone/ExceptionEscapeCheck.cpp @@ -0,0 +1,205 @@ +//===--- 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" + +using namespace clang; +using namespace clang::ast_matchers; + +namespace { +typedef llvm::SmallVector TypeVec; +typedef llvm::SmallSet StringSet; + +const TypeVec throwsException(const FunctionDecl *Func); +const TypeVec throwsException(const Stmt *St, const TypeVec &Caught); +} // namespace + +namespace clang { +namespace ast_matchers { +AST_MATCHER_P(FunctionDecl, throws, internal::Matcher, InnerMatcher) { + auto ExceptionList = throwsException(&Node); + auto *NewEnd = std::remove_if(ExceptionList.begin(), ExceptionList.end(), + [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, 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, StringSet, EnabledFunctions) { + return EnabledFunctions.count(Node.getNameAsString()) > 0; +} +} // namespace ast_matchers + +namespace tidy { +namespace bugprone { + +ExceptionEscapeCheck::ExceptionEscapeCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + RawEnabledFunctions(Options.get("EnabledFunctions", "")), + RawIgnoredExceptions(Options.get("IgnoredExceptions", "")) { + llvm::SmallVector EnabledFunctionsVec, IgnoredExceptionsVec; + StringRef(RawEnabledFunctions).split(EnabledFunctionsVec, ",", -1, false); + EnabledFunctions.insert(EnabledFunctionsVec.begin(), + EnabledFunctionsVec.end()); + StringRef(RawIgnoredExceptions).split(IgnoredExceptionsVec, ",", -1, false); + IgnoredExceptions.insert(IgnoredExceptionsVec.begin(), + IgnoredExceptionsVec.end()); +} + +void ExceptionEscapeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "EnabledFunctions", RawEnabledFunctions); + 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(EnabledFunctions)))) + .bind("thrower"), + this); +} + +void ExceptionEscapeCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MatchedDecl = Result.Nodes.getNodeAs("thrower"); + if (!MatchedDecl) + return; + + diag(MatchedDecl->getLocation(), "function %0 throws") << MatchedDecl; +} + +} // namespace bugprone +} // namespace tidy +} // namespace clang + +namespace { + +bool isBaseOf(const Type *DerivedType, const Type *BaseType); + +const TypeVec throwsException(const FunctionDecl *Func) { + static thread_local llvm::SmallSet CallStack; + + if (CallStack.count(Func)) + return TypeVec(); + + if (const auto *Body = Func->getBody()) { + CallStack.insert(Func); + const auto Result = throwsException(Body, TypeVec()); + CallStack.erase(Func); + return Result; + } + + TypeVec Result; + if (const auto *FPT = Func->getType()->getAs()) { + for (const auto Ex : FPT->exceptions()) { + Result.push_back(&*Ex); + } + } + return Result; +} + +const TypeVec throwsException(const Stmt *St, const TypeVec &Caught) { + TypeVec Results; + + 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(); + } + if (const auto *TD = ThrownType->getAsTagDecl()) { + if (TD->getDeclName().isIdentifier() && TD->getName() == "bad_alloc") + return Results; + } + Results.push_back(ThrownExpr->getType()->getUnqualifiedDesugaredType()); + } else { + Results.append(Caught.begin(), Caught.end()); + } + } else if (const auto *Try = dyn_cast(St)) { + auto Uncaught = throwsException(Try->getTryBlock(), Caught); + for (unsigned i = 0; i < Try->getNumHandlers(); ++i) { + const auto *Catch = Try->getHandler(i); + if (!Catch->getExceptionDecl()) { + const auto Rethrown = throwsException(Catch->getHandlerBlock(), + Uncaught); + Results.append(Rethrown.begin(), Rethrown.end()); + Uncaught.clear(); + } else { + const auto *CaughtType = + Catch->getCaughtType()->getUnqualifiedDesugaredType(); + if (CaughtType->isReferenceType()) { + CaughtType = CaughtType->castAs() + ->getPointeeType() + ->getUnqualifiedDesugaredType(); + } + auto *NewEnd = std::remove_if(Uncaught.begin(), Uncaught.end(), + [&CaughtType](const Type *ThrownType) { + return ThrownType == CaughtType || + isBaseOf(ThrownType, CaughtType); + }); + if (NewEnd != Uncaught.end()) { + const auto Rethrown = + throwsException(Catch->getHandlerBlock(), + TypeVec(1, CaughtType)); + Results.append(Rethrown.begin(), Rethrown.end()); + Uncaught.erase(NewEnd, Uncaught.end()); + } + } + } + Results.append(Uncaught.begin(), Uncaught.end()); + } else if (const auto *Call = dyn_cast(St)) { + if (const auto *Func = Call->getDirectCallee()) { + auto Excs = throwsException(Func); + Results.append(Excs.begin(), Excs.end()); + } + } else { + for (const auto *Child : St->children()) { + auto Excs = throwsException(Child, Caught); + Results.append(Excs.begin(), Excs.end()); + } + } + return Results; +} + +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; }); +} + +} // namespace Index: docs/ReleaseNotes.rst =================================================================== --- docs/ReleaseNotes.rst +++ docs/ReleaseNotes.rst @@ -64,6 +64,11 @@ - New module ``zircon`` for checks related to Fuchsia's Zircon kernel. +- New :doc:`bugprone-exception-escape + ` check + + Warns on exceptions escaping from functions which should not throw. + - New :doc:`bugprone-throw-keyword-missing ` check @@ -188,6 +193,7 @@ - The 'misc-unused-raii' check was renamed to :doc:`bugprone-unused-raii ` + Improvements to include-fixer ----------------------------- Index: docs/clang-tidy/checks/bugprone-exception-escape.rst =================================================================== --- /dev/null +++ docs/clang-tidy/checks/bugprone-exception-escape.rst @@ -0,0 +1,35 @@ +.. title:: clang-tidy - bugprone-exception-escape + +bugprone-exception-escape +========================= + +Finds functions which should not throw exceptions: +* Destructors +* Copy constructors +* Copy assignment operators +* The ``main()`` functions +* ``swap()`` functions +* Functions marked with ``throw()`` or ``noexcept`` +* Other functions given as option + +A destructor throwing an exception may result in undefined behavior, resource +leaks or unexpected termination of the program. Throwing move constructor or +move assignment also may result in undefined behavior or resource leak. The +``swap()`` operations expected to be non throwing most of the cases and they +are always possible to implement in a non throwing way. Non throwing ``swap()`` +operations are also used to create move operations. A throwing ``main()`` +function also results in unexpected termination. + +Options +------- + +.. option:: EnabledFunctions + + Comma separated list containing function names which should not throw. An + example for using this parameter is the function ``WinMain()`` in the + Windows API. Default vale is empty string. + +.. option:: IgnoredExceptions + + Comma separated list containing type names which are not counted as thrown + exceptions in the check. Default value is empty string. Index: docs/clang-tidy/checks/list.rst =================================================================== --- docs/clang-tidy/checks/list.rst +++ docs/clang-tidy/checks/list.rst @@ -23,6 +23,7 @@ bugprone-bool-pointer-implicit-conversion bugprone-copy-constructor-init bugprone-dangling-handle + bugprone-exception-escape bugprone-fold-init-type bugprone-forward-declaration-namespace bugprone-forwarding-reference-overload Index: test/clang-tidy/bugprone-exception-escape.cpp =================================================================== --- /dev/null +++ test/clang-tidy/bugprone-exception-escape.cpp @@ -0,0 +1,256 @@ +// RUN: %check_clang_tidy %s bugprone-exception-escape %t -- -extra-arg=-std=c++11 -config="{CheckOptions: [{key: bugprone-exception-escape.IgnoredExceptions, value: 'ignored1,ignored2'}, {key: bugprone-exception-escape.EnabledFunctions, value: 'enabled1,enabled2,enabled3'}]}" -- + +struct throwing_destructor { + ~throwing_destructor() { + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function '~throwing_destructor' throws + throw 1; + } +}; + +struct throwing_move_constructor { + throwing_move_constructor(throwing_move_constructor&&) { + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function 'throwing_move_constructor' throws + throw 1; + } +}; + +struct throwing_move_assignment { + throwing_move_assignment& operator=(throwing_move_assignment&&) { + // CHECK-MESSAGES: :[[@LINE-1]]:29: warning: function 'operator=' throws + throw 1; + } +}; + +void throwing_noexcept() noexcept { + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'throwing_noexcept' throws + throw 1; +} + +void throwing_throw_nothing() throw() { + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'throwing_throw_nothing' throws + throw 1; +} + +void throw_and_catch() noexcept { + // CHECK-MESSAGES-NOT: :[[@LINE-1]]:6: warning: function 'throw_and_catch' throws + try { + throw 1; + } catch(int &) { + } +} + +void throw_and_catch_some(int n) noexcept { + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'throw_and_catch_some' throws + try { + if (n) throw 1; + throw 1.1; + } catch(int &) { + } +} + +void throw_and_catch_each(int n) noexcept { + // CHECK-MESSAGES-NOT: :[[@LINE-1]]:6: warning: function 'throw_and_catch_each' throws + try { + if (n) throw 1; + throw 1.1; + } catch(int &) { + } catch(double &) { + } +} + +void throw_and_catch_all(int n) noexcept { + // CHECK-MESSAGES-NOT: :[[@LINE-1]]:6: warning: function 'throw_and_catch_all' throws + try { + if (n) throw 1; + throw 1.1; + } catch(...) { + } +} + +void throw_and_rethrow() noexcept { + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'throw_and_rethrow' throws + try { + throw 1; + } catch(int &) { + throw; + } +} + +void throw_catch_throw() noexcept { + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'throw_catch_throw' throws + try { + throw 1; + } catch(int &) { + throw 2; + } +} + +void throw_catch_rethrow_the_rest(int n) noexcept { + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'throw_catch_rethrow_the_rest' throws + try { + if (n) throw 1; + throw 1.1; + } catch(int &) { + } catch(...) { + throw; + } +} + +class base {}; +class derived: public base {}; + +void throw_derived_catch_base() noexcept { + // CHECK-MESSAGES-NOT: :[[@LINE-1]]:6: warning: function 'throw_derived_catch_base' throws + try { + throw derived(); + } catch(base &) { + } +} + +void try_nested_try(int n) noexcept { + // CHECK-MESSAGES-NOT: :[[@LINE-1]]:6: warning: function 'try_nested_try' throws + try { + try { + if (n) throw 1; + throw 1.1; + } catch(int &) { + } + } catch(double &) { + } +} + +void bad_try_nested_try(int n) noexcept { + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'bad_try_nested_try' throws + try { + if (n) throw 1; + try { + throw 1.1; + } catch(int &) { + } + } catch(double &) { + } +} + +void try_nested_catch() noexcept { + // CHECK-MESSAGES-NOT: :[[@LINE-1]]:6: warning: function 'try_nested_catch' throws + try { + try { + throw 1; + } catch(int &) { + throw 1.1; + } + } catch(double &) { + } +} + +void catch_nested_try() noexcept { + // CHECK-MESSAGES-NOT: :[[@LINE-1]]:6: warning: function 'catch_nested_try' throws + try { + throw 1; + } catch(int &) { + try { + throw 1; + } catch(int &) { + } + } +} + +void bad_catch_nested_try() noexcept { + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'bad_catch_nested_try' throws + try { + throw 1; + } catch(int &) { + try { + throw 1.1; + } catch(int &) { + } + } catch(double &) { + } +} + +void implicit_int_thrower() { + throw 1; +} + +void explicit_int_thrower() throw(int); + +void indirect_implicit() noexcept { + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'indirect_implicit' throws + implicit_int_thrower(); +} + +void indirect_explicit() noexcept { + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'indirect_explicit' throws + explicit_int_thrower(); +} + +void indirect_catch() noexcept { + // CHECK-MESSAGES-NOT: :[[@LINE-1]]:6: warning: function 'indirect_catch' throws + try { + implicit_int_thrower(); + } catch(int&) { + } +} + +void swap(int&, int&) { + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'swap' throws + throw 1; +} + +class bad_alloc {}; + +void alloc() { + throw bad_alloc(); +} + +void allocator() noexcept { + // CHECK-MESSAGES-NOT: :[[@LINE-1]]:6: warning: function 'allocator' throws + alloc(); +} + +void enabled1() { + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'enabled1' throws + throw 1; +} + +void enabled2() { + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'enabled2' throws + enabled1(); +} + +void enabled3() { + // CHECK-MESSAGES-NOT: :[[@LINE-1]]:6: warning: function 'enabled3' throws + try { + enabled1(); + } catch(...) { + } +} + +class ignored1 {}; +class ignored2 {}; + +void this_does_not_count() noexcept { + // CHECK-MESSAGES-NOT: :[[@LINE-1]]:6: warning: function 'this_does_not_count' throws + throw ignored1(); +} + +void this_does_not_count_either(int n) noexcept { + // CHECK-MESSAGES-NOT: :[[@LINE-1]]:6: warning: function 'this_does_not_count_either' throws + try { + throw 1; + if (n) throw ignored2(); + } catch(int &) { + } +} + +void this_counts(int n) noexcept { + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'this_counts' throws + if (n) throw 1; + throw ignored1(); +} + +int main() { + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: function 'main' throws + throw 1; + return 0; +}