org.springsource.loaded.ConstantPoolScanner Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
/**
* Enables us to check things quickly in the constant pool. This version accumulates the class references and the method
* references, for classes that start with 'j' (we want to catch: java/lang). It skips everything it can and the end
* result is a list of class references and a list of method references. The former look like this 'a/b/C' whilst the
* latter look like this 'java/lang/Foo.bar' (the descriptor for the method is not included). Interface methods are
* skipped.
*
* Useful reference: http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html
*
* @author Andy Clement
* @since 0.7.3
*/
public class ConstantPoolScanner {
private static final boolean DEBUG = false;
private final static byte CONSTANT_Utf8 = 1;
private final static byte CONSTANT_Integer = 3;
private final static byte CONSTANT_Float = 4;
private final static byte CONSTANT_Long = 5;
private final static byte CONSTANT_Double = 6;
private final static byte CONSTANT_Class = 7;
private final static byte CONSTANT_String = 8;
private final static byte CONSTANT_Fieldref = 9;
private final static byte CONSTANT_Methodref = 10;
private final static byte CONSTANT_InterfaceMethodref = 11;
private final static byte CONSTANT_NameAndType = 12;
private final static byte CONSTANT_MethodHandle = 15;
private final static byte CONSTANT_MethodType = 16;
private final static byte CONSTANT_InvokeDynamic = 18;
private byte[] classbytes;
// Used during the parse step
private int ptr;
// Filled with strings and int[]
private Object[] cpdata;
private int cpsize;
private int[] type;
// Does not need to be a set as there are no dups in the ConstantPool (for a class from a decent compiler...)
private List referencedClasses = new ArrayList();
private List referencedMethods = new ArrayList();
private String slashedclassname;
public static References getReferences(byte[] classbytes) {
ConstantPoolScanner cpScanner = new ConstantPoolScanner(classbytes);
return new References(cpScanner.slashedclassname, cpScanner.referencedClasses, cpScanner.referencedMethods);
}
private ConstantPoolScanner(byte[] bytes) {
parseClass(bytes);
computeReferences();
}
// Format of a classfile:
// ClassFile {
// u4 magic;
// u2 minor_version;
// u2 major_version;
// u2 constant_pool_count;
// cp_info constant_pool[constant_pool_count-1];
// u2 access_flags;
// u2 this_class;
// u2 super_class;
// u2 interfaces_count;
// u2 interfaces[interfaces_count];
// u2 fields_count;
// field_info fields[fields_count];
// u2 methods_count;
// method_info methods[methods_count];
// u2 attributes_count;
// attribute_info attributes[attributes_count];
// }
private void parseClass(byte[] bytes) {
try {
this.classbytes = bytes;
this.ptr = 0;
int magic = readInt(); // magic 0xCAFEBABE
if (magic != 0xCAFEBABE) {
throw new IllegalStateException("not bytecode, magic was 0x" + Integer.toString(magic, 16));
}
ptr += 4; // skip minor and major versions
cpsize = readUnsignedShort();
if (DEBUG) {
System.out.println("Constant Pool Size =" + cpsize);
}
cpdata = new Object[cpsize];
type = new int[cpsize];
for (int cpentry = 1; cpentry < cpsize; cpentry++) {
boolean wasDoubleSlotItem = processConstantPoolEntry(cpentry);
if (wasDoubleSlotItem) {
cpentry++;
}
}
ptr += 2; // access flags
int thisclassname = readUnsignedShort();
int classindex = ((Integer) cpdata[thisclassname]);
slashedclassname = accessUtf8(classindex);
}
catch (Exception e) {
throw new IllegalStateException("Unexpected problem processing bytes for class", e);
}
}
/**
* Return the UTF8 at the specified index in the constant pool. The data found at the constant pool for that index
* may not have been unpacked yet if this is the first access of the string. If not unpacked the constant pool entry
* is a pair of ints in an array representing the offset and length within the classbytes where the UTF8 string is
* encoded. Once decoded the constant pool entry is flipped from an int array to a String for future fast access.
*
* @param cpIndex constant pool index
* @return UTF8 string at that constant pool index
*/
private String accessUtf8(int cpIndex) {
Object object = cpdata[cpIndex];
if (object instanceof String) {
return (String) object;
}
int[] ptrAndLen = (int[]) object;
String value;
try {
value = new String(classbytes, ptrAndLen[0], ptrAndLen[1], "UTF8");
}
catch (UnsupportedEncodingException e) {
throw new IllegalStateException("Bad data found at constant pool position " + cpIndex + " offset="
+ ptrAndLen[0] + " length=" + ptrAndLen[1], e);
}
cpdata[cpIndex] = value;
return value;
}
/**
* @return an int constructed from the next four bytes to be processed
*/
private final int readInt() {
return ((classbytes[ptr++] & 0xFF) << 24) + ((classbytes[ptr++] & 0xFF) << 16)
+ ((classbytes[ptr++] & 0xFF) << 8)
+ (classbytes[ptr++] & 0xFF);
}
/**
* @return an unsigned short constructed from the next two bytes to be processed
*/
private final int readUnsignedShort() {
return ((classbytes[ptr++] & 0xff) << 8) + (classbytes[ptr++] & 0xff);
}
private boolean processConstantPoolEntry(int index) throws IOException {
byte b = classbytes[ptr++];
switch (b) {
case CONSTANT_Integer: // CONSTANT_Integer_info { u1 tag; u4 bytes; }
case CONSTANT_Float: // CONSTANT_Float_info { u1 tag; u4 bytes; }
case CONSTANT_Fieldref: // CONSTANT_Fieldref_info { u1 tag; u2 class_index; u2 name_and_type_index; }
case CONSTANT_InterfaceMethodref: // CONSTANT_InterfaceMethodref_info { u1 tag; u2 class_index; u2 name_and_type_index; }
case CONSTANT_InvokeDynamic: // CONSTANT_InvokeDynamic_info { u1 tag; u2 bootstrap_method_attr_index; u2 name_and_type_index; }
ptr += 4;
break;
case CONSTANT_Utf8:
// CONSTANT_Utf8_info { u1 tag; u2 length; u1 bytes[length]; }
// Cache just the index and length - do not unpack it now
int len = readUnsignedShort();
cpdata[index] = new int[] { ptr, len };
ptr += len;
break;
case CONSTANT_Long: // CONSTANT_Long_info { u1 tag; u4 high_bytes; u4 low_bytes; }
case CONSTANT_Double: // CONSTANT_Double_info { u1 tag; u4 high_bytes; u4 low_bytes; }
ptr += 8;
return true;
case CONSTANT_Class: // CONSTANT_Class_info { u1 tag; u2 name_index; }
type[index] = b;
cpdata[index] = readUnsignedShort();
break;
case CONSTANT_Methodref:
// CONSTANT_Methodref_info { u1 tag; u2 class_index; u2 name_and_type_index; }
type[index] = b;
cpdata[index] = new int[] { readUnsignedShort(), readUnsignedShort() };
break;
case CONSTANT_NameAndType:
// The CONSTANT_NameAndType_info structure is used to represent a field or method, without indicating which class or interface type it belongs to:
// CONSTANT_NameAndType_info { u1 tag; u2 name_index; u2 descriptor_index; }
// type[index] = b;
cpdata[index] = readUnsignedShort();
ptr += 2; // skip the descriptor for now
break;
case CONSTANT_MethodHandle:
// CONSTANT_MethodHandle_info { u1 tag; u1 reference_kind; u2 reference_index; }
ptr += 3;
break;
case CONSTANT_String: // CONSTANT_String_info { u1 tag; u2 string_index; }
case CONSTANT_MethodType: // CONSTANT_MethodType_info { u1 tag; u2 descriptor_index; }
ptr += 2;
break;
default:
throw new IllegalStateException("Entry: " + index + " " + Byte.toString(b));
}
return false;
}
private void computeReferences() {
for (int i = 0; i < cpsize; i++) {
switch (type[i]) {
case CONSTANT_Class:
int classindex = ((Integer) cpdata[i]);
String classname = accessUtf8(classindex);
if (classname == null) {
throw new IllegalStateException();
}
referencedClasses.add(classname);
break;
case CONSTANT_Methodref:
int[] indexes = (int[]) cpdata[i];
int classindex2 = indexes[0];
int nameAndTypeIndex = indexes[1];
StringBuilder s = new StringBuilder();
String theClassName = accessUtf8((Integer) cpdata[classindex2]);
if (theClassName.charAt(0) == 'j') {
s.append(theClassName);
s.append(".");
s.append(accessUtf8((Integer) cpdata[nameAndTypeIndex]));
referencedMethods.add(s.toString());
}
break;
// private final static byte CONSTANT_Utf8 = 1;
// private final static byte CONSTANT_Integer = 3;
// private final static byte CONSTANT_Float = 4;
// private final static byte CONSTANT_Long = 5;
// private final static byte CONSTANT_Double = 6;
// private final static byte CONSTANT_String = 8;
// private final static byte CONSTANT_Fieldref = 9;
// private final static byte CONSTANT_InterfaceMethodref = 11;
// private final static byte CONSTANT_NameAndType = 12;
}
}
}
public static class References {
public final String slashedClassName;
public final List referencedClasses;
public final List referencedMethods;
References(String slashedClassName, List rc, List rm) {
this.slashedClassName = slashedClassName;
this.referencedClasses = rc;
this.referencedMethods = rm;
}
@Override
public String toString() {
StringBuilder s = new StringBuilder();
s.append("Class=").append(slashedClassName).append("\n");
s.append("ReferencedClasses=#").append(referencedClasses.size()).append("\n");
s.append("ReferencedMethods=#").append(referencedMethods.size()).append("\n");
return s.toString();
}
}
}