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 */
15package javassist.convert;
16
17import javassist.CannotCompileException;
18import javassist.ClassPool;
19import javassist.CtClass;
20import javassist.NotFoundException;
21import javassist.CodeConverter.ArrayAccessReplacementMethodNames;
22import javassist.bytecode.BadBytecode;
23import javassist.bytecode.CodeIterator;
24import javassist.bytecode.ConstPool;
25import javassist.bytecode.Descriptor;
26import javassist.bytecode.MethodInfo;
27import javassist.bytecode.analysis.Analyzer;
28import javassist.bytecode.analysis.Frame;
29
30/**
31 * A transformer which replaces array access with static method invocations.
32 *
33 * @author <a href="kabir.khan@jboss.com">Kabir Khan</a>
34 * @author Jason T. Greene
35 * @version $Revision: 1.8 $
36 */
37public final class TransformAccessArrayField extends Transformer {
38    private final String methodClassname;
39    private final ArrayAccessReplacementMethodNames names;
40    private Frame[] frames;
41    private int offset;
42
43    public TransformAccessArrayField(Transformer next, String methodClassname,
44            ArrayAccessReplacementMethodNames names) throws NotFoundException {
45        super(next);
46        this.methodClassname = methodClassname;
47        this.names = names;
48
49    }
50
51    public void initialize(ConstPool cp, CtClass clazz, MethodInfo minfo) throws CannotCompileException {
52        /*
53         * This transformer must be isolated from other transformers, since some
54         * of them affect the local variable and stack maximums without updating
55         * the code attribute to reflect the changes. This screws up the
56         * data-flow analyzer, since it relies on consistent code state. Even
57         * if the attribute values were updated correctly, we would have to
58         * detect it, and redo analysis, which is not cheap. Instead, we are
59         * better off doing all changes in initialize() before everyone else has
60         * a chance to muck things up.
61         */
62        CodeIterator iterator = minfo.getCodeAttribute().iterator();
63        while (iterator.hasNext()) {
64            try {
65                int pos = iterator.next();
66                int c = iterator.byteAt(pos);
67
68                if (c == AALOAD)
69                    initFrames(clazz, minfo);
70
71                if (c == AALOAD || c == BALOAD || c == CALOAD || c == DALOAD
72                        || c == FALOAD || c == IALOAD || c == LALOAD
73                        || c == SALOAD) {
74                    pos = replace(cp, iterator, pos, c, getLoadReplacementSignature(c));
75                } else if (c == AASTORE || c == BASTORE || c == CASTORE
76                        || c == DASTORE || c == FASTORE || c == IASTORE
77                        || c == LASTORE || c == SASTORE) {
78                    pos = replace(cp, iterator, pos, c, getStoreReplacementSignature(c));
79                }
80
81            } catch (Exception e) {
82                throw new CannotCompileException(e);
83            }
84        }
85    }
86
87    public void clean() {
88        frames = null;
89        offset = -1;
90    }
91
92    public int transform(CtClass tclazz, int pos, CodeIterator iterator,
93            ConstPool cp) throws BadBytecode {
94        // Do nothing, see above comment
95        return pos;
96    }
97
98    private Frame getFrame(int pos) throws BadBytecode {
99        return frames[pos - offset]; // Adjust pos
100    }
101
102    private void initFrames(CtClass clazz, MethodInfo minfo) throws BadBytecode {
103        if (frames == null) {
104            frames = ((new Analyzer())).analyze(clazz, minfo);
105            offset = 0; // start tracking changes
106        }
107    }
108
109    private int updatePos(int pos, int increment) {
110        if (offset > -1)
111            offset += increment;
112
113        return pos + increment;
114    }
115
116    private String getTopType(int pos) throws BadBytecode {
117        Frame frame = getFrame(pos);
118        if (frame == null)
119            return null;
120
121        CtClass clazz = frame.peek().getCtClass();
122        return clazz != null ? Descriptor.toJvmName(clazz) : null;
123    }
124
125    private int replace(ConstPool cp, CodeIterator iterator, int pos,
126            int opcode, String signature) throws BadBytecode {
127        String castType = null;
128        String methodName = getMethodName(opcode);
129        if (methodName != null) {
130            // See if the object must be cast
131            if (opcode == AALOAD) {
132                castType = getTopType(iterator.lookAhead());
133                // Do not replace an AALOAD instruction that we do not have a type for
134                // This happens when the state is guaranteed to be null (Type.UNINIT)
135                // So we don't really care about this case.
136                if (castType == null)
137                    return pos;
138                if ("java/lang/Object".equals(castType))
139                    castType = null;
140            }
141
142            // The gap may include extra padding
143            // Write a nop in case the padding pushes the instruction forward
144            iterator.writeByte(NOP, pos);
145            CodeIterator.Gap gap
146                = iterator.insertGapAt(pos, castType != null ? 5 : 2, false);
147            pos = gap.position;
148            int mi = cp.addClassInfo(methodClassname);
149            int methodref = cp.addMethodrefInfo(mi, methodName, signature);
150            iterator.writeByte(INVOKESTATIC, pos);
151            iterator.write16bit(methodref, pos + 1);
152
153            if (castType != null) {
154                int index = cp.addClassInfo(castType);
155                iterator.writeByte(CHECKCAST, pos + 3);
156                iterator.write16bit(index, pos + 4);
157            }
158
159            pos = updatePos(pos, gap.length);
160        }
161
162        return pos;
163    }
164
165    private String getMethodName(int opcode) {
166        String methodName = null;
167        switch (opcode) {
168        case AALOAD:
169            methodName = names.objectRead();
170            break;
171        case BALOAD:
172            methodName = names.byteOrBooleanRead();
173            break;
174        case CALOAD:
175            methodName = names.charRead();
176            break;
177        case DALOAD:
178            methodName = names.doubleRead();
179            break;
180        case FALOAD:
181            methodName = names.floatRead();
182            break;
183        case IALOAD:
184            methodName = names.intRead();
185            break;
186        case SALOAD:
187            methodName = names.shortRead();
188            break;
189        case LALOAD:
190            methodName = names.longRead();
191            break;
192        case AASTORE:
193            methodName = names.objectWrite();
194            break;
195        case BASTORE:
196            methodName = names.byteOrBooleanWrite();
197            break;
198        case CASTORE:
199            methodName = names.charWrite();
200            break;
201        case DASTORE:
202            methodName = names.doubleWrite();
203            break;
204        case FASTORE:
205            methodName = names.floatWrite();
206            break;
207        case IASTORE:
208            methodName = names.intWrite();
209            break;
210        case SASTORE:
211            methodName = names.shortWrite();
212            break;
213        case LASTORE:
214            methodName = names.longWrite();
215            break;
216        }
217
218        if (methodName.equals(""))
219            methodName = null;
220
221        return methodName;
222    }
223
224    private String getLoadReplacementSignature(int opcode) throws BadBytecode {
225        switch (opcode) {
226        case AALOAD:
227            return "(Ljava/lang/Object;I)Ljava/lang/Object;";
228        case BALOAD:
229            return "(Ljava/lang/Object;I)B";
230        case CALOAD:
231            return "(Ljava/lang/Object;I)C";
232        case DALOAD:
233            return "(Ljava/lang/Object;I)D";
234        case FALOAD:
235            return "(Ljava/lang/Object;I)F";
236        case IALOAD:
237            return "(Ljava/lang/Object;I)I";
238        case SALOAD:
239            return "(Ljava/lang/Object;I)S";
240        case LALOAD:
241            return "(Ljava/lang/Object;I)J";
242        }
243
244        throw new BadBytecode(opcode);
245    }
246
247    private String getStoreReplacementSignature(int opcode) throws BadBytecode {
248        switch (opcode) {
249        case AASTORE:
250            return "(Ljava/lang/Object;ILjava/lang/Object;)V";
251        case BASTORE:
252            return "(Ljava/lang/Object;IB)V";
253        case CASTORE:
254            return "(Ljava/lang/Object;IC)V";
255        case DASTORE:
256            return "(Ljava/lang/Object;ID)V";
257        case FASTORE:
258            return "(Ljava/lang/Object;IF)V";
259        case IASTORE:
260            return "(Ljava/lang/Object;II)V";
261        case SASTORE:
262            return "(Ljava/lang/Object;IS)V";
263        case LASTORE:
264            return "(Ljava/lang/Object;IJ)V";
265        }
266
267        throw new BadBytecode(opcode);
268    }
269}
270