Home > Back-end >  C : will an std::runtime_error object leak in a longjmp?
C : will an std::runtime_error object leak in a longjmp?

Time:09-18

Suppose I have some C code which has a try-catch block in which the catch part will trigger a long jump:

#include <stdexcept>
#include <stdio.h>
#include <setjmp.h>

void my_fun()
{
    jmp_buf jump_buffer;
    if (setjmp(jump_buffer))
        return;
    
    try {
        std::string message;
        message.resize(100);
        snprintf(&message[0], 100, "error code %d\n", 3);
        throw std::runtime_error(message);
    }

    catch (std::runtime_error &e) {
        longjmp(jump_buffer, 1);
    }
}

Since the std::runtime_error object was allocated dynamically somewhere, will it leak the memory that was allocated for it or for the string?

CodePudding user response:

This is kind of complicated. About longjmp's validity, the standard says:

A setjmp/longjmp call pair has undefined behavior if replacing the setjmp and longjmp by catch and throw would invoke any non-trivial destructors for any objects with automatic storage duration.

runtime_error has a non-trivial destructor, so the question is whether the exception object has "automatic storage duration". It does not. This suggests that longjmp should be fine.

In addition, exception object destruction can happen in one of two places:

The points of potential destruction for the exception object are:

  • when an active handler for the exception exits by any means other than rethrowing, immediately after the destruction of the object (if any) declared in the exception-declaration in the handler;

  • when an object of type std​::​exception_­ptr that refers to the exception object is destroyed, before the destructor of std​::​exception_­ptr returns.

longjmp is not "rethrowing". So in theory, this should be fine thanks to bullet point 1.

That being said, never rely on this. I highly doubt that implementations of longjmp handle this correctly, and even if some do, it's probably not something you can expect.

CodePudding user response:

There is actually a C defect report about this specific case, http://wg21.link/cwg2361 . So right now it is up in the air what is correct.

CodePudding user response:

Why you would even want to do something like this is beyond me. But the question was intruiging. I know you can't jump into a catch block, and that jumping out of it (also goto :( ) is allowed.

It seems the exception is destructed just after leaving the catch scope. And nothing leaks. Here is the output of the test program below :

scope: normal_try_catch::try entered
exception constructed
scope : normal_try_catch::try left
scope : normal_try_catch::catch entered
scope : normal_try_catch::catch left
exception destructed
----------------------------
scope : long_jump_catch::try entered
exception constructed
scope : long_jump_catch::try left
scope : long_jump_catch::catch entered
scope : long_jump_catch::catch left
exception destructed
scope : long_jump_catch::leave because of setjmp entered
scope : long_jump_catch::leave because of setjmp left   

So the exception is cleaned up before the jump is made.

#include <stdexcept>
#include <iostream>
#include <string>
#include <setjmp.h>

class my_except :
    public std::exception
{
public:
    my_except() 
    {
        std::cout << "exception constructed" << std::endl;
    }

    ~my_except()
    {
        std::cout << "exception destructed" << std::endl;
    }
};

struct scope
{
    explicit scope(const std::string& scope) :
        m_scope{ scope }
    {
        std::cout << "scope : " << m_scope << " entered" << std::endl;
    }

    ~scope()
    {
        std::cout << "scope : " << m_scope << " left" << std::endl;
    }


private:
    std::string m_scope;
};


void normal_try_catch()
{
    try
    {
        scope s{ "normal_try_catch::try" };
        throw my_except();
    }
    catch(const std::exception&)
    {
        scope s{ "normal_try_catch::catch" };
    }
}

void long_jump_catch()
{
    jmp_buf jump_buffer;
    if (setjmp(jump_buffer))
    {
        scope s{ "long_jump_catch::leave because of setjmp" };
        return;
    }

    try
    {
        scope s{ "long_jump_catch::try" };
        throw my_except();
    }
    catch (const std::exception&)
    {
        scope s{ "long_jump_catch::catch" };
        longjmp(jump_buffer, 1);
    }
}


int main()
{
    normal_try_catch();
    std::cout << "----------------------------" << std::endl;
    long_jump_catch();
    return 0;
}
  • Related