diff options
author | Martin Sebor <msebor@redhat.com> | 2021-10-26 14:40:33 -0600 |
---|---|---|
committer | Martin Sebor <msebor@redhat.com> | 2021-10-26 16:53:54 -0600 |
commit | 1ff4dbddcf74203a1e16316b18e12f9e1b5085f0 (patch) | |
tree | 5f6066e4ea74c6ac5a9a3e6e52ae5abb0a6cc95b /gcc/gimple-ssa-sprintf.c | |
parent | 9a27acc30a34b7854db32eac562306cebac6fa1e (diff) | |
download | gcc-1ff4dbddcf74203a1e16316b18e12f9e1b5085f0.zip gcc-1ff4dbddcf74203a1e16316b18e12f9e1b5085f0.tar.gz gcc-1ff4dbddcf74203a1e16316b18e12f9e1b5085f0.tar.bz2 |
Improve/correct detection of overlapping aggregates [PR102238, PR102919].
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.
Diffstat (limited to 'gcc/gimple-ssa-sprintf.c')
-rw-r--r-- | gcc/gimple-ssa-sprintf.c | 250 |
1 files changed, 136 insertions, 114 deletions
diff --git a/gcc/gimple-ssa-sprintf.c b/gcc/gimple-ssa-sprintf.c index 8f42cb5..ec78090 100644 --- a/gcc/gimple-ssa-sprintf.c +++ b/gcc/gimple-ssa-sprintf.c @@ -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) { |