On Mon, Nov 16, 2020 at 02:02:00PM -0500, Jason Merrill via Gcc-patches wrote:
> On 11/16/20 12:42 PM, Martin Sebor wrote:
> > On 11/15/20 3:44 PM, Marek Polacek via Gcc-patches wrote:
> > > This patch implements the long-desired -Wuninitialized warning for
> > > member initializer lists, so that the front end can detect bugs like
> > > 
> > >    struct A {
> > >      int a;
> > >      int b;
> > >      A() : b(1), a(b) { }
> > >    };
> > > 
> > > where the field 'b' is used uninitialized because the order of member
> > > initializers in the member initializer list is irrelevant; what matters
> > > is the order of declarations in the class definition.
> > > 
> > > I've implemented this by keeping a hash set holding fields that are not
> > > initialized yet, so at first it will be {a, b}, and after initializing
> > > 'a' it will be {b} and so on.  Then I use walk_tree to walk the
> > > initializer and if we see that an uninitialized object is used, we warn.
> > > Of course, when we use the address of the object, we may not warn:
> > > 
> > >    struct B {
> > >      int &r;
> > >      int *p;
> > >      int a;
> > >      B() : r(a), p(&a), a(1) { } // ok
> > >    };
> > > 
> > > Likewise, don't warn in unevaluated contexts such as sizeof.  Classes
> > > without an explicit initializer may still be initialized by their
> > > default constructors; whether or not something is considered initialized
> > > is handled in perform_member_init, see member_initialized_p.  It is
> > > possible that we might want to tweak that if the current approach turns
> > > out to be flawed.
> > > 
> > > And this is where things stop being simple.  I had to implement certain
> > > gymnastics to handle cases like
> > > 
> > >    struct S {
> > >      int a, b, c;
> > >      S() : a((b = 42)), c(b) { }
> > >    };
> > > 
> > > where the initializer for 'a' surreptitiously initializes 'b'.  And since
> > > 'b' isn't read in the assignment, we can't warn that it's being used
> > > uninitialized.
> > > 
> > > Also, initializer lists.  Consider:
> > > 
> > >    struct A {
> > >      int x, y, z;
> > >    };
> > > 
> > >    struct S {
> > >      A a;
> > >      S() : a{ 1, 2, a.y } {} // #1
> > >      S(int) : a{ a.y, 2, 3 } {} // #2
> > >    };
> > > 
> > > In #1, a.y refers to an initialized element, so we oughtn't warn.  But
> > > in #2, a.y refers to an uninitialized element, so we should warn.  To
> > > that end I've added a vector that tracks which elements of an initializer
> > > list are already initialized, so that when we encounter them later in
> > > the list, we don't warn.  This is the find_uninit_data->list_inits
> > > thing.  It will contain paths to the initialized elements, so e.g.
> > > 
> > >    ((S *)this)->a.y
> > > 
> > > These are edge cases, but some developers live on the edge.
> > > 
> > > This patch also handles delegating constructors and initializing base
> > > classes.
> > > 
> > > A couple of TODOs:
> > > - the same approach could be used to warn for NSDMIs, but I think
> > > it's out
> > >    of scope for this patch;
> > > - the diagnostic for anonymous unions/structs should be improved
> > >    to say the name of the member instead of "<anonymous>";
> > > - certain uninitialized warnings in initializer-lists are issued
> > >    by the FE as well as the ME.  It's unclear to me what we want to do
> > >    here;
> > > - using uninitialized members of base classes is not implemented yet.
> > 
> > This is a great enhancement!  Detecting the misues even in inline
> > functions is especially useful.
> 
> Why doesn't the middle-end warning work for inline functions?
> 
> > I installed the patch to see how
> > it handles some of the cases the middle end was recently enhanced
> > to detect.  Below is a test case I tried and the outupt.  Clearly,
> > the middle end warning doesn't work as intended for C++ ctors, so
> > that's something for me to look into.
> 
> Please.  If we can handle these warnings in the middle-end (where we have
> data flow info), that should be more reliable than the front-end walking the
> trees.

For function bodies, definitely.  I don't we should attempt to use walk_tree
on them.  But for initializers, like in this patch, I think it's okay.
Though of course it will still issue false positives for cases like ?: or
a || b, or a && b, etc. in the initializer.

Marek

Reply via email to