blob: ce709df3f0cc61f3bef2ff0cda296fcab45e356d [file] [log] [blame]
/* Copyright 2006 aQute SARL
* Licensed under the Apache License, Version 2.0, see http://www.apache.org/licenses/LICENSE-2.0 */
package aQute.lib.osgi;
import java.io.*;
import java.nio.*;
import java.util.*;
public class Clazz {
public static enum QUERY {
IMPLEMENTS, EXTENDS, IMPORTS, NAMED, ANY, VERSION
};
static protected class Assoc {
Assoc(byte tag, int a, int b) {
this.tag = tag;
this.a = a;
this.b = b;
}
byte tag;
int a;
int b;
}
final static byte SkipTable[] = { 0, // 0 non existent
-1, // 1 CONSTANT_utf8 UTF 8, handled in
// method
-1, // 2
4, // 3 CONSTANT_Integer
4, // 4 CONSTANT_Float
8, // 5 CONSTANT_Long (index +=2!)
8, // 6 CONSTANT_Double (index +=2!)
-1, // 7 CONSTANT_Class
2, // 8 CONSTANT_String
4, // 9 CONSTANT_FieldRef
4, // 10 CONSTANT_MethodRef
4, // 11 CONSTANT_InterfaceMethodRef
4, // 12 CONSTANT_NameAndType
};
String className;
Object pool[];
int intPool[];
Map<String, Map<String, String>> imports = new HashMap<String, Map<String, String>>();
String path;
// static String type = "([BCDFIJSZ\\[]|L[^<>]+;)";
// static Pattern descriptor = Pattern.compile("\\(" + type + "*\\)(("
// + type + ")|V)");
int minor = 0;
int major = 0;
String sourceFile;
Set<String> xref;
Set<Integer> classes;
Set<Integer> descriptors;
int forName = 0;
int class$ = 0;
String[] interfaces;
String zuper;
ClassDataCollector cd = new ClassDataCollector() {
public void addReference(
String token) {
}
public void classBegin(
int access,
String name) {
}
public void classEnd() {
}
public void extendsClass(
String name) {
}
public void field(
int access,
String descriptor) {
}
public void implementsInterfaces(
String[] name) {
}
public void method(
int access,
String descriptor) {
}
};
public Clazz(String path) {
this.path = path;
}
public Clazz(String path, InputStream in) throws IOException {
this.path = path;
DataInputStream din = new DataInputStream(in);
parseClassFile(din);
din.close();
}
Set<String> parseClassFile(DataInputStream in) throws IOException {
xref = new HashSet<String>();
classes = new HashSet<Integer>();
descriptors = new HashSet<Integer>();
boolean crawl = false; // Crawl the byte code
int magic = in.readInt();
if (magic != 0xCAFEBABE)
throw new IOException("Not a valid class file (no CAFEBABE header)");
minor = in.readUnsignedShort(); // minor version
major = in.readUnsignedShort(); // major version
int count = in.readUnsignedShort();
pool = new Object[count];
intPool = new int[count];
process: for (int poolIndex = 1; poolIndex < count; poolIndex++) {
byte tag = in.readByte();
switch (tag) {
case 0:
break process;
case 1:
constantUtf8(in, poolIndex);
break;
// For some insane optimization reason are
// the long and the double two entries in the
// constant pool. See 4.4.5
case 5:
constantLong(in, poolIndex);
poolIndex++;
break;
case 6:
constantDouble(in, poolIndex);
poolIndex++;
break;
case 7:
constantClass(in, poolIndex);
break;
case 8:
constantString(in, poolIndex);
break;
case 10: // Method ref
methodRef(in, poolIndex);
break;
// Name and Type
case 12:
nameAndType(in, poolIndex, tag);
break;
// We get the skip count for each record type
// from the SkipTable. This will also automatically
// abort when
default:
if (tag == 2)
throw new IOException("Invalid tag " + tag);
in.skipBytes(SkipTable[tag]);
break;
}
}
pool(pool, intPool);
/*
* Parse after the constant pool, code thanks to Hans Christian
* Falkenberg
*/
int access_flags = in.readUnsignedShort(); // access
int this_class = in.readUnsignedShort();
className = (String) pool[intPool[this_class]];
cd.classBegin(access_flags, className);
try {
int super_class = in.readUnsignedShort();
zuper = (String) pool[intPool[super_class]];
if (zuper != null) {
addReference(zuper);
cd.extendsClass(zuper);
}
int interfacesCount = in.readUnsignedShort();
if (interfacesCount > 0) {
interfaces = new String[interfacesCount];
for (int i = 0; i < interfacesCount; i++)
interfaces[i] = (String) pool[intPool[in
.readUnsignedShort()]];
cd.implementsInterfaces(interfaces);
}
int fieldsCount = in.readUnsignedShort();
for (int i = 0; i < fieldsCount; i++) {
access_flags = in.readUnsignedShort(); // skip access flags
int name_index = in.readUnsignedShort();
int descriptor_index = in.readUnsignedShort();
// Java prior to 1.5 used a weird
// static variable to hold the com.X.class
// result construct. If it did not find it
// it would create a variable class$com$X
// that would be used to hold the class
// object gotten with Class.forName ...
// Stupidly, they did not actively use the
// class name for the field type, so bnd
// would not see a reference. We detect
// this case and add an artificial descriptor
String name = pool[name_index].toString(); // name_index
if (name.startsWith("class$")) {
crawl = true;
}
cd.field(access_flags, pool[descriptor_index].toString());
descriptors.add(new Integer(descriptor_index));
doAttributes(in, false);
}
//
// Check if we have to crawl the code to find
// the ldc(_w) <string constant> invokestatic Class.forName
// if so, calculate the method ref index so we
// can do this efficiently
//
if (crawl) {
forName = findMethod("java/lang/Class", "forName",
"(Ljava/lang/String;)Ljava/lang/Class;");
class$ = findMethod(className, "class$",
"(Ljava/lang/String;)Ljava/lang/Class;");
}
//
// Handle the methods
//
int methodCount = in.readUnsignedShort();
for (int i = 0; i < methodCount; i++) {
access_flags = in.readUnsignedShort();
/* int name_index = */in.readUnsignedShort();
int descriptor_index = in.readUnsignedShort();
// String s = (String) pool[name_index];
descriptors.add(new Integer(descriptor_index));
cd.method(access_flags, pool[descriptor_index].toString());
doAttributes(in, crawl);
}
doAttributes(in, false);
//
// Now iterate over all classes we found and
// parse those as well. We skip duplicates
//
for (Iterator<Integer> e = classes.iterator(); e.hasNext();) {
int class_index = e.next().shortValue();
doClassReference((String) pool[class_index]);
}
//
// Parse all the descriptors we found
//
for (Iterator<Integer> e = descriptors.iterator(); e.hasNext();) {
Integer index = e.next();
String prototype = (String) pool[index.intValue()];
if (prototype != null)
parseDescriptor(prototype);
else
System.err.println("Unrecognized descriptor: " + index);
}
Set<String> xref = this.xref;
reset();
return xref;
} finally {
cd.classEnd();
}
}
protected void pool(Object[] pool, int[] intPool) {
}
/**
* @param in
* @param poolIndex
* @param tag
* @throws IOException
*/
protected void nameAndType(DataInputStream in, int poolIndex, byte tag)
throws IOException {
int name_index = in.readUnsignedShort();
int descriptor_index = in.readUnsignedShort();
descriptors.add(new Integer(descriptor_index));
pool[poolIndex] = new Assoc(tag, name_index, descriptor_index);
}
/**
* @param in
* @param poolIndex
* @param tag
* @throws IOException
*/
private void methodRef(DataInputStream in, int poolIndex)
throws IOException {
int class_index = in.readUnsignedShort();
int name_and_type_index = in.readUnsignedShort();
pool[poolIndex] = new Assoc((byte) 10, class_index, name_and_type_index);
}
/**
* @param in
* @param poolIndex
* @throws IOException
*/
private void constantString(DataInputStream in, int poolIndex)
throws IOException {
int string_index = in.readUnsignedShort();
intPool[poolIndex] = string_index;
}
/**
* @param in
* @param poolIndex
* @throws IOException
*/
protected void constantClass(DataInputStream in, int poolIndex)
throws IOException {
int class_index = in.readUnsignedShort();
classes.add(new Integer(class_index));
intPool[poolIndex] = class_index;
}
/**
* @param in
* @throws IOException
*/
protected void constantDouble(DataInputStream in, int poolIndex)
throws IOException {
in.skipBytes(8);
}
/**
* @param in
* @throws IOException
*/
protected void constantLong(DataInputStream in, int poolIndex)
throws IOException {
in.skipBytes(8);
}
/**
* @param in
* @param poolIndex
* @throws IOException
*/
protected void constantUtf8(DataInputStream in, int poolIndex)
throws IOException {
// CONSTANT_Utf8
String name = in.readUTF();
xref.add(name);
pool[poolIndex] = name;
}
/**
* Find a method reference in the pool that points to the given class,
* methodname and descriptor.
*
* @param clazz
* @param methodname
* @param descriptor
* @return index in constant pool
*/
private int findMethod(String clazz, String methodname, String descriptor) {
for (int i = 1; i < pool.length; i++) {
if (pool[i] instanceof Assoc) {
Assoc methodref = (Assoc) pool[i];
if (methodref.tag == 10) {
// Method ref
int class_index = methodref.a;
int class_name_index = intPool[class_index];
if (clazz.equals(pool[class_name_index])) {
int name_and_type_index = methodref.b;
Assoc name_and_type = (Assoc) pool[name_and_type_index];
if (name_and_type.tag == 12) {
// Name and Type
int name_index = name_and_type.a;
int type_index = name_and_type.b;
if (methodname.equals(pool[name_index])) {
if (descriptor.equals(pool[type_index])) {
return i;
}
}
}
}
}
}
}
return -1;
}
private void doClassReference(String next) {
if (next != null) {
String normalized = normalize(next);
if (normalized != null) {
cd.addReference(next);
String pack = getPackage(normalized);
packageReference(pack);
}
} else
throw new IllegalArgumentException("Invalid class, parent=");
}
/**
* Called for each attribute in the class, field, or method.
*
* @param in
* The stream
* @throws IOException
*/
private void doAttributes(DataInputStream in, boolean crawl)
throws IOException {
int attributesCount = in.readUnsignedShort();
for (int j = 0; j < attributesCount; j++) {
// skip name CONSTANT_Utf8 pointer
doAttribute(in, crawl);
}
}
/**
* Process a single attribute, if not recognized, skip it.
*
* @param in
* the data stream
* @throws IOException
*/
private void doAttribute(DataInputStream in, boolean crawl)
throws IOException {
int attribute_name_index = in.readUnsignedShort();
String attributeName = (String) pool[attribute_name_index];
if (attribute_name_index == 560)
System.out.println("Index " + attribute_name_index + ":"
+ attributeName);
long attribute_length = in.readInt();
attribute_length &= 0xFFFFFFFF;
if ("RuntimeVisibleAnnotations".equals(attributeName))
doAnnotations(in);
else if ("RuntimeVisibleParameterAnnotations".equals(attributeName))
doParameterAnnotations(in);
else if ("SourceFile".equals(attributeName))
doSourceFile(in);
else if ("Code".equals(attributeName) && crawl)
doCode(in);
else {
if (attribute_length > 0x7FFFFFFF) {
throw new IllegalArgumentException("Attribute > 2Gb");
}
in.skipBytes((int) attribute_length);
}
}
/**
* <pre>
* Code_attribute {
* u2 attribute_name_index;
* u4 attribute_length;
* u2 max_stack;
* u2 max_locals;
* u4 code_length;
* u1 code[code_length];
* u2 exception_table_length;
* { u2 start_pc;
* u2 end_pc;
* u2 handler_pc;
* u2 catch_type;
* } exception_table[exception_table_length];
* u2 attributes_count;
* attribute_info attributes[attributes_count];
* }
* </pre>
*
* @param in
* @param pool
* @throws IOException
*/
private void doCode(DataInputStream in) throws IOException {
/* int max_stack = */in.readUnsignedShort();
/* int max_locals = */in.readUnsignedShort();
int code_length = in.readInt();
byte code[] = new byte[code_length];
in.readFully(code);
crawl(code);
int exception_table_length = in.readUnsignedShort();
in.skipBytes(exception_table_length * 8);
doAttributes(in, false);
}
/**
* We must find Class.forName references ...
*
* @param code
*/
protected void crawl(byte[] code) {
ByteBuffer bb = ByteBuffer.wrap(code);
bb.order(ByteOrder.BIG_ENDIAN);
int lastReference = -1;
while (bb.remaining() > 0) {
int instruction = 0xFF & bb.get();
switch (instruction) {
case OpCodes.ldc:
lastReference = 0xFF & bb.get();
break;
case OpCodes.ldc_w:
lastReference = 0xFFFF & bb.getShort();
break;
case OpCodes.invokestatic:
int methodref = 0xFFFF & bb.getShort();
if ((methodref == forName || methodref == class$)
&& lastReference != -1
&& pool[intPool[lastReference]] instanceof String) {
String clazz = (String) pool[intPool[lastReference]];
doClassReference(clazz.replace('.', '/'));
}
break;
case OpCodes.tableswitch:
// Skip to place divisible by 4
while ((bb.position() & 0x3) != 0)
bb.get();
/* int deflt = */
bb.getInt();
int low = bb.getInt();
int high = bb.getInt();
bb.position(bb.position() + (high - low + 1) * 4);
lastReference = -1;
break;
case OpCodes.lookupswitch:
// Skip to place divisible by 4
while ((bb.position() & 0x3) != 0)
bb.get();
/* deflt = */
bb.getInt();
int npairs = bb.getInt();
bb.position(bb.position() + npairs * 8);
lastReference = -1;
break;
default:
lastReference = -1;
bb.position(bb.position() + OpCodes.OFFSETS[instruction]);
}
}
}
private void doSourceFile(DataInputStream in) throws IOException {
int sourcefile_index = in.readUnsignedShort();
this.sourceFile = pool[sourcefile_index].toString();
}
private void doParameterAnnotations(DataInputStream in) throws IOException {
int num_parameters = in.readUnsignedByte();
for (int p = 0; p < num_parameters; p++) {
int num_annotations = in.readUnsignedShort(); // # of annotations
for (int a = 0; a < num_annotations; a++) {
doAnnotation(in);
}
}
}
private void doAnnotations(DataInputStream in) throws IOException {
int num_annotations = in.readUnsignedShort(); // # of annotations
for (int a = 0; a < num_annotations; a++) {
doAnnotation(in);
}
}
private void doAnnotation(DataInputStream in) throws IOException {
int type_index = in.readUnsignedShort();
descriptors.add(new Integer(type_index));
int num_element_value_pairs = in.readUnsignedShort();
for (int v = 0; v < num_element_value_pairs; v++) {
/* int element_name_index = */in.readUnsignedShort();
doElementValue(in);
}
}
private void doElementValue(DataInputStream in) throws IOException {
int tag = in.readUnsignedByte();
switch (tag) {
case 'B':
case 'C':
case 'D':
case 'F':
case 'I':
case 'J':
case 'S':
case 'Z':
case 's':
/* int const_value_index = */
in.readUnsignedShort();
break;
case 'e':
int type_name_index = in.readUnsignedShort();
descriptors.add(new Integer(type_name_index));
/* int const_name_index = */
in.readUnsignedShort();
break;
case 'c':
int class_info_index = in.readUnsignedShort();
descriptors.add(new Integer(class_info_index));
break;
case '@':
doAnnotation(in);
break;
case '[':
int num_values = in.readUnsignedShort();
for (int i = 0; i < num_values; i++) {
doElementValue(in);
}
break;
default:
throw new IllegalArgumentException(
"Invalid value for Annotation ElementValue tag " + tag);
}
}
void packageReference(String pack) {
if (pack.indexOf('<') >= 0)
System.out.println("Oops: " + pack);
if (!imports.containsKey(pack))
imports.put(pack, new LinkedHashMap<String, String>());
}
public void parseDescriptor(String prototype) {
if (prototype.startsWith("("))
parseMethodDescriptor(prototype);
else
addReference(prototype);
}
void parseMethodDescriptor(String prototype) {
int last = prototype.indexOf(')');
if (last < 0)
throw new IllegalArgumentException(
"Invalid method descriptor in class file: " + className
+ " " + prototype);
for (int i = 1; i < last; i++) {
if (prototype.charAt(i) == 'L') {
int next = prototype.indexOf(';', i);
addReference(prototype.substring(i, next));
i = next;
}
}
addReference(prototype.substring(last + 1));
}
private void addReference(String token) {
cd.addReference(token);
while (token.startsWith("["))
token = token.substring(1);
if (token.startsWith("L")) {
String clazz = normalize(token.substring(1));
if (clazz.startsWith("java/"))
return;
String pack = getPackage(clazz);
packageReference(pack);
}
}
static String normalize(String s) {
if (s.startsWith("[L"))
return normalize(s.substring(2));
if (s.startsWith("["))
if (s.length() == 2)
return null;
else
return normalize(s.substring(1));
if (s.endsWith(";"))
return normalize(s.substring(0, s.length() - 1));
return s + ".class";
}
public static String getPackage(String clazz) {
int n = clazz.lastIndexOf('/');
if (n < 0)
return ".";
return clazz.substring(0, n).replace('/', '.');
}
public Map<String, Map<String, String>> getReferred() {
return imports;
}
String getClassName() {
return className;
}
public String getPath() {
return path;
}
public Set<String> xref(InputStream in) throws IOException {
DataInputStream din = new DataInputStream(in);
Set<String> set = parseClassFile(din);
din.close();
return set;
}
public String getSourceFile() {
return sourceFile;
}
/**
* .class construct for different compilers
*
* sun 1.1 Detect static variable class$com$acme$MyClass 1.2 " 1.3 " 1.4 "
* 1.5 ldc_w (class) 1.6 "
*
* eclipse 1.1 class$0, ldc (string), invokestatic Class.forName 1.2 " 1.3 "
* 1.5 ldc (class) 1.6 "
*
* 1.5 and later is not an issue, sun pre 1.5 is easy to detect the static
* variable that decodes the class name. For eclipse, the class$0 gives away
* we have a reference encoded in a string.
* compilerversions/compilerversions.jar contains test versions of all
* versions/compilers.
*/
public void reset() {
pool = null;
intPool = null;
xref = null;
classes = null;
descriptors = null;
}
public boolean is(QUERY query, Instruction instr,
Map<String, Clazz> classspace) {
switch (query) {
case ANY:
return true;
case NAMED:
if (instr.matches(getClassName()))
return !instr.isNegated();
return false;
case VERSION:
String v = major + "/" + minor;
if (instr.matches(v))
return !instr.isNegated();
return false;
case IMPLEMENTS:
for (int i = 0; interfaces != null && i < interfaces.length; i++) {
if (instr.matches(interfaces[i]))
return !instr.isNegated();
}
break;
case EXTENDS:
if (zuper == null)
return false;
if (instr.matches(zuper))
return !instr.isNegated();
break;
case IMPORTS:
for (String imp : imports.keySet()) {
if (instr.matches(imp.replace('.', '/')))
return !instr.isNegated();
}
}
if (zuper == null || classspace == null)
return false;
Clazz clazz = classspace.get(zuper);
if (clazz == null)
return false;
return clazz.is(query, instr, classspace);
}
public String toString() {
return getFQN();
}
public String getFQN() {
return getClassName().replace('/', '.');
}
public void setClassDataCollector(ClassDataCollector cd) {
this.cd = cd;
}
}