https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127283

            Bug ID: 127283
           Summary: [c++26][contracts] a contract predicate on a
                    this-capturing lambda reads the closure object as if
                    it were the enclosing class
           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 65532
  --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=65532&action=edit
Predicate reads the closure instead of the captured object: exits 1

A contract predicate on a lambda that captured `this` reads the closure
object as if it were the enclosing class.  It compiles without a diagnostic
and produces wrong code.

```
static int seen = -1;

static bool probe(int observed) { seen = observed; return true; }

struct S {
    int m;

    int through_this()
    {
        // The predicate reads m through the captured `this'.
        auto l = [this]() pre(probe(m)) { return m; };
        return l();
    }
};

int main()
{
    S s{2};
    int body = s.through_this();
    // The body reads m correctly; the predicate should see the same value.
    return (body == 2 && seen == 2) ? 0 : 1;
}
```

```
$ ./gcc-16.2.0/bin/g++ -std=c++26 lambda-this-capture-in-contract.cpp \
    -lstdc++exp -o t
$ ./t; echo $?
1
```

The body reads m correctly and sees 2; the predicate, evaluated on entry to
the same call, does not.

The symptom is nondeterministic if you only watch whether the check fires --
the predicate ends up comparing against a stack address, so whether any
given comparison holds varies between builds.  That is why the reproducer
records the value the predicate saw rather than whether it fired.

The generated operator() shows it directly:

```
_1 = MEM[(struct S *)__closure].m;      // the predicate -- WRONG
_2 = __closure->__this; _3 = _2->m;     // the body      -- right
```

DISCOVERY

Found while implementing capture of enclosing entities by a lambda written
inside a contract predicate ([expr.prim.lambda.capture]/3.3, PR117435), in
the regression test for that work.  It is an older and separate defect that
the new test happened to reach.  An earlier compile-only probe of this shape
had reported "ok", which is exactly the trap described above: nothing fires
reliably, so nothing looks wrong.


ANALYSIS

remap_dummy_this_1 (gcc/cp/contracts.cc) rewrites every tree for which
is_this_parameter is true to DECL_ARGUMENTS of the function being emitted
into.  In a lambda's operator() that first argument is __closure, not an S*,
so the member access reinterprets the closure object.

is_this_parameter (gcc/cp/semantics.cc) is deliberately true for BOTH the
real `this` PARM_DECL and a lambda's captured-`this` proxy -- a VAR_DECL
named `this` whose DECL_VALUE_EXPR is already `__closure->__this`.  The proxy
needs no remapping at all: the remap exists for the DUMMY `this` of a
contract parsed on a declaration, and for re-pointing a real `this` at the
first parameter of an outlined checking function.  Both of those are
PARM_DECLs.


VERSIONS -- all on x86_64-linux-gnu

  source              version                       wrong code
  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)
```

Reply via email to