Standard library hardening (original) (raw)

Introduction

This paper proposes introducing _standard library hardening_into the C++ Standard. Hardening allows turning some instances of undefined behavior in the standard library into a contract violation. This proposal is based on our experience implementing hardening in libc++and deploying it widely.

Revision history

Motivation

There has been significantly increased attention to safety and security in C++ over the last few years, as exemplified by the well-known White House report and numerous recent security-related proposals.

While it is important to explore ways to make new code safer, we believe that the highest priority to deliver immediate real-world value should be to make existing code safer with minimal or no effort on behalf of users. Indeed, the amount of existing security-critical C++ code is so large that rewriting it or modifying it is both economically unviable and dangerous given the risk of introducing new issues.

There have been a few proposals accepted recently that eliminate some cases of undefined behavior in the core language. The standard library also contains many instances of undefined behavior, some of which is a direct source of security vulnerabilities; addressing those is often trivial, can be done with low overhead and almost no work on behalf of users.

In fact, at the moment all three major library implementations have some notion of a hardened or debug mode. This clearly shows interest, both from users and from implementers, in having a safer mode for the standard library. However, we believe these efforts would be vastly more useful if they were standardized and provided portable, cross-platform guarantees to users; as it stands, implementations differ in levels of coverage, performance guarantees and ways to enable the safer mode.

Finally, leaving security of the library to be a pure vendor extension fails to position ISO C++ as providing a credible solution for code bases with formal security requirements. We believe that formally requiring the basic safety guarantees that most implementations already provide in one way or another could make a significant difference from the point of view of anyone writing or following safety and security coding standards and guidelines.

Deployment experience

All three major implementations provide vendor-specific ways of enabling library assertions as proposed in this paper, today.

Overall, standard library hardening has been a huge success, in fact we never expected so much success. The reception has been overwhelmingly positive and while the quality of implementation will never be perfect, we are working hard to expand the scope of hardening in libc++, to improve its performance and the user experience.

Design overview

At a high level, this proposal consists of two parts:

There are a few important aspects to the proposed design:

To reiterate the last point, an important design principle is that hardening needs to be lightweight enough for production use by a wide variety of real-world programs. In our experience in libc++, a small set of checks that is widely used delivers far more value than a more extensive set of checks that is only enabled by select few users. Thankfully, many of the most valuable checks, such as checking for out-of-bounds access in standard containers, also happen to be relatively cheap.

Hardened preconditions

To specify hardening in the Standard, this proposal introduces the notion of a hardened precondition. A hardened precondition is a precondition that results in a contract violation in a hardened implementation. Adding hardening to the library largely consists of turning some of the existing preconditions into_hardened preconditions_ in the specification. For example:

(23.7.2.2.6) Element access [span.elem]

constexpr reference operator[](size_type idx) const;

1 Hardened Ppreconditions:idx < size()is true.

In the initial proposal, we decide to focus on hardened preconditions that prevent out-of-bounds memory access, i.e., compromise the memory safety of the program. These are some of the most valuable for the user since they help prevent potential security vulnerabilities; many of them are also relatively cheap to implement. More hardened preconditions can potentially be added in the future, but the intent is for their number to be limited to keep hardening viable for production use. Specifically, the proposal is to add hardened preconditions to:

In our experience, hardening all of these operations is trivial to implement and provides significant security value.

Enabling hardening

Much like a freestanding implementation, the way to request a_hardened_ implementation is left for the implementation to define. For example, similarly to -ffreestanding, we expect that most toolchains would provide a compiler flag like-fhardened, but other alternatives like a -D_LIBCPP_HARDENING_MODE=<mode>macro would also be conforming. If this proposal gets accepted, we expect implementations to converge on a portable mechanism. Other details like whether hardened implementations and non-hardened implementations can be mixed in different translation units are intentionally left unspecified, to avoid overconstraining the implementations.

Relationship to Contracts, Profiles, and Erroneous Behavior

Contracts

Contracts is a new (in-progress) language feature that allows expressing preconditions and much more, with a lot of flexibility on what happens when an assertion is not satisfied. In the latest revision of this paper, we decided to base hardened preconditions on contract violations for several reasons:

It is useful to note that we don’t require implementations to actually implement these checks as contract assertions. Implementations are free to implement precondition checking however they see fit (e.g. a macro), however they are required to employ the contract violation handling mechanism when a precondition is not satisfied.

Note that if Contracts were to not be pursued anymore, this feature could easily be reworded in terms of a guaranteed termination in an implementation-defined way, or using Erroneous Behavior. We are not strongly attached to the exact mechanism used to implement this, but we find that Contracts is a nearly perfect fit.

Profiles

The various Profiles proposals introduce a framework to specify sets of safety guarantees (such as a type safety profile or an invalidation profile). If profiles become a part of the Standard in the future, hardening can most likely be formulated as an additional profile; this would formalize how hardening is turned on and off.

However, we feel strongly that a hardening mode as specified in this paper standardizes existing practice and delivers value today without waiting for a larger and still experimental language feature.

Erroneous Behavior

While Erroneous Behavior is a way to clearly mark some code as incorrect in the specification, it does not clearly specify_what_ should happen in case of EB. For example, a conforming behavior for vector::operator[]being called out-of-bounds under EB would be to return the last element of the vector instead. While that is well-defined behavior, we feel that it is not especially useful behavior and that is certainly not what our users are looking for. In contrast, the Contracts facilities provide a well-defined and flexible framework to handle this.

FAQ

Notable omissions

erase member functions

Most containers have an erasemember function that takes an iterator into the container and erases the element it points to. These functions have an implicit precondition that comes from the fact that the iterator argument is defined to denote “a valid dereferenceable constant iterator to [the container]”.

Unfortunately, this precondition can only be checked for some containers and not others. For a contiguous container, the iterator can be lowered to a pointer; the pointer can then be tested to check whether it points within the bounds of the container’s underlying storage (usingstd::lessand the related function object classes). There is, however, no feasible way to do a similar check for a non-contiguous container (without requiring an ABI break and significant overhead). In this paper, we would prefer to focus on preconditions that can be checked in a clear and straightforward way across all applicable classes; less obvious cases would be better served by a separate dedicated paper.

Associative containers

Most preconditions of associative containers do not map clearly to spatial safety; moreover, associative containers have a significantly different interface and implementation compared to the containers covered by this paper. Since specifying preconditions that are relevant for memory safety in the associative containers is less straightforward than for sequence containers and since we don’t have sufficient implementation experience hardening those, we prefer to explore hardening for associative containers in a dedicated paper.

Algorithms

All algorithms (in fact, all library functions) that operate on ranges have an implicit precondition that the ranges are valid. Unfortunately, that precondition can only be checked partially and for contiguous iterators, where it is trivial to check that the end of the range is reachable from the beginning. In the general case, it is also impossible to check that both iterators point into the same container, which doesn’t appear to be covered by the “valid range” requirement but is required in practice for these algorithms to do something sensible. Moreover, since “valid range” is a blanket requirement effectively covering all library functions, we have limited implementation experience with hardening algorithms, our current approach having focused on containers and container-like classes so far.

Some algorithms taking one range as an input and another as an output have a precondition that the two ranges do not overlap. This precondition also cannot be checked in the general case (for example, the iterators might be input iterators, meaning they cannot be “rewinded” after being incremented which would be required to perform a check).

For all of these reasons, we decided to leave hardening of the algorithms to a dedicated paper.

mdspan extents and layout classes

Classes representing the extents and the layout mapping of anmdspan(extents,layout_left,layout_right,layout_stride) contain several preconditions that can be considered for hardening. However, these are large, complicated classes with non-trivial interactions with themdspan class itself; it would require dedicated effort and research to make sure all relevant cases are hardened and there is no redundancy leading to unnecessary overhead. As such, we would prefer to keep the initial proposal focused on preconditions that are trivial to check and reason about and defermdspan to a separate paper.

valarray

Similarly to mdspan,valarray is non-trivial to harden properly. Its API is effectively spread across several helper classes that interact in non-trivial ways. After surverying our implementation, we believe that our implementation experience withvalarray hardening is not sufficient at this time to include more than basic hardening of operator[]in this paper. We would prefer to leave a thorough exploration ofvalarray to a dedicated paper.

Proposed wording

The proposed wording changes are relative to the C++26 working draftN5001 with P2900R14 already applied.

Introduce hardened preconditions

Add a new paragraph to 4.1[intro.compliance]after paragraph 7 as indicated:

7 Two kinds of implementations are defined:An implementation is either a_hosted implementation_ andor a freestanding implementation. A freestanding implementation is one in which execution may take place without the benefit of an operating system. A hosted implementation supports all the facilities described in this document, while a freestanding implementation supports the entire C++ language described in [lex]through [cpp]and the subset of the library facilities described in [compliance].

8It is implementation-defined whether the implementation is a_hardened implementation_. If it is a hardened implementation, violating a hardened precondition results in a contract violation([structure.specifications]).

Add a new element to 16.3.2.4[structure.specifications]after element 3.3 as indicated:

(3.3) Preconditions: the conditions that the function assumes to hold whenever it is called; violation of any preconditions results in undefined behavior.

(3.4) Hardened preconditions: conditions that the function assumes to hold whenever it is called.

Add to 16.3.2.4[structure.specifications]in paragraph 4:

Whenever the Effects element specifies that the semantics of some function F are Equivalent to some code sequence, then the various elements are interpreted as follows. […] Next, the semantics of the code sequence are determined by the Constraints, Mandates, Preconditions,Hardened preconditions, Effects,Synchronization, Postconditions, Returns,Throws, Complexity, Remarks, and Error conditions specified for the function invocations contained in the code sequence. […]

span

span::operator[]

Modify 23.7.2.2.6[span.elem] paragraph 1 as indicated:

constexpr reference operator[](size_type idx) const;

1 Hardened Ppreconditions:idx < size()is true.

2 Returns: *(data() + idx).

3 Throws: Nothing.

span::front

Modify 23.7.2.2.6[span.elem] paragraph 6 as indicated:

constexpr reference front() const;

6 Hardened Ppreconditions:empty() isfalse.

7 Returns: *data().

8 Throws: Nothing.

span::back

Modify 23.7.2.2.6[span.elem] paragraph 9 as indicated:

constexpr reference back() const;

9 Hardened Ppreconditions:empty() isfalse.

10 Returns: *(data() + (size() - 1)).

11 Throws: Nothing.

span::first

Modify 23.7.2.2.4[span.sub] paragraph 2 as indicated:

template<size_t Count> constexpr span<element_type, Count> first() const;

1 Mandates: Count <= Extentis true.

2 Hardened Ppreconditions:Count <= size()is true.

3 Effects: Equivalent to: return R{data(), Count};where R is the return type.

Modify 23.7.2.2.4[span.sub] paragraph 11 as indicated:

constexpr span<element_type, dynamic_extent> first(size_type count) const;

11 Hardened Ppreconditions:count <= size()is true.

12 Effects: Equivalent to: return {data(), count};

span::last

Modify 23.7.2.2.4[span.sub] paragraph 5 as indicated:

template<size_t Count> constexpr span<element_type, Count> last() const;

4 Mandates: Count <= Extentis true.

5 Hardened Ppreconditions:Count <= size()is true.

6 Effects: Equivalent to: return R{data() + (size() - Count), Count};where R is the return type.

Modify 23.7.2.2.4[span.sub] paragraph 13 as indicated:

constexpr span<element_type, dynamic_extent> last(size_type count) const;

13 Hardened Ppreconditions:count <= size()is true.

14 Effects: Equivalent to: return {data() + (size() - count), count};

span::subspan

Modify 23.7.2.2.4[span.sub] paragraph 8 as indicated:

template<size_t Offset, size_t Count = dynamic_extent>
 constexpr span<element_type, see below> subspan() const;

7 Mandates:

 Offset <= Extent && (Count == dynamic_extent || Count <= Extent - Offset)

istrue.

8 Hardened Ppreconditions:

 Offset <= size() && (Count == dynamic_extent || Count <= size() - Offset)

istrue.

Modify 23.7.2.2.4[span.sub] paragraph 15 as indicated:

constexpr span<element_type, dynamic_extent> subspan(
 size_type offset, size_type count = dynamic_extent) const;

15 Hardened Ppreconditions:

 offset <= size() && (count == dynamic_extent || count <= size() - offset)

istrue.

span constructors

Modify 23.7.2.2.2[span.cons] around paragraph 4 as indicated:

template<class It>
 constexpr explicit(extent != dynamic_extent) span(It first, size_type count);

[…]

4 Preconditions:

(4.1) -[first, first + count)is a valid range.

(4.2) -It modelscontiguous_iterator.

(4.3) - Ifextent is not equal todynamic_extent, thencount is equal toextent.

5 Hardened preconditions: Ifextent is not equal todynamic_extent, thencount == extent istrue.

? Effects: Initializes data_with to_address(first)and size_ withcount.

? Throws: Nothing.

Modify 23.7.2.2.2[span.cons] around paragraph 8 as indicated:

template<class It, class End>
 constexpr explicit(extent != dynamic_extent) span(It first, End last);

[…]

8 Preconditions:

(8.1) - Ifextent is not equal todynamic_extent, thenlast - first is equal toextent.

(8.?) -[first, last)is a valid range.

(8.?) -It modelscontiguous_iterator.

(8.?) -End models sized_sentinel_for<It>.

9 Hardened preconditions: Ifextent is not equal todynamic_extent, then(last - first) == extent istrue.

? Effects: Initializes data_with to_address(first)and size_ withlast - first.

? Throws: When and whatlast - firstthrows.

Modify 23.7.2.2.2[span.cons] around paragraph 15 as indicated:

template<class R> constexpr explicit(extent != dynamic_extent) span(R&& r);

[…]

15 Preconditions:

(15.1) - If extent is not equal todynamic_extent, thenranges::size(r) is equal toextent.

(15.?) -R models ranges::contiguous_rangeand ranges::sized_range.

(15.?) - If is_const_v<element_type>is false,R models ranges::borrowed_range.

16 Hardened preconditions: Ifextent is not equal todynamic_extent, thenranges::size(r) == extent istrue.

? Effects: Initializes data_ with ranges::data(r)and size_ with ranges::size(r).

? Throws: What and when ranges::data(r)and ranges::size(r)throw.

Modify 23.7.2.2.2[span.cons] paragraph 19 as indicated:

constexpr explicit(extent != dynamic_extent) span(std::initializer_list<value_type> il);

18 Constraints: is_const_v<element_type>is true.

19 Hardened Ppreconditions: If extent is not equal todynamic_extent, then il.size()is equal toextentil.size() == extentistrue.

20 Effects: Initializes data_with il.begin()and size_ withil.size().

Modify 23.7.2.2.2[span.cons] paragraph 23 as indicated:

template<class OtherElementType, size_t OtherExtent>
 constexpr explicit(see below) span(const span<OtherElementType, OtherExtent>& s) noexcept;

22 Constraints:

(22.1) -extent == dynamic_extent || OtherExtent == dynamic_extent || extent == OtherExtentis true, and

(22.2) -is_convertible_v<OtherElementType(*)[], element_type(*)[]>is true.

[Note 6: The intent is to allow only qualification conversions of the OtherElementTypeto element_type. — _end note_]

23 Hardened Ppreconditions: If extent is not equal todynamic_extent, then s.size()is equal toextents.size() == extentistrue.

24 Effects: Constructs a spanthat is a view over the range [s.data(), s.data() + s.size()).

basic_string_view

basic_string_view::operator[]

Modify 27.3.3.6[string.view.access]paragraph 1 as indicated:

constexpr const_reference operator[](size_type pos) const;

1 Hardened Ppreconditions:pos < size() istrue.

4[Note 1: This precondition is stronger than the one onbasic_string::operator[]. —end note]

2 Returns: data_[pos].

3 Throws: Nothing.

4[Note 1: Unlikebasic_string::operator[],basic_string_view::operator[](size())has undefined behavior instead of returningcharT(). —end note]

basic_string_view::front

Modify 27.3.3.6[string.view.access]paragraph 7 as indicated:

constexpr const_reference front() const;

7 Hardened Ppreconditions:!empty() empty()isfalse.

8 Returns: data_[0].

9 Throws: Nothing.

basic_string_view::back

Modify 27.3.3.6[string.view.access]paragraph 10 as indicated:

constexpr const_reference back() const;

10 Hardened Ppreconditions:!empty() empty()isfalse.

11 Returns: data_[size() - 1].

12 Throws: Nothing.

basic_string_view::remove_prefix

Modify 27.3.3.7[string.view.modifiers]paragraph 1 as indicated:

constexpr void remove_prefix(size_type n);

1 Hardened Ppreconditions:n <= size() istrue.

2 Effects: Equivalent to: data_ += n; size_ -= n;

basic_string_view::remove_suffix

Modify 27.3.3.7[string.view.modifiers]paragraph 3 as indicated:

constexpr void remove_suffix(size_type n);

3 Hardened Ppreconditions:n <= size() istrue.

4 Effects: Equivalent to:size_ -= n;

Sequence containers

a.front()

Modify 23.2.4[sequence.reqmts]around paragraph 69 as indicated:

a.front()

69 Result: reference;const_reference for constanta.

70 Hardened preconditions:a.empty() isfalse.

71 Returns: *a.begin()

72 Remarks: Required forbasic_string,array,deque,forward_list,inplace_vector,list, andvector.

a.back()

Modify 23.2.4[sequence.reqmts]around paragraph 72 as indicated:

a.back()

72 Result: reference;const_reference for constanta.

73 Hardened preconditions:a.empty() isfalse.

74 Effects: Equivalent to:

auto tmp = a.end();
--tmp;
return *tmp;

75 Remarks: Required forbasic_string,array,deque,inplace_vector,list, andvector.

a.pop_front()

Modify 23.2.4[sequence.reqmts]paragraph 110 as indicated:

a.pop_front()

109 Result:void

110 Hardened Ppreconditions:a.empty()isfalse.

111 Effects: Destroys the first element.

112 Remarks: Required fordeque,forward_list, andlist.

a.pop_back()

Modify 23.2.4[sequence.reqmts]paragraph 114 as indicated:

a.pop_back()

113 Result:void

114 Hardened Ppreconditions:a.empty()isfalse.

115 Effects: Destroys the last element.

116 Remarks: Required forbasic_string,deque,inplace_vector,list, andvector.

a[n]

Modify 23.2.4[sequence.reqmts]around paragraph 117 as indicated:

a[n]

117 Result: reference;const_reference for constanta

118 Hardened preconditions:n < a.size() istrue.

119 Effects: Equivalent to: return *(a.begin() + n);

120 Remarks: Required forbasic_string,array,deque,inplace_vector, andvector.

basic_string

basic_string::operator[]

Modify 27.4.3.8.1[string.accessors]paragraph 1 as indicated:

constexpr const_reference operator[](size_type pos) const;
constexpr reference       operator[](size_type pos);

1 Hardened Ppreconditions:pos <= size() istrue.

2 Returns: *(begin() + pos)if pos < size(). Otherwise, returns a reference to an object of typecharT with valuecharT(), where modifying the object to any value other thancharT()leads to undefined behavior.

3 Throws: Nothing.

4 Complexity: Constant time.

basic_string::front

Modify 27.4.3.8.1[string.accessors]paragraph 7 as indicated:

constexpr const charT& front() const;
constexpr charT& front();

7 Hardened Ppreconditions:!empty()empty()isfalse.

8 Effects: Equivalent to: return operator[](0);

basic_string::back

Modify 27.4.3.8.1[string.accessors]paragraph 9 as indicated:

constexpr const charT& back() const;
constexpr charT& back();

9 Hardened Ppreconditions:!empty()empty()isfalse.

10 Effects: Equivalent to: return operator[](size() - 1);

basic_string::pop_back

Modify 27.4.3.7.5[string.erase]paragraph 12 as indicated:

constexpr void pop_back();

12 Hardened Ppreconditions:!empty()empty()isfalse.

13 Effects: Equivalent to erase(end() - 1).

14 Throws: Nothing.

mdspan

mdspan::operator[]

Modify 23.7.3.6.3[mdspan.mdspan.members]paragraph 3 as indicated:

template<class... OtherIndexTypes>
 constexpr reference operator[](OtherIndexTypes... indices) const;

[…]

2Let I be extents_type::_index-cast_(std::move(indices)).

3 Hardened Ppreconditions:I is a multidimensional index inextents().

[Note 1: This implies that map_(I) < map_.required_span_size()is true. —_end note_]

mdspan constructor

Modify 23.7.3.6.2[mdspan.mdspan.cons]around paragraph 21 as indicated:

template<class OtherElementType, class OtherExtents,
        class OtherLayoutPolicy, class OtherAccessor>
 constexpr explicit(see below)
   mdspan(const mdspan<OtherElementType, OtherExtents,
                       OtherLayoutPolicy, OtherAccessor>& other);

[…]

21 Preconditions:

(21.1) - For each rank index r ofextents_type, static_extent(r) == dynamic_extent || static_extent(r) == other.extent(r)is true.

(21.?)- [0, map_.required_span_size())is an accessible range of ptr_ andacc_ for values ofptr_,map_, andacc_ after the invocation of this constructor.

2? Hardened preconditions: For each rank indexr ofextents_type, static_extent(r) == dynamic_extent || static_extent(r) == other.extent(r)is true.

bitset

bitset::operator[]

Modify 22.9.2.3[bitset.members]paragraph 30 as indicated:

constexpr bool operator[](size_t pos) const;

30 Hardened Ppreconditions:posis validpos < size()istrue.

31 Returns:true if the bit at position pos in *thishas the value one, otherwisefalse.

32 Throws: Nothing.

Modify 22.9.2.3[bitset.members]paragraph 33 as indicated:

constexpr bitset::reference operator[](size_t pos);

33 Hardened Ppreconditions:posis validpos < size()istrue.

34 Returns: An object of type bitset::referencesuch that (*this)[pos] == this->test(pos), and such that (*this)[pos] = valis equivalent to this->set(pos, val).

35 Throws: Nothing.

valarray

valarray::operator[]

Modify 29.6.2.4[valarray.access]paragraph 1 as indicated:

const T&  operator[](size_t n) const;
T& operator[](size_t n);

1 Hardened Ppreconditions:n < size()is true.

2 Returns: A reference to the corresponding element of the array.

optional

optional::operator->

Modify 22.5.3.7[optional.observe]paragraph 1 as indicated:

constexpr const T* operator->() const noexcept;

constexpr T* operator->() noexcept;

1 Hardened Ppreconditions:*thiscontains a valuehas_value()istrue.

2 Returns:*val.

3 Remarks: These functions areconstexprfunctions.

optional::operator*

Modify 22.5.3.7[optional.observe]paragraph 4 as indicated:

constexpr const T& operator*() const & noexcept;

constexpr T& operator*() & noexcept;

4 Hardened Ppreconditions:*thiscontains a valuehas_value()istrue.

5 Returns:*val.

6 Remarks: These functions areconstexprfunctions.

Modify 22.5.3.7[optional.observe]paragraph 7 as indicated:

constexpr T&& operator*() && noexcept;

constexpr const T&& operator*() const && noexcept;

7 Hardened Ppreconditions:*thiscontains a valuehas_value()istrue.

8 Effects: Equivalent to: return std::move(*val);

expected

expected::operator->

Modify 22.8.6.6[expected.object.obs]paragraph 1 as indicated:

constexpr const T* operator->() const noexcept;

constexpr T* operator->() noexcept;

1 Hardened Ppreconditions:has_value()is true.

2 Returns: addressof(val).

expected::operator*

Modify 22.8.6.6[expected.object.obs]paragraph 3 as indicated:

constexpr const T& operator*() const & noexcept;

constexpr T& operator*() & noexcept;

3 Hardened Ppreconditions:has_value()is true.

4 Returns: val.

Modify 22.8.6.6[expected.object.obs]paragraph 5 as indicated:

constexpr T&& operator*() && noexcept;

constexpr const T&& operator*() const && noexcept;

5 Hardened Ppreconditions:has_value()is true.

6 Returns: std::move(val).

Modify 22.8.7.6[expected.void.obs]paragraph 2 as indicated:

constexpr void operator*() const noexcept;

2 Hardened Ppreconditions:has_value()is true.

expected::error()

Modify 22.8.6.6[expected.object.obs]paragraph 14 as indicated:

constexpr const E& error() const & noexcept;

constexpr E& error() & noexcept;

14 Hardened Ppreconditions:has_value()isfalse.

15 Returns: unex.

Modify 22.8.6.6[expected.object.obs]paragraph 16 as indicated:

constexpr E&& error() && noexcept;

constexpr const E&& error() const && noexcept;

16 Hardened Ppreconditions:has_value()isfalse.

17 Returns: std::move( unex).

Modify 22.8.7.6[expected.void.obs]paragraph 7 as indicated:

constexpr const E& error() const & noexcept;

constexpr E& error() & noexcept;

7 Hardened Ppreconditions:has_value()isfalse.

8 Returns: unex.

Modify 22.8.7.6[expected.void.obs]paragraph 9 as indicated:

constexpr E&& error() && noexcept;

constexpr const E&& error() const && noexcept;

9 Hardened Ppreconditions:has_value()isfalse.

10 Returns: std::move( unex).

Feature-test macros

(Authors’ note: we are not attached to any particular way of defining the feature-test macros. The proposed wording adds a macro for each class that would now contain hardened preconditions, but we are very open to suggestions on how these macros may be combined or split further, as well as naming)

Add a new paragraph to 17.3.2[version.syn] after paragraph 2 as indicated:

?Additionally, each of the following macros is defined in a hardened implementation:

+ #define __cpp_lib_hardened_array                    20????L // also in <array>
+ #define __cpp_lib_hardened_basic_string             20????L // also in <string>
+ #define __cpp_lib_hardened_basic_string_view        20????L // also in <string_view>
+ #define __cpp_lib_hardened_bitset                   20????L // also in <bitset>
+ #define __cpp_lib_hardened_deque                    20????L // also in <deque>
+ #define __cpp_lib_hardened_expected                 20????L // also in <expected>
+ #define __cpp_lib_hardened_forward_list             20????L // also in <forward_list>
+ #define __cpp_lib_hardened_inplace_vector           20????L // also in <inplace_vector>
+ #define __cpp_lib_hardened_list                     20????L // also in <list>
+ #define __cpp_lib_hardened_mdspan                   20????L // also in <mdspan>
+ #define __cpp_lib_hardened_optional                 20????L // also in <optional>
+ #define __cpp_lib_hardened_span                     20????L // also in <span>
+ #define __cpp_lib_hardened_valarray                 20????L // also in <valarray>
+ #define __cpp_lib_hardened_vector                   20????L // also in <vector>

?The macro__cpp_lib_freestanding_operator_newis defined to the integer literal 202306Lif all the default versions of the replaceable global allocation functions meet the requirements of a hosted implementation, and to the integer literal0 otherwise ([new.delete]).

? Recommended practice: Freestanding implementations should only define a macro from <version>if the implementation provides the corresponding facility in its entirety.

? Recommended practice: A non-hardened implementation should not define macros from<version>required for hardened implementations.