1/*
2 * Javassist, a Java-bytecode translator toolkit.
3 * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
4 *
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License.  Alternatively, the contents of this file may be used under
8 * the terms of the GNU Lesser General Public License Version 2.1 or later.
9 *
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
14 */
15
16package javassist.expr;
17
18import javassist.bytecode.*;
19import javassist.CtClass;
20import javassist.CannotCompileException;
21
22/**
23 * A translator of method bodies.
24 *
25 * <p>The users can define a subclass of this class to customize how to
26 * modify a method body.  The overall architecture is similar to the
27 * strategy pattern.
28 *
29 * <p>If <code>instrument()</code> is called in
30 * <code>CtMethod</code>, the method body is scanned from the beginning
31 * to the end.
32 * Whenever an expression, such as a method call and a <tt>new</tt>
33 * expression (object creation),
34 * is found, <code>edit()</code> is called in <code>ExprEdit</code>.
35 * <code>edit()</code> can inspect and modify the given expression.
36 * The modification is reflected on the original method body.  If
37 * <code>edit()</code> does nothing, the original method body is not
38 * changed.
39 *
40 * <p>The following code is an example:
41 *
42 * <ul><pre>
43 * CtMethod cm = ...;
44 * cm.instrument(new ExprEditor() {
45 *     public void edit(MethodCall m) throws CannotCompileException {
46 *         if (m.getClassName().equals("Point")) {
47 *             System.out.println(m.getMethodName() + " line: "
48 *                                + m.getLineNumber());
49 *     }
50 * });
51 * </pre></ul>
52 *
53 * <p>This code inspects all method calls appearing in the method represented
54 * by <code>cm</code> and it prints the names and the line numbers of the
55 * methods declared in class <code>Point</code>.  This code does not modify
56 * the body of the method represented by <code>cm</code>.  If the method
57 * body must be modified, call <code>replace()</code>
58 * in <code>MethodCall</code>.
59 *
60 * @see javassist.CtClass#instrument(ExprEditor)
61 * @see javassist.CtMethod#instrument(ExprEditor)
62 * @see javassist.CtConstructor#instrument(ExprEditor)
63 * @see MethodCall
64 * @see NewExpr
65 * @see FieldAccess
66 *
67 * @see javassist.CodeConverter
68 */
69public class ExprEditor {
70    /**
71     * Default constructor.  It does nothing.
72     */
73    public ExprEditor() {}
74
75    /**
76     * Undocumented method.  Do not use; internal-use only.
77     */
78    public boolean doit(CtClass clazz, MethodInfo minfo)
79        throws CannotCompileException
80    {
81        CodeAttribute codeAttr = minfo.getCodeAttribute();
82        if (codeAttr == null)
83            return false;
84
85        CodeIterator iterator = codeAttr.iterator();
86        boolean edited = false;
87        LoopContext context = new LoopContext(codeAttr.getMaxLocals());
88
89        while (iterator.hasNext())
90            if (loopBody(iterator, clazz, minfo, context))
91                edited = true;
92
93        ExceptionTable et = codeAttr.getExceptionTable();
94        int n = et.size();
95        for (int i = 0; i < n; ++i) {
96            Handler h = new Handler(et, i, iterator, clazz, minfo);
97            edit(h);
98            if (h.edited()) {
99                edited = true;
100                context.updateMax(h.locals(), h.stack());
101            }
102        }
103
104        // codeAttr might be modified by other partiess
105        // so I check the current value of max-locals.
106        if (codeAttr.getMaxLocals() < context.maxLocals)
107            codeAttr.setMaxLocals(context.maxLocals);
108
109        codeAttr.setMaxStack(codeAttr.getMaxStack() + context.maxStack);
110        try {
111            if (edited)
112                minfo.rebuildStackMapIf6(clazz.getClassPool(),
113                                         clazz.getClassFile2());
114        }
115        catch (BadBytecode b) {
116            throw new CannotCompileException(b.getMessage(), b);
117        }
118
119        return edited;
120    }
121
122    /**
123     * Visits each bytecode in the given range.
124     */
125    boolean doit(CtClass clazz, MethodInfo minfo, LoopContext context,
126                 CodeIterator iterator, int endPos)
127        throws CannotCompileException
128    {
129        boolean edited = false;
130        while (iterator.hasNext() && iterator.lookAhead() < endPos) {
131            int size = iterator.getCodeLength();
132            if (loopBody(iterator, clazz, minfo, context)) {
133                edited = true;
134                int size2 = iterator.getCodeLength();
135                if (size != size2)  // the body was modified.
136                    endPos += size2 - size;
137            }
138        }
139
140        return edited;
141    }
142
143    final static class NewOp {
144        NewOp next;
145        int pos;
146        String type;
147
148        NewOp(NewOp n, int p, String t) {
149            next = n;
150            pos = p;
151            type = t;
152        }
153    }
154
155    final static class LoopContext {
156        NewOp newList;
157        int maxLocals;
158        int maxStack;
159
160        LoopContext(int locals) {
161            maxLocals = locals;
162            maxStack = 0;
163            newList = null;
164        }
165
166        void updateMax(int locals, int stack) {
167            if (maxLocals < locals)
168                maxLocals = locals;
169
170            if (maxStack < stack)
171                maxStack = stack;
172        }
173    }
174
175    final boolean loopBody(CodeIterator iterator, CtClass clazz,
176                           MethodInfo minfo, LoopContext context)
177        throws CannotCompileException
178    {
179        try {
180            Expr expr = null;
181            int pos = iterator.next();
182            int c = iterator.byteAt(pos);
183
184            if (c < Opcode.GETSTATIC)   // c < 178
185                /* skip */;
186            else if (c < Opcode.NEWARRAY) { // c < 188
187                if (c == Opcode.INVOKESTATIC
188                    || c == Opcode.INVOKEINTERFACE
189                    || c == Opcode.INVOKEVIRTUAL) {
190                    expr = new MethodCall(pos, iterator, clazz, minfo);
191                    edit((MethodCall)expr);
192                }
193                else if (c == Opcode.GETFIELD || c == Opcode.GETSTATIC
194                         || c == Opcode.PUTFIELD
195                         || c == Opcode.PUTSTATIC) {
196                    expr = new FieldAccess(pos, iterator, clazz, minfo, c);
197                    edit((FieldAccess)expr);
198                }
199                else if (c == Opcode.NEW) {
200                    int index = iterator.u16bitAt(pos + 1);
201                    context.newList = new NewOp(context.newList, pos,
202                                        minfo.getConstPool().getClassInfo(index));
203                }
204                else if (c == Opcode.INVOKESPECIAL) {
205                    NewOp newList = context.newList;
206                    if (newList != null
207                        && minfo.getConstPool().isConstructor(newList.type,
208                                            iterator.u16bitAt(pos + 1)) > 0) {
209                        expr = new NewExpr(pos, iterator, clazz, minfo,
210                                           newList.type, newList.pos);
211                        edit((NewExpr)expr);
212                        context.newList = newList.next;
213                    }
214                    else {
215                        MethodCall mcall = new MethodCall(pos, iterator, clazz, minfo);
216                        if (mcall.getMethodName().equals(MethodInfo.nameInit)) {
217                            ConstructorCall ccall = new ConstructorCall(pos, iterator, clazz, minfo);
218                            expr = ccall;
219                            edit(ccall);
220                        }
221                        else {
222                            expr = mcall;
223                            edit(mcall);
224                        }
225                    }
226                }
227            }
228            else {  // c >= 188
229                if (c == Opcode.NEWARRAY || c == Opcode.ANEWARRAY
230                    || c == Opcode.MULTIANEWARRAY) {
231                    expr = new NewArray(pos, iterator, clazz, minfo, c);
232                    edit((NewArray)expr);
233                }
234                else if (c == Opcode.INSTANCEOF) {
235                    expr = new Instanceof(pos, iterator, clazz, minfo);
236                    edit((Instanceof)expr);
237                }
238                else if (c == Opcode.CHECKCAST) {
239                    expr = new Cast(pos, iterator, clazz, minfo);
240                    edit((Cast)expr);
241                }
242            }
243
244            if (expr != null && expr.edited()) {
245                context.updateMax(expr.locals(), expr.stack());
246                return true;
247            }
248            else
249                return false;
250        }
251        catch (BadBytecode e) {
252            throw new CannotCompileException(e);
253        }
254    }
255
256    /**
257     * Edits a <tt>new</tt> expression (overridable).
258     * The default implementation performs nothing.
259     *
260     * @param e         the <tt>new</tt> expression creating an object.
261     */
262    public void edit(NewExpr e) throws CannotCompileException {}
263
264    /**
265     * Edits an expression for array creation (overridable).
266     * The default implementation performs nothing.
267     *
268     * @param a         the <tt>new</tt> expression for creating an array.
269     * @throws CannotCompileException
270     */
271    public void edit(NewArray a) throws CannotCompileException {}
272
273    /**
274     * Edits a method call (overridable).
275     *
276     * The default implementation performs nothing.
277     */
278    public void edit(MethodCall m) throws CannotCompileException {}
279
280    /**
281     * Edits a constructor call (overridable).
282     * The constructor call is either
283     * <code>super()</code> or <code>this()</code>
284     * included in a constructor body.
285     *
286     * The default implementation performs nothing.
287     *
288     * @see #edit(NewExpr)
289     */
290    public void edit(ConstructorCall c) throws CannotCompileException {}
291
292    /**
293     * Edits a field-access expression (overridable).
294     * Field access means both read and write.
295     * The default implementation performs nothing.
296     */
297    public void edit(FieldAccess f) throws CannotCompileException {}
298
299    /**
300     * Edits an instanceof expression (overridable).
301     * The default implementation performs nothing.
302     */
303    public void edit(Instanceof i) throws CannotCompileException {}
304
305    /**
306     * Edits an expression for explicit type casting (overridable).
307     * The default implementation performs nothing.
308     */
309    public void edit(Cast c) throws CannotCompileException {}
310
311    /**
312     * Edits a catch clause (overridable).
313     * The default implementation performs nothing.
314     */
315    public void edit(Handler h) throws CannotCompileException {}
316}
317