-
Notifications
You must be signed in to change notification settings - Fork 11k
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
[libc++] std::expected
"has value" flag getting clobbered unexpectedly
#68552
Comments
It seems to me that the current implementation stategy triggers core language undefined behavior, although the related CWG issue is still open (CWG2757). libc++ is making several subobjects potentially-overlapping by A safe resolution may be just removing |
@huixie90 Can you take a look at this? It seems fairly important to fix this. |
I created a PR for a possible fix: #68733 It solves my problem at least. I also added a test (more or less the reproducer from above), although this is really hard to test for properly. Sanitizers (asan, ubsan) couldn't catch this in my (admittedly limited) testing. Even valgrind didn't catch this. |
The calls to `std::construct_at` might overwrite the previously set `__has_value_` flag, so reverse the order everywhere. Where possible, avoid calling `std::construct_at` and construct the value/error directly into the union.
I wonder if this note then applies for libc++'s
Does this mean that there needs to be |
In case some folks are watching this but not the PR (or in case someone sees this a long time from now and wants to see more details), there's been a lot of interesting discussion on the PR thread: #68733 |
The calls to `std::construct_at` might overwrite the previously set `__has_value_` flag, so reverse the order everywhere. Where possible, avoid calling `std::construct_at` and construct the value/error directly into the union.
…68733) The calls to std::construct_at might overwrite the previously set __has_value_ flag in the case where the flag is overlapping with the actual value or error being stored (since we use [[no_unique_address]]). To fix this issue, this patch ensures that we initialize the __has_value_ flag after we call std::construct_at. Fixes #68552
llvm#68733) The calls to std::construct_at might overwrite the previously set __has_value_ flag in the case where the flag is overlapping with the actual value or error being stored (since we use [[no_unique_address]]). To fix this issue, this patch ensures that we initialize the __has_value_ flag after we call std::construct_at. Fixes llvm#68552 (cherry picked from commit 134c915)
…68733) The calls to std::construct_at might overwrite the previously set __has_value_ flag in the case where the flag is overlapping with the actual value or error being stored (since we use [[no_unique_address]]). To fix this issue, this patch ensures that we initialize the __has_value_ flag after we call std::construct_at. Fixes #68552 (cherry picked from commit 134c915)
This patch adds a configuration of the libc++ test suite that enables optimizations when building the tests. It also adds a new CI configuration to exercise this on a regular basis. This is added in the context of [1], which requires building with optimizations in order to hit the bug. [1]: llvm#68552
This patch adds a configuration of the libc++ test suite that enables optimizations when building the tests. It also adds a new CI configuration to exercise this on a regular basis. This is added in the context of [1], which requires building with optimizations in order to hit the bug. [1]: #68552
Currently std::expected can have some padding bytes in its tail due to [[no_unique_address]]. Those padding bytes can be used by other objects. For example, in the current implementation: sizeof(std::expected<std::optional<int>, bool>) == sizeof(std::expected<std::expected<std::optional<int>, bool>, bool>) As a result, the data layout of an std::expected<std::expected<std::optional<int>, bool>, bool> can look like this: +-- optional "has value" flag | +--padding /---int---\ | | 00 00 00 00 01 00 00 00 | | | +- "outer" expected "has value" flag | +- expected "has value" flag This is problematic because `emplace()`ing the "inner" expected can not only overwrite the "inner" expected "has value" flag (issue #68552) but also the tail padding where other objects might live. This patch fixes the problem by ensuring that std::expected has no tail padding, which is achieved by conditional usage of [[no_unique_address]] based on the tail padding that this would create. This is an ABI breaking change because the following property changes: sizeof(std::expected<std::optional<int>, bool>) < sizeof(std::expected<std::expected<std::optional<int>, bool>, bool>) Before the change, this relation didn't hold. After the change, the relation does hold, which means that the size of std::expected in these cases increases after this patch. The data layout will change in the following cases where tail padding can be reused by other objects: class foo : std::expected<std::optional<int>, bool> { bool b; }; or using [[no_unique_address]]: struct foo { [[no_unique_address]] std::expected<std::optional<int>, bool> e; bool b; }; The vendor communication is handled in #70820. Fixes: #70494 Co-authored-by: philnik777 <nikolasklauser@berlin.de> Co-authored-by: Louis Dionne <ldionne.2@gmail.com>
This patch adds a configuration of the libc++ test suite that enables optimizations when building the tests. It also adds a new CI configuration to exercise this on a regular basis. This is added in the context of [1], which requires building with optimizations in order to hit the bug. [1]: llvm#68552
This patch adds a configuration of the libc++ test suite that enables optimizations when building the tests. It also adds a new CI configuration to exercise this on a regular basis. This is added in the context of [1], which requires building with optimizations in order to hit the bug. [1]: llvm/llvm-project#68552 NOKEYCHECK=True GitOrigin-RevId: ca06c330fd07f05e65a638892c32ca1474d47b5e
In some situations it seems that the "has value" flag of a
std::expected
gets overwritten when the value is constructed. Example:Godbolt: https://godbolt.org/z/nbcPr9e5f
I would expect that the
assert
is not triggered, but it is. I'm testing with commit d408770.I strongly suspect code like this, i.e. where the call to
std::construct_at
happens after setting the "has value" flag:llvm-project/libcxx/include/__expected/expected.h
Lines 252 to 258 in 7cc1bfa
In the above example,
__has_val_
(abool
) and__union_.__val_
(thestd::optional<int>
) overlap. I thinkstd::construct_at
is allowed to overwrite__has_val_
in this case by the language rules as thestd::construct_at
has no idea that the__has_val_
is there. It just assumes it has full 8 bytes to work with to construct thestd::optional<int>
.The data layout of the problematic
std::expected<std::optional<int>, int>
looks like this:The
expected
's "has value" flag reuses one byte of the padding. It should be "1" in this case but is "0".Suggested fix: move all assignments to the "has value" flag after the
std::construct_at
calls like this:I wonder if this analysis is correct or if I'm missing some subtleties with
std::construct_at
.The text was updated successfully, but these errors were encountered: