https://gcc.gnu.org/g:21a88667c67ddf1f3086b7f05dc88601f1c6b070
commit r17-4575-g21a88667c67ddf1f3086b7f05dc88601f1c6b070 Author: Joshua Berne <[email protected]> Date: Thu Sep 17 13:40:42 2026 -0400 c++/contracts: assertion predicate re-invoking constexpr function [PR125459] The RESULT_DECL of a function during constant evaluation has its value stored in the constexpr_global_ctx, and once its lifetime ends void_list_node is put in its place. If the same function is then called later during the same constant evaluation from within the scope of a modifiable_tracker (i.e., during speculative evaluation of a contract assertion, or evaluation of the predicate of an [[assume]]) the population of the return value will find an entry (void_list_node) already in the context and treat the call as an attempt to modify outside state, resulting in errors. This change updates the put_value member of constexpr_global_ctx to recognize void_list_node values in the map as not being a concern for what modifiable is tracking, and treats them the same way it treats not yet having a value in the map for that key. void_node is not treated the same way because that would indicate an object that has been destroyed but whose storage has not yet been deallocated. PR c++/125459 - [contracts] "contract condition is not constant" in constexpr function PR c++/125459 gcc/cp/ChangeLog: * constexpr.cc (constexpr_global_ctx::put_value): Treat void_list_node as not having been in the map for modifiable tracking. gcc/testsuite/ChangeLog: * g++.dg/contracts/cpp26/pr125459.C: New test. Signed-off-by: Joshua Berne <[email protected]> Diff: --- gcc/cp/constexpr.cc | 12 ++++++++++-- gcc/testsuite/g++.dg/contracts/cpp26/pr125459.C | 15 +++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc index 18d361c24325..e285582e4c02 100644 --- a/gcc/cp/constexpr.cc +++ b/gcc/cp/constexpr.cc @@ -1276,8 +1276,16 @@ public: } void put_value (tree t, tree v) { - bool already_in_map = values.put (t, v); - if (!already_in_map && modifiable) + /* If modifiable is not null, any key that was not previously in the + map, or that has been destroyed and freed (as indicated by + void_list_node), is tracked in modifiable. If the object is there, + or its storage remains (as indicated by void_node), then we don't + track it as modifiable. */ + bool already_in_map; + tree &slot = values.get_or_insert (t, &already_in_map); + bool newval = !already_in_map || slot == void_list_node; + slot = v; + if (newval && modifiable) modifiable->add (t); } void destroy_value (tree t, bool past_storage_end = true) diff --git a/gcc/testsuite/g++.dg/contracts/cpp26/pr125459.C b/gcc/testsuite/g++.dg/contracts/cpp26/pr125459.C new file mode 100644 index 000000000000..44c159306e8b --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/cpp26/pr125459.C @@ -0,0 +1,15 @@ +// Check that invoking a function during constant evaluation, then calling the +// same function with different arguments within a contract assertion, works. +// { dg-do compile { target c++26 } } +// { dg-additional-options "-fcontracts -fcontract-evaluation-semantic=enforce" } + +constexpr bool f(int) { return true; } + +constexpr bool g() +{ + f(0); + contract_assert(f(1)); // force another evaluation of f + return true; +} + +static_assert(g());
