1265. longjmp and destructors

Section: 21.11 [support.runtime] Status: NAD Submitter: Sean Hunt Opened: 2009-11-16 Last modified: 2016-02-10

Priority: Not Prioritized

View all other issues in [support.runtime].

View all issues with NAD status.

Discussion:

21.11 [support.runtime]/4 says that longjmp is undefined if unwinding by the mechanism used by catch and throw would invoke any nontrivial destructors. However, the text as written is rather vague, in particular when dealing with catch(...):

void foo() {
  jump_buf buf;
  non_trivial_dtor n1; // 1
  if (!setjmp(buf)) {
    non_trivial_dtor n2; // 2
    try {
      longjmp(buf, 1);
    } catch (...) {
    }
  }
}

My interpretation of the meaning of 21.11 [support.runtime]/4 is that declaration 2, but not 1, would cause the longjmp to be undefined behavior. However, it's not entirely clear from the text. Arguably, replacing the setjmp and longjmp with catch would still cause the destructor for n1 to be called after the unwinding, which would lead to undefined behavior. This is clearly not an intended consequence of the wording. However, it is probably still UB, as n1 now has "indeterminate" value, and running its destructor on foo's exit will cause Bad Things.

Declarations 2 has a more interesting issue. The catch(...) muddles up the definition that uses throw and catch - if longjmp() were indeed a throw, control would never return to the setjmp. As such, n2's destructor wouldn't be called (except by the argument for n1, which is that the destructor would be called later as the frame was left in the normal control flow).

I suggest that paragraph 4 of 21.11 [support.runtime] should be replaced with the following, or something that reads better but has the same effect:

The function signature longjmp(jmp_buf jbuf, int val) has more restricted behavior in this International Standard. A call to longjmp has undefined behavior if any non-trivial destructors would be called were the longjmp call replaced with a throw-expression whose nearest matching handler were a (possibly imaginary) function-try-block on the function containing the corresponding setjmp call.

[ 2009-11-17 Moved to Tentatively NAD after 5 positive votes on c++std-lib. Rationale added below. ]

Proposed resolution:

Change 21.11 [support.runtime]/4:

The function signature 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. A call to longjmp has undefined behavior if any non-trivial destructors would be called were the longjmp call replaced with a throw-expression whose nearest matching handler were a (possibly imaginary) function-try-block on the function containing the corresponding setjmp call.

Rationale:

In the given example, it is clear that it is only n2 and not n1 that is destroyed by the longjmp.

At this late stage in the standards process, we are focusing on issues that impact users or implementers. Trying to rewrite complex wording just for the sake of improved clarity is likely to do more harm than good.