001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019
020package org.apache.bytecode;
021
022
023import java.io.ByteArrayInputStream;
024import java.io.ByteArrayOutputStream;
025import java.io.EOFException;
026import java.io.IOException;
027import java.io.InputStream;
028import java.lang.reflect.Constructor;
029import java.lang.reflect.Field;
030import java.lang.reflect.InvocationTargetException;
031import java.lang.reflect.Member;
032import java.lang.reflect.Method;
033import java.util.HashMap;
034import java.util.Map;
035
036
037/**
038 * This is the class file reader for obtaining the parameter names
039 * for declared methods in a class.  The class must have debugging
040 * attributes for us to obtain this information. <p>
041 * <p/>
042 * This does not work for inherited methods.  To obtain parameter
043 * names for inherited methods, you must use a paramReader for the
044 * class that originally declared the method. <p>
045 * <p/>
046 * don't get tricky, it's the bare minimum.  Instances of this class
047 * are not threadsafe -- don't share them. <p>
048 */
049public class ClassReader extends ByteArrayInputStream {
050    // constants values that appear in java class files,
051    // from jvm spec 2nd ed, section 4.4, pp 103
052    private static final int CONSTANT_Class = 7;
053    private static final int CONSTANT_Fieldref = 9;
054    private static final int CONSTANT_Methodref = 10;
055    private static final int CONSTANT_InterfaceMethodref = 11;
056    private static final int CONSTANT_String = 8;
057    private static final int CONSTANT_Integer = 3;
058    private static final int CONSTANT_Float = 4;
059    private static final int CONSTANT_Long = 5;
060    private static final int CONSTANT_Double = 6;
061    private static final int CONSTANT_NameAndType = 12;
062    private static final int CONSTANT_Utf8 = 1;
063    /**
064     * the constant pool.  constant pool indices in the class file
065     * directly index into this array.  The value stored in this array
066     * is the position in the class file where that constant begins.
067     */
068    private int[] cpoolIndex;
069    private Object[] cpool;
070
071    private Map attrMethods;
072
073    /**
074     * Loads the bytecode for a given class, by using the class's defining
075     * classloader and assuming that for a class named P.C, the bytecodes are
076     * in a resource named /P/C.class.
077     *
078     * @param c the class of interest
079     * @return Returns a byte array containing the bytecode
080     * @throws IOException
081     */
082    protected static byte[] getBytes(Class c) throws IOException {
083        InputStream fin = c.getResourceAsStream('/' + c.getName().replace('.', '/') + ".class");
084        if (fin == null) {
085            throw new IOException("Unable to load bytecode for class " + c.getName()
086                   );
087        }
088        try {
089            ByteArrayOutputStream out = new ByteArrayOutputStream();
090            byte[] buf = new byte[1024];
091            int actual;
092            do {
093                actual = fin.read(buf);
094                if (actual > 0) {
095                    out.write(buf, 0, actual);
096                }
097            } while (actual > 0);
098            return out.toByteArray();
099        } finally {
100            fin.close();
101        }
102    }
103
104    static String classDescriptorToName(String desc) {
105        return desc.replace('/', '.');
106    }
107
108    protected static Map findAttributeReaders(Class c) {
109        HashMap map = new HashMap();
110        Method[] methods = c.getMethods();
111
112        for (int i = 0; i < methods.length; i++) {
113            String name = methods[i].getName();
114            if (name.startsWith("read") && methods[i].getReturnType() == void.class) {
115                map.put(name.substring(4), methods[i]);
116            }
117        }
118
119        return map;
120    }
121
122
123    protected static String getSignature(Member method, Class[] paramTypes) {
124        // compute the method descriptor
125
126        StringBuffer b = new StringBuffer((method instanceof Method) ? method.getName() : "<init>");
127        b.append('(');
128
129        for (int i = 0; i < paramTypes.length; i++) {
130            addDescriptor(b, paramTypes[i]);
131        }
132
133        b.append(')');
134        if (method instanceof Method) {
135            addDescriptor(b, ((Method) method).getReturnType());
136        } else if (method instanceof Constructor) {
137            addDescriptor(b, void.class);
138        }
139
140        return b.toString();
141    }
142
143    private static void addDescriptor(StringBuffer b, Class c) {
144        if (c.isPrimitive()) {
145            if (c == void.class)
146                b.append('V');
147            else if (c == int.class)
148                b.append('I');
149            else if (c == boolean.class)
150                b.append('Z');
151            else if (c == byte.class)
152                b.append('B');
153            else if (c == short.class)
154                b.append('S');
155            else if (c == long.class)
156                b.append('J');
157            else if (c == char.class)
158                b.append('C');
159            else if (c == float.class)
160                b.append('F');
161            else if (c == double.class) b.append('D');
162        } else if (c.isArray()) {
163            b.append('[');
164            addDescriptor(b, c.getComponentType());
165        } else {
166            b.append('L').append(c.getName().replace('.', '/')).append(';');
167        }
168    }
169
170
171    /**
172     * @return Returns the next unsigned 16 bit value.
173     */
174    protected final int readShort() {
175        return (read() << 8) | read();
176    }
177
178    /**
179     * @return Returns the next signed 32 bit value.
180     */
181    protected final int readInt() {
182        return (read() << 24) | (read() << 16) | (read() << 8) | read();
183    }
184
185    /**
186     * Skips n bytes in the input stream.
187     */
188    protected void skipFully(int n) throws IOException {
189        while (n > 0) {
190            int c = (int) skip(n);
191            if (c <= 0)
192                throw new EOFException("Error looking for paramter names in bytecode: unexpected end of file");
193            n -= c;
194        }
195    }
196
197    protected final Member resolveMethod(int index) throws IOException, ClassNotFoundException, NoSuchMethodException {
198        int oldPos = pos;
199        try {
200            Member m = (Member) cpool[index];
201            if (m == null) {
202                pos = cpoolIndex[index];
203                Class owner = resolveClass(readShort());
204                NameAndType nt = resolveNameAndType(readShort());
205                String signature = nt.name + nt.type;
206                if (nt.name.equals("<init>")) {
207                    Constructor[] ctors = owner.getConstructors();
208                    for (int i = 0; i < ctors.length; i++) {
209                        String sig = getSignature(ctors[i], ctors[i].getParameterTypes());
210                        if (sig.equals(signature)) {
211                            cpool[index] = m = ctors[i];
212                            return m;
213                        }
214                    }
215                } else {
216                    Method[] methods = owner.getDeclaredMethods();
217                    for (int i = 0; i < methods.length; i++) {
218                        String sig = getSignature(methods[i], methods[i].getParameterTypes());
219                        if (sig.equals(signature)) {
220                            cpool[index] = m = methods[i];
221                            return m;
222                        }
223                    }
224                }
225                throw new NoSuchMethodException(signature);
226            }
227            return m;
228        } finally {
229            pos = oldPos;
230        }
231
232    }
233
234    protected final Field resolveField(int i) throws IOException, ClassNotFoundException, NoSuchFieldException {
235        int oldPos = pos;
236        try {
237            Field f = (Field) cpool[i];
238            if (f == null) {
239                pos = cpoolIndex[i];
240                Class owner = resolveClass(readShort());
241                NameAndType nt = resolveNameAndType(readShort());
242                cpool[i] = f = owner.getDeclaredField(nt.name);
243            }
244            return f;
245        } finally {
246            pos = oldPos;
247        }
248    }
249
250    private static class NameAndType {
251        String name;
252        String type;
253
254        public NameAndType(String name, String type) {
255            this.name = name;
256            this.type = type;
257        }
258    }
259
260    protected final NameAndType resolveNameAndType(int i) throws IOException {
261        int oldPos = pos;
262        try {
263            NameAndType nt = (NameAndType) cpool[i];
264            if (nt == null) {
265                pos = cpoolIndex[i];
266                String name = resolveUtf8(readShort());
267                String type = resolveUtf8(readShort());
268                cpool[i] = nt = new NameAndType(name, type);
269            }
270            return nt;
271        } finally {
272            pos = oldPos;
273        }
274    }
275
276
277    protected final Class resolveClass(int i) throws IOException, ClassNotFoundException {
278        int oldPos = pos;
279        try {
280            Class c = (Class) cpool[i];
281            if (c == null) {
282                pos = cpoolIndex[i];
283                String name = resolveUtf8(readShort());
284                cpool[i] = c = Class.forName(classDescriptorToName(name));
285            }
286            return c;
287        } finally {
288            pos = oldPos;
289        }
290    }
291
292    protected final String resolveUtf8(int i) throws IOException {
293        int oldPos = pos;
294        try {
295            String s = (String) cpool[i];
296            if (s == null) {
297                pos = cpoolIndex[i];
298                int len = readShort();
299                skipFully(len);
300                cpool[i] = s = new String(buf, pos - len, len, "utf-8");
301            }
302            return s;
303        } finally {
304            pos = oldPos;
305        }
306    }
307
308    protected final void readCpool() throws IOException {
309        int count = readShort(); // cpool count
310        cpoolIndex = new int[count];
311        cpool = new Object[count];
312        for (int i = 1; i < count; i++) {
313            int c = read();
314            cpoolIndex[i] = super.pos;
315            switch (c) // constant pool tag
316            {
317                case CONSTANT_Fieldref:
318                case CONSTANT_Methodref:
319                case CONSTANT_InterfaceMethodref:
320                case CONSTANT_NameAndType:
321
322                    readShort(); // class index or (12) name index
323                    // fall through
324
325                case CONSTANT_Class:
326                case CONSTANT_String:
327
328                    readShort(); // string index or class index
329                    break;
330
331                case CONSTANT_Long:
332                case CONSTANT_Double:
333
334                    readInt(); // hi-value
335
336                    // see jvm spec section 4.4.5 - double and long cpool
337                    // entries occupy two "slots" in the cpool table.
338                    i++;
339                    // fall through
340
341                case CONSTANT_Integer:
342                case CONSTANT_Float:
343
344                    readInt(); // value
345                    break;
346
347                case CONSTANT_Utf8:
348
349                    int len = readShort();
350                    skipFully(len);
351                    break;
352
353                default:
354                    // corrupt class file
355                    throw new IllegalStateException("Error looking for paramter names in bytecode: unexpected bytes in file");
356            }
357        }
358    }
359
360    protected final void skipAttributes() throws IOException {
361        int count = readShort();
362        for (int i = 0; i < count; i++) {
363            readShort(); // name index
364            skipFully(readInt());
365        }
366    }
367
368    /**
369     * Reads an attributes array.  The elements of a class file that
370     * can contain attributes are: fields, methods, the class itself,
371     * and some other types of attributes.
372     */
373    protected final void readAttributes() throws IOException {
374        int count = readShort();
375        for (int i = 0; i < count; i++) {
376            int nameIndex = readShort(); // name index
377            int attrLen = readInt();
378            int curPos = pos;
379
380            String attrName = resolveUtf8(nameIndex);
381
382            Method m = (Method) attrMethods.get(attrName);
383
384            if (m != null) {
385                try {
386                    m.invoke(this, new Object[]{});
387                } catch (IllegalAccessException e) {
388                    pos = curPos;
389                    skipFully(attrLen);
390                } catch (InvocationTargetException e) {
391                    try {
392                        throw e.getTargetException();
393                    } catch (Error ex) {
394                        throw ex;
395                    } catch (RuntimeException ex) {
396                        throw ex;
397                    } catch (IOException ex) {
398                        throw ex;
399                    } catch (Throwable ex) {
400                        pos = curPos;
401                        skipFully(attrLen);
402                    }
403                }
404            } else {
405                // don't care what attribute this is
406                skipFully(attrLen);
407            }
408        }
409    }
410
411    /**
412     * Reads a code attribute.
413     *
414     * @throws IOException
415     */
416    public void readCode() throws IOException {
417        readShort(); // max stack
418        readShort(); // max locals
419        skipFully(readInt()); // code
420        skipFully(8 * readShort()); // exception table
421
422        // read the code attributes (recursive).  This is where
423        // we will find the LocalVariableTable attribute.
424        readAttributes();
425    }
426
427    protected ClassReader(byte buf[], Map attrMethods) {
428        super(buf);
429
430        this.attrMethods = attrMethods;
431    }
432}
433