14

Are there major C/C++ implementations where the longjmp function "unwinds", i.e. where it interacts with destructors for automatic-storage objects, __attribute__((__cleanup__(...))), POSIX threads cancellation handlers, etc. rather than just restoring the register context saved by setjmp? I'm particularly interested in the existence (or non-existence) of POSIX implementations with this property, but C/C++ in general are also interesting.

For the bounty, I'm looking for a POSIX-conforming or at least POSIX-like system, as opposed to Windows which has already been mentioned.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • 16
    I seriously doubt it. – Lightness Races in Orbit Aug 28 '14 at 20:30
  • That would most likely break some of the use-cases for `longjmp`. – Mats Petersson Aug 28 '14 at 20:32
  • I seriously hope there isn't any as it would break about everything longjmp is used for. – PlasmaHH Aug 28 '14 at 20:35
  • 4
    Yes, your favorite, MSVC++ implements it. – Hans Passant Aug 28 '14 at 20:38
  • Wasn't `longjmp()` 's purpose to do something like exception processing before exception did exist ? – Christophe Aug 28 '14 at 20:46
  • 1
    @HansPassant, are you sure ? Because http://msdn.microsoft.com/en-us/library/3ye15wsy.aspx says the opposite: it unwinds the stack but it doesn't call destructors ! – Christophe Aug 28 '14 at 20:48
  • 7
    @Christophe. But http://msdn.microsoft.com/en-us/library/yz2ez4as.aspx says that it does call destructors if you compiled with `/EH`. – rici Aug 28 '14 at 20:51
  • 4
    I'd recommend you to throw an exception, an catch it in place you want to stop unwinding. – GingerPlusPlus Aug 28 '14 at 20:56
  • @GingerPlusPlus: I'm not asking because I want to use such functionality (that would be utterly hideous) but because I need to know whether such implementations exist as part of a discussion of implementing other things that might be affected by it. – R.. GitHub STOP HELPING ICE Aug 28 '14 at 21:13
  • 1
    @rici Oh yes ! I just did a test, and I wondered how come that it works ! Thanks for the tip ! – Christophe Aug 28 '14 at 21:26
  • Not sure why Windows hasn't been added as an answer, since it seems to be one. I'd really like to find some POSIX[-like] examples though, so I've added a bounty and a note that I'm looking for more than just Windows for an answer to qualify for the bounty. – R.. GitHub STOP HELPING ICE Sep 03 '14 at 19:21
  • 5
    To be clear, you don't care if the behavior is legal, you just care if such an implementation exists? Because, C++11 says using `setjmp`/`longjmp` on code where unwinding would call non-trivial destructors causes undefined behavior. – jxh Sep 03 '14 at 19:36
  • 1
    @jxh: Indeed. Per the standard language, it's undefined behavior. But individual implementations might define a behavior for it. Ideally it would be nice to see one where the behavior is actually "defined" by documentation, but I'd be interested in seeing one where it's not officially defined/supported but it ends up being what happens when you invoke the undefined behavior of longjmp'ing over dtors, etc. – R.. GitHub STOP HELPING ICE Sep 03 '14 at 19:45
  • Does Interix/SUA then not count as POSIX-like? It does have GCC, but its default compiler is the Visual Studio compiler. (Actually, that should be tested: I'm not sure how much of the implementation is in the library, and the libraries are likely sufficiently different that the mere fact that it works on Win32 provides no guarantees.) –  Sep 03 '14 at 20:56
  • @hvd: Yes, Interix should count, but it's not clear to me that Interix would use the same `setjmp`/`longjmp` implementation as what's been discussed as "Windows", which is presumably the normal winapi subsystem. If you could confirm that Interix has this property, that would be a good answer. – R.. GitHub STOP HELPING ICE Sep 03 '14 at 21:31
  • Why do you ask? I guess you don't want `longjmp` to unwind, but why exactly? – Basile Starynkevitch Sep 06 '14 at 14:39
  • @BasileStarynkevitch: my guess is that it has something to do with the condition variable wait cancellation mentioned at http://www.openwall.com/lists/musl/2014/09/05/2 – ninjalj Sep 06 '14 at 15:57
  • @ninjalj: Actually not. It's related to http://austingroupbugs.net/view.php?id=863 – R.. GitHub STOP HELPING ICE Sep 06 '14 at 16:45
  • 3
    Interix could only count, if you use gcc or at least not using `/EHa`, because this feature introduces non-POSIX-compliant behaviour to your program (SEH/signals problems). So, if Interix count, then you won't have another behaviour to `setjmp`/`longjmp`, because `/EHa` will make your program incompatible with POSIX? There are always two sides of a medal. The system, that supports compliance and the program, that uses compliance. – Stefan Weiser Sep 08 '14 at 05:44
  • @StefanWeiser: Do you have a citation on how SEH breaks signals? – R.. GitHub STOP HELPING ICE Sep 08 '14 at 12:47
  • Look at the recent comment on http://stackoverflow.com/a/25701283/3781684 – Stefan Weiser Sep 08 '14 at 19:29

2 Answers2

4

I'm trying to understand the logical goals that are trying to be accomplished here.

The setjmp(3) manual page states:

setjmp() saves the stack context/environment in env for later use by longjmp(3). The stack context will be invalidated if the function which called setjmp() returns.

This says that if you return from the stack context where the setjmp() call was made, you can no longer longjmp back to it. Otherwise, undefined behavior.

Ok, So it seems to me that at the point that a valid longjmp call is made, setjmp must be somewhere in the current stack context. Therefore, a longjmp that unwinds the stack and calls all destructors of auto variables, etc, appears to be logically equivalent to throwing an exception, and catching it at the point the setjmp() call was originally made.

How does throwing and catching an exception is different from your desired setjmp/longjmp semantics? If, say, you had your wished-for setjmp/longjmp implementation, then how does replacing it with an ordinary try/throw, and catching the thrown exception, be different?

The only differences that I could see is the extra inner scope introduced by the try/catch block; while setjmp does not really open a new inner scope.

So, the answer here appears to be very easy: every compliant C++ implementation has a setjmp/longjmp equivalent that has the desired semantics. It's called try/throw/catch.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • My interest isn't whether there's something similar or "equivalent" to `longjmp` which unwinds, but whether `longjmp` does. See the discussion so far in comments. – R.. GitHub STOP HELPING ICE Sep 06 '14 at 04:20
  • No. The closest thing POSIX defines akin to unwinding is thread cancellation, and it's explicitly undefined what happens when you bypass cleanup contexts with `longjmp`. So an individual implementation could leave it undefined, or attempt to define a behavior for what happens then. And of course POSIX has nothing to say about the interaction with C++ exceptions since, from a POSIX perspective, C++ does not even exist. – R.. GitHub STOP HELPING ICE Sep 06 '14 at 13:23
0

Interix (SUA) by default does not call destructors, but in x86 mode, does have an option for it.

Taking this test program, saved as test.cc:

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

struct A {
  ~A() { puts("~A"); }
};

jmp_buf buf;

void f() {
  A a;
  longjmp(buf, 1);
}

int main() {
  if (setjmp (buf))
    return 0;

  f();
}

here's how Interix behaves. For brevity, I've omitted the required proper setup of PATH.

$ cc -mx86 test.cc && ./a.out
$ cc -mx86 -X /EHa test.cc && ./a.out
cl : Command line warning D9025 : overriding '/EHs' with '/EHa'
~A
$ cc -mamd64 test.cc && ./a.out
$ cc -mamd64 -X /EHa test.cc && ./a.out
cl : Command line warning D9025 : overriding '/EHs' with '/EHa'
$ 

The comments suggest that cc -X /EHa does not conform to POSIX, for example because /EHa would catch signals. That is not entirely true:

$ cat test.cc
#include <signal.h>
int main() {
  try {
    raise(SIGFPE);
  } catch (...) {
    // ignore
  }
}
$ cc -mx86 -X /EHa test.cc && ./a.out
cl : Command line warning D9025 : overriding '/EHs' with '/EHa'
Floating point exception (core dumped)

If I change raise(SIGFPE) to a division by zero, I do indeed see that the exception handler catches it, but neither POSIX nor C++ requires any particular behaviour for that, so that doesn't affect conformance. It is also not the case that all asynchronous signals are caught: for this program:

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void sigint(int signal) {
  puts("sigint");
  exit(0);
}
int main() {
  signal(SIGINT, sigint);
  try {
    for (;;) ;
  } catch (...) {
    // ignore
  }
}

"sigint" is printed after Ctrl-C, as expected. I don't see a reason for claiming that this implementation fails to meet POSIX requirements.

  • Based on my reading of http://msdn.microsoft.com/en-us/library/1deeycx5.aspx, it sounds like with `/EHs`, the compiler just "optimizes out" the destructor path thinking that there's no way an exception can happen, and perhaps `longjmp` is still unconditionally doing unwinding. What happens if you add something like `static volatile int x; if (x) throw(...);` just before the `longjmp`? – R.. GitHub STOP HELPING ICE Sep 06 '14 at 14:54
  • @R.. One thing I had already tried was moving the `longjmp` to a separate function, and that had no effect: the destructor would be called only if `/EHa` is passed. I've now also tried your suggestion, and that too makes no difference. –  Sep 06 '14 at 14:59
  • 1
    @R.. What's confusing is that the `/EH*` documentation *seems* to be saying the exact opposite of what it actually says. I think that what's happening, and I'm not entirely sure my understanding is correct, is that `longjmp` is implemented using SEH, and the `/EHa` option forces `f` to set up both a SEH handler and a C++ exception handler. With `/EHs`, only a C++ exception handler is set up. And on amd64, `longjmp` is not implemented using SEH. –  Sep 06 '14 at 15:04
  • Interesting. Thanks for the clarifications. – R.. GitHub STOP HELPING ICE Sep 06 '14 at 15:06
  • IMHO SEH breaks ISO C++ standard compatibility. I don't think, that `/EHa` should be taken into account, because you normally implement crossplatform by using the least common denominator. It isn't even implemented platform independent inside of Interix itself. Further it conflicts with POSIX signals, because even those will be thrown and caught. – Stefan Weiser Sep 07 '14 at 19:14
  • @StefanWeiser In what way does it fail to conform to any requirement of C++? Anyway, the point of the question, as I understand it, isn't so much that you can or should rely on cleanup functions (e.g. destructors) getting called on this particular implementation, but more that you shouldn't rely on them not getting called. –  Sep 07 '14 at 20:31
  • Your program isn't portable anymore, because it uses a compiler specific extension. This extension is implemented in a standard compliant way, but using it will break standard compliance for your code. However: SEH is only available on Microsoft products. It throws an exception on e.g. division-by-zero exceptions. POSIX-compliant programs would call a signal handler or terminate the program (POSIX-signals). So I don't even think of turning it on in case of developing crossplatform... – Stefan Weiser Sep 08 '14 at 05:37
  • @StefanWeiser All you're showing is that code that relies on platform-specific behaviour is platform-specific. Well of course. I never suggested otherwise. –  Sep 08 '14 at 05:50
  • 3
    There are always two sides of a medal. The system must support POSIX-conformity and the program has to use it. But for the bounty, R.. is searching for a POSIX-compliant implementation (1st property), that calls destructors during unwind (2nd property). Sure, Interix supports both properties, but the 2nd one is only supported, if you compile the program in a way, where the 1st property isn't supported. So I think this is problematic. BTW even Microsoft warns their users about using SEH and `/EHa` for portable programs. – Stefan Weiser Sep 08 '14 at 06:37
  • 1
    @StefanWeiser It's a fair point that if `cc -X /EHa` is not a POSIX-conforming implementation, then it doesn't answer what the OP asked. I've yet to see evidence that it mishandles any valid program: POSIX doesn't define the interaction between signals and exception handlers, because POSIX doesn't define anything relating to C++ at all. Nonetheless, I'll run some more tests when I get the chance. –  Sep 08 '14 at 07:17
  • Indeed. If @StefanWeiser is going to claim that enabling SEH breaks POSIX conformance of Interix, that claim needs to be supported. – R.. GitHub STOP HELPING ICE Sep 08 '14 at 12:49
  • 4
    I have no link, but it is easy to prove. If the compilation with `/EHa` won't break POSIX signals, then the following program would have an exit code != 0, because of an fatal error, as it does e.g. here on CentOS: http://ideone.com/HpOmzf. This is because POSIX defines, that the exit code is set to an value != 0 in case of an unhandled signal. Compiling it with `/EHa` let it return 0, because every exception is caught (the div0 exception too) as http://msdn.microsoft.com/en-us/library/1deeycx5.aspx claims. – Stefan Weiser Sep 08 '14 at 14:15
  • 2
    @StefanWeiser: That program invokes undefined behavior, so POSIX has nothing to say about what it does. If you change it to `raise(SIGFPE)`, does the same thing happen? – R.. GitHub STOP HELPING ICE Sep 08 '14 at 19:42
  • 2
    @StefanWeiser Not only is the behaviour undefined of your program as R.. points out, it wouldn't even demonstrate your point if it were defined. POSIX allows `catch(...)` to have an effect on raised signals simply because it doesn't say anything whatsoever at all about C++. (I haven't tested anything yet.) –  Sep 08 '14 at 20:07
  • 2
    @hvd, @R..: I guess, that we don't speak in the same terms of compatibility. A program is POSIX compatible IMO, if it will result in the same behaviour on every POSIX compatible system. You could exchange the SIGFPE with any other signal, that MS throws and therefore catches. BTW the behaviour is undefined as long as it is not raised by `raise` or `kill`. So I could update the program using raise... With the same result... – Stefan Weiser Sep 09 '14 at 02:28
  • Does it actually have the same result with `raise`? That's what I asked. The answer is not obvious unless you understand how Windows/Interix implements signals (which I don't). In any case, like hvd said, a C++ program can't really determine anything about POSIX conformance since POSIX does not specify the behavior of C++ programs. – R.. GitHub STOP HELPING ICE Sep 09 '14 at 02:52
  • 1
    Perhaps you could try a C program with `pthread_cleanup_push` instead of `try`/`catch`? Then it would all be in the domain that's governed by POSIX. – R.. GitHub STOP HELPING ICE Sep 09 '14 at 02:53
  • 1
    This whole discussion is ridiculous. Interix ships with a gcc to compile POSIX compatible. You cannot misuse a system, that has very different APIs (POSIX compatible and non compatible), by enforcing behaviour, that is clearly that of the non-compliant API, and then ask for POSIX compliance... – TheSlayer Sep 09 '14 at 03:20
  • @R..: Yes it has the same result with raise. POSIX defines the behaviour of an OS interface. If this interface is compatible and the program is, the program should behave equal on all compliant systems. That is why people are defining portable interfaces. BTW: If I would compile a C program, how could I enforce the C++ feature SEH? I think SEH and signals are two models to handle fatal errors among others and there should be no reason to mix both (Microsoft does suggest this too in the docs on SEH). – Stefan Weiser Sep 09 '14 at 03:29
  • 1
    @TheSlayer No, it doesn't. It ships GCC to deal with lots of non-portable code out there that cannot compile with any other compiler than GCC. It uses CL in its own `cc` and `c89` compilation scripts. As for non-compliance, please show proof already or stop claiming it's non-conforming. Merely re-asserting the same thing comment after comment after comment is not productive. Stefan Weiser has now claimed specific behaviour with `raise`, and that's testable, that's the way to convince others that they're wrong, and something I'll check when I can. (The machine isn't turned on often.) –  Sep 09 '14 at 05:44
  • 1
    @StefanWeiser FWIW though, you cannot claim that a program is POSIX-compatible if it behaves the same way on all POSIX-compatible implementations, and then conclude based on the fact that one particular implementation makes it behave differently, that that implementation isn't POSIX-compatible. It would mean that either the program or the implementation isn't POSIX-compatible, but you cannot conclude which based on merely that. You've given a good example now to check against the other requirements of POSIX, and perhaps even of pure ISO C++. –  Sep 09 '14 at 05:51
  • @hvd: Do you have any additional information I should look at as a basis for assigning a bounty? :-) – R.. GitHub STOP HELPING ICE Sep 11 '14 at 01:14
  • @R.. I may be able to run the tests based on Stefan Weiser's comments later today, but I have no additional information at this time. –  Sep 11 '14 at 05:41
  • @R.. Added a few basic tests. If there's something you'd like to see tested too, please let me know. –  Sep 11 '14 at 13:57
  • @hvd: I think you've provided good evidence that Interix has the properties I was asking about, so here's the +300. :-) I'll leave the answer unaccepted a bit longer in case any other interesting answers show up. – R.. GitHub STOP HELPING ICE Sep 11 '14 at 14:07