The basic idea of exception handling in modern programming languages
is to separate the detection of an exceptional situation
from the code that handles it.
While control is being transferred
from the point where an exception has been raised
to the point where it is handled,
the runtime stack is usually “unwound,”
causing all expressions, statements, and function invocations in between
to terminate abruptly.
In particular,
it is impossible to transfer control back to the raising point
and resume execution there,
even if the exception handler
had been able to “cure” the cause of the problem.
Applying this “termination behaviour”
to a real-life situation such as a tire breakdown
would mean to abort the entire journey
(corresponding to a possibly long and complex computation)
and return to the starting point,
instead of changing wheels there and then
(executing an exception handler)
and afterwards continuing the journey
(resuming execution at the exception point).
To give a more programming-related example,
if an out-of-memory error is raised during a complex computation
and the responsible exception handler is able to regain some memory
(e. g., by freeing data structures
which merely cache frequently needed values for performance reasons),
it would be highly desirable to resume the interrupted computation afterwards,
just as if nothing has happened.
In the C++ standard library,
this is achieved by invoking a so-called new_handler
function
instead of immediately raising an exception;
if this function is able to obtain additional memory,
it returns normally and execution continues,
while otherwise it raises an exception,
causing the current computation to be aborted.
Even though the C++ approach of handling out-of-memory situations solves the problem at hand, it actually reveals a shortcoming of today's exception handling mechanisms, as it cannot be realized with exceptions alone, but requires an additional handler function. Furthermore, it is tailored to out-of-memory situations, even though resuming execution after having successfully handled an exception would make sense in other situations as well.
The most obvious solution to the problems described above
is to extend today's exception handling mechanisms
with the possibility of resumption.
Syntactically,
this can be achieved by allowing a throw
statement
to have one or more accept
clauses,
which are syntactically quite similar
to the catch
clauses of a try
statement
(cf. the example code below).
If a throw
statement possesses accept
clauses,
the runtime stack is not unwound
while searching for a matching handler in the usual way.
If the handler,
i. e., catch
block,
is able to solve the problem that caused the exception,
it can cause execution to be resumed at the throw
statement
by executing a resume
statement.
In the same way a throw
statement
is able to “transport” information to a catch
block
by passing an exception object,
a resume
statement
is able to transport information back to the throw
statement
by passing a “solution” object to one of its accept
blocks.
If the handler does not execute a resume
statement,
because it has not been able to cure the problem,
execution continues as usual after the try
statement;
however,
since the runtime stack has not been unwound yet,
this is done now before actually continuing.
try { ...... throw new SomeException(...) accept (Solution1 s1) { ...... } accept (Solution2 s2) { ...... } ...... } catch (SomeException x) { ...... if (/* problem has been solved someway */) { resume new Solution1(...); } else if (/* problem has been solved another way */) { resume new Solution2(...); } }If a
throw
statement does not possess accept
clauses,
it is executed in the usual way,
i. e., the stack is unwound while searching for a matching handler,
implying that resumption is impossible in that case.
If the catch
block executes a resume
statement anyway
– which cannot be statically prevented by the compiler,
since throw
statements and catch
clauses
may reside in different methods (of different classes)
and the same catch
block could handle
both exceptions thrown by simple, non-resumable throw
statements
and by resumable throw
statements with accept
clauses –,
this could be either simply ignored
or throw itself a (non-resumable) runtime exception.
The same is true
if a resume
statement cannot find a matching accept
clause.
A completely different solution to the problems described in the motivation section, is to entirely replace existing exception handling mechanisms with a totally different approach. This is in fact possible using global and local virtual functions and non-local jump statements. The interested reader is referred to the respective publication mentioned below.
[1] A. Gruler, C. Heinlein:
"Exception Handling with Resumption: Design and Implementation in Java."
In: H. R. Arabnia (ed.): Proc. Int. Conf. on Programming Languages and Compilers (PLC'05) (Las Vegas, NV, June 2005), 165–171.
(PostScript, PDF)
Describes the “obvious solution” mentioned above,
i. e., an extension of the Java exception handling mechanism
with resume
statements and accept
clauses.
[2] C. Heinlein:
"Local Virtual Functions."
In: R. Hirschfeld, R. Kowalczyk, A. Polze, M. Weske (eds.): NODe 2005, GSEM 2005 (Erfurt, Germany, September 2005). Lecture Notes in Informatics P-69, Gesellschaft für Informatik e. V., Bonn, 2005, 129–144.
(PostScript, PDF)
Describes local virtual functions
as an extension of global virtual functions
as well as non-local jump statements,
which can be used, amongst others,
to achieve exception handling with the possibility of resumption,
without requiring an explicit exception handling mechanism at all.