Skip to content

Commit ce81fd9

Browse files
snarkmastermeta-codesync[bot]
authored andcommitted
result/rich_error.h
Summary: # Diff stack context in D89023323 Reviewed By: ispeters Differential Revision: D89023195 fbshipit-source-id: 904d0b57345cd50d6288aa2630f6f3992f64076b
1 parent 04b1f29 commit ce81fd9

4 files changed

Lines changed: 431 additions & 0 deletions

File tree

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
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
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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/lang/Exception.h>
21+
#include <folly/lang/Pretty.h>
22+
#include <folly/result/detail/rich_error_common.h>
23+
#include <folly/result/rich_error_fwd.h>
24+
25+
#if FOLLY_HAS_RESULT
26+
27+
/// See: docs/rich_error.md
28+
29+
namespace folly {
30+
31+
/// Syntax sugar for `folly::get_exception<rich_error_base>()`.
32+
///
33+
/// This retrieves the **underlying** rich error. `enriching_errors.md`
34+
/// describes this in detail, but in brief:
35+
/// - Enrichment only works with `folly/result/` error containers.
36+
/// - If the underlying error is not a `rich_error_base`, you will get back
37+
/// null, **even if** it was later enriched.
38+
/// - Furthermore, if you used `enrich_non_value()` or another enriching
39+
/// wrapper, `->partial_message()` will NOT give you the enrichment
40+
/// message, but the rather the one from the base rich error.
41+
///
42+
/// Very rarely, you may want `get_outer_exception` to access the enrichment
43+
/// wrapper object itself.
44+
inline constexpr get_exception_fn<rich_error_base> get_rich_error{};
45+
46+
// See: docs/rich_error.md
47+
template <typename UserBase> // must derive from `rich_error_base`
48+
class rich_error final
49+
: public detail::rich_error_with_partial_message<UserBase>,
50+
public std::exception {
51+
private:
52+
using Base = detail::rich_error_with_partial_message<UserBase>;
53+
54+
void only_rich_error_may_instantiate(
55+
rich_error_base::only_rich_error_may_instantiate_t) override {}
56+
57+
public:
58+
const char* what() const noexcept override {
59+
// These assertions are under `what()` since this virtual member **must** be
60+
// instantiated together with the class template. Has a manual test.
61+
detail::static_assert_is_valid_rich_error_type<UserBase>(this);
62+
auto msg = Base::partial_message();
63+
return msg[0] ? msg : pretty_name<rich_error>();
64+
}
65+
66+
rich_error() = default;
67+
68+
// Delegate non-standard ctors to the user-defined `Base`
69+
template <typename T, typename... Ts>
70+
requires(
71+
!std::is_same_v<rich_error, std::remove_cvref_t<T>> &&
72+
!std::is_same_v<detail::immortal_rich_error_private_t, T>)
73+
explicit rich_error(T&& t, Ts&&... ts)
74+
: Base{static_cast<T&&>(t), static_cast<Ts&&>(ts)...} {}
75+
76+
rich_error(detail::immortal_rich_error_private_t, const Base& that)
77+
: Base{that} {}
78+
79+
// This isn't `Base` because `immortal_rich_error` will wrap it again.
80+
using folly_detail_base_of_rich_error = UserBase;
81+
// NB: This hint is a no-op, unless `UserBase` declares one, in which case
82+
// this just prevents the use of the wrong hint.
83+
using folly_get_exception_hint_types = tag_t<rich_error>;
84+
};
85+
86+
} // namespace folly
87+
88+
#endif // FOLLY_HAS_RESULT

third-party/folly/src/folly/result/test/common.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,41 @@
2020
#include <string>
2121

2222
#include <folly/Benchmark.h>
23+
#include <folly/portability/GMock.h>
2324
#include <folly/portability/GTest.h>
25+
#include <folly/result/rich_exception_ptr.h>
2426

2527
namespace folly {
2628

29+
constexpr void test(bool cond) {
30+
if (!cond) {
31+
// NOLINTNEXTLINE(facebook-hte-ThrowNonStdExceptionIssue)
32+
throw "test failed";
33+
}
34+
}
35+
36+
namespace detail { // Some tests are defined in `detail`...
37+
using folly::test;
38+
} // namespace detail
39+
40+
void checkFormat(const auto& err, const std::string& re) {
41+
EXPECT_THAT(fmt::format("{}", err), ::testing::MatchesRegex(re));
42+
std::stringstream ss;
43+
ss << err;
44+
EXPECT_THAT(ss.str(), ::testing::MatchesRegex(re));
45+
}
46+
47+
template <typename... Queries>
48+
void checkFormatViaGet(const auto& container, const std::string& re) {
49+
(checkFormat(get_exception<Queries>(container), re), ...);
50+
}
51+
52+
template <typename... Queries>
53+
void checkFormatOfErrAndRep(const auto& err, const std::string& re) {
54+
checkFormat(err, re);
55+
checkFormatViaGet<Queries...>(rich_exception_ptr{err}, re);
56+
}
57+
2758
// Helper to run benchmarks as a smoke test with minimal iterations.
2859
// A "benchmarks don't crash" test is meaningful (1) since the benchmarks
2960
// actually run some basic assertions, (2) CI will run this under ASAN.

0 commit comments

Comments
 (0)