[Bug c/91688] New: -Woverride-init could use an intermediate mode of operation
eblake at redhat dot com
gcc-bugzilla@gcc.gnu.org
Fri Sep 6 16:03:00 GMT 2019
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=91688
Bug ID: 91688
Summary: -Woverride-init could use an intermediate mode of
operation
Product: gcc
Version: 9.2.1
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: c
Assignee: unassigned at gcc dot gnu.org
Reporter: eblake at redhat dot com
Target Milestone: ---
Right now, -Woverride-init is so strong that it interferes with the use of
gcc's extension of ranged-array initializer syntax as a common way to
pre-initialize an array to a specific non-zero default value then override
specific members in the array to a different value. Thus, it is not part of
-Wall (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=24010#c4).
However, this warning is so strong that the qemu project is unable to use
-Woverride-init (it would fail to compile due to intentional reinitialization
overrides of ranged defaults), but thus failed to diagnose a bug (a partial
initialization caused zero-initialization of an unmentioned member, overriding
an earlier explicit mention of that member:
https://lists.gnu.org/archive/html/qemu-devel/2019-09/msg01143.html), at least
without using pragmas to temporarily silence the warning.
I am wondering if it would be possible to split this warning into two
categories: -Woverride-init=1 (preferably hoisted into -Wall) which warns about
obvious inadvertent overrides, and -Woverride-init=2 (only part of -Wextra, if
at all) that warns about all overrides, where the difference depends on whether
the second initializer occurs on a subobject that is larger in size than the
first initializer's subobject.
For some demonstrations of what I mean (and intentionally using
-Wno-missing-braces):
$ cat foo.c
#include <stdio.h>
struct A {
int i;
int j;
};
struct B {
struct A one;
struct A two;
struct A three;
int four[2];
};
void dump (struct B *b)
{
printf ("one=[%d,%d],two=[%d,%d],three=[%d,%d],four=[%d,%d]\n",
b->one.i, b->one.j, b->two.i, b->two.j, b->three.i, b->three.j,
b->four[0], b->four[1]);
}
int main(void)
{
struct B b1 = {
.one.i = 1,
.two.j = 2,
3,
.four[1] = 9,
.one = { 5, 6 },
.two = { 7 },
.three = { .j = 8 },
.four[0 ... 1] = 4,
};
struct B b2 = {
.one = { 4, 5 },
.two = { 6 },
.three = { .j = 7 },
.four[0 ... 1] = 4,
.one.i = 1,
.two.j = 2,
3,
.four[1] = 9,
};
dump (&b1);
dump (&b2);
return 0;
}
$ gcc -Wall -Wextra -Wno-missing-braces -o foo foo.c
foo.c: In function ‘main’:
foo.c:28:11: warning: initialized field overwritten [-Woverride-init]
28 | .one = { 5, 6 },
| ^
foo.c:28:11: note: (near initialization for ‘b1.one’)
foo.c:29:11: warning: initialized field overwritten [-Woverride-init]
29 | .two = { 7 },
| ^
foo.c:29:11: note: (near initialization for ‘b1.two’)
foo.c:30:13: warning: initialized field overwritten [-Woverride-init]
30 | .three = { .j = 8 },
| ^
foo.c:30:13: note: (near initialization for ‘b1.three’)
foo.c:31:21: warning: initialized field overwritten [-Woverride-init]
31 | .four[0 ... 1] = 4,
| ^
foo.c:31:21: note: (near initialization for ‘b1.four[1]’)
foo.c:38:13: warning: initialized field overwritten [-Woverride-init]
38 | .one.i = 1,
| ^
foo.c:38:13: note: (near initialization for ‘b2.one.i’)
foo.c:41:15: warning: initialized field overwritten [-Woverride-init]
41 | .four[1] = 9,
| ^
foo.c:41:15: note: (near initialization for ‘b2.four[1]’)
The four warnings for b1 all make sense at -Woverride-init=1: they are clearly
inadvertent, in that the second half of initializers all touch a larger subset
of the overall b2 object than the first half (providing both members for .one
overrides the earlier specific setting of .one.i; providing a partial
initializer for .two implies a zero-initialization override of the earlier
.two.j; providing a partial initializer for .three implies a
zero-initialization override of the earlier (non-designated) .three.i, and
doing a ranged initializer over .four[0 ... 1] overrides the earlier specific
initialization of .four[1].)
However, while the first warning of b2 is reasonable (the explicit
initialization of .one.i overrides the earlier explicit initialization of
.one), this is a case of a more-specific override (.one.i is a smaller subset
of b2 than .one), and the second one is the show-stopper that prevents the use
of -Woverride-init in -Wall (it is intentional that a ranged initializer sets a
non-zero default, and then later initializers of obvious smaller scope change
that default). So the warnings emitted here would fit better in
-Woverride-init=2.
Perhaps we could even go one step further and have -Woverride-init=3 to catch
the case of the double-initialization of b2.two.j (first to its implicit
zero-initialization due to the partial initializer for .two, then again via the
explicit designator .two.j) and b2.three.i (again, due to implicit
zero-initializion from the partial initializer for .three, before the explicit
initialization from the (non-designated) .three.i)- but it is GOOD that gcc
does not warn about these cases under the existing -Woverride-init.
At any rate, with this enhancement, qemu could use -Woverride-init=1 (perhaps
even automatically as part of -Wall), without worrying about having to use
pragmas around the cases where it does intentional overrides of a non-zero
default.
(qemu uses an explicit -Wno-initializer-overrides to overcome the clang
spelling of the warning, because clang has the same 6 warnings as gcc on my
example code, where the 6th one interferes with intentional use, and because
clang sticks -Winitializer-overrides in -Wall, whereas gcc was smart enough to
stick it only in -Wextra)
More information about the Gcc-bugs
mailing list