CHAPTER 3: Error Handling The SPP language provides two facilities for error handling (see Table 3.1). Procedure Error Handled ---------------------------------------------------------------------- error (errno, errtext) Signal error condition (errtext cannot include \n) fatal (errno, errtext) Signal fatal error condition ---------------------------------------------------------------------- Table 3.1: Error Handlers in SPP. An error is signalled by calling the error() procedure. The error() procedure takes two arguments. The first argument is the error number. Application programs that call the error procedure should use an error number between 1 and 500. Numbers above 500 are used for system errors. The error number is used by any code which catches errors to distin- guish between the different types of errors. If your application program does not catch errors, the error number is arbitrary. The second argument is the error message. This argument is a string printed on the standard error stream, which is usually connected to the user's terminal. Note that the error message should not contain any newline (\n) characters. The proce- dure in Example 3.1 demonstrates the use of the error procedure. Example 3.1: Errors Flagged by error() Procedure. There is another procedure with the same arguments as error() named fatal(). The difference between the two procedures is the sever- ity of the error level. Errors which are posted by the fatal() procedure cannot be caught. iferr Errors are caught by enclosing the statements to be checked for errors in an iferr block or an ifnoerr block. An iferr block has one of two forms. The first form can only check a single statement and the statement must either be an assignment statement or a procedure call. The second form can check any number of statements of any type. The two forms of the iferr block have the following syntax: ----------------------------------------------------- iferr (statement) { iferr { statements statements } else { } then { statements statements } } then { statements } ------------------------------------------------------ Figure 3.2: Syntax for iferr. The else portion of the iferr block is optional. The meaning of an iferr block is that if an error occurs (i.e., if error() was called by one of the statements in the block) while executing the statements checked by the block, then execute the following code, but otherwise execute the code in the else block. The normal action of the error procedure, which is to print a message on the standard error stream, is suppressed. The syntax of an ifnoerr block is the same as that of the iferr block, except that the keyword iferr is replaced by ifnoerr. The meaning of an ifnoerr block is the opposite of that of the iferr block. If no error occurs during the execution of an ifnoerr block, then the following code is executed, otherwise the else block is executed. The following example shows the two forms of the iferr block. Example 3.3: Two Ways to Use the iferr Block. If there is more than one procedure call in a given block, then errchk() all of them except the last (see below). errchk In Example 3.3, the iferr block catches an error in a procedure that it calls directly, geomean. It is possible, however, for the error to occur in a subroutine that is called indirectly, that is, called by the called procedure. In order for the iferr block to check for these errors, an errchk state- ment must be added to each of the procedures between the procedure with the iferr block and the procedure which contains the error() call. The errchk statement is placed in the declarations section of the proce- dure and has the following syntax: errchk list of procedure names When an error occurs in a procedure whose name is listed in an errchk statement, program execution in the calling procedure jumps to the return statement. Thus the rest of the code in the calling procedure is skipped. By including errchk statements in all of the routines between the procedure with the iferr block and the procedure which contains the error() call, program execution will return to the iferr block without executing any intervening code if the error() procedure is called. Example 3.4 shows the use of the errchk statement. The lowest level procedure, gtdist, computes the distance between two points. If this dis- tance is zero, it calls the error() procedure. The intermediate level pro- cedure, gtinv, computes the inverse of the distance. To prevent the procedure from trying to compute the inverse of zero, the procedure con- tains an errchk statement for gtdist. This causes the execution of the program to skip this statement and return to the iferr block in gtline. Example 3.4: Using the errchk Statement. Additional Error Handling Procedures IRAF provides several procedures for handling errors in an iferr block. The errcode procedure returns the error code that was passed to the error() procedure. This allows the program to distinguish between different kinds of errors. The errget procedure also returns the error code and in addition returns the error message that was passed to the error() procedure. The erract procedure allows a program to repost the error that was caught by the iferr block. The erract procedure has one argument, the severity level of the error. There are three error levels and they are defined in the include file error.h. The two highest levels, EA_FATAL and EA_ERROR, correspond to the error levels produced by the procedures fatal() and error() respectively. Thus calling erract with the argument EA_FATAL is the same as calling fatal() with the same error that was previously posted by error(). Similarly, calling erract with the argument EA_ERROR is the same as calling error() again. The lowest error level is EA_WARN. If erract is called with an argument of EA_WARN, the error message is printed on the stan- dard error stream and execution of the program proceeds as usual. The call- ing sequences for these three routines are the following. Call Error Handled ---------------------------------------------------------------------- code = errcode () Return error code code = errget (oustr, maxch) Return error code and message erract (severity) Repost error xer_reset () Reset error state ---------------------------------------------------------------------- Table 3.2: Error Handling Procedures. The following example (Example 3.5) illustrates the use of the errcode and erract procedures. It converts all errors with a code of one to warnings and reposts all other errors as errors. Example 3.5: Using the errcode and erract Procedures. Example 3.5 (Continued): Using the errcode and erract Procedures. Error Handlers In addition to handling an error locally with an iferr block, it is also possible to handle an error globally by posting an error handling procedure. The purpose of posting an error handling procedure is to restore the com- puter to a known state when a program exits abnormally with an error. Error handlers can be posted with onerror or xwhen. Error handlers posted with onerror are called whatever the type of error that occurred. Also, the program will not continue executing after an error handler is called. Error handlers posted with xwhen are associated with a particular error code and execution of the program will continue after the error han- dler exits. Call Error Handling ---------------------------------------------------------------------- onerror (proc) Post an error handler xwhen (signal, handler, old_handler) Post and error handler zsvjmp (jumpbuf, status) Save system state zdojmp (jumpbuf, code) Jump ---------------------------------------------------------------------- Table 3.3: Error Handlers. The procedure onerror() has a single argument, the name of the error handling procedure. The error handling procedure must be declared external with the extern statement. If an error occurs in the program after the error handling procedure is posted, the error handling procedure will be called before the normal program cleanup. The error handling procedure will be passed a single argument, the error code passed to the error proce- dure. Other information necessary for the error handling procedure should be passed through the common block. The following example shows how an error handling procedure is posted by onerror and what it looks like. The first procedure, term_init, opens the terminal for reading and writing and puts the terminal in raw mode. The second procedure, term_end, closes the terminal and restores the terminal from raw mode. Since leaving the terminal in raw mode after the program exits will cause a lot of problems, term_init posts an error handling routine to restore the terminal. The error handling routine simply calls the normal exit procedure, term_end. Note the file descriptors are set to NULL after they are closed. This is so that if an error occurs in the program after term_end is called, the error handling routine will not try to close the same file descriptors twice. Example 3.6: An Error Handling Procedure. Example 3.6 (Continued): An Error Handling Procedure. There are two kinds of errors that can occur during the execution of a program, synchronous and asynchronous errors. Synchronous errors occur when the task calls the error() procedure. These are synchronous errors because the task is in a known state when the error condition occurs. As a result, error handling is relatively simple. Synchronous errors can be caught by an iferr block, as described previously. Asynchronous errors, also known as exceptions, occur when the hardware detects an illegal con- dition. Because these errors are detected by the hardware and not by the program, the program is in an unknown state when the error occurs. This makes error handling more difficult. IRAF divides all asynchronous errors into four kinds: access violations, arithmetic errors, interrupts, and inter- process communication errors. IRAF has a default exception handler for all asynchronous errors. The default exception handler does a non-local jump to the IRAF main routine, prints an error message, performs task cleanup such as closing files, and exits normally. If this default behavior is not suffi- cient, a program can post its own error handler by calling xwhen. xwhen takes three arguments. The first two are inputs and the third is an output. The two inputs are a symbolic constant indicating the error to be trapped and the address of the error handling procedure. The symbolic con- stants are defined in xwhen.h. The address of a procedure is computed from the function locpr. The output is the address of the old error han- dling procedure. This is provided so that the program can restore the old error handler later or so that it can chain error handlers by calling the old error handler when the error handler exits. The error handling procedure has two arguments. The first is an input, the symbolic constant representing the error code. The second is an output, the address of error handler to call after the error handler returns. If the error handler does not chain to another error handler, the second parameter should be set to the symbolic constant X_IGNORE. Usually an error handler resumes execution of a program by performing a non-local jump. A non-local jump is performed by calling two proce- dures, zsvjmp and zdojmp. Zsvjmp saves the current state of the com- puter in an array. The length of this array is hardware dependent and is specified by a symbolic constant in config.h. Zdojmp takes the array generated by zsvjmp and uses it to restore the computer state to what it was when zsvjmp was called. Thus the program calls zdojmp and returns from zsvjmp. Zsvjmp has a second argument, status, which indicates whether the return from zsvjmp is a normal return or a result of a call of zdojmp. The value returned from zsvjmp is the second argu- ment of zdojmp or OK if zdojmp was not called. When using non-local jumps, the condition which caused the error must not be repeated or the program will go into an infinite loop. Example 3.7 shows how to post an error handler with xwhen. Only two of the four asynchronous errors are trapped, access violations and arithmetic errors. The old error handlers are saved in local variables so that they can be restored at the end of the subroutine. The system state is saved by procedure zsvjmp. The length of the array is given by a symbolic con- stant defined in the header file config.h. The procedure then calls do_cmp, which executes the command read from the file. If an access vio- lation or arithmetic error occurs while the command is being executed, the program will call err_cmd. This procedure restores the system state by calling zdojmp. The array with the system state is passed through a com- mon block. The program then returns from zdojmp and prints the error message. Example 3.7: Posting an Error Handler with xwhen. Example 3.7 (Continued): Posting an Error Handler with xwhen.