c++: fix nested instances of modifiable_tracker [PR127282] PR c++/127282 - [[assume]]: a nested assumption's side effects are not rolled back during constant evaluation During constant evaluation of the predicate of an [[assume]] attribute a pointer to a set of modifiable values is stored in the constexpr_global_ctx. The class modifiable_tracker populates this value when evaluation of the predicate starts and clears it when it is complete, which results in nullptr instead of the original set when an [[assume]] is evaluated during the evaluation of the predicate of another assume. Fix this by storing the previous value (nullptr in non-nested cases) and restoring it upon destruction instead of clearing the global value. PR c++/127282 gcc/cp/ChangeLog: * constexpr.cc (modifiable_tracker::modifiable_tracker): Save the current global pointer to the modifiable set. (modifiable_tracker::~modifiable_tracker): Restore the original pointer to the modifiable set instead of clearing it. gcc/testsuite/ChangeLog: * g++.dg/cpp23/attr-assume10.C: Add a case with a nested assumption. Signed-off-by: Joshua Berne <berne@notadragon.com>
diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc index 7219c55..18d361c 100644 --- a/gcc/cp/constexpr.cc +++ b/gcc/cp/constexpr.cc
@@ -1298,7 +1298,9 @@ /* Helper class for constexpr_global_ctx. In some cases we want to avoid side-effects from evaluation of a particular subexpression of a constant-expression. In such cases we use modifiable_tracker to prevent - modification of variables created outside of that subexpression. + modification of variables created outside of that subexpression. Keep and + then restore the original value of global->modifiable so that tracker + instantiations can nest. ??? We could change the hash_set to a hash_map, allow and track external modifications, and roll them back in the destructor. It's not clear to me @@ -1308,8 +1310,10 @@ { hash_set<tree> set; constexpr_global_ctx *global; + hash_set<tree> *previous_set; public: - modifiable_tracker (constexpr_global_ctx *g): global(g) + modifiable_tracker (constexpr_global_ctx *g) + : global (g), previous_set (g->modifiable) { global->modifiable = &set; } @@ -1317,7 +1321,7 @@ { for (tree t: set) global->clear_value (t); - global->modifiable = nullptr; + global->modifiable = previous_set; } };
diff --git a/gcc/testsuite/g++.dg/cpp23/attr-assume10.C b/gcc/testsuite/g++.dg/cpp23/attr-assume10.C index 475555a..3f4af3f 100644 --- a/gcc/testsuite/g++.dg/cpp23/attr-assume10.C +++ b/gcc/testsuite/g++.dg/cpp23/attr-assume10.C
@@ -1,4 +1,4 @@ -// Test that s.i is not modified by the assume. +// Test that s.i is not modified by assume or a nested assume. // { dg-do compile { target c++17 } } struct string @@ -20,3 +20,20 @@ } static_assert (f()); + +// PR c++/127282, a nested assume also needs to not modify values or allow +// other expressions within the enclosing assume to modify values. +constexpr int g() +{ + string s ("foobar"); + [[assume ( + [&](){[[assume (s.length () > 0)]]; return true; }() + && s.length () > 0) + ]]; + if (s.i != 0) __builtin_abort(); + int len = s.length (); + if (s.i != 1) __builtin_abort(); + return len; +} + +static_assert (g());