Error Handling
From DocR23
Error Handling transfers the control of the program from one place to another if unwanted results occur or a user interrupts the program. ACIS handles all standard errors that occur while executing a program. It also provides procedures to add new error types and produce warnings at various stages of program execution.
Contents |
Exception Types
- See also: ACIS Error Messages
- Fatal errors are those from which there is no immediate recovery. A fatal error is one that causes the API function to fail and roll back to its initial state.
- Errors that are unexpected events, but are not immediately fatal, are warnings.
The same event is a fatal error under some circumstances, unexpected but nonfatal under other circumstances, and acceptable under other circumstances. Regardless of whether a given event is fatal, the same error code is used. The function used with that error code informs the system whether the error was fatal.
Error Handling Functions and Macros
The following error handling functions and macros are used by ACIS to catch and control errors. Applications may also need to use these when using ACIS Direct Interface functions or class methods (that is, non-API functions). The functions and macros are listed in the order of execution:
Error Signaling Functions
| Function | Description |
|---|---|
| error_begin | Establishes ACIS signal handling. Each call to error_begin must be offset by a corresponding call to error_end. Calls to these two routines may be nested. The outermost call to error_begin establishes signal handlers, resets the warning count, and resets the error hardness level. Each call to error_begin increments the error level. |
| error_harden | Inhibits processing of user interrupts. Each call to error_harden increments the error hardness level. User interrupts temporarily ignored while the error hardness level is greater than zero. All other signals and errors are processed normally. |
| error_soften | Enables processing of user interrupts. Each call to error_soften decrements the error hardness level. When the error hardness level reaches zero, any user interrupt that was ignored is processed. |
| error_end | Resets signal handling. Each call to error_end decrements the error level. When the error level reaches zero, error_end resets the signal handlers to those that were in effect when the corresponding call to error_begin was made. If a user interrupt was seen, The application's interrupt signal handler is called. |
| sys_error | Signals ACIS errors and interrupts. The errors reported using this function are fatal errors. If the crash option is on, the function causes a core dump. Otherwise, it transfers control to the innermost active ERROR_BEGIN macro, setting its error_no to the specified error code. Note: There are multiple signatures of sys_error. |
| sys_warning | Reports unexpected, nonfatal events that occurred during the course of the execution. It saves the error code given as input in an err_mess_type global array. Like sys_error, this routine checks for the crash option. If the option is on, it prints the message associated with the current error code. Otherwise, the application must process this warning. |
Exception Handling Macros
Four macros have been defined to allow ACIS code to take corrective action if an error or interrupt occurs. Applications may also need to use these when using the ACIS direct function or class interfaces (that is, non-APIs). The four exception handling macros are:
| Macro | Description |
|---|---|
| EXCEPTION_BEGIN | Defines the beginning of an exception handling block, which allows the declaration of local variables that must be cleared should an error occur. |
| EXCEPTION_TRY | Defines the beginning of a try/catch block, which denotes the beginning of the block that contains the normal processing code. |
| EXCEPTION_CATCH ... | Defines the termination of the try/catch block, which denotes the beginning of the block that contains the code that handles the exception. |
| EXCEPTION_END ... | Defines the termination of the catch block, with the option of re-signaling the error. |
Usage
EXCEPTION_BEGIN
// Declarations of local variables to be cleaned up go here
EXCEPTION_TRY
// Normal processing code goes here
EXCEPTION_CATCH(always_clean)
// Interrupt/error cleanup code goes here
EXCEPTION_END
The macros must appear in the order specified above (BEGIN, TRY, CATCH, END). The EXCEPTION_CATCH macro is optional and may be omitted if only local variables are to be cleaned up. Sets of macros may be nested, but they must not overlap. The inner set must be fully contained between adjacent macros (normally between EXCEPTION_TRY and EXCEPTION_CATCH) of the outer set.
In addition to the four macros listed above, three alternative macros exist:
EXCEPTION_CATCH_FALSE
// Logically the same as EXCEPTION_CATCH(FALSE)
// but it does not cause a compiler warning about
// using a constant expression in an if statement.
EXCEPTION_CATCH_TRUE
// Logically the same as EXCEPTION_CATCH(TRUE)
// but it does not cause a compiler warning about
// using a constant expression in an if statement.
EXCEPTION_END_NO_RESIGNAL
// Logically the same as setting resignal_no = 0;
You must select zero or one of the CATCH macros and exactly one of the END macros in each exception block. For example, you should never have an EXCEPTION_CATCH_TRUE and an EXCEPTION_CATCH_FALSE in the same block of protected code.
The macros are defined in errorbase.hxx which is included in all ACIS .err files. If a file currently calls sys_error no additional header files should be needed. Otherwise, errorbase.hxx should be included.
The EXCEPTION_BEGIN block (the lines of code between the EXCEPTION_BEGIN and EXCEPTION_TRY macros) is used to declare variables that must be cleaned up if an error occurs. This includes pointers to be deleted in the EXCEPTION_CATCH block as well as local instances of classes that may contain pointers to potentially large amounts of dynamically allocated memory. Local instances are automatically destroyed by block exit code in the EXCEPTION_END macro before any error is re-signaled.
Variables declared in the EXCEPTION_BEGIN block may be declared as being volatile so that they will not be created as register variables, as shown below. This will prevent the unexpected resetting of the variables during exception handling. For more information on the volatile type qualifier, refer to HowTo:Use the volatile type qualifier.
EXCEPTION_BEGIN myclass* volatile vm = NULL; EXCEPTION_TRY vm = new myclass( /*... */); //... EXCEPTION_CATCH(TRUE) delete vm; EXCEPTION_END
If multiple variables are declared in the EXCEPTION_BEGIN block, their initialization should be kept as simple as possible since any error or interrupt that occurs in this block will not cause the corresponding EXCEPTION_CATCH block to be executed nor the local variables to be destroyed.
The EXCEPTION_TRY block (the lines of code between the EXCEPTION_TRY and EXCEPTION_CATCH macros) contains the normal processing code. Variables declared here are visible only within the EXCEPTION_TRY block. They are destroyed by the block exit code only if no error or interrupt occurs. Variables that need to be cleaned up if an error occurs should be declared in or before the EXCEPTION_BEGIN block. Variables that must be visible after the EXCEPTION_END macro must be declared before the EXCEPTION_BEGIN macro.
The EXCEPTION_CATCH block (the lines of code between the EXCEPTION_CATCH and EXCEPTION_END macros) is used to free dynamically allocated memory and reset global variables. The always_clean argument to the EXCEPTION_CATCH macro is a logical expression used to indicate whether the EXCEPTION_CATCH block should be executed even if no error occurs. This is useful for avoiding the duplication of code used to free temporary memory. The variable error_no can be examined to determine what (if any) error occurred. The variable resignal_no can be modified to change the error re-signaled to higher blocks. Setting resignal_no to zero stops the error from being re-signaled. This can also be accomplished by using the EXCEPTION_END_NO_RESIGNAL macro instead of EXCEPTION_END.
ACIS error handling uses C++ try/catch statements and only throws and catches exceptions of a specific internal type. Asynchronous exceptions, such as access violations and floating point exceptions, are caught in platform specific ways but are ultimately rethrown (resignaled) with sys_error.
Macro Example
The following example demonstrates how the EXCEPTION_CATCH_TRUE macro can be used to always clean up allocated memory. In this case it is used to delete the dbls array.
| Without Exception Handling | With Exception Handling |
|---|---|
// Local instance with dynamic memory ENTITY_LIST list; //... // ENTITY handled by BB mechanism EDGE *ent = new EDGE(...); list.add(ent); //... // Temporary memory double *dbls = new double[n]; //... delete [] dbls; |
EXCEPTION_BEGIN ENTITY_LIST list; double *dbls = NULL; EXCEPTION_TRY //... EDGE *ent = new EDGE(...); list.add(ent); //... dbls = new double[n]; //... EXCEPTION_CATCH(TRUE) delete [] dbls; EXCEPTION_END |
Guidelines for Exception Handling Implementation
The following topics are some guidelines for implementing exception handling for memory cleanup. These guidelines cover some of the most common situations involved in memory cleanup. There are bound to be other situations that are not covered here. These are only guidelines and individual circumstances may be better handled in other ways.
Instances of ENTITY
Instances of classes derived from ENTITY and ACIS objects pointed to by them do not need to be cleaned up. The history mechanism will take care of them. Deallocating ACIS objects pointed to by ENTITY class instances will almost certainly cause memory access errors.
Functions Returning Pointers
Simply looking for occurrences of new and delete will not identify all places where exception handling needs to be implemented. Functions that return pointers to allocated memory and instances of classes (such as ENTITY_LIST) that may contain pointers to large amounts of allocated memory should also be identified.
// Local instance of memory consuming class ENTITY_LIST list; // Function returns allocated memory curve_curve_int *cci = int_cur_cur(c_1, c_2);
Declaration of Local Instances
Declarations of local instances of classes that may contain pointers to large amounts of dynamically allocated memory that would be freed by their destructors should be moved into a EXCEPTION_BEGIN block.
Freeing Memory when Exceptions Occur
All memory that the routine would normally free upon successful completion should be freed if an exception occurs.
| Memory that Needs Freeing | Correct Handling |
|---|---|
FOO *foo_array = new FOO[n]; //... delete [] foo_array; |
EXCEPTION_BEGIN FOO *foo_array = NULL; EXCEPTION_TRY foo_array = new FOO[n]; //... EXCEPTION_CATCH(TRUE) delete [] foo_array; EXCEPTION_END |
This becomes slightly more complicated if some memory always needs to be freed and some memory needs to be freed only in the event of an exception. The following example demonstrates how the error_no variable can be used to achieve this.
EXCEPTION_BEGIN FOO *foo_array1 = NULL; FOO *foo_array2 = NULL; EXCEPTION_TRY foo_array1 = new FOO[n]; foo_array2 = new FOO[n]; //... EXCEPTION_CATCH(TRUE) delete [] foo_array1; // This is always freed. if (error_no) { delete [] foo_array2; // This is freed only in the event of an exception. } EXCEPTION_END
Avoid Multiple Deletion
Memory that would be returned or attached to another object should be cleaned up in the EXCEPTION_CATCH block if it could exist for a significant period of time before being returned or attached. Avoid multiple deletion of allocated memory pointed to by class instances to be cleaned up. In the following example the instance of the FOO object is owned by the instance of the BAR object.
| Multiple Deletion | Correct Handling |
|---|---|
FOO *foo_ptr = new FOO(...); ... BAR *bar_ptr = new BAR(foo_ptr,...); ... return bar_ptr; |
BAR *bar_ptr = NULL; EXCEPTION_BEGIN FOO *foo_ptr = NULL: EXCEPTION_TRY foo_ptr = new FOO(...); ... bar_ptr = new BAR(foo_ptr,...); ... EXCEPTION_CATCH(FALSE) if (bar_ptr == NULL) delete foo_ptr; else delete bar_ptr; EXCEPTION_END return bar_ptr; |
Important: Several type names (such as bs2_curve and bs3_surface) are actually pointers. Functions that return these types are probably returning pointers to allocated memory.
Exception Handling Cost
Exception handling is relatively cheap, but not free. Consider size and duration of memory use when deciding whether to attempt to catch a potential leak.
// A small allocation, quickly attached FOO *foo_ptr = new FOO(...); bar.set_foo(foo_ptr);
User-Level Error Handling Functions
In ACIS-based applications, precede the code that calls ACIS functions with API_BEGIN and succeed the program with API_END. These macros set up and terminate the error system automatically and are transparent to the application.
Error Printing Functions
When an error is generated by an API function, an error code is returned as part of the outcome returned value, or as part of the warning list. The following functions are used to find or print error messages once an error has occurred:
| Function | Description |
|---|---|
| find_err_ident | Translates the error number to a string containing the mnemonic name associated with the given error number. |
| find_err_mess | Translates the error number to a string containing the message associated with the given error number. |
| find_err_module | Translates the error number to a string containing the name of the module associated with the given error number. |
| print_warnerr_mess | Prints the message associated with the current error number in a simple format for debugging purposes. |
| get_warnings | Obtains the warnings list. |
| init_warnings | Resets the number of warnings to 0. |
Error Return Mechanisms
The outcome class contains a pointer to an error_info object. Each API function has the option of returning additional error information in objects derived from error_info. Although no restriction is placed on the information you may attach to such objects, you should be aware that new ENTITYs will be lost during roll back, meaning that pointers to such objects will become stale.
Each error_info object is allocated on the heap, and the outcome cleans up any error_info object it references.
The base class for error_info, error_info_base, provides a standard interface allowing you to write generic code to process information about actual or potential problems found during API function execution. Each error_info_base object is intended to represent one instance of a problem being encountered, and has methods to query the error number or message string of the problem, the severity of the problem, and a list of reasons (themselves error_info_base objects) that provide further information concerning the problem. A virtual type method is provided which allows you to determine the exact subclass to which the error_info_base object can be downcast in order to obtain information specific to the particular problem. The error_info objects returned by outcome may, in addition, have one or more ENTITYs associated with them. The error_info::get_entities_alive method allows you to query the live ENTITYs associated with a problem or failure.
A container class, error_info_list, has been provided to manage collections of error_info objects. (For error_info_base objects, a corresponding error_info_base_list class is also available.) This class allows collections of error_info objects to be passed to user routines as a single object. In addition, the error_info_list provides shared ownership for the error_info objects that it contains. The lifetime of an error_info object is equal to the union of the lifetimes of the containers (error_info_list, outcome, or, for insanity_data objects, insanity_list) to which it has been added. As long as users do not explicitly add reference counts to error_info objects, they need not worry about memory management of these objects. It is recommended that error_info_list objects be placed on the stack wherever possible, so that they do not need to be explicitly deleted by user code.
These error_info objects are used to enhance problem reporting from API functions through the outcome mechanism. When an API encounters a problem during execution, it may report that problem by adding that error_info object to the outcome object that it returns. The outcome object can then be queried for the information. The following member functions of the outcome class provide multiple levels of query, from least detailed to most detailed:
| Method | Description |
|---|---|
| ok | FALSE if fatal errors were encountered. A fatal error is one that causes the API function to fail and roll back to its initial state. The error_info object associated with a fatal error has severity SPA_OUTCOME_FATAL. |
| error_number | This provides the error number of the fatal error that caused the ok method of the outcome class to return FALSE. If the ok method returns TRUE then the error number will be zero. |
| get_error_info | This provides a pointer to the error_info object associated with the fatal error that caused the the ok method of the outcome class to return FALSE. The error_number method of this error_info object will return the same value as the error_number method of the outcome class. If ok method of the outcome class returns TRUE, then this method will return a NULL pointer. The error_info object returned by this query gives the highest level of information about the fatal error. |
| encountered_errors | Returns TRUE if a fatal error or one or more failsafe errors were encountered. A failsafe error is an error that a failsafe API function encountered during an atomic operation. Failsafe errors do not cause an API function to roll (unless the careful option is TRUE, in which case they are promoted to fatal errors), but they do indicate that the model resulting from the API function may have problems that will cause downstream operations to fail if they are not corrected. An error_info object with severity SPA_OUTCOME_ERROR is added to the outcome class object for every failsafe error encountered. For more information, refer to the section Failsafe Behavior. |
| get_error_info_list | This returns a list of all error_info objects that have been added to the outcome class object. This list will include the fatal error_info (if any), error_info objects for any failsafe errors that have occurred, and error_info objects for any additional problems that may have been encountered by the API function. Such error_info objects, with severity SPA_OUTCOME_PROBLEM, warn that there may problems with the input data or workflow. However, they do not indicate that the outcome of the API function will be an invalid model, nor are they promoted to fatal errors when the careful option is TRUE. An example of such a problem is passing a wire body to the face stitcher. This will not result in an invalid body, but may indicate that the application is not handling wire bodies properly. |
The class error_info is also used as the base class for the insanity_data objects returned from the ACIS Checker. Such error_info objects have severity SPA_OUTCOME_INSANITY, and the error message number returned by the error_number method is the same as that returned by insanity_data::get_insane_id. A helper function, convert_insanity_list_into_error_info_list, is provided to convert an insanity_list returned from the Checker into an error_info_list. With the ability to obtain ENTITY information from an error_info object using the get_entities_alive method, users can write a generic handler routine for displaying both problems encountered during API function execution and problems located by the Checker, in the same format. In addition, the error-message-ID and message-string mechanisms are the same for check errors, sys_errors (fatal or failsafe), and problems.
A code snippet demonstrating how to examine error_info objects is provided in Sample Code.
Error System Process
Step 1
At the start of each API function, a global variable pointer to an error_info_base object is set to NULL.
Step 2
Before sys_error is called, the global pointer is set to contain the relevant error_info_base object.
Step 3
At the end of the API function, before the outcome is returned, the global variable is examined, and if non-empty, an error_info object is added to the outcome. (If the global variable points to a base-class object, an error_info is first constructed from its data.)
Two overloaded versions of the function sys_error set a global pointer to an error_info object. One version is passed an error_info object, and the other creates a standard_error_info object when passed one or two ENTITYs. (The standard_error_info class is derived from error_info, which provides error data that is adequate in many cases, such as in Local Operations and Blending.)
In the Booleans, Local Operations, Remove Faces, and Shelling components, the error_info object returns an ENTITY that further specifies where the operation first fails, when such information is available. A standard_error_info object is adequate for use in these components, and more detailed information could be returned, if necessary, by deriving a new class.