Skip to main content
Engineering LibreTexts

16.8: Objects as Method Wrappers

  • Page ID
    43478
    \( \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 already seen that compiled methods are ordinary objects in Pharo, and they support a number of methods that allow the programmer to query the runtime system. What is perhaps a bit more surprising, is that any object can play the role of a compiled method. All it has to do is respond to the method run:with:in: and a few other important messages.

    Define an empty class Demo. Evaluate Demo new answer42 and notice how the usual Message Not Understood error is raised.

    Now we will install a plain object in the method dictionary of our Demo class.

    Evaluate Demo methodDict at: #answer42 put: ObjectsAsMethodsExample new.

    Now try again to print the result of Demo new answer42. This time we get the answer 42.

    If we take look at the class ObjectsAsMethodsExample we will find the following methods:

    answer42
        ^42
    run: oldSelector with: arguments in: aReceiver
        ^self perform: oldSelector withArguments: arguments
    

    When our Demo instance receives the message answer42, method lookup proceeds as usual, however the virtual machine will detect that in place of a compiled method, an ordinary Pharo object is trying to play this role. The VM will then send this object a new message run:with:in: with the original method selector, arguments and receiver as arguments. Since ObjectsAsMethodsExample implements this method, it intercepts the message and delegates it to itself.

    We can now remove the fake method as follows:

    Demo methodDict removeKey: #answer42 ifAbsent: []
    

    If we take a closer look at ObjectsAsMethodsExample, we will see that its superclass also implements some methods like flushcache, methodClass: and selector:, but they are all empty. These messages may be sent to a compiled method, so they need to be implemented by an object pretending to be a compiled method. (flushcache is the most important method to be implemented; others may be required by some tools and depending on whether the method is installed using Behavior>>addSelector:withMethod: or directly using MethodDictionary>>at:put:.)

    Using method wrappers to perform test coverage

    Method wrappers are a well-known technique for intercepting messages. In the original implementation (http://www.squeaksource.com/MethodWrappers.html), a method wrapper is an instance of a subclass of CompiledMethod. When installed, a method wrapper can perform special actions before or after invoking the original method. When uninstalled, the original method is returned to its rightful position in the method dictionary.

    In Pharo, method wrappers can be implemented more easily by implementing run:with:in: instead of by subclassing CompiledMethod. In fact, there exists a lightweight implementation of objects as method wrappers (http://www.squeaksource.com/ObjectsAsMethodsWrap.html), but it is not part of standard Pharo at the time of this writing.

    Nevertheless, the Pharo Test Runner uses precisely this technique to evaluate test coverage. Let’s have a quick look at how it works.

    The entry point for test coverage is the method TestRunner>>runCoverage:

    TestRunner >> runCoverage
        | packages methods |
        ... "identify methods to check for coverage"
        self collectCoverageFor: methods
    

    The method TestRunner>>collectCoverageFor: clearly illustrates the coverage checking algorithm:

    TestRunner >> collectCoverageFor: methods
        | wrappers suite |
        wrappers := methods collect: [ :each | TestCoverage on: each ].
        suite := self
            resetResult;
            suiteForAllSelected.
        
        [ wrappers do: [ :each | each install ].
        [ self runSuite: suite ] ensure: [ wrappers do: [ :each | each
            uninstall ] ] ] valueUnpreemptively.
        
        wrappers := wrappers reject: [:each | each hasRun].
        wrappers := wrappers collect: [:each | each reference].
        wrappers isEmpty
            ifTrue: [ UIManager default inform: 'Congratulations. Your tests
                cover all code under analysis.' ]
            ifFalse: ...
    

    A wrapper is created for each method to be checked, and each wrapper is installed. The tests are run, and all wrappers are uninstalled. Finally the user obtains feedback concerning the methods that have not been covered.

    How does the wrapper itself work? The TestCoverage wrapper has three instance variables, hasRun, reference and method. They are initialized as follows:

    TestCoverage class >> on: aMethodReference
        ^ self new initializeOn: aMethodReference
    
    TestCoverage >> initializeOn: aMethodReference
        hasRun := false.
        reference := aMethodReference.
        method := reference compiledMethod
    

    The install and uninstall methods simply update the method dictionary in the obvious way:

    TestCoverage >> install
        reference actualClass methodDict
            at: reference selector
            put: self
    
    TestCoverage >> uninstall
        reference actualClass methodDict
            at: reference selector
            put: method
    

    The run:with:in: method simply updates the hasRun variable, uninstalls the wrapper (since coverage has been verified), and resends the message to the original method.

    run: aSelector with: anArray in: aReceiver
        self mark; uninstall.
        ^ aReceiver withArgs: anArray executeMethod: method
    
    mark
        hasRun := true
    

    Take a look at ProtoObject>>withArgs:executeMethod: to see how a method displaced from its method dictionary can be invoked.

    That’s all there is to it!

    Method wrappers can be used to perform any kind of suitable behaviour before or after the normal operation of a method. Typical applications are instrumentation (collecting statistics about the calling patterns of methods), checking optional pre- and post-conditions, and memoization (optionally cacheing computed values of methods).


    This page titled 16.8: Objects as Method Wrappers 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.