[cfe-commits] [PATCH] Checking for sometimes-uninitialized variables

Richard Smith richard at metafoo.co.uk
Thu May 17 15:22:13 PDT 2012


On Thu, May 17, 2012 at 2:04 PM, David Blaikie <dblaikie at gmail.com> wrote:

> On Thu, May 17, 2012 at 1:01 PM, Richard Smith <richard at metafoo.co.uk>
> wrote:
> > Hi,
> >
> > The attached patch adds a new classification of uninitialized variables,
> > between 'always used uninitialized' and 'may be used uninitialized',
> which
> > (unlike 'may be used uninitialized') has no false positives (after making
> > one conservative assumption), and produces more warnings than 'always
> used
> > uninitialized'.
>
> The principle and results look great. Here's some nitpicky/code review
> details, though:
>
> > include/clang/Analysis/Analyses/UninitializedValues.h:
> > /// If non-NULL, this use is always uninitialized if occurs after any of
>

Done.


> If what is non-NULL (ultra-pedantry/pet-peeve: pointer values are
> null, not NULL - the latter being just a specific constant)? The
> vector? "non-empty", perhaps?
>
> "if occurs" -> "if it occurs" ?
>

Done.


> > /// these branches is taken.
>
> "any of these branches are taken" ? It sounds more right, but strictly
> speaking it feels wrong because you're talking about only one of
> them... I'm not sure what the correct grammatical rules are here, it
> just sounded weird when I read it.
>

I was taught that "{none,one,neither,either,any} of these things" is a
singular, since we're referring to just one of them at a time. But I would
not be surprised if that isn't universal amongst English-speakers.


> > const CFGBlock *B = cfg.begin()[Queue.back()];
>
> If CFG iterators are so flexible as to allow array access, perhaps we
> should just add an op[] to the CFG type itself rather than indexing
> into an iterator?
>

Good idea, I meant to come back to that, but forgot. Thanks! I've added an
operator[] to CFG.


> > #if 0
> >     cfg.dump(dc.getParentASTContext().getLangOpts(), true);
> > #endif
>
> Not sure if you meant to leave this in, nor whether it should be
> preprocessed out, constant conditional-ed out, or commented out, if it
> is to remain (I sort of like the constant conditional option - that
> ensures that it doesn't bitrot. Between preprocessing and commenting
> out, I'd probably choose commenting out.
>

This file has a pile of "#if 0"s for debugging already, which produce all
the useful information except for dumping the CFG. This seemed like a
natural addition (though it would be great to be able to enable these
without a rebuild...).


> > switch (Use.getKind()) {
> > case UninitUse::Always: DiagID = diag::warn_uninit_var; break;
> > case UninitUse::Sometimes: DiagID = diag::warn_sometimes_uninit_var;
> break;
> > case UninitUse::Maybe: DiagID = diag::warn_maybe_uninit_var; break;
>
> OCD: worth aligning the DiagID so the maybe/sometimes/ stand out better?
>
> The switch 10 lines down is also written with multiple line breaks
> (couldn't quite fit it under 80 cols, I guess?) Perhaps worth just
> moving the switch out to the top & doing it unconditionally?
>

The two switches actually produce different values.


> > // FIXME: This iteration order, and thus the resulting diagnostic order,
> > //        is nondeterministic.
>
> Is it just luck that we don't test the cases where this would come up?
> Or are our tests unstable & just haven't exhibited it yet?
>

-verify doesn't care which order diagnostics appear in. IIRC, at least one
of current tests exhibits nondeterminism.


> > - if (DiagnoseUninitializedUse(S, vd, vi->first,
> > -                              /*isAlwaysUninit=*/vi->second))
> > - // If we have self-init, downgrade all uses to 'may be uninitialized'.
> > + UninitUse Use = hasSelfInit ? UninitUse(vi->getUser(), false) : *vi;
> > + if (DiagnoseUninitializedUse(S, vd, Use))
>
> Is this a change in behavior, or is the comment clarifying existing
> behavior? (just a little curious because the 'isAlwaysUninit'
> parameter went from 'vi->second' to 'false')
>

There's no change in behavior. If there's a self-init and we have an
always-uninitialized use, we can't get here (the diff cuts off the relevant
context, which is just a few lines above).


> > static void NoteUninitBranches(Sema &S, const UninitUse &Use) {
>
> Should you use a switch in the diagnostic rather than string literals
> in the source for the 'for', 'if' , ':?', etc? (I realize those are
> keywords & can't be translated, which is the usual hypothetical
> argument for these things - so it's not a definite
> improvement/important change in that regard)
>

I started that way, but it led to a very repetitive definition in the
diagnostic.


> >      // "condition is true / condition is false".
> >    case Stmt::IfStmtClass:
> ...
> >      // "loop is entered / loop is exited".
> >    case Stmt::WhileStmtClass:
> ...
> >      // "switch case is taken".
> >    case Stmt::CaseStmtClass:
>
> Alignment of these comments is a bit strange (they're indented, but
> they pertain (as far as I understand it) to the following case which
> is not indentend). It might even be better on the same line as (&
> following) the DiagKind assignment. Hmm, I see - it pertains to a set
> of cases, yeah, maybe just unindenting would suffice - but I can see
> that it's not clear either way how to group the case statements under
> the comment.
>

I've unindented the comments. I think you're right that that's a little
better.


> Diagnostic text: the note trace talks about loops being "entered" but
> then about them being "terminated", would it make sense to refer to
> them as "exited" and/or is there precedent for any other terminology
> here? (& I still haven't come up with any better way to describe the
> "loop exited through its condition becoming false" than your
> "exited/terminated 'normally'")
>

Yes, that not isn't as clear as it could be. I've changed this to
"uninitialized use occurs whenever 'for' loop exits because its condition
is false".


> The shortcircuit cases are nice - does the note point to the && now, I
> assume? (you could split the expressions over multiple lines to
> demonstrate this) Is it worth any clarity to point to the first
> operand to the &&? I'm wondering if people would be confused if they
> saw:
>

The diagnostic points to the expression on the LHS of the && (the one whose
value is being described in the note). I've updated the tests as you
suggest.


> if (x && y) { // whenever && condition is false
>

In this case, we say 'whenever the 'if' condition is false'.


> and think it was talking about the condition for the if block, rather
> than that of the 'x' - but perhaps it's good enough that we get that
> far.


I think we'd only want to get the '&&' diagnostic here if the
initialization happens within 'y' itself (though we don't currently catch
that case at all, because the uninitialized variable tracking code's
special case for '&&' and '||' doesn't quite do the right thing when
they're immediately used in a condition).

Updated patch attached.

-- Richard
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-commits/attachments/20120517/de700ee8/attachment.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: sometimes-uninit.diff
Type: application/octet-stream
Size: 31432 bytes
Desc: not available
URL: <http://lists.llvm.org/pipermail/cfe-commits/attachments/20120517/de700ee8/attachment.obj>


More information about the cfe-commits mailing list