5

I am trying to understand better reflection in Smalltalk. I am using the latest version of Squeak (v4.3). I want to intercept every message sent to instances of one of my classes. I assumed that I could override the method ProtoObject>>withArgs:executeMethod but Stéphane Ducasse explained me that for performance reason, this method is not used (this is my own summary of his answer). Which method should I override / how could intercept sent messages?

Here is the code of my attempt:

Object subclass: #C
    instanceVariableNames: 'i'
    classVariableNames: ''
    poolDictionaries: ''
    category: 'CSE3009'.

C class compile: 'newWithi: anInt
    ^(self new) i: anInt ; yourself.'.

C compile: 'withArgs: someArgs executeMethod: aMethod
    Transcript show: ''Caught: ''.
    ^ super withArgs: someArgs executeMethod aMethod.'.

C compile: 'foo: aText
    Transcript show: aText.
    Transcript show: i.
    Transcript cr.'.

C compile: 'i: anInt
    i := anInt.'.

o := C newWithi: 42.
o foo: 'This is foo: '.

Executing this entire piece of code yields:

This is foo: 42

When I would like to have:

Caught: This is foo: 42
Euan M
  • 1,126
  • 11
  • 20

2 Answers2

5

There's no build-in way to intercept messages to objects like that. There are two ways we commonly use to do this kind of trick.

First, you can create a wrapper object which responds to doesNotUnderstand:. This object usually has nil for the superclass so it doesn't inherit any instance methods from Object. The doesNotUnderstand: handler would delegate all its messages to the target object. It has the option of performing code before and after the call. All references to the original object would now point to the new "proxy" object. Messages to self wouldn't be intercepted and the proxy would need to test for objects that return self and change the returned object to be the proxy instead.

The second approach is to use a mechanism called Method Wrappers. Method Wrappers allows you to replace all of the methods in a set of classes with methods that do some other operations before and after calling the original method. This approach can provide fairly seemless results and intercepts all messages including those send to self.

MethodWrappers is available for VisualWorks and VASmalltalk. I believe it's also available for Squeak and Pharo but I'm not positive.

Frank Shearar
  • 17,012
  • 8
  • 67
  • 94
David Buck
  • 2,847
  • 15
  • 16
  • 1
    There are several `MethodWrapper` implementations for either Squeak or Pharo – Tobias Apr 12 '13 at 17:17
  • Thank you for your answer but I am a little bit surprised: shoudn't I be able to change the semantics of a method call through reflection? (That's why I thought naïvely that I could override `ProtoObject>>withArgs:executeMethod:`. I mean, similarly to the way I can override the new class method. – Yann-Gaël Guéhéneuc Apr 13 '13 at 01:32
  • It seems to me that the solution that uses `doesNotUnderstand:` works because the language is untyped and that the `MethodWrapper` (and other [ObjectTracer](http://stackoverflow.com/questions/888900/in-squeak-how-to-wrap-every-method-send)) is an implementation of the solution with `doesNotUnderstand:`. – Yann-Gaël Guéhéneuc Apr 13 '13 at 01:51
  • Smalltalk is late bound so receivers are determined when the actual call is happening. Smalltalk also does not invoke a method on a receiver, but it sends it a message which even more loosely coupled. MethodWrappers have nothing to do with doesNotUnderstand. It is two different things. With doesNotUnderstand you can build a replacement for a class while MethodWrappers is a mechanism to be a replacement for a CompiledMethod. The method used here is called run:with:in that the replacement object implements – Norbert Hartl Apr 13 '13 at 11:52
  • If the only way to invoke methods was to invoke another method, we'd never be able to invoke methods. The Smalltalk virtual machine understands how to look up methods in a method dictionary and invoke them. This is done at the VM level and is the default mechanism for invoking methods. It's more than just a performance issue. It absolutely needs to be done by the VM or you'll have no way to do it at all. – David Buck Apr 13 '13 at 13:41
  • Thank you @norbert and @david for the clarifications. One thought though: I understand David's point but I kind of remember that in some version/flavor of Smalltalk, you could "override" the VM mechanism to invoke method, typically by overriding some `withArgs:executeMethod:` when---and only when---using reflection so that performance would not be horrible but that you could still change the behaviour of invocations (at the risk of breaking the whole image if something silly was done, such as not "forwarding" the invocation to the VM after interception). Do I remember wrong? – Yann-Gaël Guéhéneuc Apr 14 '13 at 03:14
1

The three main techniques are:

  • Dynamic proxies
  • Method wrapper
  • Bytecode instrumentation

For a good comparision of all possible approaches, have a look at "Evaluating Message Passing Control Techniques in Smalltalk" by Stephane Ducasse (you already know him, apparently).

Of interest is also "Smalltalk: A Reflective Langauge" by F. Rivard, that shows how to implement pre- and post-conditions using bytecode rewriting. This is also a form of interception.

ewernli
  • 38,045
  • 5
  • 92
  • 123