diff mbox series

exempt invalid built-in calls from -Wrestrict (PR 83655)

Message ID 96064740-4cf0-982e-7296-9c716afa25a4@gmail.com
State New
Headers show
Series exempt invalid built-in calls from -Wrestrict (PR 83655) | expand

Commit Message

Martin Sebor Jan. 2, 2018, 11:02 p.m. UTC
In addition to assuming that built-in functions are called with
the correct number of arguments (bug 83603), the restrict pass
also assumes that they are called with arguments of the expected
types.  When a built-in is declared with no prototype and called
with arguments of the wrong type (such as memcpy (3, dst, ""))
the pass crashes GCC with a SEGV.

The attached patch prevents invalid calls from being checked for
-Wrestrict violations.  It's meant to be applied in conjunction
with the patch for bug 83603:
   https://gcc.gnu.org/ml/gcc-patches/2018-01/msg00005.html

A separate fix is to have GCC issue a warning for declarations
of built-ins with no prototype.  I opened bug 83656 to track this
change request to the C front end (C++ already warns for this).

Another improvement is to also warn about calls with the wrong number
or types of arguments when a built-in is declared without a prototype
despite the first warning, similarly to what Clang does.  I opened
bug 83657 to add this warning.

Martin

Comments

Jeff Law Jan. 2, 2018, 11:31 p.m. UTC | #1
On 01/02/2018 04:02 PM, Martin Sebor wrote:
> In addition to assuming that built-in functions are called with
> the correct number of arguments (bug 83603), the restrict pass
> also assumes that they are called with arguments of the expected
> types.  When a built-in is declared with no prototype and called
> with arguments of the wrong type (such as memcpy (3, dst, ""))
> the pass crashes GCC with a SEGV.
> 
> The attached patch prevents invalid calls from being checked for
> -Wrestrict violations.  It's meant to be applied in conjunction
> with the patch for bug 83603:
>   https://gcc.gnu.org/ml/gcc-patches/2018-01/msg00005.html
> 
> A separate fix is to have GCC issue a warning for declarations
> of built-ins with no prototype.  I opened bug 83656 to track this
> change request to the C front end (C++ already warns for this).
> 
> Another improvement is to also warn about calls with the wrong number
> or types of arguments when a built-in is declared without a prototype
> despite the first warning, similarly to what Clang does.  I opened
> bug 83657 to add this warning.
> 
> Martin
> 
> gcc-83655.diff
> 
> 
> PR tree-optimization/83655 - ICE on an invalid call to memcpy declared with no prototype
> 
> gcc/testsuite/ChangeLog:
> 
> 	PR tree-optimization/83655
> 	* gcc.dg/Wrestrict-5.c: New test.
> 	* c-c++-common/builtins.c: New test.
> 
> gcc/ChangeLog:
> 
> 	PR tree-optimization/83655
> 	* gimple-ssa-warn-restrict.c (wrestrict_dom_walker::check_call): 
Looks like your ChangeLog is incomplete.  OK with the completed
ChangeLog entry.

Jeff
Richard Sandiford Jan. 4, 2018, 5:52 p.m. UTC | #2
Martin Sebor <msebor@gmail.com> writes:
> In addition to assuming that built-in functions are called with
> the correct number of arguments (bug 83603), the restrict pass
> also assumes that they are called with arguments of the expected
> types.  When a built-in is declared with no prototype and called
> with arguments of the wrong type (such as memcpy (3, dst, ""))
> the pass crashes GCC with a SEGV.
>
> The attached patch prevents invalid calls from being checked for
> -Wrestrict violations.  It's meant to be applied in conjunction
> with the patch for bug 83603:
>    https://gcc.gnu.org/ml/gcc-patches/2018-01/msg00005.html
>
> A separate fix is to have GCC issue a warning for declarations
> of built-ins with no prototype.  I opened bug 83656 to track this
> change request to the C front end (C++ already warns for this).
>
> Another improvement is to also warn about calls with the wrong number
> or types of arguments when a built-in is declared without a prototype
> despite the first warning, similarly to what Clang does.  I opened
> bug 83657 to add this warning.
>
> Martin
>
> PR tree-optimization/83655 - ICE on an invalid call to memcpy declared with no prototype
>
> gcc/testsuite/ChangeLog:
>
> 	PR tree-optimization/83655
> 	* gcc.dg/Wrestrict-5.c: New test.
> 	* c-c++-common/builtins.c: New test.
>
> gcc/ChangeLog:
>
> 	PR tree-optimization/83655
> 	* gimple-ssa-warn-restrict.c (wrestrict_dom_walker::check_call): 
>
> diff --git a/gcc/gimple-ssa-warn-restrict.c b/gcc/gimple-ssa-warn-restrict.c
> index ac545e4..47d4900 100644
> --- a/gcc/gimple-ssa-warn-restrict.c
> +++ b/gcc/gimple-ssa-warn-restrict.c
> @@ -1688,7 +1688,17 @@ wrestrict_dom_walker::check_call (gcall *call)
>    if (!dstwr && strfun)
>      dstwr = size_one_node;
>  
> -  if (check_bounds_or_overlap (call, dst, src, dstwr, NULL_TREE))
> +  /* DST and SRC can be null for a call with an insufficient number
> +     of arguments to a built-in function declared without a protype.  */

Typo: prototype.

> +  if (!dst || !src)
> +    return;
> +
> +  /* DST, SRC, or DSTWR can also have the wrong type in a call to
> +     a function declared without a prototype.  Avoid checking such
> +     invalid calls.  */
> +  if (TREE_CODE (TREE_TYPE (dst)) != POINTER_TYPE
> +      || TREE_CODE (TREE_TYPE (src)) != POINTER_TYPE
> +      || (dstwr && !INTEGRAL_TYPE_P (TREE_TYPE (dstwr))))
>      return;

Isn't the canonical way of handling this kind of thing to use:

  if (!gimple_call_builtin_p (call, BUILT_IN_NORMAL)
    return;

instead of:

  tree func = gimple_call_fndecl (call);
  if (!func || DECL_BUILT_IN_CLASS (func) != BUILT_IN_NORMAL)
    return;

?  

Thanks,
Richard
Martin Sebor Jan. 4, 2018, 7:18 p.m. UTC | #3
On 01/04/2018 10:52 AM, Richard Sandiford wrote:
> Martin Sebor <msebor@gmail.com> writes:
>> In addition to assuming that built-in functions are called with
>> the correct number of arguments (bug 83603), the restrict pass
>> also assumes that they are called with arguments of the expected
>> types.  When a built-in is declared with no prototype and called
>> with arguments of the wrong type (such as memcpy (3, dst, ""))
>> the pass crashes GCC with a SEGV.
>>
>> The attached patch prevents invalid calls from being checked for
>> -Wrestrict violations.  It's meant to be applied in conjunction
>> with the patch for bug 83603:
>>    https://gcc.gnu.org/ml/gcc-patches/2018-01/msg00005.html
>>
>> A separate fix is to have GCC issue a warning for declarations
>> of built-ins with no prototype.  I opened bug 83656 to track this
>> change request to the C front end (C++ already warns for this).
>>
>> Another improvement is to also warn about calls with the wrong number
>> or types of arguments when a built-in is declared without a prototype
>> despite the first warning, similarly to what Clang does.  I opened
>> bug 83657 to add this warning.
>>
>> Martin
>>
>> PR tree-optimization/83655 - ICE on an invalid call to memcpy declared with no prototype
>>
>> gcc/testsuite/ChangeLog:
>>
>> 	PR tree-optimization/83655
>> 	* gcc.dg/Wrestrict-5.c: New test.
>> 	* c-c++-common/builtins.c: New test.
>>
>> gcc/ChangeLog:
>>
>> 	PR tree-optimization/83655
>> 	* gimple-ssa-warn-restrict.c (wrestrict_dom_walker::check_call):
>>
>> diff --git a/gcc/gimple-ssa-warn-restrict.c b/gcc/gimple-ssa-warn-restrict.c
>> index ac545e4..47d4900 100644
>> --- a/gcc/gimple-ssa-warn-restrict.c
>> +++ b/gcc/gimple-ssa-warn-restrict.c
>> @@ -1688,7 +1688,17 @@ wrestrict_dom_walker::check_call (gcall *call)
>>    if (!dstwr && strfun)
>>      dstwr = size_one_node;
>>
>> -  if (check_bounds_or_overlap (call, dst, src, dstwr, NULL_TREE))
>> +  /* DST and SRC can be null for a call with an insufficient number
>> +     of arguments to a built-in function declared without a protype.  */
>
> Typo: prototype.

Let me fix that.

>
>> +  if (!dst || !src)
>> +    return;
>> +
>> +  /* DST, SRC, or DSTWR can also have the wrong type in a call to
>> +     a function declared without a prototype.  Avoid checking such
>> +     invalid calls.  */
>> +  if (TREE_CODE (TREE_TYPE (dst)) != POINTER_TYPE
>> +      || TREE_CODE (TREE_TYPE (src)) != POINTER_TYPE
>> +      || (dstwr && !INTEGRAL_TYPE_P (TREE_TYPE (dstwr))))
>>      return;
>
> Isn't the canonical way of handling this kind of thing to use:
>
>   if (!gimple_call_builtin_p (call, BUILT_IN_NORMAL)
>     return;
>
> instead of:
>
>   tree func = gimple_call_fndecl (call);
>   if (!func || DECL_BUILT_IN_CLASS (func) != BUILT_IN_NORMAL)
>     return;
>
> ?

It may be how optimization passes deal with it but it would be
a poor choice in a warning pass because it would suppress
the checking of even valid calls to built-ins declared without
a prototype.  For instance, without -Wrestrict, GCC silently
accepts this code:

   void* memcpy ();

   void f (char *d)
   {
     memcpy (d, d + 2, 5);
   }

With -Wrestict, and without relying on gimple_call_fndecl(),
the overlap in the call is diagnosed, similarly to how the buffer
overflow in this example is diagnosed by -Wstringop-overflow:

   char a[4];

   void f (const char *s)
   {
     memcpy (a, s, 32);
   }

I think gimple_call_fndecl() is overly restrictive by insisting
on compatibility between integer types like int and long.  It
has the consequence of disabling optimizations like strlen that
warnings then rely on to help detect bugs.  In effect, it makes
the safety hole that declaring built-ins without a prototype opens
up that much bigger.  For example, because the strlen pass ignores
the call to memcpy below (8 is an int which is incompatible with
the size_t argument) the overlapping call to strcpy isn't diagnosed.

   char* memcpy ();
   char* strcpy ();

   char a[8];

   void f (const char *s)
   {
     memcpy (a, "12345", 8);

     strcpy (a + 3, a);
   }

Ironically, the bug is diagnosed when memcpy() isn't declared
at all -- because GCC uses the prototype of __builtin_memcpy()
to convert the arguments to the expected types, which in turn
lets gimple_call_fndecl() treat the call as one to the built-in
and the strlen pass work its magic.  There's no reason why GCC
couldn't do the same thing  even when the functions are declared
without a prototype, like Clang does.

Declaring functions without a prototype has been deprecated for
years because it defeats the type system.  There isn't a lot GCC
can do to mitigate the problems for user-defined functions, but
it could do more to help avoid the problem with built-ins.

Sorry if this seems like a long-winded response to a simple
question.  I feel strongly about the subject.

Martin
diff mbox series

Patch

PR tree-optimization/83655 - ICE on an invalid call to memcpy declared with no prototype

gcc/testsuite/ChangeLog:

	PR tree-optimization/83655
	* gcc.dg/Wrestrict-5.c: New test.
	* c-c++-common/builtins.c: New test.

gcc/ChangeLog:

	PR tree-optimization/83655
	* gimple-ssa-warn-restrict.c (wrestrict_dom_walker::check_call): 

diff --git a/gcc/gimple-ssa-warn-restrict.c b/gcc/gimple-ssa-warn-restrict.c
index ac545e4..47d4900 100644
--- a/gcc/gimple-ssa-warn-restrict.c
+++ b/gcc/gimple-ssa-warn-restrict.c
@@ -1688,7 +1688,17 @@  wrestrict_dom_walker::check_call (gcall *call)
   if (!dstwr && strfun)
     dstwr = size_one_node;
 
-  if (check_bounds_or_overlap (call, dst, src, dstwr, NULL_TREE))
+  /* DST and SRC can be null for a call with an insufficient number
+     of arguments to a built-in function declared without a protype.  */
+  if (!dst || !src)
+    return;
+
+  /* DST, SRC, or DSTWR can also have the wrong type in a call to
+     a function declared without a prototype.  Avoid checking such
+     invalid calls.  */
+  if (TREE_CODE (TREE_TYPE (dst)) != POINTER_TYPE
+      || TREE_CODE (TREE_TYPE (src)) != POINTER_TYPE
+      || (dstwr && !INTEGRAL_TYPE_P (TREE_TYPE (dstwr))))
     return;
 
   /* Avoid diagnosing the call again.  */
diff --git a/gcc/testsuite/gcc.dg/Wrestrict-5.c b/gcc/testsuite/gcc.dg/Wrestrict-5.c
new file mode 100644
index 0000000..4912ee5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wrestrict-5.c
@@ -0,0 +1,43 @@ 
+/* Test to verify that valid calls to common restrict-qualified built-in
+   functions declared with no prototype are checked for overlap, and that
+   invalid calls are ignored.
+  { dg-do compile }
+  { dg-options "-O2 -Wrestrict" }  */
+
+typedef __SIZE_TYPE__ size_t;
+
+#if __cplusplus
+extern "C" {
+
+#define NO_PROTO ...
+#else
+#define NO_PROTO /* empty */
+#endif
+
+void* memcpy ();
+char* strncpy ();
+
+#if __cplusplus
+}   /* extern "C" */
+#endif
+
+void test_memcpy_warn (char *d)
+{
+  memcpy (d, d + 2, 3);       /* { dg-warning "accessing 3 bytes at offsets 0 and 2 overlaps 1 byte at offset 2" } */
+}
+
+void test_memcpy_nowarn (char *d)
+{
+  memcpy (d, d + 2, "");
+}
+
+
+void test_strncpy_warn (char *d)
+{
+  strncpy (d + 1, d + 3, 5);  /* { dg-warning "accessing 5 bytes at offsets 1 and 3 overlaps 2 bytes at offset 3" } */
+}
+
+void test_strncpy_nowarn (char *d)
+{
+  strncpy (d + 1, d + 3, "");
+}
diff --git a/gcc/testsuite/c-c++-common/builtins.c b/gcc/testsuite/c-c++-common/builtins.c
new file mode 100644
index 0000000..673fcad
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/builtins.c
@@ -0,0 +1,193 @@ 
+/* Test to verify that calls to common built-in functions declared
+   with no prototype do not cause an ICE.
+  { dg-do compile }
+  { dg-options "-O2 -Wall -Wextra" }
+  { dg-prune-output "warning" }  */
+
+typedef __SIZE_TYPE__ size_t;
+
+#if __cplusplus
+extern "C" {
+
+#define NO_PROTO ...
+#else
+#define NO_PROTO /* empty */
+#endif
+
+  /* Character classification built-ins from <ctype.h>.  */
+  int isalpha (NO_PROTO);
+  int isalnum (NO_PROTO);
+  int isalpha (NO_PROTO);
+  int iscntrl (NO_PROTO);
+  int isdigit (NO_PROTO);
+  int isgraph (NO_PROTO);
+  int islower (NO_PROTO);
+  int isprint (NO_PROTO);
+  int ispunct (NO_PROTO);
+  int isspace (NO_PROTO);
+  int isupper (NO_PROTO);
+  int isxdigit (NO_PROTO);
+  int tolower (NO_PROTO);
+  int toupper (NO_PROTO);
+
+  /* Memory allocation built-ins from <stdlib.h>.  */
+  void* alloca (NO_PROTO);
+  void* aligned_alloc (NO_PROTO);
+  void* calloc (NO_PROTO);
+  void* malloc (NO_PROTO);
+  void* realloc (NO_PROTO);
+
+  /* Raw memory built-ins from <string.h>.  */
+  void* memcpy (NO_PROTO);
+  void* memchr (NO_PROTO);
+  void* memmove (NO_PROTO);
+  void* mempcpy (NO_PROTO);
+  void* memset (NO_PROTO);
+
+  /* String built-ins from <string.h>.  */
+  char* stpcpy (NO_PROTO);
+  char* stpncpy (NO_PROTO);
+
+  char* strcat (NO_PROTO);
+  char* strcpy (NO_PROTO);
+
+  char* strdup (NO_PROTO);
+  char* strndup (NO_PROTO);
+
+  char* strncat (NO_PROTO);
+  char* strncpy (NO_PROTO);
+
+  size_t strlen (NO_PROTO);
+  size_t strnlen (NO_PROTO);
+
+  char* strchr (NO_PROTO);
+  int strcmp (NO_PROTO);
+  int strncmp (NO_PROTO);
+
+  /* Input/output functions from <stdio.h>.  */
+  int puts (NO_PROTO);
+  int fputs (NO_PROTO);
+
+  int scanf (NO_PROTO);
+  int fscanf (NO_PROTO);
+  int sscanf (NO_PROTO);
+  int vfscanf (NO_PROTO);
+  int vsscanf (NO_PROTO);
+
+  int printf (NO_PROTO);
+  int fprintf (NO_PROTO);
+  int sprintf (NO_PROTO);
+
+  int snprintf (NO_PROTO);
+
+  int vprintf (NO_PROTO);
+  int vfprintf (NO_PROTO);
+  int vsprintf (NO_PROTO);
+
+  int vsnprintf (NO_PROTO);
+
+#if __cplusplus
+}
+#endif
+
+
+#define CONCAT(a, b) a ## b
+#define UNIQ_NAME(func, id) CONCAT (test_ ## func ## _, id)
+
+#define TEST_FUNC(func, arglist)		\
+  __typeof__ (func arglist)			\
+  UNIQ_NAME (func, __COUNTER__) (void) {	\
+    return func arglist;			\
+  }
+
+#define T1(func)				\
+  TEST_FUNC (func, ());				\
+  TEST_FUNC (func, (1));			\
+  TEST_FUNC (func, (""));			\
+  TEST_FUNC (func, ((void*)1));			\
+  TEST_FUNC (func, (iarr));			\
+  TEST_FUNC (func, (function))
+
+#define T2(func)				\
+  TEST_FUNC (func, (1, 1));			\
+  TEST_FUNC (func, (1, ""));			\
+  TEST_FUNC (func, (1, (void*)1));		\
+  TEST_FUNC (func, (1, iarr));			\
+  TEST_FUNC (func, (1, function))
+
+#define T3(func)				\
+  TEST_FUNC (func, (1, 1, 1));			\
+  TEST_FUNC (func, (1, 1, ""));			\
+  TEST_FUNC (func, (1, 1, (void*)1));		\
+  TEST_FUNC (func, (1, 1, iarr));		\
+  TEST_FUNC (func, (1, 1, function))
+
+extern int iarr[];
+extern void function (void);
+
+T1 (isalpha);
+T1 (isalnum);
+T1 (isalpha);
+T1 (iscntrl);
+T1 (isdigit);
+T1 (isgraph);
+T1 (islower);
+T1 (isprint);
+T1 (ispunct);
+T1 (isspace);
+T1 (isupper);
+T1 (isxdigit);
+T1 (tolower);
+T1 (toupper);
+
+T1 (alloca);
+T2 (aligned_alloc);
+T2 (malloc);
+T2 (calloc);
+T2 (realloc);
+
+T3 (memcpy);
+T3 (memmove);
+T3 (mempcpy);
+T3 (memset);
+T3 (memchr);
+
+T2 (stpcpy);
+T3 (stpncpy);
+
+T2 (strcat);
+T2 (strcpy);
+
+T1 (strdup);
+T2 (strndup);
+
+T3 (strncat);
+T3 (strncpy);
+
+T2 (strchr);
+T2 (strcmp);
+T3 (strncmp);
+
+T1 (strlen);
+T2 (strnlen);
+
+T1 (puts);
+T2 (fputs);
+
+T1 (scanf);
+T2 (fscanf);
+T2 (sscanf);
+T2 (vfscanf);
+T2 (vsscanf);
+
+T2 (printf);
+T3 (fprintf);
+T3 (sprintf);
+
+T3 (snprintf);
+
+T2 (vprintf);
+T2 (vfprintf);
+T2 (vsprintf);
+
+T3 (vsnprintf);