reference, declarationdefinition
definition → references, declarations, derived classes, virtual overrides
reference to multiple definitions → definitions
unreferenced
    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
//===--- 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/StringSet.h"

namespace clang {
namespace tidy {
namespace 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 : std::int8_t {
    Throwing = 0,    ///< The function can definitly throw given an AST.
    NotThrowing = 1, ///< This function can not throw, given an AST.
    Unknown = 2,     ///< This can happen for extern functions without available
                     ///< definition.
  };

  /// 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:
    using Throwables = llvm::SmallSet<const Type *, 2>;
    static ExceptionInfo createUnknown() {
      return ExceptionInfo(State::Unknown);
    }
    static ExceptionInfo createNonThrowing() {
      return ExceptionInfo(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);

    /// 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 'true' if some exceptions were filtered, otherwise 'false'.
    bool filterByCatch(const Type *BaseClass);

    /// 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 exception types that can escape from the
    /// corresponding entity.
    const Throwables &getExceptionTypes() 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 princple
    /// 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,
                  llvm::SmallSet<const FunctionDecl *, 32> &CallStack);
  ExceptionInfo
  throwsException(const Stmt *St, const ExceptionInfo::Throwables &Caught,
                  llvm::SmallSet<const FunctionDecl *, 32> &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;
  std::map<const FunctionDecl *, ExceptionInfo> FunctionCache;
};

} // namespace utils
} // namespace tidy
} // namespace clang

#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_EXCEPTION_ANALYZER_H