All Downloads are FREE. Search and download functionalities are using the official Maven repository.

gr.gousiosg.javacg.stat.JCallGraph Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2011 - Georgios Gousios 
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *
 *     * Redistributions in binary form must reproduce the above
 *       copyright notice, this list of conditions and the following
 *       disclaimer in the documentation and/or other materials provided
 *       with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package gr.gousiosg.javacg.stat;

import gr.gousiosg.javacg.common.Constants;
import gr.gousiosg.javacg.dto.*;
import gr.gousiosg.javacg.enums.CallTypeEnum;
import gr.gousiosg.javacg.util.CommonUtil;
import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * Constructs a callgraph out of a JAR archive. Can combine multiple archives
 * into a single call graph.
 *
 * @author Georgios Gousios 
 */
public class JCallGraph {

    public static final int INIT_SIZE_100 = 100;
    public static final int INIT_SIZE_500 = 500;
    public static final int INIT_SIZE_1000 = 1000;

    // added by adrninistrator
    private static Map> calleeMethodMapGlobal;
    private static Map classInterfaceMethodInfoMap;
    private static Map> interfaceMethodWithArgsMap;
    private static Map runnableImplClassMap;
    private static Map callableImplClassMap;
    private static Map threadChildClassMap;
    private static Map> methodAnnotationMap;
    private static Set extendsClassesSet;
    private static Map extendsClassMethodInfoMap;
    private static Map> childrenClassInfoMap;

    private static final String RUNNABLE_CLASS_NAME = Runnable.class.getName();
    private static final String CALLABLE_CLASS_NAME = Callable.class.getName();
    private static final String THREAD_CLASS_NAME = Thread.class.getName();

    private static int jarNum = 0;
    // added end

    public static void main(String[] args) {
        run(args);
    }

    public static boolean run(String[] args) {

        // added by adrninistrator
        String outputFilePath = System.getProperty("output.file");
        if (outputFilePath == null || outputFilePath.isEmpty()) {
            System.err.println("please use \"-Doutput.file=xxx\" to specify the output file");
            return false;
        }
        // added end

        // modified by adrninistrator
        Map filePathSet = new HashMap<>(args.length);

        String annotationOutputFilePath = outputFilePath + "-annotation.txt";
        System.out.println("write method annotation information to file: " + annotationOutputFilePath);

        try (BufferedWriter resultWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(outputFilePath))));
             BufferedWriter annotationOut = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(annotationOutputFilePath),
                     StandardCharsets.UTF_8))) {
            for (String arg : args) {
                String jarFilePath = CommonUtil.getCanonicalPath(arg);
                if (jarFilePath == null) {
                    System.err.println("getCanonicalPath fail: " + arg);
                    return false;
                }

                if (filePathSet.get(jarFilePath) != null) {
                    System.out.println(arg + " skip jar file: " + jarFilePath);
                    continue;
                }

                filePathSet.put(jarFilePath, ++jarNum);

                System.out.println(arg + " handle jar file: " + jarFilePath);
                File f = new File(jarFilePath);

                if (!f.exists()) {
                    System.err.println("Jar file " + jarFilePath + " does not exist");
                }

                try (JarFile jar = new JarFile(f)) {
                    // added by adrninistrator
                    writeResult(resultWriter, "J:" + jarNum + " " + jarFilePath);
                    writeResult(resultWriter, Constants.NEW_LINE);

                    init();

                    // pre handle classes
                    if (!preHandleClasses(jarFilePath, f)) {
                        return false;
                    }
                    // added end

                    for (Enumeration enumeration = jar.entries(); enumeration.hasMoreElements(); ) {
                        JarEntry jarEntry = enumeration.nextElement();
                        if (!jarEntry.isDirectory() && jarEntry.getName().endsWith(".class")) {
                            ClassParser cp = new ClassParser(jarFilePath, jarEntry.getName());
                            JavaClass javaClass = cp.parse();

                            System.out.println("handle class: " + javaClass.getClassName());

                            if (javaClass.isClass() && extendsClassesSet.contains(javaClass.getClassName())) {
                                findExtendsClassesInfo(javaClass);
                            }

                            ClassVisitor classVisitor = new ClassVisitor(javaClass);
                            classVisitor.setCalleeMethodMap(calleeMethodMapGlobal);
                            classVisitor.setRunnableImplClassMap(runnableImplClassMap);
                            classVisitor.setCallableImplClassMap(callableImplClassMap);
                            classVisitor.setThreadChildClassMap(threadChildClassMap);
                            classVisitor.setMethodAnnotationMap(methodAnnotationMap);
                            classVisitor.start();

                            // modified by adrninistrator
                            List methodCalls = classVisitor.methodCalls();
                            for (MethodCallDto methodCallDto : methodCalls) {
                                writeResult(resultWriter, methodCallDto.getMethodCall());
                                if (methodCallDto.getSourceLine() != Constants.NONE_LINE_NUMBER) {
                                    writeResult(resultWriter, " " + methodCallDto.getSourceLine());
                                    writeResult(resultWriter, " " + jarNum);
                                }
                                writeResult(resultWriter, Constants.NEW_LINE);
                            }
                            // modified end
                        }
                    }

                    // added by adrninistrator
                    // add abstract method in interface into abstract super class
                    if (!addInterfaceMethod4SuperClass()) {
                        return false;
                    }

                    // record super class call children method and child class call super method
                    if (!recordExtendsClassMethod(resultWriter)) {
                        return false;
                    }

                    // record interface call implementation class method
                    recordInterfaceCallClassMethod(resultWriter);

                    // record method annotation information
                    recordMethodAnnotationInfo(annotationOut);
                    // added end
                }
            }

            return true;
        } catch (IOException e) {
            System.err.println("Error while processing jar: " + e.getMessage());
            e.printStackTrace();
            return false;
        }
        // modified end
    }

    // added by adrninistrator
    private static void init() {
        // calleeMethodMapGlobal used for all jar files, only initialize once
        if (calleeMethodMapGlobal == null) {
            calleeMethodMapGlobal = new HashMap<>(INIT_SIZE_1000);
        }
        classInterfaceMethodInfoMap = new HashMap<>(INIT_SIZE_100);
        interfaceMethodWithArgsMap = new HashMap<>(INIT_SIZE_100);
        runnableImplClassMap = new HashMap<>(INIT_SIZE_100);
        callableImplClassMap = new HashMap<>(INIT_SIZE_100);
        threadChildClassMap = new HashMap<>(INIT_SIZE_100);
        methodAnnotationMap = new HashMap<>(INIT_SIZE_100);
        extendsClassesSet = new HashSet<>(INIT_SIZE_500);
        extendsClassMethodInfoMap = new HashMap<>(INIT_SIZE_500);
        childrenClassInfoMap = new HashMap<>(INIT_SIZE_500);
    }

    // add abstract method in interface into abstract super class
    private static boolean addInterfaceMethod4SuperClass() {
        for (Map.Entry> childrenClassInfoEntry : childrenClassInfoMap.entrySet()) {
            String superClassName = childrenClassInfoEntry.getKey();
            ExtendsClassMethodInfo extendsClassMethodInfo = extendsClassMethodInfoMap.get(superClassName);
            if (extendsClassMethodInfo == null) {
                // class in other jar can be found, but can't find its methods
                continue;
            }

            if (!extendsClassMethodInfo.isAbstractClass()) {
                continue;
            }

            ClassInterfaceMethodInfo classInterfaceMethodInfo = classInterfaceMethodInfoMap.get(superClassName);
            if (classInterfaceMethodInfo == null) {
                continue;
            }

            Map methodAttributeMap = extendsClassMethodInfo.getMethodAttributeMap();
            MethodAttribute methodAttribute = new MethodAttribute();
            methodAttribute.setAbstractMethod(true);
            methodAttribute.setPublicMethod(true);
            methodAttribute.setProtectedMethod(false);

            List interfaceNameList = classInterfaceMethodInfo.getInterfaceNameList();
            for (String interfaceName : interfaceNameList) {
                List interfaceMethodWithArgsList = interfaceMethodWithArgsMap.get(interfaceName);
                if (interfaceMethodWithArgsList == null) {
                    continue;
                }

                for (String interfaceMethodWithArgs : interfaceMethodWithArgsList) {
                    if (methodAttributeMap.get(interfaceMethodWithArgs) == null) {
                        methodAttributeMap.put(interfaceMethodWithArgs, methodAttribute);
                    }
                }
            }
        }

        return true;
    }

    // record super class call children method and child class call super method
    private static boolean recordExtendsClassMethod(BufferedWriter resultWriter) throws IOException {
        Set topSuperClassNameSet = new HashSet<>();

        // get top super class name
        for (Map.Entry extendsClassMethodInfoEntry : extendsClassMethodInfoMap.entrySet()) {
            String className = extendsClassMethodInfoEntry.getKey();
            ExtendsClassMethodInfo extendsClassMethodInfo = extendsClassMethodInfoEntry.getValue();
            String superClassName = extendsClassMethodInfo.getSuperClassName();
            if (superClassName.startsWith("java.")) {
                topSuperClassNameSet.add(className);
            }
        }

        for (String topSuperClassName : topSuperClassNameSet) {
            // handle one top super class
            if (!handleOneTopSuperClass(topSuperClassName, resultWriter)) {
                return false;
            }
        }
        return true;
    }

    // handle one top super class
    private static boolean handleOneTopSuperClass(String topSuperClassName, BufferedWriter resultWriter) throws IOException {
        System.out.println("handleOneTopSuperClass: " + topSuperClassName);
        List tmpNodeList = new ArrayList<>();
        int currentLevel = 0;

        // init node list
        TmpNode4ExtendsClassMethod topNode = TmpNode4ExtendsClassMethod.genInstance(topSuperClassName, -1);
        tmpNodeList.add(topNode);

        // begin loop
        while (true) {
            TmpNode4ExtendsClassMethod currentNode = tmpNodeList.get(currentLevel);
            List childrenClassInfoList = childrenClassInfoMap.get(currentNode.getSuperClassName());
            if (childrenClassInfoList == null) {
                System.err.println("can't find top super class: " + currentNode.getSuperClassName());
                return false;
            }

            int currentChildClassIndex = currentNode.getChildClassIndex() + 1;
            if (currentChildClassIndex >= childrenClassInfoList.size()) {
                if (currentLevel == 0) {
                    return true;
                }
                currentLevel--;
                continue;
            }

            // handle current child class
            String childClassName = childrenClassInfoList.get(currentChildClassIndex);

            // handle super and child class call method
            if (!handleSuperAndChildClass(currentNode.getSuperClassName(), childClassName, resultWriter)) {
                return false;
            }

            // handle next child class
            currentNode.setChildClassIndex(currentChildClassIndex);

            List nextChildClassList = childrenClassInfoMap.get(childClassName);
            if (nextChildClassList == null) {
                // current child has no child
                continue;
            }

            // current child has children
            currentLevel++;

            if (currentLevel + 1 > tmpNodeList.size()) {
                TmpNode4ExtendsClassMethod nextNode = TmpNode4ExtendsClassMethod.genInstance(childClassName, -1);
                tmpNodeList.add(nextNode);
            } else {
                TmpNode4ExtendsClassMethod nextNode = tmpNodeList.get(currentLevel);
                nextNode.setSuperClassName(childClassName);
                nextNode.setChildClassIndex(-1);
            }
        }
    }

    // handle super and child class call method
    private static boolean handleSuperAndChildClass(String superClassName, String childClassName, BufferedWriter resultWriter) throws IOException {
        ExtendsClassMethodInfo superClassMethodInfo = extendsClassMethodInfoMap.get(superClassName);
        if (superClassMethodInfo == null) {
            System.err.println("can't find information for super class: " + superClassName);
            return false;
        }

        ExtendsClassMethodInfo childClassMethodInfo = extendsClassMethodInfoMap.get(childClassName);
        if (childClassMethodInfo == null) {
            System.err.println("can't find information for child class: " + childClassName);
            return false;
        }

        Map superMethodAttributeMap = superClassMethodInfo.getMethodAttributeMap();
        Map childMethodAttributeMap = childClassMethodInfo.getMethodAttributeMap();

        for (Map.Entry superMethodAttributeEntry : superMethodAttributeMap.entrySet()) {
            String superMethodWithArgs = superMethodAttributeEntry.getKey();
            MethodAttribute superMethodAttribute = superMethodAttributeEntry.getValue();
            if (superMethodAttribute.isAbstractMethod()) {
                // super abstract method
                MethodAttribute childMethodAttribute = childMethodAttributeMap.get(superMethodWithArgs);
                if (childMethodAttribute == null) {
                    childMethodAttributeMap.put(superMethodWithArgs, superMethodAttribute);
                }
                // add super class call child class method
                String superCallChildClassMethod = String.format("M:%s:%s (%s)%s:%s %d", superClassName, superMethodWithArgs,
                        CallTypeEnum.CTE_SCC.getType(), childClassName, superMethodWithArgs, Constants.DEFAULT_LINE_NUMBER);
                writeResult(resultWriter, superCallChildClassMethod);
                writeResult(resultWriter, " " + jarNum);
                writeResult(resultWriter, Constants.NEW_LINE);
                continue;
            }
            if (superMethodAttribute.isPublicMethod() || superMethodAttribute.isProtectedMethod()) {
                // super public/protected not abstract method
                if (childMethodAttributeMap.get(superMethodWithArgs) != null) {
                    continue;
                }
                Set childCalleeMethodWithArgsSet = calleeMethodMapGlobal.get(childClassName);
                if (!childClassMethodInfo.isAbstractClass() &&
                        (childCalleeMethodWithArgsSet == null || !childCalleeMethodWithArgsSet.contains(superMethodWithArgs))) {
                    continue;
                }

                childMethodAttributeMap.put(superMethodWithArgs, superMethodAttribute);

                // add child class call super class method
                String childCallSuperClassMethod = String.format("M:%s:%s (%s)%s:%s %d", childClassName, superMethodWithArgs,
                        CallTypeEnum.CTE_CCS.getType(), superClassName, superMethodWithArgs, Constants.DEFAULT_LINE_NUMBER);
                writeResult(resultWriter, childCallSuperClassMethod);
                writeResult(resultWriter, " " + jarNum);
                writeResult(resultWriter, Constants.NEW_LINE);
            }
        }
        return true;
    }

    // record interface call implementation class method
    private static void recordInterfaceCallClassMethod(BufferedWriter resultWriter) throws IOException {
        if (classInterfaceMethodInfoMap.isEmpty() || interfaceMethodWithArgsMap.isEmpty()) {
            return;
        }

        for (Map.Entry classMethodInfo : classInterfaceMethodInfoMap.entrySet()) {
            String className = classMethodInfo.getKey();
            ClassInterfaceMethodInfo classInterfaceMethodInfo = classMethodInfo.getValue();
            List interfaceNameList = classInterfaceMethodInfo.getInterfaceNameList();

            /*
                find the same method both in interface and implementation class
                and the method should be used
             */
            for (String interfaceName : interfaceNameList) {
                Set calleeMethodWithArgsSet = calleeMethodMapGlobal.get(interfaceName);
                if (calleeMethodWithArgsSet == null) {
                    continue;
                }

                List interfaceMethodWithArgsList = interfaceMethodWithArgsMap.get(interfaceName);
                if (interfaceMethodWithArgsList == null || interfaceMethodWithArgsList.isEmpty()) {
                    continue;
                }

                List classMethodWithArgsList = classInterfaceMethodInfo.getMethodWithArgsList();
                for (String classMethodWithArgs : classMethodWithArgsList) {
                    if (!interfaceMethodWithArgsList.contains(classMethodWithArgs) || !calleeMethodWithArgsSet.contains(classMethodWithArgs)) {
                        continue;
                    }

                    String interfaceCallClassMethod = String.format("M:%s:%s (%s)%s:%s %d", interfaceName, classMethodWithArgs,
                            CallTypeEnum.CTE_ITF.getType(), className, classMethodWithArgs, Constants.DEFAULT_LINE_NUMBER);
                    writeResult(resultWriter, interfaceCallClassMethod);
                    writeResult(resultWriter, " " + jarNum);
                    writeResult(resultWriter, Constants.NEW_LINE);
                }
            }
        }
    }

    private static List genImplClassMethodWithArgs(Method[] methods) {
        List methodInfoList = new ArrayList<>(methods.length);
        for (Method method : methods) {
            String methodName = method.getName();
            // ignore "" and ""
            if (!methodName.startsWith("<") && method.isPublic() && !method.isAbstract() && !method.isStatic()) {
                methodInfoList.add(methodName + CommonUtil.argumentList(method.getArgumentTypes()));
            }
        }
        return methodInfoList;
    }

    private static List genInterfaceAbstractMethodWithArgs(Method[] methods) {
        List methodInfoList = new ArrayList<>(methods.length);
        for (Method method : methods) {
            if (method.isAbstract()) {
                methodInfoList.add(method.getName() + CommonUtil.argumentList(method.getArgumentTypes()));
            }
        }
        return methodInfoList;
    }

    // pre handle classes
    private static boolean preHandleClasses(String jarFilePath, File jarFile) {
        try (JarFile jar = new JarFile(jarFile)) {
            for (Enumeration enumeration = jar.entries(); enumeration.hasMoreElements(); ) {
                JarEntry jarEntry = enumeration.nextElement();
                if (!jarEntry.isDirectory() && jarEntry.getName().endsWith(".class")) {
                    ClassParser cp = new ClassParser(jarFilePath, jarEntry.getName());
                    JavaClass javaClass = cp.parse();

                    String className = javaClass.getClassName();
                    if (javaClass.isClass()) {
                        // pre handle class
                        preHandleClass(javaClass);
                    } else if (javaClass.isInterface()) {
                        Method[] methods = javaClass.getMethods();
                        if (methods != null && methods.length > 0 &&
                                interfaceMethodWithArgsMap.get(className) == null) {
                            List interfaceMethodWithArgsList = genInterfaceAbstractMethodWithArgs(methods);
                            interfaceMethodWithArgsMap.put(className, interfaceMethodWithArgsList);
                        }
                    }

                    // get super and children class
                    String superClassName = javaClass.getSuperclassName();
                    if (THREAD_CLASS_NAME.equals(superClassName)) {
                        // find Thread child class
                        threadChildClassMap.put(javaClass.getClassName(), Boolean.FALSE);
                    }

                    if (!superClassName.startsWith("java.")) {
                        extendsClassesSet.add(javaClass.getClassName());
                        extendsClassesSet.add(superClassName);
                    }
                }
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    // pre handle class
    private static void preHandleClass(JavaClass javaClass) {
        String className = javaClass.getClassName();
        String[] interfaceNames = javaClass.getInterfaceNames();
        Method[] methods = javaClass.getMethods();

        if (interfaceNames != null && interfaceNames.length > 0 &&
                methods != null && methods.length > 0 &&
                classInterfaceMethodInfoMap.get(className) == null) {
            ClassInterfaceMethodInfo classInterfaceMethodInfo = new ClassInterfaceMethodInfo();

            List interfaceNameList = new ArrayList<>(interfaceNames.length);
            interfaceNameList.addAll(Arrays.asList(interfaceNames));

            List implClassMethodWithArgsList = genImplClassMethodWithArgs(methods);
            classInterfaceMethodInfo.setInterfaceNameList(interfaceNameList);
            classInterfaceMethodInfo.setMethodWithArgsList(implClassMethodWithArgsList);

            classInterfaceMethodInfoMap.put(className, classInterfaceMethodInfo);

            if (!javaClass.isAbstract()) {
                if (interfaceNameList.contains(RUNNABLE_CLASS_NAME)) {
                    // find Runnable impl classes
                    runnableImplClassMap.put(className, Boolean.FALSE);
                }
                if (interfaceNameList.contains(CALLABLE_CLASS_NAME)) {
                    // find Callable impl classes
                    callableImplClassMap.put(className, Boolean.FALSE);
                }
            }
        }
    }

    private static void findExtendsClassesInfo(JavaClass javaClass) {
        String className = javaClass.getClassName();
        if (extendsClassMethodInfoMap.get(className) != null) {
            return;
        }

        String superClassName = javaClass.getSuperclassName();
        if (!superClassName.startsWith("java.")) {
            // cache super class and it's children class, ignore super class start with "java."
            List childrenClassInfoList = childrenClassInfoMap.get(superClassName);
            if (childrenClassInfoList == null) {
                List newChildrenClassInfoList = new ArrayList<>();
                newChildrenClassInfoList.add(className);
                childrenClassInfoMap.put(superClassName, newChildrenClassInfoList);
            } else {
                childrenClassInfoList.add(className);
            }
        }

        // cache the method information of current class
        ExtendsClassMethodInfo extendsClassMethodInfo = new ExtendsClassMethodInfo();
        extendsClassMethodInfo.setAbstractClass(javaClass.isAbstract());
        extendsClassMethodInfo.setSuperClassName(superClassName);
        Map methodAttributeMap = new HashMap<>();

        Method[] methods = javaClass.getMethods();
        if (methods != null && methods.length > 0) {
            for (Method method : methods) {
                String methodName = method.getName();
                if (!methodName.startsWith("<") && !method.isStatic() && (
                        method.isAbstract() ||
                                (!method.isAbstract() && (method.isPublic() || method.isProtected()))
                )) {
                    MethodAttribute methodAttribute = new MethodAttribute();
                    methodAttribute.setAbstractMethod(method.isAbstract());
                    methodAttribute.setPublicMethod(method.isPublic());
                    methodAttribute.setProtectedMethod(method.isProtected());

                    String methodWithArgs = methodName + CommonUtil.argumentList(method.getArgumentTypes());
                    methodAttributeMap.put(methodWithArgs, methodAttribute);
                }
            }
        }
        extendsClassMethodInfo.setMethodAttributeMap(methodAttributeMap);
        extendsClassMethodInfoMap.put(className, extendsClassMethodInfo);
    }

    // record method annotation information
    private static void recordMethodAnnotationInfo(BufferedWriter out) throws IOException {
        for (Map.Entry> entry : methodAnnotationMap.entrySet()) {
            String fullMethod = entry.getKey();
            Set annotationSet = entry.getValue();
            for (String annotation : annotationSet) {
                String methodWithAnnotation = fullMethod + " " + annotation + Constants.NEW_LINE;
                out.write(methodWithAnnotation);
            }
        }
    }

    // write data to result file
    private static void writeResult(BufferedWriter resultWriter, String data) throws IOException {
        resultWriter.write(data);
    }
    // added end
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy