Wrappers to the Rescue

John Brant, Brian Foote, Ralph E. Johnson, and Donald Roberts

Department of Computer Science

 University of Illinois at Urbana-Champaign

 Urbana, IL 61801

{brant, foote, johnson, droberts}@cs.uiuc.edu

Abstract. Wrappers are mechanisms for introducing new behavior that is executed before and/or after, and perhaps even in lieu of, an existing method. This paper examines several ways to implement wrappers in Smalltalk, and compares their performance. Smalltalk programmers often use Smalltalk’s lookup failure mechanism to customize method lookup. Our focus is different. Rather than changing the method lookup process, we modify the method objects that the lookup process returns. We call these objects method wrappers. We have used method wrappers to construct several program analysis tools: a coverage tool, a class collaboration tool, and an interaction diagramming tool. We also show how we used method wrappers to construct several extensions to Smalltalk: synchronized methods, assertions, and multimethods. Wrappers are relatively easy to build in Smalltalk because it was designed with reflective facilities that allow programmers to intervene in the lookup process. Other languages differ in the degree to which they can accommodate change. Our experience testifies to the value, power, and utility of openness.

1           Introduction

One benefit of building programming languages out of objects is that programmers are able to change the way a running program works. Languages like Smalltalk and CLOS, which represent program elements like Classes and Methods as objects that can be manipulated at runtime, to allow programmers to change the ways these objects work when the need arises.

This paper focuses on how to intercept and augment the behavior of existing methods in order to “wrap” new behavior around them. Several approaches are examined and contrasted and their relative performances are compared. These are:

1.        Source Code Modifications

2.        Byte Code Modifications

3.        New Selectors

4.        Dispatching Wrappers

5.        Class Wrappers

6.        Instance Wrappers

7.        Method Wrappers

We then examine several tools and extensions we’ve built using wrappers:

1.        Coverage Tool

2.        Class Collaboration Diagram Tool

3.        Interaction Diagram Tool

4.        Synchronized Methods

5.        Assertions

6.        Multimethods

Taken one at a time, it might be easy to dismiss these as Smalltalk specific minutiae, or as language specific hacks. However, taken together, they illustrate the power and importance of the reflective facilities that support them.

Before and after methods as we now know them first appeared in Flavors [30] and Loops [5]. The Common Lisp Object System (CLOS) [4] provides a powerful method standard combination facility that includes :before, :after, and :around methods. In CLOS, a method with a :before qualifier that specializes a generic function, g, is executed before any of the primary methods on g. Thus, the before methods are called before the primary method is called, and the :after methods are called afterwards. An :around method can wrap all of these, and has the option of completing the rest of the computation. The method combi­nation mechanism built into CLOS also lets pro­grammers build their own method qualifiers and combination schemes, and is very powerful.

Unfortunately, misusing method combination can lead to programs that are complex and hard to under­stand. Application programmers use them to save a little code but end up with systems that are hard to understand and maintain. Using these facilities to solve application-level problems is often symptomatic of more serious design problems that should be addressed through refactoring instead. The result is that before and after methods have gained a bad reputation.

We use method wrappers mostly as a reflective facility, not a normal application programming technique. We think of them as a way to impose additional structure on the underlying reflective facilities. For ex­ample, we use them to dynamically determine who calls a method, and which methods are called. If methods wrappers are treated as a disciplined form of reflection, then they will be used more carefully and their complexity will be less of a problem.

Our experience with method wrappers has been with Smalltalk. Smalltalk has many reflective facilities.  Indeed, Smalltalk-76 [17] was the first language to cast the elements of an object-oriented language itself, such as classes, as first-class objects.  The ability to trap messages that are not understood has been used to imple­ment encapsulators [26] and proxies in dis­tributed systems [2, 23]. The ability to manipulate contexts has been used to implement debuggers, back-trackers [21], and exception handlers [15]. The ability to compile code dynamically is used by the standard program­ming environ­ments and makes it easy to define new code management tools. Smalltalk pro­grammers can change what the system does when it accesses a global variable [1] and can change the class of an object [16].

However, it is not possible to change every as­pect of Smalltalk [10]. Smalltalk is built upon a virtual machine that defines how objects are laid out, how classes work, and how messages are handled. The virtual machine can only be changed by the Smalltalk vendors, so changes have to be made using the reflective facilities that the virtual machine provides. Thus, you can’t change how message lookup works, though you can specify what happens when it fails. You can’t change how a method returns, though you can use valueNowOrOn­UnwindDo: to trap returns out of a method. You can’t change how a method is executed, though you can change the method itself.

We use method wrappers to change how a method is executed. The most common reason for changing how a method is executed is to do something at every execution, and method wrappers work well for that purpose.

2           Compiled Methods

Many of the method wrapper implemen­tations discussed in this paper are based on CompiledMethods, so it is helpful to understand how methods work to understand the different implementations. While this discussion focuses on VisualWorks, we have also implemented wrappers in VisualAge Smalltalk. They can be implemented in most other dialects of Smalltalk. However, the method names and structure of the objects are somewhat different. A complete discussion of how to implement wrappers in these other dialects of Smalltalk is beyond the scope of this paper.

Smalltalk represents the methods of a class using instances of CompiledMethod or one of its subclasses. A CompiledMethod knows its Small­talk source, but it also provides other information about the method, such as the set of messages that it sends and the bytecodes that define the execution of the method.

Interestingly, CompiledMethods do not know the selector with which they are associated. Hence, they are oblivious as to which name they are invoked by, as well as to the names of their arguments. They are similar to Lisp lambda-expressions in this respect. Indeed, a compiled method can be invoked even if it does not reside in any MethodDictionary. We will use this fact to construct MethodWrappers.

CompiledMethod has three instance variables and a literal frame that is stored in its variable part (accessible through the at: and at:put: methods). The instance variables are bytes, mclass, and sourceCode. The sourceCode vari­able holds an index that is used to retrieve the source code for the method and can be changed so different sources appear when the method is browsed. Changing this variable does not affect the execution of the method, though. The mclass instance variable contains the class that compiled the method. One of its uses is to extract the selector for the method.

The bytes and literal frame are the most impor­tant parts of CompiledMethods. The bytes in­stance variable contains the byte codes for the method. These byte codes are stored either as a small integer (if the method is small enough) or a byte array, and contain references to items in the literal frame. The items in the literal frame include standard Smalltalk literal objects such as numbers (integers and floats), strings, arrays, symbols, and blocks (BlockClosures and Com­piledBlocks for copying and full blocks). Sym­bols are in the literal frame to specify messages being sent. Classes are in the literal frame whenever a method sends a message to a superclass. The class is placed into the literal frame so that the virtual machine knows where to begin method lookup. Associations are stored in the literal frame to represent global, class, and pool variables. Although the compiler will only store these types of objects in the literal frame, in principle any kind of object can be stored there.

Fig. 1. removeFirst method in Ordered­Collection


Figure 1 shows the CompiledMethod for the removeFirst method in OrderedCollection. The method is stored under the #removeFirst key in OrderedCollection’s method dictionary. Instead of showing the integer that is in the method’s sourceCode variable, the dashed line indicates the source code that the integer points to.

3           Implementing Wrappers

There are many different ways to implement method wrappers in Smalltalk, ranging from simple source code modification to com­plex byte code modification. In the next few sections we discuss seven possible implementations and some of their properties. Although many of the implementation details that we use are Smalltalk-specific, other languages provide similar facilities to varying degrees.

3.1         Source code modification

A common way to wrap methods is to modify the method directly. The wrapper code is directly inserted into the original method’s source and the resulting code is compiled. This requires parsing the original method to determine where the before code is placed and all possible locations for the after code. Although the locations of return state­ments can be found by parsing, these are not the only locations where the method can be exited. Other ways to leave a method are by exceptions, non-local block returns, and process termina­tion.

VisualWorks allows us to catch every exit from a method with the valueNowOr­OnUnwindDo: method. This method evaluates the receiver block, and when this block exits, either normally or abnormally, evaluates the argument block. The new source for the method using value­NowOrOnUnwindDo: is:

originalMethodName: argument

“before code”

^[“original method source”]

    valueNowOrOnUnwindDo:

      [“after code”]

To make the method appear unchanged, the source index of the new method can be set to the source index of the old method. Furthermore, the original method does not need to be saved since it can be recompiled from the source re­trieved by the source index.

The biggest drawback of this approach is that it must compile each method that it changes. Moreover, it requires another compile to rein­stall the original method. Not only is compiling slower than the other approaches listed here, it cannot be used in runtime images since they are not allowed to have the compiler.

3.2         Byte code modification

Another way to modify a method is to modify the CompiledMethod directly without recompiling [24]. This technique inserts the byte codes and literals for the before code di­rectly into the CompiledMethod so that the method does not need to be recompiled. This makes installation faster. Unfortunately, this approach does not handle the after code well. To insert the after code, we must convert the byte codes for the original method into byte codes for a block that is executed by the valueNowOrOnUn­windDo: method. This conversion is non-trivial since the byte codes used by the method will be different than the byte codes used by the block. Furthermore, this type of transformation de­pends on knowledge of the byte code instructions used by the virtual ma­chine. These codes are not standardized and can change without warning.

3.3         New selector

Another way to wrap methods is to move the original method to a new selector and create a new method that executes the before code, sends the new selector, and then executes the after code. With this approach the new method is:

originalMethodName: argument

“before code”

^[self newMethodName: argument]

    valueNowOrOnUnwindDo:

      [“after code”]

This approach was used by Böcker and Herczeg to build their Tracers [3].

This implementation has a couple of desirable prop­erties. One is that the original methods do not need to be recompiled when they are moved to their new selectors. Since CompiledMethods contain no direct reference to their selectors, they can be moved to any selector that has the same number of arguments. The other property is that the new forwarding methods with the same before and after code can be copied from another forward­ing method that has the same number of argu­ments. Cloning these CompiledMethods objects (i.e. using the Prototype pattern [11]) is much faster than compiling new ones. The main difference between the two forwarding methods is that they send different selectors for their original methods. The symbol that is sent is easily changed by replacing it in the method’s literal frame. The only other changes between the two methods are the source­Code and the mclass variables. The mclass is set to the class that will own the method, and the sourceCode is set to the original method’s sourceCode so that the source code changes aren’t noticed. Since byte codes are not modified, nei­ther the original method nor the new forwarding method needs to be compiled, so the installation is faster than the source code modi­fication ap­proach.

One problem with this approach is that the new selectors are visible to the user. Böcker and Herczeg addressed this problem by modifying the browsers. The new selec­tors cannot conflict with other selectors in the super or subclasses and should not conflict with users adding new methods. Furthermore, it is more difficult to compose two different method wrappers since we must remember which of the selectors represent the original methods and which are the new selectors.

3.4         Dispatching Wrapper

One way to wrap new behavior around existing methods is to screen every message that is sent to an object as it is dispatched. In Smalltalk, the does­Not­Understand: mechanism has long been used for this purpose [26, 2, 10, 12, 14] This approach works well when some action must be taken regardless of which method is being called, such as coordinat­ing synchronization information. Given some extra data structures, it can be used to implement wrapping on a per-method basis. For example, Classtalk [8] used doesNotUnderstand: to implement a CLOS-style before- and after- method combination mechanism.

A common way to do this is to introduce a class with no superclass to intercept the dispatching mechanism to allow per-instance changes to behavior.  However, the doesNot­Understand: mechanism is slow, and screening every message sent to an object just to change the behavior of a few methods seems wasteful and inelegant. The following sections examine how Smalltalk’s meta-archi­tecture lets us more precisely target the facilities we need.

3.5         Class Wrapper

The standard approach for specializing behavior in object-oriented programming is subclassing. We can use subclassing to specialize methods to add before and after code. In this case, the specialized subclass essentially wraps the original class by creating a new method that executes the before code, calls the original method using super mechanism, and then executes the after code. Like the methods in the new se­lector approach, the methods for the specialized subclass can also be copied, so the compiler is not needed.

Once the subclass has been created, it can be installed into the system. To install the subclass, the new class has to be inserted into the hierarchy so that subclasses will also use the wrapped methods. It can be inserted by using the super­class: method to change the superclass of all of the subclasses of the class being wrapped to be the wrapper. Next, the reference to the original class in the system dictionary must be replaced with a reference to the sub­class. Finally, all existing instances of the origi­nal class have to be converted to use the new subclass. This can be accomplished by get­ting allInstances of the original class and using the changeClass­ToThatOf: method to change their class to the new subclass.

Like the new selector approach this only re­quires one additional message send. However, these sorts of wrappers take longer to install. Each class requires a scan of object memory to look for all instances of the original class. Once the instances have been found, we have to iterate though them changing each of their classes.

3.6         Instance Wrapper

The class wrapper approach can also be used to wrap methods on a per in­stance basis, or a few at a time. Instead of replacing the class in the system dictionary, we can change only the objects that we want to wrap, by using the changeClassTo­ThatOf: method on only those objects.

Instance wrappers can be used to change the way individual objects behave. This is the intent of the Decorator pattern [11]. However since these decorations are immediately visible though existing references to the original object, objects can be decorated dynamically.

3.7         Method Wrapper

A method wrapper is like a new selector in that the old method is replaced by a new one that invokes the old. However, a method wrapper does not add new entries to the method dictionary. Instead of invoking the old method by sending a message to the receiver, a method wrapper evaluates the origi­nal method directly. A method wrapper must know the original method, and must be able to execute it with the current arguments. Executing a CompiledMethod is easy, since a CompiledMethod responds to the valueWithRe­ceiver:arguments: message by executing itself with the given a receiver and an array of argu­ments.

One way for a MethodWrapper to keep track of its original method is for MethodWrapper to be a subclass of CompiledMethod with one new instance variable, clientMethod, that stores the original method. MethodWrapper also defines beforeMethod, afterMethod, and re­ceiver:arguments: methods as well as a few helper methods. The beforeMethod and after­Method methods contain the before and after code. The valueWithReceiver:arguments: method exe­cutes the original method given the receiver and argument array.

valueWithReceiver: anObject arguments: args

self beforeMethod.

^[clientMethod

      valueWithReceiver: anObject

      arguments: args]

    valueNowOrOnUnwindDo:

      [self afterMethod]

Fig. 2. MethodWrapper on removeFirst method


The only remaining problem is how to send the valueWithReceiver:­arguments: message to a MethodWrapper. The method must be able to refer to itself when it is executing, but Smalltalk does not provide a standard way to refer to the currently executing method. When a CompiledMethod is executing, the receiver of the message, and not the CompiledMethod, is the “self” of the current computation. In VisualWorks Smalltalk, the code “thisContext method” evaluates to the currently executing method, but it is inefficient. We need some kind of “static” variable that we could initialize with the method, but Smalltalk does not have that feature. Instead, we make use of the fact that each Smalltalk method keeps track of the literals (i.e. constants) that it uses. Each MethodWrapper is compiled with a marked literal (we use #(), which is an array of size 0). After it has been created, the system replaces the reference to the literal with a reference to the MethodWrapper. Using this trick the receiver:value: message can be sent to the MethodWrapper by compiling

originalMethodName: argument

^#() receiver: self value: argument

and replacing the empty array (in the first posi­tion of the literal frame) with the method. The receiver:value: method is one of the Method­Wrapper’s helper methods. It is responsible for converting its value argument into an array and sending them to the value­With­Receiver:­arguments: method.

Figure 2 shows a MethodWrapper wrapping the removeFirst method of OrderedCollection. The CompiledMethod has been replaced by the MethodWrapper in the method dictionary. The MethodWrapper references the original method through its clientMethod variable. Also, the empty array that was initially compiled into the method has been replaced with a reference to the wrapper.

Like the new selector approach, MethodWrap­pers do not need to be compiled for each method. Instead they just need a prototype (with the same number of arguments) that can be copied. Once copied, the method sets its method literal, source index, mclass, and clientMethod. Since the method wrapper can directly execute the original method, no new entries are needed in the method dictionary for the original method. 

Smalltalk's CompiledMethod objects and byte code were designed primarily to make Smalltalk portable.  As with doesNotUnderstand:, Smalltalk's historic openness continues to pay unexpected dividends.

Table 1. Overhead per 1,000 method calls (ms)

 

Number of arguments

Approach

0

1

2

3

Method modification (no returns)

5.2

5.2

9.2

9.7

Method modification (returns)

339.0

343.8

344.5

346.5

New selector

5.5

9.7

10.3

10.7

Dispatching wrapper

21.1

22.8

23.5

27.5

Class wrapper

5.9

9.8

10.5

10.9

Method wrapper

23.4

28.7

31.5

31.8

Inlined method wrapper

18.8

20.3

21.9

24.5


 


Table 1 and Table 2 compare the different ap­proaches for both runtime overhead and instal­lation time. These tests were performed on an 486/66 with 16MB memory running Windows 95 and VisualWorks 2.0. The byte code modifi­cation approach was not implemented, thus it is not shown. The dispatching wrapper has been omitted from the installation times since it is only an instance based technique. Added to the listings is an inlined method wrapper. This new method wrapper inlines the before and after code into the wrapper without defining the ad­ditional methods. This saves four message sends over the default method wrapper. Although it helps runtime efficiency, it hurts installation times since the inlined wrappers are larger.

Table 1 shows the overhead of each approach. The method modification approach has the low­est overhead if the method does not contain a return, but when it contains a return, the over­head for method modification jumps to more than ten times greater than the other techniques. Whenever a return occurs in a block, a context object is created at runtime. Normally these context objects are not created so execution is much faster. The new selector and class wrapper approaches have the best overall times. The two method wrapper approaches and the dispatching wrap­per approaches have more than double the over­head as the new selector or class wrapper ap­proaches since the method wrappers and dis­patching wrappers must create arrays of their arguments.

Table 2. Installation times for 3,159 methods in 226 classes (sec)

Approach

Time

Method modification

262.6

New selector

25.5

Class wrapper

44.2

Method wrapper

17.0

Inlined method wrapper

19.9


 


Table 2 contains the installation times for in­stalling the various approaches on all subclasses of Model and its metaclass (226 classes with 3,159 methods). The method wrapper tech­niques are the fastest since they only need to change one entry in the method dictionary. The new selector approach is slightly slower since it needs to change two entries in the method dic­tionary. Although the class wrapper only needs to add one entry, it must scan object memory for in­stances of each class to convert them to use the new subclass wrapper. Finally, the method modification approach is the slowest since it must compile every method.

Because wrappers are relatively fast, and because the overhead associated with them is predictable, they may be more suitable in time-critical applications than classical Smalltalk approaches based on doesNotUnderstand:.

4           Applications

Method wrappers can be used in many different areas. In this section we outline six different uses.

4.1         Coverage Tool (Image Stripper)

One application that can use method wrappers is an image stripper. Strippers remove unused objects (usually methods and classes) from the image to make it more memory effi­cient. The default stripper shipped with Visual­Works only removes the development environ­ment (compilers, browsers, etc.) from the image.

A different approach to stripping is to see what methods are used while the program is running and remove the unused ones. Finding the used methods is a coverage problem and can be han­dled by method wrappers. Instead of counting how many times a method is called, the method wrapper only needs a flag to signify if its method has been called. Once the method has been called, the original method can be restored so that future calls occur at normal speeds.

We created a subclass of MethodWrapper that adds two new instance variables, selector and called. The selector variable contains the method’s selector, and called is a flag that sig­nifies if the method has been called. Since the method wrapper does not need to do anything after the method is executed, it only needs to redefine the beforeMethod method:

beforeMethod

called ifFalse:

    [called := true.

    mclass addSelector: selector

      withMethod: clientMethod]

This method first sets its flag and then reinstalls its original method. The ifFalse: test avoids infinite recursion in case that the method is called while performing the addSelector:with­Method: operation. Execution of the application program is slow at first, but it rapidly increases once the base set of methods is reinstalled.

The method wrapper correctly reports whether it has been called. However, this stripping scheme requires 100% method coverage. Any method that is not used by the test suite will be removed, so if a test suite does not provide 100% method coverage (which they rarely do) then the stripper will remove a method that is needed later. Removing methods in this manner can introduce errors into an otherwise correct program. Therefore, all methods should be saved to a file before they are removed. If one of the removed methods is called, it must be loaded, installed, and executed. The best way to detect that a deleted method has been called is with the doesNotUnderstand: mechanism, though it is also possible to use method wrappers for this purpose.

4.2         Class Collaboration

Method wrappers can also be used to dynamically analyze collaborating objects. For example, we might create call graphs that can help developers better understand how the software works. Furthermore, such information can help the developer visualize the coupling between objects. This can help the developer more quickly analyze when inappropriate objects are interacting.

Method wrappers can capture this information by getting the current context, just like the debugger does. Whenever a method is called, its wrapper needs to record who called the method, where the call occurred (which method and statement inside the method), the starting and ending times for the method, and finally how the method terminated (either normally with a return, or abnormally by a signal). Methods that return abnormally might be a problem since the programmers might not have programmed for such a case.

Using the information collected by the method wrappers, we can create a class collaboration graph such as the one shown in Figure 3. Whenever one object of a class sends a message to another object in another class, a line is drawn between them. Classes whose objects collaborate a lot are attracted to each other. The collaboration graph can help the programmer see which objects are collaborating as well as how much they are collaborating.

Fig. 3. Class collaboration graph of the Refactoring Browser


4.3         Interaction Diagrams

Interaction diagrams illustrate the dynamic sequence of the message traffic among several objects at runtime. The interaction diagram application allows users to select the set of methods that will be watched. These methods are wrapped, and the tool records traffic through them. When the wrappers are removed, the interactions among the objects that sent and received these messages are depicted, as in Figure 4.

The diagrams generated by the tool are similar to the interaction diagrams seen in many books, with one notable exception. Since we only select a few methods to observe, we miss some messages. As a result, there are times when a message is received, but the last method entered did not send the message. For example, suppose you have:

Foo>>createBar

    ^Bar new

 

Bar>>initialize

    "do some initialization"

 

Bar class>>new

    ^super new initialize

Fig. 4. Interaction Diagram on the Refactoring Browser


and that you only wrap Foo>>createBar and Bar>>initialize. If you send a Foo the create­Bar message, that event will be recorded. It will send the new message to Bar class, but since that method is not wrapped, it is not observed. When the new method sends the initialize method to a Bar, it is observed, but the last observed method did not send it. Such events are called indirect message sends and are displayed as yellow lines. In the figure, we can see that "a RefactoringBrowser" sent a closed message to some object that wasn't wrapped, which resulted in the update:­with:­from: method being called on "(nothing selected)" (a CodeModel).

Without a facility for wrapping the watched methods, tools would have to intervene at the source or binary code levels. For instance Lange and Nakamura [22] modify source code to instrument programs for tracing. The relative absence of such tools in languages without support for wrappers testifies to the difficulty of intervening at these levels.

Both Probe from Arbor Intelligent Systems and the Object Visualizer in IBM’s VisualAge for Smalltalk generate interaction diagrams using method wrappers. Probe uses method wrappers that are very similar to those described in this paper except that the before and after code is been inlined into the wrapper.

The Object Visualizer uses a combination of lightweight classes and method wrappers to capture the runtime interaction information. However, their method wrappers do not directly reference the wrapped method. Instead they look up the method for every send. Instance wrappers would have been a better choice given this approach.

4.4         Synchronized Methods

Method wrappers are also useful for synchronizing methods. In a multithreaded environment, objects used concur­rently by two different threads can become cor­rupt. A classic example in Smalltalk is the Tran­script. The Transcript is a global variable that programs use to print output on. It is most often used to print debugging information. If two processes write to the Transcript at the same time, it can become corrupt and cause excep­tions to be raised. To solve this problem we need to ensure that only one process accesses the Transcript at a time.

One solution would be to define a lan­guage construct for synchro­nization. For example, Java takes this approach by defining a method t