【问题标题】:Parse Jar file and find relationships between classes?解析 Jar 文件并查找类之间的关系?
【发布时间】:2015-01-01 19:39:48
【问题描述】:

如何检测jar文件中的类是否在扩展其他类,或者是否有对其他类对象的方法调用或创建了其他类对象? 然后系统出哪个类扩展了哪个类,哪个类调用了哪个类的方法。

我正在使用 Classparser 来解析 jar 。这是我的代码的一部分:

        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();

【问题讨论】:

    标签: java reverse-engineering reverse bcel


    【解决方案1】:

    有人投票认为这个问题“太宽泛”了。我不确定这是否是适当的关闭原因,但它可能是,因为人们可以认为这个问题(这是your previous question 的后续问题)只是要求其他人为你做一些工作。

    但是,要回答如何使用 BCEL 检测单个 JAR 文件中的类之间的引用这一基本问题:

    您可以从JarFile 获取JavaClass 对象的列表。对于每个JavaClass 对象,您可以检查Method 对象及其InstructionList。从这些说明中,您可以选择InvokeInstruction 对象并进一步检查它们以找出实际调用哪个类的哪个方法。

    以下程序打开一个 JAR 文件(出于显而易见的原因,它是 bcel-5.2.jar - 无论如何你都需要它......)并以上述方式处理它。对于 JAR 文件的每个 JavaClass,它会创建一个从所有引用的 JavaClass 对象到在这些类上调用的 Methods 列表的映射,并相应地打印信息:

    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;
        }
    }
    

    但请注意,此信息可能并非在所有意义上都是完整的。例如,由于多态性,您可能并不总是检测到某个类的对象上调用了方法,因为它“隐藏”在多态抽象后面。例如,在一个类似sn-p的代码中

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

    toString 的调用实际上发生在MyClass 的实例上。但由于多态性,只能识别为在“someObject”上调用该方法。

    【讨论】:

      【解决方案2】:

      免责声明:严格来说,这不是您问题的答案,因为它使用的不是BCEL,而是Javassist。不过,您可能会发现我的经验和代码很有用。


      几年前,我为此目的编写了 e Maven 插件(我称之为 Storyteller Maven Plugin) - 分析 JAR 文件中不必要或不需要的依赖项。

      请看这个问题:

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

      还有my answer

      虽然该插件有效,但当时我从未发布过它。现在我 moved it to GitHub 只是为了让其他人可以访问它。

      您询问有关解析 JAR 以分析 .class 文件中的代码的问题。下面是几个 Javassist 代码 sn-ps。

      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
                  }
              }
              // ...
          }
      }
      

      找出引用的类is then just

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

      总体而言,我使用 Javassist 完成非常相似的任务的体验非常积极。我希望这会有所帮助。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-10-21
        • 2021-03-17
        • 1970-01-01
        • 2014-11-05
        • 1970-01-01
        • 2021-12-04
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多