Skip to content

Commit

Permalink
[clang-tidy] Exception Escape Checker
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Adam Balogh committed Jul 13, 2018
1 parent d47bde0 commit e0e5b4c
Show file tree
Hide file tree
Showing 8 changed files with 571 additions and 0 deletions.
3 changes: 3 additions & 0 deletions clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
Expand Up @@ -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"
Expand Down Expand Up @@ -67,6 +68,8 @@ class BugproneModule : public ClangTidyModule {
"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>(
Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
Expand Up @@ -7,6 +7,7 @@ add_clang_library(clangTidyBugproneModule
BugproneTidyModule.cpp
CopyConstructorInitCheck.cpp
DanglingHandleCheck.cpp
ExceptionEscapeCheck.cpp
FoldInitTypeCheck.cpp
ForwardDeclarationNamespaceCheck.cpp
ForwardingReferenceOverloadCheck.cpp
Expand Down
214 changes: 214 additions & 0 deletions 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
47 changes: 47 additions & 0 deletions 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
3 changes: 3 additions & 0 deletions clang-tools-extra/docs/ReleaseNotes.rst
Expand Up @@ -79,6 +79,9 @@ Improvements to clang-tidy
Diagnoses comparisons that appear to be incorrectly placed in the argument to
the ``TEMP_FAILURE_RETRY`` macro.

- New :doc:`bugprone-exception-escape
<clang-tidy/checks/bugprone-exception-escape>` check

- New :doc:`bugprone-parent-virtual-call
<clang-tidy/checks/bugprone-parent-virtual-call>` check.

Expand Down
@@ -0,0 +1,37 @@
.. title:: clang-tidy - bugprone-exception-escape

bugprone-exception-escape
=========================

Finds functions which may throw an exception directly or indirectly, but they
should not. The functions which should not throw exceptions are the following:
* Destructors
* Move constructors
* Move 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:: FunctionsThatShouldNotThrow

Comma separated list containing function names which should not throw. An
example value for this parameter can be ``WinMain`` which adds function
``WinMain()`` in the Windows API to the list of the funcions which should
not throw. Default value is an empty string.

.. option:: IgnoredExceptions

Comma separated list containing type names which are not counted as thrown
exceptions in the check. Default value is an empty string.
1 change: 1 addition & 0 deletions clang-tools-extra/docs/clang-tidy/checks/list.rst
Expand Up @@ -24,6 +24,7 @@ Clang-Tidy Checks
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
Expand Down

0 comments on commit e0e5b4c

Please sign in to comment.