Parse Jar file and find relationships between clas

2020-04-20 21:36发布

问题:

How to detect whether the class from the jar file is extending other class or if there are method calls to other class objects or other class objects are created ? and then system out which class extend which class and which class called methods from which class .

Im using Classparser to parser the jar . here is part of my code :

        String jarfile = "C:\\Users\\OOOO\\Desktop\\Sample.Jar";

        jar = new JarFile(jarfile);
        Enumeration<JarEntry> entries = jar.entries();
        while (entries.hasMoreElements()) {
            JarEntry entry = entries.nextElement();
            if (!entry.getName().endsWith(".class")) {
                continue;
            }

            ClassParser parser = new ClassParser(jarfile, entry.getName());
            JavaClass javaClass = parser.parse();

回答1:

Someone voted to close this question as "too broad". I'm not sure whether this is the appropriate close reason here, but it might be, because one could consider this question (which is a follow up to your previous question) as just asking others to do some work for you.

However, to answer the basic question of how to detect references between classes in a single JAR file with BCEL:

You can obtain the list of JavaClass objects from the JarFile. For each of these JavaClass objects, you can inspect the Method objects and their InstructionList. Out of these instructions, you can select the InvokeInstruction objects and examine them further to find out which method on which class is actually invoked there.

The following program opens a JAR file (for obvious reasons, it's the bcel-5.2.jar - you'll need it anyhow...) and processes it in the way described above. For each JavaClass of the JAR file, it creates a map from all referenced JavaClass objects to the list of the Methods that are invoked on these classes, and prints the information accordingly:

import java.io.IOException;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.apache.bcel.classfile.ClassFormatException;
import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.ConstantPool;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.Instruction;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.InvokeInstruction;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.ObjectType;
import org.apache.bcel.generic.ReferenceType;
import org.apache.bcel.generic.Type;

public class BCELRelationships
{
    public static void main(String[] args) throws Exception
    {
        JarFile jarFile = null;
        try
        {
            String jarName = "bcel-5.2.jar";
            jarFile = new JarFile(jarName);
            findReferences(jarName, jarFile);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (jarFile != null)
            {
                try
                {
                    jarFile.close();
                }
                catch (IOException e)
                {
                    e.printStackTrace();
                }
            }
        }
    }

    private static void findReferences(String jarName, JarFile jarFile) 
        throws ClassFormatException, IOException, ClassNotFoundException
    {
        Map<String, JavaClass> javaClasses = 
            collectJavaClasses(jarName, jarFile);

        for (JavaClass javaClass : javaClasses.values())
        {
            System.out.println("Class "+javaClass.getClassName());
            Map<JavaClass, Set<Method>> references = 
                computeReferences(javaClass, javaClasses);
            for (Entry<JavaClass, Set<Method>> entry : references.entrySet())
            {
                JavaClass referencedJavaClass = entry.getKey();
                Set<Method> methods = entry.getValue();
                System.out.println(
                    "    is referencing class "+
                    referencedJavaClass.getClassName()+" by calling");
                for (Method method : methods)
                {
                    System.out.println(
                        "        "+method.getName()+" with arguments "+
                        Arrays.toString(method.getArgumentTypes()));
                }
            }
        }
    }

    private static Map<String, JavaClass> collectJavaClasses(
        String jarName, JarFile jarFile) 
            throws ClassFormatException, IOException
    {
        Map<String, JavaClass> javaClasses =
            new LinkedHashMap<String, JavaClass>();
        Enumeration<JarEntry> entries = jarFile.entries();
        while (entries.hasMoreElements())
        {
            JarEntry entry = entries.nextElement();
            if (!entry.getName().endsWith(".class"))
            {
                continue;
            }

            ClassParser parser = 
                new ClassParser(jarName, entry.getName());
            JavaClass javaClass = parser.parse();
            javaClasses.put(javaClass.getClassName(), javaClass);
        }
        return javaClasses;
    }

    public static Map<JavaClass, Set<Method>> computeReferences(
        JavaClass javaClass, Map<String, JavaClass> knownJavaClasses) 
            throws ClassNotFoundException
    {
        Map<JavaClass, Set<Method>> references = 
            new LinkedHashMap<JavaClass, Set<Method>>();
        ConstantPool cp = javaClass.getConstantPool();
        ConstantPoolGen cpg = new ConstantPoolGen(cp);
        for (Method m : javaClass.getMethods())
        {
            String fullClassName = javaClass.getClassName();
            String className = 
                fullClassName.substring(0, fullClassName.length()-6);
            MethodGen mg = new MethodGen(m, className, cpg);
            InstructionList il = mg.getInstructionList();
            if (il == null)
            {
                continue;
            }
            InstructionHandle[] ihs = il.getInstructionHandles();
            for(int i=0; i < ihs.length; i++) 
            {
                InstructionHandle ih = ihs[i];
                Instruction instruction = ih.getInstruction();
                if (!(instruction instanceof InvokeInstruction))
                {
                    continue;
                }
                InvokeInstruction ii = (InvokeInstruction)instruction;
                ReferenceType referenceType = ii.getReferenceType(cpg);
                if (!(referenceType instanceof ObjectType))
                {
                    continue;
                }

                ObjectType objectType = (ObjectType)referenceType;
                String referencedClassName = objectType.getClassName();
                JavaClass referencedJavaClass = 
                    knownJavaClasses.get(referencedClassName);
                if (referencedJavaClass == null)
                {
                    continue;
                }

                String methodName = ii.getMethodName(cpg);
                Type[] argumentTypes = ii.getArgumentTypes(cpg);
                Method method = 
                    findMethod(referencedJavaClass, methodName, argumentTypes);
                Set<Method> methods = references.get(referencedJavaClass);
                if (methods == null)
                {
                    methods = new LinkedHashSet<Method>();
                    references.put(referencedJavaClass, methods);
                }
                methods.add(method);
            }
        }
        return references;
    }

    private static Method findMethod(
        JavaClass javaClass, String methodName, Type argumentTypes[])
            throws ClassNotFoundException
    {
        for (Method method : javaClass.getMethods())
        {
            if (method.getName().equals(methodName))
            {
                if (Arrays.equals(argumentTypes, method.getArgumentTypes()))
                {
                    return method;
                }
            }
        }
        for (JavaClass superClass : javaClass.getSuperClasses())
        {
            Method method = findMethod(superClass, methodName, argumentTypes);
            if (method != null)
            {
                return method;
            }
        }
        return null;
    }
}

Note, however, that this information might not be complete in every sense. For example, due to polymorphism, you might not always detect that a method is called on an object of a certain class, because it is "hidden" behind the polymorphic abstraction. For example, in a code snippet like

void call() {
    MyClass m = new MyClass();
    callToString(m);
}
void callToString(Object object) {
    object.toString();
}

the call to toString actually happens on an instance of MyClass. But due to polymorphism, it can only be recognized as a call to this method on "some Object".



回答2:

Disclaimer: This is, strictly speaking, not an answer to your question because it uses not BCEL but Javassist. Nevertheless you may find my experiences and code useful.


Few years ago I've written e Maven plugin (I called it Storyteller Maven Plugin) for this very purpose - to analyse JARs files for dependencies which are unnecessary or nor required.

Please see this question:

How to find unneccesary dependencies in a maven multi-project?

And my answer to it.

Although the plugin worked I have never released it back then. Now I've moved it to GitHub just to make it accessible for others.

You ask about parsing a JAR to analyze the code in .class files. Below are a couple of Javassist code snippets.

Search a JAR file for classes and create a CtClass per entry:

final JarFile artifactJarFile = new JarFile(artifactFile);
final Enumeration<JarEntry> jarEntries = artifactJarFile
        .entries();

while (jarEntries.hasMoreElements()) {
    final JarEntry jarEntry = jarEntries.nextElement();

    if (jarEntry.getName().endsWith(".class")) {
        InputStream is = null;
        CtClass ctClass = null;
        try {
            is = artifactJarFile.getInputStream(jarEntry);
            ctClass = classPool.makeClass(is);
        } catch (IOException ioex1) {
            throw new MojoExecutionException(
                    "Could not load class from JAR entry ["
                            + artifactFile.getAbsolutePath()
                            + "/" + jarEntry.getName() + "].");
        } finally {
            try {
                if (is != null)
                    is.close();
            } catch (IOException ignored) {
                // Ignore
            }
        }
        // ...
    }
}

Finding out referenced classes is then just:

final Collection<String> referencedClassNames = ctClass.getRefClasses();

Overall my experience with Javassist for the very similar task was very positive. I hope this helps.