overcoder
overcoder

Reputation: 1553

Does this code contain a hidden bug?

The following code :

main.cpp :

# include <iostream>
# include <csetjmp>
# include <stdexcept>

using namespace std ;

void do_work(jmp_buf context)
{
    try
    {
        throw runtime_error("Ouch !") ;
    }
    catch(exception & e)
    {
    }

    longjmp(context, -1) ;                        //BP1
}

int main(int, char *[])
{
    jmp_buf context ;

    try
    {
        if( setjmp(context) != 0 )
        {
            throw runtime_error("Oops !") ;       //BP2
        }

        do_work(context) ;
    }
    catch(exception & e)
    {
        cout << "Caught an exception saying : " << e.what() << endl ;
    }
}

I tried debugging it but the program behaves strangely. Sometimes I could get past the first breakpoint (BP1), then crash at BP2, and sometimes control never reachs BP1, like if the program is stuck in an infinite loop. I cannot say more with my debugging skills.

This code is the minimal I could get that exhibits the strange behavior with MinGW 4.5. I also noticed that :

I'm aware of the setjmp/longjmp issues in C++ code, but I'm forced to use it to interface with some legacy C code.

My question :

Thanks for any advice.

Please retag if necessary.

Upvotes: 6

Views: 625

Answers (3)

mloskot
mloskot

Reputation: 38932

The longjmp(3) man page on Unix says:

The longjmp() routines may not be called after the routine which called the setjmp() routines returns

I think it explains explains your concern that "sometimes control never reachs BP1". I don't think "runs fine" is reliable judgement. I'd rather expect it randomly runs fine and generally is messing up with stack.

There are a few clear recommendations that should be taken into account while working mixing longjmp/setjmp with C++ exceptions in order to avoid crashes and undefined behaviour:

  • Do not use setjmp/longjmp in C++ programs.
  • If you use setjmp/longjmp functions in program where exceptions can occur, you are safe as long as they do not interact.
  • Never longjmp in or out of try clause and catch clause.
  • Never longjmp over point of initialization of automatic objects.
  • Never longjmp pasy point of destruction automatic objects, especially if destructor is non-trivial.
  • Never throw from signal handler.
  • Never invoke longjmp from nested signal handler.
  • Behaviour of longjmp from location X to location Y stays predictable and valid as long as if exception thrown at X and caught at X would have the same effect.
  • If you mix setjmp/longjmp with exceptions, do not expect portability.
  • Instead, refer to relevant details in documentation compiler you are using. For example, if you use Visual C++, read Use setjmp/longjmp

The question mentions dealing with legacy C code in programs written in C++. There has been an interesting discussion on sjlj issues in jpeg library during review of one of Boost libraries. The discussion was long but here is essence with recommended options.

Upvotes: 3

fizzer
fizzer

Reputation: 13806

C++ only makes one additional restriction on the use of longjmp():

If any automatic objects would be destroyed by a thrown exception transferring control to another (destination) point in the program, then a call to longjmp(jbuf, val) at the throw point that transfers control to the same (destination) point has undefined behavior. (18.7)

You seem to be aware of this, and your program doesn't do it. I vote compiler defect.

Upvotes: 1

Necrolis
Necrolis

Reputation: 26171

longjmp and setjmp are c functions, invoking them with C++ exception handling semantics and object destruction semantics is undefined behavior, which means its up to the implementation whether it works or not (your testing shows this, the SEH stack unwind semantics break things, depending on the type used, iirc your working ones are using dwarf2)

Upvotes: 0

Related Questions