[gcc r15-11391] tree-optimization/126194 - bogus PTA with C++ delete

Richard Biener via Gcc-cvs <[email protected]>
Newsgroups gmane.comp.gcc.cvs
Message-ID <[email protected]>
https://gcc.gnu.org/g:0c285c6bbb59219906072a8aeb265d84cdc99df5

commit r15-11391-g0c285c6bbb59219906072a8aeb265d84cdc99df5
Author: Richard Biener <[email protected]>
Date:   Mon Jul 13 10:03:55 2026 +0200

    tree-optimization/126194 - bogus PTA with C++ delete
    
    We currently ignore all delete calls from delete expressions for
    the purpose of generating PTA constraints.  That's in error from
    r11-3612-g4f4ced28826ece which did a tree-wide change to clarify
    and improve eliding of new/delete pairs.  The following restores
    the original restriction of only ignoring calls to the standard
    library copy of delete.  This also adds a check on
    -fassume-sane-operators-new-delete (which is the default).
    
            PR tree-optimization/126194
            * tree-ssa-structalias.cc (find_func_aliases_for_call):
            Only ignore calls to delete if DECL_IS_REPLACEABLE_OPERATOR.
    
            * g++.dg/lto/pr126194.h: New testcase.
            * g++.dg/lto/pr126194_0.C: Likewise.
            * g++.dg/lto/pr126194_1.C: Likewise.
            * g++.dg/lto/pr126194_2.C: Likewise.
    
    (cherry picked from commit 080af1a675d94dc25d248b2638e82c03e1476c01)

Diff:
---
 gcc/testsuite/g++.dg/lto/pr126194.h   | 87 +++++++++++++++++++++++++++++++++++
 gcc/testsuite/g++.dg/lto/pr126194_0.C | 71 ++++++++++++++++++++++++++++
 gcc/testsuite/g++.dg/lto/pr126194_1.C | 41 +++++++++++++++++
 gcc/testsuite/g++.dg/lto/pr126194_2.C |  8 ++++
 gcc/tree-ssa-structalias.cc           |  2 +
 5 files changed, 209 insertions(+)

diff --git a/gcc/testsuite/g++.dg/lto/pr126194.h b/gcc/testsuite/g++.dg/lto/pr126194.h
new file mode 100644
index 000000000000..21db1d9885d9
--- /dev/null
+++ b/gcc/testsuite/g++.dg/lto/pr126194.h
@@ -0,0 +1,87 @@
+#pragma once
+
+using Size = decltype(sizeof(0));
+
+struct A
+{
+    void (*const f_)(void**) noexcept;
+    void** const p_;
+    Size const a_;
+
+    A(void** pp, void (*f)(void**) noexcept, Size a) noexcept
+        : f_(f), p_(pp), a_(a) {}
+
+    void reset() const noexcept
+    {
+        if (*p_) {
+            (*f_)(p_);
+        }
+    }
+
+    A& operator=(const A&) = delete;
+
+    void* create(Size size, const void* caller, struct R& r) const;
+    void destroy(void* p, const void* caller, struct R& r) const noexcept;
+};
+
+struct R
+{
+    virtual ~R() = default;
+    virtual void* allocate(Size size, const void* caller) = 0;
+    virtual void deallocate(void* p, const void* caller) noexcept = 0;
+};
+
+R& get_r() noexcept;
+void touch(const void* p) noexcept;
+
+struct B
+{
+    void* q = nullptr;
+
+    static void cb(void** pp) noexcept
+    {
+        B* self = reinterpret_cast<B*>(pp);
+        if (self->q) {
+            extern void release_q(void*);
+            release_q(self->q);
+        }
+        self->q = nullptr;
+    }
+
+    ~B() noexcept
+    {
+        touch(this);
+        if (q) {
+            extern void release_q(void*);
+            release_q(q);
+        }
+    }
+
+    B() noexcept = default;
+    B(const B&) = delete;
+    B& operator=(const B&) = delete;
+    B(B&& other) noexcept : q(other.q)
+    {
+        other.q = nullptr;
+    }
+    B& operator=(B&& other) noexcept
+    {
+        if (this != &other) {
+            if (q) {
+                extern void release_q(void*);
+                release_q(q);
+            }
+            q = other.q;
+            other.q = nullptr;
+        }
+        return *this;
+    }
+
+    operator A() noexcept
+    {
+        return A(&q, &cb, alignof(void*));
+    }
+};
+
+void* operator new(Size size, A const& a, R& r) noexcept(false);
+void  operator delete(void* p, A const& a, R& r) noexcept;
diff --git a/gcc/testsuite/g++.dg/lto/pr126194_0.C b/gcc/testsuite/g++.dg/lto/pr126194_0.C
new file mode 100644
index 000000000000..b7c85039862b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/lto/pr126194_0.C
@@ -0,0 +1,71 @@
+// { dg-lto-do run }
+// { dg-lto-options { { -O2 -flto } } }
+
+#include "pr126194.h"
+
+extern "C" void* malloc(Size size) noexcept;
+extern "C" void free(void* p) noexcept;
+
+namespace {
+class M final : public R
+{
+public:
+    void* allocate(Size size, const void* /*caller*/) override
+    {
+        return malloc(size);
+    }
+
+    void deallocate(void* p, const void* /*caller*/) noexcept override
+    {
+        free(p);
+    }
+};
+
+M g_r;
+}
+
+R& get_r() noexcept
+{
+    return g_r;
+}
+
+void touch(const void* p) noexcept
+{
+    static const volatile void* sink = nullptr;
+    sink = p;
+}
+
+void release_q(void* p)
+{
+    free(p);
+}
+
+int g_should_throw = 0;
+
+void* A::create(Size size, const void* caller, R& r) const
+{
+    reset();
+    if (size < a_) size = a_;
+    void* p = r.allocate(size, caller);
+    if (!p) throw 2;
+    *p_ = p;
+    return p;
+}
+
+__attribute__((noinline))
+void A::destroy(void* p, const void* caller, R& r) const noexcept
+{
+    *p_ = nullptr;
+    r.deallocate(p, caller);
+}
+
+void* operator new(Size size, A const& a, R& r) noexcept(false)
+{
+    return a.create(size, __builtin_return_address(0), r);
+}
+
+__attribute__((noinline))
+void operator delete(void* p, A const& a, R& r) noexcept
+{
+    a.destroy(p, __builtin_return_address(0), r);
+}
diff --git a/gcc/testsuite/g++.dg/lto/pr126194_1.C b/gcc/testsuite/g++.dg/lto/pr126194_1.C
new file mode 100644
index 000000000000..e2894cd0105b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/lto/pr126194_1.C
@@ -0,0 +1,41 @@
+#include "pr126194.h"
+
+extern int g_should_throw;
+
+struct T
+{
+    int a;
+    __attribute__((noinline)) T() : a(0)
+    {
+        if (g_should_throw) {
+            throw 1;
+        }
+    }
+};
+
+__attribute__((noinline))
+int make_widget(bool go, void** out_ptr)
+{
+    B b;
+    R& r = get_r();
+    if (go) {
+        T* t = new(b, r) T();
+        (void)t;
+    }
+    *out_ptr = b.q;
+    b.q = nullptr;
+    return 42;
+}
+
+extern "C" __attribute__((visibility("default")))
+int run_repro(int argc)
+{
+    g_should_throw = (argc > 0);
+    void* p = nullptr;
+    try {
+        (void)make_widget(argc > 0, &p);
+        return 1;
+    } catch (...) {
+        return 0;
+    }
+}
diff --git a/gcc/testsuite/g++.dg/lto/pr126194_2.C b/gcc/testsuite/g++.dg/lto/pr126194_2.C
new file mode 100644
index 000000000000..3c024f47a196
--- /dev/null
+++ b/gcc/testsuite/g++.dg/lto/pr126194_2.C
@@ -0,0 +1,8 @@
+// { dg-options "-fno-lto" }
+
+extern "C" int run_repro(int argc);
+
+int main(int argc, char**)
+{
+    return run_repro(argc);
+}
diff --git a/gcc/tree-ssa-structalias.cc b/gcc/tree-ssa-structalias.cc
index f729cf4216a3..377ccf688c18 100644
--- a/gcc/tree-ssa-structalias.cc
+++ b/gcc/tree-ssa-structalias.cc
@@ -5092,7 +5092,9 @@ find_func_aliases_for_call (struct function *fn, gcall *t)
 	 such operator, then the effects for PTA (in particular
 	 the escaping of the pointer) can be ignored.  */
       else if (fndecl
+	       && flag_assume_sane_operators_new_delete
 	       && DECL_IS_OPERATOR_DELETE_P (fndecl)
+	       && DECL_IS_REPLACEABLE_OPERATOR (fndecl)
 	       && gimple_call_from_new_or_delete (t))
 	;
       else
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.