java bytecode.class,通过Java从.class文件中获取ByteCode(依赖性)信息

  • Post author:
  • Post category:java


I would like to analyse .class files and get information about which class uses which other class.

jdeps is a command line tool which allows you to display some information in the console, but I would like to avoid calling an external tool and scraping the command line output.

解决方案

All dependencies are recorded at a central place of a class file, the constant pool. So to efficiently process all dependencies, you need a library allowing to process the constant pool without looking at the rest of the class file (which rules out ASM, which is otherwise a very good bytecode processing library).

So using, e.g. Javassist, you can do the job as

private static Set getDependencies(InputStream is) throws IOException {

ClassFile cf = new ClassFile(new DataInputStream(is));

ConstPool constPool = cf.getConstPool();

HashSet set = new HashSet<>();

for(int ix = 1, size = constPool.getSize(); ix < size; ix++) {

int descriptorIndex;

switch (constPool.getTag(ix)) {

case ConstPool.CONST_Class: set.add(constPool.getClassInfo(ix));

default: continue;

case ConstPool.CONST_NameAndType:

descriptorIndex = constPool.getNameAndTypeDescriptor(ix);

break;

case ConstPool.CONST_MethodType:

descriptorIndex = constPool.getMethodTypeInfo(ix);

}

String desc = constPool.getUtf8Info(descriptorIndex);

for(int p = 0; p

if(desc.charAt(p)==’L’)

set.add(desc.substring(++p, p = desc.indexOf(‘;’, p)).replace(‘/’, ‘.’));

}

return set;

}

Testing it with

try(InputStream is = String.class.getResourceAsStream(“String.class”)) {

set = getDependencies(is);

}

set.stream().sorted().forEachOrdered(System.out::println);

shows

[B

[C

[I

[Ljava.lang.CharSequence;

[Ljava.lang.String;

java.io.ObjectStreamField

java.io.Serializable

java.io.UnsupportedEncodingException

java.lang.AbstractStringBuilder

java.lang.CharSequence

java.lang.Character

java.lang.Comparable

java.lang.Double

java.lang.Float

java.lang.IndexOutOfBoundsException

java.lang.Integer

java.lang.Iterable

java.lang.Long

java.lang.Math

java.lang.NullPointerException

java.lang.Object

java.lang.OutOfMemoryError

java.lang.String

java.lang.String$1

java.lang.String$CaseInsensitiveComparator

java.lang.StringBuffer

java.lang.StringBuilder

java.lang.StringCoding

java.lang.StringCoding$Result

java.lang.StringIndexOutOfBoundsException

java.lang.StringLatin1

java.lang.StringLatin1$CharsSpliterator

java.lang.StringUTF16

java.lang.StringUTF16$CharsSpliterator

java.lang.StringUTF16$CodePointsSpliterator

java.lang.System

java.lang.Throwable

java.lang.Void

java.nio.charset.Charset

java.util.ArrayList

java.util.Arrays

java.util.Comparator

java.util.Formatter

java.util.Iterator

java.util.List

java.util.Locale

java.util.Objects

java.util.Spliterator

java.util.Spliterator$OfInt

java.util.StringJoiner

java.util.regex.Matcher

java.util.regex.Pattern

java.util.stream.IntStream

java.util.stream.StreamSupport

(on Java 9)

You can get to the same result using BCEL:

private static Set getDependencies(InputStream is) throws IOException {

JavaClass cf = new ClassParser(is, “”).parse();

ConstantPool constPool = cf.getConstantPool();

HashSet set = new HashSet<>();

constPool.accept(new DescendingVisitor(cf, new EmptyVisitor() {

@Override public void visitConstantClass(ConstantClass cC) {

set.add(((String)cC.getConstantValue(constPool)).replace(‘/’, ‘.’));

}

@Override public void visitConstantNameAndType(ConstantNameAndType cNaT) {

processSignature(cNaT.getSignature(constPool));

}

@Override public void visitConstantMethodType(ConstantMethodType cMt) {

processSignature(

constPool.constantToString(cMt.getDescriptorIndex(),

(byte)ConstPool.CONST_Utf8));

}

private void processSignature(String desc) {

for(int p = 0; p

if(desc.charAt(p)==’L’)

set.add(desc.substring(++p, p=desc.indexOf(‘;’, p)).replace(‘/’, ‘.’));

}

}));

return set;

}