| Issue |
171575
|
| Summary |
Issue with lifetime extension of temporaries in default member initializers
|
| Labels |
new issue
|
| Assignees |
|
| Reporter |
bartdesmet
|
Given the following definitions:
```cpp
#include <optional>
struct s
{
const std::optional<int>& x = std::nullopt;
};
struct t
{
const s& v;
};
```
and the following usage:
```cpp
#include <iostream>
void f(const s& args)
{
if (args.x)
{
std::cout << *args.x << std::endl;
}
else
{
std::cout << "N/A" << std::endl;
}
}
void g(const t& args)
{
f(args.v);
}
```
we're seeing a stack-use-after-return ASAN error for the following case:
```cpp
int main()
{
const t args{.v = {}};
g(args);
}
```
Looking at the LLVM IR, I'm seeing:
```
define dso_local void @test4()() local_unnamed_addr {
entry:
%ref.tmp = alloca %struct.s, align 8
%ref.tmp1 = alloca %"class.std::optional", align 4
call void @llvm.lifetime.start.p0(i64 8, ptr nonnull %ref.tmp) #6
call void @llvm.lifetime.start.p0(i64 8, ptr nonnull %ref.tmp1) #6
store ptr %ref.tmp1, ptr %ref.tmp, align 8
call void @llvm.lifetime.end.p0(i64 8, ptr nonnull %ref.tmp1) #6
call void @f(s const&)(ptr noundef nonnull align 8 dereferenceable(8) %ref.tmp)
call void @llvm.lifetime.end.p0(i64 8, ptr nonnull %ref.tmp) #6
ret void
}
```
where the lifetime of `ref.tmp1` does not extend.
With clang 19, this produced warning `lifetime extension of temporary created by aggregate initialization using a default member initializer is not yet supported; lifetime of temporary will end at the end of the full-_expression_`.
In contrast, the following works fine:
```cpp
int main()
{
const s args{};
f(args);
}
```
and lifetime of the default-initialized optional gets extended:
```
define dso_local void @test2()() local_unnamed_addr {
entry:
%args = alloca %struct.s, align 8
%ref.tmp = alloca %"class.std::optional", align 4
call void @llvm.lifetime.start.p0(i64 8, ptr nonnull %args) #6
call void @llvm.lifetime.start.p0(i64 8, ptr nonnull %ref.tmp) #6
%_M_engaged.i.i.i.i = getelementptr inbounds i8, ptr %ref.tmp, i64 4
store i8 0, ptr %_M_engaged.i.i.i.i, align 4
store ptr %ref.tmp, ptr %args, align 8
call void @f(s const&)(ptr noundef nonnull align 8 dereferenceable(8) %args)
call void @llvm.lifetime.end.p0(i64 8, ptr nonnull %ref.tmp) #6
call void @llvm.lifetime.end.p0(i64 8, ptr nonnull %args) #6
ret void
}
```
With GCC, lifetime seems to get extended.
I've shared a repro in godbolt at https://godbolt.org/z/PPPG6j53W. This has a custom `my_opt<T>` with a destructor that prints, to further illustrate the difference between GCC and clang. I.e.:
```cpp
const t args{.v = {}};
g(args);
```
prints
```
N/A
~my_opt
```
on GCC, i.e. destruction of the `std::nullopt` instance happens after the call to `g`, but prints
```
~my_opt
N/A
```
on clang.
_______________________________________________
llvm-bugs mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs