Exceptions as Objects
Contents
- 1 Overview
- 2 Exception class hierarchy
- 3 Raise and catch an exception
- 4 Ignoring, continuing an exception
- 5 `origin' VS `cause'
- 6 Exception at rescue clause
- 7 OLD_VIOLATION
- 8 Class ANY
- 9 Class EXCEPTION
- 10 Class EXCEPTION_MANAGER
- 11 Default rescue
- 12 Sample code: Raising and catching developer exception
Overview
This article describes many aspects of Exceptions as Objects (ECMA-367 8.26) which will be implemented in ISE Eiffel. All exceptions will be raised as objects which can be accessed by `last_exception'. Exceptions as objects are known as type EXCEPTION and its descendants. EXCEPTION_MANAGER is introduced to handle common exception class operations, i.e. catch, and ignore.
This doc is from the original working version: http://docs.google.com/Doc?docid=dckspcv8_9htpfm4&hl=en
Exception class hierarchy
EXCEPTION SYSTEM_EXCEPTION MACHINE_EXCEPTION OPERATING_SYSTEM_EXCEPTION COM_FAILURE OPERATING_SYSTEM_FAILURE OPERATING_SYSTEM_SIGNAL_FAILURE HARDWARE_EXCEPTION FLOATING_POINT_FAILURE EIFFEL_EXCEPTION LANGUAGE_EXCEPTION BAD_INSPECT_VALUE VOID_TARGET VOID_ASSIGNED_TO_EXPANDED ROUTINE_FAILURE EIFFELSTUDIO_SPECIFIC_LANGUAGE_EXCEPTION CREATE_ON_DEFERRED ADDRESS_APPLIED_TO_MELTED_FEATURE EIFFEL_RUNTIME_EXCEPTION NO_MORE_MEMORY DATA_EXCEPTION IO_FAILURE SERIALIZATION_FAILURE MISMATCH_FAILURE EXTERNAL_FAILURE-- Only used for threads at the moment EIFFEL_RUNTIME_PANIC ASSERTION_VIOLATION INVARIANT_VIOLATION PRECONDITION_VIOLATION POSTCONDITION_VIOLATION CHECK_VIOLATION VARIANT_VIOLATION OLD_VIOLATION LOOP_INVARIANT_VIOLATION DEVELOPER_EXCEPTION OBSOLETE_EXCEPTION RESUMPTION_FAILURE Was RESUMPTION_FAILED. RESCUE_FAILURE --------- SHOULD NOT APPLY ANY MORE WITH ISO/ECMA, CHECK!!! EXCEPTION_IN_SIGNAL_HANDLER_FAILURE
Classes in red plus EXCEPTION_MANAGER will be placed at base\elks\kernel\exceptions\.
The rests are ise specific.
Terminology
We use _EXCEPTION for intermediate nodes in the hierarchy; when we need a term in the leaves of the hierarchy we don't use EXCEPTION but
* _VIOLATION for assertions (also used for ASSERTION_VIOLATION, an intermediate node) * _FAILURE otherwise This is consistent with Eiffel exception terminology since from the viewpoint of Eiffel these things have failed, for example a signal was not handled properly. The Eiffel runtime is causing an exception in the Eiffel code as a result, but the original event was a failure. Hence SERIALIZATION_FAILURE etc.
Raise and catch an exception
Catching an exception
All exceptions can possibly be caught. `is_caught' is set by default. Only the given type of exception is `ignore'd in EXCEPTION_MANAGER if possible, it hence is not caught. The caught exception can be accessed by `last_exception'.
Raising an exception
There are two types of exceptions concerning how an exception is raised.
- System raised
Exception raised through the runtime. Most exceptions of this type are predefine as classes in base library. i.e. assertion violations, no memory exceptions and routine failures.
- User raised
User raised exceptions are initialized by users and raised by explicit call of {EXCEPTION}.raise.
Theoretically the runtime can raise any exceptions known. A user can only raise an EXCEPTION that is `is_raisable'. `is_raisable' is possible true only when the exception is of DEVELOPER_EXCEPTION. This means only raising a DEVELOPER_EXCEPTION or its descendant is guaranteed correct.
Ignoring, continuing an exception
Quote from ECMA 8.26.12:
It is possible, through routines of the Kernel Library class EXCEPTION, to ensure that exceptions of certain types be: * Ignored: lead to no change of non-exception semantics. * Continued: lead to execution of a programmer-specified routine, then to continuation of the execution according to non-exception semantics.
An ignorable exception can be ignored. When an exception is ignored, the raising does nothing as if non-exception semantics. What are the exceptions not ignorable?
Related queries in EXCEPTION:
is_ignored: BOOLEAN is_caught: BOOLEAN is_ignorable: BOOLEAN
Related routines in EXCEPTION_MANAGER:
is_caught (a_exception: TYPE [EXCEPTION]): BOOLEAN is_ignored (a_exception: TYPE [EXCEPTION]): BOOLEAN is_ignorable (a_exception: TYPE [EXCEPTION]): BOOLEAN catch (a_exception: TYPE [EXCEPTION]) ignore (a_exception: TYPE [EXCEPTION]) set_is_ignored (a_exception: TYPE [EXCEPTION]; a_ignored: BOOLEAN)
`origin' VS `cause'
- The origin of an exception is, among all exceptions that have so far been triggered but not handled, the most recent one that is not a ROUTINE_FAILURE. Because a ROUTINE_FAILURE is always the result of an exception of another kind, the `origin' is always defined.
- The `cause' of an exception is only interesting in the case of an exception triggered in a rescue clause. In that case the exception is the result of incomplete handling of another exception (which might be a ROUTINE_FAILURE or any other kind). Then the `cause' is that other exception.
- If an exception was not triggered in a rescue clause, its `cause' is the exception itself.
Exception at rescue clause
See the following diagram:
Exception e2 is kept as `cause' at d.f4 in which e3 is raised, and being called in rescue clause r2. In this case e3 is accessible by `last_exception' and e2 is accessible by `cause' of e3.
A behavior should be clarified, that when an exception occurs deep in execution level of a rescue clause, `last_exception' is understood as "last unhandled exception".
1. A new exception causes "last_exception" to be saved somewhere before
entering "rescue" clause. After that the new exception object is attached to
"last_exception".
2. If an exception is not handled (there is no rescue clause or "retry" is not performed), a new exception object "ROUTINE_FAILURE" is created, its attribute "original_exception" is set to "last_exception", and "last_exception" is set to this new "ROUTINE_FAILURE" object.
3. If an exception is handled ("retry" is executed), "last_exception" is attached a value saved in step 1.
Example: If "last_exception" in the following code snippet is modified by the call to feature "h" that raises an exception internally and handles it?
rescue -- "last_exception" is set to "X". -- Call feature that raises exception "Y", but handles it. h -- Is "last_exception" set to "X" or to "Y"? -- The answer is X.
OLD_VIOLATION
OLD_VIOLATION behaves similarly with ROUTINE_FAILURE. In other words, OLD_VIOLATION is never a "root" exception of an exception chain. According to ECMA 8.26.11, OLD_VIOLATION should be treated as a "root" exception. But I believe it is more interesting to know the cause (original exception) of the old voilation. This will be clarified soon.
Class ANY
This is now done in EXCEPTION_MANAGER.
class ANY ... feature -- Exception frozen last_exception: EXCEPTION -- Last exception external "built_in" end ...
Class EXCEPTION
deferred class interface EXCEPTION feature -- Access code: INTEGER_32 -- Code of the exception. -- |Maybe we don't need this anymore exception_trace: STRING_8 -- String representation of current exception trace meaning: STRING_8 -- A message in English describing what `except' is message: STRING_8 -- Tag of current exception original: EXCEPTION -- Original exception that triggered current exception cause: EXCEPTION -- Unhandled exception before current exception was triggered in a rescue clause recipient_name: STRING_8 -- Name of the routine whose execution was -- interrupted by current exception type_name: STRING_8 -- Name of the class that includes the recipient -- of original form of current exception feature -- Status report is_caught: BOOLEAN -- If set, exception is raised. ensure not_is_caught_implies_is_ignorable: not Result implies is_ignorable not_is_ignored: not is_ignored is_ignorable: BOOLEAN -- Is current exception ignorable? is_ignored: BOOLEAN -- If set, no exception is raised. ensure is_ignored_implies_is_ignorable: Result implies is_ignorable not_is_caught: not is_caught is_raisable: BOOLEAN -- Is current exception raisable by raise? feature -- Raise raise -- Raise current exception require is_raisable: is_raisable end -- class EXCEPTION
Class EXCEPTION_MANAGER
class interface EXCEPTION_MANAGER create default_create feature -- Access frozen last_exception: EXCEPTION -- Last exception feature -- Status report is_caught (a_exception: TYPE [EXCEPTION]): BOOLEAN -- If set, type of `a_exception' is raised. ensure not_is_ignored: not is_ignored (a_exception) is_ignorable (a_exception: TYPE [EXCEPTION]): BOOLEAN -- If set, type of `a_exception' is ignorable. is_ignored (a_exception: TYPE [EXCEPTION]): BOOLEAN -- If set, type of `a_exception' is not raised. ensure not_is_caught: not is_caught (a_exception) feature -- Status setting catch (a_exception: TYPE [EXCEPTION]) -- Set type of `a_exception' is_ignored. require a_exception_not_void: a_exception /= Void ensure is_ignored: not is_ignored (a_exception) ignore (a_exception: TYPE [EXCEPTION]) -- Make sure that any exception of code `code' will be -- ignored. This is not the default. require a_exception_not_void: a_exception /= Void is_ignorable: is_ignorable (a_exception) ensure is_caught: is_ignored (a_exception) set_is_ignored (a_exception: TYPE [EXCEPTION]; a_ignored: BOOLEAN) -- Set type of `a_exception' to be `a_ignored'. require a_exception_not_void: a_exception /= Void a_ignored_implies_is_ignorable: a_ignored implies is_ignorable (a_exception) ensure is_ignored_set: is_ignored (a_exception) = a_ignored end -- class EXCEPTION_MANAGER
Default rescue
As specified in ECMA, `default_rescue' in ANY or its descendant is called as unqualified when an internal routine or an attributes with no rescue clause fails. We choose not to invoke it when it is not redefined, or this will be too expensive.
class ANY ... default_rescue do end ... end
Sample code: Raising and catching developer exception
class MY_EXCEPTION inherit DEVELOPER_EXCEPTION end
class A feature f local retried: BOOLEAN do if not retried then my_exception.raise -- Raise user-defined exception. end rescue if last_exception = my_exception then -- Check the exception object was the one user defined. print ("True" + "%N") else print ("False" + "%N") end end my_exception: MY_EXCEPTION once create Result end end
class APPLICATION inherit A create make feature -- Initialization make -- Run application. local a: A retried: BOOLEAN do if not retried then create a a.f end rescue retried := True -- `last_exception' is an object of ROUTINE_FAILURE? if attached {ROUTINE_FAILURE} (create {EXCEPTION_MANAGER}).last_exception as l_exception then print ("True" + "%N") else print ("False" + "%N") end -- `original' exception is the one caused `last_exception'? if last_exception.original = my_exception then print ("True" + "%N") else print ("False" + "%N") end retry end end -- class APPLICATION