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());

Reply via email to