Skip to content

Commit

Permalink
[libc++] Avoid -Wzero-as-null-pointer-constant in operator<=> (llvm#7…
Browse files Browse the repository at this point in the history
…9465)

Issue llvm#43670 describes a situation where the following comparison will
issue a warning when -Wzero-as-null-pointer-constant is enabled:

    #include <compare>
    auto b = (1 <=> 2) < 0;

This code uses operator<(strong_ordering, Unspecified), which is
specified by the Standard to only work with a literal 0. In the library,
this is achieved by constructing Unspecified from a pointer, which works
but has the downside of triggering the warning.

This patch uses an alternative implementation where we require that the
operator is used exactly with an int of value 0 (known at compile-time),
however that value can technically be an expression like `1 - 1`, which
makes us a bit less strict than what's specified in the Standard.

Fixes llvm#43670
  • Loading branch information
ldionne authored Aug 21, 2024
1 parent 4f14bfe commit aa08843
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 65 deletions.
23 changes: 15 additions & 8 deletions libcxx/include/__compare/ordering.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,20 @@ class partial_ordering;
class weak_ordering;
class strong_ordering;

template <class _Tp, class... _Args>
inline constexpr bool __one_of_v = (is_same_v<_Tp, _Args> || ...);

struct _CmpUnspecifiedParam {
_LIBCPP_HIDE_FROM_ABI constexpr _CmpUnspecifiedParam(int _CmpUnspecifiedParam::*) noexcept {}

template <class _Tp, class = enable_if_t<!__one_of_v<_Tp, int, partial_ordering, weak_ordering, strong_ordering>>>
_CmpUnspecifiedParam(_Tp) = delete;
// If anything other than a literal 0 is provided, the behavior is undefined by the Standard.
//
// The alternative to the `__enable_if__` attribute would be to use the fact that a pointer
// can be constructed from literal 0, but this conflicts with `-Wzero-as-null-pointer-constant`.
template <class _Tp, class = __enable_if_t<is_same_v<_Tp, int> > >
_LIBCPP_HIDE_FROM_ABI consteval _CmpUnspecifiedParam(_Tp __zero) noexcept
# if __has_attribute(__enable_if__)
__attribute__((__enable_if__(
__zero == 0, "Only literal 0 is allowed as the operand of a comparison with one of the ordering types")))
# endif
{
(void)__zero;
}
};

class partial_ordering {
Expand Down Expand Up @@ -269,7 +275,8 @@ inline constexpr strong_ordering strong_ordering::greater(_OrdResult::__greater)
/// The types partial_ordering, weak_ordering, and strong_ordering are
/// collectively termed the comparison category types.
template <class _Tp>
concept __comparison_category = __one_of_v<_Tp, partial_ordering, weak_ordering, strong_ordering>;
concept __comparison_category =
is_same_v<_Tp, partial_ordering> || is_same_v<_Tp, weak_ordering> || is_same_v<_Tp, strong_ordering>;

#endif // _LIBCPP_STD_VER >= 20

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

// UNSUPPORTED: c++03, c++11, c++14, c++17

// <compare>

// Ensure we reject all cases where an argument other than a literal 0 is used
// for a comparison against a comparison category type.

// Also ensure that we don't warn about providing a null pointer constant when
// comparing an ordering type against literal 0, since one of the common
// implementation strategies is to use a pointer as the "unspecified type".
// ADDITIONAL_COMPILE_FLAGS: -Wzero-as-null-pointer-constant

#include <compare>

#include "test_macros.h"

#define TEST_FAIL(v, op) \
do { \
/* invalid types */ \
void(v op 0L); \
void(0L op v); \
void(v op 0.0); \
void(0.0 op v); \
void(v op nullptr); \
void(nullptr op v); \
/* invalid value */ \
void(v op 1); \
void(1 op v); \
/* value not known at compile-time */ \
int i = 0; \
void(v op i); \
void(i op v); \
} while (false)

#define TEST_PASS(v, op) \
do { \
void(v op 0); \
void(0 op v); \
LIBCPP_ONLY(void(v op(1 - 1))); \
LIBCPP_ONLY(void((1 - 1) op v)); \
} while (false)

template <typename T>
void test_category(T v) {
TEST_FAIL(v, ==); // expected-error 30 {{invalid operands to binary expression}}
TEST_FAIL(v, !=); // expected-error 30 {{invalid operands to binary expression}}
TEST_FAIL(v, <); // expected-error 30 {{invalid operands to binary expression}}
TEST_FAIL(v, <=); // expected-error 30 {{invalid operands to binary expression}}
TEST_FAIL(v, >); // expected-error 30 {{invalid operands to binary expression}}
TEST_FAIL(v, >=); // expected-error 30 {{invalid operands to binary expression}}
TEST_FAIL(v, <=>); // expected-error 30 {{invalid operands to binary expression}}

TEST_PASS(v, ==);
TEST_PASS(v, !=);
TEST_PASS(v, <);
TEST_PASS(v, >);
TEST_PASS(v, <=);
TEST_PASS(v, >=);
TEST_PASS(v, <=>);
}

void f() {
test_category(std::strong_ordering::equivalent);
test_category(std::weak_ordering::equivalent);
test_category(std::partial_ordering::equivalent);
}

This file was deleted.

0 comments on commit aa08843

Please sign in to comment.