In this code: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ template <typename E> struct enum_flags { enum_flags &operator|= (E e) { m_enum_value = (E) (m_enum_value | e); // uses uninitialized m_enum_value return *this; } E m_enum_value; }; enum flag { FLAG1 = 1 }; void foo () { enum_flags<flag> ef; ef |= FLAG1; // missing warning here } void bar () { flag f; f = (flag) (f | FLAG1); // warning here } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ I expect to get a warning on the "missing warning here" line, because inside operator|= there's a reference to the uninitialized m_enum_value field. However, compiling the code below using GCC 7.0.1 20170221 (current trunk), with "-O2 -Wall -Wextra -Wuninitialized" does not catch that bug. I tried adding __attribute__((__always_inline__)) to the operator|= method, to try to help gcc see through the method, but it makes no difference. Note there's no user-defined ctor here. The equivalent code using raw enums directly like in the "bar" function, does warn on the "warning here" line. Enabling optimization does NOT help. Here's current G++ output with g++ 7.0.1 20170221 (experimental): $ /opt/gcc/bin/g++ enum_flags.cc -o enum_flags -g3 -O2 -c -Wall -Wextra -Wuninitialized enum_flags.cc: In function ‘void bar()’: enum_flags.cc:31:17: warning: ‘f’ is used uninitialized in this function [-Wuninitialized] f = (flag) (f | FLAG1); ~~~^~~~~~~~
Created attachment 40797 [details] reproducer Here's the same reproducer source.
For your testcase foo () is quickly optimized to nothing given ef is unused. For -O0 we only warn before inlining.
> For your testcase foo () is quickly optimized to nothing given ef is unused. Shouldn't that be true for "bar ()" too then? Looks like I over reduced. I'll try again.
Here's a better one: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ template <typename E> struct enum_flags { enum_flags &operator|= (E e) { m_enum_value = (E) (m_enum_value | e); return *this; } operator E () const { return m_enum_value; } E m_enum_value; }; enum flag { FLAG1 = 1, }; typedef enum_flags<flag> eflags; extern void bar (); void foo_0 (int param) { eflags f0; if (param) f0 |= FLAG1; if (f0 != 0) // doesn't warn bar (); } void foo_1 (int param) { eflags f1; if (param) f1 |= FLAG1; if (f1 != FLAG1) // warns bar (); } void foo_2 (int param) { eflags f2; if (param) f2 |= FLAG1; if (f2 != 1) // doesn't warn bar (); } void foo_3 (int param) { eflags f3; if (param) f3 |= FLAG1; if (f3 != 3) // warns bar (); } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We get: $ /opt/gcc/bin/g++ -std=gnu++11 enum_flags2.cc -o enum_flags.o -g3 -O2 -Wuninitialized -c -Wall -Wextra enum_flags2.cc: In function ‘void foo_1(int)’: enum_flags2.cc:6:25: warning: ‘f1’ may be used uninitialized in this function [-Wmaybe-uninitialized] m_enum_value = (E) (m_enum_value | e); ^~~~~~~~~~~~ enum_flags2.cc:40:10: note: ‘f1’ was declared here eflags f1; ^~ enum_flags2.cc: In function ‘void foo_3(int)’: enum_flags2.cc:6:25: warning: ‘f3’ may be used uninitialized in this function [-Wmaybe-uninitialized] m_enum_value = (E) (m_enum_value | e); ^~~~~~~~~~~~ enum_flags2.cc:64:10: note: ‘f3’ was declared here eflags f3; ^~ In particular, note that the uses in foo_0 and foo_2 did NOT warn while the uses in foo_1 and foo_3 did warn. Why's that? The use I was originally most interested in catching was the one like in foo_0, where we compare against 0. The original code has means to make comparing enum_flags against other integer literals fail to compile. (0 means bit flags not set, the code is implementing a type-safe enum flags wrapper). I just tried the other values for completeness, thinking that maybe 0 was being given "special" treatment, though foo_2 seems to show otherwise. FWIW, adding enum values to cover all the compared-to values makes no difference: enum flag { FLAG0 = 0, FLAG1 = 1, FLAG2 = 2, FLAG3 = 3, }; Same set of warnings.
Created attachment 40806 [details] reproducer v2 Same code as the previous comment.
Created attachment 40807 [details] reproducer v3 Slightly reduced testcase. enum_flags no longer a template, and stores/uses/converts to int instead of enum type. Same set of (missing) warnings.
Today I remembered to try reducing the v3 reproducer by converting to C instead of C++. The resulting warnings are not exactly the same, but they're very similar. The fact that we still only get warnings for foo_1 and foo_3 is preserved. All the foo_* functions are identical except for the literal being compared to. Somehow comparing against 0, 1, 3, or FLAG1 behaves differently. Comparing against 1 doesn't warn, but against FLAG1 (which is 1) does. Quite mystifying. ============================================ struct enum_flags { int value; }; static inline void set_flag (struct enum_flags *f, int e) { f->value = f->value | e; } static inline int get_flag (struct enum_flags *f) { return f->value; } enum flag { FLAG1 = 1, }; extern void bar (); void foo_0 (int param) { struct enum_flags f0; // doesn't warn if (param) set_flag (&f0, FLAG1); if (get_flag (&f0) != 0) // doesn't warn bar (); } void foo_1 (int param) { struct enum_flags f1; // warns if (param) set_flag (&f1, FLAG1); if (get_flag (&f1) != FLAG1) // warns bar (); } void foo_2 (int param) { struct enum_flags f2; // doesn't warn if (param) set_flag (&f2, FLAG1); if (get_flag (&f2) != 1) bar (); } void foo_3 (int param) { struct enum_flags f3; // warns if (param) set_flag (&f3, FLAG1); if (get_flag (&f3) != 3) bar (); } ============================================ Results in: ============================================ $ /opt/gcc/bin/gcc enum_flags4.c -o enum_flags -O2 -Wall -Werror -Wuninitialized -c enum_flags4.c: In function ‘foo_1’: enum_flags4.c:3:81: error: ‘f1.value’ may be used uninitialized in this function [-Werror=maybe-uninitialized] static inline void set_flag (struct enum_flags *f, int e) { f->value = f->value | e; } ~~~~~~~~~^~~ enum_flags4.c:25:21: note: ‘f1.value’ was declared here struct enum_flags f1; // warns ^~ enum_flags4.c: In function ‘foo_3’: enum_flags4.c:3:81: error: ‘f3.value’ may be used uninitialized in this function [-Werror=maybe-uninitialized] static inline void set_flag (struct enum_flags *f, int e) { f->value = f->value | e; } ~~~~~~~~~^~~ enum_flags4.c:49:21: note: ‘f3.value’ was declared here struct enum_flags f3; // warns ^~ cc1: all warnings being treated as errors ============================================
Created attachment 40823 [details] reproducer v4 Same as above.
confirmed that reproducer v4 warns for f1 and f3 but not f0 or f2.
Many test cases here... For the test case in comment #0, GCC 11 still diagnoses only the second function. The first was never diagnosed because the variable is optimized out. That should be expected. When the variable is used it is diagnosed. $ gcc -O2 -S -Wall pr79658-c0.C pr79658-c0.C: In function ‘void bar()’: pr79658-c0.C:31:15: warning: ‘f’ is used uninitialized [-Wuninitialized] 31 | f = (flag) (f | FLAG1); // warning here | ^ pr79658-c0.C:29:8: note: ‘f’ was declared here 29 | flag f; | ^ For the test case in comment #4, with -O, GCC 11 diagnoses all but the first function. The first function isn't diagnosed because the conditionals are folded away and all the warning sees is an unconditional call to bar(). Without optimization, the member functions aren't inlined and so the warning doesn't see the uninitialized reads. That's mostly unavoidable. The warning does have logic to trigger when an uninitialized object is passed to a const-qualified reference (or pointer) but not if the object is passed to a non-const argument by reference prior to that. $ gcc -O2 -S -Wall pr79658-c4.C pr79658-c4.C: In function ‘void foo_1(int)’: pr79658-c4.C:40:10: warning: ‘*(unsigned int*)((char*)&f1 + offsetof(eflags, enum_flags<flag>::m_enum_value))’ may be used uninitialized [-Wmaybe-uninitialized] 40 | eflags f1; | ^~ pr79658-c4.C: In function ‘void foo_2(int)’: pr79658-c4.C:52:10: warning: ‘*(unsigned int*)((char*)&f2 + offsetof(eflags, enum_flags<flag>::m_enum_value))’ may be used uninitialized [-Wmaybe-uninitialized] 52 | eflags f2; | ^~ pr79658-c4.C: In function ‘void foo_3(int)’: pr79658-c4.C:64:10: warning: ‘*(unsigned int*)((char*)&f3 + offsetof(eflags, enum_flags<flag>::m_enum_value))’ may be used uninitialized [-Wmaybe-uninitialized] 64 | eflags f3; | ^~ Finally, for the test case in comment #7, GCC 11 (and prior, including 9, since r264078) diagnoses all four functions: $ gcc -O2 -S -Wall pr79658.c pr79658.c: In function ‘foo_0’: pr79658.c:13:21: warning: ‘f0.value’ is used uninitialized [-Wuninitialized] 13 | struct enum_flags f0; // doesn't warn | ^~ pr79658.c: In function ‘foo_1’: pr79658.c:3:81: warning: ‘f1.value’ may be used uninitialized [-Wmaybe-uninitialized] 3 | oid set_flag (struct enum_flags *f, int e) { f->value = f->value | e; } | ~~~~~~~~~^~~ pr79658.c:25:21: note: ‘f1.value’ was declared here 25 | struct enum_flags f1; // warns | ^~ pr79658.c: In function ‘foo_2’: pr79658.c:3:81: warning: ‘f1.value’ may be used uninitialized [-Wmaybe-uninitialized] 3 | oid set_flag (struct enum_flags *f, int e) { f->value = f->value | e; } | ~~~~~~~~~^~~ pr79658.c:25:21: note: ‘f1.value’ was declared here 25 | struct enum_flags f1; // warns | ^~ pr79658.c: In function ‘foo_3’: pr79658.c:3:81: warning: ‘f3.value’ may be used uninitialized [-Wmaybe-uninitialized] 3 | oid set_flag (struct enum_flags *f, int e) { f->value = f->value | e; } | ~~~~~~~~~^~~ pr79658.c:49:21: note: ‘f3.value’ was declared here 49 | struct enum_flags f3; // warns | ^~ With that, I'm going to resolve this as fixed (for the last test case).