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>;
createUnknown()43     static ExceptionInfo createUnknown() {
44       return ExceptionInfo(State::Unknown);
45     }
createNonThrowing()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.
ExceptionInfo()52     ExceptionInfo() : Behaviour(State::NotThrowing), ContainsUnknown(false) {}
ExceptionInfo(State S)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 
getBehaviour()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.
getExceptionTypes()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.
containsUnknownElements()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 
ignoreBadAlloc(bool ShallIgnore)126   void ignoreBadAlloc(bool ShallIgnore) { IgnoreBadAlloc = ShallIgnore; }
ignoreExceptions(llvm::StringSet<> ExceptionNames)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
157