Home > Blockchain >  setjmp / longjmp does not jump where I think it should
setjmp / longjmp does not jump where I think it should

Time:12-14

I would like to understand how setjmp / longjmp works, so I created an example program, where routineA prints even, routineB prints odd numbers and they jump to each other with longjmp:

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

#define COUNTER_BEGIN 0
#define COUNTER_END   6

void routineA( jmp_buf* pEnvA, jmp_buf* pEnvB );
void routineB( jmp_buf* pEnvA, jmp_buf* pEnvB );

int main() {
    const char message[] = "main      [ &envA=0x^6lx &envB=0x^6lx ]  -- %s\n";
    jmp_buf envA;
    jmp_buf envB;

    fprintf( stdout, message, &envA, &envB,
             "Started; Before calling routineA" );
    routineA( &envA, &envB );
    fprintf( stdout, message, &envA, &envB,
             "After routineA returned; Exiting" );

    return 0;
}

/* Prints even numbers from COUNTER_BEGIN to COUNTER_END */
void routineA( jmp_buf* pEnvA, jmp_buf* pEnvB ) {
    const char* message = "routineA: [ i=%d, sjr=%d ]  -- %s\n";
    static int i;
    static int sjr;
    static jmp_buf *s_pEnvA;
    static jmp_buf *s_pEnvB;

    s_pEnvA = pEnvA;
    s_pEnvB = pEnvB;
    fprintf( stdout, message, i, sjr, "After saving statics; Before starting the for loop" );
    for( i = COUNTER_BEGIN; i < COUNTER_END; i   ) {
        if( i % 2 == COUNTER_BEGIN   0 ) {
            fprintf( stdout, message, i, sjr, "Inside for and if; Before setjmp" );
            sjr = setjmp( *s_pEnvA );           /* Added */
            if( sjr == 0 ) {                    /* Added */
            /* if( ( sjr = setjmp( *s_pEnvA ) ) == 0 ) { */
                fprintf( stdout, message, i, sjr, "Got to this line directly - go to routineB somehow" );
                if( i == 0 ) {
                    fprintf( stdout, message, i, sjr, "This is the first iteration - call routineB as function" );
                    routineB( s_pEnvA, s_pEnvB );
                } else {
                    fprintf( stdout, message, i, sjr, "This is not the first iteration - longjmp to routineB" );
                    longjmp( *s_pEnvB, 12 );
                }
            } else {
                fprintf( stdout, message, i, sjr, "Got to this line via longjmp (in this program it must be from routineB) - continue" );
                ; /* Just continue */
            }
        }
    }
    fprintf( stdout, message, i, sjr, "After the for loop, Before leaving the function" );
}

/* Prints odd numbers from COUNTER_BEGIN to COUNTER_END */
void routineB( jmp_buf* pEnvA, jmp_buf* pEnvB ) {
    const char* message = "routineB: [ i=%d, sjr=%d ]  -- %s\n";
    static int i;
    static int sjr;
    static jmp_buf *s_pEnvA;
    static jmp_buf *s_pEnvB;

    s_pEnvA = pEnvA;
    s_pEnvB = pEnvB;
    fprintf( stdout, message, i, sjr, "After saving statics; Before starting the for loop" );
    for( i = COUNTER_BEGIN; i < COUNTER_END; i   ) {
        if( i % 2 == 1 ) {
            fprintf( stdout, message, i, sjr, "Inside for and if; Before setjmp" );
            sjr = setjmp( *s_pEnvB );           /* Added */
            if( sjr == 0 ) {                    /* Added */
            /* if( ( sjr = setjmp( *s_pEnvB ) ) == 0 ) { */
                fprintf( stdout, message, i, sjr, "Got to this line directly - longjmp to routineA" );
                longjmp( *s_pEnvA, 21 );
            } else {
                fprintf( stdout, message, i, sjr, "Got to this line via longjmp (in this program it must be from routineA) - continue" );
                ; /* Just continue */
            }
        }
    }
    fprintf( stdout, message, i, sjr, "After the for loop, Before leaving the function" );
}

I compiled it with gcc and received the following output (line numbers inserted for later reference):

01 main      [ &envA=0x^00007ffce81b0280 &envB=0x^00007ffce81b01b0 ]  -- Started; Before calling routineA
02 routineA: [ i=0, sjr=0 ]  -- After saving statics; Before starting the for loop
03 routineA: [ i=0, sjr=0 ]  -- Inside for and if; Before setjmp
04 routineA: [ i=0, sjr=0 ]  -- Got to this line directly - go to routineB somehow
05 routineA: [ i=0, sjr=0 ]  -- This is the first iteration - call routineB as function
06 routineB: [ i=0, sjr=0 ]  -- After saving statics; Before starting the for loop
07 routineB: [ i=1, sjr=0 ]  -- Inside for and if; Before setjmp
08 routineB: [ i=1, sjr=0 ]  -- Got to this line directly - longjmp to routineA
09 routineA: [ i=0, sjr=21 ]  -- Got to this line via longjmp (in this program it must be from routineB) - continue
10 routineA: [ i=2, sjr=21 ]  -- Inside for and if; Before setjmp
11 routineA: [ i=2, sjr=0 ]  -- Got to this line directly - go to routineB somehow
12 routineA: [ i=2, sjr=0 ]  -- This is not the first iteration - longjmp to routineB
13 routineA: [ i=2, sjr=21 ]  -- Got to this line via longjmp (in this program it must be from routineB) - continue
14 routineA: [ i=4, sjr=21 ]  -- Inside for and if; Before setjmp
15 routineA: [ i=4, sjr=0 ]  -- Got to this line directly - go to routineB somehow
16 routineA: [ i=4, sjr=0 ]  -- This is not the first iteration - longjmp to routineB
17 routineA: [ i=4, sjr=21 ]  -- Got to this line via longjmp (in this program it must be from routineB) - continue
18 routineA: [ i=6, sjr=21 ]  -- After the for loop, Before leaving the function
19 main      [ &envA=0x^00007ffce81b0280 &envB=0x^00007ffce81b01b0 ]  -- After routineA returned; Exiting

The output is OK until line 12: This is not the first iteration - longjmp to routineB, however, after this it should go to routineB, and print Got to this line via longjmp (in this program it must be from routineA) - continue but it stays in routineA, and says Got to this line via longjmp (in this program it must be from routineB) - continue.

Is there an error in my code or do I misunderstand something about setjmp / longjmp?

Update

Revised the code according to Eric Postpischil's answer's first part: I think now it suits the last bullet point, as an assignment statement is an expression statement. I received the same result (except for different pointer values).

Alright, I replaced sjr = setjmp( *s_pEnvA ); (and the corresponding part of routineB) with

sjr = -1;
switch( setjmp( *s_pEnvA ) ) {
case  0: sjr =  0; break;
case  1: sjr =  1; break;
case 12: sjr = 12; break;
case 21: sjr = 21; break;
default: sjr = -2; break;
}

I think this is completely in line with the bullet point "the entire controlling expression of a selection or iteration statement".

The result is still the same.

Final update

Eric and Nate are right, this is undefined behavior. But since I was wondering why the code in this answer works, I transformed my code gradually to that one, and watched at which point it breaks. I ended up with this:

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

#if defined( SORTOFWORKS )
#define CHECKPOINTFORMAT "routineX:\n  -- %s\n\n"
#elif defined( DOESNOTWORK )
#define CHECKPOINTFORMAT message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr
#else
#error Version (SORTOFWORKS or DOESNOTWORK) is not defined
#endif

#define STRINGIFY( x ) STRINGIFY_IMPLEMENTATION( x )
#define                STRINGIFY_IMPLEMENTATION( x ) #x

#define COUNTER_BEGIN 0
#define COUNTER_END   6

void routineA( jmp_buf* pEnvA, jmp_buf* pEnvB );
void routineB( jmp_buf* pEnvA, jmp_buf* pEnvB );

int main() {
    const char message[] = "main      [ &envA=0x^6lx &envB=0x^6lx ]\n  -- %s\n\n";
    jmp_buf envA;
    jmp_buf envB;

    fprintf( stdout, message, &envA, &envB,
             "Started; Before calling routineA the first time" );
    routineA( &envA, &envB );
    fprintf( stdout, message, &envA, &envB,
             "Before calling routineA the second time" );
    routineA( &envA, &envB );
    fprintf( stdout, message, &envA, &envB,
             "Exiting" );

    return 0;
}

/* Prints even numbers from COUNTER_BEGIN to COUNTER_END */
void routineA( jmp_buf* pEnvA, jmp_buf* pEnvB ) {
    const char* const message = "routineA: [ pEnvA=0x^6lx, pEnvB=0x^6lx, s_pEnvA=0x^6lx, s_pEnvB=0x^6lx, i=%d, sjr=%d ]\n"
                                "  -- %s\n\n";
    static int i;
    static jmp_buf *s_pEnvA;
    static jmp_buf *s_pEnvB;
    static int sjr;

    fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
             "Called; Before saving statics" );
    s_pEnvA = pEnvA;
    s_pEnvB = pEnvB;
    fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
             "After saving statics; Before starting the counting" );

    fprintf( stdout, CHECKPOINTFORMAT,
             "Checkpoint A0 at line " STRINGIFY( __LINE__ ) );

    i = COUNTER_BEGIN;
    fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
             "Part of counting; Before setjmp" );
    sjr = -1;
    switch( setjmp( *s_pEnvA ) ) {
    case  0: sjr =  0; break;
    case  1: sjr =  1; break;
    case 12: sjr = 12; break;
    case 21: sjr = 21; break;
    default: sjr = -2; break;
    }
    if( sjr == 0 ) {
        fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
                 "Got to this line directly - This is the first iteration - call routineB as function" );
        routineB( s_pEnvA, s_pEnvB );
    } else {
        fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
                 "Got to this line via longjmp (in this program it must be from routineB) - continue" );
        ; /* Just continue */
    }

    fprintf( stdout, CHECKPOINTFORMAT,
             "Checkpoint A1 at line " STRINGIFY( __LINE__ ) );

    i  = 2;
    fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
             "Part of counting; Before setjmp" );
    sjr = -1;
    switch( setjmp( *s_pEnvA ) ) {
    case  0: sjr =  0; break;
    case  1: sjr =  1; break;
    case 12: sjr = 12; break;
    case 21: sjr = 21; break;
    default: sjr = -2; break;
    }
    if( sjr == 0 ) {
        fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
                 "Got to this line directly - This is not the first iteration - longjmp to routineB" );
        longjmp( *s_pEnvB, 12 );
    } else {
        fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
                 "Got to this line via longjmp (in this program it must be from routineB) - continue" );
        ; /* Just continue */
    }

    fprintf( stdout, CHECKPOINTFORMAT,
             "Checkpoint A2 at line " STRINGIFY( __LINE__ ) );

    i  = 2;
    fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
             "Part of counting; Before setjmp" );
    sjr = -1;
    switch( setjmp( *s_pEnvA ) ) {
    case  0: sjr =  0; break;
    case  1: sjr =  1; break;
    case 12: sjr = 12; break;
    case 21: sjr = 21; break;
    default: sjr = -2; break;
    }
    if( sjr == 0 ) {
        fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
                 "Got to this line directly - This is not the first iteration - longjmp to routineB" );
        longjmp( *s_pEnvB, 12 );
    } else {
        fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
                 "Got to this line via longjmp (in this program it must be from routineB) - continue" );
        ; /* Just continue */
    }

    fprintf( stdout, CHECKPOINTFORMAT,
             "Checkpoint A3 at line " STRINGIFY( __LINE__ ) );

    /* Should be able to do this but it causes segfault: longjmp( *s_pEnvB, 12 ); */

    fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
             "After the counting, Before leaving the function" );
}

/* Prints odd numbers from COUNTER_BEGIN to COUNTER_END */
void routineB( jmp_buf* pEnvA, jmp_buf* pEnvB ) {
    const char* const message = "routineB: [ pEnvA=0x^6lx, pEnvB=0x^6lx, s_pEnvA=0x^6lx, s_pEnvB=0x^6lx, i=%d, sjr=%d ]\n"
                                "  -- %s\n\n";
    static int i;
    static jmp_buf *s_pEnvA;
    static jmp_buf *s_pEnvB;
    static int sjr;

    fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
             "Called; Before saving statics" );
    s_pEnvA = pEnvA;
    s_pEnvB = pEnvB;
    fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
             "After saving statics; Before starting the for loop" );

    fprintf( stdout, CHECKPOINTFORMAT,
             "Checkpoint B0 at line " STRINGIFY( __LINE__ ) );

    i = 1   COUNTER_BEGIN;
    fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
             "Part of counting; Before setjmp" );
    sjr = -1;
    switch( setjmp( *s_pEnvB ) ) {
    case  0: sjr =  0; break;
    case  1: sjr =  1; break;
    case 12: sjr = 12; break;
    case 21: sjr = 21; break;
    default: sjr = -2; break;
    }
    if( sjr == 0 ) {
        fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
                 "Got to this line directly - longjmp to routineA" );
        longjmp( *s_pEnvA, 21 );
    } else {
        fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
                 "Got to this line via longjmp (in this program it must be from routineA) - continue" );
        ; /* Just continue */
    }
    
    fprintf( stdout, CHECKPOINTFORMAT,
             "Checkpoint B1 at line " STRINGIFY( __LINE__ ) ); /* SORTOFWORKS: printed, DOESNOTWORK: missing */

    i  = 2;
    fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
             "Part of counting; Before setjmp" );
    sjr = -1;
    switch( setjmp( *s_pEnvB ) ) {
    case  0: sjr =  0; break;
    case  1: sjr =  1; break;
    case 12: sjr = 12; break;
    case 21: sjr = 21; break;
    default: sjr = -2; break;
    }
    if( sjr == 0 ) {
        fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
                 "Got to this line directly - longjmp to routineA" );
        longjmp( *s_pEnvA, 21 );
    } else {
        fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
                 "Got to this line via longjmp (in this program it must be from routineA) - continue" );
        ; /* Just continue */
    }
    
    fprintf( stdout, CHECKPOINTFORMAT,
             "Checkpoint B2 at line " STRINGIFY( __LINE__ ) ); /* SORTOFWORKS: printed, DOESNOTWORK: missing */

    i  = 2;
    fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
             "Part of counting; Before setjmp" );
    sjr = -1;
    switch( setjmp( *s_pEnvB ) ) {
    case  0: sjr =  0; break;
    case  1: sjr =  1; break;
    case 12: sjr = 12; break;
    case 21: sjr = 21; break;
    default: sjr = -2; break;
    }
    if( sjr == 0 ) {
        fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
                 "Got to this line directly - longjmp to routineA" );
        longjmp( *s_pEnvA, 21 );
    } else {
        fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
                 "Got to this line via longjmp (in this program it must be from routineA) - continue" );
        ; /* Just continue */
    }
    
    fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
             "After the for loop, Before leaving the function" );
}

If you compile the above with gcc -DSORTOFWORKS -o ./jump_sortofworks ./jump.c, then it prints the lines marked with the comment /* SORTOFWORKS: printed, DOESNOTWORK: missing */ but if you compile it with gcc -DDOESNOTWORK -o ./jump_doesnotwork ./jump.c, then it doesn't (Compiler: GNU C11 (Debian 6.3.0-18 deb9u1) version 6.3.0 20170516 (x86_64-linux-gnu)).

I think the fact that such a small change (different string format to print) breaks it, clearly demonstrates that setjmp / longjmp cannot be used for this purpose.

CodePudding user response:

First, the behavior of your calls to setjmp is not defined by the C standard because they violate the constraints in C 2018 7.13.1.1 4 and 5:

An invocation of the setjmp macro shall appear only in one of the following contexts:

— the entire controlling expression of a selection or iteration statement;

— one operand of a relational or equality operator with the other operand an integer constant expression, with the resulting expression being the entire controlling expression of a selection or iteration statement;

— the operand of a unary ! operator with the resulting expression being the entire controlling expression of a selection or iteration statement; or

— the entire expression of an expression statement (possibly cast to void).

If the invocation appears in any other context, the behavior is undefined.

For example, in if( ( sjr = setjmp( *s_pEnvA ) ) == 0 ), the setjmp invocation is not the entire controlling expression, it is not an operand of a relational or equality operator (<, <=, >, >=, ==, or !=), it is not the operand of !, and it is not the entire expression of an expression statement.

Second, longjmp can only jump up the call stack, to functions that are still executing. Once a call to a function stops executing (as when it returns), you cannot jump back into that call. Your code saves the context in routineB, then jumps to routineA (which ends execution of routineB), then attempts to jump to the saved context. But C 2018 7.13.2.1 2, about longjmp says:

… if the function containing the invocation of the setjmp macro has terminated execution in the interim,… the behavior is undefined.

  • Related