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