Home > User Guide > Error Management
Error Handling
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 a procedure to add new error types. ACIS also provides a procedure that produces warnings at various stages of program execution.
Fatal errors are those from which there is no immediate recovery. 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.
Topics include:
- Error Handling Functions and Macros
- Exception Handling Macros
- Macro Example
- User-Level Error Handling Functions
- Error Printing Functions
- Error Return Mechanisms
- Error System Process
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 the ACIS direct function or class interfaces (that is, non-APIs). The functions and macros are listed in the order of execution:
- 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..
- 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 warnings.
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:
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_CATCH_FALSE
// logically the same as EXCEPTION_CATCH(FALSE)
// but it does not cause a 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 warning about using a
// constant expression in an if statement.
EXCEPTION_END_NO_RESIGNAL
// logically the same as resignal_no = 0;
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.
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 errorsys.hxx which is included in all .err files generated from .msg files. If a file currently calls sys_error, no new header files should be needed. Otherwise, errorsys.hxx should be included.
The EXCEPTION_BEGIN block (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-signalled.
Variables declared in the EXCEPTION_BEGIN block should be declared as being "volatile" so that they will not be created as register variables. This will prevent the unexpected resetting of the variables during exception handling. Similarly, variables that are declared before the EXCEPTION_BEGIN block—and whose values are changed in the EXCEPTION_TRY block and are cleaned up in the EXCEPTION_CATCH block—are candidates to be made volatile. For example:
EXCEPTION_BEGIN
myclass* volatile vm = NULL;
EXCEPTION_TRY
vm = new myclass(...);
...
EXCEPTION_CATCH(TRUE)
delete vm;
EXCEPTION_ENDIf 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 (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 (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 to avoid 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-signalled to higher blocks. Setting resignal_no to zero stops the error from being re-signalled.
On most platforms, the use of setjmp and longjmp (for example, in macros API_BEGIN and API_END) has been replaced with the C++ try/catch statements. Some platforms do not support the try/catch statements, so ACIS uses setjmp and longjmp on those platforms (ACIS uses setjmp and longjmp if UNIX_EXCEPTION_TRAP is defined, and uses the try/catch statements if CPLUSPLUS_EXCEPTION_TRAP is defined).
Macro Example
Without exception handling:
ENTITY_LIST list; // Local instance with dynamic
// memory
...
EDGE *ent = new EDGE(...); // ENTITY handled by BB mechanism
list.add(ent);
...
double *dbls = new double[n];// Temporary memory
...
delete [] dbls;
// With exception handling:
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_ENDA parallel set of C_EXCEPTION_... macros is defined in except.h for use in C code. Due to the lack of constructors/destructors, local variables cannot be automatically cleaned up. Also, any statement (return, break, continue, goto) that would transfer control outside the EXCEPTION_BEGIN /EXCEPTION_END block should not be used.
Guidelines
Following 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 classes derived from ENTITY and items pointed to by them do not need to be cleaned up. The bulletin board mechanism will take care of them. Cleaning up items pointed to by ENTITY class instances will almost certainly cause memory access errors.
- 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(c1, c2);
- 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.
- All memory that the routine would normally free upon successful completion should be freed if an exception occurs.
FOO *foo_array = new FOO[n];
...
delete [] foo_array;
becomes
EXCEPTION_BEGIN
FOO *foo_array = NULL;
EXCEPTION_TRY
foo_array = new FOO[n];
...
EXCEPTION_CATCH(TRUE)
delete [] foo_array;
EXCEPTION_END
- 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.
FOO *foo_ptr = new FOO(...);
...
BAR *bar_ptr = new BAR(foo_ptr,...);
...
return bar_ptr;
becomes
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;
- It is important to keep in mind that 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 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, 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:
- find_err_entry
- Uses the error code to return an error_table_entry. When an error is generated, an error code is returned as part of the outcome or as part of the warning list. The class error_table_entry contains the error code value, error code mnemonic, the corresponding error message, and the directory in which the error code was originally defined.
- 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_warning
- Resets the number of warnings to 0.
Error Return Mechanisms
The outcome class contains a pointer to an error_info object. Each API has the option of returning additional error information in objects derived from error_info. Although no restriction is placed on the information these objects contain, new ENTITYs will be lost during roll back.
The base class error_info object contains class ID and object type methods, allowing the user to quickly determine the information available in a given error_info object. Each error_info object is allocated on the heap, and the outcome cleans up any error_info object it references.
The class error_info is a base class that is intended to provide a standard interface allowing users to write generic code to process information about actual or potential problems found during API execution or while checking an entity. The interface is designed to allow users to choose the level of detail that they wish to expose in problem reporting.
Each error_info 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 objects) that provide sub-information on the problem. In addition, the error_info object may have one or more ENTITYs associated with it. A helper function, get_error_info_entity_id_count, is provided to query the number of ENTITYs associated with an error_info object. If a non-zero count is returned, then the error_info object can be downcast to an entity_error_info object, which in turn can be queried to find the ENTITYs associated with the problem. Finally, a virtual type method is provided which allows users to determine the exact subclass to which the error_info object can be downcast in order to obtain information specific to the particular problem.
A container class, error_info_list has been provided to manage collections of error_info objects. 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 error_info 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.
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 which it returns. The outcome can then be queried for the information. The following levels of query are available in the outcome class, from least detailed to most detailed:
- 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 in the outcome class to return FALSE. If ok() method in the outcome class returns TRUE, then this number will be zero.
- get_error_info() - This provides a pointer to the error_info object associated with the fatal error which caused the the ok() method in 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 in 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 encountered during an atomic operation. Failsafe errors do not cause an API 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 may have problems which 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 occurred, and error_info objects for any additional problems that my have been encountered by the API. 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 will be an invalid model or 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 a bad body, but may indicate that the user's code 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 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 by downcasting it to an entity_error_info object, users can write a generic handler routine for displaying both problems encountered during API 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.
Error System Process
Step 1
At the start of each API, a global variable pointer to an error_info object is set to NULL.
Step 2
Before sys_error is called, the global pointer is set to contain the relevant error_info object.
Step 3
At the end of the API, before the outcome is returned, the global variable is examined, and if nonempty, the error_info is added to the outcome.
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 sys_error is passed one or two ENTITYs. The standard_error_info class is derived from error_info, which provides error data that is adequate in a majority of cases, such as local operations and blending.
In the Local Operations, Remove Faces, and Shelling frameworks, the error_info object returns an ENTITY that further specifies where the local 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.
[Top]
© 1989-2007 Spatial Corp., a Dassault Systèmes company. All rights reserved.