Exceptions are normal Prolog terms that are handled specially by the
PREDICATE macro when they are used by a C++ throw
, and
converted into Prolog exceptions. The exception term may not be unbound;
that is, throw(_) must raise an error. The C++ code and underlying C
code do not explicitly check for the term being a variable, and
behaviour of raising an exception that is an unbound term is undefined,
including the possibility of causing a crash or corrupting data.
The Prolog exception term error(Formal, _) is special. If the 2nd
argument of error/2
is undefined, and the term is thrown, the system finds the catcher (if
any), and calls the hooks in library(prolog_stack) to add the context
and stack trace information when appropriate. That is, throw
PlDomainError(Domain,Culprit)
ends up doing the same thing as
calling
PL_domain_error(Domain,Culprit)
which internally
calls
PL_raise_exception() and returns control back to Prolog.
The VM handling of calling to C finds the FALSE
return
code, checks for the pending exception and propagates the exception into
the Prolog environment. As the term references (term_t
)
used to create the exception are lost while returning from the foreign
function we need some way to protect them. That is done using a global term_t
handle that is allocated at the epoch of Prolog.
PL_raise_exception() sets this to the term using PL_put_term().
PL_exception(0) returns the global exception term_t
if it is bound and 0 otherwise.
Special care needs to be taken with data backtracking using
PL_discard_foreign_frame() or PL_close_query() because
that will invalidate the exception term. So, between raising the
exception and returning control back to Prolog we must make sure not to
do anything that invalidates the exception term. If you suspect
something like that to happen, use the debugger with a breakpoint on
__do_undo__LD() defined in pl-wam.c
.
In order to always preserve Prolog exceptions and return as quickly as possible to Prolog on an exception, some of the C++ classes can throw an exception in their destructor. This is theoretically a dangerous thing to do, and can lead to a crash or program termination if the destructor is envoked as part of handling another exception.