Skip to main content
Engineering LibreTexts

12.8: Finding Handlers

  • Page ID
    45958
  • \( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \) \( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)\(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\) \(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\)\(\newcommand{\AA}{\unicode[.8,0]{x212B}}\)

    We will now take a look at how exception handlers are found and fetched from the execution stack when an exception is signaled. However, before we do this, we need to understand how the control flow of a program is internally represented in the virtual machine.

    At each point in the execution of a program, the execution stack of the program is represented as a list of activation contexts. Each activation context represents a method invocation and contains all the information needed for its execution, namely its receiver, its arguments, and its local variables. It also contains a reference to the context that triggered its creation, i.e., the activation context associated with the method execution that sent the message that created this context. In Pharo, the class MethodContext (whose superclass is ContextPart) models this information. The references between activation contexts link them into a chain: this chain of activation contexts is Smalltalk’s execution stack.

    Suppose that we attempt to open a FileStream on a non-existent file from a doIt. A FileDoesNotExistException will be signaled, and the execution stack will contain MethodContexts for doIt, oldFileNamed:, and signal, as shown in Figure \(\PageIndex{1}\).

    Since everything is an object in Smalltalk, we would expect method contexts to be objects. However, some Smalltalk implementations use the native C execution stack of the virtual machine to avoid creating objects all the time. The current Pharo virtual machine does actually use full Smalltalk objects all the time; for speed, it recycles old method context objects rather than creating a new one for each message-send.

    Pharo execution stack.
    Figure \(\PageIndex{1}\): A Pharo execution stack.

    When we send aBlock on: ExceptionClass do: actionHandler, we intend to associate an exception handler (actionHandler) with a given class of exceptions (ExceptionClass) for the activation context of the protected block aBlock. This information is used to identify and execute actionHandler whenever an exception of an appropriate class is signaled; actionHandler can be found by traversing the stack starting from the top (the most recent message-send) and working downward to the context that sent the on:do: message.

    If there is no exception handler on the stack, the message defaultAction will be sent either by ContextPart»handleSignal: or by UndefinedObject»handleSignal:. The latter is associated with the bottom of the stack, and is defined as follows:

    UndefinedObject»handleSignal: exception
        "When no more handler (on:do:) context is left in the sender chain, this gets called.
            Return from signal with default action."
        ^ exception resumeUnchecked: exception defaultAction 
    

    The message handleSignal: is sent by Exception»signal. When an exception E is signaled, the system identifies and fetches the corresponding exception handler by searching down the stack as follows:

    1. Look in the current activation context for a handler, and test if that handler canHandleSignal: E.
    2. If no handler is found and the stack is not empty, go down the stack and return to step 1.
    3. If no handler is found and the stack is empty, then send defaultAction to E. The default implementation in the Error class leads to the opening of a debugger.
    4. If the handler is found, send to it value: E.

    Nested Exceptions. Exception handlers are outside of their own scope. This means that if an exception is signaled from within an exception handler — what we call a nested exception — a separate handler must be set to catch the nested exception.

    Here is an example where one on:do: message is the receiver of another one; the second will catch errors signaled by the handler of the first (remember that the result of on:do: is either the protected block value or the handler action block value):

    result := [[ Error signal: 'error 1' ]
        on: Exception
        do: [ Error signal: 'error 2' ]]
            on: Exception
            do: [:ex | ex description ].
    result    →    'Error: error 2'
    

    Without the second handler, the nested exception will not be caught, and the debugger will be invoked.

    An alternative would be to specify the second handler within the first one:

    result := [ Error signal: 'error 1' ]
        on: Exception
        do: [[ Error signal: 'error 2' ]
            on: Exception
            do: [:ex | ex description ]].
    result    →    'Error: error 2'
    

    This is subtle, but important point - study it carefully.


    This page titled 12.8: Finding Handlers is shared under a CC BY-SA 3.0 license and was authored, remixed, and/or curated by Alexandre Bergel, Damien Cassou, Stéphane Ducasse, Jannik Laval (Square Bracket Associates) via source content that was edited to the style and standards of the LibreTexts platform; a detailed edit history is available upon request.