Bug 124145 - Repeat call to constexpr throwing function doesn't throw
Summary: Repeat call to constexpr throwing function doesn't throw
Status: RESOLVED FIXED
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 16.0
: P3 normal
Target Milestone: 16.0
Assignee: Jakub Jelinek
URL:
Keywords: c++26, rejects-valid
Depends on:
Blocks: constexpr
  Show dependency treegraph
 
Reported: 2026-02-17 19:33 UTC by Ivan Lazaric
Modified: 2026-03-05 20:48 UTC (History)
3 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2026-03-03 00:00:00


Attachments
gcc16-pr124145.patch (776 bytes, patch)
2026-03-03 12:54 UTC, Jakub Jelinek
Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Ivan Lazaric 2026-02-17 19:33:58 UTC
```
struct S {};

constexpr S tail(S x) {
  throw 123;
  return x;
}

constexpr inline void head() { tail(S{}); }

consteval {
  try {
    head();
    asm(""); // never reached
  } catch (int) {
  }

  try {
    head();
    asm(""); // somehow reached
  } catch (int) {
  }
}
```

Flags used for compilation: -std=c++26

`tail()` and `head()` unconditionally throw an int.
Second call to `head()` doesn't seem to throw, and the asm statement is reached.

Compiler output:
```
in 'constexpr' expansion of '<lambda()> static()'
<source>:22:1:   
   22 | }
      | ^
<source>:19:5: error: inline assembly is not a constant expression
   19 |     asm(""); // somehow reached
      |     ^~~
<source>:19:5: note: only unevaluated inline assembly is allowed in a 'constexpr' function in C++20
Compiler returned: 1
```

Godbolt of example:
https://godbolt.org/z/zaoWEd7jG
Comment 1 Tomasz Kamiński 2026-02-24 12:36:03 UTC
The use of consteval block is irrevelant for the issue, the same problem appears for calls inside constexpr functions.
Comment 2 Jakub Jelinek 2026-03-03 12:54:01 UTC
Created attachment 63815 [details]
gcc16-pr124145.patch

Untested fix.
Comment 3 GCC Commits 2026-03-05 20:47:34 UTC
The master branch has been updated by Jakub Jelinek <jakub@gcc.gnu.org>:

https://gcc.gnu.org/g:0970bb8565616f61c6b7a7dd0edbc829b0064703

commit r16-7920-g0970bb8565616f61c6b7a7dd0edbc829b0064703
Author: Jakub Jelinek <jakub@redhat.com>
Date:   Thu Mar 5 21:43:55 2026 +0100

    c++: Avoid caching TARGET_EXPR slot value if exception is thrown from TARGET_EXPR_INITIAL [PR124145]
    
    The following testcase is miscompiled, we throw exception only during
    the first bar () call and not during the second and in that case reach
    the inline asm.
    The problem is that the TARGET_EXPR handling calls
                ctx->global->put_value (new_ctx.object, new_ctx.ctor);
    first for aggregate/vectors, then
            if (is_complex)
              /* In case no initialization actually happens, clear out any
                 void_node from a previous evaluation.  */
              ctx->global->put_value (slot, NULL_TREE);
    and then recurses on TARGET_EXPR_INITIAL.
    Even for is_complex it can actually store partially the result in the
    slot before throwing.
    
    When TARGET_EXPR_INITIAL doesn't throw, we do
      if (ctx->save_expr)
        ctx->save_expr->safe_push (slot);
    and that arranges for the value in slot be invalidated at the end of
    surrounding CLEANUP_POINT_EXPR.
    But in case when it does throw this isn't done.
    
    The following patch fixes it by moving that push to save_expr
    before the if (*jump_target) return NULL_TREE; check.
    
    2026-03-05  Jakub Jelinek  <jakub@redhat.com>
    
            PR c++/124145
            * constexpr.cc (cxx_eval_constant_expression) <case TARGET_EXPR>: Move
            ctx->save_expr->safe_push (slot) call before if (*jump_target) test.
            Use TARGET_EXPR_INITIAL instead of TREE_OPERAND.
    
            * g++.dg/cpp26/constexpr-eh18.C: New test.
Comment 4 Jakub Jelinek 2026-03-05 20:48:47 UTC
Fixed.