Previous: Memory leaks, Up: Working around library misfeatures


3.9.3.3 Suicidal exception handling

An inconvenient characteristic of some external library functions is to terminate the program rather than returning an error status to the caller for routine events such as a failure of memory allocation. Although in many cases there is no simple workaround for this behavior, memory allocation failures at least can be detected and preventive action taken by using the functions described in this section.

The general approach is to use memory management functions from mwrap.h as described previously (Memory leaks), while additionally registering a return destination for a non-local jump to be taken in the event of a memory overflow. The jump is taken when an external library function calls malloc or realloc unsuccessfully. The jump avoids passing control back to the library function, thereby denying it the opportunity to abort, but restores the context to that of the jump destination almost as if the library function and all of its intervening callers had returned normally.

The interface is similar to that of the standard setjmp function defined in the system header file setjmp.h, and in fact is built on it, but differs in that the client module does not explicitly refer to jump buffers. Instead, the mwrap module internally maintains a stack of return destinations.

If a jump is taken, it always goes to the most recently registered destination. It may revert to the previously registered destination only when the current one is cleared. This organization provides the necessary flexibility for multiple clients and recursion, but it necessitates a protocol whereby each registration of a destination must be explicitly cleared exactly once.

The following functions implement these two features.

— Function: int avm_setjmp ()

This function specifies the point to which control will pass by a non-local jump if there is insufficient memory to complete a subsequent malloc or realloc operation. Only the operations that take place while memory is being managed due to avm_manage_memory are affected (Memory leaks).

The function returns zero when it is called normally and successfully registers the return point.

It returns a non-zero value when it has been entered by a non-local jump (i.e., when malloc or realloc has reported insufficient memory while memory management is active), or when the return point could not be successfully registered due to insufficient memory. The client need not distinguish between these two cases, because both correspond to memory overflows and the destination must be cleared by avm_clearjmp regardless.

When a non-zero value is returned due to this function being reached by a non-local jump, it has the side effects of reclaiming all managed memory by calling avm_free_managed_memory and disabling memory management by calling avm_dont_manage_memory.

— Function: void avm_clearjmp ()

This function cancels the effect of avm_setjmp () by preventing further non-local jumps to its destination if the destination was successfully registered, or by acknowledging unsuccessful registration otherwise. It should be called before exiting any function that calls avm_setjmp () or anomalous results may ensue.

The memory management functions avm_manage_memory and avm_dont_manage_memory can be useful with or without avm_setjmp, depending on how much of a workaround is needed for a given library. If a library does not abort on memory overflows, there is no need to use avm_setjmp, while it may still be appropriate to use the other functions against memory leaks.

Calling avm_clearjmp is particularly important if a client module with memory management that doesn't use avm_setjmp is invoked subsequently to one that does, so that memory overflows in the latter won't cause an attempted jump to a stale destination.

A further complication that arises from careful consideration of these issues is the situation of a client module that does not intend to use avm_setjmp but is called (perhaps indirectly) by one that does. The latter will have registered a return destination that remains active and valid even if the former refrains from doing so, thereby allowing a branch to be taken that should have been prevented. Although it is an unusual situation, it can be accommodated by the following function.

— Function: void avm_setnonjump ()

This function temporarily inhibits non-local jumps to destinations previously registered by avm_setjmp until the next time avm_clearjmp is called. Thereafter, any previously registered destinations are reinstated.

A sketch of how some of these functions might be used to cope with library functions that would otherwise terminate the program in the event of a memory overflow is shown below. The GNU libc reference manual contains a related discussion of non-local jumps.

     #include <avm/mwrap.h>
     ...
     
     int
     function foobar (foo, bar)
     ...
     {
     char *my_data;
     
       my_data = (char *) malloc (100);
       if (avm_setjmp () != 0)
         {
           avm_clearjmp ();
           avm_turn_on_stdout ();       /* reaching here */
           free (my_data);              /* means malloc  */
           return ABNORMAL_STATUS;      /* failed below  */
         }
       avm_turn_off_stdout ();
       avm_manage_memory ();
       ...
       call_library_functions (foo, bar);    /* may jump */
       ...                                   /* to above */
       avm_free_managed_memory ();
       avm_turn_on_stdout ();
       avm_clearjmp ();
       free (my_data);            /* reaching here means */
       return OK_STATUS;          /* jumping wasn't done */
     }

Portability issues with these functions are not well known at this writing. If the configuration script for avram fails to detect the required features in setjmp.h on the host system, conditional compilation directives will disable the functions avm_setjmp, avm_clearjmp, and avm_setnonjmp. However, it may still be possible for the other avm_* memory management functions to be configured.

If setjmp is not configured, the avm_setjmp function is still callable but will always return a value of zero, and will provide no protection against external library functions aborting the program. The other two will perform no operation and return.