Skip to main content
Engineering LibreTexts

12.16: Exceptions Implementation

  • Page ID
    45977
  • \( \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}}\)

    \( \newcommand{\vectorA}[1]{\vec{#1}}      % arrow\)

    \( \newcommand{\vectorAt}[1]{\vec{\text{#1}}}      % arrow\)

    \( \newcommand{\vectorB}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)

    \( \newcommand{\vectorC}[1]{\textbf{#1}} \)

    \( \newcommand{\vectorD}[1]{\overrightarrow{#1}} \)

    \( \newcommand{\vectorDt}[1]{\overrightarrow{\text{#1}}} \)

    \( \newcommand{\vectE}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash{\mathbf {#1}}}} \)

    \( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)

    \( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)

    \(\newcommand{\avec}{\mathbf a}\) \(\newcommand{\bvec}{\mathbf b}\) \(\newcommand{\cvec}{\mathbf c}\) \(\newcommand{\dvec}{\mathbf d}\) \(\newcommand{\dtil}{\widetilde{\mathbf d}}\) \(\newcommand{\evec}{\mathbf e}\) \(\newcommand{\fvec}{\mathbf f}\) \(\newcommand{\nvec}{\mathbf n}\) \(\newcommand{\pvec}{\mathbf p}\) \(\newcommand{\qvec}{\mathbf q}\) \(\newcommand{\svec}{\mathbf s}\) \(\newcommand{\tvec}{\mathbf t}\) \(\newcommand{\uvec}{\mathbf u}\) \(\newcommand{\vvec}{\mathbf v}\) \(\newcommand{\wvec}{\mathbf w}\) \(\newcommand{\xvec}{\mathbf x}\) \(\newcommand{\yvec}{\mathbf y}\) \(\newcommand{\zvec}{\mathbf z}\) \(\newcommand{\rvec}{\mathbf r}\) \(\newcommand{\mvec}{\mathbf m}\) \(\newcommand{\zerovec}{\mathbf 0}\) \(\newcommand{\onevec}{\mathbf 1}\) \(\newcommand{\real}{\mathbb R}\) \(\newcommand{\twovec}[2]{\left[\begin{array}{r}#1 \\ #2 \end{array}\right]}\) \(\newcommand{\ctwovec}[2]{\left[\begin{array}{c}#1 \\ #2 \end{array}\right]}\) \(\newcommand{\threevec}[3]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \end{array}\right]}\) \(\newcommand{\cthreevec}[3]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \end{array}\right]}\) \(\newcommand{\fourvec}[4]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \\ #4 \end{array}\right]}\) \(\newcommand{\cfourvec}[4]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \\ #4 \end{array}\right]}\) \(\newcommand{\fivevec}[5]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \\ #4 \\ #5 \\ \end{array}\right]}\) \(\newcommand{\cfivevec}[5]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \\ #4 \\ #5 \\ \end{array}\right]}\) \(\newcommand{\mattwo}[4]{\left[\begin{array}{rr}#1 \amp #2 \\ #3 \amp #4 \\ \end{array}\right]}\) \(\newcommand{\laspan}[1]{\text{Span}\{#1\}}\) \(\newcommand{\bcal}{\cal B}\) \(\newcommand{\ccal}{\cal C}\) \(\newcommand{\scal}{\cal S}\) \(\newcommand{\wcal}{\cal W}\) \(\newcommand{\ecal}{\cal E}\) \(\newcommand{\coords}[2]{\left\{#1\right\}_{#2}}\) \(\newcommand{\gray}[1]{\color{gray}{#1}}\) \(\newcommand{\lgray}[1]{\color{lightgray}{#1}}\) \(\newcommand{\rank}{\operatorname{rank}}\) \(\newcommand{\row}{\text{Row}}\) \(\newcommand{\col}{\text{Col}}\) \(\renewcommand{\row}{\text{Row}}\) \(\newcommand{\nul}{\text{Nul}}\) \(\newcommand{\var}{\text{Var}}\) \(\newcommand{\corr}{\text{corr}}\) \(\newcommand{\len}[1]{\left|#1\right|}\) \(\newcommand{\bbar}{\overline{\bvec}}\) \(\newcommand{\bhat}{\widehat{\bvec}}\) \(\newcommand{\bperp}{\bvec^\perp}\) \(\newcommand{\xhat}{\widehat{\xvec}}\) \(\newcommand{\vhat}{\widehat{\vvec}}\) \(\newcommand{\uhat}{\widehat{\uvec}}\) \(\newcommand{\what}{\widehat{\wvec}}\) \(\newcommand{\Sighat}{\widehat{\Sigma}}\) \(\newcommand{\lt}{<}\) \(\newcommand{\gt}{>}\) \(\newcommand{\amp}{&}\) \(\definecolor{fillinmathshade}{gray}{0.9}\)

    Up to now, we have presented the use of exceptions without really explaining in depth how they are implemented. Note that since you do not need to know how exceptions are implemented to use them, you can simply skip this section on the first reading. Now if you are curious and really want to know how they are implemented, this section is for you.

    The mechanism is quite simple, making it worthwhile to know how it operates. On the contrary to most mainstream languages, exceptions are implemented in the language side without virtual machine support, using the reification of the runtime stack as a linked list of contexts (method or closure activation record). Let’s have a look at how exceptions are implemented and use contexts to store their information.

    Storing Handlers. First we need to understand how the exception class and its associated handler are stored and how this information is found at runtime. Let’s look at the definition of the central method on:do: defined on the class BlockClosure.

    BlockClosure»on: exception do: handlerAction
        "Evaluate the receiver in the scope of an exception handler."
        | handlerActive |
        <primitive: 199>
        handlerActive := true.
        ^self value 
    

    This code tells us two things: Firstly, this method is implemented as a primitive, which usually means that a primitive operation of the virtual machine is executed when this method is invoked. In the case of exception, the primitive pragma is not a primitive operation: the primitive index is used only to mark the method and it does not execute any code in the virtual machine (the primitive always fail). One could change this implementation and mark differently the method, for example by adding an extra bytecode at the end. It was designed as a primitive because there was already bits reserved for the primitive number and APIs to set and get it in compiled methods so the implementation was straight-forward.

    Then, as the primitive is just a marker and not a real primitive operation, the fall back code below the pragma is always executed. In this code, we see that on:do: simply sets the temporary variable handlerActive to true, and then evaluates the receiver (which is of course, a block).

    This is surprisingly simple, but somewhat puzzling. Where are the arguments of the on:do: method stored? To get the answer, let’s look at the definition of the class MethodContext, whose instances represent the method and closure activation records. As described in Chapter 14, a context (also called activation record, a context is a reification of a stack frame with notable differences) represents a specific point of execution (it holds a program counter, points to the next instruction to execute, previous context, arguments and receiver):

    There is no instance variable here to store the exception class or the handler, nor is there any place in the superclass to store them. However, note that MethodContext is defined as a variableSubclass. This means that in addition to the named instance variables, instances of this class have some indexed slots. Every MethodContext has indexed slots, that are used to store, among others, arguments and temporary variables of the method whose invocation it represents.

    In the case that interests us, the arguments of the on:do: message are stored in the indexed variables of the stack execution instance. To verify this, evaluate the following piece of code:

    | exception handler |
        [ thisContext explore.
        self halt.
        exception := thisContext sender at: 1.
        handler := thisContext sender at: 2.
        1 / 0]
            on: Error
            do: [:ex | 666].
        ^ {exception. handler} explore
    

    In the protected block, we query the context that represents the protected block execution using thisContext sender. This execution was triggered by the on:do: message execution. The last line explores a 2-element array that contains the exception class and the exception handler.

    If you get some strange results using halt and inspect inside the protected block, note that as the method is being executed, the state of the context object changes, and when the method returns, the context is terminated, setting to nil several of its fields. Opening an explorer on thisContext will show you that the context sender is effectively the execution of the method on:do:.

    Note that you can also execute the following code:

    [thisContext sender explore] on: Error do: [:ex|].
    

    You obtain an explorer and you can see that the exception class and the handler are stored in the first and second variable instance variables of the method context object (a method context represents an execution stack element).

    We see that on:do: execution stores the exception class and its handler on the method context. Note that this is not specific to on:do: but any message execution stores arguments on its corresponding context.

    Method context with the exception class and handler.
    Figure \(\PageIndex{1}\): Explore a method context to find the exception class and the handler.

    Finding Handlers. Now that we know where the information is stored, let’s have a look at how it is found at runtime.

    As discussed before, the primitive 199 (the one used by on:do:) always fails! As the primitive always fails, the Smalltalk body of on:do: is always executed. However, the presence of the <primitive: 199> is used as a marker.

    The source code of the primitive is found in Interpreter»primitiveMarkHandlerMethod in the VMMaker SqueakSource package:

    primitiveMarkHandlerMethod
        "Primitive. Mark the method for exception handling. The primitive must fail after
        marking the context so that the regular code is run."
        
        self inline: false.
        ^self primitiveFail 
    

    Now we know that the context corresponding to the method on:do: is marked and a context has a direct reference through an instance variable to the method it has activated. Therefore, we can know if the context is an exception handler by checking if the method it has activated holds primitive 199. That’s what’s the method isHandlerContext is doing (code below).

    MethodContext»isHandlerContext
        "is this context for method that is marked?"
        ^method primitive = 199
    

    Now, if an exception is signaled further down the stack (assuming the stack is growing down), the method signal can search the stack to find the appropriate handler. This is what the following code is doing:

    Exception»signal
        "Ask ContextHandlers in the sender chain to handle this signal.
        The default is to execute and return my defaultAction."
        
        signalContext := thisContext contextTag.
        ^ thisContext nextHandlerContext handleSignal: self
    
    ContextPart»nextHandlerContext
    
        ^ self sender findNextHandlerContextStarting 
    

    The method findNextHandlerContextStarting is implemented as a primitive (number 197); its body describes what it does. It walks up the linked list of context from the current context and looks for a context whose method is marked with the on:do: primitive 199. If it finds such a context, it answers it.

    In the introduction of the section, we said that the implementation of exceptions is done fully at language level. However, we have here primitive 197 which is implemented in the virtual machine. Please note that primitive 197 is just here for performance. In fact, the virtual machine heavily optimizes contexts, allocating an object to represent them only when it is needed (else contexts are present in the form of a machine stack). The primitive 197 is faster because it creates context objects only if the method is marked with the on:do: primitive 199 whereas the fallback code requires all the context objects in-between the error signaling context and the error handling context to be created. As every primitive presents for performance, primitive 197 is optional: you can remove <primitive: 197 > and your Pharo runtime will still work fine (exceptions would be slower however).

    Primitive 197 enforces the use of primitive 199 to mark exception handler. Therefore, if one wants to edit the implementation of exceptions and use other marker than primitive numbers to mark exception handler contexts, one needs to remove this primitive and rely only on fallback code.

    ContextPart»findNextHandlerContextStarting
        "Return the next handler marked context, returning nil if there
        is none. Search starts with self and proceeds up to nil."
        | ctx |
        <primitive: 197>
        ctx := self.
        [ ctx isHandlerContext ifTrue: [^ctx].
            (ctx := ctx sender) == nil ] whileFalse.
        ^nil 
    

    Since the method context supplied by findNextHandlerContextStarting contains all the exception-handling information, it can be examined to see if the exception class is suitable for handling the current exception. If so, the associated handler can be executed; if not, the look-up can continue further. This is all implemented in the handleSignal: method.

    ContextPart»handleSignal: exception
        "Sent to handler (on:do:) contexts only. If my exception class (first arg) handles
            exception then execute my handle block (second arg), otherwise forward this
            message to the next handler context. If none left, execute exception's defaultAction
            (see nil>>handleSignal:)."
            
            | value |
            ((self exceptionClass handles: exception)
            and: [self exceptionHandlerIsActive])
                ifFalse: [ ^ self nextHandlerContext handleSignal: exception ].
            
            exception privHandlerContext: self contextTag.
            "disable self while executing handle block"
            self exceptionHandlerIsActive: false.
            value := [ self exceptionHandlerBlock cull: exception ]
                ensure: [ self exceptionHandlerIsActive: true ].
            "return from self if not otherwise directed in handle block"
            self return: value.
    
    ContextPart»exceptionClass
        "handlercontext only. access temporaries from BlockClosure>>#on:do:"
        ^self tempAt: 1
    
    exceptionHandlerBlock
        "handlercontext only. access temporaries from BlockClosure>>#on:do:"
        ^self tempAt: 2
    
    exceptionHandlerIsActive
        "handlercontext only. access temporaries from BlockClosure>>#on:do:"
        ^self tempAt: 3
    
    exceptionHandlerIsActive: aBoolean
        "handlercontext only. access temporaries from BlockClosure>>#on:do:"
        self tempAt: 3 put: aBoolean
    

    Notice how this method uses tempAt: 1 to access the exception class, and ask if it handles the exception. What about tempAt: 3? That is the temporary variable handlerActive of the on:do: method. Checking that handlerActive is true and then setting it to false ensures that a handler will not be asked to handle an exception that it signals itself. The return: message sent as the final action of handleSignal is responsible for unwinding the stack, i.e., removing all the context between the exception signaler context and its exception handler as well as executing unwind blocks (blocks created with ensure).

    To summarize, the signal method, with optional assistance from the virtual machine for performance, finds the context that correspond to an on:do: message with an appropriate exception class. Because the execution stack is made up of a linked list of Context objects that may be manipulated just like any other object, the stack can be shortened at any time. This is a superb example of flexibility of Pharo.


    This page titled 12.16: Exceptions Implementation 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.