Provides macros for handling the LALStatus structure.
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.
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 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:
Code | Message | Explanation |
---|---|---|
0 | Nominal execution; the function returned successfully. | |
-1 | Recursive error | The function aborted due to failure of a subroutine. |
-2 | INITSTATUS: non-null status pointer | The 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). |
-4 | ATTATCHSTATUSPTR: memory allocation error | The function was unable to allocate a statusPtr field to pass down to a subroutine. |
-8 | DETATCHSTATUSPTR: null status pointer | The 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 . |
-16 | INITSTATUS: non-zero xlalErrno | The xlalError variable is non-zero, which suggests that an error in an XLAL routine has occured and has not been handled. |
-16 | RETURN: untrapped XLAL error code | The xlalError variable is non-zero, which indicates that an error in an XLAL routine has occured and has not been handled. |
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
.
statusCode
; an error message is printed automatically whenever a function exits with non-zero statusCode
.statusCode
.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 #define
s 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.
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.
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:
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.
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.
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.
Upon completion, the function should issue the macro RETURN()
, which takes one argument: the function's status pointer.
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()
.
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
:
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.
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:
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()
.
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.
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.
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).
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:
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.
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.
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:
For another example, if a function had to create three vectors *vector1
, *vector2
, *vector3
, the allocation would look something like this:
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.
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:
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.
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:
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.
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.
LAL provides two flags that can be used to exclude or modify debugging code at compile time. Although these flags are typically #define
d or #undef
ined globally and can affect many modules, their primary effect is on the debugging and status-reporting tools defined in this header. The two flags are named LAL_ASSERT_MACRO_DISABLED
and LAL_STATUS_MACROS_DISABLED
.
Compiling with the LAL_ASSERT_MACRO_DISABLED
flag set 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.
Compiling with LAL_ASSERT_MACRO_DISABLED
set is not recommended. Notwithstanding the philosophy behind ASSERT()
, it is quite likely in places that this macro has been (mis)used for critical checks without which code will not operate as expected. Correct function with LAL_ASSERT_MACRO_DISABLED
set is therefore not guaranteed.
Code that checks function input arguments for correctness, reports error messages, etc. should not be disabled even in production code. The performance impact of such checks is likely minimal, particularly compared to the human cost of diagnosing obscure failures in production code because checks/error messages were disabled. (Error messages and other diagnostics should use the error-printing functions in Header LALError.h to selective print messages according to lalDebugLevel
.)
Setting the LAL_STATUS_MACROS_DISABLED
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.
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:
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:
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 -DLAL_ASSERT_MACRO_DISABLED
or -DLAL_STATUS_MACROS_DISABLED
(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:
while in csh
or tcsh
shells it is:
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.
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 LAL_STATUS_MACROS_DISABLED
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... | |