Vittorio Romeo
Vittorio Romeo

Reputation: 93264

Lambda machine-dependent segmentation-fault (possible compiler bug?)

Today I came across a very strange bug. I created a minimal example:

https://gist.github.com/SuperV1234/5792381

Basically, on some machines, "test 2" segfaults; on others, it works as intended. On my desktop computer it works, both on Windows 8 x64 and Linux Mint 15 x64. On my laptop computer it segfaults, both on Windows 8 x64 and Linux Mint 15 x64.

What puzzles me is:

Is this a compiler bug? Or is there a difference between Game::test1() and the lambda body?

// Test 1 works
// Test 2 segfaults... on some machines.
// Compiled with -std=c++11, GCC 4.8.1, tested both on native Linux, Windows and Wine

#include <iostream>
#include <functional>
#include <vector>

using namespace std;

struct Command
{
    function<void()> func;
    void reset() { }
};

struct Timeline
{
    vector<Command*> commands;
    void clear() 
    {
        for(auto& c : commands) delete c;
        commands.clear();
    }
    void reset() { for(auto& c : commands) c->reset(); }
};

struct Game
{
    Timeline timeline;

    void test1() { timeline.clear(); timeline.reset(); }
    void run()
    {
        {
            cout << "Starting test 1..." << endl;

            Command* cmd{new Command};
            cmd->func = [&]{ test1(); };
            timeline.commands.push_back(cmd); cmd->func();

            cout << "Successfully ending test 1..." << endl;
        }

        {
            cout << "Starting test 2..." << endl;

            Command* cmd{new Command};
            cmd->func = [&]{ timeline.clear(); timeline.reset(); };
            timeline.commands.push_back(cmd); cmd->func();

            cout << "Successfully ending test 2..." << endl;
        }
    }
};

int main() { Game{}.run(); return 0; }

Real code (not a minimal example) is available here: https://github.com/SuperV1234/SSVOpenHexagon/commit/77784ae142768f964666afacfeed74300501ec07

Backtrace from real code: http://paste2.org/W7yeCxOO

Upvotes: 3

Views: 667

Answers (2)

rodrigo
rodrigo

Reputation: 98348

You are deleting the lambda while it is being run. I don't think it is a sane thing to do.

Your code is somewhat equivalent to this:

Command *c = new Command;
c->func = [&] { delete c; };
c->fun();

If you really need to do something like this, you may copy the function before calling it:

Command *c = new Command;
c->func = [&] { delete c; };
auto f = c->func; //copy the function
f();  //c->func is deleted, but f is not!

PS: You are aware that your clear / reset thing, as it is, makes little sense, aren't you?

Upvotes: 3

catscradle
catscradle

Reputation: 1719

If you look at the disassembly, the first lambda looks like this:

      test1();
mov         eax,dword ptr [this]  
mov         ecx,dword ptr [eax]  
call        Game::test1 (021717h)  

The first two lines get the address of the captured Game object and pass it to the Game::test1.

The second lambda looks like this:

      timeline.clear();
mov         eax,dword ptr [this]  
mov         ecx,dword ptr [eax]  
call        Timeline::clear (08415D2h)  
      timeline.reset(); 
mov         eax,dword ptr [this]  
mov         ecx,dword ptr [eax]  
call        Timeline::reset (08416D6h)  

The problem here is that after timeline.clear the lambda is destroyed and the second attempt to get the captured Game object puts some garbage in ecx. As a result of that, Timeline::reset gets called with an invalid pointer.

Edit: your lambdas are basically this:

struct lambda_1 {
    Game* game;
    void operator()() {
        game->test1();
    }
};

struct lambda_2 {
    Game* game;
    void operator()() {
        game->timeline.clear();
        game->timeline.reset();
    }
};

So what happens is that you're trying to access a member of a deleted object.

Upvotes: 4

Related Questions