## 4.10 Exception handling

The predicates catch/3 and throw/1 provide ISO compliant raising and catching of exceptions.

[ISO]catch(:Goal, +Catcher, :Recover)
  Behaves as call/1 if no exception is raised when executing Goal. If an exception is raised using throw/1 while Goal executes, and the Goal is the innermost goal for which Catcher unifies with the argument of throw/1, all choice points generated by Goal are cut, the system backtracks to the start of catch/3 while preserving the thrown exception term, and Recover is called as in call/1.

  As of version 9.3.13, constraints (attributed variables) in Catcher are respected. If evaluating the constraint raises an exception, the most urgent exception is preserved (see section 4.10.2) and searching for a matching catch/3 call is continued. If both exceptions are equally urgent, the exception raised by the constraint evaluation is preserved.

  The overhead of calling a goal through catch/3 is comparable to call/1. Recovery from an exception is much slower, especially if the exception term is large due to the copying thereof or is decorated with a stack trace using, e.g., the library `library(prolog_stack)` based on the prolog_exception_hook/5 hook predicate to rewrite exceptions.

[ISO]throw(+Exception)
  Raise an exception. The system looks for the innermost catch/3 ancestor for which Exception unifies with the Catcher argument of the catch/3 call. See catch/3 for details.

  ISO demands that throw/1 make a copy of Exception, walk up the stack to a catch/3 call, backtrack and try to unify the copy of Exception with Catcher. SWI-Prolog delays backtracking until it actually finds a matching catch/3 goal. The advantage is that we can start the debugger at the first possible location while preserving the entire exception context if there is no matching catch/3 goal. This approach can lead to different behaviour if Goal and Catcher of catch/3 call shared variables. We assume this to be highly unlikely and could not think of a scenario where this is useful.82I'd like to acknowledge Bart Demoen for his clarifications on these matters.

  In addition to explicit calls to throw/1, many built-in predicates throw exceptions directly from C. If the Exception term cannot be copied due to lack of stack space, the following actions are tried in order:

  1. If the exception is of the form `error(Formal, ImplementationDefined)`, try to raise the exception without the ImplementationDefined part.
  2. Try to raise `error(`resource_error(stack)`, global)`.
  3. Abort (see abort/0).

  If an exception is raised in a call-back from C (see chapter 12) and not caught in the same call-back, PL_next_solution() fails and the exception context can be retrieved using PL_exception().

catch_with_backtrace(:Goal, +Catcher, :Recover)
  As catch/3, but if library `library(prolog_stack)` is loaded and an exception of the shape `error(Format, Context)` is raised Context is extended with a backtrace. To catch an error and print its message including a backtrace, use the following template:

      :- use_module(library(prolog_stack)).

          ...,
          catch_with_backtrace(Goal, Error,
                               print_message(error, Error)),
          ...,

  This is good practice for a catch-all wrapper around an application. See also main/0 from library `library(main)`.

### 4.10.1 Unwind exceptions

Starting with SWI-Prolog 9.3.13, SWI-Prolog introduces a new class of reserved exceptions. An exception of the shape `unwind(Term)` is handled special by catch/3 and friends. Rather than simply calling the Recover goal (third argument), catch/3 acts as if called as

        catch(Goal, Ball, call_cleanup(once(Recover), throw(Ball)))

The above implies that cleanup that may be required on exceptions should use call_cleanup/2 (or one of its variations) or should perform the cleanup in the Recover goal. For example, the following does not properly cleanup after an exception:

        (   catch(Goal, Error, true)
        ->  (   var(Error)
            ->  <no exception>
            ;   <cleanup>      % NOT called on throw(unwind(...))
                ...

This implies that, unless the user ensures Recover does not terminate, the exception will unwind the entire Prolog stack. While foreign code that catches these exceptions should propagate them to the parent environment, we cannot enforce this behaviour.

Currently, this mechanism defines these values for Term:

abort
  Abort the current Prolog thread. This exception is raised by abort/0. Previous versions raised `'$aborted'` using the same unwind semantics.
halt(Status)
  In the standard setup, if `unwind(`halt(Status)`)` is raised in the `main` thread, this system halts with exit code Status
thread_exit(ExitValue)
  This exception is raised by thread_exit/1. See this predicate for details.

### 4.10.2 Urgency of exceptions

Under some conditions an exception may be raised as a result of handling another exception. Below are some of the scenarios:

- The predicate setup_call_cleanup/3 calls the cleanup handler as a result of an exception and the cleanup handler raises an exception itself. In this case the most urgent exception is propagated into the environment.
- Raising an exception fails due to lack of resources, e.g., lack of stack space to store the exception. In this case a resource exception is raised. If that too fails the system tries to raise a resource exception without (stack) context. If that fails it will raise the exception `unwind(abort)`, also raised by abort/0.
- Certain callback operations raise an exception while processing another exception or a previous callback already raised an exception before there was an opportunity to process the exception. The most notable callback subject to this issue are prolog_event_hook/1 (supporting e.g., the graphical debugger), prolog_exception_hook/5 (rewriting exceptions, e.g., by adding context) and print_message/2 when called from the core facilities such as the internal debugger. As with setup_call_cleanup/3, the most urgent exception is preserved.

If the most urgent exceptions needs to be preserved, the following exception ordering is respected, preserving the topmost matching error.

1. `unwind(halt(_))` (halt/1)
2. `unwind(thread_exit(_))` (thread_exit/1)
3. `unwind(abort)` (abort/0)
4. `time_limit_exceeded` (call_with_time_limit/2)
5. `error(`resource_error(Resource)`, Context)`
6. `error(Formal, Context)`
7. All other exceptions

Note The above resolution is not described in the ISO standard. This is not needed either because ISO does not specify setup_call_cleanup/3 and does not deal with environment management issues such as (debugger) callbacks. Neither does it define abort/0 or timeout handling. Notably abort/0 and timeout are non-logical control structures. They are implemented on top of exceptions as they need to unwind the stack, destroy choice points and call cleanup handlers in the same way. However, the pending exception should not be replaced by another one before the intended handler is reached. The abort exception cannot be caught, something which is achieved by wrapping the cleanup handler of catch/3 into `call_cleanup(Handler, abort)`.

### 4.10.3 Debugging and exceptions

Before the introduction of exceptions in SWI-Prolog a runtime error was handled by printing an error message, after which the predicate failed. If the Prolog flag debug_on_error was in effect (default), the tracer was switched on. The combination of the error message and trace information is generally sufficient to locate the error.

With exception handling, things are different. A programmer may wish to trap an exception using catch/3 to avoid it reaching the user. If the exception is not handled by user code, the interactive top level will trap it to prevent termination.

If we do not take special precautions, the context information associated with an unexpected exception (i.e., a programming error) is lost. Therefore, if an exception is raised which is not caught using catch/3 and the top level is running, the error will be printed, and the system will enter trace mode.

If the system is in a non-interactive call-back from foreign code and there is no catch/3 active in the current context, it cannot determine whether or not the exception will be caught by the external routine calling Prolog. It will then base its behaviour on the Prolog flag debug_on_error:

- current_prolog_flag(debug_on_error, false)
  The exception does not trap the debugger and is returned to the foreign routine calling Prolog, where it can be accessed using PL_exception(). This is the default.
- current_prolog_flag(debug_on_error, true)
  If the exception is not caught by Prolog in the current context, it will trap the tracer to help analyse the context of the error.

While looking for the context in which an exception takes place, it is advised to switch on debug mode using the predicate debug/0. The hook prolog_exception_hook/5 can be used to add more debugging facilities to exceptions. An example is the library `library(http/http_error)`, generating a full stack trace on errors in the HTTP server library.

### 4.10.4 The exception term

#### 4.10.4.1 General form of the ISO standard exception term

The predicate throw/1 takes a single argument, the exception term, and the ISO standard stipulates that the exception term be of the form `error(Formal, Context)` with:

- Formal
  the‘formal’description of the error, as listed in chapter 7.12.2 pp. 62-63 ("Error classification") of the ISO standard. It indicates the error class and possibly relevant error context information. It may be a compound term of arity 1,2 or 3 - or simply an atom if there is no relevant error context information.

- Context
  additional context information beyond the one in Formal. If may be unset, i.e. a fresh variable, or set to something that hopefully will help the programmer in debugging. The structure of Context is left unspecified by the ISO Standard, so SWI-Prolog creates it own convention (see below).

Thus, constructing an error term and throwing it might take this form (although you would not use the illustrative explicit naming given here; instead composing the exception term directly in a one-liner):

    Exception = error(Formal, Context),
    Context   = ... some local convention ...,
    Formal    = type_error(ValidType, Culprit), % for "type error" for example
    ValidType = integer,                        % valid atoms are listed in the ISO standard
    Culprit   = ... some value ...,
    throw(Exception)

Note that the ISO standard formal term expresses what should be the case or what is the expected correct state, and not what is the problem. For example:

- If a variable is found to be uninstantiated but should be instantiated, the error term is `instantiation_error`: The problem is not that there is an unwanted instantiation, but that the correct state is the one with an instantiated variable.

- In case a variable is found to be instantiated but should be uninstantiated (because it will be used for output), the error term is `uninstantiation_error(Culprit)`: The problem is not that there is lack of instantiation, but that the correct state is the one which Culprit (or one of its subterms) is more uninstantiated than is the case.

- If you try to disassemble an empty list with compound_name_arguments/3, the error term is `type_error(compound,[])`. The problem is not that ``[]`` is (erroneously) a compound term, but that a compound term is expected and ``[]`` does not belong to that class.

#### 4.10.4.2 Throwing exceptions from applications and libraries

User predicates are free to choose the structure of their exception terms (i.e., they can define their own conventions) but should adhere to the ISO standard if possible, in particular for libraries.

Notably, exceptions of the shape `error(Formal,Context)` are recognised by the development tools and therefore expressing unexpected situations using these exceptions improves the debugging experience.

In SWI-Prolog, the second argument of the exception term, i.e., the Context argument, is generally of the form `context(Location, Message)`, where:

- Location
  describes the execution context in which the exception occurred. While the Location argument may be specified as a predicate indicator (Name/Arity), it is typically filled by the `library(prolog_stack)` library. This library recognises uncaught errors or errors caught by catch_with_backtrace/3 and fills the Location argument with a backtrace.

- Message
  provides an additional description of the error or can be left as a fresh variable if there is nothing appropriate to fill in.

ISO standard exceptions can be thrown via the predicates exported from `library(error)`. Termwise, these predicates look exactly like the Formal of the ISO standard error term they throw:

- instantiation_error/1 (the argument is not used: ISO specifies no argument)
- uninstantiation_error/1
- type_error/2
- domain_error/2
- existence_error/2
- existence_error/3 (a SWI-Prolog extension that is not ISO)
- permission_error/3
- representation_error/1
- resource_error/1
- syntax_error/1