3

My project is written in C++, and it makes use of dynamically generated code to glue some things together (using Fabrice Bellard's TCC and a bit of manually generated assembly thunks). Dynamically generated code sometimes jumps into "runtime helpers" implemented in C++ and back.

There's a feature that allows to abort dynamically generated code altogether, wherever it is, jumping back to C++ (the caller). To achieve that, I'm simply using C++ exceptions: a runtime helper (posing as a C function) simply throws a C++ exception, and it propagates through generated functions back to C++. I'm using SJLJ and everything works OK so far, but I don't want to depend on a particilar implementation (I read that that's safe only with SJLJ).

Other than the aborting scheme above, my C++ code uses exceptions mostly in critical situations, it's not used for general purpose control flow. However, I rely on RAII to automatically destruct objects on stack.

My question is: Is it theoretically and practically safe to use longjmp/setjmp instead, provided setjmp is set right before calling a dynamically generated function, and provided that longjmp never propagates through C++ functions that rely on RAII (I must make sure that none of runtime helpers implemented in C++ make use of it) and always lands in setjmp (set right before the function)?

Or C++ is so fragile that even this isn't guaranteed to work well and will corrupt something? Or maybe C++ breaks only if actual exceptions are thrown? What if the exceptions are thrown locally and caught immediatelly (in runtime helpers called by generated assembly), is it safe? Or maybe just because there are a few foreign frames up the stack, it will refuse to work?

EG:

jmp_buf buf; // thread-local
char* msg;   // thread-local

// ... some C++ code here, potentially some RAII thingy

GeneratedFunc func = (GeneratedFunc)compile_stuff();
if (!setjmp(buf)) {
    // somewhere deep inside, it calls longjmp to jump back to the top function in case a problem happens
    func();
} else {
    printf("error: %s\n", msg);
    // do something about the error here
}

// some other C++ code
Peter M
  • 7,309
  • 3
  • 50
  • 91
carsten
  • 163
  • 6

1 Answers1

1

Is it theoretically and practically safe to use longjmp/setjmp instead, provided setjmp is set right before calling a dynamically generated function, and provided that longjmp never propagates through C++ functions that rely on RAII (I must make sure that none of runtime helpers implemented in C++ make use of it) and always lands in setjmp (set right before the function)?

The Standard's 18.10/4 says:

...longjmp(jmp_buf jbuf, int val) has more restricted behavior in this International Standard. 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 automatic objects.

So, it's not just RAII, but any stack-hosted objected with non-trivial destructors (i.e. "resources" might be acquired after construction but still need to be released during destruction, or there could be side effects of destruction other than resource release, such as logging).

Or C++ is so fragile that even this isn't guaranteed to work well and will corrupt something?

It should work subject to the caveat above about trivial destructors (which is quite a big restriction).

Or maybe C++ breaks only if actual exceptions are thrown? What if the exceptions are thrown locally and caught immediatelly (in runtime helpers called by generated assembly), is it safe?

That's unrelated to the setjmp / longjmp behaviour. If you throw and catch inside normal C++ compiler generated code there shouldn't be any residual/consequent issues with execution (re)entering the dynamically generated code. The same approach is used for wrapped C++ libraries called to/from C; exceptions may be caught on the boundaries of a C++ library and converted to error codes that C can handle.

Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
  • So, it means that setjmp/longjmp are only unsafe if destructors somewhere in between them are ignored? I was concerned about situations when destructors are present in the same function as setjmp, and somehow it would corrupt things, because who knows what kind of magic is used to implement exceptions. – carsten Feb 12 '15 at 03:53
  • P.S. By "destructors" I also mean the whole potential stack unwinding thing (native internal code emitted to support it) – carsten Feb 12 '15 at 04:00
  • @carsten *"it means setjmp/longjmp are only unsafe if destructors somewhere in between them are ignored?"* - not what it means... the destructors will *definitely* be "ignored" if by that you mean "not executed" during the jump, so the danger is that when they're ignored necessary destruction actions aren't executed, and the Standard's operational guarantee is pretty weak: the behaviour is undefined if all the destructors in question aren't trivial. See [here](http://stackoverflow.com/questions/8190879/what-is-a-non-trivial-destructor-in-c) for an explanation of what that means. – Tony Delroy Feb 12 '15 at 04:26
  • *"I was concerned about situations when destructors are present in the same function as setjmp, and somehow it would corrupt things, because who knows what kind of magic is used to implement exceptions"* - that shouldn't be a concern: it's up to the compiler to make sure that if/when you jump back there, the local scopes' destructors run properly. It's what's missed during the jump - not after the jump - that's a potential concern. – Tony Delroy Feb 12 '15 at 04:28
  • *"P.S. By "destructors" I also mean the whole potential stack unwinding thing (native internal code emitted to support it)"* - if the stack state is valid when you do the longjmp, and the stack state is still valid (i.e. your dynamically generated code hasn't corrupted it) when you setjmp, then *all* stack unwinding actions will be taken care of. – Tony Delroy Feb 12 '15 at 04:32