Skip to content

Commit 9b12742

Browse files
committed
[clang-tidy] refactor bugprone-exception-escape analysis into class
Summary: The check `bugprone-exception-escape` does an AST-based analysis to determine if a function might throw an exception and warns based on that information. The analysis part is refactored into a standalone class similiar to `ExprMutAnalyzer` that is generally useful. I intent to use that class in a new check to automatically introduce `noexcept` if possible. Reviewers: aaron.ballman, alexfh, hokein, baloghadamsoftware, lebedev.ri Reviewed By: baloghadamsoftware, lebedev.ri Subscribers: lebedev.ri, mgorny, xazax.hun, rnkovacs, cfe-commits Tags: #clang-tools-extra Differential Revision: https://reviews.llvm.org/D57100 llvm-svn: 352741
1 parent 4b70204 commit 9b12742

File tree

5 files changed

+223
-151
lines changed

5 files changed

+223
-151
lines changed

clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp

Lines changed: 17 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//===--- ExceptionEscapeCheck.cpp - clang-tidy-----------------------------===//
1+
//===--- ExceptionEscapeCheck.cpp - clang-tidy ----------------------------===//
22
//
33
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
44
// See https://llvm.org/LICENSE.txt for license information.
@@ -10,158 +10,21 @@
1010

1111
#include "clang/AST/ASTContext.h"
1212
#include "clang/ASTMatchers/ASTMatchFinder.h"
13-
1413
#include "llvm/ADT/SmallSet.h"
1514
#include "llvm/ADT/StringSet.h"
1615

1716
using namespace clang::ast_matchers;
1817

19-
namespace {
20-
typedef llvm::SmallVector<const clang::Type *, 8> TypeVec;
21-
} // namespace
22-
2318
namespace clang {
24-
25-
static bool isBaseOf(const Type *DerivedType, const Type *BaseType) {
26-
const auto *DerivedClass = DerivedType->getAsCXXRecordDecl();
27-
const auto *BaseClass = BaseType->getAsCXXRecordDecl();
28-
if (!DerivedClass || !BaseClass)
29-
return false;
30-
31-
return !DerivedClass->forallBases(
32-
[BaseClass](const CXXRecordDecl *Cur) { return Cur != BaseClass; });
33-
}
34-
35-
static const TypeVec
36-
throwsException(const Stmt *St, const TypeVec &Caught,
37-
llvm::SmallSet<const FunctionDecl *, 32> &CallStack);
38-
39-
static const TypeVec
40-
throwsException(const FunctionDecl *Func,
41-
llvm::SmallSet<const FunctionDecl *, 32> &CallStack) {
42-
if (CallStack.count(Func))
43-
return TypeVec();
44-
45-
if (const Stmt *Body = Func->getBody()) {
46-
CallStack.insert(Func);
47-
const TypeVec Result = throwsException(Body, TypeVec(), CallStack);
48-
CallStack.erase(Func);
49-
return Result;
50-
}
51-
52-
TypeVec Result;
53-
if (const auto *FPT = Func->getType()->getAs<FunctionProtoType>()) {
54-
for (const QualType Ex : FPT->exceptions()) {
55-
Result.push_back(Ex.getTypePtr());
56-
}
57-
}
58-
return Result;
59-
}
60-
61-
static const TypeVec
62-
throwsException(const Stmt *St, const TypeVec &Caught,
63-
llvm::SmallSet<const FunctionDecl *, 32> &CallStack) {
64-
TypeVec Results;
65-
66-
if (!St)
67-
return Results;
68-
69-
if (const auto *Throw = dyn_cast<CXXThrowExpr>(St)) {
70-
if (const auto *ThrownExpr = Throw->getSubExpr()) {
71-
const auto *ThrownType =
72-
ThrownExpr->getType()->getUnqualifiedDesugaredType();
73-
if (ThrownType->isReferenceType()) {
74-
ThrownType = ThrownType->castAs<ReferenceType>()
75-
->getPointeeType()
76-
->getUnqualifiedDesugaredType();
77-
}
78-
if (const auto *TD = ThrownType->getAsTagDecl()) {
79-
if (TD->getDeclName().isIdentifier() && TD->getName() == "bad_alloc"
80-
&& TD->isInStdNamespace())
81-
return Results;
82-
}
83-
Results.push_back(ThrownExpr->getType()->getUnqualifiedDesugaredType());
84-
} else {
85-
Results.append(Caught.begin(), Caught.end());
86-
}
87-
} else if (const auto *Try = dyn_cast<CXXTryStmt>(St)) {
88-
TypeVec Uncaught = throwsException(Try->getTryBlock(), Caught, CallStack);
89-
for (unsigned i = 0; i < Try->getNumHandlers(); ++i) {
90-
const CXXCatchStmt *Catch = Try->getHandler(i);
91-
if (!Catch->getExceptionDecl()) {
92-
const TypeVec Rethrown =
93-
throwsException(Catch->getHandlerBlock(), Uncaught, CallStack);
94-
Results.append(Rethrown.begin(), Rethrown.end());
95-
Uncaught.clear();
96-
} else {
97-
const auto *CaughtType =
98-
Catch->getCaughtType()->getUnqualifiedDesugaredType();
99-
if (CaughtType->isReferenceType()) {
100-
CaughtType = CaughtType->castAs<ReferenceType>()
101-
->getPointeeType()
102-
->getUnqualifiedDesugaredType();
103-
}
104-
auto NewEnd =
105-
llvm::remove_if(Uncaught, [&CaughtType](const Type *ThrownType) {
106-
return ThrownType == CaughtType ||
107-
isBaseOf(ThrownType, CaughtType);
108-
});
109-
if (NewEnd != Uncaught.end()) {
110-
Uncaught.erase(NewEnd, Uncaught.end());
111-
const TypeVec Rethrown = throwsException(
112-
Catch->getHandlerBlock(), TypeVec(1, CaughtType), CallStack);
113-
Results.append(Rethrown.begin(), Rethrown.end());
114-
}
115-
}
116-
}
117-
Results.append(Uncaught.begin(), Uncaught.end());
118-
} else if (const auto *Call = dyn_cast<CallExpr>(St)) {
119-
if (const FunctionDecl *Func = Call->getDirectCallee()) {
120-
TypeVec Excs = throwsException(Func, CallStack);
121-
Results.append(Excs.begin(), Excs.end());
122-
}
123-
} else {
124-
for (const Stmt *Child : St->children()) {
125-
TypeVec Excs = throwsException(Child, Caught, CallStack);
126-
Results.append(Excs.begin(), Excs.end());
127-
}
128-
}
129-
return Results;
130-
}
131-
132-
static const TypeVec throwsException(const FunctionDecl *Func) {
133-
llvm::SmallSet<const FunctionDecl *, 32> CallStack;
134-
return throwsException(Func, CallStack);
135-
}
136-
137-
namespace ast_matchers {
138-
AST_MATCHER_P(FunctionDecl, throws, internal::Matcher<Type>, InnerMatcher) {
139-
TypeVec ExceptionList = throwsException(&Node);
140-
auto NewEnd = llvm::remove_if(
141-
ExceptionList, [this, Finder, Builder](const Type *Exception) {
142-
return !InnerMatcher.matches(*Exception, Finder, Builder);
143-
});
144-
ExceptionList.erase(NewEnd, ExceptionList.end());
145-
return ExceptionList.size();
146-
}
147-
148-
AST_MATCHER_P(Type, isIgnored, llvm::StringSet<>, IgnoredExceptions) {
149-
if (const auto *TD = Node.getAsTagDecl()) {
150-
if (TD->getDeclName().isIdentifier())
151-
return IgnoredExceptions.count(TD->getName()) > 0;
152-
}
153-
return false;
154-
}
155-
19+
namespace {
15620
AST_MATCHER_P(FunctionDecl, isEnabled, llvm::StringSet<>,
15721
FunctionsThatShouldNotThrow) {
15822
return FunctionsThatShouldNotThrow.count(Node.getNameAsString()) > 0;
15923
}
160-
} // namespace ast_matchers
24+
} // namespace
16125

16226
namespace tidy {
16327
namespace bugprone {
164-
16528
ExceptionEscapeCheck::ExceptionEscapeCheck(StringRef Name,
16629
ClangTidyContext *Context)
16730
: ClangTidyCheck(Name, Context), RawFunctionsThatShouldNotThrow(Options.get(
@@ -173,9 +36,12 @@ ExceptionEscapeCheck::ExceptionEscapeCheck(StringRef Name,
17336
.split(FunctionsThatShouldNotThrowVec, ",", -1, false);
17437
FunctionsThatShouldNotThrow.insert(FunctionsThatShouldNotThrowVec.begin(),
17538
FunctionsThatShouldNotThrowVec.end());
39+
40+
llvm::StringSet<> IgnoredExceptions;
17641
StringRef(RawIgnoredExceptions).split(IgnoredExceptionsVec, ",", -1, false);
17742
IgnoredExceptions.insert(IgnoredExceptionsVec.begin(),
17843
IgnoredExceptionsVec.end());
44+
Tracer.ignoreExceptions(std::move(IgnoredExceptions));
17945
}
18046

18147
void ExceptionEscapeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
@@ -193,22 +59,25 @@ void ExceptionEscapeCheck::registerMatchers(MatchFinder *Finder) {
19359
cxxConstructorDecl(isMoveConstructor()),
19460
cxxMethodDecl(isMoveAssignmentOperator()),
19561
hasName("main"), hasName("swap"),
196-
isEnabled(FunctionsThatShouldNotThrow)),
197-
throws(unless(isIgnored(IgnoredExceptions))))
62+
isEnabled(FunctionsThatShouldNotThrow)))
19863
.bind("thrower"),
19964
this);
20065
}
20166

20267
void ExceptionEscapeCheck::check(const MatchFinder::MatchResult &Result) {
203-
const FunctionDecl *MatchedDecl =
204-
Result.Nodes.getNodeAs<FunctionDecl>("thrower");
68+
const auto *MatchedDecl = Result.Nodes.getNodeAs<FunctionDecl>("thrower");
69+
20570
if (!MatchedDecl)
20671
return;
20772

208-
// FIXME: We should provide more information about the exact location where
209-
// the exception is thrown, maybe the full path the exception escapes
210-
diag(MatchedDecl->getLocation(), "an exception may be thrown in function %0 "
211-
"which should not throw exceptions") << MatchedDecl;
73+
if (Tracer.throwsException(MatchedDecl))
74+
// FIXME: We should provide more information about the exact location where
75+
// the exception is thrown, maybe the full path the exception escapes
76+
diag(MatchedDecl->getLocation(),
77+
"an exception may be thrown in function %0 "
78+
79+
"which should not throw exceptions")
80+
<< MatchedDecl;
21281
}
21382

21483
} // namespace bugprone

clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//===--- ExceptionEscapeCheck.h - clang-tidy---------------------*- C++ -*-===//
1+
//===--- ExceptionEscapeCheck.h - clang-tidy --------------------*- C++ -*-===//
22
//
33
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
44
// See https://llvm.org/LICENSE.txt for license information.
@@ -10,7 +10,7 @@
1010
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_EXCEPTION_ESCAPE_H
1111

1212
#include "../ClangTidy.h"
13-
13+
#include "../utils/ExceptionAnalyzer.h"
1414
#include "llvm/ADT/StringSet.h"
1515

1616
namespace clang {
@@ -36,7 +36,7 @@ class ExceptionEscapeCheck : public ClangTidyCheck {
3636
std::string RawIgnoredExceptions;
3737

3838
llvm::StringSet<> FunctionsThatShouldNotThrow;
39-
llvm::StringSet<> IgnoredExceptions;
39+
utils::ExceptionAnalyzer Tracer;
4040
};
4141

4242
} // namespace bugprone

clang-tools-extra/clang-tidy/utils/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ set(LLVM_LINK_COMPONENTS support)
33
add_clang_library(clangTidyUtils
44
ASTUtils.cpp
55
DeclRefExprUtils.cpp
6+
ExceptionAnalyzer.cpp
67
ExprSequence.cpp
78
FixItHintUtils.cpp
89
HeaderFileExtensionsUtils.cpp

0 commit comments

Comments
 (0)