Skip to content

[libc++] Add a utility to check whether a range is valid #87665

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 4 commits into from
Apr 15, 2024
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
1 change: 1 addition & 0 deletions libcxx/include/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -856,6 +856,7 @@ set(files
__utility/in_place.h
__utility/integer_sequence.h
__utility/is_pointer_in_range.h
__utility/is_valid_range.h
__utility/move.h
__utility/no_destroy.h
__utility/pair.h
Expand Down
6 changes: 3 additions & 3 deletions libcxx/include/__utility/is_pointer_in_range.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <__type_traits/is_constant_evaluated.h>
#include <__type_traits/void_t.h>
#include <__utility/declval.h>
#include <__utility/is_valid_range.h>

#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC system_header
Expand All @@ -34,16 +35,15 @@ struct __is_less_than_comparable<_Tp, _Up, __void_t<decltype(std::declval<_Tp>()
template <class _Tp, class _Up, __enable_if_t<__is_less_than_comparable<const _Tp*, const _Up*>::value, int> = 0>
_LIBCPP_CONSTEXPR_SINCE_CXX14 _LIBCPP_HIDE_FROM_ABI _LIBCPP_NO_SANITIZE("address") bool __is_pointer_in_range(
const _Tp* __begin, const _Tp* __end, const _Up* __ptr) {
if (__libcpp_is_constant_evaluated()) {
_LIBCPP_ASSERT_VALID_INPUT_RANGE(__builtin_constant_p(__begin <= __end), "__begin and __end do not form a range");
_LIBCPP_ASSERT_VALID_INPUT_RANGE(std::__is_valid_range(__begin, __end), "[__begin, __end) is not a valid range");

if (__libcpp_is_constant_evaluated()) {
// If this is not a constant during constant evaluation we know that __ptr is not part of the allocation where
// [__begin, __end) is.
if (!__builtin_constant_p(__begin <= __ptr && __ptr < __end))
return false;
}

// Checking this for unrelated pointers is technically UB, but no compiler optimizes based on it (currently).
return !__less<>()(__ptr, __begin) && __less<>()(__ptr, __end);
}

Expand Down
37 changes: 37 additions & 0 deletions libcxx/include/__utility/is_valid_range.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

#ifndef _LIBCPP___UTILITY_IS_VALID_RANGE_H
#define _LIBCPP___UTILITY_IS_VALID_RANGE_H

#include <__algorithm/comp.h>
#include <__config>
#include <__type_traits/is_constant_evaluated.h>

#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC system_header
#endif

_LIBCPP_BEGIN_NAMESPACE_STD

template <class _Tp>
_LIBCPP_CONSTEXPR_SINCE_CXX14 _LIBCPP_HIDE_FROM_ABI _LIBCPP_NO_SANITIZE("address") bool
__is_valid_range(const _Tp* __first, const _Tp* __last) {
if (__libcpp_is_constant_evaluated()) {
// If this is not a constant during constant evaluation, that is because __first and __last are not
// part of the same allocation. If they are part of the same allocation, we must still make sure they
// are ordered properly.
return __builtin_constant_p(__first <= __last) && __first <= __last;
}

return !__less<>()(__last, __first);
}

_LIBCPP_END_NAMESPACE_STD

#endif // _LIBCPP___UTILITY_IS_VALID_RANGE_H
1 change: 1 addition & 0 deletions libcxx/include/libcxx.imp
Original file line number Diff line number Diff line change
Expand Up @@ -851,6 +851,7 @@
{ include: [ "<__utility/in_place.h>", "private", "<utility>", "public" ] },
{ include: [ "<__utility/integer_sequence.h>", "private", "<utility>", "public" ] },
{ include: [ "<__utility/is_pointer_in_range.h>", "private", "<utility>", "public" ] },
{ include: [ "<__utility/is_valid_range.h>", "private", "<utility>", "public" ] },
{ include: [ "<__utility/move.h>", "private", "<utility>", "public" ] },
{ include: [ "<__utility/no_destroy.h>", "private", "<utility>", "public" ] },
{ include: [ "<__utility/pair.h>", "private", "<utility>", "public" ] },
Expand Down
1 change: 1 addition & 0 deletions libcxx/include/module.modulemap
Original file line number Diff line number Diff line change
Expand Up @@ -2064,6 +2064,7 @@ module std_private_utility_forward_like [system] { header "__utility/f
module std_private_utility_in_place [system] { header "__utility/in_place.h" }
module std_private_utility_integer_sequence [system] { header "__utility/integer_sequence.h" }
module std_private_utility_is_pointer_in_range [system] { header "__utility/is_pointer_in_range.h" }
module std_private_utility_is_valid_range [system] { header "__utility/is_valid_range.h" }
module std_private_utility_move [system] {
header "__utility/move.h"
export std_private_type_traits_is_copy_constructible
Expand Down
68 changes: 68 additions & 0 deletions libcxx/test/libcxx/utilities/is_valid_range.pass.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

#include <__utility/is_valid_range.h>
#include <cassert>

#include "test_macros.h"

template <class T, class TQualified>
TEST_CONSTEXPR_CXX14 void check_type() {
{
// We need to ensure that the addresses of i and j are ordered as &i < &j for
// the test below to work portably, so we define them in a struct.
struct {
T i = 0;
T j = 0;
} storage;
assert(std::__is_valid_range(static_cast<TQualified*>(&storage.i), static_cast<TQualified*>(&storage.i)));
assert(std::__is_valid_range(static_cast<TQualified*>(&storage.i), static_cast<TQualified*>(&storage.i + 1)));

assert(!std::__is_valid_range(static_cast<TQualified*>(&storage.j), static_cast<TQualified*>(&storage.i)));
assert(!std::__is_valid_range(static_cast<TQualified*>(&storage.i + 1), static_cast<TQualified*>(&storage.i)));

// We detect this as being a valid range even though it is not really valid.
assert(std::__is_valid_range(static_cast<TQualified*>(&storage.i), static_cast<TQualified*>(&storage.j)));
}

{
T arr[3] = {1, 2, 3};
assert(std::__is_valid_range(static_cast<TQualified*>(&arr[0]), static_cast<TQualified*>(&arr[0])));
assert(std::__is_valid_range(static_cast<TQualified*>(&arr[0]), static_cast<TQualified*>(&arr[1])));
assert(std::__is_valid_range(static_cast<TQualified*>(&arr[0]), static_cast<TQualified*>(&arr[2])));

assert(!std::__is_valid_range(static_cast<TQualified*>(&arr[1]), static_cast<TQualified*>(&arr[0])));
assert(!std::__is_valid_range(static_cast<TQualified*>(&arr[2]), static_cast<TQualified*>(&arr[0])));
}

#if TEST_STD_VER >= 20
{
T* arr = new int[4]{1, 2, 3, 4};
assert(std::__is_valid_range(static_cast<TQualified*>(arr), static_cast<TQualified*>(arr + 4)));
delete[] arr;
}
#endif
}

TEST_CONSTEXPR_CXX14 bool test() {
check_type<int, int>();
check_type<int, int const>();
check_type<int, int volatile>();
check_type<int, int const volatile>();

return true;
}

int main(int, char**) {
test();
#if TEST_STD_VER >= 14
static_assert(test(), "");
#endif

return 0;
}