Skip to main content
Engineering LibreTexts

16.6: Accessing the Run-Time Context

  • Page ID
    39672
    \( \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 have seen how Pharo’s reflective capabilities let us query and explore objects, classes and methods. But what about the run-time environment?

    Method contexts

    In fact, the run-time context of an executing method is in the virtual machine — it is not in the image at all! On the other hand, the debugger obviously has access to this information, and we can happily explore the run-time context, just like any other object. How is this possible?

    Actually, there is nothing magical about the debugger. The secret is the pseudo-variable thisContext, which we have encountered only in passing before. Whenever thisContext is referred to in a running method, the entire run-time context of that method is reified and made available to the image as a series of chained Context objects.

    We can easily experiment with this mechanism ourselves.

    Change the definition of Integer>>factorial by inserting the expression thisContext inspect. self halt. as shown below:

    Integer>>factorial
        "Answer the factorial of the receiver."
        self = 0 ifTrue: [thisContext inspect. self halt. ^ 1].
        self > 0 ifTrue: [^ self * (self - 1) factorial].
        self error: 'Not valid for negative integers'
    

    Now evaluate 3 factorial in a workspace. You should obtain both a debugger window and an inspector, as shown in Figure \(\PageIndex{1}\).

    Inspecting thisContext.
    Figure \(\PageIndex{1}\): Inspecting thisContext.

    Inspecting thisContext gives you full access to the current execution context, the stack, the local tempories and arguments, the senders chain and the receiver. Welcome to the poor man’s debugger! If you now browse the class of the explored object (i.e., by evaluating self browse in the bottom pane of the inspector) you will discover that it is an instance of the class Context, as is each sender in the chain.

    thisContext is not intended to be used for day-to-day programming, but it is essential for implementing tools like debuggers, and for accessing information about the call stack. You can evaluate the following expression to discover which methods make use of thisContext:

    SystemNavigation default browseMethodsWithSourceString: 'thisContext'
        matchCase: true
    

    As it turns out, one of the most common applications is to discover the sender of a message. Here is a typical application:

    subclassResponsibility
        "This message sets up a framework for the behavior of the class'
            subclasses.
        Announce that the subclass should have implemented this message."
        
        SubclassResponsibility signalFor: thisContext sender selector
    

    By convention, methods that send self subclassResponsibility are considered to be abstract. But how does Object>>subclassResponsibility provide a useful error message indicating which abstract method has been invoked? Very simply, by asking thisContext for the sender.

    Intelligent breakpoints

    The Pharo way to set a breakpoint is to evaluate self halt at an interesting point in a method. This will cause thisContext to be reified, and a debugger window will open at the breakpoint. Unfortunately this poses problems for methods that are intensively used in the system.

    Suppose, for instance, that we want to explore the execution of Morph>>openInWorld. Setting a breakpoint in this method is problematic.

    Pay attention the following experiment will break everything! Take a fresh image and set the following breakpoint:

    Morph >> openInWorld
        "Add this morph to the world."
        self halt.
        self openInWorld: self currentWorld
    

    Notice how your image immediately freezes as soon as you try to open any new Morph (Menu/Window/...)! We do not even get a debugger window. The problem is clear once we understand that 1) Morph>>openInWorld is used by many parts of the system, so the breakpoint is triggered very soon after we interact with the user interface, but 2) the debugger itself sends openInWorld as soon as it opens a window, preventing the debugger from opening! What we need is a way to conditionally halt only if we are in a context of interest. This is exactly what Object>>haltIf: offers.

    Suppose now that we only want to halt if openInWorld is sent from, say, the context of MorphTest>>testOpenInWorld.

    Fire up a fresh image again, and set the following breakpoint:

    Morph>>openInWorld
        "Add this morph to the world."
        self haltIf: #testOpenInWorld.
        self openInWorld: self currentWorld
    

    This time the image does not freeze. Try running the MorphTest.

    MorphTest run:#testOpenInWorld.
    

    How does this work? Let’s have a look at Object>>haltIf:. It first calls if: with the condition to the Exception class Halt. This method itself will check if the condition is a symbol, which is true in this case and finally calls

    Object >> haltIfCallChainContains: aSelector
        
        | cntxt |
        cntxt := thisContext.
            [cntxt sender isNil] whileFalse: [
                cntxt := cntxt sender.
                (cntxt selector = aSelector) ifTrue: [self signal]].
    

    Starting from thisContext, haltIfCallChainContains: goes up through the execution stack, checking if the name of the calling method is the same as the one passed as parameter. If this is the case, then it signals itself, the exception which, by default, summons the debugger.

    It is also possible to supply a boolean or a boolean block as an argument to haltIf:, but these cases are straightforward and do not make use of thisContext.


    This page titled 16.6: Accessing the Run-Time Context is shared under a CC BY-SA 3.0 license and was authored, remixed, and/or curated by via source content that was edited to the style and standards of the LibreTexts platform; a detailed edit history is available upon request.