There are two fairly gross hacks needed to make this go. One is a
means of taking over errors signaled using stop
or the internal
error
and errorcall
functions and transferring them to the
exception mechanism. This is done by hijacking the error
and
show.error.message
options (i.e. user settings of those options
will be ignored when inside the exception handling mechanism). The
second hack is needed to notify the internal error handling code that
the exception handler mechanism has taken over and we are no longer in
internal error handling code (i.e. the static variable inError
in
errors.c
needs to be set to zero). This is accomplished by
calling Rf_resetStack
, which sets inError
, and undoing
everything else this routine does. (Which means this will break
spectacularly when the internals of that routine change, but hopefully
by then we may have a better way to do this.)
exception
. The class simple.exception
is the class currently
used by stop
and all internal error signals. The constructor by
the same name takes a string describing the exception as argument and
returns a simple.exception
object.
Uncaught exceptions will be processed by the generic function
default.exception.handler
. The method for class exception
calls stop
with the result of applying as.character
to the
exception argument.
The function try.catch
is used to catch exceptions. Its usage is
try.catch(expr, ..., finally = NULL)It evaluates its expression argument in a context where the handlers provided in the
...
argument are available. The finally
expression is then evaluated in the context in which try.catch
was
called; that is, the handlers supplied to the current try.catch
call are not active when the finally
expression is evaluated.
Handlers provided in the ...
argument to try.catch
are
established for the duration of the evaluation of expr
. If no
exception is raised when evaluating expr
then try.catch
returns the value of the expression.
If an exception is raised while evaluating expr
then established
handlers are checked, starting with the most recently established
ones, for one matching the class of the exception. If a handler is
found then control is transferred to the try.catch
call that
established the handler, the handlers in that call are dis-established,
the handler is called with the exception as its argument, and the
result returned by the handler is returned as the value of the
try.catch
call.
If an exception is raised and no handler is found, then the default
handler is called. The context of the call is either the outer-most
try.catch
call or the raise.exception
call if there is no
surrounding try.catch
call.
When a try.catch
call is on the stack, calls to stop
and errors
signaled internally are converted into exceptions of type
simple.exception
and raised by raise.exception
.
The only form of non-local transfer of control that try.catch
can
catch is raising of exceptions. It cannot capture the other three
possibilities: calls to return
, break
, and next
(there are
actually one or two more, depending on how you count, that also are
not caught). Whether these should be made, conceptually at least,
into a kind of exception, or perhaps into something inheriting off a
superclass of exception (in the spirit of Java's Throwable class) is
not clear. try
and restart
currently also do not catch these
three, but perhaps they should.
The printed representation of converted exceptions produced for
stop
calls and internal errors is less that ideal (as is the
method of trapping and converting them) but this should do for getting
a feel for how this might work.
try.catch
does not raise an
exception, the value of the expression is returned. If a finally
expression is provided, it is evaluated before return:
> try.catch(1, finally=print("Hello")) [1] "Hello" [1] 1
A simple exception can be constructed and raised by explicitly calling
the constructor and passing the result to raise.exception
:
> e<-simple.exception("test exception") > raise.exception(e) Error in default.exception.handler.exception(e) : unhandled exception (simple.exception): test exceptionAlternatively,
raise.exception
can be called with a string
argument:
> raise.exception("test exception") Error in default.exception.handler.exception(e) : unhandled exception (simple.exception): test exception
If an exception is raised in an expression protected by a
try.catch
and the exception is not caught, then the finally
expression is evaluated before passing the exception to the default
handler:
> try.catch(raise.exception(e), finally=print("Hello")) [1] "Hello" Error in default.exception.handler.exception(e) : unhandled exception (simple.exception): test exceptionInternal errors and calls to
stop
are converted to simple exceptions:
> try.catch(stop("fred"), finally=print("Hello")) [1] "Hello" Error in default.exception.handler.exception(e) : unhandled exception (simple.exception): Error in catch("__TRY_CATCH__", list(value = expr, throw = FALSE)) : fredThe printed representation of converted exceptions leave something to be desired.
If an exception is raised and a handler is provided, then the result of calling the handler function with the exception object as argument is returned:
> try.catch(raise.exception(e), exception = function(e) e, finally=print("Hello")) [1] "Hello" <simple.exception: test exception> > try.catch(stop("fred"), exception = function(e) e, finally=print("Hello")) [1] "Hello" <simple.exception: Error in catch("__TRY_CATCH__", list(value = expr, throw = FALSE)) : fred
There are (at least) five other forms of non-local exits
return
, break
, and next
Q
command in the browser
quit
or sending a user signal
Should we have a class throwable
that is a superclass of
exception
and can be used to implement all non-local exits?
Doing this could have the benefit of cleaning up some mechanisms we
currently have. For example, we could define a class exit
with a
constructor of the same name that takes the same three arguments as
q
and define q
as
q <- function(save = "default", status = 0, runLast = TRUE) raise.throwable(exit(save, status, runLast))and have the R main loop defined something like
mainloop <- function() repeat try.catch(try.catch(repl(), exception=default.exception.handler), exception = function(e) cat("uncaught exception"), exit = function(e) if (ask.about.exit()) break)Here
repl
would be the actual read-eval-print loop. The outer
exception handler is intended to be be as fail-safe as possible since
the next stop would have to be an exit from mainloop
.
[This is too simplistic even for the current level of concurrency we have and certainly would not work with threads, but the point is we could get clearer semantics for the shutdown process if we do things this way. At the moment, if you cancel a quit things don't seem to go back quite to normal.]
One other reason for explicitly allowing us to manage all non-local
exiting is embedding and callbacks. Suppose an R function foo
does .C("bar", ...)
and bar
calls back into R to a function
baz
. If baz
calls stop
or executes a return
from
foo
a longjmp
will occur that skips over the bar
C level
call frames. A lot of C code is robust to this sort of thing but a
lot isn't. In cases where the C code isn't it would be nice to have a
means of trapping all transfers of control, something like
calling baz
with
try.catch(list(value=baz(...), throw=FALSE), throwable=function(e) list(value=e,throw=TRUE))This would allow the C code to then be given an exit status and to do whatever cleanup it needs to do before returning to
foo
. foo
can then either ignore the throwable it receives, or re-throw it.
Currently we only have try
, which is based on restart
, to do
this sort of thing at the R level. restart
and hence try
only
catch errors, not return
, break
, or next
calls; leaving
browser with Q
also goes through a restart
. Whether this
should be changed to make restart
more absorbent is not clear. At
the C level we currently have another option, which is to establish a
new toplevel context for the call. This is particularly appropriate
for things like running finalizers or processing events where we are
at least conceptually running in a concurrent thread (to support this
there are other stack examinations that should stop at the first
toplevel context they find as well). It may not be the right approach
for nested callbacks where we are conceptually nested deep in the call
stack.
stop
and errorcall
contain the call that raised the
error. This could be folded into the exception mechanism. All
internal error calls currently also save the traceback information.
It would probably make sense to provide this sort of information to
exceptions as well. Java has a somewhat peculiar approach to this.
The constructor of throwables calls a method called
fillInStackTrace
(or something like that) that fills in a stack
trace based on where the constructor is called. This stack trace is
then part of the exception and can be printed by a handler. This
approach assumes that errors will always be signaled by creating an
exception object on the spot. This is not always feasible; for
out-of-memory errors it would be a good idea to allocate the exception
object in advance.
We should probably do something along these lines, but exactly what is not clear.
<simpexcept.R>= <catch and throw> <dynamically scoped variables> <exceptions> <raising and catching exceptions> <converting internal exceptions> .First.lib <- function(lib, pkg) library.dynam(pkg, pkg, lib)
<simpexcept.c>=
#include "Rinternals.h"
<ResetErrorHandling
definition>
catch
and
throw
, which are patterned after the Common Lisp special operators
with the same names.
Catch uses local variables with hopefully unique names to save its tag
argument, provide a means for throw to transfer control to the
catch
call, and provide a place for throw
to place a value.
(It would be better to have a more structured mechanism for
maintaining dynamic state, some form of dynamically scoped variables.)
The means for transferring control is a closure that captures a
promise containing a return
expression. Calling the closure
evaluates the return expression which then causes a return from the
catch
call.
<catch and throw>= (<-U) [D->] catch<-function(tag, expr) { "__CATCH_TAG__" <- as.character(tag) "__CATCH_THROWER__"<- make.thrower(return(get("__CATCH_VALUE__"))) "__CATCH_VALUE__" <- NULL expr } make.thrower <- function(expr) function() expr
Definescatch
,make.thrower
(links are to index).
The throw
function evaluates and saves the value of the expr
argument and then searches for an active catch for the specified tag.
The search begins with the current frame and will find the most
recently established catch if there is more than one. An error is
signaled if no matching catch is found. The value of the expression
argument is placed in the value variable in the frame of the catch
call, the throwing closure created by make.thrower
in the catch
call is obtained and called. This call evaluates the captured
return
expression and causes a return from the catch
expression.
<catch and throw>+= (<-U) [<-D] throw<-function(tag, expr, no.tag = tag.not.found) { value <- expr # forces evaluation of expr tag <- as.character(tag) env <- get.target.frame(tag, "__CATCH_TAG__") assign("__CATCH_VALUE__", value, env = env) fun <- get("__CATCH_THROWER__", env = env) fun() } ##****parent.env with arg > 1 doesn't seem to work as advertised?? get.target.frame <- function(tag, name) { n <- sys.nframe() if (n > 1) for (i in (n-1):1) { env <- sys.frame(i) if (exists(name, env = env) && get(name, env = env) == tag) return(env) } stop(paste("no catch for tag \"", tag, "\"", sep = "")) }
Definesget.target.frame
,throw
(links are to index).
Matching of tags is done with ==
, so tags can be any sort of
object for which this makes sense.
Some simple tests:
<tests>= [D->] catch("x", 1) catch("x", { throw("x", 1); 2 }) catch("x", throw("y", 1))
try.catch
call
on the stack without causing an error. This is done by having
try.catch
create a binding for a variable with a reasonably unique
name and searching for the existence of such a binding in the frame
stack. The function dynamic.exists
does this search.
<dynamically scoped variables>= (<-U) dynamic.exists <- function(name) { n <- sys.nframe() if (n > 1) for (i in (n-1):1) if (exists(name, env = sys.frame(i))) return(TRUE) FALSE; }
Definesdynamic.exists
(links are to index).
This function could be viewed as part of a mechanism for managing
dynamically scoped variables. The rest of such a mechanism would
consist of dynamic.get
for getting the value of the current
dynamic binding, dynamic.assign
for changing the value, and either
a separate mechanism for creating new dynamic bindings or a mechanism
or a variant of dynamic.assign
that does this. It might also be
useful to use a separate mechanism for holding the bindings, a
separate environment for example, to avoid the possibility of name
clashes. Proper interaction with name spaces would also need a look.
It may well be that MzScheme's parameter
mechanism would work well
for us.
exception
. They are assumed to have a method for as.character
that produces a printed representation describing the exception that
occurred. The print method for exceptions uses as.character
to
produce a representation of the exception.
<exceptions>= (<-U) [D->] print.exception <- function(e, ...) cat("<", class(e)[1], ": ", as.character(e), ">\n", sep="")
Definesprint.exception
(links are to index).
The exception class is a virtual class. It has no constructor, and
its as.character
method warns that this method should be
overridden by non-virtual classes. I'm not sure this is really a good
idea, but it seemed useful during development.
<exceptions>+= (<-U) [<-D->] as.character.exception <- function(e, ...) { warning(paste("no as.character method for exception of class", class(e))) as.character(unclass(e)) }
Definesas.character.exception
(links are to index).
One concrete exception class is provided: simple.exception
.
Simple exceptions contain a string
slot that is used to provide
the printed representation.
<exceptions>+= (<-U) [<-D->] simple.exception <- function(string) { class <- c("simple.exception", "exception") structure(list(string=as.character(string)), class=class) } as.character.simple.exception <- function(e, ...) e$string
Definesas.character.simple.exception
,simple.exception
(links are to index).
When an exception occurs for which no handler is available, the
generic function default.exception.handler
is called. The method
for the exception class just calls stop
.
<exceptions>+= (<-U) [<-D] default.exception.handler <- function(e) UseMethod("default.exception.handler", e) default.exception.handler.exception <- function(e) stop(paste("unhandled exception (", class(e)[1], "): ", as.character(e), sep = ""))
stop
and with the internal
error
and errorcall
functions into the exception mechanism I
use a fairly gross device: I turn off error message printing with the
show.error.messages
option and set the error
option to call a
function that grabs the error string, creates a simple exception with
this string, and raises the exception. Since the internal variable
inError
in error.c
is set before the error
option
expression is evaluated, I need to reset this if the error is handled;
this is where the ResetErrorHandling
C routine comes in. With
this approach calls to stop
and error
or errorcall
will
only be converted if a try.catch
call is on the stack.
Error conversion is done by
<converting internal exceptions>= (<-U) [D->] error.converter <- function() { raise.exception(simple.exception(geterrmessage())) }
Defineserror.converter
(links are to index).
The initial setting of the two options is handled by
set.error.options
. This function returns the previous settings of
these two options in a list. The code is a little convoluted for two
reasons:
error
option is NULL
it is not in the options
list, and options()$error
matches the error.messages
option,
which is a logical.
show.error.messages
is set the first time
options()$show.error.messages
returns NULL
, which is not a
valid value to use when setting this option.
<converting internal exceptions>+= (<-U) [<-D->] set.error.options <- function() { op <- options() options(show.error.messages = FALSE) options(error = quote(error.converter())) if (is.logical(op$show.error.messages)) show <- op$show.error.messages else show <- TRUE if (is.logical(op$error)) err <- NULL else err <- op$error list(show.error.messages = show, error = err) }
Definesset.error.options
(links are to index).
During the raising of an exception, the options are set back to default values to prevent infinite recursion. I'm not sure this is really necessary, but for now I'll just do it to be a little safer.
<converting internal exceptions>+= (<-U) [<-D->] reset.error.options <- function() { options(show.error.messages = TRUE) options(error = NULL) }
Definesreset.error.options
(links are to index).
At the end of a try.catch
the original values of the options are
restored by the function
<converting internal exceptions>+= (<-U) [<-D] restore.error.options <- function(op) { options(op) }
Definesrestore.error.options
(links are to index).
raise.exception
takes a single argument representing
an exception and raises the exception. To avoid recursion it first
calls reset.error.options
to turn off the calling handler and turn
on printing of error messages. This corresponds in the current
internal error code to setting inError
to a non-zero value. Next
it converts its argument to a simple exception if it is not already an
exception. This allows raise.exception
to be called with a string
argument, for example. Finally the exception is thrown to the
innermost try.catch
, or the default handler is called if no
try.catch is established.
<raising and catching exceptions>= (<-U) [D->] raise.exception <- function(e) { reset.error.options() # turn off to avoid recursion if (! inherits(e, "exception")) e <- simple.exception(as.character(e)) if (exists.try.catch()) throw("__TRY_CATCH__", list(value = e, throw = TRUE)) else default.exception.handler(e) } exists.try.catch <- function() dynamic.exists("__TRY_CATCH__")
Definesexists.try.catch
,raise.exception
(links are to index).
The try.catch
function uses catch
to establish a target for
raise.exception
to throw an exception to. The catch
expression is contained in a locally
call that also establishes a
marker used by exists.try.catch
to determine whether a
try.catch
form is on the stack. Leaving this local
dis-establishes the try.catch
; it will not be seen during the
remaining processing.
The result returned by the catch
call will be a list with two
named components. If the throw
component is false, then no
exception was raised and the value
component is the value of the
expression. If the throw
component is true, then an exception did
occur, and the exception object is in the value
component.
If an exception is caught, then the options used for converting
internal error need to be turned back on since they have been turned
off by raise.exception
. In addition, if the exception was
converted from an internal error or a call to stop
, then we need
to turn off the internal setting that indicates that we are in error
handling code. This is done by calling a little C routine with
.Call
.
Next, we search the ...
argument for a handler that matches the
exception. If one is found, it is called and the result is returned
as the value of the try.catch
call. Otherwise, we need to raise
the exception again. Before raising the exception, we disable the
on.exit
, restore the error options and evaluate the finally
expression. This needs to be done in case raise.exception
calls
the default handler, since that call will occur from within the
try.catch
call.
<raising and catching exceptions>+= (<-U) [<-D] try.catch <- function(expr, ..., finally = NULL) { op <- set.error.options() on.exit({restore.error.options(op); finally }) result <- local({ "__TRY_CATCH__" <- NULL catch("__TRY_CATCH__", list(value = expr, throw = FALSE)) }) if (result$throw) { set.error.options() # turn back on because turned off in raise .Call("ResetErrorHandling") # is this the right place for this? handlers <- list(...) names <- names(handlers) e <- result$value for (i in seq(along = names)) if (inherits(e, names[i])) return(handlers[[i]](e)) on.exit() # to get restore.error.options(op) # things reasonable finally # for stop calls going to default handler raise.exception(e) } else result$value }
The C function used to reset inError
is ResetErrorHandling
.
This approach is very brittle, since it depends on the way a
particular function Rf_resetStack
happens to be implemented. It
would be much better to provide a hook within error.c
that is
intended for this purpose, but I wanted to get something going that
did not require changes to R, so this will have to do for now. It is
likely to break very soon as changes are made to the internal error
handling code, but I will try to make modifications as necessary to
keep it working.
<ResetErrorHandling
definition>= (U->)
SEXP ResetErrorHandling(void)
{
extern SEXP R_Warnings;
extern int R_CollectWarnings;
extern int R_PPStackTop;
SEXP oldWarnings = R_Warnings;
int oldCollectWarnings = R_CollectWarnings;
int oldPPStackTop = R_PPStackTop;
Rf_resetStack(FALSE);
R_PPStackTop = oldPPStackTop;
R_Warnings = oldWarnings;
R_CollectWarnings = oldCollectWarnings;
return R_NilValue;
}
DefinesResetErrorHandling
(links are to index).
<tests>+= [<-D] try.catch(1, finally=print("Hello")) e<-simple.exception("test exception") raise.exception(e) try.catch(raise.exception(e), finally=print("Hello")) try.catch(stop("fred"), finally=print("Hello")) try.catch(raise.exception(e), exception = function(e) e, finally=print("Hello")) try.catch(stop("fred"), exception = function(e) e, finally=print("Hello"))
ResetErrorHandling
definition>: U1, D2