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

            Bug ID: 127294
           Summary: [c++26][contracts] a member named unqualified in an
                    explicit-object member function's contract is
                    diagnosed as a constructor/destructor contract check
           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 65539
  --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=65539&action=edit
The four shapes given the wrong message, with four controls including a real
constructor

Naming a non-static data member unqualified in a contract predicate of an
explicit object member function is correctly rejected, but with a message
about a rule that does not apply -- the function is neither a constructor
nor a destructor.

The expected diagnostic is the one the function body already gives for the
same expression: "invalid use of non-static data member 'S::x'".  An
unqualified member means (*this).x, and an explicit object member function
has no this ([expr.prim.this]/1).

```
struct S {
    int x = 0;
    bool ok() const;
};

// (1) Unqualified data member in a precondition.
struct ImplicitThisPre : S {
    void f(this ImplicitThisPre& self) pre(x == 0);
};

// (2) The same in a postcondition.
struct ImplicitThisPost : S {
    int f(this ImplicitThisPost& self) post(r : x == r);
};

// (3) The same in an assertion-statement in the body.
struct ImplicitThisAssert : S {
    void f(this ImplicitThisAssert& self)
    {
        contract_assert(x == 0);
    }
};

// (4) An unqualified member function call in a predicate -- the second of the
// two guards carrying the same defect.
struct ImplicitThisCall : S {
    void f(this ImplicitThisCall& self) pre(ok());
};

// CONTROL: the explicit `this' spelling was always right --
// "'this' is unavailable for explicit object member functions".
struct ExplicitThis : S {
    void f(this ExplicitThis& self) pre(this->x == 0);
};

// CONTROL: naming the member through the explicit object parameter is of
// course fine, and must keep compiling.
struct ViaSelf : S {
    void f(this ViaSelf& self) pre(self.x == 0) pre(self.ok());
};

// CONTROL: an ordinary implicit-object member function may name `this' and
// its members unqualified.
struct ImplicitObject : S {
    void g() const pre(this->x == 0) pre(x == 0);
};

// CONTROL: a genuine constructor precondition is where that message belongs,
// and it must keep being produced there.
struct RealCtor : S {
    RealCtor() pre(x == 0);
};
```

```
$ ./gcc-16.2.0/bin/g++ -std=c++26 -fsyntax-only \
    xobj-member-in-predicate-ctor-message.cpp
xobj-member-in-predicate-ctor-message.cpp: In explicit object member function
'void ImplicitThisPre::f(this ImplicitThisPre&)':
xobj-member-in-predicate-ctor-message.cpp:22:44: error: 'S::x' 'this' required
when accessing a member within a constructor precondition or destructor
postcondition contract check
   22 |     void f(this ImplicitThisPre& self) pre(x == 0);
      |                                            ^
xobj-member-in-predicate-ctor-message.cpp: In explicit object member function
'int ImplicitThisPost::f(this ImplicitThisPost&)':
xobj-member-in-predicate-ctor-message.cpp:27:49: error: 'S::x' 'this' required
when accessing a member within a constructor precondition or destructor
postcondition contract check
   27 |     int f(this ImplicitThisPost& self) post(r : x == r);
      |                                                 ^
xobj-member-in-predicate-ctor-message.cpp: In explicit object member function
'void ImplicitThisAssert::f(this ImplicitThisAssert&)':
xobj-member-in-predicate-ctor-message.cpp:34:25: error: 'S::x' 'this' required
when accessing a member within a constructor precondition or destructor
postcondition contract check
   34 |         contract_assert(x == 0);
      |                         ^
xobj-member-in-predicate-ctor-message.cpp: In explicit object member function
'void ImplicitThisCall::f(this ImplicitThisCall&)':
xobj-member-in-predicate-ctor-message.cpp:41:45: error: 'bool S::ok() const'
'this' required when accessing a member within a constructor precondition or
destructor postcondition contract check
   41 |     void f(this ImplicitThisCall& self) pre(ok());
      |                                             ^~
xobj-member-in-predicate-ctor-message.cpp: In explicit object member function
'void ExplicitThis::f(this ExplicitThis&)':
xobj-member-in-predicate-ctor-message.cpp:47:41: error: 'this' is unavailable
for explicit object member functions
   47 |     void f(this ExplicitThis& self) pre(this->x == 0);
      |                                         ^~~~
xobj-member-in-predicate-ctor-message.cpp:47:31: note: use explicit object
parameter 'self' instead
   47 |     void f(this ExplicitThis& self) pre(this->x == 0);
      |            ~~~~~~~~~~~~~~~~~~~^~~~
xobj-member-in-predicate-ctor-message.cpp: In constructor
'RealCtor::RealCtor()':
xobj-member-in-predicate-ctor-message.cpp:65:20: error: 'S::x' 'this' required
when accessing a member within a constructor precondition or destructor
postcondition contract check
   65 |     RealCtor() pre(x == 0);
      |                    ^
```

Four shapes are affected -- a precondition, a postcondition, an
assertion-statement, and an unqualified member function call -- and the
message is the same wrong one in each.

ExplicitThis, spelling `this->x` outright, gets the right words in the
diagnostic: "'this' is unavailable for explicit object member functions",
with a note suggesting the explicit object parameter.  RealCtor, a
genuine constructor precondition, is where that constructor/destructor
message belongs and must keep producing it.

DISCOVERY

Found by an audit comparing every test in a C++26 contracts implementation's
own suite against stock trunk, looking for cases that pass locally only
because they were fixed locally.  The member-function-call shape (4) had no
test coverage at all on either side.

ANALYSIS

Two guards in the contracts code reach for the constructor/destructor
message when an unqualified member cannot be resolved in a predicate -- one
for a data member, one for a member function call.  Both assume the only way
to arrive there is from a constructor's precondition or a destructor's
postcondition.  An explicit object member function is a third way in, and
neither guard checks.

VERSIONS -- all on x86_64-linux-gnu

  source              version                       wrong message
  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