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