Skip to content

[libc++][hardening] Categorize assertions that produce incorrect results #77183

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion libcxx/include/__algorithm/clamp.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ clamp(_LIBCPP_LIFETIMEBOUND const _Tp& __v,
_LIBCPP_LIFETIMEBOUND const _Tp& __lo,
_LIBCPP_LIFETIMEBOUND const _Tp& __hi,
_Compare __comp) {
_LIBCPP_ASSERT_UNCATEGORIZED(!__comp(__hi, __lo), "Bad bounds passed to std::clamp");
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(!__comp(__hi, __lo), "Bad bounds passed to std::clamp");
return __comp(__v, __lo) ? __lo : __comp(__hi, __v) ? __hi : __v;
}

Expand Down
5 changes: 3 additions & 2 deletions libcxx/include/__algorithm/ranges_clamp.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ struct __fn {
indirect_strict_weak_order<projected<const _Type*, _Proj>> _Comp = ranges::less>
_LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr const _Type& operator()(
const _Type& __value, const _Type& __low, const _Type& __high, _Comp __comp = {}, _Proj __proj = {}) const {
_LIBCPP_ASSERT_UNCATEGORIZED(!bool(std::invoke(__comp, std::invoke(__proj, __high), std::invoke(__proj, __low))),
"Bad bounds passed to std::ranges::clamp");
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
!bool(std::invoke(__comp, std::invoke(__proj, __high), std::invoke(__proj, __low))),
"Bad bounds passed to std::ranges::clamp");

auto&& __projected = std::invoke(__proj, __value);
if (std::invoke(__comp, std::forward<decltype(__projected)>(__projected), std::invoke(__proj, __low)))
Expand Down
2 changes: 1 addition & 1 deletion libcxx/include/__bit/bit_ceil.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr _Tp __bit_ceil(_Tp __t) no
if (__t < 2)
return 1;
const unsigned __n = numeric_limits<_Tp>::digits - std::__countl_zero((_Tp)(__t - 1u));
_LIBCPP_ASSERT_UNCATEGORIZED(__n != numeric_limits<_Tp>::digits, "Bad input to bit_ceil");
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(__n != numeric_limits<_Tp>::digits, "Bad input to bit_ceil");

if constexpr (sizeof(_Tp) >= sizeof(unsigned))
return _Tp{1} << __n;
Expand Down
13 changes: 12 additions & 1 deletion libcxx/include/__config
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,13 @@
// - `_LIBCPP_ASSERT_COMPATIBLE_ALLOCATOR` -- checks any operations that exchange nodes between containers to make sure
// the containers have compatible allocators.
//
// - `_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN` -- checks that the given argument is within the domain of valid arguments
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I spent a lot of time thinking how to name this category and I can't say I'm completely satisfied with the result. The criteria I'm using is roughly "The given arguments make it impossible for us to produce the correct result; returning a nonsensical value doesn't cause any immediate runtime issues within our library, but is likely to lead to a bug, potentially a serious one, in the user code once they access the value".

Intuitively, it does seem like a well-defined concept to me. I think the difficulty I experienced with the naming is due to the fact that our naming scheme mostly describes the expectation for the condition, whereas my intuitive definition focuses on the outcomes of violating the condition. It's not an issue when there is a direct one-to-one correspondence between the two, which I think is the case for most other category names (e.g. valid-element-access arguably describes both the cause and the effect because there is a direct correspondence between them). This, however, is more of a many-to-one relationship. An obvious name would be valid-argument, but since we're mostly checking user-provided arguments, this really applies to every category we have -- for example, valid-element-access is ultimately also a check for a valid argument. I still think that a naming scheme based on the outcomes being prevented would be easier to work with, but it goes against the fact that asserting asserts the successful case, not the failing case.

While argument-within-domain is not that different from invalid-argument, it seems a little more specific to me and also seems to apply to the existing usages pretty well (it is, of course, inspired by std::domain_error). I thought about something like valid-argument-fallback or nonspecific-valid-argument but it doesn't really help better express the part I consider the more important one, giving the user back an incorrect result (or object, which can be seen as a result of running a constructor).

Another aspect to consider here is how easy it would be for a library developer, especially someone who is new to libc++, to use the new category in the intended way.

Copy link
Contributor

@Zingam Zingam Jan 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have a guide about the different categories? Even a link to the definitions would give a clearer context. Would that make sense? -> https://libcxx.llvm.org/Hardening.html

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, absolutely. I want to finish the actual classification before writing it down -- I think it would be easier to describe it and generalize once the design is finalized. There are only a few dozen unclassified assertions left (not counting in-flight patches like this one), so I hope things settle down pretty soon on that front.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the writeup, this is really useful. Naming this is indeed really tricky. Some suggestions:

  • _LIBCPP_ASSERT_CANT_MEET_POSTCONDITIONS
  • _LIBCPP_ASSERT_CONSEQUENCES_UNKNOWN
  • _LIBCPP_ASSERT_CONSEQUENCES_OUTSIDE_LIBRARY

None of those is really pretty, but that's some food for thought. I think the unifying theme here is that the assertion is about times when you can't meet your postconditions, not about a "fallback category". The documentation should probably be updated not to give the impression that this is a fallback category.

// for the function. Violating this typically produces an incorrect result (e.g. the clamp algorithm returns the
// original value without clamping it due to incorrect functors) or puts an object into an invalid state (e.g.
// a string view where only a subset of elements is possible to access). This category is for assertions violating
// which doesn't cause any immediate issues in the library -- whatever the consequences are, they will happen in the
// user code.
//
// - `_LIBCPP_ASSERT_PEDANTIC` -- checks prerequisites which are imposed by the Standard, but violating which happens to
// be benign in our implementation.
//
Expand Down Expand Up @@ -338,6 +345,7 @@ _LIBCPP_HARDENING_MODE_DEBUG
// Overlapping ranges will make algorithms produce incorrect results but don't directly lead to a security
// vulnerability.
# define _LIBCPP_ASSERT_NON_OVERLAPPING_RANGES(expression, message) _LIBCPP_ASSUME(expression)
# define _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(expression, message) _LIBCPP_ASSUME(expression)
# define _LIBCPP_ASSERT_COMPATIBLE_ALLOCATOR(expression, message) _LIBCPP_ASSUME(expression)
# define _LIBCPP_ASSERT_PEDANTIC(expression, message) _LIBCPP_ASSUME(expression)
# define _LIBCPP_ASSERT_INTERNAL(expression, message) _LIBCPP_ASSUME(expression)
Expand All @@ -353,8 +361,9 @@ _LIBCPP_HARDENING_MODE_DEBUG
# define _LIBCPP_ASSERT_NON_NULL(expression, message) _LIBCPP_ASSERT(expression, message)
# define _LIBCPP_ASSERT_NON_OVERLAPPING_RANGES(expression, message) _LIBCPP_ASSERT(expression, message)
# define _LIBCPP_ASSERT_COMPATIBLE_ALLOCATOR(expression, message) _LIBCPP_ASSERT(expression, message)
# define _LIBCPP_ASSERT_UNCATEGORIZED(expression, message) _LIBCPP_ASSERT(expression, message)
# define _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(expression, message) _LIBCPP_ASSERT(expression, message)
# define _LIBCPP_ASSERT_PEDANTIC(expression, message) _LIBCPP_ASSERT(expression, message)
# define _LIBCPP_ASSERT_UNCATEGORIZED(expression, message) _LIBCPP_ASSERT(expression, message)
// Disabled checks.
# define _LIBCPP_ASSERT_INTERNAL(expression, message) _LIBCPP_ASSUME(expression)

Expand All @@ -368,6 +377,7 @@ _LIBCPP_HARDENING_MODE_DEBUG
# define _LIBCPP_ASSERT_NON_NULL(expression, message) _LIBCPP_ASSERT(expression, message)
# define _LIBCPP_ASSERT_NON_OVERLAPPING_RANGES(expression, message) _LIBCPP_ASSERT(expression, message)
# define _LIBCPP_ASSERT_COMPATIBLE_ALLOCATOR(expression, message) _LIBCPP_ASSERT(expression, message)
# define _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(expression, message) _LIBCPP_ASSERT(expression, message)
# define _LIBCPP_ASSERT_PEDANTIC(expression, message) _LIBCPP_ASSERT(expression, message)
# define _LIBCPP_ASSERT_INTERNAL(expression, message) _LIBCPP_ASSERT(expression, message)
# define _LIBCPP_ASSERT_UNCATEGORIZED(expression, message) _LIBCPP_ASSERT(expression, message)
Expand All @@ -382,6 +392,7 @@ _LIBCPP_HARDENING_MODE_DEBUG
# define _LIBCPP_ASSERT_NON_NULL(expression, message) _LIBCPP_ASSUME(expression)
# define _LIBCPP_ASSERT_NON_OVERLAPPING_RANGES(expression, message) _LIBCPP_ASSUME(expression)
# define _LIBCPP_ASSERT_COMPATIBLE_ALLOCATOR(expression, message) _LIBCPP_ASSUME(expression)
# define _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(expression, message) _LIBCPP_ASSUME(expression)
# define _LIBCPP_ASSERT_PEDANTIC(expression, message) _LIBCPP_ASSUME(expression)
# define _LIBCPP_ASSERT_INTERNAL(expression, message) _LIBCPP_ASSUME(expression)
# define _LIBCPP_ASSERT_UNCATEGORIZED(expression, message) _LIBCPP_ASSUME(expression)
Expand Down
2 changes: 1 addition & 1 deletion libcxx/include/__hash_table
Original file line number Diff line number Diff line change
Expand Up @@ -861,7 +861,7 @@ public:

template <class _Key>
_LIBCPP_HIDE_FROM_ABI size_type bucket(const _Key& __k) const {
_LIBCPP_ASSERT_UNCATEGORIZED(
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
bucket_count() > 0, "unordered container::bucket(key) called when bucket_count() == 0");
return std::__constrain_hash(hash_function()(__k), bucket_count());
}
Expand Down
3 changes: 2 additions & 1 deletion libcxx/include/__memory/assume_aligned.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ _LIBCPP_NODISCARD _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 _Tp* __ass
if (__libcpp_is_constant_evaluated()) {
return __ptr;
} else {
_LIBCPP_ASSERT_UNCATEGORIZED(reinterpret_cast<uintptr_t>(__ptr) % _Np == 0, "Alignment assumption is violated");
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
reinterpret_cast<uintptr_t>(__ptr) % _Np == 0, "Alignment assumption is violated");
return static_cast<_Tp*>(__builtin_assume_aligned(__ptr, _Np));
}
}
Expand Down
2 changes: 1 addition & 1 deletion libcxx/include/__numeric/gcd_lcm.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ _LIBCPP_CONSTEXPR _LIBCPP_HIDE_FROM_ABI common_type_t<_Tp, _Up> lcm(_Tp __m, _Up
using _Rp = common_type_t<_Tp, _Up>;
_Rp __val1 = __ct_abs<_Rp, _Tp>()(__m) / std::gcd(__m, __n);
_Rp __val2 = __ct_abs<_Rp, _Up>()(__n);
_LIBCPP_ASSERT_UNCATEGORIZED((numeric_limits<_Rp>::max() / __val1 > __val2), "Overflow in lcm");
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN((numeric_limits<_Rp>::max() / __val1 > __val2), "Overflow in lcm");
return __val1 * __val2;
}

Expand Down
12 changes: 6 additions & 6 deletions libcxx/include/barrier
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ public:
__completion_(std::move(__completion)),
__phase_(0) {}
[[__nodiscard__]] _LIBCPP_AVAILABILITY_SYNC _LIBCPP_HIDE_FROM_ABI arrival_token arrive(ptrdiff_t __update) {
_LIBCPP_ASSERT_UNCATEGORIZED(
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
__update <= __expected_, "update is greater than the expected count for the current barrier phase");

auto const __old_phase = __phase_.load(memory_order_relaxed);
Expand Down Expand Up @@ -187,7 +187,7 @@ public:
auto const __result = __arrived.fetch_sub(update, memory_order_acq_rel) - update;
auto const new_expected = __expected.load(memory_order_relaxed);

_LIBCPP_ASSERT_UNCATEGORIZED(
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
update <= new_expected, "update is greater than the expected count for the current barrier phase");

if (0 == __result) {
Expand Down Expand Up @@ -232,7 +232,7 @@ public:
auto const __inc = __arrived_unit * update;
auto const __old = __phase_arrived_expected.fetch_add(__inc, memory_order_acq_rel);

_LIBCPP_ASSERT_UNCATEGORIZED(
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
update <= __old, "update is greater than the expected count for the current barrier phase");

if ((__old ^ (__old + __inc)) & __phase_bit) {
Expand Down Expand Up @@ -268,10 +268,10 @@ public:
_LIBCPP_AVAILABILITY_SYNC _LIBCPP_HIDE_FROM_ABI explicit barrier(
ptrdiff_t __count, _CompletionF __completion = _CompletionF())
: __b_(__count, std::move(__completion)) {
_LIBCPP_ASSERT_UNCATEGORIZED(
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
__count >= 0,
"barrier::barrier(ptrdiff_t, CompletionFunction): barrier cannot be initialized with a negative value");
_LIBCPP_ASSERT_UNCATEGORIZED(
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
__count <= max(),
"barrier::barrier(ptrdiff_t, CompletionFunction): barrier cannot be initialized with "
"a value greater than max()");
Expand All @@ -281,7 +281,7 @@ public:
barrier& operator=(barrier const&) = delete;

[[__nodiscard__]] _LIBCPP_AVAILABILITY_SYNC _LIBCPP_HIDE_FROM_ABI arrival_token arrive(ptrdiff_t __update = 1) {
_LIBCPP_ASSERT_UNCATEGORIZED(__update > 0, "barrier:arrive must be called with a value greater than 0");
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(__update > 0, "barrier:arrive must be called with a value greater than 0");
return __b_.arrive(__update);
}
_LIBCPP_AVAILABILITY_SYNC _LIBCPP_HIDE_FROM_ABI void wait(arrival_token&& __phase) const {
Expand Down
10 changes: 5 additions & 5 deletions libcxx/include/latch
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,11 @@ public:
static _LIBCPP_HIDE_FROM_ABI constexpr ptrdiff_t max() noexcept { return numeric_limits<ptrdiff_t>::max(); }

inline _LIBCPP_HIDE_FROM_ABI constexpr explicit latch(ptrdiff_t __expected) : __a_(__expected) {
_LIBCPP_ASSERT_UNCATEGORIZED(
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
__expected >= 0,
"latch::latch(ptrdiff_t): latch cannot be "
"initialized with a negative value");
_LIBCPP_ASSERT_UNCATEGORIZED(
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
__expected <= max(),
"latch::latch(ptrdiff_t): latch cannot be "
"initialized with a value greater than max()");
Expand All @@ -88,9 +88,9 @@ public:
latch& operator=(const latch&) = delete;

inline _LIBCPP_AVAILABILITY_SYNC _LIBCPP_HIDE_FROM_ABI void count_down(ptrdiff_t __update = 1) {
_LIBCPP_ASSERT_UNCATEGORIZED(__update >= 0, "latch::count_down called with a negative value");
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(__update >= 0, "latch::count_down called with a negative value");
auto const __old = __a_.fetch_sub(__update, memory_order_release);
_LIBCPP_ASSERT_UNCATEGORIZED(
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
__update <= __old,
"latch::count_down called with a value greater "
"than the internal counter");
Expand All @@ -102,7 +102,7 @@ public:
__cxx_atomic_wait(&__a_.__a_, [this]() -> bool { return try_wait(); });
}
inline _LIBCPP_AVAILABILITY_SYNC _LIBCPP_HIDE_FROM_ABI void arrive_and_wait(ptrdiff_t __update = 1) {
_LIBCPP_ASSERT_UNCATEGORIZED(__update >= 0, "latch::arrive_and_wait called with a negative value");
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(__update >= 0, "latch::arrive_and_wait called with a negative value");
// other preconditions on __update are checked in count_down()

count_down(__update);
Expand Down
8 changes: 4 additions & 4 deletions libcxx/include/semaphore
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public:
_LIBCPP_HIDE_FROM_ABI constexpr explicit __atomic_semaphore_base(ptrdiff_t __count) : __a_(__count) {}
_LIBCPP_AVAILABILITY_SYNC _LIBCPP_HIDE_FROM_ABI void release(ptrdiff_t __update = 1) {
auto __old = __a_.fetch_add(__update, memory_order_release);
_LIBCPP_ASSERT_UNCATEGORIZED(
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
__update <= _LIBCPP_SEMAPHORE_MAX - __old, "update is greater than the expected value");

if (__old > 0) {
Expand Down Expand Up @@ -138,11 +138,11 @@ public:
static constexpr ptrdiff_t max() noexcept { return __least_max_value; }

_LIBCPP_HIDE_FROM_ABI constexpr explicit counting_semaphore(ptrdiff_t __count) : __semaphore_(__count) {
_LIBCPP_ASSERT_UNCATEGORIZED(
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
__count >= 0,
"counting_semaphore::counting_semaphore(ptrdiff_t): counting_semaphore cannot be "
"initialized with a negative value");
_LIBCPP_ASSERT_UNCATEGORIZED(
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
__count <= max(),
"counting_semaphore::counting_semaphore(ptrdiff_t): counting_semaphore cannot be "
"initialized with a value greater than max()");
Expand All @@ -153,7 +153,7 @@ public:
counting_semaphore& operator=(const counting_semaphore&) = delete;

_LIBCPP_AVAILABILITY_SYNC _LIBCPP_HIDE_FROM_ABI void release(ptrdiff_t __update = 1) {
_LIBCPP_ASSERT_UNCATEGORIZED(__update >= 0, "counting_semaphore:release called with a negative value");
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(__update >= 0, "counting_semaphore:release called with a negative value");
__semaphore_.release(__update);
}
_LIBCPP_AVAILABILITY_SYNC _LIBCPP_HIDE_FROM_ABI void acquire() { __semaphore_.acquire(); }
Expand Down
7 changes: 5 additions & 2 deletions libcxx/include/string_view
Original file line number Diff line number Diff line change
Expand Up @@ -307,8 +307,11 @@ public:
: __data_(__s),
__size_(__len) {
#if _LIBCPP_STD_VER >= 14
_LIBCPP_ASSERT_UNCATEGORIZED(__len <= static_cast<size_type>(numeric_limits<difference_type>::max()),
"string_view::string_view(_CharT *, size_t): length does not fit in difference_type");
// This will result in creating an invalid `string_view` object -- some calculations involving `size` would
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: while a few of these involve integer overflow, I don't think it's a good criterion for classification. To me, it seems very implementation-centric -- the fact that there is an overflow involved doesn't say much about the actual issue that will happen, which can range from something very minor or even benign to compromizing the memory safety of the program.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this one has more serious consequences than the categorization and comment suggest. The size parameter determines the bounds of the string. Every byte from __s[0] up to __s[__len - 1] is fair game for the program to access. E.g. the bounds checks in operator[] assume the length is correct.

It is not possible for a length over PTRDIFF_MAX to be the correct bounds for __s. No allocation can exceed that amount. Moreover, it's not hard for a program to accidentally construct such a string_view by accidentally underflowing a computation and passing a negative number. That negative number will, in turn, be read as SIZE_MAX.

See #61100 for context.

// overflow, making it effectively truncated.
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
__len <= static_cast<size_type>(numeric_limits<difference_type>::max()),
"string_view::string_view(_CharT *, size_t): length does not fit in difference_type");
_LIBCPP_ASSERT_NON_NULL(
__len == 0 || __s != nullptr, "string_view::string_view(_CharT *, size_t): received nullptr");
#endif
Expand Down
5 changes: 2 additions & 3 deletions libcxx/src/filesystem/operations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -608,10 +608,9 @@ void __permissions(const path& p, perms prms, perm_options opts, error_code* ec)
const bool resolve_symlinks = !has_opt(perm_options::nofollow);
const bool add_perms = has_opt(perm_options::add);
const bool remove_perms = has_opt(perm_options::remove);
_LIBCPP_ASSERT_UNCATEGORIZED(
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
(add_perms + remove_perms + has_opt(perm_options::replace)) == 1,
"One and only one of the perm_options constants replace, add, or remove "
"is present in opts");
"One and only one of the perm_options constants 'replace', 'add', or 'remove' must be present in opts");

bool set_sym_perms = false;
prms &= perms::mask;
Expand Down
2 changes: 1 addition & 1 deletion libcxx/src/include/to_chars_floating_point.h
Original file line number Diff line number Diff line change
Expand Up @@ -994,7 +994,7 @@ to_chars_result _Floating_to_chars(
if constexpr (_Overload == _Floating_to_chars_overload::_Plain) {
_LIBCPP_ASSERT_INTERNAL(_Fmt == chars_format{}, ""); // plain overload must pass chars_format{} internally
} else {
_LIBCPP_ASSERT_UNCATEGORIZED(_Fmt == chars_format::general || _Fmt == chars_format::scientific
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(_Fmt == chars_format::general || _Fmt == chars_format::scientific
|| _Fmt == chars_format::fixed || _Fmt == chars_format::hex,
"invalid format in to_chars()");
}
Expand Down