1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
|
//===--- ExceptionAnalyzer.h - clang-tidy -----------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_EXCEPTION_ANALYZER_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_EXCEPTION_ANALYZER_H
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringSet.h"
namespace clang::tidy::utils {
/// This class analysis if a `FunctionDecl` can in principle throw an
/// exception, either directly or indirectly. It can be configured to ignore
/// custom exception types.
class ExceptionAnalyzer {
public:
enum class State {
Throwing, ///< The function can definitely throw given an AST.
NotThrowing, ///< This function can not throw, given an AST.
Unknown, ///< This can happen for extern functions without available
///< definition.
};
/// We use a MapVector to preserve the order of the functions in the call
/// stack as well as have fast lookup.
using CallStack = llvm::MapVector<const FunctionDecl *, SourceLocation>;
/// Bundle the gathered information about an entity like a function regarding
/// it's exception behaviour. The 'NonThrowing'-state can be considered as the
/// neutral element in terms of information propagation.
/// In the case of 'Throwing' state it is possible that 'getExceptionTypes'
/// does not include *ALL* possible types as there is the possibility that
/// an 'Unknown' function is called that might throw a previously unknown
/// exception at runtime.
class ExceptionInfo {
public:
/// Holds information about where an exception is thrown.
/// First element in the call stack is analyzed function.
struct ThrowInfo {
SourceLocation Loc;
CallStack Stack;
};
using Throwables = llvm::SmallDenseMap<const Type *, ThrowInfo, 2>;
static ExceptionInfo createUnknown() { return {State::Unknown}; }
static ExceptionInfo createNonThrowing() { return {State::Throwing}; }
/// By default the exception situation is unknown and must be
/// clarified step-wise.
ExceptionInfo() : Behaviour(State::NotThrowing), ContainsUnknown(false) {}
ExceptionInfo(State S)
: Behaviour(S), ContainsUnknown(S == State::Unknown) {}
ExceptionInfo(const ExceptionInfo &) = default;
ExceptionInfo &operator=(const ExceptionInfo &) = default;
ExceptionInfo(ExceptionInfo &&) = default;
ExceptionInfo &operator=(ExceptionInfo &&) = default;
State getBehaviour() const { return Behaviour; }
/// Register a single exception type as recognized potential exception to be
/// thrown.
void registerException(const Type *ExceptionType,
const ThrowInfo &ThrowInfo);
/// Registers a `SmallVector` of exception types as recognized potential
/// exceptions to be thrown.
void registerExceptions(const Throwables &Exceptions);
/// Updates the local state according to the other state. That means if
/// for example a function contains multiple statements the 'ExceptionInfo'
/// for the final function is the merged result of each statement.
/// If one of these statements throws the whole function throws and if one
/// part is unknown and the rest is non-throwing the result will be
/// unknown.
ExceptionInfo &merge(const ExceptionInfo &Other);
/// This method is useful in case 'catch' clauses are analyzed as it is
/// possible to catch multiple exception types by one 'catch' if they
/// are a subclass of the 'catch'ed exception type.
/// Returns filtered exceptions.
Throwables filterByCatch(const Type *HandlerTy, const ASTContext &Context);
/// Filter the set of thrown exception type against a set of ignored
/// types that shall not be considered in the exception analysis.
/// This includes explicit `std::bad_alloc` ignoring as separate option.
ExceptionInfo &
filterIgnoredExceptions(const llvm::StringSet<> &IgnoredTypes,
bool IgnoreBadAlloc);
/// Clear the state to 'NonThrowing' to make the corresponding entity
/// neutral.
void clear();
/// References the set of known exceptions that can escape from the
/// corresponding entity.
const Throwables &getExceptions() const { return ThrownExceptions; }
/// Signal if the there is any 'Unknown' element within the scope of
/// the related entity. This might be relevant if the entity is 'Throwing'
/// and to ensure that no other exception then 'getExceptionTypes' can
/// occur. If there is an 'Unknown' element this can not be guaranteed.
bool containsUnknownElements() const { return ContainsUnknown; }
private:
/// Recalculate the 'Behaviour' for example after filtering.
void reevaluateBehaviour();
/// Keep track if the entity related to this 'ExceptionInfo' can in
/// principle throw, if it's unknown or if it won't throw.
State Behaviour;
/// Keep track if the entity contains any unknown elements to keep track
/// of the certainty of decisions and/or correct 'Behaviour' transition
/// after filtering.
bool ContainsUnknown;
/// 'ThrownException' is empty if the 'Behaviour' is either 'NotThrowing' or
/// 'Unknown'.
Throwables ThrownExceptions;
};
ExceptionAnalyzer() = default;
void ignoreBadAlloc(bool ShallIgnore) { IgnoreBadAlloc = ShallIgnore; }
void ignoreExceptions(llvm::StringSet<> ExceptionNames) {
IgnoredExceptions = std::move(ExceptionNames);
}
ExceptionInfo analyze(const FunctionDecl *Func);
ExceptionInfo analyze(const Stmt *Stmt);
private:
ExceptionInfo throwsException(const FunctionDecl *Func,
const ExceptionInfo::Throwables &Caught,
CallStack &CallStack, SourceLocation CallLoc);
ExceptionInfo throwsException(const Stmt *St,
const ExceptionInfo::Throwables &Caught,
CallStack &CallStack);
ExceptionInfo analyzeImpl(const FunctionDecl *Func);
ExceptionInfo analyzeImpl(const Stmt *Stmt);
template <typename T> ExceptionInfo analyzeDispatch(const T *Node);
bool IgnoreBadAlloc = true;
llvm::StringSet<> IgnoredExceptions;
llvm::DenseMap<const FunctionDecl *, ExceptionInfo> FunctionCache{32U};
};
} // namespace clang::tidy::utils
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_EXCEPTION_ANALYZER_H
|