aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/javassist/CodeConverter.java
blob: d8a645fff190567835155414a16527806b92b969 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
/*
 * Javassist, a Java-bytecode translator toolkit.
 * Copyright (C) 1999-2006 Shigeru Chiba. All Rights Reserved.
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License.  Alternatively, the contents of this file may be used under
 * the terms of the GNU Lesser General Public License Version 2.1 or later.
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 */

package javassist;

import javassist.bytecode.*;
import javassist.convert.*;

/**
 * Simple translator of method bodies
 * (also see the <code>javassist.expr</code> package).
 *
 * <p>Instances of this class specifies how to instrument of the
 * bytecodes representing a method body.  They are passed to
 * <code>CtClass.instrument()</code> or
 * <code>CtMethod.instrument()</code> as a parameter.
 *
 * <p>Example:
 * <ul><pre>
 * ClassPool cp = ClassPool.getDefault();
 * CtClass point = cp.get("Point");
 * CtClass singleton = cp.get("Singleton");
 * CtClass client = cp.get("Client");
 * CodeConverter conv = new CodeConverter();
 * conv.replaceNew(point, singleton, "makePoint");
 * client.instrument(conv);
 * </pre></ul>
 *
 * <p>This program substitutes "<code>Singleton.makePoint()</code>"
 * for all occurrences of "<code>new Point()</code>"
 * appearing in methods declared in a <code>Client</code> class.
 *
 * @see javassist.CtClass#instrument(CodeConverter)
 * @see javassist.CtMethod#instrument(CodeConverter)
 * @see javassist.expr.ExprEditor
 */
public class CodeConverter {
    Transformer transformers = null;

    /**
     * Modify a method body so that instantiation of the specified class
     * is replaced with a call to the specified static method.  For example,
     * <code>replaceNew(ctPoint, ctSingleton, "createPoint")</code>
     * (where <code>ctPoint</code> and <code>ctSingleton</code> are
     * compile-time classes for class <code>Point</code> and class
     * <code>Singleton</code>, respectively)
     * replaces all occurrences of:
     *
     * <ul><code>new Point(x, y)</code></ul>
     *
     * in the method body with:
     *
     * <ul><code>Singleton.createPoint(x, y)</code></ul>
     * 
     * <p>This enables to intercept instantiation of <code>Point</code>
     * and change the samentics.  For example, the following
     * <code>createPoint()</code> implements the singleton pattern:
     *
     * <ul><pre>public static Point createPoint(int x, int y) {
     *     if (aPoint == null)
     *         aPoint = new Point(x, y);
     *     return aPoint;
     * }
     * </pre></ul>
     *
     * <p>The static method call substituted for the original <code>new</code>
     * expression must be
     * able to receive the same set of parameters as the original
     * constructor.  If there are multiple constructors with different
     * parameter types, then there must be multiple static methods
     * with the same name but different parameter types.
     *
     * <p>The return type of the substituted static method must be
     * the exactly same as the type of the instantiated class specified by
     * <code>newClass</code>.
     *
     * @param newClass          the instantiated class.
     * @param calledClass       the class in which the static method is
     *                          declared.
     * @param calledMethod      the name of the static method.
     */
    public void replaceNew(CtClass newClass,
                           CtClass calledClass, String calledMethod) {
        transformers = new TransformNew(transformers, newClass.getName(),
                                        calledClass.getName(), calledMethod);
    }

    /**
     * Modify a method body so that field read/write expressions access
     * a different field from the original one.
     *
     * <p>Note that this method changes only the filed name and the class
     * declaring the field; the type of the target object does not change.
     * Therefore, the substituted field must be declared in the same class
     * or a superclass of the original class.
     *
     * <p>Also, <code>clazz</code> and <code>newClass</code> must specify
     * the class directly declaring the field.  They must not specify
     * a subclass of that class.
     *
     * @param field             the originally accessed field.
     * @param newClass  the class declaring the substituted field.
     * @param newFieldname      the name of the substituted field.
     */
    public void redirectFieldAccess(CtField field,
                                    CtClass newClass, String newFieldname) {
        transformers = new TransformFieldAccess(transformers, field,
                                                newClass.getName(),
                                                newFieldname);
    }

    /**
     * Modify a method body so that an expression reading the specified
     * field is replaced with a call to the specified <i>static</i> method.
     * This static method receives the target object of the original
     * read expression as a parameter.  It must return a value of
     * the same type as the field.
     *
     * <p>For example, the program below
     *
     * <ul><pre>Point p = new Point();
     * int newX = p.x + 3;</pre></ul>
     *
     * <p>can be translated into:
     *
     * <ul><pre>Point p = new Point();
     * int newX = Accessor.readX(p) + 3;</pre></ul>
     *
     * <p>where
     *
     * <ul><pre>public class Accessor {
     *     public static int readX(Object target) { ... }
     * }</pre></ul>
     *
     * <p>The type of the parameter of <code>readX()</code> must
     * be <code>java.lang.Object</code> independently of the actual
     * type of <code>target</code>.  The return type must be the same
     * as the field type.
     *
     * @param field             the field.
     * @param calledClass       the class in which the static method is
     *                          declared.
     * @param calledMethod      the name of the static method.
     */
    public void replaceFieldRead(CtField field,
                                 CtClass calledClass, String calledMethod) {
        transformers = new TransformReadField(transformers, field,
                                              calledClass.getName(),
                                              calledMethod);
    }

    /**
     * Modify a method body so that an expression writing the specified
     * field is replaced with a call to the specified static method.
     * This static method receives two parameters: the target object of
     * the original
     * write expression and the assigned value.  The return type of the
     * static method is <code>void</code>.
     * 
     * <p>For example, the program below
     *
     * <ul><pre>Point p = new Point();
     * p.x = 3;</pre></ul>
     *
     * <p>can be translated into:
     *
     * <ul><pre>Point p = new Point();
     * Accessor.writeX(3);</pre></ul>
     *
     * <p>where
     *
     * <ul><pre>public class Accessor {
     *     public static void writeX(Object target, int value) { ... }
     * }</pre></ul>
     *
     * <p>The type of the first parameter of <code>writeX()</code> must
     * be <code>java.lang.Object</code> independently of the actual
     * type of <code>target</code>.  The type of the second parameter
     * is the same as the field type.
     *
     * @param field             the field.
     * @param calledClass       the class in which the static method is
     *                          declared.
     * @param calledMethod      the name of the static method.
     */
    public void replaceFieldWrite(CtField field,
                                  CtClass calledClass, String calledMethod) {
        transformers = new TransformWriteField(transformers, field,
                                               calledClass.getName(),
                                               calledMethod);
    }

    /**
     * Modify method invocations in a method body so that a different
     * method will be invoked.
     *
     * <p>Note that the target object, the parameters, or 
     * the type of invocation
     * (static method call, interface call, or private method call)
     * are not modified.  Only the method name is changed.  The substituted
     * method must have the same signature that the original one has.
     * If the original method is a static method, the substituted method
     * must be static.
     *
     * @param origMethod        original method
     * @param substMethod       substituted method
     */
    public void redirectMethodCall(CtMethod origMethod,
                                   CtMethod substMethod)
        throws CannotCompileException
    {
        String d1 = origMethod.getMethodInfo2().getDescriptor();
        String d2 = substMethod.getMethodInfo2().getDescriptor();
        if (!d1.equals(d2))
            throw new CannotCompileException("signature mismatch");

        int mod1 = origMethod.getModifiers();
        int mod2 = substMethod.getModifiers();
        if (Modifier.isPrivate(mod1) != Modifier.isPrivate(mod2)
            || Modifier.isStatic(mod1) != Modifier.isStatic(mod2)
            || origMethod.getDeclaringClass().isInterface()
               != substMethod.getDeclaringClass().isInterface())
            throw new CannotCompileException("invoke-type mismatch");

        transformers = new TransformCall(transformers, origMethod,
                                         substMethod);
    }

    /**
     * Correct invocations to a method that has been renamed.
     * If a method is renamed, calls to that method must be also
     * modified so that the method with the new name will be called.
     *
     * <p>The method must be declared in the same class before and
     * after it is renamed.
     *
     * <p>Note that the target object, the parameters, or 
     * the type of invocation
     * (static method call, interface call, or private method call)
     * are not modified.  Only the method name is changed.
     *
     * @param oldMethodName        the old name of the method.
     * @param newMethod            the method with the new name.
     * @see javassist.CtMethod#setName(String)
     */
    public void redirectMethodCall(String oldMethodName,
                                   CtMethod newMethod)
        throws CannotCompileException
    {
        transformers
            = new TransformCall(transformers, oldMethodName, newMethod);
    }

    /**
     * Insert a call to another method before an existing method call.
     * That "before" method must be static.  The return type must be
     * <code>void</code>.  As parameters, the before method receives
     * the target object and all the parameters to the originally invoked
     * method.  For example, if the originally invoked method is
     * <code>move()</code>:
     *
     * <ul><pre>class Point {
     *     Point move(int x, int y) { ... }
     * }</pre></ul>
     *
     * <p>Then the before method must be something like this:
     *
     * <ul><pre>class Verbose {
     *     static void print(Point target, int x, int y) { ... }
     * }</pre></ul>
     *
     * <p>The <code>CodeConverter</code> would translate bytecode
     * equivalent to:
     *
     * <ul><pre>Point p2 = p.move(x + y, 0);</pre></ul>
     *
     * <p>into the bytecode equivalent to:
     *
     * <ul><pre>int tmp1 = x + y;
     * int tmp2 = 0;
     * Verbose.print(p, tmp1, tmp2);
     * Point p2 = p.move(tmp1, tmp2);</pre></ul>
     *
     * @param origMethod        the method originally invoked.
     * @param beforeMethod      the method invoked before
     *                          <code>origMethod</code>.
     */
    public void insertBeforeMethod(CtMethod origMethod,
                                   CtMethod beforeMethod)
        throws CannotCompileException
    {
        try {
            transformers = new TransformBefore(transformers, origMethod,
                                               beforeMethod);
        }
        catch (NotFoundException e) {
            throw new CannotCompileException(e);
        }
    }

    /**
     * Inserts a call to another method after an existing method call.
     * That "after" method must be static.  The return type must be
     * <code>void</code>.  As parameters, the after method receives
     * the target object and all the parameters to the originally invoked
     * method.  For example, if the originally invoked method is
     * <code>move()</code>:
     *
     * <ul><pre>class Point {
     *     Point move(int x, int y) { ... }
     * }</pre></ul>
     *
     * <p>Then the after method must be something like this:
     *
     * <ul><pre>class Verbose {
     *     static void print(Point target, int x, int y) { ... }
     * }</pre></ul>
     *
     * <p>The <code>CodeConverter</code> would translate bytecode
     * equivalent to:
     *
     * <ul><pre>Point p2 = p.move(x + y, 0);</pre></ul>
     *
     * <p>into the bytecode equivalent to:
     *
     * <ul><pre>int tmp1 = x + y;
     * int tmp2 = 0;
     * Point p2 = p.move(tmp1, tmp2);
     * Verbose.print(p, tmp1, tmp2);</pre></ul>
     *
     * @param origMethod        the method originally invoked.
     * @param afterMethod       the method invoked after
     *                          <code>origMethod</code>.
     */
    public void insertAfterMethod(CtMethod origMethod,
                                  CtMethod afterMethod)
        throws CannotCompileException
    {
        try {
            transformers = new TransformAfter(transformers, origMethod,
                                               afterMethod);
        }
        catch (NotFoundException e) {
            throw new CannotCompileException(e);
        }
    }

    /**
     * Performs code conversion.
     */
    void doit(CtClass clazz, MethodInfo minfo, ConstPool cp)
        throws CannotCompileException
    {
        Transformer t;

        CodeAttribute codeAttr = minfo.getCodeAttribute();
        if (codeAttr == null || transformers == null)
            return;

        for (t = transformers; t != null; t = t.getNext())
            t.initialize(cp, codeAttr);

        CodeIterator iterator = codeAttr.iterator();
        while (iterator.hasNext()) {
            try {
                int pos = iterator.next();
                for (t = transformers; t != null; t = t.getNext())
                    pos = t.transform(clazz, pos, iterator, cp);
            }
            catch (BadBytecode e) {
                throw new CannotCompileException(e);
            }
        }

        int locals = 0;
        for (t = transformers; t != null; t = t.getNext()) {
            int s = t.extraLocals();
            if (s > locals)
                locals = s;
        }

        for (t = transformers; t != null; t = t.getNext())
            t.clean();

        codeAttr.setMaxLocals(codeAttr.getMaxLocals() + locals);
    }
}