This code should warning (with -Wall) about the use of `border` that is uninitialized #include <stdio.h> #include <vector> struct Camera { struct P2d { float x, y; }; std::vector<P2d> clip_area; float border = 10.f; int z = 3; Camera() : clip_area({{border, border}, {1-border, border}, {1-border, 1-border}, {border, 1-border}}) { } }; int main() { Camera c; printf("%.18g\n", c.clip_area[0].x); } However does so only if member `z` is present; commenting out the line `int z = 3;` silences the warning. This show/hide of the warning happens also pseudo-randomly in other cases (while I was trying to get the minimum code showing the problem I found many cases in which removing even an executable statement in the body of a method triggered the behavior change).
Forgot to say that -O3 is needed to see the warning (this is however expected)
There's a change in inlining when z is removed. We then have the separate CTOR void Camera::Camera (struct Camera * const this) { ... <bb 2> [local count: 1073741824]: _2 = *this_6(D).border; _3 = 1.0e+0 - _2; _68 = {_2, _2, _3, _2}; and fno CLOBBER of *this_6(D) at its start at the time we do late uninit diagnostics. That's because we remove those indirect clobbers too early.
Adding [[gnu::noinline]] to Camera::Camera will never warn.
The interesting thing is that enabling the middle-end diagnostic to trigger shows we emit duplicate diagnostics: struct Camera { float clip_area; float border = 10.f; [[gnu::noinline]] Camera() : clip_area(border) { } }; Camera foo() { Camera c; return c; } emits t.C: In constructor 'Camera::Camera()': t.C:4:44: warning: member 'Camera::border' is used uninitialized [-Wuninitialized] 4 | [[gnu::noinline]] Camera() : clip_area(border) { } | ^~~~~~ t.C: In constructor 'Camera::Camera()': t.C:4:44: warning: '*this.Camera::border' is used uninitialized [-Wuninitialize] 4 | [[gnu::noinline]] Camera() : clip_area(border) { } | ^~~~~~ the first is from the C++ FE find_uninit_fields diagnostic which for some reason doesn't work for the testcase in the description, possibly the initializer list(?) isn't handled? The early uninit IL is <bb 2> : MEM[(struct __as_base &)this_6(D)] ={v} {CLOBBER}; _1 = &this_6(D)->clip_area; std::allocator<Camera::P2d>::allocator (&D.26049); _2 = this_6(D)->border; D.26047[0].x = _2; _3 = this_6(D)->border; D.26047[0].y = _3; D.27007._M_array = &D.26047; D.27007._M_len = 1; std::vector<Camera::P2d>::vector (_1, D.27007, &D.26049); the call to std::allocator<Camera::P2d>::allocator is thought to clobber *this and thus possibly initialize 'border' here. I'm testing a middle-end fix here - Marek, can you see whether it's possible to detect this in the frontend? The middle-end will require optimization. The duplicate diagnostic might also be interesting to look at, but that might already be reported separately?
The master branch has been updated by Richard Biener <rguenth@gcc.gnu.org>: https://gcc.gnu.org/g:abf915193fbf725bb359e6936e10dcc282eb94cc commit r14-3460-gabf915193fbf725bb359e6936e10dcc282eb94cc Author: Richard Biener <rguenther@suse.de> Date: Thu Aug 24 09:32:54 2023 +0200 tree-optimization/111123 - indirect clobbers thrown away too early The testcase in the PR shows that late uninit diagnostic relies on indirect clobbers in CTORs but we throw those away in the fab pass which is too early. The reasoning was they were supposed to keep SSA names live but that's no longer the case since DCE doesn't treat them as keeping SSA uses live. The following instead removes them before out-of-SSA coalescing which is the thing that's still affected by them. PR tree-optimization/111123 * tree-ssa-ccp.cc (pass_fold_builtins::execute): Do not remove indirect clobbers here ... * tree-outof-ssa.cc (rewrite_out_of_ssa): ... but here. (remove_indirect_clobbers): New function. * g++.dg/warn/Wuninitialized-pr111123-1.C: New testcase.
middle-end part are fixed, so with optimization we should diagnose this more consistently now. Leaving open for -O0 and the C++ FE issue.