diff --git a/pom.xml b/pom.xml index 2c02afe..c224ab0 100644 --- a/pom.xml +++ b/pom.xml @@ -24,8 +24,65 @@ 4.13.1 test + + + + fr.inria.gforge.spoon + spoon-core + 10.4.1 + + + + + + org.soot-oss + sootup.core + 1.2.0 + + + org.soot-oss + sootup.java.core + 1.2.0 + + + org.soot-oss + sootup.java.sourcecode + 1.2.0 + + + org.soot-oss + sootup.java.bytecode + 1.2.0 + + + org.soot-oss + sootup.jimple.parser + 1.2.0 + + + org.soot-oss + sootup.callgraph + 1.2.0 + + + org.soot-oss + sootup.analysis + 1.2.0 + + + + + jitpack.io + https://jitpack.io + + + /maven.google.com + https://maven.google.com + + + diff --git a/src/main/java/tum/dpid/App.java b/src/main/java/tum/dpid/App.java deleted file mode 100644 index cf88980..0000000 --- a/src/main/java/tum/dpid/App.java +++ /dev/null @@ -1,13 +0,0 @@ -package tum.dpid; - -/** - * Hello world! - * - */ -public class App -{ - public static void main( String[] args ) - { - System.out.println( "Hello World!" ); - } -} diff --git a/src/main/java/tum/dpid/Runner.java b/src/main/java/tum/dpid/Runner.java new file mode 100644 index 0000000..a3bebca --- /dev/null +++ b/src/main/java/tum/dpid/Runner.java @@ -0,0 +1,81 @@ +package tum.dpid; + +import spoon.Launcher; +import spoon.reflect.CtModel; +import spoon.reflect.declaration.CtClass; +import spoon.reflect.declaration.CtMethod; +import spoon.reflect.reference.CtExecutableReference; +import spoon.reflect.reference.CtTypeReference; +import spoon.reflect.visitor.filter.TypeFilter; +import tum.dpid.services.DatabaseMethodFinder; +import tum.dpid.services.processors.ClassHierarchyOrder; +import tum.dpid.services.processors.MethodCallChain; +import tum.dpid.services.processors.MethodOrder; + +import java.util.List; +import java.util.Map; +import java.util.Set; + + +/** + * Runner class of method call chain processor and analyzer by utilizing spoon + * + */ +public class Runner +{ + + public static void main( String[] args ) + { + Launcher launcher = new Launcher(); + launcher.getEnvironment().setNoClasspath(true); + + //String testDirectory = "src/main/resources/tester"; //"../../../resources/tester"; + String directoryPath = "../../fromItestra/LoopAntiPattern"; + launcher.addInputResource(directoryPath); + launcher.buildModel(); + CtModel model = launcher.getModel(); + + //All Java Classes in the project + List> allClasses = model.getElements(new TypeFilter<>(CtClass.class)); + + //All Methods in the project + List> allMethods = model.getElements(new TypeFilter<>(CtMethod.class)); + + // Methods which makes request to database in project + Set> databaseMethods = DatabaseMethodFinder.findDatabaseMethods(model, allClasses); + for (CtMethod method : databaseMethods) { + System.out.println("Database Method is: " + method.getSignature() + " (" + method.getDeclaringType().getQualifiedName() + ")"); + } + + //Initialize class hierarchy order processor and start processing it + ClassHierarchyOrder classHierarchyOrder = new ClassHierarchyOrder(); + launcher.addProcessor(classHierarchyOrder); + launcher.process(); + + //Initialize method execution order processor (call chain of method) and start processing it + MethodOrder methodOrder = new MethodOrder(); + launcher.addProcessor(methodOrder); + launcher.process(); + + Map, List>> callList = methodOrder.getCallList(); + Map, Set>> classHierarchy = classHierarchyOrder.getClassImplementors() ; + + //Process each method in the project and print out their call chain + for (CtMethod ctMethod: allMethods) { + List methodCallHierarchies = MethodCallChain.processMethod(ctMethod, callList, classHierarchy); + if (methodCallHierarchies.isEmpty()) { + System.out.println("No method `" + ctMethod.getDeclaringType() + "` found. \n"); + } + if (methodCallHierarchies.size() > 1) { + System.out.println("Found " + methodCallHierarchies.size() + " matching methods...\n"); + } + for (MethodCallChain methodCallHierarchy : methodCallHierarchies) { + methodCallHierarchy.printCallChain(); + System.out.println(); + } + } + } + + +} + diff --git a/src/main/java/tum/dpid/cfg/CfgExtractor.java b/src/main/java/tum/dpid/cfg/CfgExtractor.java new file mode 100644 index 0000000..bacd985 --- /dev/null +++ b/src/main/java/tum/dpid/cfg/CfgExtractor.java @@ -0,0 +1,88 @@ +package tum.dpid.cfg; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.Optional; + +import sootup.callgraph.CallGraph; +import sootup.callgraph.CallGraphAlgorithm; +import sootup.callgraph.ClassHierarchyAnalysisAlgorithm; +import sootup.core.inputlocation.AnalysisInputLocation; +import sootup.core.jimple.common.expr.JVirtualInvokeExpr; +import sootup.core.jimple.common.stmt.JInvokeStmt; +import sootup.core.model.SootClass; +import sootup.core.model.SootMethod; +import sootup.core.signatures.MethodSignature; +import sootup.core.typehierarchy.ViewTypeHierarchy; +import sootup.core.types.ClassType; +import sootup.core.types.VoidType; +import sootup.core.util.DotExporter; +import sootup.core.views.View; +import sootup.java.bytecode.inputlocation.JavaClassPathAnalysisInputLocation; +import sootup.java.bytecode.inputlocation.PathBasedAnalysisInputLocation; +import sootup.java.core.JavaIdentifierFactory; +import sootup.java.core.JavaSootMethod; +import sootup.java.core.language.JavaJimple; +import sootup.java.core.types.JavaClassType; +import sootup.java.core.views.JavaView; +import sootup.java.sourcecode.inputlocation.JavaSourcePathAnalysisInputLocation; + +/** + * Experimental class to extract control flow graph of project by using SootUp + */ + +public class CfgExtractor { + + public void CfgExtractorFunc(String inputPath){ + + AnalysisInputLocation inputLocation = + new JavaClassPathAnalysisInputLocation("target/classes"); + + JavaView view = new JavaView(inputLocation); + + System.out.println("All Class: " + view.getClasses()); + + JavaClassType classType = + view.getIdentifierFactory().getClassType("tum.dpid.App"); + + if (!view.getClass(classType).isPresent()) { + System.out.println("Class not found!"); + return; + } + + // Retrieve the specified class from the project. + SootClass sootClass = view.getClass(classType).get(); + + + MethodSignature methodSignature = view.getIdentifierFactory().getMethodSignature( + classType, "main", "void", Collections.singletonList("java.lang.String[]")); + + Optional opt = view.getMethod(methodSignature); + + // Create type hierarchy and CHA + final ViewTypeHierarchy typeHierarchy = new ViewTypeHierarchy(view); + + CallGraphAlgorithm cha = new ClassHierarchyAnalysisAlgorithm(view); + + // Create CG by initializing CHA with entry method(s) + CallGraph cg = cha.initialize(Collections.singletonList(methodSignature)); + + cg.callsFrom(methodSignature).forEach(System.out::println); + + var x = DotExporter.createUrlToWebeditor( opt.get().getBody().getStmtGraph()); + + System.out.println("Dot Graph is " + x); + + //ystem.out.println(cg); + } + + /* //CFG with sootup + String sourcePath = "../../fromItestra/LoopAntiPattern"; + String binaryPath = "../../fromItestra/LoopAntiPattern/build/classes/java/main/com/example/LoopAntiPattern" ; //LoopAntiPattern-0.0.1-SNAPSHOT.jar" + CfgExtractor cfgExtractor = new CfgExtractor(); + cfgExtractor.CfgExtractorFunc(binaryPath); + System.out.println("*************************************************************************************************************************************"); + + */ +} diff --git a/src/main/java/tum/dpid/services/DatabaseMethodFinder.java b/src/main/java/tum/dpid/services/DatabaseMethodFinder.java new file mode 100644 index 0000000..e88d105 --- /dev/null +++ b/src/main/java/tum/dpid/services/DatabaseMethodFinder.java @@ -0,0 +1,49 @@ +package tum.dpid.services; + +import spoon.reflect.CtModel; +import spoon.reflect.declaration.CtClass; +import spoon.reflect.declaration.CtMethod; +import spoon.reflect.declaration.ModifierKind; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class DatabaseMethodFinder { + + public static Set> findDatabaseMethods(CtModel model, List> allClasses) { + Set> databaseMethods = new HashSet<>(); + + for (CtClass ctClass : allClasses) { + if (isDatabaseClass(ctClass)) { + for(CtMethod method : ctClass.getMethods()) { + if (!isGetterOrSetter(method)) { + databaseMethods.add(method); + } + } + } + } + return databaseMethods; + } + + private static boolean isDatabaseClass(CtClass ctClass) { + // Define the package name where database-related classes are located + String databasePackage = "com.example.LoopAntiPattern.data.repository"; + return ctClass.getPackage().getQualifiedName().startsWith(databasePackage); + } + + private static boolean isGetterOrSetter(CtMethod method) { + String methodName = method.getSimpleName(); + boolean isStatic = method.getModifiers().contains(ModifierKind.STATIC); + + // Check for getter method + boolean isGetter = !isStatic && method.getParameters().isEmpty() && + (methodName.startsWith("get") || methodName.startsWith("is")); + + // Check for setter method + boolean isSetter = !isStatic && method.getParameters().size() == 1 && + methodName.startsWith("set"); + + return isGetter || isSetter; + } +} diff --git a/src/main/java/tum/dpid/services/processors/ClassHierarchyOrder.java b/src/main/java/tum/dpid/services/processors/ClassHierarchyOrder.java new file mode 100644 index 0000000..e4a3111 --- /dev/null +++ b/src/main/java/tum/dpid/services/processors/ClassHierarchyOrder.java @@ -0,0 +1,39 @@ +package tum.dpid.services.processors; + +import spoon.processing.AbstractProcessor; +import spoon.reflect.reference.CtTypeReference; +import spoon.support.reflect.declaration.CtClassImpl; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class ClassHierarchyOrder extends AbstractProcessor> { + + private final Map, Set>> classImplementors = new HashMap<>(); + + public void findInheritance(CtTypeReference classRef, CtTypeReference superClass) { + Set> subclasses = classImplementors.computeIfAbsent(superClass, k -> new HashSet<>()); + subclasses.add(classRef); + } + + @Override + public void process(CtClassImpl ctClass) { + if (ctClass.getReference().isAnonymous()) { + return; + } + if (ctClass.getSuperclass() != null) { + findInheritance(ctClass.getReference(), ctClass.getSuperclass()); + } + for (Object o : ctClass.getSuperInterfaces()) { + CtTypeReference superclass = (CtTypeReference) o; + findInheritance(ctClass.getReference(), superclass); + } + } + + public Map, Set>> getClassImplementors() { + return classImplementors; + } + +} diff --git a/src/main/java/tum/dpid/services/processors/MethodCallChain.java b/src/main/java/tum/dpid/services/processors/MethodCallChain.java new file mode 100644 index 0000000..660a8bf --- /dev/null +++ b/src/main/java/tum/dpid/services/processors/MethodCallChain.java @@ -0,0 +1,73 @@ +package tum.dpid.services.processors; + +import spoon.reflect.declaration.CtMethod; +import spoon.reflect.reference.CtExecutableReference; +import spoon.reflect.reference.CtTypeReference; + +import java.util.*; + +public class MethodCallChain { + + private final CtExecutableReference executableReference; + private final Map , List >> callList; + private final Map , Set >> classHierarchy; + + private MethodCallChain(CtExecutableReference executableReference, + Map , List >> callList, + Map , Set >> classHierarchy) { + this.executableReference = executableReference; + this.callList = callList; + this.classHierarchy = classHierarchy; + } + + public static List processMethod(CtMethod methodName, + Map , List>> callList, + Map , Set >> classHierarchy) { + ArrayList result = new ArrayList<>(); + for (CtExecutableReference executableReference : findExecutablesForMethod(methodName, callList)) { + result.add(new MethodCallChain(executableReference, callList, classHierarchy)); + } + return result; + } + + static List > findExecutablesForMethod(CtMethod methodName, Map , List >> callList) { + ArrayList > result = new ArrayList<>(); + for (CtExecutableReference executableReference : callList.keySet()) { + if (executableReference.equals(methodName.getReference())){ + result.add(executableReference); + } + } + return result; + } + + public void printCallChain() { + System.out.println("Method call hierarchy of " + executableReference + ""); + printCallChain(executableReference, "\t", new HashSet >()); + } + + private void printCallChain(CtExecutableReference method, String indents, Set > alreadyVisited) { + if (alreadyVisited.contains(method)) { + return; + } + alreadyVisited.add(method); + List > callListForMethod = callList.get(method); + if (callListForMethod == null) { + return; + } + for (CtExecutableReference eachReference : callListForMethod) { + System.out.println(indents + eachReference.toString()); + + printCallChain(eachReference, indents.concat("\t"), alreadyVisited); + Set > subclasses = classHierarchy.get(eachReference.getDeclaringType()); + if (subclasses != null) { + for (CtTypeReference subclass : subclasses) { + CtExecutableReference ctExecutableReference = eachReference.getOverridingExecutable(subclass); + if (ctExecutableReference != null) { + System.out.println(indents + "* " + ctExecutableReference.toString()); + printCallChain( ctExecutableReference, indents.concat("\t"), alreadyVisited); + } + } + } + } + } +} diff --git a/src/main/java/tum/dpid/services/processors/MethodOrder.java b/src/main/java/tum/dpid/services/processors/MethodOrder.java new file mode 100644 index 0000000..c6042a8 --- /dev/null +++ b/src/main/java/tum/dpid/services/processors/MethodOrder.java @@ -0,0 +1,38 @@ +package tum.dpid.services.processors; + +import spoon.processing.AbstractProcessor; +import spoon.reflect.code.CtAbstractInvocation; +import spoon.reflect.declaration.CtElement; +import spoon.reflect.reference.CtExecutableReference; +import spoon.reflect.visitor.filter.AbstractFilter; +import spoon.support.reflect.declaration.CtMethodImpl; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class MethodOrder extends AbstractProcessor> { + private Map, List>> callList = new HashMap<>(); + + @Override + public void process(CtMethodImpl ctMethod) { + List elements = ctMethod.getElements(new AbstractFilter(CtElement.class) { + @Override + public boolean matches(CtElement ctElement) { + return ctElement instanceof CtAbstractInvocation; + } + }); + List> calls = new ArrayList<>(); + for (CtElement element : elements) { + CtAbstractInvocation invocation = (CtAbstractInvocation) element; + calls.add(invocation.getExecutable()); + + } + callList.put(ctMethod.getReference(), calls); + } + + public Map, List>> getCallList() { + return callList; + } +} diff --git a/src/main/java/tum/dpid/services/v1/CallHierarchyProcessor.java b/src/main/java/tum/dpid/services/v1/CallHierarchyProcessor.java new file mode 100644 index 0000000..26f4771 --- /dev/null +++ b/src/main/java/tum/dpid/services/v1/CallHierarchyProcessor.java @@ -0,0 +1,39 @@ +package tum.dpid.services.v1; + +import spoon.reflect.code.CtInvocation; +import spoon.reflect.declaration.CtMethod; +import spoon.reflect.reference.CtExecutableReference; +import spoon.reflect.visitor.CtAbstractVisitor; + +import java.util.*; + +/* + Previous version of method call chain processor but it lacks of all. It only prints first degree of method call chain + */ +public class CallHierarchyProcessor extends CtAbstractVisitor { + private Map> callHierarchy = new HashMap<>(); + + /*This prints one degree of children*/ + @Override + public void visitCtMethod(CtMethod method) { + String methodSignature = method.getDeclaringType().getQualifiedName() + "#" + method.getSignature(); + Set calledMethods = new HashSet<>(); + method.getElements(e -> e instanceof CtInvocation) + .forEach(e -> { + CtInvocation invocation = (CtInvocation) e; + CtExecutableReference executable = invocation.getExecutable(); + if (executable.getDeclaringType() != null) { + //System.out.println("CtExecutableReference is " + executable); + String calledMethodSignature = executable.getDeclaringType().getQualifiedName() + "#" + executable.getSignature(); + calledMethods.add(calledMethodSignature); + } + }); + callHierarchy.put(methodSignature, calledMethods); + + } + + public Map> getCallHierarchy() { + return callHierarchy; + } + +} diff --git a/src/main/java/tum/dpid/services/v1/MethodCallTracer.java b/src/main/java/tum/dpid/services/v1/MethodCallTracer.java new file mode 100644 index 0000000..ca9c582 --- /dev/null +++ b/src/main/java/tum/dpid/services/v1/MethodCallTracer.java @@ -0,0 +1,92 @@ +package tum.dpid.services.v1; + +import spoon.reflect.CtModel; +import spoon.reflect.code.CtInvocation; +import spoon.reflect.declaration.CtMethod; +import spoon.reflect.reference.CtExecutableReference; +import spoon.reflect.visitor.filter.TypeFilter; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/* + Previous version of method call chain processor. + */ +public class MethodCallTracer { + + public static Set> findMethodsCallingDatabaseMethods(CtModel model, List> allMethods, Set> databaseMethods) { + Set> callingMethods = new HashSet<>(); + + for (CtMethod method : allMethods) { + for (CtExecutableReference calledMethodRef : method.getElements(new TypeFilter<>(CtExecutableReference.class))) { + for (CtMethod dbMethod : databaseMethods) { + if (calledMethodRef.getSimpleName().equals(dbMethod.getSimpleName()) && + calledMethodRef.getDeclaringType().equals(dbMethod.getDeclaringType().getReference())) { + callingMethods.add(method); + } + } + } + } + return callingMethods; + } + + + public static Set traceMethodCalls(Set> databaseMethods, List> allMethods) { + + Set callChains = new HashSet<>(); + Set> visitedMethods = new HashSet<>(); + + for (CtMethod dbMethod : databaseMethods) { + traceMethod(dbMethod, allMethods, "", callChains, visitedMethods); + } + + return callChains; + } + + private static void traceMethod(CtMethod method, List> allMethods, String callChain, Set callChains, Set> visitedMethods) { + if (visitedMethods.contains(method)) { + return; + } + visitedMethods.add(method); + + callChain = method.getSimpleName() + " #" + method.getDeclaringType().getQualifiedName() + " -> " + callChain; + + for (CtMethod caller : findCallingMethods(method, allMethods)) { + traceMethod(caller, allMethods, callChain, callChains, visitedMethods); + } + callChains.add(callChain); + + } + + + private static Set> findCallingMethods(CtMethod method, List> allMethods) { + Set> callingMethods = new HashSet<>(); + + for (CtMethod candidateMethod : allMethods) { + for (CtInvocation invocation : candidateMethod.getElements(new TypeFilter<>(CtInvocation.class))) { + CtExecutableReference executableRef = invocation.getExecutable(); + if (executableRef.getDeclaration() != null && executableRef.getDeclaration().equals(method)) { + callingMethods.add(candidateMethod); + } + } + } + + return callingMethods; + } + +} + +//Runner Code Snippet +// Set> callingMethods = MethodCallTracer.findMethodsCallingDatabaseMethods(model,allMethods, databaseMethods); +// for (CtMethod method : callingMethods) { +// System.out.println("Method calling database: " + method.getSignature()); +// } + +// +// //Method Call Tracer File +// Set callChains = MethodCallTracer.traceMethodCalls(databaseMethods, allMethods); +// // Print results +// for (String callChain : callChains) { +// System.out.println("Call chain: " + callChain); +// }