setjmp/longjmp in C++: Is this undefined behaviour?

143 Views Asked by At

Does the following code invoke undefined behaviour:

#include <iostream>
#include <csetjmp>

jmp_buf buffer1;
jmp_buf buffer2;

class Test
{
    public:
        ~Test()
        {
            std::cout << "Test destructor: " << this << std::endl;
        }
};

void p2()
{
    longjmp( buffer2, 1 );
}

void p1()
{
    {
        Test test;

        if ( setjmp( buffer2 ) == 0 )
        {
            p2();
        }
    }

    std::cout << "Jumping" << std::endl;
    longjmp( buffer1, 1 );
}

int main()
{
   if ( setjmp( buffer1 ) == 0 )
   {
       p1();
   }

   return 0;
}

With the intel compiler on windows test's destructor gets called twice. I logged this as a bug, however the intel developers claim this is undefined behaviour. You can see our discussion here. Every other compiler I tried did what I expected (aka called the destructor once).

The C++ standard says:

If replacing std::longjmp with throw and setjmp with catch would invoke a non-trivial destructor for any automatic object, the behavior of such std::longjmp is undefined.

However it doesn't specify where the try should go and so if test is declared within the try block (leading to undefined behaviour) or before the try (which should not be UB).

My argument is that nothing before the setjmp could longjmp to its location, so logically the try should start at the setjmp. However that is not explicitly part of what the standard says, so why couldn't it be extended to include more than necessary?

1

There are 1 best solutions below

8
Jerry Coffin On

I believe the code modified as the standard discusses comes out like this (I've left the setjmp and longjmp in place as comments to make it clear what I've changed). I have added one extra annotation, so we can see when each exception is thrown.

#include <iostream>
#include <csetjmp>

// jmp_buf buffer1;
// jmp_buf buffer2;

class Test
{
    public:
        ~Test()
        {
            std::cout << "Test destructor: " << this << std::endl;
        }
};

void p2()
{
    std::cerr << "Jumping from p2\n";
    throw 1;
    // longjmp( buffer2, 1 );
}

void p1()
{
    {
        Test test;

        try
        {
            p2();
        }
        catch(...) {
            std::cerr << "caught in p1\n";
        }
    }

    std::cout << "Jumping" << std::endl;
    // longjmp( buffer1, 1 );
    throw 1;
}

int main()
{
   try
   {
       p1();
   }
   catch (...) {
    std::cerr << "caught in main\n";
   }

   return 0;
}

When we run this, we get:

Jumping from p2
caught in p1
Test destructor: 0x7ffdff2c9a3f
Jumping
caught in main

The dtor is not invoked in the process of handling an exception, so the original code has defined behavior.