diff mbox

tree-optimization/71831 - __builtin_object_size poor results with no optimization

Message ID 57BA0F39.2000808@gmail.com
State New
Headers show

Commit Message

Martin Sebor Aug. 21, 2016, 8:29 p.m. UTC
On 08/20/2016 01:26 AM, Jakub Jelinek wrote:
> On Fri, Aug 19, 2016 at 04:30:47PM -0600, Martin Sebor wrote:
>> The patch looks bigger than it actually is because:
>>
>> 1) It modifies the return type of the function to bool rather than
>>     unsigned HOST_WIDE_INT representing the object size (this was
>>     necessary to avoid having its callers misinterpret zero as
>>     unknown when it means zero bytes).
>
> Can you explain why do you need this?  I don't understand why do you need to
> differentiate between unknown and maximum (or minimum for modes 2 and 3 that
> nobody actually uses in real-world), the builtin after all returns the same
> value for both.  If you want to know if the compiler knows the size
> precisely, you can request both mode 0 (or 1) and 2 (or 3) and compare, if
> the values are the same, it is the exact size, if there is a range, then you
> have minimum and maximum (and, if minimum is 0, but maximum non-zero, you
> really don't know minimum, if maximum is -1, then you really don't know the
> maximum (no object should be better that big).  For the return value, I
> don't see how you could reliably differentiate between the two even if it
> made for whatever strange reason sense - for SSA_NAMEs etc. you have just
> recorded the sizes, not also a flag whether it is unknown or known.

The change makes it possible to fold into constants even at -O0 type
2 and 3 calls to the built-in with zero-sized objects.

fold_builtin_object_size repeatedly calls compute_builtin_object_size
to compute the same result (zero) only to interpret it as a failure
and try over and over, never succeeding.  The built-in call is
ultimately expanded into a zero but that's too late to eliminate code
that depends on it. For example, the following emits the call to abort
that's never executed.

   char a[2];

   void f (void)
   {
     if (__builtin_object_size (a + 2, 2) != 0)
       __builtin_abort ();
   }

With the change, compute_builtin_object_size is called just once and
the call to abort is not emitted.

The initial version of the test verified this folding but adding code
to work around the built-ins various idiosyncrasies inadvertently wound
up removing it.  The attached patch adds a new test to exercise this
folding.

>
>> 2) As a result of a small change to the conditional that controls
>>     the main algorithm of the compute_builtin_object_size function
>>     it changes the depth of its indentation (without actually
>>     changing any of the code there).
>
> If you've done lots of redindentation, then additionally diff -upb would be
> appreciated.

Sure.  The attached diff was created without regard to whitespace.

Martin

Comments

Jeff Law Aug. 25, 2016, 7:02 p.m. UTC | #1
On 08/21/2016 02:29 PM, Martin Sebor wrote:
> On 08/20/2016 01:26 AM, Jakub Jelinek wrote:
>> On Fri, Aug 19, 2016 at 04:30:47PM -0600, Martin Sebor wrote:
>>> The patch looks bigger than it actually is because:
>>>
>>> 1) It modifies the return type of the function to bool rather than
>>>     unsigned HOST_WIDE_INT representing the object size (this was
>>>     necessary to avoid having its callers misinterpret zero as
>>>     unknown when it means zero bytes).
>>
>> Can you explain why do you need this?  I don't understand why do you
>> need to
>> differentiate between unknown and maximum (or minimum for modes 2 and
>> 3 that
>> nobody actually uses in real-world), the builtin after all returns the
>> same
>> value for both.  If you want to know if the compiler knows the size
>> precisely, you can request both mode 0 (or 1) and 2 (or 3) and
>> compare, if
>> the values are the same, it is the exact size, if there is a range,
>> then you
>> have minimum and maximum (and, if minimum is 0, but maximum non-zero, you
>> really don't know minimum, if maximum is -1, then you really don't
>> know the
>> maximum (no object should be better that big).  For the return value, I
>> don't see how you could reliably differentiate between the two even if it
>> made for whatever strange reason sense - for SSA_NAMEs etc. you have just
>> recorded the sizes, not also a flag whether it is unknown or known.
>
> The change makes it possible to fold into constants even at -O0 type
> 2 and 3 calls to the built-in with zero-sized objects.
This refers to the tests in builtin-object-size-17.c, particularly when 
compiled at -O0, right?

We generally haven't worried much about trying to optimize -O0 under the 
theory that the resulting code should as closely match the source as 
closely as possible -- including leaving dead/unreachable code in the 
resulting .o.  That dead/unreachable code may in fact be useful during a 
debugging session when the developer manually changes the state into a 
"can't happen" state and expects the previously dead/unreachable code to 
execute.

But that's a bit of a stretch in my mind and doesn't really apply in 
this case due to how __b_o_s is expanded.  Ultimately we end up with 
conditionals with compile-time constant arguments, so there's no way for 
the programmer to twiddle things (ok, maybe on a target where the 
compare and branch are separate, the developer could put a break on the 
branch, change the condition codes...  but that's *really* a stretch in 
my mind)

So I don't see any reason to object to this part of the patch -- 
essentially it falls under the "don't generate stupid code at -O0" in my 
mind.

More shortly...


jeff
Jeff Law Aug. 25, 2016, 7:18 p.m. UTC | #2
On 08/21/2016 02:29 PM, Martin Sebor wrote:
>
>
>
> gcc-71831.w.diff
>
>
> PR tree-optimization/71831 - __builtin_object_size poor results with no
> 	optimization
>
> gcc/testsuite/ChangeLog:
> 2016-08-21  Martin Sebor  <msebor@redhat.com>
>
> 	PR tree-optimization/71831
> 	* gcc.dg/builtin-object-size-16.c: New test.
> 	* gcc.dg/builtin-object-size-17.c: New test.
>
> gcc/ChangeLog:
> 2016-08-21  Martin Sebor  <msebor@redhat.com>
>
> 	PR tree-optimization/71831
> 	* tree-object-size.h: Return bool instead of the size and add
> 	argument for the size.
> 	* tree-object-size.c (compute_object_offset): Update signature.
> 	(addr_object_size): Same.
> 	(compute_builtin_object_size): Return bool instead of the size
> 	and add argument for the size.  Handle POINTER_PLUS_EXPR when
> 	optimization is disabled.
> 	(expr_object_size): Adjust.
> 	(plus_stmt_object_size): Adjust.
> 	(pass_object_sizes::execute): Adjust.
> 	* builtins.c (fold_builtin_object_size): Adjust.
> 	* doc/extend.texi (Object Size Checking): Update.
> 	* ubsan.c (instrument_object_size): Adjust.
So just to be sure, this change really only helps with our ability to 
discover object sizes with -O0.  Essentially when the objsz pass isn't 
run.   That's my reading and I just want to make sure I read it correctly.

Assuming that is the intent here, this is OK for the trunk.

jeff
Martin Sebor Aug. 25, 2016, 11:56 p.m. UTC | #3
On 08/25/2016 01:02 PM, Jeff Law wrote:
> On 08/21/2016 02:29 PM, Martin Sebor wrote:
>> On 08/20/2016 01:26 AM, Jakub Jelinek wrote:
>>> On Fri, Aug 19, 2016 at 04:30:47PM -0600, Martin Sebor wrote:
>>>> The patch looks bigger than it actually is because:
>>>>
>>>> 1) It modifies the return type of the function to bool rather than
>>>>     unsigned HOST_WIDE_INT representing the object size (this was
>>>>     necessary to avoid having its callers misinterpret zero as
>>>>     unknown when it means zero bytes).
>>>
>>> Can you explain why do you need this?  I don't understand why do you
>>> need to
>>> differentiate between unknown and maximum (or minimum for modes 2 and
>>> 3 that
>>> nobody actually uses in real-world), the builtin after all returns the
>>> same
>>> value for both.  If you want to know if the compiler knows the size
>>> precisely, you can request both mode 0 (or 1) and 2 (or 3) and
>>> compare, if
>>> the values are the same, it is the exact size, if there is a range,
>>> then you
>>> have minimum and maximum (and, if minimum is 0, but maximum non-zero,
>>> you
>>> really don't know minimum, if maximum is -1, then you really don't
>>> know the
>>> maximum (no object should be better that big).  For the return value, I
>>> don't see how you could reliably differentiate between the two even
>>> if it
>>> made for whatever strange reason sense - for SSA_NAMEs etc. you have
>>> just
>>> recorded the sizes, not also a flag whether it is unknown or known.
>>
>> The change makes it possible to fold into constants even at -O0 type
>> 2 and 3 calls to the built-in with zero-sized objects.
> This refers to the tests in builtin-object-size-17.c, particularly when
> compiled at -O0, right?

That's right.

> We generally haven't worried much about trying to optimize -O0 under the
> theory that the resulting code should as closely match the source as
> closely as possible -- including leaving dead/unreachable code in the
> resulting .o.  That dead/unreachable code may in fact be useful during a
> debugging session when the developer manually changes the state into a
> "can't happen" state and expects the previously dead/unreachable code to
> execute.

Sure, I understand that.  It just seemed that if the function folded
non-zero sizes it should also fold the zero.  Otherwise the zero size
would not be available to callers like -Wformat-length (at -O0),
causing them to miss potentially important instances of buffer overflow.

>
> But that's a bit of a stretch in my mind and doesn't really apply in
> this case due to how __b_o_s is expanded.  Ultimately we end up with
> conditionals with compile-time constant arguments, so there's no way for
> the programmer to twiddle things (ok, maybe on a target where the
> compare and branch are separate, the developer could put a break on the
> branch, change the condition codes...  but that's *really* a stretch in
> my mind)
>
> So I don't see any reason to object to this part of the patch --
> essentially it falls under the "don't generate stupid code at -O0" in my
> mind.

Great, thanks.

Martin
Martin Sebor Aug. 25, 2016, 11:56 p.m. UTC | #4
On 08/25/2016 01:18 PM, Jeff Law wrote:
> On 08/21/2016 02:29 PM, Martin Sebor wrote:
>>
>>
>>
>> gcc-71831.w.diff
>>
>>
>> PR tree-optimization/71831 - __builtin_object_size poor results with no
>>     optimization
>>
>> gcc/testsuite/ChangeLog:
>> 2016-08-21  Martin Sebor  <msebor@redhat.com>
>>
>>     PR tree-optimization/71831
>>     * gcc.dg/builtin-object-size-16.c: New test.
>>     * gcc.dg/builtin-object-size-17.c: New test.
>>
>> gcc/ChangeLog:
>> 2016-08-21  Martin Sebor  <msebor@redhat.com>
>>
>>     PR tree-optimization/71831
>>     * tree-object-size.h: Return bool instead of the size and add
>>     argument for the size.
>>     * tree-object-size.c (compute_object_offset): Update signature.
>>     (addr_object_size): Same.
>>     (compute_builtin_object_size): Return bool instead of the size
>>     and add argument for the size.  Handle POINTER_PLUS_EXPR when
>>     optimization is disabled.
>>     (expr_object_size): Adjust.
>>     (plus_stmt_object_size): Adjust.
>>     (pass_object_sizes::execute): Adjust.
>>     * builtins.c (fold_builtin_object_size): Adjust.
>>     * doc/extend.texi (Object Size Checking): Update.
>>     * ubsan.c (instrument_object_size): Adjust.
> So just to be sure, this change really only helps with our ability to
> discover object sizes with -O0.  Essentially when the objsz pass isn't
> run.   That's my reading and I just want to make sure I read it correctly.

That's correct.  It helps the -Wformat-length warning detect more
cases when optimization is not enabled.

>
> Assuming that is the intent here, this is OK for the trunk.

Thanks.

Martin
diff mbox

Patch

PR tree-optimization/71831 - __builtin_object_size poor results with no
	optimization

gcc/testsuite/ChangeLog:
2016-08-21  Martin Sebor  <msebor@redhat.com>

	PR tree-optimization/71831
	* gcc.dg/builtin-object-size-16.c: New test.
	* gcc.dg/builtin-object-size-17.c: New test.

gcc/ChangeLog:
2016-08-21  Martin Sebor  <msebor@redhat.com>

	PR tree-optimization/71831
	* tree-object-size.h: Return bool instead of the size and add
	argument for the size.
	* tree-object-size.c (compute_object_offset): Update signature.
	(addr_object_size): Same.
	(compute_builtin_object_size): Return bool instead of the size
	and add argument for the size.  Handle POINTER_PLUS_EXPR when
	optimization is disabled.
	(expr_object_size): Adjust.
	(plus_stmt_object_size): Adjust.
	(pass_object_sizes::execute): Adjust.
	* builtins.c (fold_builtin_object_size): Adjust.
	* doc/extend.texi (Object Size Checking): Update.
	* ubsan.c (instrument_object_size): Adjust.

diff --git a/gcc/builtins.c b/gcc/builtins.c
index 03a0dc8..5d0c1af 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -9610,7 +9610,7 @@  fold_builtin_object_size (tree ptr, tree ost)
 
   if (TREE_CODE (ptr) == ADDR_EXPR)
     {
-      bytes = compute_builtin_object_size (ptr, object_size_type);
+      compute_builtin_object_size (ptr, object_size_type, &bytes);
       if (wi::fits_to_tree_p (bytes, size_type_node))
 	return build_int_cstu (size_type_node, bytes);
     }
@@ -9619,8 +9619,7 @@  fold_builtin_object_size (tree ptr, tree ost)
       /* If object size is not known yet, delay folding until
        later.  Maybe subsequent passes will help determining
        it.  */
-      bytes = compute_builtin_object_size (ptr, object_size_type);
-      if (bytes != (unsigned HOST_WIDE_INT) (object_size_type < 2 ? -1 : 0)
+      if (compute_builtin_object_size (ptr, object_size_type, &bytes)
 	  && wi::fits_to_tree_p (bytes, size_type_node))
 	return build_int_cstu (size_type_node, bytes);
     }
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index 5285e00..d9dc137 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -10009,8 +10009,15 @@  __atomic_store_n(&lockvar, 0, __ATOMIC_RELEASE|__ATOMIC_HLE_RELEASE);
 @findex __builtin___fprintf_chk
 @findex __builtin___vfprintf_chk
 
-GCC implements a limited buffer overflow protection mechanism
-that can prevent some buffer overflow attacks.
+GCC implements a limited buffer overflow protection mechanism that can
+prevent some buffer overflow attacks by determining the sizes of objects
+into which data is about to be written and preventing the writes when
+the size isn't sufficient.  The built-in functions described below yield
+the best results when used together and when optimization is enabled.
+For example, to detect object sizes across function boundaries or to
+follow pointer assignments through non-trivial control flow they rely
+on various optimization passes enabled with @option{-O2}.  However, to
+a limited extent, they can be used without optimization as well.
 
 @deftypefn {Built-in Function} {size_t} __builtin_object_size (void * @var{ptr}, int @var{type})
 is a built-in construct that returns a constant number of bytes from
diff --git a/gcc/testsuite/gcc.dg/builtin-object-size-16.c b/gcc/testsuite/gcc.dg/builtin-object-size-16.c
new file mode 100644
index 0000000..15721e5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/builtin-object-size-16.c
@@ -0,0 +1,201 @@ 
+/* PR 71831 - __builtin_object_size poor results with no optimization
+   Verify that even without optimization __builtin_object_size returns
+   a meaningful result for a subset of simple expressins.  In cases
+   where the result could not easily be made to match the one obtained
+   with optimization the built-in was made to fail instead.  */
+/* { dg-do run } */
+/* { dg-options "-O0" } */
+
+static int nfails;
+
+#define TEST_FAILURE(line, obj, type, expect, result)		\
+  __builtin_printf ("FAIL: line %i: __builtin_object_size("	\
+		    #obj ", %i) == %zu, got %zu\n",		\
+		    line, type, expect, result), ++nfails
+
+#define bos(obj, type) __builtin_object_size (obj, type)
+#define size(obj, n) ((size_t)n == X ? sizeof *obj : (size_t)n)
+
+#define test(expect, type, obj)						\
+  do {									\
+    if (bos (obj, type)	!= size (obj, expect))				\
+      TEST_FAILURE (__LINE__, obj, type, size (obj, expect), bos (obj, type)); \
+  } while (0)
+
+#define T(r0, r1, r2, r3, obj)			\
+  do {						\
+    test (r0, 0, obj);				\
+    test (r1, 1, obj);				\
+    test (r2, 2, obj);				\
+    test (r3, 3, obj);				\
+  } while (0)
+
+/* For convenience.  Substitute for 'sizeof object' in test cases where
+   the size can vary from target to target.  */
+#define X  (size_t)0xdeadbeef
+
+/* __builtin_object_size checking results are inconsistent for equivalent
+   expressions (see bug 71831).  To avoid having duplicate the inconsistency
+   at -O0 the built-in simply fails.  The results hardcoded in this test
+   are those obtained with optimization (for easy comparison) but without
+   optimization the macros below turn them into expected failures .  */
+#if __OPTIMIZE__
+#  define F0(n)    n
+#  define F1(n)    n
+#  define F2(n)    n
+#  define F3(n)    n
+#else
+#  define F0(n)   -1
+#  define F1(n)   -1
+#  define F2(n)    0
+#  define F3(n)    0
+#endif
+
+typedef __SIZE_TYPE__ size_t;
+
+extern char ax[];
+char ax2[];               /* { dg-warning "assumed to have one element" } */
+
+extern char a0[0];
+static char a1[1];
+static char a2[2];
+static char a9[9];
+
+#if __SIZEOF_SHORT__ == 4
+extern short ia0[0];
+static short ia1[1];
+static short ia9[9];
+#elif __SIZEOF_INT__ == 4
+extern int ia0[0];
+static int ia1[1];
+static int ia9[9];
+#endif
+
+static char a2x2[2][2];
+static char a3x5[3][5];
+
+struct Sx { char n, a[]; } sx;
+struct S0 { char n, a[0]; } s0;
+struct S1 { char n, a[1]; } s1;
+struct S2 { char n, a[2]; } s2;
+struct S9 { char n, a[9]; } s9;
+
+struct S2x2 { char n, a[2][2]; } s2x2;
+struct S3x5 { char n, a[3][5]; } s3x5;
+
+static __attribute__ ((noclone, noinline)) void
+test_arrays ()
+{
+  T (    -1,      -1,       0,       0,   ax);
+
+  T (     0,       0,       0,       0,   a0);
+  T (     1,       1,       1,       1,   ax2);
+
+  T (     1,       1,       1,       1,   a1);
+  T (     2,       2,       2,       2,   a2);
+  T (     9,       9,       9,       9,   a9);
+
+  T (     0,       0,       0,       0,   a0);
+  T (     1,       1,       1,       1,   ax2);
+
+  T (     0,       0,       0,       0,   ia0);
+  T (     4,       4,       4,       4,   ia1);
+  T (    36,      36,      36,      36,   ia9);
+
+  /* Not all results for multidimensional arrays make sense (see
+     bug 77293).  The expected results below simply reflect those
+     obtained at -O2 (modulo the known limitations at -O1).  */
+  T (     4,       4,       4,       4,   a2x2);
+  T (     4,       4,       4,       4,   &a2x2[0]);
+  T (     4,       2,       4,       2,   &a2x2[0][0]);
+  T (     0,  F1  (0),      0,       0,   &a2x2 + 1);
+  T (     2,  F1 ( 2),      2,  F3 ( 2),  &a2x2[0] + 1);
+  T (     3,  F1 ( 1),      3,  F3 ( 3),  &a2x2[0][0] + 1);
+
+  T (    15,      15,      15,      15,   a3x5);
+  T (    15,       5,      15,       5,   &a3x5[0][0] + 0);
+  T (    14,  F1 ( 4),     14,  F3 (14),  &a3x5[0][0] + 1);
+
+  T (     1,       1,       1,       1,   a1 + 0);
+  T (     0,  F1  (0),      0,       0,   a1 + 1);
+  T (     0,  F1 ( 0),      0,       0,   &a1 + 1);
+  /* In the following the offset is out of bounds which makes
+     the expression undefined.  Still, verify that the returned
+     size is zero (and not some large number).  */
+  T (     0,  F1  (0),      0,       0,   a1 + 2);
+
+  T (     2,       2,       2,       2,   a2 + 0);
+  T (     1,  F1 ( 1),      1, F3 ( 1),   a2 + 1);
+  T (     0,  F1 ( 0),      0,       0,   a2 + 2);
+}
+
+static __attribute__ ((noclone, noinline)) void
+test_structs (struct Sx *psx, struct S0 *ps0, struct S1 *ps1, struct S9 *ps9)
+{
+  /* The expected size of a declared object with a flexible array member
+     is sizeof sx in all __builtin_object_size types.  */
+  T (     X,       X,       X,       X,   &sx);
+
+  /* The expected size of an unknown object with a flexible array member
+     is unknown in all __builtin_object_size types.  */
+  T (    -1,      -1,       0,       0,   psx);
+
+  /* The expected size of a flexible array member of a declared object
+     is zero.  */
+  T (     0,       0,       0,       0,   sx.a);
+
+  /* The expected size of a flexible array member of an unknown object
+     is unknown.  */
+  T (    -1,      -1,       0,       0,   psx->a);
+
+  /* The expected size of a declared object with a zero-length array member
+     is sizeof sx in all __builtin_object_size types.  */
+  T (     X,       X,       X,       X,   &s0);
+
+  /* The expected size of an unknown object with a zero-length array member
+     is unknown in all __builtin_object_size types.  */
+  T (    -1,      -1,       0,       0,   ps0);
+
+  /* The expected size of a zero-length array member of a declared object
+     is zero.  */
+  T (     0,       0,       0,       0,   s0.a);
+
+  /* The expected size of a zero-length array member of an unknown object
+     is unknown.  */
+  T (    -1,      -1,       0,       0,   ps0->a);
+
+  T (     X,       X,       X,       X,   &s1);
+  T (     1,       1,       1,       1,   s1.a);
+  T (     0,  F1 (0),       0,       0,   s1.a + 1);
+
+  /* GCC treats arrays of all sizes that are the last member of a struct
+     as flexible array members.  */
+  T (    -1,      -1,       0,       0,   ps1);
+  T (    -1,      -1,       0,       0,   ps1->a);
+  T (    -1,      -1,       0,       0,   ps1->a + 1);
+
+  T (     X,       X,       X,       X,   &s9);
+  T (     9,       9,       9,       9,   s9.a);
+  T (     9,       9,       9,       9,   s9.a + 0);
+  T (     8,  F1 ( 8),      8, F3 (  8),  s9.a + 1);
+  T (     7,  F1 ( 7),      7, F3 (  7),  s9.a + 2);
+  T (     0,  F1 ( 0),      0, F3 (  0),  s9.a + 9);
+
+  /* The following make little sense but see bug 77301.  */
+  T (    -1,      -1,       0,       0,   ps9);
+  T (    -1,      -1,       0,       0,   ps9->a);
+  T (    -1,      -1,       0,       0,   ps9->a + 1);
+}
+
+int
+main()
+{
+  test_arrays ();
+
+  test_structs (&sx, &s0, &s1, &s9);
+
+  if (nfails)
+    __builtin_abort ();
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/builtin-object-size-17.c b/gcc/testsuite/gcc.dg/builtin-object-size-17.c
new file mode 100644
index 0000000..03664d5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/builtin-object-size-17.c
@@ -0,0 +1,158 @@ 
+/* PR 71831 - __builtin_object_size poor results with no optimization
+   Verify that even without optimization __builtin_object_size result
+   is folded into a constant and dead code that depends on it is
+   eliminated.  */
+/* { dg-do compile } */
+/* { dg-options "-O0 -fdump-tree-ssa" } */
+
+#define concat(a, b)   a ## b
+#define CAT(a, b)      concat (a, b)
+
+/* Create a symbol name unique to each tes and object size type.  */
+#define SYM(type)      CAT (CAT (CAT (failure_on_line_, __LINE__), _type_), type)
+
+/* References to the following undefined symbol which is unique for each
+   test case are expected to be eliminated.  */
+#define TEST_FAILURE(type)			\
+  do {						\
+    extern void SYM (type)(void);		\
+    SYM (type)();				\
+  } while (0)
+
+#define bos(obj, type) __builtin_object_size (obj, type)
+#define size(obj, n) ((size_t)n == X ? sizeof *obj : (size_t)n)
+
+#define test(expect, type, obj)			\
+  do {						\
+    if (bos (obj, type)	!= size (obj, expect))	\
+      TEST_FAILURE (type);			\
+  } while (0)
+
+#define FOLD_ALL(r0, r1, r2, r3, obj)		\
+  do {						\
+    test (r0, 0, obj);				\
+    test (r1, 1, obj);				\
+    test (r2, 2, obj);				\
+    test (r3, 3, obj);				\
+  } while (0)
+
+#define FOLD_0_2(r0, r1, r2, r3, obj)		\
+  do {						\
+    test (r0, 0, obj);				\
+    test (r2, 2, obj);				\
+  } while (0)
+
+/* For convenience.  Substitute for 'sizeof object' in test cases where
+   the size can vary from target to target.  */
+#define X  (size_t)0xdeadbeef
+
+typedef __SIZE_TYPE__ size_t;
+
+extern char ax[];
+char ax2[];               /* { dg-warning "assumed to have one element" } */
+
+extern char a0[0];
+static char a1[1];
+static char a2[2];
+static char a9[9];
+
+#if __SIZEOF_SHORT__ == 4
+extern short ia0[0];
+static short ia1[1];
+static short ia9[9];
+#elif __SIZEOF_INT__ == 4
+extern int ia0[0];
+static int ia1[1];
+static int ia9[9];
+#endif
+
+static char a2x2[2][2];
+static char a3x5[3][5];
+
+struct Sx { char n, a[]; } sx;
+struct S0 { char n, a[0]; } s0;
+struct S1 { char n, a[1]; } s1;
+struct S2 { char n, a[2]; } s2;
+struct S9 { char n, a[9]; } s9;
+
+struct S2x2 { char n, a[2][2]; } s2x2;
+struct S3x5 { char n, a[3][5]; } s3x5;
+
+static __attribute__ ((noclone, noinline)) void
+test_arrays ()
+{
+  FOLD_ALL (     1,       1,       1,       1,   ax2);
+
+  FOLD_ALL (     1,       1,       1,       1,   a1);
+  FOLD_ALL (     2,       2,       2,       2,   a2);
+  FOLD_ALL (     9,       9,       9,       9,   a9);
+
+  FOLD_ALL (     0,       0,       0,       0,   a0);
+  FOLD_ALL (     1,       1,       1,       1,   ax2);
+
+  FOLD_ALL (     0,       0,       0,       0,   ia0);
+  FOLD_ALL (     4,       4,       4,       4,   ia1);
+  FOLD_ALL (    36,      36,      36,      36,   ia9);
+
+  /* Not all results for multidimensional arrays make sense (see
+     bug 77293).  The expected results below simply reflect those
+     obtained at -O2 (modulo the known limitations at -O1).  */
+  FOLD_ALL (     4,       4,       4,       4,   a2x2);
+  FOLD_ALL (     4,       4,       4,       4,   &a2x2[0]);
+  FOLD_ALL (     4,       2,       4,       2,   &a2x2[0][0]);
+  FOLD_0_2 (     0,  F1  (0),      0,       0,   &a2x2 + 1);
+  FOLD_0_2 (     2,  F1 ( 2),      2,  F3 ( 2),  &a2x2[0] + 1);
+  FOLD_0_2 (     3,  F1 ( 1),      3,  F3 ( 3),  &a2x2[0][0] + 1);
+
+  FOLD_ALL (    15,      15,      15,      15,   a3x5);
+  FOLD_ALL (    15,       5,      15,       5,   &a3x5[0][0] + 0);
+  FOLD_0_2 (    14,  F1 ( 4),     14,  F3 (14),  &a3x5[0][0] + 1);
+
+  FOLD_ALL (     1,       1,       1,       1,   a1 + 0);
+  FOLD_0_2 (     0,  F1 ( 0),      0,       0,   &a1 + 1);
+  FOLD_ALL (     2,       2,       2,       2,   a2 + 0);
+  FOLD_0_2 (     1,  F1 ( 1),      1, F3 ( 1),   a2 + 1);
+  FOLD_0_2 (     0,  F1 ( 0),      0,       0,   a2 + 2);
+}
+
+static __attribute__ ((noclone, noinline)) void
+test_structs (void)
+{
+  /* The expected size of a declared object with a flexible array member
+     is sizeof sx in all __builtin_object_size types.  */
+  FOLD_ALL (     X,       X,       X,       X,   &sx);
+
+  /* The expected size of a flexible array member of a declared object
+     is zero.  */
+  FOLD_ALL (     0,       0,       0,       0,   sx.a);
+
+  /* The expected size of a declared object with a zero-length array member
+     is sizeof sx in all __builtin_object_size types.  */
+  FOLD_ALL (     X,       X,       X,       X,   &s0);
+
+  /* The expected size of a zero-length array member of a declared object
+     is zero.  */
+  FOLD_ALL (     0,       0,       0,       0,   s0.a);
+
+  FOLD_ALL (     X,       X,       X,       X,   &s1);
+  FOLD_ALL (     1,       1,       1,       1,   s1.a);
+  FOLD_0_2 (     0,  F1 (0),       0,       0,   s1.a + 1);
+
+  FOLD_ALL (     X,       X,       X,       X,   &s9);
+  FOLD_ALL (     9,       9,       9,       9,   s9.a);
+  FOLD_ALL (     9,       9,       9,       9,   s9.a + 0);
+  FOLD_0_2 (     8,  F1 ( 8),      8, F3 (  8),  s9.a + 1);
+  FOLD_0_2 (     7,  F1 ( 7),      7, F3 (  7),  s9.a + 2);
+  FOLD_0_2 (     0,  F1 ( 0),      0, F3 (  0),  s9.a + 9);
+}
+
+int
+main()
+{
+  test_arrays ();
+  test_structs ();
+
+  return 0;
+}
+
+/* { dg-final { scan-tree-dump-not "failure_on_line" "ssa" } } */
diff --git a/gcc/tree-object-size.c b/gcc/tree-object-size.c
index c088451..1317ad7 100644
--- a/gcc/tree-object-size.c
+++ b/gcc/tree-object-size.c
@@ -51,8 +51,8 @@  static const unsigned HOST_WIDE_INT unknown[4] = {
 };
 
 static tree compute_object_offset (const_tree, const_tree);
-static unsigned HOST_WIDE_INT addr_object_size (struct object_size_info *,
-						const_tree, int);
+static bool addr_object_size (struct object_size_info *,
+			      const_tree, int, unsigned HOST_WIDE_INT *);
 static unsigned HOST_WIDE_INT alloc_object_size (const gcall *, int);
 static tree pass_through_call (const gcall *);
 static void collect_object_sizes_for (struct object_size_info *, tree);
@@ -163,14 +163,18 @@  compute_object_offset (const_tree expr, const_tree var)
    OBJECT_SIZE_TYPE is the second argument from __builtin_object_size.
    If unknown, return unknown[object_size_type].  */
 
-static unsigned HOST_WIDE_INT
+static bool
 addr_object_size (struct object_size_info *osi, const_tree ptr,
-		  int object_size_type)
+		  int object_size_type, unsigned HOST_WIDE_INT *psize)
 {
   tree pt_var, pt_var_size = NULL_TREE, var_size, bytes;
 
   gcc_assert (TREE_CODE (ptr) == ADDR_EXPR);
 
+  /* Set to unknown and overwrite just before returning if the size
+     could be determined.  */
+  *psize = unknown[object_size_type];
+
   pt_var = TREE_OPERAND (ptr, 0);
   while (handled_component_p (pt_var))
     pt_var = TREE_OPERAND (pt_var, 0);
@@ -183,8 +187,8 @@  addr_object_size (struct object_size_info *osi, const_tree ptr,
       if (!osi || (object_size_type & 1) != 0
 	  || TREE_CODE (TREE_OPERAND (pt_var, 0)) != SSA_NAME)
 	{
-	  sz = compute_builtin_object_size (TREE_OPERAND (pt_var, 0),
-					    object_size_type & ~1);
+	  compute_builtin_object_size (TREE_OPERAND (pt_var, 0),
+				       object_size_type & ~1, &sz);
 	}
       else
 	{
@@ -224,7 +228,7 @@  addr_object_size (struct object_size_info *osi, const_tree ptr,
 	      < offset_limit)
     pt_var_size = TYPE_SIZE_UNIT (TREE_TYPE (pt_var));
   else
-    return unknown[object_size_type];
+    return false;
 
   if (pt_var != TREE_OPERAND (ptr, 0))
     {
@@ -339,7 +343,7 @@  addr_object_size (struct object_size_info *osi, const_tree ptr,
       if (var != pt_var)
 	var_size = TYPE_SIZE_UNIT (TREE_TYPE (var));
       else if (!pt_var_size)
-	return unknown[object_size_type];
+	return false;
       else
 	var_size = pt_var_size;
       bytes = compute_object_offset (TREE_OPERAND (ptr, 0), var);
@@ -369,14 +373,17 @@  addr_object_size (struct object_size_info *osi, const_tree ptr,
 	}
     }
   else if (!pt_var_size)
-    return unknown[object_size_type];
+    return false;
   else
     bytes = pt_var_size;
 
   if (tree_fits_uhwi_p (bytes))
-    return tree_to_uhwi (bytes);
+    {
+      *psize = tree_to_uhwi (bytes);
+      return true;
+    }
 
-  return unknown[object_size_type];
+  return false;
 }
 
 
@@ -484,24 +491,61 @@  pass_through_call (const gcall *call)
 }
 
 
-/* Compute __builtin_object_size value for PTR.  OBJECT_SIZE_TYPE is the
-   second argument from __builtin_object_size.  */
+/* Compute __builtin_object_size value for PTR and set *PSIZE to
+   the resulting value.  OBJECT_SIZE_TYPE is the second argument
+   to __builtin_object_size.  Return true on success and false
+   when the object size could not be determined.  */
 
-unsigned HOST_WIDE_INT
-compute_builtin_object_size (tree ptr, int object_size_type)
+bool
+compute_builtin_object_size (tree ptr, int object_size_type,
+			     unsigned HOST_WIDE_INT *psize)
 {
   gcc_assert (object_size_type >= 0 && object_size_type <= 3);
 
+  /* Set to unknown and overwrite just before returning if the size
+     could be determined.  */
+  *psize = unknown[object_size_type];
+
   if (! offset_limit)
     init_offset_limit ();
 
   if (TREE_CODE (ptr) == ADDR_EXPR)
-    return addr_object_size (NULL, ptr, object_size_type);
+    return addr_object_size (NULL, ptr, object_size_type, psize);
+
+  if (TREE_CODE (ptr) != SSA_NAME
+      || !POINTER_TYPE_P (TREE_TYPE (ptr)))
+      return false;
 
-  if (TREE_CODE (ptr) == SSA_NAME
-      && POINTER_TYPE_P (TREE_TYPE (ptr))
-      && computed[object_size_type] != NULL)
+  if (computed[object_size_type] == NULL)
     {
+      if (optimize || object_size_type & 1)
+	return false;
+
+      /* When not optimizing, rather than failing, make a small effort
+	 to determine the object size without the full benefit of
+	 the (costly) computation below.  */
+      gimple *def = SSA_NAME_DEF_STMT (ptr);
+      if (gimple_code (def) == GIMPLE_ASSIGN)
+	{
+	  tree_code code = gimple_assign_rhs_code (def);
+	  if (code == POINTER_PLUS_EXPR)
+	    {
+	      tree offset = gimple_assign_rhs2 (def);
+	      ptr = gimple_assign_rhs1 (def);
+
+	      if (cst_and_fits_in_hwi (offset)
+		  && compute_builtin_object_size (ptr, object_size_type, psize))
+		{
+		  /* Return zero when the offset is out of bounds.  */
+		  unsigned HOST_WIDE_INT off = tree_to_shwi (offset);
+		  *psize = off < *psize ? *psize - off : 0;
+		  return true;
+		}
+	    }
+	}
+      return false;
+    }
+
   if (!bitmap_bit_p (computed[object_size_type], SSA_NAME_VERSION (ptr)))
     {
       struct object_size_info osi;
@@ -616,10 +660,8 @@  compute_builtin_object_size (tree ptr, int object_size_type)
       BITMAP_FREE (osi.visited);
     }
 
-      return object_sizes[object_size_type][SSA_NAME_VERSION (ptr)];
-    }
-
-  return unknown[object_size_type];
+  *psize = object_sizes[object_size_type][SSA_NAME_VERSION (ptr)];
+  return *psize != unknown[object_size_type];
 }
 
 /* Compute object_sizes for PTR, defined to VALUE, which is not an SSA_NAME.  */
@@ -643,7 +685,7 @@  expr_object_size (struct object_size_info *osi, tree ptr, tree value)
 	      || !POINTER_TYPE_P (TREE_TYPE (value)));
 
   if (TREE_CODE (value) == ADDR_EXPR)
-    bytes = addr_object_size (osi, value, object_size_type);
+    addr_object_size (osi, value, object_size_type, &bytes);
   else
     bytes = unknown[object_size_type];
 
@@ -809,7 +851,7 @@  plus_stmt_object_size (struct object_size_info *osi, tree var, gimple *stmt)
 	  unsigned HOST_WIDE_INT off = tree_to_uhwi (op1);
 
           /* op0 will be ADDR_EXPR here.  */
-	  bytes = addr_object_size (osi, op0, object_size_type);
+	  addr_object_size (osi, op0, object_size_type, &bytes);
 	  if (bytes == unknown[object_size_type])
 	    ;
 	  else if (off > offset_limit)
@@ -1282,10 +1324,9 @@  pass_object_sizes::execute (function *fun)
 		      && lhs)
 		    {
 		      tree type = TREE_TYPE (lhs);
-		      unsigned HOST_WIDE_INT bytes
-			= compute_builtin_object_size (ptr, object_size_type);
-		      if (bytes != (unsigned HOST_WIDE_INT) (object_size_type == 1
-							     ? -1 : 0)
+		      unsigned HOST_WIDE_INT bytes;
+		      if (compute_builtin_object_size (ptr, object_size_type,
+						       &bytes)
 			  && wi::fits_to_tree_p (bytes, type))
 			{
 			  tree tem = make_ssa_name (type);
diff --git a/gcc/tree-object-size.h b/gcc/tree-object-size.h
index 836f6d3..38c3e07 100644
--- a/gcc/tree-object-size.h
+++ b/gcc/tree-object-size.h
@@ -21,6 +21,6 @@  along with GCC; see the file COPYING3.  If not see
 #define GCC_TREE_OBJECT_SIZE_H
 
 extern void init_object_sizes (void);
-extern unsigned HOST_WIDE_INT compute_builtin_object_size (tree, int);
+extern bool compute_builtin_object_size (tree, int, unsigned HOST_WIDE_INT *);
 
 #endif  // GCC_TREE_OBJECT_SIZE_H
diff --git a/gcc/ubsan.c b/gcc/ubsan.c
index a118af2..5cbc98d 100644
--- a/gcc/ubsan.c
+++ b/gcc/ubsan.c
@@ -1826,8 +1826,8 @@  instrument_object_size (gimple_stmt_iterator *gsi, bool is_lhs)
   if (decl_p)
     base_addr = build1 (ADDR_EXPR,
 			build_pointer_type (TREE_TYPE (base)), base);
-  unsigned HOST_WIDE_INT size = compute_builtin_object_size (base_addr, 0);
-  if (size != HOST_WIDE_INT_M1U)
+  unsigned HOST_WIDE_INT size;
+  if (compute_builtin_object_size (base_addr, 0, &size))
     sizet = build_int_cst (sizetype, size);
   else if (optimize)
     {