diff mbox series

handle initialized flexible array members in __builtin_object_size [PR92815]

Message ID 9962840d-d5e0-7cdc-8e46-ade42ae1d18d@gmail.com
State New
Headers show
Series handle initialized flexible array members in __builtin_object_size [PR92815] | expand

Commit Message

Martin Sebor April 22, 2020, 9:36 p.m. UTC
When computing the size of an object with a flexible array member
the object size pass doesn't consider that the initializer of such
an object can result in its size being in excess of the size of
the enclosing type.  As a result, stores into such objects by
string functions causes false positive warnings and can abort
at runtime.

The warnings are an old regression but as more of them make use
of the object size results more of them are affected by the bug.
The abort goes back to when support for _FORTIFY_SOURCE was added.

The same problem has already been independently fixed in GCC 10
for -Warray-bounds which doesn't use the object size checking pass,
but the object size bug still remains.  The attached patch corrects
it as well.

Tested on x86_64-linux.

Martin

Comments

Li, Pan2 via Gcc-patches April 23, 2020, 3:42 p.m. UTC | #1
On Wed, 2020-04-22 at 15:36 -0600, Martin Sebor via Gcc-patches wrote:
> When computing the size of an object with a flexible array member
> the object size pass doesn't consider that the initializer of such
> an object can result in its size being in excess of the size of
> the enclosing type.  As a result, stores into such objects by
> string functions causes false positive warnings and can abort
> at runtime.
> 
> The warnings are an old regression but as more of them make use
> of the object size results more of them are affected by the bug.
> The abort goes back to when support for _FORTIFY_SOURCE was added.
> 
> The same problem has already been independently fixed in GCC 10
> for -Warray-bounds which doesn't use the object size checking pass,
> but the object size bug still remains.  The attached patch corrects
> it as well.
> 
> Tested on x86_64-linux.
Do you need to change guarding condition to use decl_init_size instead of
DECL_SIZE_UNIT as well?

 else if (pt_var
           && DECL_P (pt_var)
           && tree_fits_uhwi_p (DECL_SIZE_UNIT (pt_var))
                                ^^^^^^^^^^^^^^
           && tree_to_uhwi (DECL_SIZE_UNIT (pt_var)) < offset_limit)
                            ^^^^^^^^^^^^^^
    {
      *pdecl = pt_var;
      pt_var_size = DECL_SIZE_UNIT (pt_var);
    }

Jeff
Martin Sebor April 23, 2020, 10:05 p.m. UTC | #2
On 4/23/20 9:42 AM, Jeff Law wrote:
> On Wed, 2020-04-22 at 15:36 -0600, Martin Sebor via Gcc-patches wrote:
>> When computing the size of an object with a flexible array member
>> the object size pass doesn't consider that the initializer of such
>> an object can result in its size being in excess of the size of
>> the enclosing type.  As a result, stores into such objects by
>> string functions causes false positive warnings and can abort
>> at runtime.
>>
>> The warnings are an old regression but as more of them make use
>> of the object size results more of them are affected by the bug.
>> The abort goes back to when support for _FORTIFY_SOURCE was added.
>>
>> The same problem has already been independently fixed in GCC 10
>> for -Warray-bounds which doesn't use the object size checking pass,
>> but the object size bug still remains.  The attached patch corrects
>> it as well.
>>
>> Tested on x86_64-linux.
> Do you need to change guarding condition to use decl_init_size instead of
> DECL_SIZE_UNIT as well?
> 
>   else if (pt_var
>             && DECL_P (pt_var)
>             && tree_fits_uhwi_p (DECL_SIZE_UNIT (pt_var))
>                                  ^^^^^^^^^^^^^^
>             && tree_to_uhwi (DECL_SIZE_UNIT (pt_var)) < offset_limit)
>                              ^^^^^^^^^^^^^^

It doesn't see that changing it is strictly necessary.  If the tests
above pass and the result doesn't satisfy the conditions because it's
either null or doesn't fit in UHWI it's handled later by returning
false.  With offset_limit set to SIZE_MAX / 2, I don't think the result
can as big as that or bigger: it would imply the whole object, including
its initializer, is bigger than half the address space and GCC rejects
such objects with an error.  I've added another test in the patch to
to verify this.

I do agree it would be better to validate the final result the same
way.  That makes the change a little more intrusive to avoid validating
the size multiple times, but I think it also improves the readability
of the code, so the updated patch does that.  It passes testing on
x86_64-linux.

Let me know which one of the two you prefer, or if you'd rather hold
off until stage 1.

Martin

>      {
>        *pdecl = pt_var;
>        pt_var_size = DECL_SIZE_UNIT (pt_var);
>      }
> 
> Jeff
>
Li, Pan2 via Gcc-patches May 1, 2020, 5:01 p.m. UTC | #3
On Thu, 2020-04-23 at 16:05 -0600, Martin Sebor wrote:
> On 4/23/20 9:42 AM, Jeff Law wrote:
> > On Wed, 2020-04-22 at 15:36 -0600, Martin Sebor via Gcc-patches wrote:
> > > When computing the size of an object with a flexible array member
> > > the object size pass doesn't consider that the initializer of such
> > > an object can result in its size being in excess of the size of
> > > the enclosing type.  As a result, stores into such objects by
> > > string functions causes false positive warnings and can abort
> > > at runtime.
> > > 
> > > The warnings are an old regression but as more of them make use
> > > of the object size results more of them are affected by the bug.
> > > The abort goes back to when support for _FORTIFY_SOURCE was added.
> > > 
> > > The same problem has already been independently fixed in GCC 10
> > > for -Warray-bounds which doesn't use the object size checking pass,
> > > but the object size bug still remains.  The attached patch corrects
> > > it as well.
> > > 
> > > Tested on x86_64-linux.
> > Do you need to change guarding condition to use decl_init_size instead of
> > DECL_SIZE_UNIT as well?
> > 
> >   else if (pt_var
> >             && DECL_P (pt_var)
> >             && tree_fits_uhwi_p (DECL_SIZE_UNIT (pt_var))
> >                                  ^^^^^^^^^^^^^^
> >             && tree_to_uhwi (DECL_SIZE_UNIT (pt_var)) < offset_limit)
> >                              ^^^^^^^^^^^^^^
> 
> It doesn't see that changing it is strictly necessary.  If the tests
> above pass and the result doesn't satisfy the conditions because it's
> either null or doesn't fit in UHWI it's handled later by returning
> false.  With offset_limit set to SIZE_MAX / 2, I don't think the result
> can as big as that or bigger: it would imply the whole object, including
> its initializer, is bigger than half the address space and GCC rejects
> such objects with an error.  I've added another test in the patch to
> to verify this.
> 
> I do agree it would be better to validate the final result the same
> way.  That makes the change a little more intrusive to avoid validating
> the size multiple times, but I think it also improves the readability
> of the code, so the updated patch does that.  It passes testing on
> x86_64-linux.
> 
> Let me know which one of the two you prefer, or if you'd rather hold
> off until stage 1.
I think at this point we should defer.  
jeff
>
Li, Pan2 via Gcc-patches May 12, 2020, 4:24 p.m. UTC | #4
On Thu, 2020-04-23 at 16:05 -0600, Martin Sebor wrote:
> On 4/23/20 9:42 AM, Jeff Law wrote:
> > On Wed, 2020-04-22 at 15:36 -0600, Martin Sebor via Gcc-patches wrote:
> > > When computing the size of an object with a flexible array member
> > > the object size pass doesn't consider that the initializer of such
> > > an object can result in its size being in excess of the size of
> > > the enclosing type.  As a result, stores into such objects by
> > > string functions causes false positive warnings and can abort
> > > at runtime.
> > > 
> > > The warnings are an old regression but as more of them make use
> > > of the object size results more of them are affected by the bug.
> > > The abort goes back to when support for _FORTIFY_SOURCE was added.
> > > 
> > > The same problem has already been independently fixed in GCC 10
> > > for -Warray-bounds which doesn't use the object size checking pass,
> > > but the object size bug still remains.  The attached patch corrects
> > > it as well.
> > > 
> > > Tested on x86_64-linux.
> > Do you need to change guarding condition to use decl_init_size instead of
> > DECL_SIZE_UNIT as well?
> > 
> >   else if (pt_var
> >             && DECL_P (pt_var)
> >             && tree_fits_uhwi_p (DECL_SIZE_UNIT (pt_var))
> >                                  ^^^^^^^^^^^^^^
> >             && tree_to_uhwi (DECL_SIZE_UNIT (pt_var)) < offset_limit)
> >                              ^^^^^^^^^^^^^^
> 
> It doesn't see that changing it is strictly necessary.  If the tests
> above pass and the result doesn't satisfy the conditions because it's
> either null or doesn't fit in UHWI it's handled later by returning
> false.  With offset_limit set to SIZE_MAX / 2, I don't think the result
> can as big as that or bigger: it would imply the whole object, including
> its initializer, is bigger than half the address space and GCC rejects
> such objects with an error.  I've added another test in the patch to
> to verify this.
> 
> I do agree it would be better to validate the final result the same
> way.  That makes the change a little more intrusive to avoid validating
> the size multiple times, but I think it also improves the readability
> of the code, so the updated patch does that.  It passes testing on
> x86_64-linux.
> 
> Let me know which one of the two you prefer, or if you'd rather hold
> off until stage 1.
So we're in stage1, so you're good to go with the patch as-is or revamped per the
discussion above.

jeff
>
diff mbox series

Patch

PR middle-end/92815 - spurious -Wstringop-overflow writing into a flexible array of an extern struct

gcc/ChangeLog:

	PR middle-end/92815
	* tree-object-size.c (decl_init_size): New function.
	(addr_object_size): Call it.
	* tree.h (last_field): Declare.
	(first_field): Add attribute nonnull.

gcc/testsuite/ChangeLog:

	PR middle-end/92815
	* gcc.dg/Warray-bounds-56.c: Remove xfails.
	* gcc.dg/builtin-object-size-20.c: New test.

diff --git a/gcc/testsuite/gcc.dg/Warray-bounds-56.c b/gcc/testsuite/gcc.dg/Warray-bounds-56.c
index 399c9b263b3..04c26a659ad 100644
--- a/gcc/testsuite/gcc.dg/Warray-bounds-56.c
+++ b/gcc/testsuite/gcc.dg/Warray-bounds-56.c
@@ -42,8 +42,8 @@  struct Flex f3 = { 3, { 1, 2, 3 } };
 
 NOIPA void test_strcpy_flexarray (void)
 {
-  T (S (0), fx);                // { dg-bogus "\\\[-Warray-bounds" "pr92815" { xfail *-*-*} }
-  T (S (9), fx);                // { dg-bogus "\\\[-Warray-bounds" "pr92815" { xfail *-*-*} }
+  T (S (0), fx);                // { dg-bogus "\\\[-Warray-bounds" "pr92815" }
+  T (S (9), fx);                // { dg-bogus "\\\[-Warray-bounds" "pr92815" }
 
   T (S (0), f1);
   T (S (1), f1);                // { dg-warning "\\\[-Warray-bounds" }
diff --git a/gcc/testsuite/gcc.dg/builtin-object-size-20.c b/gcc/testsuite/gcc.dg/builtin-object-size-20.c
new file mode 100644
index 00000000000..47821c06d76
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/builtin-object-size-20.c
@@ -0,0 +1,315 @@ 
+/* PR middle-end/92815 - spurious -Wstringop-overflow writing into
+   a flexible array of an extern struct
+   { dg-do compile }
+   { dg-options "-Wall -fdump-tree-optimized" } */
+
+#define ASSERT(expr) ((expr) ? (void)0 : fail (__LINE__))
+#define bos0(expr) __builtin_object_size (expr, 1)
+#define bos1(expr) __builtin_object_size (expr, 1)
+#define bos2(expr) __builtin_object_size (expr, 2)
+#define bos3(expr) __builtin_object_size (expr, 3)
+
+typedef __INT16_TYPE__ int16_t;
+typedef __INT32_TYPE__ int32_t;
+typedef __INT64_TYPE__ int64_t;
+typedef __SIZE_TYPE__  size_t;
+
+
+extern void fail (int);
+
+
+/* Verify sizes of a struct with a flexible array member and no padding.  */
+
+struct ACX { char n, a[]; };
+
+struct ACX ac0 = { };
+struct ACX ac1 = { 1, { 1 } };
+struct ACX ac2 = { 2, { 1, 2 } };
+struct ACX ac3 = { 3, { 1, 2, 3 } };
+
+extern struct ACX eacx;
+
+void facx (void)
+{
+  ASSERT (bos0 (&ac0) == sizeof ac0);
+  ASSERT (bos0 (&ac1) == 2);
+  ASSERT (bos0 (&ac2) == 3);
+  ASSERT (bos0 (&ac3) == 4);
+  ASSERT (bos0 (&eacx) == (size_t)-1);
+
+  ASSERT (bos1 (&ac0) == sizeof ac0);
+  ASSERT (bos1 (&ac1) == 2);
+  ASSERT (bos1 (&ac2) == 3);
+  ASSERT (bos1 (&ac3) == 4);
+  ASSERT (bos1 (&eacx) == (size_t)-1);
+
+  ASSERT (bos2 (&ac0) == sizeof ac0);
+  ASSERT (bos2 (&ac1) == 2);
+  ASSERT (bos2 (&ac2) == 3);
+  ASSERT (bos2 (&ac3) == 4);
+  ASSERT (bos2 (&eacx) == sizeof eacx);
+
+  ASSERT (bos3 (&ac0) == sizeof ac0);
+  ASSERT (bos3 (&ac1) == 2);
+  ASSERT (bos3 (&ac2) == 3);
+  ASSERT (bos3 (&ac3) == 4);
+  ASSERT (bos3 (&eacx) == sizeof eacx);
+}
+
+
+
+/* Verify sizes of a struct with a flexible array member and 1 byte
+   of tail padding.  */
+
+struct AI16CX { int16_t i; char n, a[]; };
+
+struct AI16CX ai16c0 = { 0 };
+struct AI16CX ai16c1 = { 0, 1, { 1 } };
+struct AI16CX ai16c2 = { 0, 2, { 1, 2 } };
+struct AI16CX ai16c3 = { 0, 3, { 1, 2, 3 } };
+struct AI16CX ai16c4 = { 0, 4, { 1, 2, 3, 4 } };
+struct AI16CX ai16c5 = { 0, 5, { 1, 2, 3, 4, 5 } };
+struct AI16CX ai16c6 = { 0, 6, { 1, 2, 3, 4, 5, 6 } };
+struct AI16CX ai16c7 = { 0, 7, { 1, 2, 3, 4, 5, 6, 7 } };
+struct AI16CX ai16c8 = { 0, 8, { 1, 2, 3, 4, 5, 6, 7, 8 } };
+
+extern struct AI16CX eai16cx;
+
+void fai16cx (void)
+{
+  ASSERT (bos0 (&ai16c0) == sizeof ai16c0);
+  ASSERT (bos0 (&ai16c1) == sizeof ai16c1);
+  ASSERT (bos0 (&ai16c2) == sizeof ai16c2 + 1);
+  ASSERT (bos0 (&ai16c3) == sizeof ai16c3 + 2);
+
+  ASSERT (bos0 (&ai16c4) == sizeof ai16c4 + 3);
+  ASSERT (bos0 (&ai16c5) == sizeof ai16c5 + 4);
+  ASSERT (bos0 (&ai16c6) == sizeof ai16c6 + 5);
+  ASSERT (bos0 (&ai16c7) == sizeof ai16c6 + 6);
+  ASSERT (bos0 (&ai16c8) == sizeof ai16c6 + 7);
+
+  ASSERT (bos0 (&eai16cx) == (size_t)-1);
+
+
+  ASSERT (bos1 (&ai16c0) == sizeof ai16c0);
+  ASSERT (bos1 (&ai16c1) == sizeof ai16c1);
+  ASSERT (bos1 (&ai16c2) == sizeof ai16c2 + 1);
+  ASSERT (bos1 (&ai16c3) == sizeof ai16c3 + 2);
+
+  ASSERT (bos1 (&ai16c4) == sizeof ai16c4 + 3);
+  ASSERT (bos1 (&ai16c5) == sizeof ai16c5 + 4);
+  ASSERT (bos1 (&ai16c6) == sizeof ai16c6 + 5);
+  ASSERT (bos1 (&ai16c7) == sizeof ai16c6 + 6);
+  ASSERT (bos1 (&ai16c8) == sizeof ai16c6 + 7);
+
+  ASSERT (bos1 (&eai16cx) == (size_t)-1);
+
+
+  ASSERT (bos2 (&ai16c0) == sizeof ai16c0);
+  ASSERT (bos2 (&ai16c1) == sizeof ai16c1);
+  ASSERT (bos2 (&ai16c2) == sizeof ai16c2 + 1);
+  ASSERT (bos2 (&ai16c3) == sizeof ai16c3 + 2);
+
+  ASSERT (bos2 (&ai16c4) == sizeof ai16c4 + 3);
+  ASSERT (bos2 (&ai16c5) == sizeof ai16c5 + 4);
+  ASSERT (bos2 (&ai16c6) == sizeof ai16c6 + 5);
+  ASSERT (bos2 (&ai16c7) == sizeof ai16c6 + 6);
+  ASSERT (bos2 (&ai16c8) == sizeof ai16c6 + 7);
+
+  ASSERT (bos2 (&eai16cx) == sizeof eai16cx);
+
+
+  ASSERT (bos3 (&ai16c0) == sizeof ai16c0);
+  ASSERT (bos3 (&ai16c1) == sizeof ai16c1);
+  ASSERT (bos3 (&ai16c2) == sizeof ai16c2 + 1);
+  ASSERT (bos3 (&ai16c3) == sizeof ai16c3 + 2);
+
+  ASSERT (bos3 (&ai16c4) == sizeof ai16c4 + 3);
+  ASSERT (bos3 (&ai16c5) == sizeof ai16c5 + 4);
+  ASSERT (bos3 (&ai16c6) == sizeof ai16c6 + 5);
+  ASSERT (bos3 (&ai16c7) == sizeof ai16c6 + 6);
+  ASSERT (bos3 (&ai16c8) == sizeof ai16c6 + 7);
+
+  ASSERT (bos3 (&eai16cx) == sizeof eai16cx);
+}
+
+
+/* Verify sizes of a struct with a flexible array member and 3 bytes
+   of tail padding.  */
+
+struct AI32CX { int32_t i; char n, a[]; };
+
+struct AI32CX ai32c0 = { 0 };
+struct AI32CX ai32c1 = { 0, 1, { 1 } };
+struct AI32CX ai32c2 = { 0, 2, { 1, 2 } };
+struct AI32CX ai32c3 = { 0, 3, { 1, 2, 3 } };
+struct AI32CX ai32c4 = { 0, 4, { 1, 2, 3, 4 } };
+struct AI32CX ai32c5 = { 0, 5, { 1, 2, 3, 4, 5 } };
+struct AI32CX ai32c6 = { 0, 6, { 1, 2, 3, 4, 5, 6 } };
+struct AI32CX ai32c7 = { 0, 7, { 1, 2, 3, 4, 5, 6, 7 } };
+struct AI32CX ai32c8 = { 0, 8, { 1, 2, 3, 4, 5, 6, 7, 8 } };
+
+extern struct AI32CX eai32cx;
+
+void fai32cx (void)
+{
+  ASSERT (bos0 (&ai32c0) == sizeof ai32c0);
+  ASSERT (bos0 (&ai32c1) == sizeof ai32c1);
+  ASSERT (bos0 (&ai32c2) == sizeof ai32c2);
+  ASSERT (bos0 (&ai32c3) == sizeof ai32c3);
+
+  ASSERT (bos0 (&ai32c4) == sizeof ai32c4 + 1);
+  ASSERT (bos0 (&ai32c5) == sizeof ai32c5 + 2);
+  ASSERT (bos0 (&ai32c6) == sizeof ai32c6 + 3);
+  ASSERT (bos0 (&ai32c7) == sizeof ai32c6 + 4);
+  ASSERT (bos0 (&ai32c8) == sizeof ai32c6 + 5);
+
+  ASSERT (bos0 (&eai32cx) == (size_t)-1);
+
+
+  ASSERT (bos1 (&ai32c0) == sizeof ai32c0);
+  ASSERT (bos1 (&ai32c1) == sizeof ai32c1);
+  ASSERT (bos1 (&ai32c2) == sizeof ai32c2);
+  ASSERT (bos1 (&ai32c3) == sizeof ai32c3);
+
+  ASSERT (bos1 (&ai32c4) == sizeof ai32c4 + 1);
+  ASSERT (bos1 (&ai32c5) == sizeof ai32c5 + 2);
+  ASSERT (bos1 (&ai32c6) == sizeof ai32c6 + 3);
+  ASSERT (bos1 (&ai32c7) == sizeof ai32c6 + 4);
+  ASSERT (bos1 (&ai32c8) == sizeof ai32c6 + 5);
+
+  ASSERT (bos1 (&eai32cx) == (size_t)-1);
+
+
+  ASSERT (bos2 (&ai32c0) == sizeof ai32c0);
+  ASSERT (bos2 (&ai32c1) == sizeof ai32c1);
+  ASSERT (bos2 (&ai32c2) == sizeof ai32c2);
+  ASSERT (bos2 (&ai32c3) == sizeof ai32c3);
+
+  ASSERT (bos2 (&ai32c4) == sizeof ai32c4 + 1);
+  ASSERT (bos2 (&ai32c5) == sizeof ai32c5 + 2);
+  ASSERT (bos2 (&ai32c6) == sizeof ai32c6 + 3);
+  ASSERT (bos2 (&ai32c7) == sizeof ai32c6 + 4);
+  ASSERT (bos2 (&ai32c8) == sizeof ai32c6 + 5);
+
+  ASSERT (bos2 (&eai32cx) == sizeof eai32cx);
+
+
+  ASSERT (bos3 (&ai32c0) == sizeof ai32c0);
+  ASSERT (bos3 (&ai32c1) == sizeof ai32c1);
+  ASSERT (bos3 (&ai32c2) == sizeof ai32c2);
+  ASSERT (bos3 (&ai32c3) == sizeof ai32c3);
+
+  ASSERT (bos3 (&ai32c4) == sizeof ai32c4 + 1);
+  ASSERT (bos3 (&ai32c5) == sizeof ai32c5 + 2);
+  ASSERT (bos3 (&ai32c6) == sizeof ai32c6 + 3);
+  ASSERT (bos3 (&ai32c7) == sizeof ai32c6 + 4);
+  ASSERT (bos3 (&ai32c8) == sizeof ai32c6 + 5);
+
+  ASSERT (bos3 (&eai32cx) == sizeof eai32cx);
+}
+
+
+/* Verify sizes of a struct with a flexible array member and 7 bytes
+   of tail padding.  */
+
+struct AI64CX { int64_t i; char n, a[]; };
+
+struct AI64CX ai64c0 = { 0 };
+struct AI64CX ai64c1 = { 0, 1, { 1 } };
+struct AI64CX ai64c2 = { 0, 2, { 1, 2 } };
+struct AI64CX ai64c3 = { 0, 3, { 1, 2, 3 } };
+struct AI64CX ai64c4 = { 0, 4, { 1, 2, 3, 4 } };
+struct AI64CX ai64c5 = { 0, 5, { 1, 2, 3, 4, 5 } };
+struct AI64CX ai64c6 = { 0, 6, { 1, 2, 3, 4, 5, 6 } };
+struct AI64CX ai64c7 = { 0, 7, { 1, 2, 3, 4, 5, 6, 7 } };
+struct AI64CX ai64c8 = { 0, 8, { 1, 2, 3, 4, 5, 6, 7, 8 } };
+struct AI64CX ai64c9 = { 0, 8, { 1, 2, 3, 4, 5, 6, 7, 8, 9 } };
+
+extern struct AI64CX eai64cx;
+
+void fai64cx (void)
+{
+  ASSERT (bos0 (&ai64c0) == sizeof ai64c0);
+  ASSERT (bos0 (&ai64c1) == sizeof ai64c1);
+  ASSERT (bos0 (&ai64c2) == sizeof ai64c2);
+  ASSERT (bos0 (&ai64c3) == sizeof ai64c3);
+  ASSERT (bos0 (&ai64c4) == sizeof ai64c4);
+  ASSERT (bos0 (&ai64c5) == sizeof ai64c5);
+  ASSERT (bos0 (&ai64c6) == sizeof ai64c6);
+  ASSERT (bos0 (&ai64c7) == sizeof ai64c7);
+
+  ASSERT (bos0 (&ai64c8) == sizeof ai64c8 + 1);
+  ASSERT (bos0 (&ai64c9) == sizeof ai64c9 + 2);
+
+  ASSERT (bos0 (&eai64cx) == (size_t)-1);
+
+
+  ASSERT (bos1 (&ai64c0) == sizeof ai64c0);
+  ASSERT (bos1 (&ai64c1) == sizeof ai64c1);
+  ASSERT (bos1 (&ai64c2) == sizeof ai64c2);
+  ASSERT (bos1 (&ai64c3) == sizeof ai64c3);
+  ASSERT (bos1 (&ai64c4) == sizeof ai64c4);
+  ASSERT (bos1 (&ai64c5) == sizeof ai64c5);
+  ASSERT (bos1 (&ai64c6) == sizeof ai64c6);
+  ASSERT (bos1 (&ai64c7) == sizeof ai64c7);
+
+  ASSERT (bos1 (&ai64c8) == sizeof ai64c8 + 1);
+  ASSERT (bos1 (&ai64c9) == sizeof ai64c9 + 2);
+
+  ASSERT (bos1 (&eai64cx) == (size_t)-1);
+
+
+  ASSERT (bos2 (&ai64c0) == sizeof ai64c0);
+  ASSERT (bos2 (&ai64c1) == sizeof ai64c1);
+  ASSERT (bos2 (&ai64c2) == sizeof ai64c2);
+  ASSERT (bos2 (&ai64c3) == sizeof ai64c3);
+  ASSERT (bos2 (&ai64c4) == sizeof ai64c4);
+  ASSERT (bos2 (&ai64c5) == sizeof ai64c5);
+  ASSERT (bos2 (&ai64c6) == sizeof ai64c6);
+  ASSERT (bos2 (&ai64c7) == sizeof ai64c7);
+
+  ASSERT (bos2 (&ai64c8) == sizeof ai64c8 + 1);
+  ASSERT (bos2 (&ai64c9) == sizeof ai64c9 + 2);
+
+  ASSERT (bos2 (&eai64cx) == sizeof eai64cx);
+
+  ASSERT (bos3 (&ai64c0) == sizeof ai64c0);
+  ASSERT (bos3 (&ai64c1) == sizeof ai64c1);
+  ASSERT (bos3 (&ai64c2) == sizeof ai64c2);
+  ASSERT (bos3 (&ai64c3) == sizeof ai64c3);
+  ASSERT (bos3 (&ai64c4) == sizeof ai64c4);
+  ASSERT (bos3 (&ai64c5) == sizeof ai64c5);
+  ASSERT (bos3 (&ai64c6) == sizeof ai64c6);
+  ASSERT (bos3 (&ai64c7) == sizeof ai64c7);
+
+  ASSERT (bos3 (&ai64c8) == sizeof ai64c8 + 1);
+  ASSERT (bos3 (&ai64c9) == sizeof ai64c9 + 2);
+
+  ASSERT (bos3 (&eai64cx) == sizeof eai64cx);
+}
+
+
+/* Also verify sizes of a struct with a zero length array member.  */
+
+struct A0C0 { char n, a[0]; };
+
+struct A0C0 a0c0 = { };
+extern struct A0C0 ea0c0;
+
+void fa0c0 (void)
+{
+  ASSERT (bos0 (&a0c0) == sizeof a0c0);
+  ASSERT (bos0 (&ea0c0) == sizeof ea0c0);
+
+  ASSERT (bos1 (&a0c0) == sizeof a0c0);
+  ASSERT (bos1 (&a0c0) == sizeof ea0c0);
+
+  ASSERT (bos2 (&a0c0) == sizeof a0c0);
+  ASSERT (bos2 (&a0c0) == sizeof ea0c0);
+
+  ASSERT (bos3 (&a0c0) == sizeof a0c0);
+  ASSERT (bos3 (&a0c0) == sizeof ea0c0);
+}
+
+/* { dg-final { scan-tree-dump-not "fail" "optimized" } } */
diff --git a/gcc/tree-object-size.c b/gcc/tree-object-size.c
index 255ea63cab6..5d4cc485fba 100644
--- a/gcc/tree-object-size.c
+++ b/gcc/tree-object-size.c
@@ -166,6 +166,42 @@  compute_object_offset (const_tree expr, const_tree var)
   return size_binop (code, base, off);
 }
 
+/* Returns the size of the object designated by DECL considering its
+   initializer if it either has one or if it would not affect its size,
+   otherwise the size of the object without the initializer when MIN
+   is true, else null.  An object's initializer affects the object's
+   size if it's a struct type with a flexible array member.  */
+
+static tree
+decl_init_size (tree decl, bool min)
+{
+  tree size = DECL_SIZE_UNIT (decl);
+  tree type = TREE_TYPE (decl);
+  if (TREE_CODE (type) != RECORD_TYPE)
+    return size;
+
+  tree last = last_field (type);
+  if (!last)
+    return size;
+
+  tree last_type = TREE_TYPE (last);
+  if (TREE_CODE (last_type) != ARRAY_TYPE
+      || TYPE_SIZE (last_type))
+    return size;
+
+  /* Use TYPE_SIZE_UNIT; DECL_SIZE_UNIT sometimes reflects the size
+     of the initializer and sometimes doesn't.  */
+  size = TYPE_SIZE_UNIT (type);
+  tree ref = build3 (COMPONENT_REF, type, decl, last, NULL_TREE);
+  tree compsize = component_ref_size (ref);
+  if (!compsize)
+    return min ? size : NULL_TREE;
+
+  /* The size includes tail padding and initializer elements.  */
+  tree pos = byte_position (last);
+  size = fold_build2 (PLUS_EXPR, TREE_TYPE (size), pos, compsize);
+  return size;
+}
 
 /* Compute __builtin_object_size for PTR, which is a ADDR_EXPR.
    OBJECT_SIZE_TYPE is the second argument from __builtin_object_size.
@@ -242,7 +278,7 @@  addr_object_size (struct object_size_info *osi, const_tree ptr,
 	   && tree_to_uhwi (DECL_SIZE_UNIT (pt_var)) < offset_limit)
     {
       *pdecl = pt_var;
-      pt_var_size = DECL_SIZE_UNIT (pt_var);
+      pt_var_size = decl_init_size (pt_var, object_size_type & 2);
     }
   else if (pt_var
 	   && TREE_CODE (pt_var) == STRING_CST
diff --git a/gcc/tree.h b/gcc/tree.h
index 1c28785d411..c91a834564d 100644
--- a/gcc/tree.h
+++ b/gcc/tree.h
@@ -4692,9 +4692,10 @@  extern tree nreverse (tree);
 
 extern int list_length (const_tree);
 
-/* Returns the first FIELD_DECL in a type.  */
+/* Returns the first/last FIELD_DECL in a RECORD_TYPE.  */
 
-extern tree first_field (const_tree);
+extern tree first_field (const_tree) ATTRIBUTE_NONNULL (1);
+extern tree last_field (const_tree) ATTRIBUTE_NONNULL (1);
 
 /* Given an initializer INIT, return TRUE if INIT is zero or some
    aggregate of zeros.  Otherwise return FALSE.  If NONZERO is not