Bug 37722 - destructors not called on computed goto
Summary: destructors not called on computed goto
Alias: None
Product: gcc
Classification: Unclassified
Component: middle-end (show other bugs)
Version: 4.3.2
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
Keywords: wrong-code
Depends on:
Reported: 2008-10-02 20:45 UTC by cburger
Modified: 2013-01-11 14:52 UTC (History)
4 users (show)

See Also:
Host: x86_64-linux-gnu
Target: x86_64-linux-gnu
Build: x86_64-linux-gnu
Known to work:
Known to fail: 3.3 4.4.0 4.0.1
Last reconfirmed:


Note You need to log in before you can comment on or make changes to this bug.
Description cburger 2008-10-02 20:45:00 UTC
#include <iostream>

struct foo {
    foo() { std::cout << "foo constructed\n"; }
   ~foo() { std::cout << "foo destructed\n"; }

enum opcode { NOP, FOO, DONE };

void exec0(const opcode* o) {
    loop: switch (*o) {
        case NOP:          ++o; goto loop;
        case FOO: { foo f; ++o; goto loop; } // f destructed
        case DONE:  return;

void exec1(const opcode* o) {
    static void* label[] = { &&NOP, &&FOO, &&DONE };
    goto *label[*o];
    NOP:          ++o;   goto *label[*o];
    FOO: { foo f; ++o;   goto *label[*o]; } // f not destructed
//  FOO: { foo f; ++o; } goto *label[*o];   // work-around
    DONE:  return;

int main() {
    const opcode program[] = { NOP, FOO, NOP, NOP, DONE };
    return 0;

foo constructed
foo destructed
foo constructed

Tested with: 4.3.2, 4.2.4, 4.1.3, 3.4.6
Optimization level makes no difference.
Intel icpc 10.1, 9.1 show the same problem.
Comment 1 Andrew Pinski 2008-10-02 21:22:13 UTC
EH lowering for some reason does not copy the finally part of the try before the goto.  This also happens with 3.3.
Comment 2 Ryan Johnson 2009-05-09 08:16:44 UTC
Computed gotos can easily make it impossible for the compiler to call constructors and destructors consistently. This is a major gotcha of computed gotos for people who have used normal gotos in C++ and expect destructors to be handled properly. Consider this program, for instance:

#include <stdio.h>
template<int i>
struct foo {
    foo() { printf("%s<%d>\n", __FUNCTION__, i); }
    ~foo() { printf("%s<%d>\n", __FUNCTION__, i); }
int bar(int idx) {
    static void* const gotos[] = {&&RETRY, &&INSIDE, &&OUTSIDE, &&EVIL};
    bool first = true;
        foo<1> f1;
        if(first) {
            first = false;
            goto *gotos[idx];
        return 1;
    if(0) {
        foo<2> f2;
        return 2;
    return 0;
int main() {
    for(int i=RETRY; i <= EVIL; i++)
        printf("%d\n", bar(i));
    return 0;

Not only does it let you jump out of a block without calling destructors, it lets you jump into one without calling constructors:

$ g++-4.4.0 -Wall -O3 scratch.cpp && ./a.out

Ideally, the compiler could analyze possible destinations of the goto (best-effort, of course) and emit suitable diagnostics:

scratch.cpp:16: warning: computed goto bypasses destructor of 'foo<1> f1'
scratch.cpp:13: warning:   declared here

scratch.cpp:23: warning: possible jump to label 'EVIL'
scratch.cpp:16: warning:   from here
scratch.cpp:22: warning:   crosses initialization of 'foo<2> f2'

In this particular example the compiler should be able to figure out that no labels reach a live f1 and call its destructor properly. If it's not feasible to analyze the possible destinations of the computed goto, regular control flow analysis should at least be able to identify potentially dangerous labels and gotos, e.g.:

scratch.cpp:16: warning: computed goto may bypass destructor of 'foo<1> f1'
scratch.cpp:13: warning:   declared here

scratch.cpp:23: warning: jump to label 'EVIL'
scratch.cpp:8:  warning:   using a computed goto
scratch.cpp:22: warning:   may cross initialization of 'foo<2> f2'
Comment 3 Timo Kreuzer 2013-01-11 14:52:03 UTC
(In reply to comment #2)
> int bar(int idx) {
>     static void* const gotos[] = {&&RETRY, &&INSIDE, &&OUTSIDE, &&EVIL};
>     bool first = true;
>     {
>     RETRY:
>         foo<1> f1;
>         if(first) {
>             first = false;

Well usually you cannot declare a variable after a label. That's a gcc extension. So you should either add curly braces between RETRY and INSIDE, or move RETRY above the preceeding brace. But that is just syntax, since it would be desired that it behaved like it would with a normal goto, even if that was placed like that. (I assume normal gotos will handle this properly)

To theoretically solve the proplem, you could replace every indirect goto with code like this:

    static void* const local_gotos[] = {&&label1, &&label2, &&label3, &&label4};
    goto *local_gotos[idx];
label1: goto RETRY;
label2: goto INSIDE;
label3: goto OUTSIDE;
label4: goto EVIL;

And now have the compiler optimize the pathes.
This way, there are no more crazy jumps, just a very simple indirect jump and a number of normal jumps that the compiler should be able to handle anyway.

The problem is now, that there might be multiple indirect jumps that use the same static data, which is just a bunch of void pointers.
So depending on the origin of the jump different pathes would need to be generated for each target. This is incompatible with an indirect jump instruction though, which only utilizes some arbitrary address.

One possible solution for this is to always invoke all destructors and constructors, even for local indirect gotos. So you would need to consider an indirect jump to always leave all scope blocks, up to the top level of the function and reenter from there. This would allow to generate labels/codepathes that are consistent for multiple indirect gotos.

Another solution, is to use multiple jump tables. One table for each indirect goto containing one entry for each possibly referenced label in the function. The trick is that the original label addresses, that are produced with the && operator will all point to an "array" of direct jmp instructions. all of the same size. Now the first indirect goto would emit an indirect jump instruction to the address itself. The second one would substract the address of the first label, divide by the size of the jmp stub code and use the result as an index into it's own private jump table. Alternatively a static offset could be added to the actual address and multiple direct jump stubs would be generated in a row.

  .long label1, label2, label3, label4;

  .long label1_from_2, label2_from_2, label3_from_2, label4_from_2;

   // first indirect goto
   mov eax, table[ecx * 4] // get the address from the table
   jmp eax // jump there

   // Second indirect goto
   mov ecx, table[ecx * 4] // get the address from the table
   sub ecx, label1 // substract the address of the first label
   shr ecx, 3 // divide by 8 (assuming each stub is 8 bytes)
   mov eax, table[ecx * 4] // get the address from the 2nd table
   jmp eax // jump there

   // Alternative: using multiple stub arrays
   mov eax, table[ecx * 4] // get the address from the table
   add eax, label5 - label1 // add the offset to the second "jmp array"
   jmp eax // jump there

label1: jmp label1_from_1
label2: jmp label2_from_1
label3: jmp label3_from_1
label4: jmp label4_from_1

label5: jmp label1_from_2
label6: jmp label2_from_2
label7: jmp label3_from_2
label8: jmp label4_from_2

This mechanism would only be needed as soon as multiple indirect jumps could reference the same labels and different code pathes would need to be constructed for the targets depending on the origin of the goto.
As an optimization it should be considered that labels, of which the address has been put in a table, which is now out of scope are not actually available anymore. Other optimizations might be using 2 different tables directly, if they are only used for 2 indirect jumps (someone might (mis)use it for different things like non-local gotos, exception handling, saving the address of code that is being executed for debugging purposes, etc)