public inbox for gcc-help@gcc.gnu.org
 help / color / mirror / Atom feed
* g++ problem with order of evaluation of arguments of delete.
@ 2023-05-04  9:45 Georg-Johann Lay
  2023-05-04  9:58 ` LIU Hao
                   ` (2 more replies)
  0 siblings, 3 replies; 11+ messages in thread
From: Georg-Johann Lay @ 2023-05-04  9:45 UTC (permalink / raw)
  To: gcc-help

Given the following C++ code:

struct Lexer;

struct Token
{
     Lexer* const lexer_;
     Token (Lexer *l) : lexer_(l) {}
     ~Token() = default;

     Token() = delete;
     Token (const Token&) = delete;
     Token (Token&&) = delete;
     void operator= (const Token&) = delete;
     void operator= (Token&&) = delete;
};

struct Lexer
{
     Token *token_;
     Lexer() = default;
     ~Lexer() { delete token_; }

     Lexer (const Lexer&) = delete;
     Lexer (Lexer&&) = delete;
     void operator= (const Lexer&) = delete;
     void operator= (Lexer&&) = delete;
};

int main()
{
     Lexer *lexer = new Lexer();
     Token *token = new Token (lexer);
     lexer->token_ = token;
     delete token->lexer_;
     // delete lexer; // is OK
}

When I compile this with g++ v11.3 (same with g++ from master from 
2023-04-20) and run

$ g++ main-3.cpp -Os -W -Wall -Wextra -dumpbase "" -save-temps -dp  && 
./a.out

Segmentation fault (core dumped)

The assembly shows that the generated code does two calls to "delete" 
but just one call to "new", so it's clear something is going wrong.

As far as I understand, the "delete token_" in ~Lexer is a sequence 
point, so that dereferencing token in "delete->lexer_" must be sequenced 
before calling ~Token ?

Segmentation fault also occurs with -O0, but goes away when removing the 
"const" in "Lexer* const lexer_;".

My question: Is this a GCC problem, or a problem with the code and 
sequence points?

Johann

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: g++ problem with order of evaluation of arguments of delete.
  2023-05-04  9:45 g++ problem with order of evaluation of arguments of delete Georg-Johann Lay
@ 2023-05-04  9:58 ` LIU Hao
  2023-05-04 10:02   ` Jonathan Wakely
  2023-05-04 10:00 ` Marc Glisse
  2023-05-04 10:06 ` Jonathan Wakely
  2 siblings, 1 reply; 11+ messages in thread
From: LIU Hao @ 2023-05-04  9:58 UTC (permalink / raw)
  To: Georg-Johann Lay, gcc-help


[-- Attachment #1.1: Type: text/plain, Size: 1563 bytes --]

在 2023/5/4 17:45, Georg-Johann Lay 写道:
> My question: Is this a GCC problem, or a problem with the code and sequence points?

I recommend you learn about smart pointers and stop managing object lifetime by hand.



lh_mouse@lhmouse-xps ~/Desktop $ g++-12 test.cc -fsanitize=address,undefined -g -O2
lh_mouse@lhmouse-xps ~/Desktop $ ./a.out
=================================================================
==75277==ERROR: AddressSanitizer: heap-use-after-free on address 0x602000000030 at pc 0x55629bb062b1 
bp 0x7ffe92a56000 sp 0x7ffe92a55ff0
READ of size 8 at 0x602000000030 thread T0
     #0 0x55629bb062b0 in main /home/lh_mouse/Desktop/test.cc:33
     #1 0x7f6d4aff8d8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
     #2 0x7f6d4aff8e3f in __libc_start_main_impl ../csu/libc-start.c:392
     #3 0x55629bb06394 in _start (/home/lh_mouse/Desktop/a.out+0x2394)

0x602000000030 is located 0 bytes inside of 8-byte region [0x602000000030,0x602000000038)
freed by thread T0 here:
     #0 0x7f6d4bc20530 in operator delete(void*, unsigned long) 
../../../../src/libsanitizer/asan/asan_new_delete.cpp:164
     #1 0x55629bb0626d in Lexer::~Lexer() /home/lh_mouse/Desktop/test.cc:20
     #2 0x55629bb0626d in main /home/lh_mouse/Desktop/test.cc:33

previously allocated by thread T0 here:
     #0 0x7f6d4bc1f488 in operator new(unsigned long) 
../../../../src/libsanitizer/asan/asan_new_delete.cpp:95
     #1 0x55629bb061cc in main /home/lh_mouse/Desktop/test.cc:31






-- 
Best regards,
LIU Hao


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 840 bytes --]

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: g++ problem with order of evaluation of arguments of delete.
  2023-05-04  9:45 g++ problem with order of evaluation of arguments of delete Georg-Johann Lay
  2023-05-04  9:58 ` LIU Hao
@ 2023-05-04 10:00 ` Marc Glisse
  2023-05-04 10:06 ` Jonathan Wakely
  2 siblings, 0 replies; 11+ messages in thread
From: Marc Glisse @ 2023-05-04 10:00 UTC (permalink / raw)
  To: Georg-Johann Lay; +Cc: gcc-help

On Thu, 4 May 2023, Georg-Johann Lay wrote:

> Given the following C++ code:
>
> struct Lexer;
>
> struct Token
> {
>    Lexer* const lexer_;
>    Token (Lexer *l) : lexer_(l) {}
>    ~Token() = default;
>
>    Token() = delete;
>    Token (const Token&) = delete;
>    Token (Token&&) = delete;
>    void operator= (const Token&) = delete;
>    void operator= (Token&&) = delete;
> };
>
> struct Lexer
> {
>    Token *token_;
>    Lexer() = default;
>    ~Lexer() { delete token_; }
>
>    Lexer (const Lexer&) = delete;
>    Lexer (Lexer&&) = delete;
>    void operator= (const Lexer&) = delete;
>    void operator= (Lexer&&) = delete;
> };
>
> int main()
> {
>    Lexer *lexer = new Lexer();
>    Token *token = new Token (lexer);
>    lexer->token_ = token;
>    delete token->lexer_;
>    // delete lexer; // is OK
> }
>
> When I compile this with g++ v11.3 (same with g++ from master from 
> 2023-04-20) and run
>
> $ g++ main-3.cpp -Os -W -Wall -Wextra -dumpbase "" -save-temps -dp  && 
> ./a.out
>
> Segmentation fault (core dumped)
>
> The assembly shows that the generated code does two calls to "delete" but 
> just one call to "new", so it's clear something is going wrong.
>
> As far as I understand, the "delete token_" in ~Lexer is a sequence point, so 
> that dereferencing token in "delete->lexer_" must be sequenced before calling 
> ~Token ?
>
> Segmentation fault also occurs with -O0, but goes away when removing the 
> "const" in "Lexer* const lexer_;".
>
> My question: Is this a GCC problem, or a problem with the code and sequence 
> points?

I think this is a GCC problem. It normally uses a SAVE_EXPR, but seems to 
consider token->lexer_ as "safe".

         try
           {
             Lexer::~Lexer ((struct Lexer *) token->lexer_);
           }
         finally
           {
             operator delete ((void *) token->lexer_, 8);
           }

whereas if I write delete (token->lexer_ + i); where i is 0, I get

         try
           {
             Lexer::~Lexer (SAVE_EXPR <(struct Lexer *) token->lexer_ + 
(sizetype) (i * 8)>);
           }
         finally
           {
             operator delete ((void *) (SAVE_EXPR <(struct Lexer *) 
token->lexer_ + (sizetype) (i * 8)>), 8);
           }

which works.

-- 
Marc Glisse

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: g++ problem with order of evaluation of arguments of delete.
  2023-05-04  9:58 ` LIU Hao
@ 2023-05-04 10:02   ` Jonathan Wakely
  0 siblings, 0 replies; 11+ messages in thread
From: Jonathan Wakely @ 2023-05-04 10:02 UTC (permalink / raw)
  To: LIU Hao; +Cc: Georg-Johann Lay, gcc-help

On Thu, 4 May 2023 at 10:59, LIU Hao via Gcc-help <gcc-help@gcc.gnu.org> wrote:
>
> 在 2023/5/4 17:45, Georg-Johann Lay 写道:
> > My question: Is this a GCC problem, or a problem with the code and sequence points?
>
> I recommend you learn about smart pointers and stop managing object lifetime by hand.

This is a GCC bug and has nothing to do with any stylistic problem in the code.

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: g++ problem with order of evaluation of arguments of delete.
  2023-05-04  9:45 g++ problem with order of evaluation of arguments of delete Georg-Johann Lay
  2023-05-04  9:58 ` LIU Hao
  2023-05-04 10:00 ` Marc Glisse
@ 2023-05-04 10:06 ` Jonathan Wakely
  2023-05-04 10:12   ` Jonathan Wakely
  2 siblings, 1 reply; 11+ messages in thread
From: Jonathan Wakely @ 2023-05-04 10:06 UTC (permalink / raw)
  To: Georg-Johann Lay; +Cc: gcc-help

On Thu, 4 May 2023 at 10:46, Georg-Johann Lay <avr@gjlay.de> wrote:
>
> Given the following C++ code:
>
> struct Lexer;
>
> struct Token
> {
>      Lexer* const lexer_;
>      Token (Lexer *l) : lexer_(l) {}
>      ~Token() = default;
>
>      Token() = delete;
>      Token (const Token&) = delete;
>      Token (Token&&) = delete;
>      void operator= (const Token&) = delete;
>      void operator= (Token&&) = delete;
> };
>
> struct Lexer
> {
>      Token *token_;
>      Lexer() = default;
>      ~Lexer() { delete token_; }
>
>      Lexer (const Lexer&) = delete;
>      Lexer (Lexer&&) = delete;
>      void operator= (const Lexer&) = delete;
>      void operator= (Lexer&&) = delete;
> };
>
> int main()
> {
>      Lexer *lexer = new Lexer();
>      Token *token = new Token (lexer);
>      lexer->token_ = token;
>      delete token->lexer_;
>      // delete lexer; // is OK
> }
>
> When I compile this with g++ v11.3 (same with g++ from master from
> 2023-04-20) and run
>
> $ g++ main-3.cpp -Os -W -Wall -Wextra -dumpbase "" -save-temps -dp  &&
> ./a.out
>
> Segmentation fault (core dumped)
>
> The assembly shows that the generated code does two calls to "delete"
> but just one call to "new", so it's clear something is going wrong.
>
> As far as I understand, the "delete token_" in ~Lexer is a sequence
> point, so that dereferencing token in "delete->lexer_" must be sequenced
> before calling ~Token ?
>
> Segmentation fault also occurs with -O0, but goes away when removing the
> "const" in "Lexer* const lexer_;".
>
> My question: Is this a GCC problem, or a problem with the code and
> sequence points?

It's definitely a GCC bug.

The code is compiled to something like:

token->lexer_->~Lexer();
operator delete(token->lexer_);

But that means that we evaluate 'token' twice, even though it's been
invalidated by the destructor. It should be compiled to something more
like:

auto* p = token->lexer_;
p->~Lexer();
operator delete(p);

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: g++ problem with order of evaluation of arguments of delete.
  2023-05-04 10:06 ` Jonathan Wakely
@ 2023-05-04 10:12   ` Jonathan Wakely
  2023-05-04 10:25     ` Jonathan Wakely
                       ` (2 more replies)
  0 siblings, 3 replies; 11+ messages in thread
From: Jonathan Wakely @ 2023-05-04 10:12 UTC (permalink / raw)
  To: Georg-Johann Lay; +Cc: gcc-help

On Thu, 4 May 2023 at 11:06, Jonathan Wakely <jwakely.gcc@gmail.com> wrote:
>
> On Thu, 4 May 2023 at 10:46, Georg-Johann Lay <avr@gjlay.de> wrote:
> >
> > Given the following C++ code:
> >
> > struct Lexer;
> >
> > struct Token
> > {
> >      Lexer* const lexer_;
> >      Token (Lexer *l) : lexer_(l) {}
> >      ~Token() = default;
> >
> >      Token() = delete;
> >      Token (const Token&) = delete;
> >      Token (Token&&) = delete;
> >      void operator= (const Token&) = delete;
> >      void operator= (Token&&) = delete;
> > };
> >
> > struct Lexer
> > {
> >      Token *token_;
> >      Lexer() = default;
> >      ~Lexer() { delete token_; }
> >
> >      Lexer (const Lexer&) = delete;
> >      Lexer (Lexer&&) = delete;
> >      void operator= (const Lexer&) = delete;
> >      void operator= (Lexer&&) = delete;
> > };
> >
> > int main()
> > {
> >      Lexer *lexer = new Lexer();
> >      Token *token = new Token (lexer);
> >      lexer->token_ = token;
> >      delete token->lexer_;
> >      // delete lexer; // is OK
> > }
> >
> > When I compile this with g++ v11.3 (same with g++ from master from
> > 2023-04-20) and run
> >
> > $ g++ main-3.cpp -Os -W -Wall -Wextra -dumpbase "" -save-temps -dp  &&
> > ./a.out
> >
> > Segmentation fault (core dumped)
> >
> > The assembly shows that the generated code does two calls to "delete"
> > but just one call to "new", so it's clear something is going wrong.
> >
> > As far as I understand, the "delete token_" in ~Lexer is a sequence
> > point, so that dereferencing token in "delete->lexer_" must be sequenced
> > before calling ~Token ?
> >
> > Segmentation fault also occurs with -O0, but goes away when removing the
> > "const" in "Lexer* const lexer_;".
> >
> > My question: Is this a GCC problem, or a problem with the code and
> > sequence points?
>
> It's definitely a GCC bug.
>
> The code is compiled to something like:
>
> token->lexer_->~Lexer();
> operator delete(token->lexer_);
>
> But that means that we evaluate 'token' twice, even though it's been
> invalidated by the destructor. It should be compiled to something more
> like:
>
> auto* p = token->lexer_;
> p->~Lexer();
> operator delete(p);

The C++ standard is clear, see [expr.delete] p4:

"The cast-expression in a delete-expression shall be evaluated exactly once."

That wording has been present since C++98.

Please file a bug.

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: g++ problem with order of evaluation of arguments of delete.
  2023-05-04 10:12   ` Jonathan Wakely
@ 2023-05-04 10:25     ` Jonathan Wakely
  2023-05-04 10:54       ` Georg-Johann Lay
  2023-05-04 10:38     ` Georg-Johann Lay
  2023-05-10  1:31     ` LIU Hao
  2 siblings, 1 reply; 11+ messages in thread
From: Jonathan Wakely @ 2023-05-04 10:25 UTC (permalink / raw)
  To: Georg-Johann Lay; +Cc: gcc-help

On Thu, 4 May 2023 at 11:12, Jonathan Wakely <jwakely.gcc@gmail.com> wrote:
>
> On Thu, 4 May 2023 at 11:06, Jonathan Wakely <jwakely.gcc@gmail.com> wrote:
> >
> > On Thu, 4 May 2023 at 10:46, Georg-Johann Lay <avr@gjlay.de> wrote:
> > >
> > > Given the following C++ code:
> > >
> > > struct Lexer;
> > >
> > > struct Token
> > > {
> > >      Lexer* const lexer_;
> > >      Token (Lexer *l) : lexer_(l) {}
> > >      ~Token() = default;
> > >
> > >      Token() = delete;
> > >      Token (const Token&) = delete;
> > >      Token (Token&&) = delete;
> > >      void operator= (const Token&) = delete;
> > >      void operator= (Token&&) = delete;
> > > };
> > >
> > > struct Lexer
> > > {
> > >      Token *token_;
> > >      Lexer() = default;
> > >      ~Lexer() { delete token_; }
> > >
> > >      Lexer (const Lexer&) = delete;
> > >      Lexer (Lexer&&) = delete;
> > >      void operator= (const Lexer&) = delete;
> > >      void operator= (Lexer&&) = delete;
> > > };
> > >
> > > int main()
> > > {
> > >      Lexer *lexer = new Lexer();
> > >      Token *token = new Token (lexer);
> > >      lexer->token_ = token;
> > >      delete token->lexer_;
> > >      // delete lexer; // is OK
> > > }
> > >
> > > When I compile this with g++ v11.3 (same with g++ from master from
> > > 2023-04-20) and run
> > >
> > > $ g++ main-3.cpp -Os -W -Wall -Wextra -dumpbase "" -save-temps -dp  &&
> > > ./a.out
> > >
> > > Segmentation fault (core dumped)
> > >
> > > The assembly shows that the generated code does two calls to "delete"
> > > but just one call to "new", so it's clear something is going wrong.
> > >
> > > As far as I understand, the "delete token_" in ~Lexer is a sequence
> > > point, so that dereferencing token in "delete->lexer_" must be sequenced
> > > before calling ~Token ?
> > >
> > > Segmentation fault also occurs with -O0, but goes away when removing the
> > > "const" in "Lexer* const lexer_;".
> > >
> > > My question: Is this a GCC problem, or a problem with the code and
> > > sequence points?
> >
> > It's definitely a GCC bug.
> >
> > The code is compiled to something like:
> >
> > token->lexer_->~Lexer();
> > operator delete(token->lexer_);
> >
> > But that means that we evaluate 'token' twice, even though it's been
> > invalidated by the destructor. It should be compiled to something more
> > like:
> >
> > auto* p = token->lexer_;
> > p->~Lexer();
> > operator delete(p);
>
> The C++ standard is clear, see [expr.delete] p4:
>
> "The cast-expression in a delete-expression shall be evaluated exactly once."
>
> That wording has been present since C++98.
>
> Please file a bug.

The bug was already present in gcc 4.1.0, I didn't check anything
older than r0-71179-gc6ff1944941b0c aka r105000

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: g++ problem with order of evaluation of arguments of delete.
  2023-05-04 10:12   ` Jonathan Wakely
  2023-05-04 10:25     ` Jonathan Wakely
@ 2023-05-04 10:38     ` Georg-Johann Lay
  2023-05-04 10:44       ` Jonathan Wakely
  2023-05-10  1:31     ` LIU Hao
  2 siblings, 1 reply; 11+ messages in thread
From: Georg-Johann Lay @ 2023-05-04 10:38 UTC (permalink / raw)
  To: Jonathan Wakely; +Cc: gcc-help



Am 04.05.23 um 12:12 schrieb Jonathan Wakely:
> On Thu, 4 May 2023 at 11:06, Jonathan Wakely <jwakely.gcc@gmail.com> wrote:
>>
>> On Thu, 4 May 2023 at 10:46, Georg-Johann Lay <avr@gjlay.de> wrote:
>>>
>>> Given the following C++ code:
>>>
>>> struct Lexer;
>>>
>>> struct Token
>>> {
>>>       Lexer* const lexer_;
>>>       Token (Lexer *l) : lexer_(l) {}
>>>       ~Token() = default;
>>>
>>>       Token() = delete;
>>>       Token (const Token&) = delete;
>>>       Token (Token&&) = delete;
>>>       void operator= (const Token&) = delete;
>>>       void operator= (Token&&) = delete;
>>> };
>>>
>>> struct Lexer
>>> {
>>>       Token *token_;
>>>       Lexer() = default;
>>>       ~Lexer() { delete token_; }
>>>
>>>       Lexer (const Lexer&) = delete;
>>>       Lexer (Lexer&&) = delete;
>>>       void operator= (const Lexer&) = delete;
>>>       void operator= (Lexer&&) = delete;
>>> };
>>>
>>> int main()
>>> {
>>>       Lexer *lexer = new Lexer();
>>>       Token *token = new Token (lexer);
>>>       lexer->token_ = token;
>>>       delete token->lexer_;
>>>       // delete lexer; // is OK
>>> }
>>>
>>> When I compile this with g++ v11.3 (same with g++ from master from
>>> 2023-04-20) and run
>>>
>>> $ g++ main-3.cpp -Os -W -Wall -Wextra -dumpbase "" -save-temps -dp  &&
>>> ./a.out
>>>
>>> Segmentation fault (core dumped)
>>>
>>> The assembly shows that the generated code does two calls to "delete"
>>> but just one call to "new", so it's clear something is going wrong.
>>>
>>> As far as I understand, the "delete token_" in ~Lexer is a sequence
>>> point, so that dereferencing token in "delete->lexer_" must be sequenced
>>> before calling ~Token ?
>>>
>>> Segmentation fault also occurs with -O0, but goes away when removing the
>>> "const" in "Lexer* const lexer_;".
>>>
>>> My question: Is this a GCC problem, or a problem with the code and
>>> sequence points?
>>
>> It's definitely a GCC bug.
>>
>> The code is compiled to something like:
>>
>> token->lexer_->~Lexer();
>> operator delete(token->lexer_);
>>
>> But that means that we evaluate 'token' twice, even though it's been
>> invalidated by the destructor. It should be compiled to something more
>> like:
>>
>> auto* p = token->lexer_;
>> p->~Lexer();
>> operator delete(p);
> 
> The C++ standard is clear, see [expr.delete] p4:
> 
> "The cast-expression in a delete-expression shall be evaluated exactly once."
> 
> That wording has been present since C++98.
> 
> Please file a bug.

Thank you. I came across that clause, but why is "token->lexer_" in 
delete's argument a cast?


^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: g++ problem with order of evaluation of arguments of delete.
  2023-05-04 10:38     ` Georg-Johann Lay
@ 2023-05-04 10:44       ` Jonathan Wakely
  0 siblings, 0 replies; 11+ messages in thread
From: Jonathan Wakely @ 2023-05-04 10:44 UTC (permalink / raw)
  To: Georg-Johann Lay; +Cc: gcc-help

On Thu, 4 May 2023 at 11:38, Georg-Johann Lay <avr@gjlay.de> wrote:
>
>
>
> Am 04.05.23 um 12:12 schrieb Jonathan Wakely:
> > On Thu, 4 May 2023 at 11:06, Jonathan Wakely <jwakely.gcc@gmail.com> wrote:
> >>
> >> On Thu, 4 May 2023 at 10:46, Georg-Johann Lay <avr@gjlay.de> wrote:
> >>>
> >>> Given the following C++ code:
> >>>
> >>> struct Lexer;
> >>>
> >>> struct Token
> >>> {
> >>>       Lexer* const lexer_;
> >>>       Token (Lexer *l) : lexer_(l) {}
> >>>       ~Token() = default;
> >>>
> >>>       Token() = delete;
> >>>       Token (const Token&) = delete;
> >>>       Token (Token&&) = delete;
> >>>       void operator= (const Token&) = delete;
> >>>       void operator= (Token&&) = delete;
> >>> };
> >>>
> >>> struct Lexer
> >>> {
> >>>       Token *token_;
> >>>       Lexer() = default;
> >>>       ~Lexer() { delete token_; }
> >>>
> >>>       Lexer (const Lexer&) = delete;
> >>>       Lexer (Lexer&&) = delete;
> >>>       void operator= (const Lexer&) = delete;
> >>>       void operator= (Lexer&&) = delete;
> >>> };
> >>>
> >>> int main()
> >>> {
> >>>       Lexer *lexer = new Lexer();
> >>>       Token *token = new Token (lexer);
> >>>       lexer->token_ = token;
> >>>       delete token->lexer_;
> >>>       // delete lexer; // is OK
> >>> }
> >>>
> >>> When I compile this with g++ v11.3 (same with g++ from master from
> >>> 2023-04-20) and run
> >>>
> >>> $ g++ main-3.cpp -Os -W -Wall -Wextra -dumpbase "" -save-temps -dp  &&
> >>> ./a.out
> >>>
> >>> Segmentation fault (core dumped)
> >>>
> >>> The assembly shows that the generated code does two calls to "delete"
> >>> but just one call to "new", so it's clear something is going wrong.
> >>>
> >>> As far as I understand, the "delete token_" in ~Lexer is a sequence
> >>> point, so that dereferencing token in "delete->lexer_" must be sequenced
> >>> before calling ~Token ?
> >>>
> >>> Segmentation fault also occurs with -O0, but goes away when removing the
> >>> "const" in "Lexer* const lexer_;".
> >>>
> >>> My question: Is this a GCC problem, or a problem with the code and
> >>> sequence points?
> >>
> >> It's definitely a GCC bug.
> >>
> >> The code is compiled to something like:
> >>
> >> token->lexer_->~Lexer();
> >> operator delete(token->lexer_);
> >>
> >> But that means that we evaluate 'token' twice, even though it's been
> >> invalidated by the destructor. It should be compiled to something more
> >> like:
> >>
> >> auto* p = token->lexer_;
> >> p->~Lexer();
> >> operator delete(p);
> >
> > The C++ standard is clear, see [expr.delete] p4:
> >
> > "The cast-expression in a delete-expression shall be evaluated exactly once."
> >
> > That wording has been present since C++98.
> >
> > Please file a bug.
>
> Thank you. I came across that clause, but why is "token->lexer_" in
> delete's argument a cast?

It isn't.

"cast-expression" is just the name of a grammar production, it doesn't
mean there's a cast present.

delete-expression :
  ::opt delete cast-expression
  ::opt delete [ ] cast-expression

cast-expression :
  unary-expression
  ( type-id ) cast-expression

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: g++ problem with order of evaluation of arguments of delete.
  2023-05-04 10:25     ` Jonathan Wakely
@ 2023-05-04 10:54       ` Georg-Johann Lay
  0 siblings, 0 replies; 11+ messages in thread
From: Georg-Johann Lay @ 2023-05-04 10:54 UTC (permalink / raw)
  To: Jonathan Wakely; +Cc: gcc-help



Am 04.05.23 um 12:25 schrieb Jonathan Wakely:
> On Thu, 4 May 2023 at 11:12, Jonathan Wakely <jwakely.gcc@gmail.com> wrote:
>>
>> On Thu, 4 May 2023 at 11:06, Jonathan Wakely <jwakely.gcc@gmail.com> wrote:
>>>
>>> On Thu, 4 May 2023 at 10:46, Georg-Johann Lay <avr@gjlay.de> wrote:
>>>>
>>>> Given the following C++ code:
>>>>
>>>> struct Lexer;
>>>>
>>>> struct Token
>>>> {
>>>>       Lexer* const lexer_;
>>>>       Token (Lexer *l) : lexer_(l) {}
>>>>       ~Token() = default;
>>>>
>>>>       Token() = delete;
>>>>       Token (const Token&) = delete;
>>>>       Token (Token&&) = delete;
>>>>       void operator= (const Token&) = delete;
>>>>       void operator= (Token&&) = delete;
>>>> };
>>>>
>>>> struct Lexer
>>>> {
>>>>       Token *token_;
>>>>       Lexer() = default;
>>>>       ~Lexer() { delete token_; }
>>>>
>>>>       Lexer (const Lexer&) = delete;
>>>>       Lexer (Lexer&&) = delete;
>>>>       void operator= (const Lexer&) = delete;
>>>>       void operator= (Lexer&&) = delete;
>>>> };
>>>>
>>>> int main()
>>>> {
>>>>       Lexer *lexer = new Lexer();
>>>>       Token *token = new Token (lexer);
>>>>       lexer->token_ = token;
>>>>       delete token->lexer_;
>>>>       // delete lexer; // is OK
>>>> }
>>>>
>>>> When I compile this with g++ v11.3 (same with g++ from master from
>>>> 2023-04-20) and run
>>>>
>>>> $ g++ main-3.cpp -Os -W -Wall -Wextra -dumpbase "" -save-temps -dp  &&
>>>> ./a.out
>>>>
>>>> Segmentation fault (core dumped)
>>>>
>>>> The assembly shows that the generated code does two calls to "delete"
>>>> but just one call to "new", so it's clear something is going wrong.
>>>>
>>>> As far as I understand, the "delete token_" in ~Lexer is a sequence
>>>> point, so that dereferencing token in "delete->lexer_" must be sequenced
>>>> before calling ~Token ?
>>>>
>>>> Segmentation fault also occurs with -O0, but goes away when removing the
>>>> "const" in "Lexer* const lexer_;".
>>>>
>>>> My question: Is this a GCC problem, or a problem with the code and
>>>> sequence points?
>>>
>>> It's definitely a GCC bug.
>>>
>>> The code is compiled to something like:
>>>
>>> token->lexer_->~Lexer();
>>> operator delete(token->lexer_);
>>>
>>> But that means that we evaluate 'token' twice, even though it's been
>>> invalidated by the destructor. It should be compiled to something more
>>> like:
>>>
>>> auto* p = token->lexer_;
>>> p->~Lexer();
>>> operator delete(p);
>>
>> The C++ standard is clear, see [expr.delete] p4:
>>
>> "The cast-expression in a delete-expression shall be evaluated exactly once."
>>
>> That wording has been present since C++98.
>>
>> Please file a bug.
> 
> The bug was already present in gcc 4.1.0, I didn't check anything
> older than r0-71179-gc6ff1944941b0c aka r105000

Filed as https://gcc.gnu.org/PR109731

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: g++ problem with order of evaluation of arguments of delete.
  2023-05-04 10:12   ` Jonathan Wakely
  2023-05-04 10:25     ` Jonathan Wakely
  2023-05-04 10:38     ` Georg-Johann Lay
@ 2023-05-10  1:31     ` LIU Hao
  2 siblings, 0 replies; 11+ messages in thread
From: LIU Hao @ 2023-05-10  1:31 UTC (permalink / raw)
  To: Jonathan Wakely, Georg-Johann Lay; +Cc: gcc-help


[-- Attachment #1.1: Type: text/plain, Size: 957 bytes --]

在 2023/5/4 18:12, Jonathan Wakely via Gcc-help 写道:
> The C++ standard is clear, see [expr.delete] p4:
> 
> "The cast-expression in a delete-expression shall be evaluated exactly once."
> 
> That wording has been present since C++98.
> 
> Please file a bug.

The issue here about C++ standard is that nothing says the operand of a delete-expression shall be a 
prvalue [1] [2].

It looks to me that, if its operand is an lvalue, the lvalue-to-rvalue conversion of the operand, 
necessary for calling the destructor and deallocation function, may occur twice, because it's not 
part of the delete-expression. So my opinion is that, although GCC behavior is a little unexpected, 
it's reasonable and such code should be avoided.

But [expr.delete]-7 doesn't seem to allow this anyway...


[1] https://cplusplus.github.io/CWG/issues/1642.html
[2] https://cplusplus.github.io/CWG/issues/2728.html



-- 
Best regards,
LIU Hao


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 840 bytes --]

^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2023-05-10  1:31 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-05-04  9:45 g++ problem with order of evaluation of arguments of delete Georg-Johann Lay
2023-05-04  9:58 ` LIU Hao
2023-05-04 10:02   ` Jonathan Wakely
2023-05-04 10:00 ` Marc Glisse
2023-05-04 10:06 ` Jonathan Wakely
2023-05-04 10:12   ` Jonathan Wakely
2023-05-04 10:25     ` Jonathan Wakely
2023-05-04 10:54       ` Georg-Johann Lay
2023-05-04 10:38     ` Georg-Johann Lay
2023-05-04 10:44       ` Jonathan Wakely
2023-05-10  1:31     ` LIU Hao

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).