public inbox for gcc-bugs@sourceware.org
help / color / mirror / Atom feed
* [Bug c++/108565] New: -Wuse-after-free false positive on a shared_ptr implementation triggered by -O2
@ 2023-01-26 23:01 egor_suvorov at mail dot ru
  2023-01-26 23:24 ` [Bug tree-optimization/108565] -Wuse-after-free false positive triggered by -O2 on a shared_ptr implementation pinskia at gcc dot gnu.org
                   ` (4 more replies)
  0 siblings, 5 replies; 6+ messages in thread
From: egor_suvorov at mail dot ru @ 2023-01-26 23:01 UTC (permalink / raw)
  To: gcc-bugs

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

            Bug ID: 108565
           Summary: -Wuse-after-free false positive on a shared_ptr
                    implementation triggered by -O2
           Product: gcc
           Version: unknown
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: c++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: egor_suvorov at mail dot ru
  Target Milestone: ---

May or may not be a duplicate of
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106119

Consider the following code, which is a subset of a shared_ptr's
implementation:

struct shared_ptr {
    int *counter_ = nullptr;
    int *data_ = nullptr;
    shared_ptr(int *data)
        : counter_(data ? new int(1) : nullptr), data_(data) {
    }
    shared_ptr(const shared_ptr &other)
        : counter_(other.counter_), data_(other.data_) {
        if (counter_ != nullptr) {
            ++*counter_;
        }
    }
    ~shared_ptr() {
        if (counter_ != nullptr) {
            --*counter_;
            if (*counter_ == 0) {
                delete counter_;
                delete data_;
            }
        }
    }
};
void foo() {
    shared_ptr a(new int(10));  // should be non-nullptr
    shared_ptr b(a);
    shared_ptr c(new int(20));  // should be non-nullptr
}
int main() {
    foo();
}

`g++ -std=c++17 -Wuse-after-free -O1` compiles this code without any problems,
and it runs without memory leaks or double-frees. However, `g++ -std=c++17
-Wuse-after-free -O2` generates a bogus warning:

In destructor 'shared_ptr::~shared_ptr()',
    inlined from 'void foo()' at x.cpp:27:1:
x.cpp:15:15: warning: pointer used after 'void operator delete(void*, long long
unsigned int)' [-Wuse-after-free]
   15 |             --*counter_;
      |               ^~~~~~~~~
In destructor 'shared_ptr::~shared_ptr()',
    inlined from 'void foo()' at x.cpp:27:1:
x.cpp:17:24: note: call to 'void operator delete(void*, long long unsigned
int)' here
   17 |                 delete counter_;
      |                        ^~~~~~~~

Seems like GCC is not smart enough to understand the underlying logic of
'shared_ptr'. Any of the following transformations remove the warning for me:

* Replacing `new int(10)` or `new int(20)` with nullptr
* Removing the ternary operator from `counter_`'s initialization.
* Replacing `-O2` with `-O1` or `-O0`

I've tested it both on trunk via Godbolt (https://godbolt.org/z/6ed1GY9Y7), and
on my local GCC 12.2 (Windows, msys2) and GCC 12.1 (Ubuntu 22.04).

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

* [Bug tree-optimization/108565] -Wuse-after-free false positive triggered by -O2 on a shared_ptr implementation
  2023-01-26 23:01 [Bug c++/108565] New: -Wuse-after-free false positive on a shared_ptr implementation triggered by -O2 egor_suvorov at mail dot ru
@ 2023-01-26 23:24 ` pinskia at gcc dot gnu.org
  2023-01-26 23:37 ` pinskia at gcc dot gnu.org
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: pinskia at gcc dot gnu.org @ 2023-01-26 23:24 UTC (permalink / raw)
  To: gcc-bugs

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

--- Comment #1 from Andrew Pinski <pinskia at gcc dot gnu.org> ---
The missed optimization is because we don't optimize:

  MEM[(int *)_28] = 2;
  _8 = operator new (4);

  <bb 3> [local count: 1073741825]:
  MEM[(int *)_8] = 20;
  MEM[(int *)_8] ={v} {CLOBBER};
  operator delete (_8, 4);
  _37 = MEM[(int *)_28];

Until after fre5 and then we miss that _37 == 2.

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

* [Bug tree-optimization/108565] -Wuse-after-free false positive triggered by -O2 on a shared_ptr implementation
  2023-01-26 23:01 [Bug c++/108565] New: -Wuse-after-free false positive on a shared_ptr implementation triggered by -O2 egor_suvorov at mail dot ru
  2023-01-26 23:24 ` [Bug tree-optimization/108565] -Wuse-after-free false positive triggered by -O2 on a shared_ptr implementation pinskia at gcc dot gnu.org
@ 2023-01-26 23:37 ` pinskia at gcc dot gnu.org
  2023-01-27 10:37 ` rguenth at gcc dot gnu.org
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: pinskia at gcc dot gnu.org @ 2023-01-26 23:37 UTC (permalink / raw)
  To: gcc-bugs

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

Andrew Pinski <pinskia at gcc dot gnu.org> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
           Keywords|                            |EH

--- Comment #2 from Andrew Pinski <pinskia at gcc dot gnu.org> ---
This is also exceptions related, that is -fno-exceptions causes no warning to
show up and also the function optimized too.

Also using malloc/free works too:
```
#if 1
int *mymalloc(int t)
{
    int *a = (int*)__builtin_malloc(sizeof(int));
    *a = t;
    return a;
}
void myfree(int *a)
{
    __builtin_free(a);
}
#else
#include <new>
int *mymalloc(int t)
{
    return new int(t);
}
void myfree(int *a)
{
    delete a;
}
#endif
struct shared_ptr {
    int *counter_ = nullptr;
    int *data_ = nullptr;
    shared_ptr(int *data)
    {
       // try {
        counter_ = (data ? mymalloc(1) : nullptr);
       // }catch(...) {
      //      counter_ = nullptr;
           // throw;
       // }
        data_ = (data);
    }
    shared_ptr(const shared_ptr &other)
        : counter_(other.counter_), data_(other.data_) {
        if (counter_ != nullptr) {
            ++*counter_;
        }
    }
    ~shared_ptr() {
        if (counter_ != nullptr) {
            --*counter_;
            if (*counter_ == 0) {
                myfree(counter_);
                myfree(data_);
            }
        }
    }
};
void foo() {
    shared_ptr a(mymalloc(10));  // should be non-nullptr
    shared_ptr b(a);
    shared_ptr c(mymalloc(20));  // should be non-nullptr
}
int main() {
    foo();
}
```

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

* [Bug tree-optimization/108565] -Wuse-after-free false positive triggered by -O2 on a shared_ptr implementation
  2023-01-26 23:01 [Bug c++/108565] New: -Wuse-after-free false positive on a shared_ptr implementation triggered by -O2 egor_suvorov at mail dot ru
  2023-01-26 23:24 ` [Bug tree-optimization/108565] -Wuse-after-free false positive triggered by -O2 on a shared_ptr implementation pinskia at gcc dot gnu.org
  2023-01-26 23:37 ` pinskia at gcc dot gnu.org
@ 2023-01-27 10:37 ` rguenth at gcc dot gnu.org
  2023-01-27 11:23 ` redi at gcc dot gnu.org
  2023-01-28 23:49 ` hubicka at gcc dot gnu.org
  4 siblings, 0 replies; 6+ messages in thread
From: rguenth at gcc dot gnu.org @ 2023-01-27 10:37 UTC (permalink / raw)
  To: gcc-bugs

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

Richard Biener <rguenth at gcc dot gnu.org> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
     Ever confirmed|0                           |1
             Status|UNCONFIRMED                 |NEW
   Last reconfirmed|                            |2023-01-27
                 CC|                            |hubicka at gcc dot gnu.org,
                   |                            |jason at gcc dot gnu.org

--- Comment #3 from Richard Biener <rguenth at gcc dot gnu.org> ---
  <bb 2> [local count: 1073741824]:
  _4 = operator new (4);
  MEM[(int *)_4] = 10;
  a ={v} {CLOBBER};
  if (_4 != 0B)
    goto <bb 3>; [53.47%]
  else
    goto <bb 4>; [46.53%]

I suspect 'operator new' is not returns_nonnull for the variant that isn't
noexcept.  That would be my first stab at improving things.  Not sure
what the standard guarantees and where we can adjust things here?  We
currently rely on VRP assumptions to derive _4 != 0 from the store
preceeding it, but value-numbering doesn't (yet) have any of that wired in.

The issue with the CSE of MEM[(int *)_28] is that _28 escapes through
passing 'b' to the not inlined shared_ptr::~shared_ptr (&b) DTOR.  It
looks like modref is not powerful enough to see that this DTOR doesn't
make *this or **this escape (it "escapes" to operator delete maybe).
The late modref sees

modref analyzing 'shared_ptr::~shared_ptr()/7' (ipa=0)
Past summary:
  loads:
    Every base
  stores:
    Every base
  kills:
     Parm 0 param offset:0 offset:0 size:128 max_size:128
  Side effects
  Nondeterministic
  Global memory read
  Global memory written
  parm 0 flags: no_direct_escape

  Analyzing stmt: _10 = this_6(D)->data_;
    Analyzing flags of ssa name: _10
      Analyzing stmt: operator delete (_10, 4); [tail call]
      current flags of _10 no_direct_escape no_indirect_escape
not_returned_directly not_returned_indirectly
      Analyzing stmt: if (_10 != 0B)
      current flags of _10 no_direct_escape no_indirect_escape
not_returned_directly not_returned_indirectly
    flags of ssa name _10 no_direct_escape no_indirect_escape
not_returned_directly not_returned_indirectly
  current flags of this_6(D) no_direct_clobber no_direct_escape
no_indirect_escape not_returned_directly not_returned_indirectly

good!  But:

  Analyzing stmt: _1 = this_6(D)->counter_;
    Analyzing flags of ssa name: _1
      Analyzing stmt: operator delete (_1, 4);
      current flags of _1 no_direct_escape no_indirect_escape
not_returned_directly not_returned_indirectly
      Analyzing stmt: *_1 = _3;
      current flags of _1 no_direct_escape no_indirect_escape
not_returned_directly not_returned_indirectly
      Analyzing stmt: _2 = *_1;
        Analyzing flags of ssa name: _2
          Analyzing stmt: _3 = _2 + -1;
            Analyzing flags of ssa name: _3
              Analyzing stmt: if (_3 == 0)
              current flags of _3 no_direct_clobber no_indirect_clobber
no_direct_escape no_indirect_escape not_returned_directly
not_returned_indirectly no_direct_read no_indirect_read
              Analyzing stmt: *_1 = _3;
              ssa name saved to memory
              current flags of _3
            flags of ssa name _3
          current flags of _2
        flags of ssa name _2
      current flags of _1 no_direct_escape not_returned_directly
      Analyzing stmt: if (_1 != 0B)
      current flags of _1 no_direct_escape not_returned_directly
    flags of ssa name _1 no_direct_escape not_returned_directly
  current flags of this_6(D) no_direct_clobber no_direct_escape
not_returned_directly

not sure what it makes dropping he no_indirect_escape - it seems the
'ssa name saved to memory', but that should affect _3 which only
"escapes" to where it was loaded from?

With the modref analysis fixed points-to done before PRE should
improve and thus PRE VN should perform the desired transform earlier.

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

* [Bug tree-optimization/108565] -Wuse-after-free false positive triggered by -O2 on a shared_ptr implementation
  2023-01-26 23:01 [Bug c++/108565] New: -Wuse-after-free false positive on a shared_ptr implementation triggered by -O2 egor_suvorov at mail dot ru
                   ` (2 preceding siblings ...)
  2023-01-27 10:37 ` rguenth at gcc dot gnu.org
@ 2023-01-27 11:23 ` redi at gcc dot gnu.org
  2023-01-28 23:49 ` hubicka at gcc dot gnu.org
  4 siblings, 0 replies; 6+ messages in thread
From: redi at gcc dot gnu.org @ 2023-01-27 11:23 UTC (permalink / raw)
  To: gcc-bugs

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

--- Comment #4 from Jonathan Wakely <redi at gcc dot gnu.org> ---
(In reply to Richard Biener from comment #3)
> I suspect 'operator new' is not returns_nonnull for the variant that isn't
> noexcept.  That would be my first stab at improving things.  Not sure
> what the standard guarantees and where we can adjust things here?

The standard guarantees that the noexcept(false) operator new returns nonnull
or throws, it never returns null.

I thought that was already implicitly returns_nonnull, but maybe not the FE
just treats it as non-null for the new-expression, but that info doesn't make
it into the IR.

I don't think we can just add the attribute in the library because of:

       -fcheck-new
           Check that the pointer returned by "operator new" is non-null before
attempting to modify the storage allocated.  This check is normally unnecessary
because the C++ standard specifies that "operator new" only returns 0 if it is
declared "throw()", in which case the compiler always checks the return value
even without this option.  In all other cases, when "operator new" has a
non-empty exception specification, memory exhaustion is signalled by throwing
"std::bad_alloc".  See also new (nothrow).

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

* [Bug tree-optimization/108565] -Wuse-after-free false positive triggered by -O2 on a shared_ptr implementation
  2023-01-26 23:01 [Bug c++/108565] New: -Wuse-after-free false positive on a shared_ptr implementation triggered by -O2 egor_suvorov at mail dot ru
                   ` (3 preceding siblings ...)
  2023-01-27 11:23 ` redi at gcc dot gnu.org
@ 2023-01-28 23:49 ` hubicka at gcc dot gnu.org
  4 siblings, 0 replies; 6+ messages in thread
From: hubicka at gcc dot gnu.org @ 2023-01-28 23:49 UTC (permalink / raw)
  To: gcc-bugs

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

--- Comment #5 from Jan Hubicka <hubicka at gcc dot gnu.org> ---
Teaching modref that THIS parameter of all destructors is nonescape looks like
interesting idea (and easy to implement).

Memory stores are currently indeed handled as "anyting may happen". modref does
dataflow at SSA graph.  Making it to walk from non-escaping stores to loads may
make sense, but it is also somewhat heavy but I guess we will eventually want
to have it.

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

end of thread, other threads:[~2023-01-28 23:49 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-01-26 23:01 [Bug c++/108565] New: -Wuse-after-free false positive on a shared_ptr implementation triggered by -O2 egor_suvorov at mail dot ru
2023-01-26 23:24 ` [Bug tree-optimization/108565] -Wuse-after-free false positive triggered by -O2 on a shared_ptr implementation pinskia at gcc dot gnu.org
2023-01-26 23:37 ` pinskia at gcc dot gnu.org
2023-01-27 10:37 ` rguenth at gcc dot gnu.org
2023-01-27 11:23 ` redi at gcc dot gnu.org
2023-01-28 23:49 ` hubicka at gcc dot gnu.org

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).