https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127282
Bug ID: 127282
Summary: [[assume]]: a nested assumption's side effects are not
rolled back during constant evaluation
Product: gcc
Version: 16.2.0
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: c++
Assignee: unassigned at gcc dot gnu.org
Reporter: berne at notadragon dot com
Target Milestone: ---
Created attachment 65531
--> https://gcc.gnu.org/bugzilla/attachment.cgi?id=65531&action=edit
Nested `[[assume]]` leaks a side effect: the second static_assert fires, the
control passes
The operand of an [[assume]] is not evaluated, so a side effect performed
while the constant evaluator speculatively evaluates it must not be visible
afterwards. It is not, until the evaluation of one [[assume]] operand
reaches another during constant evaluation: from then on, everything sequenced
after the inner one escapes.
```
constexpr bool bump(unsigned* p) { *p += 1; return true; }
constexpr bool inner(unsigned* p) { [[assume(*p < 100)]]; return true; }
// Control: a single tracker; the modification is correctly rolled back.
constexpr unsigned plain()
{
unsigned x = 0;
[[assume(bump(&x))]];
return x;
}
// The bug: inner's tracker is created and destroyed before bump runs, so
// bump's store escapes.
constexpr unsigned nested()
{
unsigned x = 0;
[[assume(inner(&x) && bump(&x))]];
return x;
}
static_assert(plain() == 0, "plain: modification leaked out of [[assume]]");
static_assert(nested() == 0, "nested: modification leaked out of [[assume]]");
int main() { }
```
```
$ ./gcc-16.2.0/bin/g++ -std=c++23 -fdiagnostics-text-art-charset=ascii \
-fsyntax-only nested-assume-side-effect-leak.cpp
nested-assume-side-effect-leak.cpp:34:24: error: static assertion failed:
nested: modification leaked out of [[assume]]
34 | static_assert(nested() == 0, "nested: modification leaked out of
[[assume]]");
| ~~~~~~~~~^~~~
* the comparison reduces to '(1 == 0)'
```
plain() and nested() differ only in that nested()'s operand first calls a
constexpr function that contains an [[assume]] of its own. plain() passes.
DISCOVERY
Found while adding -Wcontract-constexpr-side-effect to a C++26 contracts
implementation -- a warning for a contract predicate that modifies state
during constant evaluation. That made the contract predicate a SECOND user
of modifiable_tracker, where previously only [[assume]] used it, which
led to quickly uncovering the bug and generating a reproducer that did not
involve the new warning.
ANALYSIS
modifiable_tracker (gcc/cp/constexpr.cc) is what keeps the speculative
evaluation invisible: while one is active, constexpr_global_ctx's store path
refuses writes to objects created outside the operand, and the destructor
rolls back the ones it allowed.
Trackers nest -- an [[assume]] whose operand calls a constexpr function
containing another [[assume]] starts a second one -- but ~modifiable_tracker
ends with
global->modifiable = nullptr;
rather than restoring the ENCLOSING tracker's set. So everything in the
outer operand sequenced after the inner tracker is destroyed runs untracked:
neither refused nor recorded, and so never rolled back. The two neighbouring
fields in the same class already save and restore.
VERSIONS -- all on x86_64-linux-gnu
source version leaks
compiler-explorer 13.4.0 yes
compiler-explorer 14.4.0 yes
compiler-explorer 15.3.0 yes
compiler-explorer 16.1.0 yes
compiler-explorer 16.2.0 yes
compiler-explorer 17.0.0 20260909, 919c0d16c91 yes
local build -g 17.0.0 20260909, 7dab38c9d71 yes
```
$ ./gcc-16.2.0/bin/g++ -v
Using built-in specs.
COLLECT_GCC=./gcc-16.2.0/bin/g++
COLLECT_LTO_WRAPPER=/home/jberne4/repos/compilers/gcc-16.2.0/bin/../libexec/gcc/x86_64-linux-gnu/16.2.0/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../gcc-16.2.0/configure
--prefix=/opt/compiler-explorer/gcc-build/staging --build=x86_64-linux-gnu
--host=x86_64-linux-gnu --target=x86_64-linux-gnu --disable-bootstrap
--enable-multiarch --with-abi=m64 --with-multilib-list=m32,m64,mx32
--enable-multilib --enable-clocale=gnu
--enable-languages=c,c++,fortran,ada,objc,obj-c++,go,d,m2,rust,cobol,algol68
--enable-ld=yes --enable-gold=yes --enable-libstdcxx-time=yes
--enable-linker-build-id --enable-lto --enable-plugins --enable-threads=posix
--with-pkgversion=Compiler-Explorer-Build-gcc--binutils-2.44
Thread model: posix
Supported LTO compression algorithms: zlib
gcc version 16.2.0 (Compiler-Explorer-Build-gcc--binutils-2.44)
```