[cfe-dev] Working on the rest of PR10063: destructors and the CFG are causing issues with -Wreturn-type

Chandler Carruth chandlerc at google.com
Mon Sep 12 02:22:16 PDT 2011


Hey Ted (and any other analyzer hackers out there)

On Sat, Sep 10, 2011 at 1:14 AM, Chandler Carruth <chandlerc at google.com>wrote:

> Everything we need is here, but there is an unfortunate structure: the
> block (B3) with the no-return destructor is nat an immediate predecessor of
> the exit block. As a result, we never look within B3 for no-return
> destructors, and fail to recognize that B1 and B0 are dead, and that there
> is no possibility of reaching the exit block without returning.
>
> There is a comment in AnalysisBasedWarnings which indicates that the
> correct solution may be to actually prevent B1 (or any other block) from
> being added as a successor of B3, and explicitly add the exit block as a
> successor so we don't end up with an orphaned exit block. Is this the right
> approach to take? Can you provide any tips for how to implement this? I
> looked into it for a while, and it didn't seem straightforward. I can find
> all of the places where we add implicit destructors to the CFG, and when one
> of them is added that in attributed no-return, clear the successors of the
> current block and add the exit block as a successor. However, it looks like
> in some cases we process implicit destructors before actually adding
> successor(s) to a block. In that case, clearing won't help. What do you
> think is the cleanest place to put this in the CFGBuilder, and what is the
> cleanest strategy? Maybe we should track on the CFGBlock itself whether it
> is a 'no-return' block, and when adding successors check that bit and refuse
> to add any. Or we could catch when lazily creating a new block, and if the
> previous one contains any no-return elements clear its successors?
>

So I dived into this with fresh eyes and I think I figured the various bits
of it out. Now I understand better both what was going wrong, and what the
FIXME about severing edges is *really* talking about.

The fundamental problem I'm hitting is that we special case the formation of
CFGBlocks when a no-return construct is encountered. When such a block is
hit, we ignore any successor-sequence already built and instead create a
block with a direct and immediate successor of the exit block. This allows
the analysis to quickly find the no-return CFGBlocks (and elements), and
also ensures that the CFGBlocks that *would* have succeeded a no-return
construct are correctly modeled as unreachable blocks.

In order to handle this situation when working with implicit destructors, we
have to from a new block whenever we encounter one of these destructors, and
correctly setup its successors. I've attached a patch which I think mostly
does this. It adds a FIXME to the only part of the CFG builder where we
don't do this, because that part injects destructors into the *beginning* of
a block. I need to think a bit more about how the block datastructures work
before I'll feel comfortable fixing that, as it'll be a different form of
split from the others. This code path only comes up when patching up gotos,
so I'm not particularly concerned if the no-return modeling there is
imperfect. =]

With this patch I get the following CFG for the code snippet I posted
originally:
 [ B4 (ENTRY) ]
    Predecessors (0):
    Successors (1): B2

 [ B1 ]
      1: [B2.2].~other() (Implicit destructor)
    Predecessors (0):
    Successors (1): B0

 [ B2 ]
      1:
      2: other o;
      3: i
      4: [B2.3]
      T: switch [B2.4]
    Predecessors (1): B4
    Successors (1): B3

 [ B3 ]
    default:
      1: my_abort_struct()
      2: [B3.1] (BindTemporary)
      3: ~my_abort_struct() (Temporary object destructor)
    Predecessors (1): B2
    Successors (1): B0

 [ B0 (EXIT) ]
    Predecessors (2): B1 B3
    Successors (0):

Here we correctly model that B1 cannot be reached, and B3 has the immediate
edge to B0. If I change the code snippet to declare a variable of
my_abort_struct type instead of create a temporary (thus exercising the
other CFG-buiding path I changed in this patch) I get this CFG:
 [ B4 (ENTRY) ]
    Predecessors (0):
    Successors (1): B2

 [ B1 ]
      1: [B2.2].~other() (Implicit destructor)
    Predecessors (0):
    Successors (1): B0

 [ B2 ]
      1:
      2: other o;
      3: i
      4: [B2.3]
      T: switch [B2.4]
    Predecessors (1): B4
    Successors (1): B3

 [ B3 ]
    default:
      1:
      2: my_abort_struct s;
      3: [B3.2].~my_abort_struct() (Implicit destructor)
    Predecessors (1): B2
    Successors (1): B0

 [ B0 (EXIT) ]
    Predecessors (2): B1 B3
    Successors (0):

Same story, the CFG correctly models the no-return destructor. For a
somewhat trickier case, with 3 variables, the middle forming a no-return
destructor you can even see that this correctly marks which destructor is
unreachable!
code:
int f7(int i) {
  other o;
  switch (i) {
    default:
      other o1;
      my_abort_struct s;
      other o2;
  }
}

CFG:
 [ B5 (ENTRY) ]
    Predecessors (0):
    Successors (1): B2

 [ B1 ]
      1: [B2.2].~other() (Implicit destructor)
    Predecessors (1): B3
    Successors (1): B0

 [ B2 ]
      1:
      2: other o;
      3: i
      4: [B2.3]
      T: switch [B2.4]
    Predecessors (1): B5
    Successors (1): B4

 [ B3 ]
      1: [B4.2].~other() (Implicit destructor)
    Predecessors (0):
    Successors (1): B1

 [ B4 ]
    default:
      1:
      2: other o1;
      3:
      4: my_abort_struct s;
      5:
      6: other o2;
      7: [B4.6].~other() (Implicit destructor)
      8: [B4.4].~my_abort_struct() (Implicit destructor)
    Predecessors (1): B2
    Successors (1): B0

 [ B0 (EXIT) ]
    Predecessors (2): B1 B4
    Successors (0):


I think this patch is a really useful improvement in accuracy, but it does
add some non-trivial overhead to the process of adding implicit destructors
into the CFG for automatic variables.. Does it seem acceptable? I don't see
particularly cleaner ways of doing this... =/

If this patch is OK, or after any performance tuning you'd like to see here,
I think I also better understand the FIXME in the analysis file. Do you
actually want to switch the CFG to have *no* successors for blocks which
contain a no-return element? That seems like it would be a very clean way of
modelling this and would simplify the analysis for -Wreturn-type
significantly. Let me know your thoughts here; I see a clean way to
implement this after a couple of refactorings to consolidate the code I'm
changing in this patch. (Currently, there is duplicate code. I left the
refactoring and consolidation for a follow-up to make reviewing this one
easier.)

Thanks!
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20110912/7b5b5111/attachment.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: fix-implicit-dtor-noreturn.patch
Type: text/x-patch
Size: 8977 bytes
Desc: not available
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20110912/7b5b5111/attachment.bin>


More information about the cfe-dev mailing list