When the object size estimate is returned for a PHI node, it is the maximum possible value, which is fine in isolation. When combined with negative offsets however, it may sometimes end up in zero size because the resultant size was larger than the wholesize, leading size_for_offset to conclude that there's a potential underflow. Fix this by allowing a non-strict mode to size_for_offset, which conservatively returns the size (or wholesize) in case of a negative offset.
gcc/ChangeLog: PR tree-optimization/117355 * tree-object-size.cc (size_for_offset): New argument STRICT, return SZ if it is set to false. (plus_stmt_object_size): Adjust call to SIZE_FOR_OFFSET. gcc/testsuite/ChangeLog: PR tree-optimization/117355 * g++.dg/ext/builtin-object-size2.C (test9): New test. (main): Call it. * gcc.dg/builtin-object-size-3.c (test10): Adjust expected size. Signed-off-by: Siddhesh Poyarekar <siddh...@gotplt.org> --- Testing: - bootstrapped on x86_64 - tested on i686, no new regressions - bootstrapp with config-ubsan in progress .../g++.dg/ext/builtin-object-size2.C | 27 ++++++++++++++++++ gcc/testsuite/gcc.dg/builtin-object-size-3.c | 2 +- gcc/tree-object-size.cc | 28 +++++++++++++++---- 3 files changed, 50 insertions(+), 7 deletions(-) diff --git a/gcc/testsuite/g++.dg/ext/builtin-object-size2.C b/gcc/testsuite/g++.dg/ext/builtin-object-size2.C index 7a8f4e62733..45401b5a9c1 100644 --- a/gcc/testsuite/g++.dg/ext/builtin-object-size2.C +++ b/gcc/testsuite/g++.dg/ext/builtin-object-size2.C @@ -406,6 +406,32 @@ test8 (union F *f) FAIL (); } +// PR117355 +#define STR "bbbbbbbbbbbbbbbbbbbbbbbbbbb" + +void +__attribute__ ((noinline)) +test9 (void) +{ + char line[256]; + const char *p = STR; + const char *q = p + sizeof (STR) - 1; + + char *q1 = line; + for (const char *p1 = p; p1 < q;) + { + *q1++ = *p1++; + + if (p1 < q && (*q1++ = *p1++) != '\0') + { + if (__builtin_object_size (q1 - 2, 0) == 0) + __builtin_abort (); + if (__builtin_object_size (q1 - 2, 1) == 0) + __builtin_abort (); + } + } +} + int main (void) { @@ -430,5 +456,6 @@ main (void) union F f, *fp = &f; __asm ("" : "+r" (fp)); test8 (fp); + test9 (); DONE (); } diff --git a/gcc/testsuite/gcc.dg/builtin-object-size-3.c b/gcc/testsuite/gcc.dg/builtin-object-size-3.c index ec2c62c9640..e0c967e003f 100644 --- a/gcc/testsuite/gcc.dg/builtin-object-size-3.c +++ b/gcc/testsuite/gcc.dg/builtin-object-size-3.c @@ -619,7 +619,7 @@ test10 (void) if (__builtin_object_size (p - 3, 2) != sizeof (buf) - i + 3) FAIL (); #else - if (__builtin_object_size (p - 3, 2) != 0) + if (__builtin_object_size (p - 3, 2) != 3) FAIL (); #endif break; diff --git a/gcc/tree-object-size.cc b/gcc/tree-object-size.cc index 09aad88498e..6413ebcca37 100644 --- a/gcc/tree-object-size.cc +++ b/gcc/tree-object-size.cc @@ -344,7 +344,8 @@ init_offset_limit (void) be positive and hence, be within OFFSET_LIMIT for valid offsets. */ static tree -size_for_offset (tree sz, tree offset, tree wholesize = NULL_TREE) +size_for_offset (tree sz, tree offset, tree wholesize = NULL_TREE, + bool strict = true) { gcc_checking_assert (types_compatible_p (TREE_TYPE (sz), sizetype)); @@ -377,9 +378,17 @@ size_for_offset (tree sz, tree offset, tree wholesize = NULL_TREE) return sz; /* Negative or too large offset even after adjustment, cannot be within - bounds of an object. */ + bounds of an object. The exception here is when the base object size + has been overestimated (e.g. through PHI nodes or a COND_EXPR) and the + adjusted offset remains negative. If the caller wants to be + permissive, return the base size. */ if (compare_tree_int (offset, offset_limit) > 0) - return size_zero_node; + { + if (strict) + return size_zero_node; + else + return sz; + } } return size_binop (MINUS_EXPR, size_binop (MAX_EXPR, sz, offset), offset); @@ -1521,16 +1530,23 @@ plus_stmt_object_size (struct object_size_info *osi, tree var, gimple *stmt) addr_object_size (osi, op0, object_size_type, &bytes, &wholesize); } + bool pos_offset = (size_valid_p (op1, 0) + && compare_tree_int (op1, offset_limit) <= 0); + /* size_for_offset doesn't make sense for -1 size, but it does for size 0 since the wholesize could be non-zero and a negative offset could give a non-zero size. */ if (size_unknown_p (bytes, 0)) ; + /* In the static case, We want SIZE_FOR_OFFSET to go a bit easy on us if + it sees a negative offset since BYTES could have been + overestimated. */ else if ((object_size_type & OST_DYNAMIC) || bytes != wholesize - || (size_valid_p (op1, object_size_type) - && compare_tree_int (op1, offset_limit) <= 0)) - bytes = size_for_offset (bytes, op1, wholesize); + || pos_offset) + bytes = size_for_offset (bytes, op1, wholesize, + ((object_size_type & OST_DYNAMIC) + || pos_offset)); /* In the static case, with a negative offset, the best estimate for minimum size is size_unknown but for maximum size, the wholesize is a better estimate than size_unknown. */ -- 2.46.0