Skip to content

Commit 9865b1a

Browse files
committed
[clang-tidy] add stacktrace to exception-escape
1 parent 5c65a32 commit 9865b1a

File tree

8 files changed

+361
-52
lines changed

8 files changed

+361
-52
lines changed

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

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,46 @@ void ExceptionEscapeCheck::check(const MatchFinder::MatchResult &Result) {
8080
if (!MatchedDecl)
8181
return;
8282

83-
if (Tracer.analyze(MatchedDecl).getBehaviour() ==
84-
utils::ExceptionAnalyzer::State::Throwing)
85-
// FIXME: We should provide more information about the exact location where
86-
// the exception is thrown, maybe the full path the exception escapes
87-
diag(MatchedDecl->getLocation(), "an exception may be thrown in function "
88-
"%0 which should not throw exceptions")
89-
<< MatchedDecl;
83+
const utils::ExceptionAnalyzer::ExceptionInfo Info =
84+
Tracer.analyze(MatchedDecl);
85+
86+
if (Info.getBehaviour() != utils::ExceptionAnalyzer::State::Throwing) {
87+
return;
88+
}
89+
90+
diag(MatchedDecl->getLocation(), "an exception may be thrown in function "
91+
"%0 which should not throw exceptions")
92+
<< MatchedDecl;
93+
94+
const utils::ExceptionAnalyzer::ExceptionInfo::ThrowInfo ThrowInfo =
95+
Info.getExceptions().begin()->getSecond();
96+
97+
if (ThrowInfo.Loc.isInvalid()) {
98+
return;
99+
}
100+
101+
// FIXME: We should provide exact position of functions calls, not only call
102+
// stack of thrown exception.
103+
const utils::ExceptionAnalyzer::CallStack &Stack = ThrowInfo.Stack;
104+
diag(Stack.front()->getLocation(),
105+
"example of unhandled exception throw stack, starting from function %0",
106+
DiagnosticIDs::Note)
107+
<< Stack.front();
108+
109+
size_t FrameNo = 0;
110+
for (const FunctionDecl *CallNode : Stack) {
111+
if (FrameNo != Stack.size() - 1) {
112+
diag(CallNode->getLocation(), "frame #%0: function %1",
113+
DiagnosticIDs::Note)
114+
<< FrameNo << CallNode;
115+
} else {
116+
diag(ThrowInfo.Loc,
117+
"frame #%0: function %1 throws unhandled exception here",
118+
DiagnosticIDs::Note)
119+
<< FrameNo << CallNode;
120+
}
121+
++FrameNo;
122+
}
90123
}
91124

92125
} // namespace clang::tidy::bugprone

clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp

Lines changed: 45 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@
1111
namespace clang::tidy::utils {
1212

1313
void ExceptionAnalyzer::ExceptionInfo::registerException(
14-
const Type *ExceptionType) {
14+
const Type *ExceptionType, const ThrowInfo &ThrowInfo) {
1515
assert(ExceptionType != nullptr && "Only valid types are accepted");
1616
Behaviour = State::Throwing;
17-
ThrownExceptions.insert(ExceptionType);
17+
ThrownExceptions.insert({ExceptionType, ThrowInfo});
1818
}
1919

2020
void ExceptionAnalyzer::ExceptionInfo::registerExceptions(
@@ -354,10 +354,12 @@ static bool canThrow(const FunctionDecl *Func) {
354354
};
355355
}
356356

357-
bool ExceptionAnalyzer::ExceptionInfo::filterByCatch(
358-
const Type *HandlerTy, const ASTContext &Context) {
357+
ExceptionAnalyzer::ExceptionInfo::Throwables
358+
ExceptionAnalyzer::ExceptionInfo::filterByCatch(const Type *HandlerTy,
359+
const ASTContext &Context) {
359360
llvm::SmallVector<const Type *, 8> TypesToDelete;
360-
for (const Type *ExceptionTy : ThrownExceptions) {
361+
for (const auto &ThrownException : ThrownExceptions) {
362+
const Type *ExceptionTy = ThrownException.getFirst();
361363
CanQualType ExceptionCanTy = ExceptionTy->getCanonicalTypeUnqualified();
362364
CanQualType HandlerCanTy = HandlerTy->getCanonicalTypeUnqualified();
363365

@@ -407,11 +409,18 @@ bool ExceptionAnalyzer::ExceptionInfo::filterByCatch(
407409
}
408410
}
409411

410-
for (const Type *T : TypesToDelete)
411-
ThrownExceptions.erase(T);
412+
Throwables DeletedExceptions;
413+
414+
for (const Type *TypeToDelete : TypesToDelete) {
415+
const auto DeleteIt = ThrownExceptions.find(TypeToDelete);
416+
if (DeleteIt != ThrownExceptions.end()) {
417+
DeletedExceptions.insert(*DeleteIt);
418+
ThrownExceptions.erase(DeleteIt);
419+
}
420+
}
412421

413422
reevaluateBehaviour();
414-
return !TypesToDelete.empty();
423+
return DeletedExceptions;
415424
}
416425

417426
ExceptionAnalyzer::ExceptionInfo &
@@ -420,7 +429,8 @@ ExceptionAnalyzer::ExceptionInfo::filterIgnoredExceptions(
420429
llvm::SmallVector<const Type *, 8> TypesToDelete;
421430
// Note: Using a 'SmallSet' with 'llvm::remove_if()' is not possible.
422431
// Therefore this slightly hacky implementation is required.
423-
for (const Type *T : ThrownExceptions) {
432+
for (const auto &ThrownException : ThrownExceptions) {
433+
const Type *T = ThrownException.getFirst();
424434
if (const auto *TD = T->getAsTagDecl()) {
425435
if (TD->getDeclName().isIdentifier()) {
426436
if ((IgnoreBadAlloc &&
@@ -452,10 +462,10 @@ void ExceptionAnalyzer::ExceptionInfo::reevaluateBehaviour() {
452462
else
453463
Behaviour = State::Throwing;
454464
}
455-
456-
ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException(
457-
const FunctionDecl *Func, const ExceptionInfo::Throwables &Caught,
458-
llvm::SmallSet<const FunctionDecl *, 32> &CallStack) {
465+
ExceptionAnalyzer::ExceptionInfo
466+
ExceptionAnalyzer::throwsException(const FunctionDecl *Func,
467+
const ExceptionInfo::Throwables &Caught,
468+
CallStack &CallStack) {
459469
if (!Func || CallStack.contains(Func) ||
460470
(!CallStack.empty() && !canThrow(Func)))
461471
return ExceptionInfo::createNonThrowing();
@@ -473,23 +483,25 @@ ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException(
473483
}
474484
}
475485

476-
CallStack.erase(Func);
486+
CallStack.remove(Func);
477487
return Result;
478488
}
479489

480490
auto Result = ExceptionInfo::createUnknown();
481491
if (const auto *FPT = Func->getType()->getAs<FunctionProtoType>()) {
482492
for (const QualType &Ex : FPT->exceptions())
483-
Result.registerException(Ex.getTypePtr());
493+
// Nothing in ThrowInfo because there is no location of 'throw'
494+
Result.registerException(Ex.getTypePtr(), {});
484495
}
485496
return Result;
486497
}
487498

488499
/// Analyzes a single statement on it's throwing behaviour. This is in principle
489500
/// possible except some 'Unknown' functions are called.
490-
ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException(
491-
const Stmt *St, const ExceptionInfo::Throwables &Caught,
492-
llvm::SmallSet<const FunctionDecl *, 32> &CallStack) {
501+
ExceptionAnalyzer::ExceptionInfo
502+
ExceptionAnalyzer::throwsException(const Stmt *St,
503+
const ExceptionInfo::Throwables &Caught,
504+
CallStack &CallStack) {
493505
auto Results = ExceptionInfo::createNonThrowing();
494506
if (!St)
495507
return Results;
@@ -503,7 +515,8 @@ ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException(
503515
->getPointeeType()
504516
->getUnqualifiedDesugaredType();
505517
Results.registerException(
506-
ThrownExpr->getType()->getUnqualifiedDesugaredType());
518+
ThrownExpr->getType()->getUnqualifiedDesugaredType(),
519+
{Throw->getBeginLoc(), CallStack});
507520
} else
508521
// A rethrow of a caught exception happens which makes it possible
509522
// to throw all exception that are caught in the 'catch' clause of
@@ -518,7 +531,7 @@ ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException(
518531
// Everything is caught through 'catch(...)'.
519532
if (!Catch->getExceptionDecl()) {
520533
ExceptionInfo Rethrown = throwsException(
521-
Catch->getHandlerBlock(), Uncaught.getExceptionTypes(), CallStack);
534+
Catch->getHandlerBlock(), Uncaught.getExceptions(), CallStack);
522535
Results.merge(Rethrown);
523536
Uncaught.clear();
524537
} else {
@@ -534,12 +547,12 @@ ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException(
534547
// thrown types (because it's sensitive to inheritance) the throwing
535548
// situation changes. First of all filter the exception types and
536549
// analyze if the baseclass-exception is rethrown.
537-
if (Uncaught.filterByCatch(
538-
CaughtType, Catch->getExceptionDecl()->getASTContext())) {
539-
ExceptionInfo::Throwables CaughtExceptions;
540-
CaughtExceptions.insert(CaughtType);
541-
ExceptionInfo Rethrown = throwsException(Catch->getHandlerBlock(),
542-
CaughtExceptions, CallStack);
550+
const ExceptionInfo::Throwables FilteredExceptions =
551+
Uncaught.filterByCatch(CaughtType,
552+
Catch->getExceptionDecl()->getASTContext());
553+
if (!FilteredExceptions.empty()) {
554+
ExceptionInfo Rethrown = throwsException(
555+
Catch->getHandlerBlock(), FilteredExceptions, CallStack);
543556
Results.merge(Rethrown);
544557
}
545558
}
@@ -567,9 +580,10 @@ ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException(
567580
}
568581
ExceptionInfo Excs = throwsException(Coro->getBody(), Caught, CallStack);
569582
Results.merge(throwsException(Coro->getExceptionHandler(),
570-
Excs.getExceptionTypes(), CallStack));
571-
for (const Type *Throwable : Excs.getExceptionTypes()) {
572-
if (const auto ThrowableRec = Throwable->getAsCXXRecordDecl()) {
583+
Excs.getExceptions(), CallStack));
584+
for (const auto &Exception : Excs.getExceptions()) {
585+
const Type *ExcType = Exception.getFirst();
586+
if (const CXXRecordDecl *ThrowableRec = ExcType->getAsCXXRecordDecl()) {
573587
ExceptionInfo DestructorExcs =
574588
throwsException(ThrowableRec->getDestructor(), Caught, CallStack);
575589
Results.merge(DestructorExcs);
@@ -591,7 +605,7 @@ ExceptionAnalyzer::analyzeImpl(const FunctionDecl *Func) {
591605
// Check if the function has already been analyzed and reuse that result.
592606
const auto CacheEntry = FunctionCache.find(Func);
593607
if (CacheEntry == FunctionCache.end()) {
594-
llvm::SmallSet<const FunctionDecl *, 32> CallStack;
608+
CallStack CallStack;
595609
ExceptionList =
596610
throwsException(Func, ExceptionInfo::Throwables(), CallStack);
597611

@@ -608,7 +622,7 @@ ExceptionAnalyzer::analyzeImpl(const FunctionDecl *Func) {
608622

609623
ExceptionAnalyzer::ExceptionInfo
610624
ExceptionAnalyzer::analyzeImpl(const Stmt *Stmt) {
611-
llvm::SmallSet<const FunctionDecl *, 32> CallStack;
625+
CallStack CallStack;
612626
return throwsException(Stmt, ExceptionInfo::Throwables(), CallStack);
613627
}
614628

clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.h

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "clang/AST/ASTContext.h"
1313
#include "clang/ASTMatchers/ASTMatchFinder.h"
1414
#include "llvm/ADT/SmallSet.h"
15+
#include "llvm/ADT/SmallVector.h"
1516
#include "llvm/ADT/StringSet.h"
1617

1718
namespace clang::tidy::utils {
@@ -28,6 +29,12 @@ class ExceptionAnalyzer {
2829
///< definition.
2930
};
3031

32+
/// We use a SetVector to preserve the order of the functions in the call
33+
/// stack as well as have fast lookup.
34+
using CallStack = llvm::SetVector<const FunctionDecl *,
35+
llvm::SmallVector<const FunctionDecl *, 32>,
36+
llvm::DenseSet<const FunctionDecl *>, 32>;
37+
3138
/// Bundle the gathered information about an entity like a function regarding
3239
/// it's exception behaviour. The 'NonThrowing'-state can be considered as the
3340
/// neutral element in terms of information propagation.
@@ -37,7 +44,15 @@ class ExceptionAnalyzer {
3744
/// exception at runtime.
3845
class ExceptionInfo {
3946
public:
40-
using Throwables = llvm::SmallSet<const Type *, 2>;
47+
/// Holds information about where an exception is thrown.
48+
/// First element in the call stack is analyzed function.
49+
struct ThrowInfo {
50+
SourceLocation Loc;
51+
CallStack Stack;
52+
};
53+
54+
using Throwables = llvm::SmallDenseMap<const Type *, ThrowInfo, 2>;
55+
4156
static ExceptionInfo createUnknown() { return {State::Unknown}; }
4257
static ExceptionInfo createNonThrowing() { return {State::Throwing}; }
4358

@@ -56,7 +71,8 @@ class ExceptionAnalyzer {
5671

5772
/// Register a single exception type as recognized potential exception to be
5873
/// thrown.
59-
void registerException(const Type *ExceptionType);
74+
void registerException(const Type *ExceptionType,
75+
const ThrowInfo &ThrowInfo);
6076

6177
/// Registers a `SmallVector` of exception types as recognized potential
6278
/// exceptions to be thrown.
@@ -73,8 +89,8 @@ class ExceptionAnalyzer {
7389
/// This method is useful in case 'catch' clauses are analyzed as it is
7490
/// possible to catch multiple exception types by one 'catch' if they
7591
/// are a subclass of the 'catch'ed exception type.
76-
/// Returns 'true' if some exceptions were filtered, otherwise 'false'.
77-
bool filterByCatch(const Type *HandlerTy, const ASTContext &Context);
92+
/// Returns filtered exceptions.
93+
Throwables filterByCatch(const Type *HandlerTy, const ASTContext &Context);
7894

7995
/// Filter the set of thrown exception type against a set of ignored
8096
/// types that shall not be considered in the exception analysis.
@@ -87,9 +103,9 @@ class ExceptionAnalyzer {
87103
/// neutral.
88104
void clear();
89105

90-
/// References the set of known exception types that can escape from the
106+
/// References the set of known exceptions that can escape from the
91107
/// corresponding entity.
92-
const Throwables &getExceptionTypes() const { return ThrownExceptions; }
108+
const Throwables &getExceptions() const { return ThrownExceptions; }
93109

94110
/// Signal if the there is any 'Unknown' element within the scope of
95111
/// the related entity. This might be relevant if the entity is 'Throwing'
@@ -126,14 +142,12 @@ class ExceptionAnalyzer {
126142
ExceptionInfo analyze(const Stmt *Stmt);
127143

128144
private:
129-
ExceptionInfo
130-
throwsException(const FunctionDecl *Func,
131-
const ExceptionInfo::Throwables &Caught,
132-
llvm::SmallSet<const FunctionDecl *, 32> &CallStack);
133-
ExceptionInfo
134-
throwsException(const Stmt *St, const ExceptionInfo::Throwables &Caught,
135-
llvm::SmallSet<const FunctionDecl *, 32> &CallStack);
136-
145+
ExceptionInfo throwsException(const FunctionDecl *Func,
146+
const ExceptionInfo::Throwables &Caught,
147+
CallStack &CallStack);
148+
ExceptionInfo throwsException(const Stmt *St,
149+
const ExceptionInfo::Throwables &Caught,
150+
CallStack &CallStack);
137151
ExceptionInfo analyzeImpl(const FunctionDecl *Func);
138152
ExceptionInfo analyzeImpl(const Stmt *Stmt);
139153

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ New check aliases
121121
Changes in existing checks
122122
^^^^^^^^^^^^^^^^^^^^^^^^^^
123123

124+
- Improved :doc:`bugprone-exception-escape
125+
<clang-tidy/checks/bugprone/exception-escape>` check to print stack trace
126+
of a potentially escaped exception.
127+
124128
- Improved :doc:`bugprone-optional-value-conversion
125129
<clang-tidy/checks/bugprone/optional-value-conversion>` check to detect
126130
conversion in argument of ``std::make_optional``.

0 commit comments

Comments
 (0)