5

What would be the right way to generate a C++ API for old C code that is extensively using longjmp with multiple jump targets for error management?

My idea was to write a function that sets jump targets for each used target, e.g.:

void catchJumps() {
    if (setjmp(target1)) throw Error1(); //Error1 and Error2 are some exception classes
    if (setjmp(target2)) throw Error2();
    //...
}

Then I'd call catchJumps in each C++-function (in each scope, to be more specific) that is using the C code:

int some_wrapper() {
    catchJumps();
    callCFunction()

    for (int i = 0; i < 1000; i++) {
        catchJumps();
        callOtherCFunction();
    }

    catchJumps();
    callOneMoreCFunction();
    callEvenOneMoreCFunction();
}

Is this a safe way to catch all the longjumps without destroying the stack? I know, that it's dangerous to longjmp into a different stack frame. Now my function catchJumps is in another stack frame than the calling some_wrapper. I'd hope (or can I even make it to) catchJumps can be inlined, so that the frame is the same, but I don't know.

The call in each scope (and after the loop above) should be necessary for all destructors of scoped objects to be called, right?

If this is not a valid method for 'converting' longjmps to assertions for the calling application, what else can we do?

urzeit
  • 2,863
  • 20
  • 36
  • 2
    "I know, that it's dangerous to longjmp into a different stack frame." That's actually the *whole point* of longjmp, and it's not particularly dangerous as long as you *unwind* to a previous frame, in other words that you don't longjmp to a function that has returned. The C++ runtime also provides an implementation of `longjmp` that correctly unwinds stack frames, so it's safe to use (even if discouraged). – zneak Dec 19 '13 at 16:06
  • 3
    That probably won't work. The jump targets are in `catchJumps`, a routine that is no longer on the stack when the long jumps occur. Macros are your best bet to "hide" long jumps. Whether that's a good idea is another question. – M Oehm Dec 19 '13 at 16:06
  • Also, can you access the library's source code? – zneak Dec 19 '13 at 16:08
  • @zneak: Yes, but it's not an option to change the longjmps in the library to something else, because it's used in different projects. – urzeit Dec 19 '13 at 16:09
  • @zneak: So it's not such a good idea to jump to the `catchJumps`-function, because it has returned. Would turning it into a macro (as M Oehm suggested) be better? – urzeit Dec 19 '13 at 16:10
  • @urzeit it would at least have the merit to not crash. – zneak Dec 19 '13 at 16:11
  • @zneak: That's the whole requirement. Is it necessary to set the jump targets after the loop? – urzeit Dec 19 '13 at 16:12
  • Unless you believe that something else might reset the jump buffers, no. – zneak Dec 19 '13 at 16:13
  • 1
    If recompiling the library is an option, I suggest you define `setjmp` to nothing and `longjmp(x)` to `throw &x`. That way, you'll be able to catch a `jmpbuf_t*` and you'll be able to check which one it is with a simple equality check, with minimal disruption to the source code. – zneak Dec 19 '13 at 16:14
  • In C, the typical way to handle various return values from jong jumps is to write a `switch` statement. You could put this in a macro and mimick a function call like `catchJumps()`, at the cost of repeating the code. (That's probably the same as inlining it. I'm more familiar with C than with C++, so that's why i thought of maros. There are probably better ways to do this in C++, but I don't know them.) – M Oehm Dec 19 '13 at 16:16

2 Answers2

3

You might have problems when using catchJumps with automatic objects that have destructors as explained in https://stackoverflow.com/a/1376099/471164 and citing 18.7/4 "Other runtime support":

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.

I think a better approach is to create a wrapper for each C function that you use and that can do longjmp translating all these non-local gotos into exceptions. This will also make your code cleaner because you won't have catchJumps() all over the place but only in these wrapper functions.

Community
  • 1
  • 1
vitaut
  • 49,672
  • 25
  • 199
  • 336
  • Thank you for your answer, this might perhaps be the most clean solution. I just spare the effort to create thousands of wrappers :-/ – urzeit Dec 20 '13 at 06:55
  • 1
    Yes, I also like this solution. @urzeit: Surely you are not going to call all functions of the old API? You'll just have to create wrappers as needed. – M Oehm Dec 20 '13 at 11:01
2

Since you're stuck with such an API in the library, what about having catchJumps do the actual call by requiring a zero-parameter callable to be passed in and using a function pointer or boost/std::function?

template <typename CallMe>
void catchJumps(CallMe wrappee)
{
    if (setjmp(target1)) throw Error1(); //Error1 and Error2 are some exception classes
    if (setjmp(target2)) throw Error2();
    //...

    wrappee();
}

int some_wrapper()
{
    catchJumps(&callCFunction);

    for (int i = 0; i < 1000; i++)
    {
        catchJumps(&callOtherCFunction);
    }

    catchJumps(&callOneMoreCFunction);
    catchJumps(&callEvenOneMoreCFunction);
}
Mark B
  • 95,107
  • 10
  • 109
  • 188