You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
MethodProxies is a Pharo instrumentation library that implements message-passing control (it controls method execution). It features a stratified architecture that cleanly separates the instrumentation mechanism from the user-defined controlling methods.
4
-
Users simply need to **subclass a handler class** and define the desired controlling methods.
5
-
The library ensures that these methods execute safely, as concerns such as meta-safety and stack unwinding are managed by the framework. Its robust design allows MethodProxies to instrument any method safely.
3
+
MethodProxies is a Pharo instrumentation library for **message-passing control**. It lets you execute custom code **before**, **after**, **instead of**, or **during the unwind of** any method execution — without changing the method's source.
6
4
7
-
Message-passing control is an instrumentation paradigm where programs are instrumented to perform actions before, instead, and after a message is sent. This replaces the original method entirely, or performs no additional action at all.
5
+
It is designed for building profilers, tracers, call-graph analyzers, mocks, and other dynamic analysis tools. Its stratified architecture cleanly separates the instrumentation mechanism (handled by the framework) from the user-defined behavior, so all you need to do is **subclass a handler** and define a few hooks.
8
6
9
-
## How to load
7
+
MethodProxies guarantees:
8
+
9
+
-**Meta-safety** — instrumented methods can safely call other instrumented methods without triggering infinite recursion.
10
+
-**Unwind-safety** — non-local returns and exceptions are handled correctly.
11
+
-**Meta-thread safety** — the meta-level state is tracked per process.
12
+
-**Dynamic (de)instrumentation** — instrumentation can be installed and removed at run time.
13
+
-**Practical overhead** — the trap-method approach integrates with the JIT and with polymorphic inline caches.
A more advanced and challenging for the architecture of method proxies is a propagating handler.
89
-
The idea is simple, before a method executes, all the implementor methods of its messages are proxified with propagating handlers.
90
-
So instead of proxifying the complete system only we subpart is spyied. This is this challenging because the infrastructure should make sure that we are not proxifying the code that is proxifying the system else we would end up in a severe and endless loop.
91
-
The handler design protects you from that by avoiding that you extend and break the clear separation between base and meta-level.
92
+
### `instead` — replace the original method
93
+
94
+
The `instead` hook completely replaces the original method body. When `instead` is defined, `before` and `after` are **not** invoked: the original method is never called.
95
+
96
+
For example, a handler `MpAlwaysReturn42Handler` (a subclass of `MpHandler`) can override the receiver's behavior like this:
For methods that take arguments, use `insteadExecutionWithReceiver:arguments:` (or one of the keyword variants `insteadExecutionWithReceiver:with:`, `...with:with:`, etc.).
106
117
107
-
MethodProxies design rests on two pillars: **handlers** and the **trap method** as presented in the Figure below.
118
+
The `instead` hook is useful for mocking, stubbing in tests, fault injection, or quickly experimenting with alternative implementations.
108
119
109
-
- Each controlled method is associated with a dedicated handler that defines the controlling methods. This specialization allows different instrumented methods to execute different handlers: **each controlled method can have its own handler**. Handlers can also be shared.
110
-
- The second pillar, the trap method, is a **pre-compiled template method**. Instrumentation is achieved by copying the trap method and applying literal patching to replace the literal references to the actual handler and the original method. Finally, the trap method leverages Pharo’s *stack unwinding* mechanism to guarantee execution of the handlers without allocating block closures, gaining significantly in performance. The trap method is a precompiled template in which the before and after method handlers, and the original method are represented as literals, later updated through literal patching. It ensures safety by including a meta-safe mechanism to prevent infinite recursion and safe stack unwinds.
`aboutToReturnWithReceiver:arguments:` is invoked when the controlled method exits via a stack unwind — an exception or a non-local return. By default it delegates to `afterExecutionWithReceiver:arguments:returnValue:` with a `nil` return value, but you can override it to react specifically to abnormal exits:
113
123
114
-
MethodProxies has a stratified architecture structured around two core classes: `MpMethodProxy` and `MpHandler`:
-`MpMethodProxy` manages the lifecycle of an instrumented method. Users are not exposed to the internal logic and the implementation details.
117
-
-`MpHandler` is the root of handlers. Users can subclass it and define the controlling methods.
130
+
### Sharing handlers
118
131
119
-
Its API is composed of three methods:
132
+
The same handler can be installed on multiple methods, which is convenient for aggregating information from several places. For example, monitoring `basicNew` and `clone` together:
120
133
121
-
-`beforeExecutionWithReceiver:arguments:` is called before the controlled method is invoked. It receives as arguments the actual receiver and the arguments of the controlled message send.
122
-
-`aboutToReturnWithReceiver:arguments:` is called before the controlled method exits due to a stack unwind. This situation occurs in the presence of non-local returns or exceptions.
123
-
-`afterExecutionWithReceiver:arguments:returnValue:` is called after the controlled method returns. This hook allows executing actions after the method has run, inspecting or modifying the return value. The return value of the controlled call is passed as an argument, and the method must return the final value to be used as the result of the instrumented method.
134
+
```st
135
+
h := MpAllocationProfilerHandler new.
136
+
p1 := MpMethodProxy onMethod: Behavior >> #basicNew handler: h.
137
+
p2 := MpMethodProxy onMethod: Object >> #clone handler: h.
124
138
125
-
Moreover, two higher-level hooks are provided, defined in terms of the ones described above.
126
-
-`beforeMethod` is invoked before the method execution begins.
127
-
- It is a simpler version of `beforeExecutionWithReceiver:arguments:` that does not receives any arguments.
128
-
-`afterMethod` is invoked before the method returns, either by normal completion or due to a stack unwind. This method does not receives any arguments and it does not allow modification of the return value.
Method Wrappers were originally developed by John Brant for proprietary software. You can read "Evaluating Message Passing Control" article from S. Ducasse to understand the original implementation and a comparison with other approaches. What you see is that back in 1998/9 there were already multiple ways to control message passing. Method Wrappers is one of them.
149
+
## Design
135
150
136
-
MethodWrappers were basically using CompiledMethod prototypes that were patched (cloned + adding selector/class) during their application because the VM of the system where Method Wrappers were implemented did not support to have anything but CompiledMethod as value of method dictionaries.
137
-
This is not the case for Pharo. And the design and implementation of MethodProxies allows us to wrap any part of the system and in addition to design propagating proxies without blowing up the system. This is a key properties for us.
151
+
MethodProxies rests on two pillars: **handlers** and the **trap method**.
138
152
139
-
**Note for grumpies.** For the people that could think that we would like to steal this idea, we encourage them to lower their paranoia by having a look at our CV and publication record list. We redeveloped from scratch this library while taking into account the original intention and we wrote many tests to validate them.
153
+
- Each instrumented method is associated with a handler. Users subclass `MpHandler` and override the hooks; they never touch the low-level machinery. Different methods can have different handlers, and handlers can be shared.
154
+
- The **trap method** is a precompiled template installed in place of the instrumented method. At installation time it is patched via *literal patching* to reference the actual handler and the original method. The original method is kept in the same method dictionary under a hidden selector, so the forwarding call becomes a monomorphic, JIT-friendly message send.
140
155
141
-
## Bibliography
156
+
The trap also tracks a meta-level state on the active process. Whenever a hook executes, the process is marked as *meta*. While in this state, any instrumented method called from within the handler short-circuits to the original behavior, which avoids the infinite-recursion problem that affects simpler approaches. Stack unwinds are handled directly through the VM's unwind mechanism, **without allocating block closures**, giving significantly better performance than approaches based on `ensure:`.
142
157
143
-
- Jordan Montaño S., Sandoval Alcócer J., Polito G., Ducasse S., Tesone P., MethodProxies: A Safe and Fast Message-Passing Control Library, IWST '24 June 2024, France. [PDF](https://hal.science/hal-04708729v1/document)
144
-
- Stéphane Ducasse, Evaluating Message Passing Control Techniques in Smalltalk, Journal of Object-Oriented Programming (JOOP), 12, 39–44, SIGS Press, 1999, Impact factor 0.306. [PDF](http://rmod-files.lille.inria.fr/Team/Texts/Papers/Duca99aMsgPassingControl.pdf)
145
-
- John Brant, Brian Foote, Ralph E. Johnson, and Donald Roberts. Wrappers to the Rescue. In Proceedings of European Conference on Object-Oriented Programming (ECOOP). Springer, Berlin, Heidelberg, 1998. http://www.laputan.org/brant/brant.html (https://link.springer.com/chapter/10.1007/BFb0054101)
158
+
The architecture has two strata: `MpMethodProxy` manages the lifecycle of an instrumented method and hides the implementation details, while `MpHandler` is the root class users subclass to define their controlling methods.
A full description of the design, the implementation, and the empirical evaluation is available in the IWST '24 paper listed in the bibliography. On a suite of 48 benchmarks across four real-world applications, the average overhead is about 1.54× and the meta-safety mechanism itself adds roughly 9% on top. Compared to instrumentation based on the `run:with:in:` hook — which cannot be JIT-compiled — MethodProxies is on average 6.5× faster, with peaks up to 40×.
165
+
166
+
## Advanced: Propagating Handlers
167
+
168
+
A more advanced use case is a *propagating* handler: before a method runs, all the implementors of the messages it sends are themselves wrapped with the same kind of handler. Instead of instrumenting the entire system upfront, only the subset of code that actually executes is instrumented. The challenge is to avoid instrumenting the instrumentation code itself, which would lead to an infinite loop. The handler design and the meta-safety mechanism take care of this by preserving the separation between base and meta levels.
- Jordan Montaño S., Sandoval Alcócer J., Polito G., Ducasse S., Tesone P. *MethodProxies: A Safe and Fast Message-Passing Control Library.* IWST '24, June 2024, France. [PDF](https://hal.science/hal-04708729v1/document)
185
+
- Stéphane Ducasse. *Evaluating Message Passing Control Techniques in Smalltalk.* Journal of Object-Oriented Programming (JOOP), 12, 39–44, SIGS Press, 1999. [PDF](http://rmod-files.lille.inria.fr/Team/Texts/Papers/Duca99aMsgPassingControl.pdf)
0 commit comments