Exception Handling |
|
|
An exception is a named event, typically an error, that can be caught and handled by an application. Exceptions are thrown either by use of the THROW statement in an application program or automatically by QM as an alternative to the abort events normally raised by data type errors and similar failures.
The actual name of an exception has no significance to QM but should ideally relate to the situation that the exception handles. Exception names are case insensitive and may be up to 63 characters. Application developers should avoid creating exception names that begin with "SYS." and names containing a $ symbol as these are reserved as described below.
QMBasic allows creation of exception handlers in a manner broadly similar to that of some other programming languages by use of a TRY/CATCH construct:
TRY {statement} {statements} CATCH exception {, exception...} {statements} CATCH exception {, exception...} {statements} END
where
A single TRY construct may have multiple CATCH clauses, each handling one or more exceptions.
The TRY/CATCH construct executes the statement(s) in the TRY clause, monitoring for named exceptions being thrown. The TRY clause may include GOSUB, CALL or EXECUTE to execute lower level code outside of this clause, however, unless an exception is raised, it is essential that these operations return to complete the TRY clause. Use of GOTO to jump into or out of the TRY clause may have undesirable results.
On throwing an exception, the application exits from all lower level programs, subroutines, etc and continues execution in the CATCH clause that handles this exception. Where an object oriented programming object is discarded by an exception, the DESTROY.OBJECT subroutine will be executed in the normal way.
Transactions started by actions within the TRY clause and still active at the point when the exception is thrown will be rolled back.
A single CATCH clause may catch multiple exception names. On arrival in the CATCH clause, the @EXCEPTION variable will contain the name of the exception that has been caught and the @EXCEPTION.DATA variable will contain any data associated with this exception. The @EXCEPTION.ORIGIN variable is set to a dynamic array in which field 1 holds the program name from which the exception was thrown and field 2 holds the line number in that program. If the program has no cross-reference tables, the line number will be -1.
The CATCH clause has two optional qualifiers that can appear after an exception name. The DUMPING qualifier causes a process dump file to be created, showing the full state of the call stack at the point when the exception was thrown. The SAVING.STACK qualifier sets @EXCEPTION.STACK to contain the call stack in the same form as produced by the SYSTEM() function with key 1002 (SYS$CALL.STACK). See TRY/CATCH for an example of a simple code fragment to report this data in a useful form.
Example
LOOP READNEXT ACC.ID ELSE EXIT TRY CALL PROCESS.ACCOUNT CATCH ACCOUNT.INVALID DISPLAY 'Account ' : ACC.ID : ' is not valid' END REPEAT
The above rather simple example shows a program fragment that processes successive items from a select list. If the PROCESS.ACCOUNT subroutine, or any lower level action called from it, throws an ACCOUNT.INVALID exception, the program will continue execution at the DISPLAY statement.
Exception Groups
An exception name may be formed from multiple components separated by a period where each component forms a level of grouping. In the above example, the ACCOUNT.INVALID exception can be considered as part of an exception group named ACCOUNT. The CATCH clause in the example will be entered on throwing the specific ACCOUNT.INVALID exception. Alternatively, the CATCH clause could have been written as CATCH ACCOUNT and this would be executed for any exception in the ACCOUNT group, including the ACCOUNT.INVALID exception. Use of exception groups can significantly simplify programs by using a single CATCH clause to trap a whole exception group, the actual exception name being available in the @EXCEPTION variable. There is no restriction on the number of levels in an exception group aside from the entire exception name being limited to 63 characters. The example above would also catch further exception levels such as ACCOUNT.INVALID.CLOSED to show the reason for the account to be invalid.
Scope of Exception Handlers
The structure of an application may validly nest TRY/CATCH constructs to any depth. On throwing an exception, the system works back through the established exception handlers to find the first that catches the relevant exception. This process will not continue past a program that uses the TRAPPING ABORTS option of the EXECUTE statement as this option requests that any errors in lower level programs should cause return from the EXECUTE. An application can test whether there is a handler for a specific named exception by use of the CAUGHT() function.
The SYS$ANY Exception
A CATCH clause that references the SYS$ANY exception name will catch any exception and can be used as a general error handler. When used, this exception name must be the last one referenced in the CATCH clauses associated with the TRY. The exception name returned via @EXCEPTION will be that of the actual exception thrown, not SYS$ANY.
This exception can be used in much the same way as the TRAPPING ABORTS option of the EXECUTE statement to act as a firewall beyond which no exceptions will be thrown.
The SYS$UNHANDLED Exception
A CATCH clause that references the SYS$UNHANDLED exception name will be executed if there is no specific handler for the thrown exception or SYS$ANY. The check for this handler occurs after checking the entire exception handler stack for the named exception or SYS$ANY, therefore SYS$ANY effectively takes priority over SYS$UNHANDLED regardless of their relative positions in the exception handler stack.
System Generated Exceptions
Many of the situations that would otherwise cause an abort event to be generated such as a data type error can be trapped by exceptions from the SYS exception group.
If there is no handler for the exception, including the SYS$ANY and SYS$UNHANDLED handlers described above, the application will abort in the usual way. Note that the abort error message is stored in both @ABORT.MESSAGE and @EXCEPTION.DATA but is not displayed if it is caught by an exception handler.
The full list of exception names in the SYS group is shown hierarchically below. In all cases, the name is formed by adding the desired hierarchical elements to a "SYS." prefix. For example, a deadlock situation throws an exception named "SYS.LOCKS.DEADLOCK".
See also: |