The bytecode compiler was incorrectly emitting an
RVOPtr
opcode for void functions if the return expression had a non-void type
(e.g. from a conditional operator). This triggered an assertion in the
interpreter because void functions lack RVO metadata.
This patch updates
visitReturnStmt
to check the function’s return type and use
discard()
for the expression in void contexts, preventing erroneous RVO pathing.
Fixes#176536
The current interpreter does _not_ evaluate function calls when checking
for a potential constant expression.
However, it _does_ evaluate the initializers of constructors. In the
bytecode interpreter, this is harder because we compile the initializers
and the body of a constructor all in the same function.
Add a special opcode that we emit after the constructor initializers and
that aborts when we're checking for a potential constant expression.
Previously, we had two very similar diagnostics, "read of object outside
its lifetime" and "read of variable whose lifetime has ended".
The difference, as far as I can tell, is that the latter was used when
the variable was created in a function frame that has since vanished,
i.e. in this case:
```c++
constexpr const int& return_local() { return 5; }
static_assert(return_local() == 5);
```
so the output used to be:
```console
array.cpp:602:15: error: static assertion expression is not an integral constant expression
602 | static_assert(return_local() == 5);
| ^~~~~~~~~~~~~~~~~~~
array.cpp:602:15: note: read of temporary whose lifetime has ended
array.cpp:601:46: note: temporary created here
601 | constexpr const int& return_local() { return 5; }
| ^
```
But then this scenario gets the other diagnostic:
```c++
constexpr int b = b;
```
```console
array.cpp:603:15: error: constexpr variable 'b' must be initialized by a constant expression
603 | constexpr int b = b;
| ^ ~
array.cpp:603:19: note: read of object outside its lifetime is not allowed in a constant expression
603 | constexpr int b = b;
| ^
```
With this patch, we diagnose both cases similarly:
```c++
constexpr const int& return_local() { return 5; }
static_assert(return_local() == 5);
constexpr int b = b;
```
```console
array.cpp:602:15: error: static assertion expression is not an integral constant expression
602 | static_assert(return_local() == 5);
| ^~~~~~~~~~~~~~~~~~~
array.cpp:602:15: note: read of object outside its lifetime is not allowed in a constant expression
602 | static_assert(return_local() == 5);
| ^~~~~~~~~~~~~~
array.cpp:601:46: note: temporary created here
601 | constexpr const int& return_local() { return 5; }
| ^
array.cpp:603:15: error: constexpr variable 'b' must be initialized by a constant expression
603 | constexpr int b = b;
| ^ ~
array.cpp:603:19: note: read of object outside its lifetime is not allowed in a constant expression
603 | constexpr int b = b;
| ^
```
We do lose the "object" vs. "temporary" distinction in the note and only
mention it in the "created here" note. That can be added back if it's
important enough. I wasn't sure.
We might create a local temporary variable for a ParmVarDecl, in which
case a DeclRefExpr for that ParmVarDecl should _still_ result in us
choosing the parameter, not that local.
When casting a 0 to a pointer type, the IsNullPtr flag was always set to
false, leading to weird results like a pointer with value 0 that isn't a
null pointer.
This caused
```c++
struct B { const int *p;};
template<B> void f() {}
template void f<B{nullptr}>();
template void f<B{fold(reinterpret_cast<int*>(0))}>();
```
to be valid code, since nullptr and (int*)0 aren't equal. This seems
weird and GCC doesn't behave like this.
This has been explicitly forbidden since C++11, but somehow the edge
case of converting a function pointer to void* using a cast like
`(void*)f` wasn't handled.
Fixes#150340 .
... between unrelated declarations or literals.
Leaving this small (I haven't run the whole test suite locally) to get
some feedback on the wording and implementation first.
The output of the sample in
https://github.com/llvm/llvm-project/issues/117409 is now:
```console
./array.cpp:57:6: warning: expression result unused [-Wunused-value]
57 | am - aj.af();
| ~~ ^ ~~~~~~~
./array.cpp:70:8: error: call to consteval function 'L::L<bx>' is not a constant expression
70 | q(0, [] {
| ^
./array.cpp:57:6: note: arithmetic on addresses of literals has unspecified value
57 | am - aj.af();
| ^
./array.cpp:62:5: note: in call to 'al(&""[0], {&""[0]})'
62 | al(bp.af(), k);
| ^~~~~~~~~~~~~~
./array.cpp:70:8: note: in call to 'L<bx>({})'
70 | q(0, [] {
| ^~~~
71 | struct bx {
| ~~~~~~~~~~~
72 | constexpr operator ab<g<l<decltype(""[0])>::e>::e>() { return t(""); }
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
73 | };
| ~~
74 | return bx();
| ~~~~~~~~~~~~
75 | }());
| ~~~
```
The output for
```c++
int a, b;
constexpr int n = &b - &a
```
is now:
```console
./array.cpp:80:15: error: constexpr variable 'n' must be initialized by a constant expression
80 | constexpr int n = &b - &a;
| ^ ~~~~~~~
./array.cpp:80:22: note: arithmetic involving '&b' and '&a' has unspecified value
80 | constexpr int n = &b - &a;
| ^
1 error generated.
```