Skip to content

Commit 9fe5ef7

Browse files
committed
[clang-tidy] add stacktrace to exception-escape
1 parent fdbd9c1 commit 9fe5ef7

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
@@ -78,13 +78,46 @@ void ExceptionEscapeCheck::check(const MatchFinder::MatchResult &Result) {
7878
if (!MatchedDecl)
7979
return;
8080

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

90123
} // 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(
@@ -356,10 +356,12 @@ static bool canThrow(const FunctionDecl *Func) {
356356
};
357357
}
358358

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

@@ -409,11 +411,18 @@ bool ExceptionAnalyzer::ExceptionInfo::filterByCatch(
409411
}
410412
}
411413

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

415424
reevaluateBehaviour();
416-
return !TypesToDelete.empty();
425+
return DeletedExceptions;
417426
}
418427

419428
ExceptionAnalyzer::ExceptionInfo &
@@ -422,7 +431,8 @@ ExceptionAnalyzer::ExceptionInfo::filterIgnoredExceptions(
422431
llvm::SmallVector<const Type *, 8> TypesToDelete;
423432
// Note: Using a 'SmallSet' with 'llvm::remove_if()' is not possible.
424433
// Therefore this slightly hacky implementation is required.
425-
for (const Type *T : ThrownExceptions) {
434+
for (const auto &ThrownException : ThrownExceptions) {
435+
const Type *T = ThrownException.getFirst();
426436
if (const auto *TD = T->getAsTagDecl()) {
427437
if (TD->getDeclName().isIdentifier()) {
428438
if ((IgnoreBadAlloc &&
@@ -454,10 +464,10 @@ void ExceptionAnalyzer::ExceptionInfo::reevaluateBehaviour() {
454464
else
455465
Behaviour = State::Throwing;
456466
}
457-
458-
ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException(
459-
const FunctionDecl *Func, const ExceptionInfo::Throwables &Caught,
460-
llvm::SmallSet<const FunctionDecl *, 32> &CallStack) {
467+
ExceptionAnalyzer::ExceptionInfo
468+
ExceptionAnalyzer::throwsException(const FunctionDecl *Func,
469+
const ExceptionInfo::Throwables &Caught,
470+
CallStack &CallStack) {
461471
if (!Func || CallStack.contains(Func) ||
462472
(!CallStack.empty() && !canThrow(Func)))
463473
return ExceptionInfo::createNonThrowing();
@@ -475,23 +485,25 @@ ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException(
475485
}
476486
}
477487

478-
CallStack.erase(Func);
488+
CallStack.remove(Func);
479489
return Result;
480490
}
481491

482492
auto Result = ExceptionInfo::createUnknown();
483493
if (const auto *FPT = Func->getType()->getAs<FunctionProtoType>()) {
484494
for (const QualType &Ex : FPT->exceptions())
485-
Result.registerException(Ex.getTypePtr());
495+
// Nothing in ThrowInfo because there is no location of 'throw'
496+
Result.registerException(Ex.getTypePtr(), {});
486497
}
487498
return Result;
488499
}
489500

490501
/// Analyzes a single statement on it's throwing behaviour. This is in principle
491502
/// possible except some 'Unknown' functions are called.
492-
ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException(
493-
const Stmt *St, const ExceptionInfo::Throwables &Caught,
494-
llvm::SmallSet<const FunctionDecl *, 32> &CallStack) {
503+
ExceptionAnalyzer::ExceptionInfo
504+
ExceptionAnalyzer::throwsException(const Stmt *St,
505+
const ExceptionInfo::Throwables &Caught,
506+
CallStack &CallStack) {
495507
auto Results = ExceptionInfo::createNonThrowing();
496508
if (!St)
497509
return Results;
@@ -505,7 +517,8 @@ ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException(
505517
->getPointeeType()
506518
->getUnqualifiedDesugaredType();
507519
Results.registerException(
508-
ThrownExpr->getType()->getUnqualifiedDesugaredType());
520+
ThrownExpr->getType()->getUnqualifiedDesugaredType(),
521+
{Throw->getBeginLoc(), CallStack});
509522
} else
510523
// A rethrow of a caught exception happens which makes it possible
511524
// to throw all exception that are caught in the 'catch' clause of
@@ -520,7 +533,7 @@ ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException(
520533
// Everything is caught through 'catch(...)'.
521534
if (!Catch->getExceptionDecl()) {
522535
ExceptionInfo Rethrown = throwsException(
523-
Catch->getHandlerBlock(), Uncaught.getExceptionTypes(), CallStack);
536+
Catch->getHandlerBlock(), Uncaught.getExceptions(), CallStack);
524537
Results.merge(Rethrown);
525538
Uncaught.clear();
526539
} else {
@@ -536,12 +549,12 @@ ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException(
536549
// thrown types (because it's sensitive to inheritance) the throwing
537550
// situation changes. First of all filter the exception types and
538551
// analyze if the baseclass-exception is rethrown.
539-
if (Uncaught.filterByCatch(
540-
CaughtType, Catch->getExceptionDecl()->getASTContext())) {
541-
ExceptionInfo::Throwables CaughtExceptions;
542-
CaughtExceptions.insert(CaughtType);
543-
ExceptionInfo Rethrown = throwsException(Catch->getHandlerBlock(),
544-
CaughtExceptions, CallStack);
552+
const ExceptionInfo::Throwables FilteredExceptions =
553+
Uncaught.filterByCatch(CaughtType,
554+
Catch->getExceptionDecl()->getASTContext());
555+
if (!FilteredExceptions.empty()) {
556+
ExceptionInfo Rethrown = throwsException(
557+
Catch->getHandlerBlock(), FilteredExceptions, CallStack);
545558
Results.merge(Rethrown);
546559
}
547560
}
@@ -569,9 +582,10 @@ ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException(
569582
}
570583
ExceptionInfo Excs = throwsException(Coro->getBody(), Caught, CallStack);
571584
Results.merge(throwsException(Coro->getExceptionHandler(),
572-
Excs.getExceptionTypes(), CallStack));
573-
for (const Type *Throwable : Excs.getExceptionTypes()) {
574-
if (const auto *ThrowableRec = Throwable->getAsCXXRecordDecl()) {
585+
Excs.getExceptions(), CallStack));
586+
for (const auto &Exception : Excs.getExceptions()) {
587+
const Type *ExcType = Exception.getFirst();
588+
if (const CXXRecordDecl *ThrowableRec = ExcType->getAsCXXRecordDecl()) {
575589
ExceptionInfo DestructorExcs =
576590
throwsException(ThrowableRec->getDestructor(), Caught, CallStack);
577591
Results.merge(DestructorExcs);
@@ -593,7 +607,7 @@ ExceptionAnalyzer::analyzeImpl(const FunctionDecl *Func) {
593607
// Check if the function has already been analyzed and reuse that result.
594608
const auto CacheEntry = FunctionCache.find(Func);
595609
if (CacheEntry == FunctionCache.end()) {
596-
llvm::SmallSet<const FunctionDecl *, 32> CallStack;
610+
CallStack CallStack;
597611
ExceptionList =
598612
throwsException(Func, ExceptionInfo::Throwables(), CallStack);
599613

@@ -610,7 +624,7 @@ ExceptionAnalyzer::analyzeImpl(const FunctionDecl *Func) {
610624

611625
ExceptionAnalyzer::ExceptionInfo
612626
ExceptionAnalyzer::analyzeImpl(const Stmt *Stmt) {
613-
llvm::SmallSet<const FunctionDecl *, 32> CallStack;
627+
CallStack CallStack;
614628
return throwsException(Stmt, ExceptionInfo::Throwables(), CallStack);
615629
}
616630

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
@@ -187,6 +187,10 @@ Changes in existing checks
187187
false positives on deleted constructors that cannot be used to construct
188188
objects, even if they have public or protected access.
189189

190+
- Improved :doc:`bugprone-exception-escape
191+
<clang-tidy/checks/bugprone/exception-escape>` check to print stack trace
192+
of a potentially escaped exception.
193+
190194
- Added an option to :doc:`bugprone-multi-level-implicit-pointer-conversion
191195
<clang-tidy/checks/bugprone/multi-level-implicit-pointer-conversion>` to
192196
choose whether to enable the check in C code or not.

0 commit comments

Comments
 (0)