Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[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
- Loading branch information
Adam Balogh
committed
Jul 13, 2018
1 parent
d47bde0
commit e0e5b4c
Showing
8 changed files
with
571 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
214 changes: 214 additions & 0 deletions
214
clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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
47
clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
37 changes: 37 additions & 0 deletions
37
clang-tools-extra/docs/clang-tidy/checks/bugprone-exception-escape.rst
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.