diff mbox series

correct -Wmismatched-new-delete for template instantiations (PR 98305)

Message ID 6bd181ad-20ad-0d23-d6a2-120dfdb33971@gmail.com
State New
Headers show
Series correct -Wmismatched-new-delete for template instantiations (PR 98305) | expand

Commit Message

Martin Sebor Dec. 17, 2020, 2:10 a.m. UTC
The -Wmismatched-new-delete detection of operator members of class
template instantiations is incomplete and overly simplistic, leading
to incorrect results and false positives.  Rather than reinventing
the wheel and parsing the mangled qualified names of the operators
the attached patch uses the demangler to compare their names.  Since
the code is only entered rarely (for user- defined overloads of
the operators, the cost of the demangling should be negligible in
most code bases).

Tested on x86_64-linux.

Martin

Comments

Martin Sebor Jan. 4, 2021, 6:53 p.m. UTC | #1
Ping:
https://gcc.gnu.org/pipermail/gcc-patches/2020-December/562141.html

On 12/16/20 7:10 PM, Martin Sebor wrote:
> The -Wmismatched-new-delete detection of operator members of class
> template instantiations is incomplete and overly simplistic, leading
> to incorrect results and false positives.  Rather than reinventing
> the wheel and parsing the mangled qualified names of the operators
> the attached patch uses the demangler to compare their names.  Since
> the code is only entered rarely (for user- defined overloads of
> the operators, the cost of the demangling should be negligible in
> most code bases).
> 
> Tested on x86_64-linux.
> 
> Martin
Jeff Law Jan. 5, 2021, 12:02 a.m. UTC | #2
On 12/16/20 7:10 PM, Martin Sebor via Gcc-patches wrote:
> The -Wmismatched-new-delete detection of operator members of class
> template instantiations is incomplete and overly simplistic, leading
> to incorrect results and false positives.  Rather than reinventing
> the wheel and parsing the mangled qualified names of the operators
> the attached patch uses the demangler to compare their names.  Since
> the code is only entered rarely (for user- defined overloads of
> the operators, the cost of the demangling should be negligible in
> most code bases).
>
> Tested on x86_64-linux.
>
> Martin
>
> gcc-98305.diff
>
> PR c++/98305 spurious -Wmismatched-new-delete on template instance
>
> 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.
>
> diff --git a/gcc/builtins.c b/gcc/builtins.c
> index 28e44445ab2..b1d69855523 100644
> --- a/gcc/builtins.c
> +++ b/gcc/builtins.c
> @@ -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
> @@ -13053,121 +13054,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.  */
s/DELC/DECL/

OK with the nit fixed.
jeff
diff mbox series

Patch

PR c++/98305 spurious -Wmismatched-new-delete on template instance

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.

diff --git a/gcc/builtins.c b/gcc/builtins.c
index 28e44445ab2..b1d69855523 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -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
@@ -13053,121 +13054,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
index 00000000000..25e13461fe7
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete-3.C
@@ -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" }
+  }
+}