PR c++/98305 spurious -Wmismatched-new-delete on template instance
authorMartin Sebor <msebor@redhat.com>
Wed, 6 Jan 2021 20:36:18 +0000 (13:36 -0700)
committerMartin Sebor <msebor@redhat.com>
Wed, 6 Jan 2021 20:36:18 +0000 (13:36 -0700)
gcc/ChangeLog:

PR c++/98305
* builtins.c (new_delete_mismatch_p): New overload.
(new_delete_mismatch_p (tree, tree)): Call it.

gcc/testsuite/ChangeLog:

PR c++/98305
* g++.dg/warn/Wmismatched-new-delete-3.C: New test.

gcc/builtins.c
gcc/testsuite/g++.dg/warn/Wmismatched-new-delete-3.C [new file with mode: 0644]

index e770942..02e7815 100644 (file)
@@ -78,6 +78,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "tree-ssa-live.h"
 #include "tree-outof-ssa.h"
 #include "attr-fnspec.h"
+#include "demangle.h"
 
 struct target_builtins default_target_builtins;
 #if SWITCHABLE_TARGET
@@ -13055,121 +13056,152 @@ call_dealloc_argno (tree exp)
   return UINT_MAX;
 }
 
-/* Return true if DELETE_DECL is an operator delete that's not suitable
-   to call with a pointer returned fron NEW_DECL.  */
+/* Return true if DELC doesn't refer to an operator delete that's
+   suitable to call with a pointer returned from the operator new
+   described by NEWC.  */
 
 static bool
-new_delete_mismatch_p (tree new_decl, tree delete_decl)
+new_delete_mismatch_p (const demangle_component &newc,
+                      const demangle_component &delc)
 {
-  tree new_name = DECL_ASSEMBLER_NAME (new_decl);
-  tree delete_name = DECL_ASSEMBLER_NAME (delete_decl);
-
-  /* valid_new_delete_pair_p() returns a conservative result.  A true
-     result is reliable but a false result doesn't necessarily mean
-     the operators don't match.  */
-  if (valid_new_delete_pair_p (new_name, delete_name))
-    return false;
-
-  const char *new_str = IDENTIFIER_POINTER (new_name);
-  const char *del_str = IDENTIFIER_POINTER (delete_name);
-
-  if (*new_str != '_')
-    return *new_str != *del_str;
+  if (newc.type != delc.type)
+    return true;
 
-  ++del_str;
-  if (*++new_str != 'Z')
-    return *new_str != *del_str;
+  switch (newc.type)
+    {
+    case DEMANGLE_COMPONENT_NAME:
+      {
+       int len = newc.u.s_name.len;
+       const char *news = newc.u.s_name.s;
+       const char *dels = delc.u.s_name.s;
+       if (len != delc.u.s_name.len || memcmp (news, dels, len))
+         return true;
 
-  ++del_str;
-  if (*++new_str == 'n')
-    return *del_str != 'd';
+       if (news[len] == 'n')
+         {
+           if (news[len + 1] == 'a')
+             return dels[len] != 'd' || dels[len + 1] != 'a';
+           if (news[len + 1] == 'w')
+             return dels[len] != 'd' || dels[len + 1] != 'l';
+         }
+       return false;
+      }
 
-  if (*new_str != 'N')
-    return *del_str != 'N';
+    case DEMANGLE_COMPONENT_OPERATOR:
+      /* Operator mismatches are handled above.  */
+      return false;
 
-  /* Handle user-defined member operators below.  */
-  ++new_str;
-  ++del_str;
+    case DEMANGLE_COMPONENT_EXTENDED_OPERATOR:
+      if (newc.u.s_extended_operator.args != delc.u.s_extended_operator.args)
+       return true;
+      return new_delete_mismatch_p (*newc.u.s_extended_operator.name,
+                                   *delc.u.s_extended_operator.name);
 
-  do
-    {
-      /* Determine if both operators are members of the same type.
-        If not, they don't match.  */
-      char *new_end, *del_end;
-      unsigned long nlen = strtoul (new_str, &new_end, 10);
-      unsigned long dlen = strtoul (del_str, &del_end, 10);
-      if (nlen != dlen)
+    case DEMANGLE_COMPONENT_FIXED_TYPE:
+      if (newc.u.s_fixed.accum != delc.u.s_fixed.accum
+         || newc.u.s_fixed.sat != delc.u.s_fixed.sat)
        return true;
+      return new_delete_mismatch_p (*newc.u.s_fixed.length,
+                                   *delc.u.s_fixed.length);
 
-      /* Skip past the name length.   */
-      new_str = new_end;
-      del_str = del_end;
+    case DEMANGLE_COMPONENT_CTOR:
+      if (newc.u.s_ctor.kind != delc.u.s_ctor.kind)
+       return true;
+      return new_delete_mismatch_p (*newc.u.s_ctor.name,
+                                   *delc.u.s_ctor.name);
 
-      /* Skip past the names making sure each has the expected length
-        (it would suggest some sort of a corruption if they didn't).  */
-      while (nlen--)
-       if (!*++new_end)
-         return true;
+    case DEMANGLE_COMPONENT_DTOR:
+      if (newc.u.s_dtor.kind != delc.u.s_dtor.kind)
+       return true;
+      return new_delete_mismatch_p (*newc.u.s_dtor.name,
+                                   *delc.u.s_dtor.name);
 
-      for (nlen = dlen; nlen--; )
-       if (!*++del_end)
+    case DEMANGLE_COMPONENT_BUILTIN_TYPE:
+      {
+       /* The demangler API provides no better way to compare built-in
+          types except to by comparing their demangled names. */
+       size_t nsz, dsz;
+       demangle_component *pnc = const_cast<demangle_component *>(&newc);
+       demangle_component *pdc = const_cast<demangle_component *>(&delc);
+       char *nts = cplus_demangle_print (0, pnc, 16, &nsz);
+       char *dts = cplus_demangle_print (0, pdc, 16, &dsz);
+       if (!nts != !dts)
          return true;
+       bool mismatch = strcmp (nts, dts);
+       free (nts);
+       free (dts);
+       return mismatch;
+      }
 
-      /* The names have the expected length.  Compare them.  */
-      if (memcmp (new_str, del_str, dlen))
+    case DEMANGLE_COMPONENT_SUB_STD:
+      if (newc.u.s_string.len != delc.u.s_string.len)
        return true;
+      return memcmp (newc.u.s_string.string, delc.u.s_string.string,
+                    newc.u.s_string.len);
 
-      new_str = new_end;
-      del_str = del_end;
-
-      if (*new_str == 'I')
-       {
-         /* Template instantiation.  */
-         do
-           {
-             ++new_str;
-             ++del_str;
+    case DEMANGLE_COMPONENT_FUNCTION_PARAM:
+    case DEMANGLE_COMPONENT_TEMPLATE_PARAM:
+      return newc.u.s_number.number != delc.u.s_number.number;
 
-             if (*new_str == 'n')
-               break;
-             if (*new_str != *del_str)
-               return true;
-           }
-         while (*new_str);
-       }
+    case DEMANGLE_COMPONENT_CHARACTER:
+      return newc.u.s_character.character != delc.u.s_character.character;
 
-      if (*new_str == 'n')
-       {
-         if (*del_str != 'd')
-           return true;
+    case DEMANGLE_COMPONENT_DEFAULT_ARG:
+    case DEMANGLE_COMPONENT_LAMBDA:
+      if (newc.u.s_unary_num.num != delc.u.s_unary_num.num)
+       return true;
+      return new_delete_mismatch_p (*newc.u.s_unary_num.sub,
+                                   *delc.u.s_unary_num.sub);
+    default:
+      break;
+    }
 
-         ++del_str;
-         if (*++new_str == 'w' && *del_str != 'l')
-           return true;
-         if (*new_str == 'a' && *del_str != 'a')
-           return true;
-         ++new_str;
-         ++del_str;
-         break;
-       }
-    } while (true);
+  if (!newc.u.s_binary.left != !delc.u.s_binary.left)
+    return true;
 
-  if (*new_str != 'E')
-    return *del_str != *new_str;
+  if (!newc.u.s_binary.left)
+    return false;
 
-  ++new_str;
-  ++del_str;
-  if (*new_str != 'j' && *new_str != 'm' && *new_str != 'y')
-    return true;
-  if (*del_str != 'P' || *++del_str != 'v')
+  if (new_delete_mismatch_p (*newc.u.s_binary.left, *delc.u.s_binary.left)
+      || !newc.u.s_binary.right != !delc.u.s_binary.right)
     return true;
 
-  /* Ignore any remaining arguments.  Since both operators are members
-     of the same class, mismatches in those should be detectable and
-     diagnosed by the front end.  */
+  if (newc.u.s_binary.right)
+    return new_delete_mismatch_p (*newc.u.s_binary.right,
+                                 *delc.u.s_binary.right);
   return false;
 }
 
+/* Return true if DELETE_DECL is an operator delete that's not suitable
+   to call with a pointer returned fron NEW_DECL.  */
+
+static bool
+new_delete_mismatch_p (tree new_decl, tree delete_decl)
+{
+  tree new_name = DECL_ASSEMBLER_NAME (new_decl);
+  tree delete_name = DECL_ASSEMBLER_NAME (delete_decl);
+
+  /* valid_new_delete_pair_p() returns a conservative result (currently
+     it only handles global operators).  A true result is reliable but
+     a false result doesn't necessarily mean the operators don't match.  */
+  if (valid_new_delete_pair_p (new_name, delete_name))
+    return false;
+
+  /* For anything not handled by valid_new_delete_pair_p() such as member
+     operators compare the individual demangled components of the mangled
+     name.  */
+  const char *new_str = IDENTIFIER_POINTER (new_name);
+  const char *del_str = IDENTIFIER_POINTER (delete_name);
+
+  void *np = NULL, *dp = NULL;
+  demangle_component *ndc = cplus_demangle_v3_components (new_str, 0, &np);
+  demangle_component *ddc = cplus_demangle_v3_components (del_str, 0, &dp);
+  bool mismatch = new_delete_mismatch_p (*ndc, *ddc);
+  free (np);
+  free (dp);
+  return mismatch;
+}
+
 /* ALLOC_DECL and DEALLOC_DECL are pair of allocation and deallocation
    functions.  Return true if the latter is suitable to deallocate objects
    allocated by calls to the former.  */
diff --git a/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete-3.C b/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete-3.C
new file mode 100644 (file)
index 0000000..25e1346
--- /dev/null
@@ -0,0 +1,159 @@
+/* PR c++/98305 spurious -Wmismatched-new-delete on template instance
+   { dg-do compile }
+   { dg-options "-Wall" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+void sink (void*, ...);
+
+template <class>
+struct A1
+{
+  A1 ();
+
+  void* operator new (size_t);
+  void operator delete (void*);
+
+  void* operator new[] (size_t);
+  void operator delete[] (void*);
+
+  template <class, class>
+  struct A2
+  {
+    A2 ();
+
+    void* operator new (size_t);
+    void operator delete (void*);
+
+    void* operator new[] (size_t);
+    void operator delete[] (void*);
+  };
+};
+
+void test_a1 ()
+{
+  {
+    A1<int> *p = new A1<int>();
+    sink (p);
+    delete p;
+  }
+
+  {
+    A1<long> *p = new A1<long>();
+    sink (p);
+    delete p;
+  }
+
+  {
+    void *p = new A1<int>();
+    A1<long> *q = (A1<long>*)p;
+    sink (q);
+    delete q;       // { dg-warning "-Wmismatched-new-delete" }
+  }
+}
+
+void test_a2 ()
+{
+  {
+    A1<int>::A2<int, int> *p = new A1<int>::A2<int, int>();
+    sink (p);
+    delete p;
+  }
+  {
+    A1<void>::A2<int, long> *p = new A1<void>::A2<int, long>();
+    sink (p);
+    delete p;
+  }
+  {
+    A1<char*>::A2<long, double> *p = new A1<char*>::A2<long, double>();
+    sink (p);
+    delete p;
+  }
+
+  typedef A1<char>::A2<char, char> A;
+  {
+    A *p = (A*)new A1<char>::A2<char, int>();
+    sink (p);
+    delete p;       // { dg-warning "-Wmismatched-new-delete" }
+  }
+
+  {
+    A *p = (A*)new A1<char>::A2<int, char>();
+    sink (p);
+    delete p;       // { dg-warning "-Wmismatched-new-delete" }
+  }
+
+  {
+    A *p = (A*)new A1<int>::A2<char, char>();
+    sink (p);
+    delete p;       // { dg-warning "-Wmismatched-new-delete" }
+  }
+}
+
+
+template <class>
+struct B1
+{
+  B1 ();
+
+  void* operator new (size_t);
+  void operator delete (void*);
+
+  void* operator new[] (size_t);
+  void operator delete[] (void*);
+
+  template <class, class>
+  struct B2
+  {
+    B2 ();
+
+    void* operator new (size_t);
+    void operator delete (void*);
+
+    void* operator new[] (size_t);
+    void operator delete[] (void*);
+  };
+};
+
+
+void test_b_b ()
+{
+  typedef B1<char> B1c;
+  typedef B1c::B2<B1c, B1c> B1cB2B1c;
+
+  {
+    B1cB2B1c *p = new B1cB2B1c;
+    sink (p);
+    delete p;
+  }
+
+  {
+    B1cB2B1c *p = new B1cB2B1c[1];
+    sink (p);
+    delete[] p;
+  }
+}
+
+
+void test_a_b ()
+{
+  typedef B1<char>::B2<char, char> B;
+
+  {
+    B *p = (B*)new A1<char>::A2<char, int>[1];
+    sink (p);
+    delete[] p;     // { dg-warning "-Wmismatched-new-delete" }
+  }
+
+  {
+    B *p = (B*)new A1<char>::A2<int, char>[2];
+    sink (p);
+    delete[] p;     // { dg-warning "-Wmismatched-new-delete" }
+  }
+
+  {
+    B *p = (B*)new A1<int>::A2<char, char>[3];
+    sink (p);
+    delete[] p;     // { dg-warning "-Wmismatched-new-delete" }
+  }
+}