This checks whether a pointer is within a range, even during constant evaluation. This allows running optimized code paths during constant evaluation, instead of falling back to the general-purpose implementation all the time. This is also a central place for comparing unrelated pointers, which is technically UB.
Details
- Reviewers
ldionne Mordante var-const - Group Reviewers
Restricted Project - Commits
- rG7949ee0d4f10: [libc++] Introduce __is_pointer_in_range
Diff Detail
- Repository
- rG LLVM Github Monorepo
Event Timeline
libcxx/include/__utility/is_pointer_in_range.h | ||
---|---|---|
48 | Am I correct here that it's impossible to get aliasing pointers during constant evaluation that are not comparable normally? |
Note I fixed a typo in the commit message.
Thanks for working on this, I think it's useful to have, but it requires a bit more work.
libcxx/include/CMakeLists.txt | ||
---|---|---|
833 | I miss the module map entry. | |
libcxx/include/__config | ||
1105 | Can you add a link to the documentation of this attribute? | |
libcxx/include/__utility/is_pointer_in_range.h | ||
34 | Or are empty ranges prohibited? Based on the code below it is indeed prohibited. If that is intended I really dislike the name of the function. | |
42 | I think we should document this is technically UB, but all supported compilers accept this. | |
51 | Is this really safe on all supported platforms? | |
libcxx/test/libcxx/utilities/is_pointer_in_range.pass.cpp | ||
38 | I really like to see more test. This only compares pointers where _Tp and _Up are the same type. Since the code tests different pointer types I really would like to see tests for volatile pointers against non-volatile pointers. Test with object pointers against function pointers, data member pointer, member function pointers. |
Address comments
libcxx/include/__config | ||
---|---|---|
1105 | I don't really see the point here. All the clang attributes are described at https://clang.llvm.org/docs/AttributeReference.html. Having a link to the same page for every attribute we use seems redundant. We can add a link at the top of the file if you think that's useful. | |
libcxx/include/__utility/is_pointer_in_range.h | ||
34 | No, empty ranges are not prohibited. This just checks that it is valid to compare the two pointers. It doesn't actually check that __begin is smaller than __end. IOW __builtin_constant_p(ptr < ptr) is always true, since the expression is known to result in false (https://godbolt.org/z/EMfW7179r). AFAIK a range is normally [begin, end), i.e. excluding the end pointer. Returning true for the end pointer would be inconsistent with any other function taking a range. | |
51 | I think it should be. Do you have anything specific in mind? | |
libcxx/test/libcxx/utilities/is_pointer_in_range.pass.cpp | ||
38 | I don't think it makes sense to call __is_pointer_in_range with function pointers or member pointers, so I added static_asserts instead. |
libcxx/include/__config | ||
---|---|---|
1105 | I think it would help readers to quickly find the documentation, instead of remember where the clang attributes are exactly available. | |
libcxx/include/__utility/is_pointer_in_range.h | ||
34 | Yes a range is [begin, end), and an empty for an empty range begin == end. | |
51 | You can compare integral types with different sizes, but you can't just cast pointers from one to another. Concretely I wonder whether we violate alignment rules. |
Address comments
libcxx/include/__utility/is_pointer_in_range.h | ||
---|---|---|
51 | Yeah, I'm not sure. Casting all pointers to char should make it safe. |
libcxx/include/module.modulemap.in | ||
---|---|---|
1743–1744 | By the way, I think this is highly relevant to the existing issue that std::less is supposed to be blessed to compare unrelated pointers, but we currently don't do anything special with it. I think we should have a single solution for both of those issues. | |
libcxx/include/string | ||
1952 | The new code is not equivalent to the old one. The old code does data() <= __p && __p <= data() + size(), the new code looks for data() <= __p && __p < data() + size() (notice <= vs <). | |
1957 | I would handle the !is-less-than-comparable case here instead of in __is_pointer_in_range. __is_pointer_in_range is more general purpose, and it doesn't make sense for it to return false when trying to compare unrelated pointers -- instead it should be a compiler error to do so. In other words, __is_pointer_in_range should be equivalent to first <= p < last, except blessed by the compiler -- nothing less, nothing more. If that expression would not be well formed, then __is_pointer_in_range should also not be well formed, and we shouldn't try to give it a value. |
LGTM, but this needs to be rebased on the patch with the __less refactoring before going in.
libcxx/include/__utility/is_pointer_in_range.h | ||
---|---|---|
42–43 | That way, we technically don't have to change anything but std::less in the future. We could also use __less instead here, and make __less be the canonical place where we bless comparison of unrelated pointers. We would then also use __less from std::less. Let's do that part as a separate patch, it's actually a nice cleanup. | |
libcxx/include/string | ||
1958 | Here and below. | |
1971 | For symmetry with above? | |
libcxx/test/libcxx/utilities/is_pointer_in_range.pass.cpp | ||
35 | Could we move this test to test_cv_quals? |
I miss the module map entry.