rick@tetrauk.UUCP (Rick Jones) (11/19/90)
(This is a re-post of an article I tried to send last week. I don't think it
made it to anywhere on the net; if anyone has already received it, I apologise
for the duplication.)
------------------------------------------------------------
On one of my favourite subjects (as readers of this newsgroup may have noticed),
I have been trying to devise ways to extend the effectiveness of Eiffel's
exception handling. As a result, I recently hit on a mechanism for passing
arbitrary objects from an exception occurrence to a rescue clause. The idea
was remotely (only remotely :-) inspired by the proposed exception handling
system for C++.
After considering that this could be suitable as publicly contributed software
to Eiffel-shelf, etc. the implementation turned out to be so simple that I
thought I might as well just post it to the net. What follows is largely my
notes on the system (written originally for internal consumption, excuse the
verbosity); the source of the two key classes is appended.
I would of course like to hear any comments on this from interested parties
(before or after trying it).
------------------------------------------------------------
Enhanced programmed exceptions for Eiffel
=========================================
Rick Jones version: 1.1
Tetra Ltd. date: 90/11/12
These classes provide an enhanced exception mechanism for programmed
exceptions. It provides an extension to the "raise" facility, allowing
arbitrary objects to be passed between the raise and trap points of an
exception. Exception propagation is also enhanced. The standard "raise"
mechanism is used internally, the system is written entirely in conventional
Eiffel classes.
Classes
Only two fundamental classes are involved, these are EXCEP and EXTRAP.
EXCEP is a class which provides a basic exception object. The creation
of an EXCEP object implicitly raises an exception (i.e. calling such a
Create will NOT return to the existing line of code).
A class which wishes to trap exceptions resulting from creation of
EXCEP objects must inherit from EXTRAP. EXTRAP objects should NOT be
instantiated.
Note that a class which creates EXCEP objects does not need to inherit
from any other class in order to use the facility.
Facilities
EXTRAP provides a facility for descendants in the form of the feature
"last_excep". This should be used by code within a rescue clause, and
returns an object reference of type EXCEP. If the exception which
causes the rescue clause to be invoked is the result of creating an
exception of class EXCEP, "last_excep" will return the EXCEP object
which was created at that time. Otherwise it will return a Void. It
will also return a Void if called outside a rescue clause.
Examples
The following code shows examples of using EXCEP and EXTRAP:
------------------------------------------------------------------------------
class CATCH -- test special exception catching
inherit
STD_FILES ;
EXTRAP ;
feature
Create is
local
excep: EXCEP ;
do
-- excep will only be non-void if it was assigned a value
-- in the rescue clause, indicating the "retry" condition
if excep.Void then
-- normal condition
try ;
else
-- we are being re-tried
putstring ("Trapped an exception\n") ;
-- will exit program
end ;
rescue
-- get the last exception - will only be non-void
-- if the exception was of class EXCEP
excep := last_excep ;
-- retry only if we found such an exception
if not excep.Void then retry end ;
putstring ("No retry - abort\n") ;
end ;
try is
-- a dummy routine to force an exception,
-- of course this need NOT be in the same class
-- as the routine which rescues the exception
local
excep: EXCEP ;
do
-- create an exception
excep.Create ;
-- there is no return from the Create above!
end ;
end ;
------------------------------------------------------------------------------
It should be noted that since creation of an EXCEP object always raises
an exception, an EXCEP entity will in general only be non-void if
assigned a value (directly or indirectly) via the use of "last_excep".
Since this itself only yields a non-void result when used in a rescue
clause, EXCEP objects are only ever accessible after they have been
trapped following an exception. If EXCEP references are only ever used
as local entities, it is impossible to erroneously have an accessible
EXCEP object when an exception has not occurred.
Inheriting from EXCEP
Since this system passes back exception identifiers as objects, it is
possible to define more complex exception objects by inheritance. A
programmer may define an exception class which inherits from EXCEP, and
allow it to contain more complex information regarding the exception.
As this is a general requirement, a class STD_EXCEP is provided which
contains contain two simple attributes, "code" and "text", whose
initial values are supplied in the call to Create. These are arbitrary
values to allow the programmer to identify different exceptions.
The following is the code of the STD_EXCEP class:
------------------------------------------------------------------------------
class STD_EXCEP -- extended exceptions, standard class
-- this class provides an exception object with two attributes,
-- an integer and a string; this should suffice for many cases
-- where some information needs to be attached to exceptions
export
code, text, print_exception
-- standard features for coding exceptions
inherit
EXCEP -- general exception class
feature
code: INTEGER ; -- arbitrary code for exception
text: STRING ; -- arbitrary text for exception
Create (c_code: INTEGER, c_text: STRING) is
-- general create, defining a code & text
do
code := c_code ; text := c_text ;
raisex (Current) ;
ensure
code = c_code ;
text = c_text ;
-- an exception is raised, there is no return from Create
end ;
print_exception( msg: STRING ) is
-- Print error code and text on standard error
-- with msg appended
external
fprintf: INTEGER language "C";
local
fmt: STRING;
res: INTEGER;
do
if not msg.Void then
fmt := "%d: %s: %s\n";
res := fprintf( io.error.file_pointer, fmt.to_c,
code, text.to_c, msg.to_c );
else
fmt := "%d: %s\n";
res := fprintf( io.error.file_pointer, fmt.to_c,
code, text.to_c );
end;
end; -- print_exception
end
------------------------------------------------------------------------------
It is important that the Create routine of the new class either invokes
the inherited (renamed) Create, or calls the inherited feature
"raisex", passing "Current" as the parameter. This will ensure that
the exception is correctly raised. It must also be the last thing
which the Create routine does, as it will not return.
When an exception is trapped, reverse assignment (the ?= operator) may
be used to establish if the trapped exception is a specific descendant
of EXCEP rather than treat it as the general case. This may be done
either in the rescue clause, or in the "retry" section of the routine
body.
A modification of the above example, where STD_EXCEP is raised, is
shown below:
------------------------------------------------------------------------------
class CATCH -- test special exception catching
inherit
STD_FILES ;
EXTRAP ;
feature
Create is
local
excep: EXCEP ;
std_excep: STD_EXCEP ;
do
-- excep will only be non-void if it was assigned a value
-- in the rescue clause, indicating the "retry" condition
if excep.Void then
-- normal condition
try ;
else
-- we are being re-tried,
-- first treat as a general exception
putstring ("Trapped an exception\n") ;
-- is it a "standard" exception?
std_excep ?= excep ;
if not std_excep.Void then
std_excep.print_exception ;
end ;
-- will exit program
end ;
rescue
-- get the last exception - will only be non-void
-- if the exception was of class EXCEP
excep := last_excep ;
-- retry only if we found such an exception
if not excep.Void then retry end ;
putstring ("No retry - abort\n") ;
end ;
try is
-- a dummy routine to force an exception,
-- here the exception is a descendant of EXCEP
local
excep: STD_EXCEP ;
do
-- create an exception
excep.Create (1, "Exception 1") ;
-- there is no return from the Create above!
end ;
end ;
------------------------------------------------------------------------------
Application-specific exception classes may inherit either directly from
EXCEP, or from STD_EXCEP, according to the requirements of the design.
Propagating an exception
It is often a requirement to trap an exception, but rather than retry,
propagate the exception back, maybe after doing some cleanup work in
the rescue clause. It may also be desirable to change the nature of
the exception being propagated, but this should be done without
actually raising a new exception within a rescue clause. Exception
substitution is required.
The EXCEP class handles this completely transparently, allowing a new
EXCEP object to be created while in a rescue clause. If the exception
being rescued is already of an EXCEP class, then the new exception
object is substituted for the previous one and no new exception is
raised. This prevents the current rescue clause being re-entered, so
avoiding indefinite loops. It should be noted that in this situation
the Create routine WILL return, allowing the exception to fall-back in
the normal way.
The following further adaptation of the previous examples shows this in
cascading rescue clauses:
------------------------------------------------------------------------------
class CATCH -- test special exception catching
inherit
STD_FILES ;
EXTRAP ;
feature
Create is
local
excep: STD_EXCEP ; -- only handles standard exceptions
do
-- excep will only be non-void if it was assigned a value
-- in the rescue clause -
-- this indicates the "retry" condition
if excep.Void then
-- normal condition
try ;
else
-- we are being re-tried,
putstring ("Trapped an exception\n") ;
putstring ("code = ") ;
putint (excep.code) ;
putstring (", text = ") ;
putstring (excep.text) ;
new_line ;
-- will exit program
end ;
rescue
-- get the last exception - will only be non-void
-- if the exception conforms to class STD_EXCEP
excep ?= last_excep ;
-- retry only if we found such an exception
if not excep.Void then retry end ;
putstring ("No retry - abort\n") ;
end ;
try is
-- a dummy routine to propagate an exception,
-- descendants of EXCEP which do not conform to
-- STD_EXCEP will be propagated back as the latter
local
other: SOME_CLASS ;
std_excep: STD_EXCEP ;
do
-- we presume that this call to other results in
-- an exception being raised
other.Create ;
rescue
-- see if the exception is of class STD_EXCEP, if not,
-- but of class EXCEP , propagate a standard exception
-- (an example of reverse assignment in the rescue clause)
std_excep ?= last_excep ;
if std_excep.Void and not last_excep.Void then
-- we propagate a general exception
std_excep.Create (0, "Undefined exception") ;
end ;
-- no retry, but the exception may have been changed
end ;
end ;
------------------------------------------------------------------------------
Conclusions
This mechanism provides a clean and simple way of passing exception
objects to rescue clauses, using the standard "raise" mechanism of the
language.
It should be noted that as well as using "raise" as the exception
mechanism, it depends on the current behaviour of the
"is_programmer_exception" feature of class EXCEPTIONS. This will never
return True outside a rescue clause, since one of the effects of the
"retry" statement is to clear the name of the raised exception (the
string returned by the "programmer_exception_name" feature). There is
a case for not clearing this name on a retry when the simple "raise"
exception mechanism is used, but for the purposes of this system the
present method is preferable.
There is also an interesting issue of the semantics of Create under
exception conditions. It is generally agreed that when an exception
occurs during object creation the object in question should be
inaccessible. This is on the basis that the object can be presumed to
be in an invalid state. The current implementation of Eiffel does not
in fact behave this way, but it is anticipated that it will in future.
However, in this mechanism the exception object being created raises an
exception but must continue to exist so that it can be accessed by the
rescuing routine.
There need not be a real conflict if the different conditions are
examined. In the normal case of Create, a reference to the new object
is assigned to the entity associated with Create, e.g. the statements:
a: A_CLASS
a.Create
can be considered equivalent to the notion of:
a := new (A_CLASS)
(a style of syntax used in some other OOPLs).
If the creation of the object fails, the important point is that the
entity "a" should remain void. The fact that the object has been
created and partially initialised will in the normal case result in it
being collected by the garbage collector since it is unreachable. If,
however, the Create routine of A_CLASS deliberately chooses to store a
reference to itself somewhere else then this need not be prevented,
even under exception conditions. This is the basis of the EXCEP class,
since a reference to the new exception object is stored in a Once CELL,
accessible to classes inheriting from EXTRAP, immediately before the
exception is raised.
In short, the fact that a Create routine never returns a reference for
assignment to the invoking entity need not prevent the object existing
and being referred to via other routes.
------------------------------------------------------------
Source of EXTRAP:
indexing
version: "1.2" ;
date: "90/11/12" ;
class EXTRAP -- an extended exception mechanism
-- this class supports trapping of extended exceptions
-- and must be inherited from by any class which contains a
-- rescue clause to deal with these exceptions
inherit
EXCEPTIONS -- library exception class
feature
-- feature {NONE}
stored_excep: CELL[EXCEP] is
-- a common reference to the last exception
once
Result.Create ;
end ;
-- feature {} -- used only by EXCEP descendant classes
magic_text: STRING is "EXCEPTIONAL" ;
our_rescue: BOOLEAN is
-- True if in a rescue clause and
-- the trapped exception is one of ours
do
Result := is_programmer_exception (magic_text) ;
end ;
-- feature {} -- used only by application descendant classes
last_excep: EXCEP is
do
if our_rescue then
Result := stored_excep.item ;
end ;
end ;
end
------------------------------------------------------------
Source of EXCEP:
indexing
version: "1.2" ;
date: "90/11/12" ;
class EXCEP -- an extended exception mechanism
-- this class provides objects which represent actual exceptions,
-- the exception is raised when such an object is created;
-- it may be inherited from to yield exception objects with
-- special properties according to actual requirements
inherit
EXTRAP -- exception trap class
feature
Create is
do
raisex (Current) ;
ensure
-- an exception is raised, there is no return from Create
end ;
-- feature {} -- may be called by descendants
raisex (excep: like Current) is
-- store and raise the given exception -
-- if already rescuing, no raise is called
do
stored_excep.put (excep) ;
if not our_rescue then
raise (magic_text) ;
end ;
end ;
end
------------------------------------------------------------
--
Rick Jones
Tetra Ltd. Maidenhead, Berks, UK
rick@tetrauk.uucp Absence of .signature is not .signature of absence