]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
Improve/correct detection of overlapping aggregates [PR102238, PR102919].
authorMartin Sebor <msebor@redhat.com>
Tue, 26 Oct 2021 20:40:33 +0000 (14:40 -0600)
committerMartin Sebor <msebor@redhat.com>
Tue, 26 Oct 2021 22:53:54 +0000 (16:53 -0600)
Resolves:
PR tree-optimization/102238 - alias_offset in gimple-ssa-sprintf.c is broken
PR tree-optimization/102919 - spurious -Wrestrict warning for sprintf into the same member array as argument plus offset

gcc/ChangeLog:

PR tree-optimization/102238
PR tree-optimization/102919
* gimple-ssa-sprintf.c (get_string_length): Add an argument.
(array_elt_at_offset): Move to pointer-query.
(set_aggregate_size_and_offset): New function.
(field_at_offset):  Move to pointer-query.
(get_origin_and_offset): Rename...
(get_origin_and_offset_r): this.  Add an argument.  Make aggregate
handling more robust.
(get_origin_and_offset): New.
(alias_offset): Add an argument.
(format_string): Use subobject size determined by get_origin_and_offset.
* pointer-query.cc (field_at_offset): Move from gimple-ssa-sprintf.c.
Improve/correct handling of aggregates.
(array_elt_at_offset): Same.
* pointer-query.h (field_at_offset): Declare.
(array_elt_at_offset): Declare.

gcc/testsuite/ChangeLog:

PR tree-optimization/102238
PR tree-optimization/102919
* gcc.dg/tree-ssa/builtin-sprintf-warn-23.c: Remove warnings.
* gcc.dg/Wrestrict-23.c: New test.

gcc/gimple-ssa-sprintf.c
gcc/pointer-query.cc
gcc/pointer-query.h
gcc/testsuite/gcc.dg/Wrestrict-23.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-23.c

index 8f42cb59540906c9167c03140a426d39e5ad2847..ec78090b382ef8060fda9326c48b39f3f07680e3 100644 (file)
@@ -2024,8 +2024,8 @@ format_floating (const directive &dir, tree arg, range_query *)
    Used by the format_string function below.  */
 
 static fmtresult
-get_string_length (tree str, gimple *stmt, unsigned eltsize,
-                  range_query *query)
+get_string_length (tree str, gimple *stmt, unsigned HOST_WIDE_INT max_size,
+                  unsigned eltsize, range_query *query)
 {
   if (!str)
     return fmtresult ();
@@ -2065,6 +2065,20 @@ get_string_length (tree str, gimple *stmt, unsigned eltsize,
       && (!lendata.maxbound || lenmax <= tree_to_uhwi (lendata.maxbound))
       && lenmax <= tree_to_uhwi (lendata.maxlen))
     {
+      if (max_size > 0 && max_size < HOST_WIDE_INT_MAX)
+       {
+         /* Adjust the conservative unknown/unbounded result if MAX_SIZE
+            is valid.  Set UNLIKELY to maximum in case MAX_SIZE refers
+            to a subobject.
+            TODO: This is overly conservative.  Set UNLIKELY to the size
+            of the outermost enclosing declared object.  */
+         fmtresult res (0, max_size - 1);
+         res.nonstr = lendata.decl;
+         res.range.likely = res.range.max;
+         res.range.unlikely = HOST_WIDE_INT_MAX;
+         return res;
+       }
+
       fmtresult res;
       res.nonstr = lendata.decl;
       return res;
@@ -2203,110 +2217,80 @@ format_character (const directive &dir, tree arg, range_query *query)
   return res.adjust_for_width_or_precision (dir.width);
 }
 
-/* Determine the offset *INDEX of the first byte of an array element of
-   TYPE (possibly recursively) into which the byte offset OFF points.
-   On success set *INDEX to the offset of the first byte and return type.
-   Otherwise, if no such element can be found, return null.  */
+/* If TYPE is an array or struct or union, increment *FLDOFF by the starting
+   offset of the member that *OFF point into and set *FLDSIZE to its size
+   in bytes and decrement *OFF by the same.  Otherwise do nothing.  */
 
-static tree
-array_elt_at_offset (tree type, HOST_WIDE_INT off, HOST_WIDE_INT *index)
+static void
+set_aggregate_size_and_offset (tree type, HOST_WIDE_INT *fldoff,
+                              HOST_WIDE_INT *fldsize, HOST_WIDE_INT *off)
 {
-  gcc_assert (TREE_CODE (type) == ARRAY_TYPE);
-
-  tree eltype = type;
-  while (TREE_CODE (TREE_TYPE (eltype)) == ARRAY_TYPE)
-    eltype = TREE_TYPE (eltype);
-
-  if (TYPE_MODE (TREE_TYPE (eltype)) != TYPE_MODE (char_type_node))
-    eltype = TREE_TYPE (eltype);
-
-  if (eltype == type)
-    {
-      *index = 0;
-      return type;
-    }
-
-  HOST_WIDE_INT typsz = int_size_in_bytes (type);
-  HOST_WIDE_INT eltsz = int_size_in_bytes (eltype);
-  if (off < typsz * eltsz)
+  /* The byte offset of the most basic struct member the byte
+     offset *OFF corresponds to, or for a (multidimensional)
+     array member, the byte offset of the array element.  */
+  if (TREE_CODE (type) == ARRAY_TYPE
+      && TREE_CODE (TREE_TYPE (type)) == ARRAY_TYPE)
     {
-      *index = (off / eltsz) * eltsz;
-      return TREE_CODE (eltype) == ARRAY_TYPE ? TREE_TYPE (eltype) : eltype;
+      HOST_WIDE_INT index = 0, arrsize = 0;
+      if (array_elt_at_offset (type, *off, &index, &arrsize))
+       {
+         *fldoff += index;
+         *off -= index;
+         *fldsize = arrsize;
+       }
     }
-
-  return NULL_TREE;
-}
-
-/* Determine the offset *INDEX of the first byte of a struct member of TYPE
-   (possibly recursively) into which the byte offset OFF points.  On success
-   set *INDEX to the offset of the first byte and return true.  Otherwise,
-   if no such member can be found, return false.  */
-
-static bool
-field_at_offset (tree type, HOST_WIDE_INT off, HOST_WIDE_INT *index)
-{
-  gcc_assert (RECORD_OR_UNION_TYPE_P (type));
-
-  for (tree fld = TYPE_FIELDS (type); fld; fld = TREE_CHAIN (fld))
+  else if (RECORD_OR_UNION_TYPE_P (type))
     {
-      if (TREE_CODE (fld) != FIELD_DECL || DECL_ARTIFICIAL (fld))
-       continue;
-
-      tree fldtype = TREE_TYPE (fld);
-      HOST_WIDE_INT fldoff = int_byte_position (fld);
-
-      /* If the size is not available the field is a flexible array
-        member.  Treat this case as success.  */
-      tree typesize = TYPE_SIZE_UNIT (fldtype);
-      HOST_WIDE_INT fldsize = (tree_fits_uhwi_p (typesize)
-                              ? tree_to_uhwi (typesize)
-                              : off);
-
-      if (fldoff + fldsize < off)
-       continue;
-
-      if (TREE_CODE (fldtype) == ARRAY_TYPE)
+      HOST_WIDE_INT index = 0;
+      tree sub = field_at_offset (type, NULL_TREE, *off, &index);
+      if (sub)
        {
-         HOST_WIDE_INT idx = 0;
-         if (tree ft = array_elt_at_offset (fldtype, off, &idx))
-           fldtype = ft;
+         tree subsize = DECL_SIZE_UNIT (sub);
+         if (*fldsize < HOST_WIDE_INT_MAX
+             && subsize
+             && tree_fits_uhwi_p (subsize))
+           *fldsize = tree_to_uhwi (subsize);
          else
-           break;
-
-         *index += idx;
-         fldoff -= idx;
-         off -= idx;
-       }
-
-      if (RECORD_OR_UNION_TYPE_P (fldtype))
-       {
-         *index += fldoff;
-         return field_at_offset (fldtype, off - fldoff, index);
+           *fldsize = HOST_WIDE_INT_MAX;
+         *fldoff += index;
+         *off -= index;
        }
-
-      *index += fldoff;
-      return true;
     }
-
-  return false;
 }
 
 /* For an expression X of pointer type, recursively try to find the same
-   origin (object or pointer) as Y it references and return such an X.
-   When X refers to a struct member, set *FLDOFF to the offset of the
-   member from the beginning of the "most derived" object.  */
+   origin (object or pointer) as Y it references and return such a Y.
+   When X refers to an array element or struct member, set *FLDOFF to
+   the offset of the element or member from the beginning of the "most
+   derived" object and *FLDSIZE to its size.  When nonnull, set *OFF to
+   the overall offset from the beginning of the object so that
+   *FLDOFF <= *OFF.  */
 
 static tree
-get_origin_and_offset (tree x, HOST_WIDE_INT *fldoff, HOST_WIDE_INT *off)
+get_origin_and_offset_r (tree x, HOST_WIDE_INT *fldoff, HOST_WIDE_INT *fldsize,
+                        HOST_WIDE_INT *off)
 {
   if (!x)
     return NULL_TREE;
 
+  HOST_WIDE_INT sizebuf = -1;
+  if (!fldsize)
+    fldsize = &sizebuf;
+
+  if (DECL_P (x))
+    {
+      /* Set the size if it hasn't been set yet.  */
+      if (tree size = DECL_SIZE_UNIT (x))
+       if (*fldsize < 0 && tree_fits_shwi_p (size))
+         *fldsize = tree_to_shwi (size);
+      return x;
+    }
+
   switch (TREE_CODE (x))
     {
     case ADDR_EXPR:
       x = TREE_OPERAND (x, 0);
-      return get_origin_and_offset (x, fldoff, off);
+      return get_origin_and_offset_r (x, fldoff, fldsize, off);
 
     case ARRAY_REF:
       {
@@ -2326,7 +2310,7 @@ get_origin_and_offset (tree x, HOST_WIDE_INT *fldoff, HOST_WIDE_INT *off)
          *fldoff = idx;
 
        x = TREE_OPERAND (x, 0);
-       return get_origin_and_offset (x, fldoff, NULL);
+       return get_origin_and_offset_r (x, fldoff, fldsize, nullptr);
       }
 
     case MEM_REF:
@@ -2345,32 +2329,19 @@ get_origin_and_offset (tree x, HOST_WIDE_INT *fldoff, HOST_WIDE_INT *off)
            = (TREE_CODE (x) == ADDR_EXPR
               ? TREE_TYPE (TREE_OPERAND (x, 0)) : TREE_TYPE (TREE_TYPE (x)));
 
-         /* The byte offset of the most basic struct member the byte
-            offset *OFF corresponds to, or for a (multidimensional)
-            array member, the byte offset of the array element.  */
-         HOST_WIDE_INT index = 0;
-
-         if ((RECORD_OR_UNION_TYPE_P (xtype)
-              && field_at_offset (xtype, *off, &index))
-             || (TREE_CODE (xtype) == ARRAY_TYPE
-                 && TREE_CODE (TREE_TYPE (xtype)) == ARRAY_TYPE
-                 && array_elt_at_offset (xtype, *off, &index)))
-           {
-             *fldoff += index;
-             *off -= index;
-           }
+         set_aggregate_size_and_offset (xtype, fldoff, fldsize, off);
        }
 
-      return get_origin_and_offset (x, fldoff, NULL);
+      return get_origin_and_offset_r (x, fldoff, fldsize, nullptr);
 
     case COMPONENT_REF:
       {
        tree fld = TREE_OPERAND (x, 1);
        *fldoff += int_byte_position (fld);
 
-       get_origin_and_offset (fld, fldoff, off);
+       get_origin_and_offset_r (fld, fldoff, fldsize, off);
        x = TREE_OPERAND (x, 0);
-       return get_origin_and_offset (x, fldoff, off);
+       return get_origin_and_offset_r (x, fldoff, nullptr, off);
       }
 
     case SSA_NAME:
@@ -2382,27 +2353,41 @@ get_origin_and_offset (tree x, HOST_WIDE_INT *fldoff, HOST_WIDE_INT *off)
            if (code == ADDR_EXPR)
              {
                x = gimple_assign_rhs1 (def);
-               return get_origin_and_offset (x, fldoff, off);
+               return get_origin_and_offset_r (x, fldoff, fldsize, off);
              }
 
            if (code == POINTER_PLUS_EXPR)
              {
                tree offset = gimple_assign_rhs2 (def);
-               if (off)
-                 *off = (tree_fits_uhwi_p (offset)
-                         ? tree_to_uhwi (offset) : HOST_WIDE_INT_MAX);
+               if (off && tree_fits_uhwi_p (offset))
+                 *off = tree_to_uhwi (offset);
 
                x = gimple_assign_rhs1 (def);
-               return get_origin_and_offset (x, fldoff, NULL);
+               x = get_origin_and_offset_r (x, fldoff, fldsize, off);
+               if (off && !tree_fits_uhwi_p (offset))
+                 *off = HOST_WIDE_INT_MAX;
+               if (off)
+                 {
+                   tree xtype = TREE_TYPE (x);
+                   set_aggregate_size_and_offset (xtype, fldoff, fldsize, off);
+                 }
+               return x;
              }
            else if (code == VAR_DECL)
              {
                x = gimple_assign_rhs1 (def);
-               return get_origin_and_offset (x, fldoff, off);
+               return get_origin_and_offset_r (x, fldoff, fldsize, off);
              }
          }
        else if (gimple_nop_p (def) && SSA_NAME_VAR (x))
          x = SSA_NAME_VAR (x);
+
+       tree xtype = TREE_TYPE (x);
+       if (POINTER_TYPE_P (xtype))
+         xtype = TREE_TYPE (xtype);
+
+       if (off)
+         set_aggregate_size_and_offset (xtype, fldoff, fldsize, off);
       }
 
     default:
@@ -2412,13 +2397,41 @@ get_origin_and_offset (tree x, HOST_WIDE_INT *fldoff, HOST_WIDE_INT *off)
   return x;
 }
 
+/* Nonrecursive version of the above.  */
+
+static tree
+get_origin_and_offset (tree x, HOST_WIDE_INT *fldoff, HOST_WIDE_INT *off,
+                      HOST_WIDE_INT *fldsize = nullptr)
+{
+  HOST_WIDE_INT sizebuf;
+  if (!fldsize)
+    fldsize = &sizebuf;
+
+  *fldsize = -1;
+
+  *fldoff = *off = *fldsize = 0;
+  tree orig = get_origin_and_offset_r (x, fldoff, fldsize, off);
+  if (!orig)
+    return NULL_TREE;
+
+  if (!*fldoff && *off == *fldsize)
+    {
+      *fldoff = *off;
+      *off = 0;
+    }
+
+  return orig;
+}
+
 /* If ARG refers to the same (sub)object or array element as described
    by DST and DST_FLD, return the byte offset into the struct member or
-   array element referenced by ARG.  Otherwise return HOST_WIDE_INT_MIN
-   to indicate that ARG and DST do not refer to the same object.  */
+   array element referenced by ARG and set *ARG_SIZE to the size of
+   the (sub)object.  Otherwise return HOST_WIDE_INT_MIN to indicate
+   that ARG and DST do not refer to the same object.  */
 
 static HOST_WIDE_INT
-alias_offset (tree arg, tree dst, HOST_WIDE_INT dst_fld)
+alias_offset (tree arg, HOST_WIDE_INT *arg_size,
+             tree dst, HOST_WIDE_INT dst_fld)
 {
   /* See if the argument refers to the same base object as the destination
      of the formatted function call, and if so, try to determine if they
@@ -2430,7 +2443,7 @@ alias_offset (tree arg, tree dst, HOST_WIDE_INT dst_fld)
      to a struct member, see if the members are one and the same.  */
   HOST_WIDE_INT arg_off = 0, arg_fld = 0;
 
-  tree arg_orig = get_origin_and_offset (arg, &arg_fld, &arg_off);
+  tree arg_orig = get_origin_and_offset (arg, &arg_fld, &arg_off, arg_size);
 
   if (arg_orig == dst && arg_fld == dst_fld)
     return arg_off;
@@ -2448,6 +2461,10 @@ format_string (const directive &dir, tree arg, range_query *query)
 {
   fmtresult res;
 
+  /* The size of the (sub)object ARG refers to.  Used to adjust
+     the conservative get_string_length() result.  */
+  HOST_WIDE_INT arg_size = 0;
+
   if (warn_restrict)
     {
       /* See if ARG might alias the destination of the call with
@@ -2455,8 +2472,12 @@ format_string (const directive &dir, tree arg, range_query *query)
         so that the overlap can be determined for certain later,
         when the amount of output of the call (including subsequent
         directives) has been computed.  Otherwise, store HWI_MIN.  */
-      res.dst_offset = alias_offset (arg, dir.info->dst_origin,
+      res.dst_offset = alias_offset (arg, &arg_size, dir.info->dst_origin,
                                     dir.info->dst_field);
+      if (res.dst_offset >= 0 && res.dst_offset <= arg_size)
+       arg_size -= res.dst_offset;
+      else
+       arg_size = 0;
     }
 
   /* Compute the range the argument's length can be in.  */
@@ -2473,7 +2494,8 @@ format_string (const directive &dir, tree arg, range_query *query)
       gcc_checking_assert (count_by == 2 || count_by == 4);
     }
 
-  fmtresult slen = get_string_length (arg, dir.info->callstmt, count_by, query);
+  fmtresult slen =
+    get_string_length (arg, dir.info->callstmt, arg_size, count_by, query);
   if (slen.range.min == slen.range.max
       && slen.range.min < HOST_WIDE_INT_MAX)
     {
index 3b9f970846b90ac92d6925eec9ac5eb66de0a3c5..a0e4543d8a3e86575d01ced95afc64ebc73130c9 100644 (file)
@@ -2194,3 +2194,167 @@ compute_objsize (tree ptr, int ostype, tree *pdecl /* = NULL */,
 
   return size;
 }
+
+/* Determine the offset *FLDOFF of the first byte of a struct member
+   of TYPE (possibly recursively) into which the byte offset OFF points,
+   starting after the field START_AFTER if it's non-null.  On success,
+   if nonnull, set *FLDOFF to the offset of the first byte, and return
+   the field decl.  If nonnull, set *NEXTOFF to the offset of the next
+   field (which reflects any padding between the returned field and
+   the next).  Otherwise, if no such member can be found, return null.  */
+
+tree
+field_at_offset (tree type, tree start_after, HOST_WIDE_INT off,
+                HOST_WIDE_INT *fldoff /* = nullptr */,
+                HOST_WIDE_INT *nextoff /* = nullptr */)
+{
+  tree first_fld = TYPE_FIELDS (type);
+
+  HOST_WIDE_INT offbuf = 0, nextbuf = 0;
+  if (!fldoff)
+    fldoff = &offbuf;
+  if (!nextoff)
+    nextoff = &nextbuf;
+
+  *nextoff = 0;
+
+  /* The field to return.  */
+  tree last_fld = NULL_TREE;
+  /* The next field to advance to.  */
+  tree next_fld = NULL_TREE;
+
+  /* NEXT_FLD's cached offset.  */
+  HOST_WIDE_INT next_pos = -1;
+
+  for (tree fld = first_fld; fld; fld = next_fld)
+    {
+      next_fld = fld;
+      do
+       /* Advance to the next relevant data member.  */
+       next_fld = TREE_CHAIN (next_fld);
+      while (next_fld
+            && (TREE_CODE (next_fld) != FIELD_DECL
+                || DECL_ARTIFICIAL (next_fld)));
+
+      if (TREE_CODE (fld) != FIELD_DECL || DECL_ARTIFICIAL (fld))
+       continue;
+
+      if (fld == start_after)
+       continue;
+
+      tree fldtype = TREE_TYPE (fld);
+      /* The offset of FLD within its immediately enclosing structure.  */
+      HOST_WIDE_INT fldpos = next_pos < 0 ? int_byte_position (fld) : next_pos;
+
+      /* If the size is not available the field is a flexible array
+        member.  Treat this case as success.  */
+      tree typesize = TYPE_SIZE_UNIT (fldtype);
+      HOST_WIDE_INT fldsize = (tree_fits_uhwi_p (typesize)
+                              ? tree_to_uhwi (typesize)
+                              : off);
+
+      /* If OFF is beyond the end of the current field continue.  */
+      HOST_WIDE_INT fldend = fldpos + fldsize;
+      if (fldend < off)
+       continue;
+
+      if (next_fld)
+       {
+         /* If OFF is equal to the offset of the next field continue
+            to it and skip the array/struct business below.  */
+         next_pos = int_byte_position (next_fld);
+         *nextoff = *fldoff + next_pos;
+         if (*nextoff == off && TREE_CODE (type) != UNION_TYPE)
+           continue;
+       }
+      else
+       *nextoff = HOST_WIDE_INT_MAX;
+
+      /* OFF refers somewhere into the current field or just past its end,
+        which could mean it refers to the next field.  */
+      if (TREE_CODE (fldtype) == ARRAY_TYPE)
+       {
+         /* Will be set to the offset of the first byte of the array
+            element (which may be an array) of FLDTYPE into which
+            OFF - FLDPOS points (which may be past ELTOFF).  */
+         HOST_WIDE_INT eltoff = 0;
+         if (tree ft = array_elt_at_offset (fldtype, off - fldpos, &eltoff))
+           fldtype = ft;
+         else
+           continue;
+
+         /* Advance the position to include the array element above.
+            If OFF - FLPOS refers to a member of FLDTYPE, the member
+            will be determined below.  */
+         fldpos += eltoff;
+       }
+
+      *fldoff += fldpos;
+
+      if (TREE_CODE (fldtype) == RECORD_TYPE)
+       /* Drill down into the current field if it's a struct.  */
+       fld = field_at_offset (fldtype, start_after, off - fldpos,
+                              fldoff, nextoff);
+
+      last_fld = fld;
+
+      /* Unless the offset is just past the end of the field return it.
+        Otherwise save it and return it only if the offset of the next
+        next field is greater (i.e., there is padding between the two)
+        or if there is no next field.  */
+      if (off < fldend)
+       break;
+    }
+
+  if (*nextoff == HOST_WIDE_INT_MAX && next_fld)
+    *nextoff = next_pos;
+
+  return last_fld;
+}
+
+/* Determine the offset *ELTOFF of the first byte of the array element
+   of array ARTYPE into which the byte offset OFF points.  On success
+   set *ELTOFF to the offset of the first byte and return type.
+   Otherwise, if no such element can be found, return null.  */
+
+tree
+array_elt_at_offset (tree artype, HOST_WIDE_INT off,
+                    HOST_WIDE_INT *eltoff /* = nullptr */,
+                    HOST_WIDE_INT *subar_size /* = nullptr */)
+{
+  gcc_assert (TREE_CODE (artype) == ARRAY_TYPE);
+
+  HOST_WIDE_INT dummy;
+  if (!eltoff)
+    eltoff = &dummy;
+  if (!subar_size)
+    subar_size = &dummy;
+
+  tree eltype = artype;
+  while (TREE_CODE (TREE_TYPE (eltype)) == ARRAY_TYPE)
+    eltype = TREE_TYPE (eltype);
+
+  tree subartype = eltype;
+  if (RECORD_OR_UNION_TYPE_P (TREE_TYPE (eltype))
+      || TYPE_MODE (TREE_TYPE (eltype)) != TYPE_MODE (char_type_node))
+    eltype = TREE_TYPE (eltype);
+
+  *subar_size = int_size_in_bytes (subartype);
+
+  if (eltype == artype)
+    {
+      *eltoff = 0;
+      return artype;
+    }
+
+  HOST_WIDE_INT artype_size = int_size_in_bytes (artype);
+  HOST_WIDE_INT eltype_size = int_size_in_bytes (eltype);
+
+  if (off < artype_size)// * eltype_size)
+    {
+      *eltoff = (off / eltype_size) * eltype_size;
+      return TREE_CODE (eltype) == ARRAY_TYPE ? TREE_TYPE (eltype) : eltype;
+    }
+
+  return NULL_TREE;
+}
index 96c500132b6ca752bb97df8e01819720fafed62a..c8215b681ef859442847af3d0a4d8408ebf070af 100644 (file)
@@ -266,4 +266,13 @@ inline tree compute_objsize (tree ptr, int ostype, access_ref *pref)
 extern tree compute_objsize (tree, int, tree * = nullptr, tree * = nullptr,
                             range_query * = nullptr);
 
+/* Return the field at the constant offset.  */
+extern tree field_at_offset (tree, tree, HOST_WIDE_INT,
+                            HOST_WIDE_INT * = nullptr,
+                            HOST_WIDE_INT * = nullptr);
+/* Return the array at the constant offset.  */
+extern tree array_elt_at_offset (tree, HOST_WIDE_INT,
+                                HOST_WIDE_INT * = nullptr,
+                                HOST_WIDE_INT * = nullptr);
+
 #endif   // GCC_POINTER_QUERY_H
diff --git a/gcc/testsuite/gcc.dg/Wrestrict-23.c b/gcc/testsuite/gcc.dg/Wrestrict-23.c
new file mode 100644 (file)
index 0000000..c7a828b
--- /dev/null
@@ -0,0 +1,146 @@
+/* PR tree-optimization/102238 - missing -Wrestrict on sprintf formatting
+   a struct member into enclosing object
+   { dg-do compile }
+   { dg-options "-O2 -Wall -Wno-format-overflow" } */
+
+extern int sprintf (char*, const char*, ...);
+
+extern void sink (void*, ...);
+
+struct A
+{
+  char a[4];
+};
+
+struct B
+{
+  struct A a1, a2;
+};
+
+extern struct B eb;
+
+enum { B_a2_a_off = __builtin_offsetof (struct B, a2.a) };
+
+
+void test_warn_src_decl_plus (void)
+{
+  {
+    char *s = (char*)&eb + B_a2_a_off;
+    char *d = eb.a2.a;
+    sprintf (d, "%s", s);     // { dg-warning "overlaps" }
+  }
+
+  {
+    // If strlen (s) > 0 there is overlap with a[1].
+    char *s = (char*)&eb + B_a2_a_off + 1;
+    char *d = eb.a2.a;
+    sprintf (d, "%s", s);     // { dg-warning "may overlap" }
+  }
+
+  {
+    // strlen (s) must be at most 1 so there can be no overlap with a.
+    char *s = (char*)&eb + B_a2_a_off + 2;
+    char *d = eb.a2.a;
+    sprintf (d, "%s", s);     // { dg-bogus "-Wrestrict" }
+  }
+
+  {
+    // strlen (s) must be at most 0 so there can be no overlap with a.
+    char *s = (char*)&eb + B_a2_a_off + 3;
+    char *d = eb.a2.a;
+    sprintf (d, "%s", s);     // { dg-bogus "-Wrestrict" }
+  }
+}
+
+
+void test_warn_src_ptr_plus (struct B *p)
+{
+  {
+    char *s = (char*)p + B_a2_a_off;
+    char *d = p->a2.a;
+    sprintf (d, "%s", s);     // { dg-warning "overlaps" }
+  }
+
+  {
+    // If strlen (s) > 0 there is overlap with a[1].
+    char *s = (char*)p + B_a2_a_off + 1;
+    char *d = p->a2.a;
+    sprintf (d, "%s", s);     // { dg-warning "may overlap" }
+  }
+
+  {
+    // strlen (s) must be at most 1 so there can be no overlap with a.
+    char *s = (char*)p + B_a2_a_off + 2;
+    char *d = p->a2.a;
+    sprintf (d, "%s", s);     // { dg-bogus "-Wrestrict" }
+  }
+
+  {
+    // strlen (s) must be at most 0 so there can be no overlap with a.
+    char *s = (char*)p + B_a2_a_off + 3;
+    char *d = p->a2.a;
+    sprintf (d, "%s", s);     // { dg-bogus "-Wrestrict" }
+  }
+}
+
+
+void test_warn_dst_decl_plus (void)
+{
+  {
+    char *s = eb.a2.a;
+    char *d = (char*)&eb + B_a2_a_off;
+    sprintf (d, "%s", s);     // { dg-warning "overlaps" }
+  }
+
+  {
+    // If strlen (a) > 0 there is overlap with a[1].
+    char *s = eb.a2.a;
+    char *d = (char*)&eb + B_a2_a_off + 1;
+    sprintf (d, "%s", s);     // { dg-warning "may overlap" }
+  }
+
+  {
+    // If strlen (a) > 1 there is overlap with a[2].
+    char *s = eb.a2.a;
+    char *d = (char*)&eb + B_a2_a_off + 2;
+    sprintf (d, "%s", s);     // { dg-warning "may overlap" }
+  }
+
+  {
+    // If strlen (a) > 2 there is overlap with a[3].
+    char *s = eb.a2.a;
+    char *d = (char*)&eb + B_a2_a_off + 3;
+    sprintf (d, "%s", s);     // { dg-warning "may overlap" }
+  }
+}
+
+
+void test_warn_dst_ptr_plus (struct B *p)
+{
+  {
+    char *s = p->a2.a;
+    char *d = (char*)p + B_a2_a_off;
+    sprintf (d, "%s", s);     // { dg-warning "overlaps" }
+  }
+
+  {
+    // If strlen (a) > 0 there is overlap with a[1].
+    char *s = p->a2.a;
+    char *d = (char*)p + B_a2_a_off + 1;
+    sprintf (d, "%s", s);     // { dg-warning "may overlap" }
+  }
+
+  {
+    // If strlen (a) > 1 there is overlap with a[2].
+    char *s = p->a2.a;
+    char *d = (char*)p + B_a2_a_off + 2;
+    sprintf (d, "%s", s);     // { dg-warning "may overlap" }
+  }
+
+  {
+    // If strlen (a) > 2 there is overlap with a[3].
+    char *s = p->a2.a;
+    char *d = (char*)p + B_a2_a_off + 3;
+    sprintf (d, "%s", s);     // { dg-warning "may overlap" }
+  }
+}
index 7fb96514f1ddff13b346fcf0554abf7625562f39..112b08afc44bef104b494061b015d1284a456b9d 100644 (file)
@@ -214,12 +214,14 @@ void test_struct_member_array (struct S3 *s3, int i)
   T (d, "%s", d);       /* { dg-warning "overlaps" } */
   T (d, "%s", d + 0);   /* { dg-warning "overlaps" } */
   T (d, "%s", d + 1);   /* { dg-warning "may overlap" } */
-  T (d, "%s", d + 2);   /* { dg-warning "may overlap" } */
+  /* Since d below points to char[4], strlen(d + 2) must be at most 1
+     and so the call cannot overlap. */
+  T (d, "%s", d + 2);
   T (d, "%s", d + i);   /* { dg-warning "may overlap" } */
 
   T (d, "%s", &d[0]);   /* { dg-warning "overlaps" } */
   T (d, "%s", &d[1]);   /* { dg-warning "may overlap" } */
-  T (d, "%s", &d[2]);   /* { dg-warning "may overlap" } */
+  T (d, "%s", &d[2]);
   T (d, "%s", &d[i]);   /* { dg-warning "may overlap" } */
 
   T (d + 0, "%s", d);   /* { dg-warning "overlaps" } */
@@ -236,7 +238,7 @@ void test_struct_member_array (struct S3 *s3, int i)
 
   T (d, "%s", s);       /* { dg-warning "overlaps" } */
   T (d, "%s", s + 1);   /* { dg-warning "may overlap" } */
-  T (d, "%s", s + 2);   /* { dg-warning "may overlap" } */
+  T (d, "%s", s + 2);
   T (d, "%s", s + i);   /* { dg-warning "may overlap" } */
 
   s = s3->s2_1.s_1.b;
@@ -324,7 +326,7 @@ void test_struct_member_array (struct S3 *s3, int i)
 
   T (d, "%s", s);       /* { dg-warning "overlaps" } */
   T (d, "%s", s + 1);   /* { dg-warning "may overlap" } */
-  T (d, "%s", s + 2);   /* { dg-warning "may overlap" } */
+  T (d, "%s", s + 2);
   T (d, "%s", s + i);   /* { dg-warning "may overlap" } */
 
   s = s3->s2_2.s_2.a;
@@ -368,7 +370,7 @@ void test_struct_member_array (struct S3 *s3, int i)
 
   T (d, "%s", s);       /* { dg-warning "overlaps" } */
   T (d, "%s", s + 1);   /* { dg-warning "may overlap" } */
-  T (d, "%s", s + 2);   /* { dg-warning "may overlap" } */
+  T (d, "%s", s + 2);
   T (d, "%s", s + i);   /* { dg-warning "may overlap" } */
 
   s = s3->s2_2.s_2.a;
@@ -394,12 +396,12 @@ void test_struct_member_array_array (struct S3 *s3, int i)
   T (d, "%s", s);       /* { dg-warning "overlaps" } */
   T (d, "%s", s + 0);   /* { dg-warning "overlaps" } */
   T (d, "%s", s + 1);   /* { dg-warning "may overlap" } */
-  T (d, "%s", s + 2);   /* { dg-warning "may overlap" } */
+  T (d, "%s", s + 2);
   T (d, "%s", s + i);   /* { dg-warning "may overlap" } */
 
   T (d, "%s", &s[0]);   /* { dg-warning "overlaps" } */
   T (d, "%s", &s[1]);   /* { dg-warning "may overlap" } */
-  T (d, "%s", &s[2]);   /* { dg-warning "may overlap" } */
+  T (d, "%s", &s[2]);
   T (d, "%s", &s[i]);   /* { dg-warning "may overlap" } */
 
   T (d + 0, "%s", s);   /* { dg-warning "overlaps" } */
@@ -566,12 +568,12 @@ void test_union_member_array (union U *un, int i)
   T (d, "%s", d);       /* { dg-warning "overlaps" } */
   T (d, "%s", d + 0);   /* { dg-warning "overlaps" } */
   T (d, "%s", d + 1);   /* { dg-warning "may overlap" } */
-  T (d, "%s", d + 2);   /* { dg-warning "may overlap" } */
+  T (d, "%s", d + 2);
   T (d, "%s", d + i);   /* { dg-warning "may overlap" } */
 
   T (d, "%s", &d[0]);   /* { dg-warning "overlaps" } */
   T (d, "%s", &d[1]);   /* { dg-warning "may overlap" } */
-  T (d, "%s", &d[2]);   /* { dg-warning "may overlap" } */
+  T (d, "%s", &d[2]);
   T (d, "%s", &d[i]);   /* { dg-warning "may overlap" } */
 
   T (d + 0, "%s", d);   /* { dg-warning "overlaps" } */
@@ -588,7 +590,7 @@ void test_union_member_array (union U *un, int i)
 
   T (d, "%s", s);       /* { dg-warning "overlaps" } */
   T (d, "%s", s + 1);   /* { dg-warning "may overlap" } */
-  T (d, "%s", s + 2);   /* { dg-warning "may overlap" } */
+  T (d, "%s", s + 2);
   T (d, "%s", s + i);   /* { dg-warning "may overlap" } */
 
   s = un->s2_1.s_1.b;
@@ -616,7 +618,7 @@ void test_union_member_array (union U *un, int i)
 
   T (d, "%s", s);       /* { dg-warning "overlaps" } */
   T (d, "%s", s + 1);   /* { dg-warning "may overlap" } */
-  T (d, "%s", s + 2);   /* { dg-warning "may overlap" } */
+  T (d, "%s", s + 2);
   T (d, "%s", s + i);   /* { dg-warning "may overlap" } */
 
   s = un->s2_2.s_1.b;