-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[libc++] Prevent calling the projection more than three times #66315
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
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.
Thanks for looking into this! I have some comments but this is a great start!
libcxx/test/std/algorithms/ranges_projection_not_called_more_than_three_times.pass.cpp
Outdated
Show resolved
Hide resolved
return __low; | ||
else if (std::invoke(__comp, std::invoke(__proj, __high), std::invoke(__proj, __value))) | ||
else if (std::invoke(__comp, std::invoke(__proj, __high), std::forward<decltype(__projected)>(__projected)) |
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.
There is actually a problem here. We can potentially do a double-move. Imagine we have the following comparator:
struct Foo {
std::string s;
};
// taking by value is important here
auto comparator = [](std::string a, std::string b) {
return std::atoi(a.c_str()) < std::atoi(b.c_str());
};
auto projection = [](Foo const& foo) {
return foo.s;
};
Foo foo{"12"};
Foo high{"10"};
Foo low{"1"};
std::ranges::clamp(foo, low, high, comparator, projection);
Here, we end up calling std::invoke(comp, forward<...>(projected), low)
, it returns false and then we call std::invoke(comp, forward<...>(projected), high)
. We end up moving projected
twice into the invoke
calls, so the second time we get an empty string.
Is that right? If so, this would need a test too.
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.
Ok, so actually it turns out that MSVC does the same thing here and they went through the exact same questioning as us, see microsoft/STL#1898 (comment).
However, I played around and I wasn't able to craft a case where passing an lvalue would fail (despite what @timsong-cpp said in that review). Unless we are able to come up with one, I think it would be reasonable to pass a lvalue.
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.
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.
Ah, thanks a lot! Like I said in the other comment, @pandaninjas let's add a test for that and go back to std::forward
. Then I think this patch will be good to go.
libcxx/test/std/algorithms/alg.sorting/alg.clamp/ranges.clamp.pass.cpp
Outdated
Show resolved
Hide resolved
libcxx/test/std/algorithms/alg.sorting/alg.clamp/ranges.clamp.pass.cpp
Outdated
Show resolved
Hide resolved
What a tricky change! The ones that seem simple are sometimes the ones that end up being the most involved :) |
Any idea why the CI is failing? It mentions line 86 not being constexpr because of assert, but it doesn't seem like I've done anything which should affect that... |
I looked into it a bit and I think the CI is failing because of the |
libcxx/test/std/algorithms/alg.sorting/alg.clamp/ranges.clamp.pass.cpp
Outdated
Show resolved
Hide resolved
return __low; | ||
else if (std::invoke(__comp, std::invoke(__proj, __high), std::invoke(__proj, __value))) | ||
else if (std::invoke(__comp, std::invoke(__proj, __high), std::forward<decltype(__projected)>(__projected)) |
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.
Ah, thanks a lot! Like I said in the other comment, @pandaninjas let's add a test for that and go back to std::forward
. Then I think this patch will be good to go.
@@ -37,9 +37,10 @@ struct __fn { | |||
_LIBCPP_ASSERT_UNCATEGORIZED(!bool(std::invoke(__comp, std::invoke(__proj, __high), std::invoke(__proj, __low))), | |||
"Bad bounds passed to std::ranges::clamp"); | |||
|
|||
if (std::invoke(__comp, std::invoke(__proj, __value), std::invoke(__proj, __low))) | |||
auto&& __projected = std::forward<decltype(__projected)>(std::invoke(__proj, __value)); |
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.
We don't want to use std::forward
here, we want to use it in the argument to std::invoke
.
libcxx/test/std/algorithms/alg.sorting/alg.clamp/ranges.clamp.pass.cpp
Outdated
Show resolved
Hide resolved
It seems like tests are still failing because there is a double move. |
It seems that we should remove this block because it turns out that llvm-project/libcxx/test/std/algorithms/alg.sorting/alg.clamp/ranges.clamp.pass.cpp Lines 89 to 110 in 4c1c96e
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
This seems to be a bit stuck. I checked out the patch locally and that was helpful to understand some of the issues. I think the final patch should be:
Can you update the PR accordingly? Also, in the future I believe that following the instructions in https://libcxx.llvm.org/TestingLibcxx.html#usage would make it a lot easier to see issues in testing before running them through the CI. Most of the issues I fixed in the patch locally were pretty immediately visible when running the tests locally. |
Picked up in #68413. |
Resolves #64717