/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * Author: Eric Niebler <eniebler@fb.com>
 */

#pragma once

#include <cassert>
#include <cstdint>
#include <exception>
#include <iosfwd>
#include <memory>
#include <new>
#include <type_traits>
#include <typeinfo>
#include <utility>

#include <folly/CPortability.h>
#include <folly/CppAttributes.h>
#include <folly/Demangle.h>
#include <folly/ExceptionString.h>
#include <folly/FBString.h>
#include <folly/Portability.h>
#include <folly/Traits.h>
#include <folly/Utility.h>
#include <folly/lang/Assume.h>
#include <folly/lang/Exception.h>

#define FOLLY_EXCEPTION_WRAPPER_H_INCLUDED

namespace folly {

#define FOLLY_REQUIRES_DEF(...) \
  std::enable_if_t<static_cast<bool>(__VA_ARGS__), long>

#define FOLLY_REQUIRES(...) FOLLY_REQUIRES_DEF(__VA_ARGS__) = __LINE__

//! Throwing exceptions can be a convenient way to handle errors. Storing
//! exceptions in an `exception_ptr` makes it easy to handle exceptions in a
//! different thread or at a later time. `exception_ptr` can also be used in a
//! very generic result/exception wrapper.
//!
//! However, there are some issues with throwing exceptions and
//! `std::exception_ptr`. These issues revolve around `throw` being expensive,
//! particularly in a multithreaded environment (see
//! ExceptionWrapperBenchmark.cpp).
//!
//! Imagine we have a library that has an API which returns a result/exception
//! wrapper. Let's consider some approaches for implementing this wrapper.
//! First, we could store a `std::exception`. This approach loses the derived
//! exception type, which can make exception handling more difficult for users
//! that prefer rethrowing the exception. We could use a `folly::dynamic` for
//! every possible type of exception. This is not very flexible - adding new
//! types of exceptions requires a change to the result/exception wrapper. We
//! could use an `exception_ptr`. However, constructing an `exception_ptr` as
//! well as accessing the error requires a call to throw. That means that there
//! will be two calls to throw in order to process the exception. For
//! performance sensitive applications, this may be unacceptable.
//!
//! `exception_wrapper` is designed to handle exception management for both
//! convenience and high performance use cases. `make_exception_wrapper` is
//! templated on derived type, allowing us to rethrow the exception properly for
//! users that prefer convenience. These explicitly named exception types can
//! therefore be handled without any performance penalty. `exception_wrapper` is
//! also flexible enough to accept any type. If a caught exception is not of an
//! explicitly named type, then `std::exception_ptr` is used to preserve the
//! exception state. For performance sensitive applications, the accessor
//! methods can test or extract a pointer to a specific exception type with very
//! little overhead.
//!
//! \par Example usage:
//! \code
//! exception_wrapper globalExceptionWrapper;
//!
//! // Thread1
//! void doSomethingCrazy() {
//!   int rc = doSomethingCrazyWithLameReturnCodes();
//!   if (rc == NAILED_IT) {
//!     globalExceptionWrapper = exception_wrapper();
//!   } else if (rc == FACE_PLANT) {
//!     globalExceptionWrapper = make_exception_wrapper<FacePlantException>();
//!   } else if (rc == FAIL_WHALE) {
//!     globalExceptionWrapper = make_exception_wrapper<FailWhaleException>();
//!   }
//! }
//!
//! // Thread2: Exceptions are ok!
//! void processResult() {
//!   try {
//!     globalExceptionWrapper.throw_exception();
//!   } catch (const FacePlantException& e) {
//!     LOG(ERROR) << "FACEPLANT!";
//!   } catch (const FailWhaleException& e) {
//!     LOG(ERROR) << "FAILWHALE!";
//!   }
//! }
//!
//! // Thread2: Exceptions are bad!
//! void processResult() {
//!   globalExceptionWrapper.handle(
//!       [&](FacePlantException& faceplant) {
//!         LOG(ERROR) << "FACEPLANT";
//!       },
//!       [&](FailWhaleException& failwhale) {
//!         LOG(ERROR) << "FAILWHALE!";
//!       },
//!       [](...) {
//!         LOG(FATAL) << "Unrecognized exception";
//!       });
//! }
//! \endcode
class exception_wrapper final {
 private:
  struct FOLLY_EXPORT AnyException : std::exception {
    std::type_info const* typeinfo_;
    template <class T>
    /* implicit */ AnyException(T&& t) noexcept : typeinfo_(&typeid(t)) {}
  };

  template <class Fn>
  struct arg_type_;
  template <class Fn>
  using arg_type = _t<arg_type_<Fn>>;

  struct with_exception_from_fn_;
  struct with_exception_from_ex_;

  // exception_wrapper is implemented as a simple variant over four
  // different representations:
  //  0. Empty, no exception.
  //  1. An small object stored in-situ.
  //  2. A larger object stored on the heap and referenced with a
  //     std::shared_ptr.
  //  3. A std::exception_ptr.
  // This is accomplished with the help of a union and a pointer to a hand-
  // rolled virtual table. This virtual table contains pointers to functions
  // that know which field of the union is active and do the proper action.
  // The class invariant ensures that the vtable ptr and the union stay in sync.
  struct VTable {
    void (*copy_)(exception_wrapper const*, exception_wrapper*);
    void (*move_)(exception_wrapper*, exception_wrapper*);
    void (*delete_)(exception_wrapper*);
    void (*throw_)(exception_wrapper const*);
    std::type_info const* (*type_)(exception_wrapper const*);
    std::exception const* (*get_exception_)(exception_wrapper const*);
    exception_wrapper (*get_exception_ptr_)(exception_wrapper const*);
  };

  [[noreturn]] static void onNoExceptionError(char const* name);

  template <class Ret, class... Args>
  static Ret noop_(Args...);

  static std::type_info const* uninit_type_(exception_wrapper const*);

  static VTable const uninit_;

  template <class Ex>
  using IsStdException = std::is_base_of<std::exception, std::decay_t<Ex>>;
  template <class CatchFn>
  using IsCatchAll =
      std::is_same<arg_type<std::decay_t<CatchFn>>, AnyException>;

  // Sadly, with the gcc-4.9 platform, std::logic_error and std::runtime_error
  // do not fit here. They also don't have noexcept copy-ctors, so the internal
  // storage wouldn't be used anyway. For the gcc-5 platform, both logic_error
  // and runtime_error can be safely stored internally.
  struct Buffer {
    using Storage =
        std::aligned_storage_t<2 * sizeof(void*), alignof(std::exception)>;
    Storage buff_;

    Buffer() : buff_{} {}

    template <class Ex, typename... As>
    Buffer(in_place_type_t<Ex>, As&&... as_);
    template <class Ex>
    Ex& as() noexcept;
    template <class Ex>
    Ex const& as() const noexcept;
  };

  struct ThrownTag {};
  struct InSituTag {};
  struct OnHeapTag {};

  template <class T>
  using PlacementOf = std::conditional_t<
      !IsStdException<T>::value,
      ThrownTag,
      std::conditional_t<
          sizeof(T) <= sizeof(Buffer::Storage) &&
              alignof(T) <=
                  alignof(Buffer::Storage)&& noexcept(
                      T(std::declval<
                          T&&>()))&& noexcept(T(std::declval<T const&>())),
          InSituTag,
          OnHeapTag>>;

  static std::exception const* as_exception_or_null_(std::exception const& ex);
  static std::exception const* as_exception_or_null_(AnyException);

  struct ExceptionPtr {
    std::exception_ptr ptr_;

    static void copy_(exception_wrapper const* from, exception_wrapper* to);
    static void move_(exception_wrapper* from, exception_wrapper* to);
    static void delete_(exception_wrapper* that);
    [[noreturn]] static void throw_(exception_wrapper const* that);
    static std::type_info const* type_(exception_wrapper const* that);
    static std::exception const* get_exception_(exception_wrapper const* that);
    static exception_wrapper get_exception_ptr_(exception_wrapper const* that);
    static VTable const ops_;
  };

  template <class Ex>
  struct InPlace {
    static_assert(IsStdException<Ex>::value, "only deriving std::exception");
    static void copy_(exception_wrapper const* from, exception_wrapper* to);
    static void move_(exception_wrapper* from, exception_wrapper* to);
    static void delete_(exception_wrapper* that);
    [[noreturn]] static void throw_(exception_wrapper const* that);
    static std::type_info const* type_(exception_wrapper const*);
    static std::exception const* get_exception_(exception_wrapper const* that);
    static exception_wrapper get_exception_ptr_(exception_wrapper const* that);
    static constexpr VTable const ops_{
        copy_,
        move_,
        delete_,
        throw_,
        type_,
        get_exception_,
        get_exception_ptr_};
  };

  struct SharedPtr {
    struct Base {
      std::type_info const* info_;
      Base() = delete;
      explicit Base(std::type_info const& info) : info_(&info) {}
      virtual ~Base() {}
      virtual void throw_() const = 0;
      virtual std::exception const* get_exception_() const noexcept = 0;
      virtual exception_wrapper get_exception_ptr_() const noexcept = 0;
    };
    template <class Ex>
    struct Impl final : public Base {
      static_assert(IsStdException<Ex>::value, "only deriving std::exception");
      Ex ex_;
      Impl() : Base{typeid(Ex)}, ex_() {}
      // clang-format off
      template <typename... As>
      explicit Impl(As&&... as)
          : Base{typeid(Ex)}, ex_(std::forward<As>(as)...) {}
      [[noreturn]] void throw_() const override;
      // clang-format on
      std::exception const* get_exception_() const noexcept override;
      exception_wrapper get_exception_ptr_() const noexcept override;
    };
    std::shared_ptr<Base> ptr_;

    static void copy_(exception_wrapper const* from, exception_wrapper* to);
    static void move_(exception_wrapper* from, exception_wrapper* to);
    static void delete_(exception_wrapper* that);
    [[noreturn]] static void throw_(exception_wrapper const* that);
    static std::type_info const* type_(exception_wrapper const* that);
    static std::exception const* get_exception_(exception_wrapper const* that);
    static exception_wrapper get_exception_ptr_(exception_wrapper const* that);
    static VTable const ops_;
  };

  union {
    Buffer buff_{};
    ExceptionPtr eptr_;
    SharedPtr sptr_;
  };
  VTable const* vptr_{&uninit_};

  template <class Ex, typename... As>
  exception_wrapper(ThrownTag, in_place_type_t<Ex>, As&&... as);

  template <class Ex, typename... As>
  exception_wrapper(OnHeapTag, in_place_type_t<Ex>, As&&... as);

  template <class Ex, typename... As>
  exception_wrapper(InSituTag, in_place_type_t<Ex>, As&&... as);

  template <class T>
  struct IsRegularExceptionType
      : StrictConjunction<
            std::is_copy_constructible<T>,
            Negation<std::is_base_of<exception_wrapper, T>>,
            Negation<std::is_abstract<T>>> {};

  template <class This, class Fn>
  static bool with_exception_(This& this_, Fn fn_, tag_t<AnyException>);

  template <class This, class Fn, typename Ex>
  static bool with_exception_(This& this_, Fn fn_, tag_t<Ex>);

  template <class Ex, class This, class Fn>
  static bool with_exception_(This& this_, Fn fn_);

  template <class This, class... CatchFns>
  static void handle_(This& this_, char const* name, CatchFns&... fns);

 public:
  static exception_wrapper from_exception_ptr(
      std::exception_ptr const& eptr) noexcept;
  static exception_wrapper from_exception_ptr(
      std::exception_ptr&& eptr) noexcept;

  //! Default-constructs an empty `exception_wrapper`
  //! \post `type() == none()`
  exception_wrapper() noexcept {}

  //! Move-constructs an `exception_wrapper`
  //! \post `*this` contains the value of `that` prior to the move
  //! \post `that.type() == none()`
  exception_wrapper(exception_wrapper&& that) noexcept;

  //! Copy-constructs an `exception_wrapper`
  //! \post `*this` contains a copy of `that`, and `that` is unmodified
  //! \post `type() == that.type()`
  exception_wrapper(exception_wrapper const& that) noexcept;

  //! Move-assigns an `exception_wrapper`
  //! \pre `this != &that`
  //! \post `*this` contains the value of `that` prior to the move
  //! \post `that.type() == none()`
  exception_wrapper& operator=(exception_wrapper&& that) noexcept;

  //! Copy-assigns an `exception_wrapper`
  //! \post `*this` contains a copy of `that`, and `that` is unmodified
  //! \post `type() == that.type()`
  exception_wrapper& operator=(exception_wrapper const& that) noexcept;

  ~exception_wrapper();

  //! \post `!ptr || bool(*this)`
  explicit exception_wrapper(std::exception_ptr const& ptr) noexcept;
  explicit exception_wrapper(std::exception_ptr&& ptr) noexcept;

  //! \pre `ptr` holds a reference to `ex`.
  //! \post `bool(*this)`
  //! \post `type() == typeid(ex)`
  template <class Ex>
  exception_wrapper(std::exception_ptr const& ptr, Ex& ex) noexcept;
  template <class Ex>
  exception_wrapper(std::exception_ptr&& ptr, Ex& ex) noexcept;

  //! \pre `typeid(ex) == typeid(typename decay<Ex>::type)`
  //! \post `bool(*this)`
  //! \post `type() == typeid(ex)`
  //! \note Exceptions of types derived from `std::exception` can be implicitly
  //!     converted to an `exception_wrapper`.
  template <
      class Ex,
      class Ex_ = std::decay_t<Ex>,
      FOLLY_REQUIRES(
          Conjunction<IsStdException<Ex_>, IsRegularExceptionType<Ex_>>::value)>
  /* implicit */ exception_wrapper(Ex&& ex);

  //! \pre `typeid(ex) == typeid(typename decay<Ex>::type)`
  //! \post `bool(*this)`
  //! \post `type() == typeid(ex)`
  //! \note Exceptions of types not derived from `std::exception` can still be
  //!     used to construct an `exception_wrapper`, but you must specify
  //!     `folly::in_place` as the first parameter.
  template <
      class Ex,
      class Ex_ = std::decay_t<Ex>,
      FOLLY_REQUIRES(IsRegularExceptionType<Ex_>::value)>
  exception_wrapper(in_place_t, Ex&& ex);

  template <
      class Ex,
      typename... As,
      FOLLY_REQUIRES(IsRegularExceptionType<Ex>::value)>
  exception_wrapper(in_place_type_t<Ex>, As&&... as);

  //! Swaps the value of `*this` with the value of `that`
  void swap(exception_wrapper& that) noexcept;

  //! \return `true` if `*this` is holding an exception.
  explicit operator bool() const noexcept;

  //! \return `!bool(*this)`
  bool operator!() const noexcept;

  //! Make this `exception_wrapper` empty
  //! \post `!*this`
  void reset();

  //! \return `true` if this `exception_wrapper` holds a reference to an
  //!     exception that was thrown (i.e., if it was constructed with
  //!     a `std::exception_ptr`, or if `to_exception_ptr()` was called on a
  //!     (non-const) reference to `*this`).
  bool has_exception_ptr() const noexcept;

  //! \return a pointer to the `std::exception` held by `*this`, if it holds
  //!     one; otherwise, returns `nullptr`.
  //! \note This function does not mutate the `exception_wrapper` object.
  //! \note This function never causes an exception to be thrown.
  std::exception* get_exception() noexcept;
  //! \overload
  std::exception const* get_exception() const noexcept;

  //! \returns a pointer to the `Ex` held by `*this`, if it holds an object
  //!     whose type `From` permits `std::is_convertible<From*, Ex*>`;
  //!     otherwise, returns `nullptr`.
  //! \note This function does not mutate the `exception_wrapper` object.
  //! \note This function may cause an exception to be thrown and immediately
  //!     caught internally, affecting runtime performance.
  template <typename Ex>
  Ex* get_exception() noexcept;
  //! \overload
  template <typename Ex>
  Ex const* get_exception() const noexcept;

  //! \return A `std::exception_ptr` that references either the exception held
  //!     by `*this`, or a copy of same.
  //! \note This function may need to throw an exception to complete the action.
  //! \note The non-const overload of this function mutates `*this` to cache the
  //!     computed `std::exception_ptr`; that is, this function may cause
  //!     `has_exception_ptr()` to change from `false` to `true`.
  std::exception_ptr to_exception_ptr() noexcept;
  //! \overload
  std::exception_ptr to_exception_ptr() const noexcept;

  //! \return the `typeid` of an unspecified type used by
  //!     `exception_wrapper::type()` to denote an empty `exception_wrapper`.
  static std::type_info const& none() noexcept;

  //! Returns the `typeid` of the wrapped exception object. If there is no
  //!     wrapped exception object, returns `exception_wrapper::none()`.
  std::type_info const& type() const noexcept;

  //! \return If `get_exception() != nullptr`, `class_name() + ": " +
  //!     get_exception()->what()`; otherwise, `class_name()`.
  folly::fbstring what() const;

  //! \return If `!*this`, the empty string; otherwise,
  //!     the result of `type().name()` after demangling.
  folly::fbstring class_name() const;

  //! \tparam Ex The expression type to check for compatibility with.
  //! \return `true` if and only if `*this` wraps an exception that would be
  //!     caught with a `catch(Ex const&)` clause.
  //! \note If `*this` is empty, this function returns `false`.
  template <class Ex>
  bool is_compatible_with() const noexcept;

  //! Throws the wrapped expression.
  //! \pre `bool(*this)`
  [[noreturn]] void throw_exception() const;

  //! Terminates the process with the wrapped expression.
  [[noreturn]] void terminate_with() const noexcept { throw_exception(); }

  //! Throws the wrapped expression nested into another exception.
  //! \pre `bool(*this)`
  //! \param ex Exception in *this will be thrown nested into ex;
  //      see std::throw_with_nested() for details on this semantic.
  template <class Ex>
  [[noreturn]] void throw_with_nested(Ex&& ex) const;

  //! Call `fn` with the wrapped exception (if any), if `fn` can accept it.
  //! \par Example
  //! \code
  //! exception_wrapper ew{std::runtime_error("goodbye cruel world")};
  //!
  //! assert( ew.with_exception([](std::runtime_error& e){/*...*/}) );
  //!
  //! assert( !ew.with_exception([](int& e){/*...*/}) );
  //!
  //! assert( !exception_wrapper{}.with_exception([](int& e){/*...*/}) );
  //! \endcode
  //! \tparam Ex Optionally, the type of the exception that `fn` accepts.
  //! \tparam Fn The type of a monomophic function object.
  //! \param fn A function object to call with the wrapped exception
  //! \return `true` if and only if `fn` was called.
  //! \note Optionally, you may explicitly specify the type of the exception
  //!     that `fn` expects, as in
  //! \code
  //! ew.with_exception<std::runtime_error>([](auto&& e) { /*...*/; });
  //! \endcode
  //! \note The handler is not invoked with an active exception.
  //!     **Do not try to rethrow the exception with `throw;` from within your
  //!     handler -- that is, a throw expression with no operand.** This may
  //!     cause your process to terminate. (It is perfectly ok to throw from
  //!     a handler so long as you specify the exception to throw, as in
  //!     `throw e;`.)
  template <class Ex = void const, class Fn>
  bool with_exception(Fn fn);
  //! \overload
  template <class Ex = void const, class Fn>
  bool with_exception(Fn fn) const;

  //! Handle the wrapped expression as if with a series of `catch` clauses,
  //!     propagating the exception if no handler matches.
  //! \par Example
  //! \code
  //! exception_wrapper ew{std::runtime_error("goodbye cruel world")};
  //!
  //! ew.handle(
  //!   [&](std::logic_error const& e) {
  //!      LOG(DFATAL) << "ruh roh";
  //!      ew.throw_exception(); // rethrow the active exception without
  //!                           // slicing it. Will not be caught by other
  //!                           // handlers in this call.
  //!   },
  //!   [&](std::exception const& e) {
  //!      LOG(ERROR) << ew.what();
  //!   });
  //! \endcode
  //! In the above example, any exception _not_ derived from `std::exception`
  //!     will be propagated. To specify a catch-all clause, pass a lambda that
  //!     takes a C-style ellipses, as in:
  //! \code
  //! ew.handle(/*...* /, [](...) { /* handle unknown exception */ } )
  //! \endcode
  //! \pre `!*this`
  //! \tparam CatchFns A pack of unary monomorphic function object types.
  //! \param fns A pack of unary monomorphic function objects to be treated as
  //!     an ordered list of potential exception handlers.
  //! \note The handlers are not invoked with an active exception.
  //!     **Do not try to rethrow the exception with `throw;` from within your
  //!     handler -- that is, a throw expression with no operand.** This may
  //!     cause your process to terminate. (It is perfectly ok to throw from
  //!     a handler so long as you specify the exception to throw, as in
  //!     `throw e;`.)
  template <class... CatchFns>
  void handle(CatchFns... fns);
  //! \overload
  template <class... CatchFns>
  void handle(CatchFns... fns) const;
};

template <class Ex>
constexpr exception_wrapper::VTable exception_wrapper::InPlace<Ex>::ops_;

/**
 * \return An `exception_wrapper` that wraps an instance of type `Ex`
 *     that has been constructed with arguments `std::forward<As>(as)...`.
 */
template <class Ex, typename... As>
exception_wrapper make_exception_wrapper(As&&... as) {
  return exception_wrapper{in_place_type<Ex>, std::forward<As>(as)...};
}

/**
 * Inserts `ew.what()` into the ostream `sout`.
 * \return `sout`
 */
template <class Ch>
std::basic_ostream<Ch>& operator<<(
    std::basic_ostream<Ch>& sout, exception_wrapper const& ew) {
  return sout << ew.what();
}

/**
 * Swaps the value of `a` with the value of `b`.
 */
inline void swap(exception_wrapper& a, exception_wrapper& b) noexcept {
  a.swap(b);
}

// For consistency with exceptionStr() functions in ExceptionString.h
fbstring exceptionStr(exception_wrapper const& ew);

//! `try_and_catch` is a convenience for `try {} catch(...) {}`` that returns an
//! `exception_wrapper` with the thrown exception, if any.
template <typename F>
exception_wrapper try_and_catch(F&& fn) noexcept {
  auto x = [&] { return void(static_cast<F&&>(fn)()), std::exception_ptr{}; };
  return exception_wrapper{catch_exception(x, std::current_exception)};
}
} // namespace folly

#include <folly/ExceptionWrapper-inl.h>

#undef FOLLY_REQUIRES
#undef FOLLY_REQUIRES_DEF
