Skip to content

Commit a06073f

Browse files
authored
[libc++] Add a utility to check whether a range is valid (#87665)
In the future, this utility could be made to also work with iterators, including bounded iterators. We could also query the ASAN runtime for this information when it's around.
1 parent a269195 commit a06073f

File tree

6 files changed

+111
-3
lines changed

6 files changed

+111
-3
lines changed

libcxx/include/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -860,6 +860,7 @@ set(files
860860
__utility/in_place.h
861861
__utility/integer_sequence.h
862862
__utility/is_pointer_in_range.h
863+
__utility/is_valid_range.h
863864
__utility/move.h
864865
__utility/no_destroy.h
865866
__utility/pair.h

libcxx/include/__utility/is_pointer_in_range.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <__type_traits/is_constant_evaluated.h>
1818
#include <__type_traits/void_t.h>
1919
#include <__utility/declval.h>
20+
#include <__utility/is_valid_range.h>
2021

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

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

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef _LIBCPP___UTILITY_IS_VALID_RANGE_H
10+
#define _LIBCPP___UTILITY_IS_VALID_RANGE_H
11+
12+
#include <__algorithm/comp.h>
13+
#include <__config>
14+
#include <__type_traits/is_constant_evaluated.h>
15+
16+
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
17+
# pragma GCC system_header
18+
#endif
19+
20+
_LIBCPP_BEGIN_NAMESPACE_STD
21+
22+
template <class _Tp>
23+
_LIBCPP_CONSTEXPR_SINCE_CXX14 _LIBCPP_HIDE_FROM_ABI _LIBCPP_NO_SANITIZE("address") bool
24+
__is_valid_range(const _Tp* __first, const _Tp* __last) {
25+
if (__libcpp_is_constant_evaluated()) {
26+
// If this is not a constant during constant evaluation, that is because __first and __last are not
27+
// part of the same allocation. If they are part of the same allocation, we must still make sure they
28+
// are ordered properly.
29+
return __builtin_constant_p(__first <= __last) && __first <= __last;
30+
}
31+
32+
return !__less<>()(__last, __first);
33+
}
34+
35+
_LIBCPP_END_NAMESPACE_STD
36+
37+
#endif // _LIBCPP___UTILITY_IS_VALID_RANGE_H

libcxx/include/libcxx.imp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -853,6 +853,7 @@
853853
{ include: [ "<__utility/in_place.h>", "private", "<utility>", "public" ] },
854854
{ include: [ "<__utility/integer_sequence.h>", "private", "<utility>", "public" ] },
855855
{ include: [ "<__utility/is_pointer_in_range.h>", "private", "<utility>", "public" ] },
856+
{ include: [ "<__utility/is_valid_range.h>", "private", "<utility>", "public" ] },
856857
{ include: [ "<__utility/move.h>", "private", "<utility>", "public" ] },
857858
{ include: [ "<__utility/no_destroy.h>", "private", "<utility>", "public" ] },
858859
{ include: [ "<__utility/pair.h>", "private", "<utility>", "public" ] },

libcxx/include/module.modulemap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2075,6 +2075,7 @@ module std_private_utility_forward_like [system] { header "__utility/f
20752075
module std_private_utility_in_place [system] { header "__utility/in_place.h" }
20762076
module std_private_utility_integer_sequence [system] { header "__utility/integer_sequence.h" }
20772077
module std_private_utility_is_pointer_in_range [system] { header "__utility/is_pointer_in_range.h" }
2078+
module std_private_utility_is_valid_range [system] { header "__utility/is_valid_range.h" }
20782079
module std_private_utility_move [system] {
20792080
header "__utility/move.h"
20802081
export std_private_type_traits_is_copy_constructible
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include <__utility/is_valid_range.h>
10+
#include <cassert>
11+
12+
#include "test_macros.h"
13+
14+
template <class T, class TQualified>
15+
TEST_CONSTEXPR_CXX14 void check_type() {
16+
{
17+
// We need to ensure that the addresses of i and j are ordered as &i < &j for
18+
// the test below to work portably, so we define them in a struct.
19+
struct {
20+
T i = 0;
21+
T j = 0;
22+
} storage;
23+
assert(std::__is_valid_range(static_cast<TQualified*>(&storage.i), static_cast<TQualified*>(&storage.i)));
24+
assert(std::__is_valid_range(static_cast<TQualified*>(&storage.i), static_cast<TQualified*>(&storage.i + 1)));
25+
26+
assert(!std::__is_valid_range(static_cast<TQualified*>(&storage.j), static_cast<TQualified*>(&storage.i)));
27+
assert(!std::__is_valid_range(static_cast<TQualified*>(&storage.i + 1), static_cast<TQualified*>(&storage.i)));
28+
29+
// We detect this as being a valid range even though it is not really valid.
30+
assert(std::__is_valid_range(static_cast<TQualified*>(&storage.i), static_cast<TQualified*>(&storage.j)));
31+
}
32+
33+
{
34+
T arr[3] = {1, 2, 3};
35+
assert(std::__is_valid_range(static_cast<TQualified*>(&arr[0]), static_cast<TQualified*>(&arr[0])));
36+
assert(std::__is_valid_range(static_cast<TQualified*>(&arr[0]), static_cast<TQualified*>(&arr[1])));
37+
assert(std::__is_valid_range(static_cast<TQualified*>(&arr[0]), static_cast<TQualified*>(&arr[2])));
38+
39+
assert(!std::__is_valid_range(static_cast<TQualified*>(&arr[1]), static_cast<TQualified*>(&arr[0])));
40+
assert(!std::__is_valid_range(static_cast<TQualified*>(&arr[2]), static_cast<TQualified*>(&arr[0])));
41+
}
42+
43+
#if TEST_STD_VER >= 20
44+
{
45+
T* arr = new int[4]{1, 2, 3, 4};
46+
assert(std::__is_valid_range(static_cast<TQualified*>(arr), static_cast<TQualified*>(arr + 4)));
47+
delete[] arr;
48+
}
49+
#endif
50+
}
51+
52+
TEST_CONSTEXPR_CXX14 bool test() {
53+
check_type<int, int>();
54+
check_type<int, int const>();
55+
check_type<int, int volatile>();
56+
check_type<int, int const volatile>();
57+
58+
return true;
59+
}
60+
61+
int main(int, char**) {
62+
test();
63+
#if TEST_STD_VER >= 14
64+
static_assert(test(), "");
65+
#endif
66+
67+
return 0;
68+
}

0 commit comments

Comments
 (0)