Search code examples
c++undefined-behaviorsetjmp

Why is this usage of setjmp/longjmp undefined behavior?


The code

#include <csetjmp>

template <typename Callable>
void create_checkpoint(std::jmp_buf buf, Callable&& callable)
{
    if (setjmp(buf) != 0)
    {
        callable();
    }
}

#include <iostream>

struct announcer {
    int id;
    announcer(int id):
        id{id}
    {
        std::cout << "created announcer with id " << id << '\n';
    }
    ~announcer() {
        std::cout << "destructing announcer with id " << id << '\n'; 
    }
};

void oopsie(std::jmp_buf buf, bool shouldJump)
{
    if (shouldJump)
    {
        // std::cout << "performing jump...\n";
        std::longjmp(buf, 1);
    }
}

void test1() 
{
    std::jmp_buf buf;
    announcer a1{1};
    create_checkpoint(buf, []() {throw std::exception();});
    oopsie(buf, true);
}

void test2()
{
    std::jmp_buf buf;
    announcer a1{1};
    create_checkpoint(buf, []() {throw std::exception();});
    oopsie(buf, false);


    announcer a2{2};
    create_checkpoint(buf, []() {throw std::exception();});
    oopsie(buf, true);
}

int main()
{
    try 
    {
        test1();
    }
    catch (...)
    {}

    try 
    {
        test2();
    }
    catch (...)
    {}
}

Context

I have to call some C library that reports errors via longjmp. To provide strong exception guarantee, I want to create a function that is very similar to std::lock_guard, e.g. I just write create_checkpoint(buf, handler) and keep calling the C library functions until I allocate more resources (destructors for objects created below setjmp line are not called, if I understand correctly).

Question

Why undefined behavior is invoked in this case and how can I fix it?

How did I find out this is undefined behavior?

Printing message to std::cout before std::longjmp vs not printing produces very different results even though that line has little to do with control flow.

What do I understand by now?

I understand that std::longjmp essentially restores the registers and jumps at the instruction pointer saved by setjmp macro. Also, functions are not optimized away, and at least during compilation, there is an instruction to call longjmp.

Converting the create_checkpoint to macro seems to solve the issue. Yet I wonder is there a better way to do this?


Solution

  • From https://en.cppreference.com/w/cpp/utility/program/longjmp

    If the function that called setjmp has exited, the behavior is undefined (in other words, only long jumps up the call stack are allowed)

    As you aren't following this rule your program has undefined behaviour