LAL  7.1.7.1-56c71ae
Header LALStatusMacros.h

Detailed Description

Provides macros for handling the LALStatus structure.

Author
Creighton, J. D. E. and Creighton, T. D.

Synopsis

#include <lal/LALStatusMacros.h>

This header provides macros and functions for tracking and reporting the runtime status of a program. The intent is simultaneously to standardize the error reporting, and to make the reporting as transparent as possible to people coding individual routines.

Status-reporting objects

LAL routines make use of two objects in reporting their current status: the status structure LALStatus, and the global integer lalDebugLevel. These two objects are described in the following sections.

The LALStatus structure

The LALStatus structure is the means by which LAL functions report their success or failure; it provides a useful mechanism for tracking progress and errors through nested function calls. The error reporting structure is a linked list of LALStatus structures, with each node corresponding to a given function in the current calling sequence. When a function terminates successfully, its node is dropped from the list. If a function encounters an error, it must still return control to the calling routine, reporting the error through its LALStatus. The calling routine must either deal with the error (pruning the linked list if it succeeds), or else return an error itself. A fatal error will thus return a linked list of LALStatus structures to the top-level routine, where the tail of the list identifies the source of the error, and the intermediate nodes identify the sequence of nested function calls that led to the error.

In almost all circumstances the programmer will not have to access this structure directly, relying instead on the macros defined in this header. The exception is the statusCode field, which the programmer may want to query directly.

The LALStatus::statusCode field is set to a nonzero value any time an error condition arises that would lead to abnormal termination of the current function. Programmers can assign positive error codes to the various types of error that may be encountered in their routines.

Additionally, the following following status codes are reserved to report certain standard conditions:

CodeMessageExplanation
0Nominal execution; the function returned successfully.
-1Recursive errorThe function aborted due to failure of a subroutine.
-2INITSTATUS: non-null status pointerThe status structure passed to the function had a non-NULL statusPtr field, which blocks the function from calling subroutines (it is symptomatic of something screwy going on in the calling routine).
-4ATTATCHSTATUSPTR: memory allocation errorThe function was unable to allocate a statusPtr field to pass down to a subroutine.
-8DETATCHSTATUSPTR: null status pointerThe statusPtr field could not be deallocated at the end of all subroutine calls; one of the subroutines must have lost it or set it to NULL.
-16INITSTATUS: non-zero xlalErrnoThe xlalError variable is non-zero, which suggests that an error in an XLAL routine has occured and has not been handled.
-16RETURN: untrapped XLAL error codeThe xlalError variable is non-zero, which indicates that an error in an XLAL routine has occured and has not been handled.

The lalDebugLevel

The lalDebugLevel is a global variable, set at runtime, that determines how much and what kind of debugging information will be reported. It is declared as an extern int in the header LALStatusMacros.h, and is therefore accessible in any standard LAL module that includes this header. Note, however, that it is declared to be of the C type int, which is usually but not always a 32-bit integer (on some systems it may only be 16 bits).

The value of lalDebugLevel should be thought of not as a number, but as a bit mask, wherein each bit in the binary representation turns on or off a specific type of status reporting. At present, there are five types of status reporting, each associated with a bit in lalDebugLevel.

Error messages
tell the operator that a computation has terminated abnormally, and has failed to produce an acceptable result. Normally this is associated with assigning a non-zero statusCode; an error message is printed automatically whenever a function exits with non-zero statusCode.
Warning messages
tell the user that a computation is working, but with unusual behaviour that might indicate an unreliable or meaningless result. Warnings do not normally result in a non-zero statusCode.
Information messages
tell the operator that the computation is proceeding as expected, and simply provide additional information about its progress.
Tracing messages
are printed automatically a subroutine is called or returned; they simply track the current sequence of function calls.
Memory information messages
are a special type of information message; they tell the operator when and how much memory is allocated or freed from the memory heap.

The module LALError.c defines functions for printing each of these types of status message. Each type of message is turned on by setting the corrsponding bit in lalDebugLevel to 1, and is suppressed by setting the bit to 0. This header file #defines flags with numerical values designed to switch on the appropriate bits. Combinations of bits can be switched on by combining these flags using the bitwise-or operator, |.

The most significant bit of lalDebugLevel has a special meaning in that it is not associated with any type of status message. However, certain pieces of debugging or error-tracking code - such as the memory leak detection code in LALMalloc.c - do not write status messages and are not associated with a lalDebugLevel bit; instead, these pieces of code are turned on for any nonzero value of lalDebugLevel, unless the LALMEMDBG bit is set. Switching on only the most significant bit with LALMEMDBG activates this code without turning on any other error reporting.

To turn debugging code on or off at compile time (rather than runtime), see this the section Compilation flags below.

Using the status tools

The following summarizes everything the common programmer needs to know in order to follow LAL standard error reporting. It can be treated as a primer on LAL coding conventions.

LAL function calls

All LAL functions should have return type void. The first argument of any function should be a pointer to a structure of type LALStatus. Thus:

void MyFunction( LALStatus *stat, ... )

Since the function has no return code, it must report all errors or failure through the status structure. A function that is passed a NULL pointer in place of the status pointer should terminate the program with a SIGABRT signal, as this is its only way to report the error. However, this is one of the few circumstances under which a function sould deliberately raise a signal. In all other cases the error should be trapped, reported in the status structure, and control returned to the calling routine.

Initializing the status structure

The first instruction in any function, after variable declarations, should be the macro INITSTATUS(), which takes three arguments: the function's status pointer, the function name (a string literal) and the module's RCS $Id$ string.

INITSTATUS( stat, "MyFunction", MYFILEC );

This macro checks that a valid status pointer has been passed to the function, and if so, initializes the other fields to indicate (by default) nominal execution. If stat is null, the macro causes the program to terminate with a SIGABRT signal, as described above.

Normal return from a function

Upon completion, the function should issue the macro RETURN(), which takes one argument: the function's status pointer.

RETURN( stat );

This takes the place of any return statements. If stat->statusCode is non-zero, the macro calls LALError() (see LALError.c) to log stat->statusDescription and other information, depending on implementation and the value of lalDebugLevel. Typically RETURN() is used only for successful completion, with other macros ABORT(), ASSERT(), CHECKSTATUSPTR(), and TRY() being used to report failure. However, it is possible for the programmer to assign the fields of *stat by hand, and then issue RETURN().

Abnormal return from a function

The standard method to terminate a function unsuccessfully is with the ABORT() macro, which takes three arguments: the status pointer, the status code, and the status description string. Normally the various error codes and descriptions will be constants defined in the function's header file MyHeader.h:

ABORT( stat, MYHEADERH_EMYERR, MYHEADERH_MSGEMYERR );

where the error code MYHEADERH_EMYERR and the error message MYHEADERH_MSGEMYERR are defined in MyHeader.h. This standard LAL naming convention for error messages prevents namespace conflicts between different header files. Like RETURN(), ABORT() correctly handles any status logging required by the implementation and the lalDebugLevel. Note that ABORT() does not raise a SIGABRT signal, but instead returns control to the calling routine.

Error checking within a function

Another way to indicate an unsuccessful termination is with the macro ASSERT(), which takes as arguments a test statement, a status pointer, a status code, and a status description. The statement ASSERT( assertion, ... ); is in all ways equivalent to the statement if ( !assertion ) ABORT( ... );, except on a failure the ASSERT() macro will also report the failed assertion. In the above example, one might have:

ASSERT( assertion, stat, MYHEADERH_EMYERR, MYHEADERH_MSGEMYERR );

One subtle but important point is that the ASSERT() should be used only to trap coding errors, rather than runtime errors, which would be trapped using ABORT(). In other words, the assertion should always test true in the final debugged program. This is vital because certain compilation flags will remove all ASSERT() macros at compile time, in order to speed execution of the final code. See the section Compilation flags below.

Programmers should also be aware that using ASSERT() to exit a function in normal runtime can have serious side effects. For example, it is an error to allocate dynamic memory to local variables in a function and then fail to free it before returning. Thus, if you have dynamically allocated memory, you cannot then use ASSERT() for runtime error checking, as this does not permit you to free the memory before returning. Instead, you must explicitly check the assertion, and, if it fails, free the memory and call ABORT().

Calling subroutines

If the function is to call other LAL functions as subroutines, four more macros are used to report possible errors arising in these routines. The macros are ATTATCHSTATUSPTR(), DETATCHSTATUSPTR(), CHECKSTATUSPTR(), and TRY(). The usage of these macros is as follows.

  1. First, before any subroutines are called, the function must call the macro ATTATCHSTATUSPTR() which takes as its argument the status pointer of the current function:

    This allocates stat->statusPtr, which is the status pointer that will be handed down into any and all subroutines. If the pointer has already been allocated, ATTATCHSTATUSPTR() will raise a SIGABRT, as this is symptomatic of a coding error.

    In most cases ATTATCHSTATUSPTR() need only be called once in a given function, immediately after INITSTATUS(), no matter how many subroutine calls that function makes. The exception is if the function deals with (or ignores) errors reported by its subroutines. In that case, the function should detatch the status pointer using DETATCHSTATUSPTR() (below), and then re-attatch it.

    The macro ATTATCHSTATUSPTR() sets the status code to be \(-1\) and the status message to be "Recursive error". These flags are unset when DETATCHSTATUSPTR() (below) is called. This is so that a use of RETURN() prior to detatching the status pointer will yield an error.

  2. When a subroutine is called, it should be handed the statusPtr field of the calling function's status structure, to report its own errors. The calling function should test the returned status code, and either attempt to deal with any abnormal returns, or abort with status code \(-1\). The macro CHECKSTATUSPTR() simplifies the latter case. It takes one arguments: the status pointer of the current function (not the subroutine).

    MySubroutine( stat->statusPtr, ... );

    The TRY() macro is a somewhat more streamlined approach but with equivalent results. It takes two arguments. The first is the subroutine call, and the second is the status pointer. Thus:

    TRY( MySubroutine( stat->statusPtr, ... ), stat );

    The only practical difference between these two approaches is that TRY() also reports the name of the failed subroutine call when logging errors.

    Similar caveats apply when using CHECKSTATUSPTR() and TRY() as when using ASSERT(), in that these macros can force an immediate return with no additional housekeeping instructions. For instance, if you have dynamically-allocated local memory, you should explicitly check the statusPtr->statusCode field to see if a subroutine failed, then free the memory and call ABORT() to exit.

    If the calling routine attempts to work around an error reported from a subroutine, and the attempt fails, the routine should not use CHECKSTATUSPTR() to exit with status code \(-1\). Instead, it should call ABORT() with an appropriate (positive) code and message to indicate how the attempted workaround failed.

  3. After all subroutines have been called, but before any RETURN() statement, the function must call the DETATCHSTATUSPTR() macro, with the status pointer of the current function (not the subroutines) as its argument:

    This simply deallocates stat->statusPtr (and any subsequent structures in the list), and sets it to NULL. It is an error to exit the function with non-NULL statusPtr, unless the exit was due to a subroutine failure. ABORT() and ASSERT() check for this automatically; the only place you normally need to call DETATCHSTATUSPTR() is immediately before RETURN(). This macro also sets the status code and the status message to nominal values.

    Additionally, if a function successfully works around an error reported by a subroutine, it should call DETATCHSTATUSPTR() and ATTATCHSTATUSPTR() to create a fresh status pointer before calling another subroutine.

Cleaning up after subroutine failure

Although they are convenient, the TRY() and CHECKSTATUSPTR() macros have a serious drawback in that they may cause the calling function to return immediately. If the calling function had previously allocated any local memory storage, this memory will be cast adrift, with no means of accessing or subsequently freeing it (short of terminating the runtime process). Such a memory leak is a violation of the LAL function standard.

The macros BEGINFAIL() and ENDFAIL() allow a function to test the return code of a subroutine, and, if that indicates a failure, to execute one or more "cleanup" instructions before itself returning. Each macro takes a single argument: the current function's status pointer. The macros must occur in matched pairs, and use the same syntax as a do ... while statement: they either span a single instruction, or a block of instructions enclosed in braces.

For example, if a function had allocated memory to some pointer localPointer, any subsequent call to a subroutine LALSubroutine() would take the following form:

LALSubroutine( stat->statusPtr, ... );
BEGINFAIL( stat )
LALFree( localPointer );
ENDFAIL( stat );

For another example, if a function had to create three vectors *vector1, *vector2, *vector3, the allocation would look something like this:

TRY( LALSCreateVector( stat->statusPtr, &vector1, 100 ), stat );
LALSCreateVector( stat->statusPtr, &vector2, 100 );
BEGINFAIL( stat )
TRY( LALSDestroyVector( stat->statusPtr, &vector1 ), stat );
ENDFAIL( stat );
LALSCreateVector( stat->statusPtr, &vector3, 100 );
BEGINFAIL( stat ) {
TRY( LALSDestroyVector( stat->statusPtr, &vector1 ), stat );
TRY( LALSDestroyVector( stat->statusPtr, &vector2 ), stat );
} ENDFAIL( stat );

As indicated above, the cleanup instructions can include calls to other LAL routines. The BEGINFAIL( stat ) macro call first checks stat->statusPtr to see if a subroutine error has occured. If it has, the macro detaches and saves that pointer, then attaches a new stat->statusPtr to be used in calls to the cleanup routines. After the cleanup instructions have been executed, the ENDFAIL( stat ) macro call reattaches the saved status pointer and returns with a subroutine error code. In this way, the returned status list indicates where the original failure occurred, rather than giving an uninformative report from the last cleanup routine.

Of course a second failure in one of the cleanup routines can cause serious problems. If the routine was called using a TRY() macro, it will force an immediate return from the calling function, with a status code and status list indicating how the cleanp routine failed. The original status list saved by BEGINFAIL() is lost. While this loss does constitute a memory leak, the failure of a cleanup routine in itself indicates that there are serious problems with the memory management.

It is possible to nest BEGINFAIL() ... ENDFAIL(); blocks, but this is unlikely to serve any useful purpose. Once cleanup routines start to fail, it is probably beyond the scope of the LAL function to deal with the resulting memory leaks.

Issuing status messages

The module LALError.c defines the functions LALError(), LALWarning(), LALInfo(), and LALTrace() to issue various types of status message. This is the preferred means of printing status messages, since each type of message can be activated or suppressed by setting lalDebugLevel appropriately. In fact, LALError() and LALTrace() are called automatically by the status macros whenever they are required, so most LAL modules will explicitly invoke only the LALWarning() and LALInfo() functions.

LALStatusMacros.h provides a macro, REPORTSTATUS(), which is used to report the current state of the LALStatus list. It takes a status pointer as its argument:

REPORTSTATUS( stat );

This macro iteratively prints the contents of stat and all subsequent structures in the list to the error log.

The action of REPORTSTATUS() is not suppressed by any value of lalDebugLevel. Therefore, as a rule, it should only be called by test programs, not by LAL routines intended for use in production code.

Setting the initial LALStatus structure and global lalDebugLevel

As mentioned above, any module including LALStatusMacros.h includes the global variable lalDebugLevel as an extern int. At least one module in the final executable most cases lalDebugLevel will be declared in the module containing the main() function, and will be assigned a value on declaration or from command-line arguments to main(). Alternatively, if the LAL functions are to be embedded in a non-LAL program, lalDebugLevel can be declared and set in the topmost module that calls LAL functions.

A LALStatus structure should also be declared as a local variable in the main() function of a LAL program, or in the topmost function calling LAL functions withing a non-LAL program, to pass in its LAL function calls. The structure must be empty (all fields set to zero) before being passed into a function. The LALStatus structure need only be declared and initialized once, no matter how many LAL functions are called.

Thus a typical LAL program might look something like the following:

int main( int argc, char **argv )
{
static LALStatus stat;
MyFunction( &stat );
REPORTSTATUS( &stat );
return stat.statusCode;
}

Please note that all status macros described above can force a return from the calling routine. This is a Bad Thing if the calling routine is main(), since main() must normally return int rather than void. It is therefore recommended that none of these macros except REPORTSTATUS() be used at the top level.

Non-confomant functions

These standards apply only to functions that will be publicly available in the LAL libraries. Within a module, a programmer may define and use subroutines that do not conform to the LAL function standards, provided these routines are only visible within that module. Such functions should be declared as static to ensure this. A publicly-visible non-conformant function requires special dispensation.

Compilation flags

LAL provides two flags that can be used to exclude or modify debugging code at compile time. Although these flags are typically #defined or #undefined globally and can affect many modules (notably modules in the support package), their primary effect is on the debugging and status-reporting tools defined in this header. The two flags are named NDEBUG and NOLALMACROS.

The NDEBUG flag

Setting the NDEBUG flag turns off debugging and error-reporting code, in order to get condensed production-line programs. As far as error reporting is concerned, setting the NDEBUG flag at compile time is similar to setting lalDebugLevel equal to zero at runtime, in that it suppresses all status messages and memory leak detection. However, the NDEBUG flag accoplishes this by telling the compiler preprocessor to remove the relevant code from the object file, thus eliminating frequent and unnecessary tests on lalDebugLevel. When debugging is turned off, the global integer variable lalNoDebug is non-zero; otherwise it is zero.

Compiling with the NDEBUG flag set also removes all ASSERT() macros from the object code, in keeping with the philosophy that ASSERT() statements should only be used to catch coding bugs, not runtime errors.

The NOLALMACROS flag

Setting the NOLALMACROS flag replaces the status-handling macros described above with actual functions that accomplish the same results. These functions are defined in the module LALError.c. Function calls introduce computational and memory overheads that are absent in macros, since macro replacement occurs at compile time. However, there are circumstances in which one might want to use function calls rather than macro replacement.

For example, debuggers typically cannot step through the individual instructions within a macro. If a conflict somehow arose between a particular piece of code and its status macros, this conflict would be easier to catch and resolve by replacing the macros with function calls into which the debugger could step.

Using the compilation flags

There are three ways to set these flags when compiling LAL programs or libraries.

When compiling your own modules, the flags can be set using one or more #define statements within the module or its header file:

#define NDEBUG
#define NOLALMACROS

To restrict the scope of these flags, they should later be unset using the corresponding #undef statements.

Alternatively, these can be set in the Makefile or when compiling. The syntax for most UNIX C compilers is something like the following:

> gcc ... -DNDEBUG -DNOLALMACROS ...

If you want to compile a large number of modules, or the entire library, under the effects of one or more of these flags, you will not want to go through and modify every header or Makefile. Instead, you may add either -DNDEBUG or -DNOLALMACROS (or both) to the environment variable CPPFLAGS. They will then automatically be set for all compilations done in that environment. The command for doing this in sh or bash shells is:

> CPPFLAGS="\f$CPPFLAGS -DNDEBUG -DNOLALMACROS"

while in csh or tcsh shells it is:

> setenv CPPFLAGS "\f$CPPFLAGS -DNDEBUG -DNOLALMACROS"

Note that if you plan to do further LAL code development on the same system, you may want to keep two versions of the library around: one with the flag(s) set and one without.

Notes

Why are the status handling routines written as macros rather than functions? There are three good reasons.

First, many of the handling routines must be able to force an exit from the function calling them. This cannot be done if the routine is in its own function, except by raising signal flags (which is a Bad Thing according to LAL standards).

Second, it is useful for these routines to assign a status structure's file and line fields using the FILE and LINE macros. If the routine is its own function, then these will just give the file and line number where the error handling routine is defined. If the routine is a macro, then these will give the file and line number where the macro was called, which is much more interesting.

Third, by expanding macros at compile time, the runtime performance of the resulting code is marginally better. Most of these macros will, under nominal conditions, reduce to a single conditional test of an integer value, with no additional overhead from function calling and parameter passing. Thus programmers can be encouraged to include extensive error trapping in all their routines, without having to worry about compromising performance.

It should be mentioned that, for these reasons, compiling a module with the NOLALMACROS flag above does not actually eliminate the status handling macros. Rather, the macros are modified to call specialized functions that do most (but not all) of the processing.

Data Structures

struct  LALStatus
 LAL status structure, see The LALStatus structure for more details. More...