diff mbox series

diagnose more out-of-bounds char writes (PR 91977)

Message ID 4dcfe377-d3c5-124b-6f3a-df31bc569dbb@gmail.com
State New
Headers show
Series diagnose more out-of-bounds char writes (PR 91977) | expand

Commit Message

Martin Sebor Oct. 3, 2019, 11:28 p.m. UTC
The count_nonzero_bytes() function that computes the range of bytes
stored by a multi-byte assignment is overly conservative and fails
to determine the number of bytes to store for expressions involving
MEM_REF with DECL operands without a constant initializer.  Even
though it's not possible to determine the length of a string stored
in a DECL without an initializer, the MEM_REF type reflects the size
of the access.  By returning it to the caller (along with
a conservative worst-case length range), the function lets
it diagnose stores that are too large for the destination.

The attached patch relaxes the function to return such a conservative
result reflecting the size in these cases.  In addition, the patch also
lets the function return this size (but not the length) for non-constant
single-character stores for which it previously also failed.

Tested on x86_64-linux.

Martin

Comments

Jeff Law Oct. 4, 2019, 2:41 a.m. UTC | #1
On 10/3/19 5:28 PM, Martin Sebor wrote:
> The count_nonzero_bytes() function that computes the range of bytes
> stored by a multi-byte assignment is overly conservative and fails
> to determine the number of bytes to store for expressions involving
> MEM_REF with DECL operands without a constant initializer.  Even
> though it's not possible to determine the length of a string stored
> in a DECL without an initializer, the MEM_REF type reflects the size
> of the access.  By returning it to the caller (along with
> a conservative worst-case length range), the function lets
> it diagnose stores that are too large for the destination.
> 
> The attached patch relaxes the function to return such a conservative
> result reflecting the size in these cases.  In addition, the patch also
> lets the function return this size (but not the length) for non-constant
> single-character stores for which it previously also failed.
> 
> Tested on x86_64-linux.
> 
> Martin
> 
> gcc-91977.diff
> 
> PR middle-end/91977 - missing -Wstringop-overflow on memcpy into a pointer plus offset
> 
> gcc/ChangeLog:
> 
> 	PR middle-end/91977
> 	* tree-ssa-strlen.c (count_nonzero_bytes): Handle assignments with
> 	MEM_REF right operand.  Avoid failing for MEM_REF assignments from
> 	uninitialized objects.
> 
> gcc/testsuite/ChangeLog:
> 
> 	PR middle-end/91977
> 	* gcc.dg/Wstringop-overflow-18.c: New test.
OK
jeff
diff mbox series

Patch

PR middle-end/91977 - missing -Wstringop-overflow on memcpy into a pointer plus offset

gcc/ChangeLog:

	PR middle-end/91977
	* tree-ssa-strlen.c (count_nonzero_bytes): Handle assignments with
	MEM_REF right operand.  Avoid failing for MEM_REF assignments from
	uninitialized objects.

gcc/testsuite/ChangeLog:

	PR middle-end/91977
	* gcc.dg/Wstringop-overflow-18.c: New test.

Index: gcc/tree-ssa-strlen.c
===================================================================
--- gcc/tree-ssa-strlen.c	(revision 276491)
+++ gcc/tree-ssa-strlen.c	(working copy)
@@ -3801,40 +3801,44 @@  count_nonzero_bytes (tree exp, unsigned HOST_WIDE_
       tree type = TREE_TYPE (exp);
       if (TREE_CODE (type) == INTEGER_TYPE
 	  && TYPE_MODE (type) == TYPE_MODE (char_type_node)
-	  && TYPE_PRECISION (type) == TYPE_PRECISION (char_type_node))
+	  && TYPE_PRECISION (type) == TYPE_PRECISION (char_type_node)
+	  && tree_expr_nonzero_p (exp))
 	{
-	  /* Determine if the character EXP is known to be non-zero
-	     (even if its exact value is not known) and if so, recurse
-	     once to set the range, etc.  */
-	  if (tree_expr_nonzero_p (exp))
-	    return count_nonzero_bytes (build_int_cst (type, 1),
-					offset, nbytes, lenrange,
-					nulterm, allnul, allnonnul, snlim);
-	  /* Don't know whether EXP is or isn't nonzero.  */
-	  return false;
+	  /* If the character EXP is known to be non-zero (even if its
+	     exact value is not known) recurse once to set the range
+	     for an arbitrary constant.  */
+	  exp = build_int_cst (type, 1);
+	  return count_nonzero_bytes (exp, offset, 1, lenrange,
+				      nulterm, allnul, allnonnul, snlim);
 	}
 
       gimple *stmt = SSA_NAME_DEF_STMT (exp);
-      if (gimple_code (stmt) != GIMPLE_PHI)
-	return false;
-
-      /* Avoid processing an SSA_NAME that has already been visited
-	 or if an SSA_NAME limit has been reached.  Indicate success
-	 if the former and failure if the latter.  */
-      if (int res = snlim.next_ssa_name (exp))
-	return res > 0;
-
-      /* Determine the minimum and maximum from the PHI arguments.  */
-      unsigned int n = gimple_phi_num_args (stmt);
-      for (unsigned i = 0; i != n; i++)
+      if (gimple_assign_single_p (stmt))
 	{
-	  tree def = gimple_phi_arg_def (stmt, i);
-	  if (!count_nonzero_bytes (def, offset, nbytes, lenrange, nulterm,
-				    allnul, allnonnul, snlim))
+	  exp = gimple_assign_rhs1 (stmt);
+	  if (TREE_CODE (exp) != MEM_REF)
 	    return false;
 	}
+      else if (gimple_code (stmt) == GIMPLE_PHI)
+	{
+	  /* Avoid processing an SSA_NAME that has already been visited
+	     or if an SSA_NAME limit has been reached.  Indicate success
+	     if the former and failure if the latter.  */
+	  if (int res = snlim.next_ssa_name (exp))
+	    return res > 0;
 
-      return true;
+	  /* Determine the minimum and maximum from the PHI arguments.  */
+	  unsigned int n = gimple_phi_num_args (stmt);
+	  for (unsigned i = 0; i != n; i++)
+	    {
+	      tree def = gimple_phi_arg_def (stmt, i);
+	      if (!count_nonzero_bytes (def, offset, nbytes, lenrange, nulterm,
+					allnul, allnonnul, snlim))
+		return false;
+	    }
+
+	  return true;
+	}
     }
 
   if (TREE_CODE (exp) == MEM_REF)
@@ -3897,14 +3901,25 @@  count_nonzero_bytes (tree exp, unsigned HOST_WIDE_
       prep = reinterpret_cast <char *>(buf);
       /* Try to extract the representation of the constant object
 	 or expression starting from the offset.  */
-      nbytes = native_encode_expr (exp, buf, sizeof buf, offset);
-      if (!nbytes)
-	return false;
+      unsigned repsize = native_encode_expr (exp, buf, sizeof buf, offset);
+      if (repsize < nbytes)
+	{
+	  /* This should only happen when REPSIZE is zero because EXP
+	     doesn't denote an object with a known initializer, except
+	     perhaps when the reference reads past its end.  */
+	  lenrange[0] = 0;
+	  prep = NULL;
+	}
+      else
+	nbytes = repsize;
     }
 
+  if (!nbytes)
+    return false;
+
   /* Compute the number of leading nonzero bytes in the representation
      and update the minimum and maximum.  */
-  unsigned n = strnlen (prep, nbytes);
+  unsigned n = prep ? strnlen (prep, nbytes) : nbytes;
 
   if (n < lenrange[0])
     lenrange[0] = n;
Index: gcc/testsuite/gcc.dg/Warray-bounds-18.c
===================================================================
--- gcc/testsuite/gcc.dg/Warray-bounds-18.c	(revision 276491)
+++ gcc/testsuite/gcc.dg/Warray-bounds-18.c	(working copy)
@@ -1,5 +1,5 @@ 
 /* { dg-do compile } */
-/* { dg-options "-O2 -Warray-bounds" } */
+/* { dg-options "-O2 -Warray-bounds -Wno-stringop-overflow" } */
 
 typedef struct
 {
Index: gcc/testsuite/gcc.dg/Wstringop-overflow-18.c
===================================================================
--- gcc/testsuite/gcc.dg/Wstringop-overflow-18.c	(nonexistent)
+++ gcc/testsuite/gcc.dg/Wstringop-overflow-18.c	(working copy)
@@ -0,0 +1,239 @@ 
+/* PR middle-end/91977 - missing -Wstringop-overflow on memcpy into
+   a pointer plus offset
+   { dg-do compile }
+   { dg-options "-O2 -Wall -Wno-array-bounds -ftrack-macro-expansion=0" } */
+
+#define NOIPA          __attribute__ ((noipa))
+#define CONCAT(a, b)   a ## b
+#define CAT(a, b)      CONCAT (a, b)
+
+#define S3 "123"
+#define S4 "1234"
+
+char a1[1], a2[2], a3[3], a4[4], a5[5], a6[6], a7[7], a8[8];
+char b1[1], b2[2], b3[3], b4[4], b5[5], b6[6], b7[7], b8[8];
+
+#define T(dst, src, off, n)					\
+  NOIPA void CAT (test_on_line_, __LINE__) (const void *s)	\
+  {								\
+    __builtin_memcpy (dst + off, src, n);			\
+  } typedef void dummy_type
+
+T (a4, s, 0, 1);
+T (a4, s, 1, 1);
+T (a4, s, 2, 1);
+T (a4, s, 3, 1);
+T (a4, s, 4, 1);    // { dg-warning "writing 1 byte into a region of size 0" }
+
+T (a4, s, 0, 2);
+T (a4, s, 1, 2);
+T (a4, s, 2, 2);
+T (a4, s, 3, 2);    // { dg-warning "writing 2 bytes into a region of size 1" }
+T (a4, s, 4, 2);    // { dg-warning "writing 2 bytes into a region of size 0" }
+
+T (a4, s, 0, 3);
+T (a4, s, 1, 3);
+T (a4, s, 2, 3);    // { dg-warning "writing 3 bytes into a region of size 2" }
+T (a4, s, 3, 3);    // { dg-warning "writing 3 bytes into a region of size 1" }
+T (a4, s, 4, 3);    // { dg-warning "writing 3 bytes into a region of size 0" }
+
+T (a4, s, 0, 4);
+T (a4, s, 1, 4);    // { dg-warning "writing 4 bytes into a region of size 3" }
+T (a4, s, 2, 4);    // { dg-warning "writing 4 bytes into a region of size 2" }
+T (a4, s, 3, 4);    // { dg-warning "writing 4 bytes into a region of size 1" }
+T (a4, s, 4, 4);    // { dg-warning "writing 4 bytes into a region of size 0" }
+
+T (a7, s, 3, 3);
+T (a7, s, 4, 3);
+T (a7, s, 5, 3);    // { dg-warning "writing 3 bytes into a region of size 2" }
+T (a7, s, 6, 3);    // { dg-warning "writing 3 bytes into a region of size 1" }
+T (a7, s, 7, 3);    // { dg-warning "writing 3 bytes into a region of size 0" }
+
+T (a7, s, 3, 4);
+T (a7, s, 4, 4);    // { dg-warning "writing 4 bytes into a region of size 3" }
+T (a7, s, 5, 4);    // { dg-warning "writing 4 bytes into a region of size 2" }
+T (a7, s, 6, 4);    // { dg-warning "writing 4 bytes into a region of size 1" }
+T (a7, s, 7, 4);    // { dg-warning "writing 4 bytes into a region of size 0" }
+
+T (a7, s, 1, 6);
+T (a7, s, 2, 6);    // { dg-warning "writing 6 bytes into a region of size 5" }
+T (a7, s, 3, 6);    // { dg-warning "writing 6 bytes into a region of size 4" }
+T (a7, s, 4, 6);    // { dg-warning "writing 6 bytes into a region of size 3" }
+T (a7, s, 5, 6);    // { dg-warning "writing 6 bytes into a region of size 2" }
+T (a7, s, 6, 6);    // { dg-warning "writing 6 bytes into a region of size 1" }
+T (a7, s, 7, 6);    // { dg-warning "writing 6 bytes into a region of size 0" }
+
+T (a8, s, 1, 7);
+T (a8, s, 2, 7);    // { dg-warning "writing 7 bytes into a region of size 6" }
+T (a8, s, 3, 7);    // { dg-warning "writing 7 bytes into a region of size 5" }
+T (a8, s, 4, 7);    // { dg-warning "writing 7 bytes into a region of size 4" }
+T (a8, s, 5, 7);    // { dg-warning "writing 7 bytes into a region of size 3" }
+T (a8, s, 6, 7);    // { dg-warning "writing 7 bytes into a region of size 2" }
+T (a8, s, 7, 7);    // { dg-warning "writing 7 bytes into a region of size 1" }
+T (a8, s, 8, 7);    // { dg-warning "writing 7 bytes into a region of size 0" }
+
+#undef T
+#define T(dst, src, off, n)					\
+  NOIPA void CAT (test_on_line_, __LINE__) (const void *s)	\
+  {								\
+    char *d = dst + off;					\
+    __builtin_memcpy (d, src, n);				\
+  } typedef void dummy_type
+
+T (a4, s, 0, 1);
+T (a4, s, 1, 1);
+T (a4, s, 2, 1);
+T (a4, s, 3, 1);
+T (a4, s, 4, 1);    // { dg-warning "writing 1 byte into a region of size 0" }
+
+T (a4, s, 0, 2);
+T (a4, s, 1, 2);
+T (a4, s, 2, 2);
+T (a4, s, 3, 2);    // { dg-warning "writing 2 bytes into a region of size 1" }
+T (a4, s, 4, 2);    // { dg-warning "writing 2 bytes into a region of size 0" }
+
+T (a4, s, 0, 3);
+T (a4, s, 1, 3);
+T (a4, s, 2, 3);    // { dg-warning "writing 3 bytes into a region of size 2" }
+T (a4, s, 3, 3);    // { dg-warning "writing 3 bytes into a region of size 1" }
+T (a4, s, 4, 3);    // { dg-warning "writing 3 bytes into a region of size 0" }
+
+T (a4, s, 0, 4);
+T (a4, s, 1, 4);    // { dg-warning "writing 4 bytes into a region of size 3" }
+T (a4, s, 2, 4);    // { dg-warning "writing 4 bytes into a region of size 2" }
+T (a4, s, 3, 4);    // { dg-warning "writing 4 bytes into a region of size 1" }
+T (a4, s, 4, 4);    // { dg-warning "writing 4 bytes into a region of size 0" }
+
+T (a7, s, 3, 3);
+T (a7, s, 4, 3);
+T (a7, s, 5, 3);    // { dg-warning "writing 3 bytes into a region of size 2" }
+T (a7, s, 6, 3);    // { dg-warning "writing 3 bytes into a region of size 1" }
+T (a7, s, 7, 3);    // { dg-warning "writing 3 bytes into a region of size 0" }
+
+T (a7, s, 3, 4);
+T (a7, s, 4, 4);    // { dg-warning "writing 4 bytes into a region of size 3" }
+T (a7, s, 5, 4);    // { dg-warning "writing 4 bytes into a region of size 2" }
+T (a7, s, 6, 4);    // { dg-warning "writing 4 bytes into a region of size 1" }
+T (a7, s, 7, 4);    // { dg-warning "writing 4 bytes into a region of size 0" }
+
+T (a7, s, 1, 6);
+T (a7, s, 2, 6);    // { dg-warning "writing 6 bytes into a region of size 5" }
+T (a7, s, 3, 6);    // { dg-warning "writing 6 bytes into a region of size 4" }
+T (a7, s, 4, 6);    // { dg-warning "writing 6 bytes into a region of size 3" }
+T (a7, s, 5, 6);    // { dg-warning "writing 6 bytes into a region of size 2" }
+T (a7, s, 6, 6);    // { dg-warning "writing 6 bytes into a region of size 1" }
+T (a7, s, 7, 6);    // { dg-warning "writing 6 bytes into a region of size 0" }
+
+#undef T
+#define T(dst, src, init, off, n)			\
+  NOIPA void CAT (test_on_line_, __LINE__) (void)	\
+  {							\
+    __builtin_strcpy (src, init);			\
+    char *d = dst + off;				\
+    __builtin_memcpy (d, src, n);			\
+  } typedef void dummy_type
+
+
+T (a6, b6, S4, 0, 4);
+T (a6, b6, S4, 1, 4);
+T (a6, b6, S4, 2, 4);
+T (a6, b6, S4, 3, 4);   // { dg-warning "writing 4 bytes into a region of size 3" } */
+T (a6, b6, S4, 4, 4);   // { dg-warning "writing 4 bytes into a region of size 2" } */
+T (a6, b6, S4, 5, 4);   // { dg-warning "writing 4 bytes into a region of size 1" } */
+T (a6, b6, S4, 6, 4);   // { dg-warning "writing 4 bytes into a region of size 0" } */
+
+T (a7, b7, S4, 0, 4);
+T (a7, b7, S4, 1, 4);
+T (a7, b7, S4, 2, 4);
+T (a7, b7, S4, 3, 4);
+T (a7, b7, S4, 4, 4);   // { dg-warning "writing 4 bytes into a region of size 3" } */
+T (a7, b7, S4, 5, 4);   // { dg-warning "writing 4 bytes into a region of size 2" } */
+T (a7, b7, S4, 6, 4);   // { dg-warning "writing 4 bytes into a region of size 1" } */
+T (a7, b7, S4, 7, 4);   // { dg-warning "writing 4 bytes into a region of size 0" } */
+
+T (a8, b4, S3, 0, 4);
+T (a8, b4, S3, 1, 4);
+T (a8, b4, S3, 2, 4);
+T (a8, b4, S3, 3, 4);
+T (a8, b4, S3, 4, 4);
+T (a8, b4, S3, 5, 4);   // { dg-warning "writing 4 bytes into a region of size 3" } */
+T (a8, b4, S3, 6, 4);   // { dg-warning "writing 4 bytes into a region of size 2" } */
+T (a8, b4, S3, 7, 4);   // { dg-warning "writing 4 bytes into a region of size 1" } */
+T (a8, b4, S3, 8, 4);   // { dg-warning "writing 4 bytes into a region of size 0" } */
+
+T (a8, b8, S3, 0, 4);
+T (a8, b8, S3, 1, 4);
+T (a8, b8, S3, 2, 4);
+T (a8, b8, S3, 3, 4);
+T (a8, b8, S3, 4, 4);
+T (a8, b8, S3, 5, 4);   // { dg-warning "writing 4 bytes into a region of size 3" } */
+T (a8, b8, S3, 6, 4);   // { dg-warning "writing 4 bytes into a region of size 2" } */
+T (a8, b8, S3, 7, 4);   // { dg-warning "writing 4 bytes into a region of size 1" } */
+T (a8, b8, S3, 8, 4);   // { dg-warning "writing 4 bytes into a region of size 0" } */
+
+T (a8, b8, S4, 0, 4);
+T (a8, b8, S4, 1, 4);
+T (a8, b8, S4, 2, 4);
+T (a8, b8, S4, 3, 4);
+T (a8, b8, S4, 4, 4);
+T (a8, b8, S4, 5, 4);   // { dg-warning "writing 4 bytes into a region of size 3" } */
+T (a8, b8, S4, 6, 4);   // { dg-warning "writing 4 bytes into a region of size 2" } */
+T (a8, b8, S4, 7, 4);   // { dg-warning "writing 4 bytes into a region of size 1" } */
+T (a8, b8, S4, 8, 4);   // { dg-warning "writing 4 bytes into a region of size 0" } */
+
+T (a8, b8, S4, 0, 5);
+T (a8, b8, S4, 1, 5);
+T (a8, b8, S4, 2, 5);
+T (a8, b8, S4, 3, 5);
+T (a8, b8, S4, 4, 5);   // { dg-warning "writing 5 bytes into a region of size 4" } */
+T (a8, b8, S4, 5, 5);   // { dg-warning "writing 5 bytes into a region of size 3" } */
+T (a8, b8, S4, 6, 5);   // { dg-warning "writing 5 bytes into a region of size 2" } */
+T (a8, b8, S4, 7, 5);   // { dg-warning "writing 5 bytes into a region of size 1" } */
+T (a8, b8, S4, 8, 5);   // { dg-warning "writing 5 bytes into a region of size 0" } */
+
+T (a8, b8, S4, 0, 6);
+T (a8, b8, S4, 1, 6);
+T (a8, b8, S4, 2, 6);
+T (a8, b8, S4, 3, 6);   // { dg-warning "writing 6 bytes into a region of size 5" } */
+T (a8, b8, S4, 4, 6);   // { dg-warning "writing 6 bytes into a region of size 4" } */
+T (a8, b8, S4, 5, 6);   // { dg-warning "writing 6 bytes into a region of size 3" } */
+T (a8, b8, S4, 6, 6);   // { dg-warning "writing 6 bytes into a region of size 2" } */
+T (a8, b8, S4, 7, 6);   // { dg-warning "writing 6 bytes into a region of size 1" } */
+T (a8, b8, S4, 8, 6);   // { dg-warning "writing 6 bytes into a region of size 0" } */
+
+
+#undef T
+#define T(dst, init, off, n)				\
+  NOIPA void CAT (test_on_line_, __LINE__) (char *src)	\
+  {							\
+    __builtin_strcpy (src, init);			\
+    char *d = dst + off;				\
+    __builtin_memcpy (d, src, n);			\
+  } typedef void dummy_type
+
+T (a6, S4, 0, 4);
+T (a6, S4, 1, 4);
+T (a6, S4, 2, 4);
+T (a6, S4, 3, 4);   // { dg-warning "writing 4 bytes into a region of size 3" } */
+T (a6, S4, 4, 4);   // { dg-warning "writing 4 bytes into a region of size 2" } */
+T (a6, S4, 5, 4);   // { dg-warning "writing 4 bytes into a region of size 1" } */
+T (a6, S4, 6, 4);   // { dg-warning "writing 4 bytes into a region of size 0" } */
+
+T (a7, S4, 0, 4);
+T (a7, S4, 1, 4);
+T (a7, S4, 2, 4);
+T (a7, S4, 3, 4);
+T (a7, S4, 4, 4);   // { dg-warning "writing 4 bytes into a region of size 3" } */
+T (a7, S4, 5, 4);   // { dg-warning "writing 4 bytes into a region of size 2" } */
+T (a7, S4, 6, 4);   // { dg-warning "writing 4 bytes into a region of size 1" } */
+T (a7, S4, 7, 4);   // { dg-warning "writing 4 bytes into a region of size 0" } */
+
+T (a8, S3, 0, 4);
+T (a8, S3, 1, 4);
+T (a8, S3, 2, 4);
+T (a8, S3, 3, 4);
+T (a8, S3, 4, 4);
+T (a8, S3, 5, 4);   // { dg-warning "writing 4 bytes into a region of size 3" } */
+T (a8, S3, 6, 4);   // { dg-warning "writing 4 bytes into a region of size 2" } */
+T (a8, S3, 7, 4);   // { dg-warning "writing 4 bytes into a region of size 1" } */
+T (a8, S3, 8, 4);   // { dg-warning "writing 4 bytes into a region of size 0" } */