|
| 1 | +/* |
| 2 | + * Copyright (c) Meta Platforms, Inc. and affiliates. |
| 3 | + * |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | + * you may not use this file except in compliance with the License. |
| 6 | + * You may obtain a copy of the License at |
| 7 | + * |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | + * |
| 10 | + * Unless required by applicable law or agreed to in writing, software |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | + * See the License for the specific language governing permissions and |
| 14 | + * limitations under the License. |
| 15 | + */ |
| 16 | + |
| 17 | +#pragma once |
| 18 | + |
| 19 | +#include <folly/Portability.h> // FOLLY_HAS_RESULT |
| 20 | +#include <folly/Traits.h> |
| 21 | +#include <folly/lang/Pretty.h> |
| 22 | +#include <folly/result/rich_error_base.h> |
| 23 | + |
| 24 | +// Shared details for `rich_error.h` and `immortal_rich_error.h`. |
| 25 | + |
| 26 | +#if FOLLY_HAS_RESULT |
| 27 | + |
| 28 | +namespace folly::detail { |
| 29 | + |
| 30 | +template <typename T> |
| 31 | +struct rich_error_add_partial_message_impl : public T { |
| 32 | + using T::T; |
| 33 | + constexpr const char* partial_message() const noexcept override { |
| 34 | + return pretty_name<T>(); |
| 35 | + } |
| 36 | +}; |
| 37 | + |
| 38 | +// Non-abstract only if `T` implements `partial_message()` |
| 39 | +template <typename T> |
| 40 | +class rich_error_test_for_partial_message : T { |
| 41 | + private: |
| 42 | + rich_error_test_for_partial_message() = delete; // don't construct or inherit |
| 43 | + void only_rich_error_may_instantiate( |
| 44 | + rich_error_base::only_rich_error_may_instantiate_t) override {} |
| 45 | +}; |
| 46 | + |
| 47 | +// Adds `partial_message() only if `T` doesn't provide it. |
| 48 | +// |
| 49 | +// Design rationale: There are two reasonable ideas for having `rich_error<>` |
| 50 | +// and `immortal_rich_error<>` automatically add in a simple `partial_message` |
| 51 | +// if it's not already supplied: |
| 52 | +// |
| 53 | +// - The current one. It has the downside of making the class hierarchy |
| 54 | +// deeper for errors that don't implement their own. This in turn can slow |
| 55 | +// down `dynamic_cast` (when our RTTI-free optimizations don't apply, see |
| 56 | +// `rich_exception_ptr_bench.cpp`). However, in practice, production |
| 57 | +// errors should likely define a better message anyhow, so this is fine. |
| 58 | +// |
| 59 | +// - Have the leaf classes always define `partial_message()`, and internally |
| 60 | +// delegate via `if constexpr ()` to the base if it has one. This one is |
| 61 | +// also ok, but it adds code size for ALL error types, unconditionally. |
| 62 | +template <typename T> |
| 63 | +using rich_error_with_partial_message = conditional_t< |
| 64 | + !std::is_abstract_v<rich_error_test_for_partial_message<T>>, |
| 65 | + T, |
| 66 | + rich_error_add_partial_message_impl<T>>; |
| 67 | + |
| 68 | +// Validate a user-defined "rich error" class `Ex` while instantiating |
| 69 | +// `rich_error<Ex>` or `immortal_rich_error<Ex>`. |
| 70 | +// |
| 71 | +// Thanks to the `rich_error_base` passkey, a user should not be able to |
| 72 | +// instantiate such an `Ex` directly, so this provides pretty good guarantees |
| 73 | +// that `Ex*` can only be obtained from `...rich_error<Ex>`, and thus has |
| 74 | +// passed these validations. |
| 75 | +// |
| 76 | +// Why isn't this a concept contraining class templates? Two reasons: |
| 77 | +// - A constraint prevents referencing `rich_error<A>` from `A`. |
| 78 | +// - Forward-declarations get messier. |
| 79 | +// |
| 80 | +// Future: Most user types for `rich_error` should **NOT** inherit from |
| 81 | +// `std::exception`, since the leaf wrappers will add it to the dynamic type. |
| 82 | +// But, we cannot uniformly enforce this since there are valid uses that do |
| 83 | +// need it on the base. If the issue of "accidentally adding an |
| 84 | +// `std::exception` diamond" is both common and problematic, we could either: |
| 85 | +// - Change `rich_error` to only add it if it's not already there. |
| 86 | +// - Ban it by default and add an API to bypass that check. |
| 87 | +// |
| 88 | +// `require_sizeof` since some checks are UB with incomplete types |
| 89 | +template < |
| 90 | + typename UserEx, |
| 91 | + std::derived_from<UserEx> ActualEx, // rich_error<> or immortal...storage<> |
| 92 | + size_t = require_sizeof<UserEx>> |
| 93 | +inline constexpr bool static_assert_is_valid_rich_error_type(const ActualEx*) { |
| 94 | + static_assert( |
| 95 | + std::derived_from<UserEx, rich_error_base>, |
| 96 | + "Rich error types must derive from `rich_error_base`."); |
| 97 | + // Without the offset-0 property, it would be UB to do the RTTI-free |
| 98 | + // `rich_error_base` access, as implemented by `rich_exception_ptr`. |
| 99 | + static_assert( |
| 100 | + detail::has_offset0_base<ActualEx, rich_error_base>, |
| 101 | + "When inheriting from `rich_error_base`, that type (or its derived type) " |
| 102 | + "must be first in the inheritance list."); |
| 103 | + // Did the user type correctly use `rich_error_hints`? |
| 104 | + // |
| 105 | + // Future: If you have a strong case for NOT hinting a base error class in |
| 106 | + // its own definition, add a bypass for the "each type must hint at least |
| 107 | + // itself" rich error static assert. The bypass can simply be a |
| 108 | + // `folly_`-prefixed member type alias that points to the current class, e.g. |
| 109 | + // struct UnhintedBase : rich_error_base { |
| 110 | + // using folly_rich_error_do_not_require_hint_for = UnhintedBase; |
| 111 | + // }; |
| 112 | + using Hint = typename UserEx::folly_get_exception_hint_types; |
| 113 | + static_assert( |
| 114 | + type_list_find_v<rich_error<UserEx>, Hint> < type_list_size_v<Hint>, |
| 115 | + "A rich error user type `T` should use `rich_error_hints<T>` to avoid " |
| 116 | + "RTTI costs. If this is a base class, read the 'hints' docblock."); |
| 117 | + return true; |
| 118 | +} |
| 119 | + |
| 120 | +} // namespace folly::detail |
| 121 | + |
| 122 | +#endif // FOLLY_HAS_RESULT |
0 commit comments