-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[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
Conversation
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.
@llvm/pr-subscribers-libcxx Author: Louis Dionne (ldionne) ChangesIn 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. Full diff: https://github.com/llvm/llvm-project/pull/87665.diff 4 Files Affected:
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index db3980342f50bf..a3dce266ea912d 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -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
diff --git a/libcxx/include/__utility/is_pointer_in_range.h b/libcxx/include/__utility/is_pointer_in_range.h
index 68cdfea6f94529..fa032d80cb2630 100644
--- a/libcxx/include/__utility/is_pointer_in_range.h
+++ b/libcxx/include/__utility/is_pointer_in_range.h
@@ -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
@@ -34,9 +35,9 @@ 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))
diff --git a/libcxx/include/__utility/is_valid_range.h b/libcxx/include/__utility/is_valid_range.h
new file mode 100644
index 00000000000000..0cf1ab5420a36b
--- /dev/null
+++ b/libcxx/include/__utility/is_valid_range.h
@@ -0,0 +1,38 @@
+//===----------------------------------------------------------------------===//
+//
+// 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;
+ }
+
+ // Checking this for unrelated pointers is technically UB, but no compiler optimizes based on it (currently).
+ return !__less<>()(__last, __first);
+}
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP___UTILITY_IS_VALID_RANGE_H
diff --git a/libcxx/test/libcxx/utilities/is_valid_range.pass.cpp b/libcxx/test/libcxx/utilities/is_valid_range.pass.cpp
new file mode 100644
index 00000000000000..e5ce71c2bdc48a
--- /dev/null
+++ b/libcxx/test/libcxx/utilities/is_valid_range.pass.cpp
@@ -0,0 +1,64 @@
+//===----------------------------------------------------------------------===//
+//
+// 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>
+TEST_CONSTEXPR_CXX14 void check_type() {
+ {
+ T i = 0;
+ T j = 0;
+ assert(std::__is_valid_range(&i, &i));
+
+ assert(!std::__is_valid_range(&i, &j));
+ assert(!std::__is_valid_range(&i + 1, &i));
+
+ // We detect this one as being a valid range.
+ // Ideally we would detect it as an invalid range, but this may not be implementable.
+ assert(std::__is_valid_range(&i, &i + 1));
+ }
+
+ {
+ T arr[3] = {1, 2, 3};
+ assert(std::__is_valid_range(&arr[0], &arr[0]));
+ assert(std::__is_valid_range(&arr[0], &arr[1]));
+ assert(std::__is_valid_range(&arr[0], &arr[2]));
+
+ assert(!std::__is_valid_range(&arr[1], &arr[0]));
+ assert(!std::__is_valid_range(&arr[2], &arr[0]));
+ }
+
+#if TEST_STD_VER >= 20
+ {
+ T* arr = new int[4]{1, 2, 3, 4};
+ assert(std::__is_valid_range(arr, arr + 4));
+ delete[] arr;
+ }
+#endif
+}
+
+TEST_CONSTEXPR_CXX14 bool test() {
+ check_type<int>();
+ check_type<const int>();
+ check_type<volatile int>();
+ check_type<const volatile int>();
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 14
+ static_assert(test(), "");
+#endif
+
+ return 0;
+}
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
// Checking this for unrelated pointers is technically UB, but no compiler optimizes based on it (currently). | ||
return !__less<>()(__last, __first); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No change requested. IIUC, the intent should be that __less
also establishes the "implementation-defined strict total order over pointers" ([defns.order.ptr]) like std::less
and std::ranges::less
. So ideally there should be well-defined results.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will remove the comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, modulo one comment.
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.
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.
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.