From ab7fdaa7b839c92ccf9f5087178ab30e4fc168de Mon Sep 17 00:00:00 2001 From: Melis Ece Unsal Date: Wed, 22 May 2024 22:15:09 +0200 Subject: [PATCH 1/7] test --- pom.xml | 5 + .../java/tum/dpid/AntiPatternDetector.java | 125 ++++++++++++++++++ src/main/java/tum/dpid/App.java | 13 -- 3 files changed, 130 insertions(+), 13 deletions(-) create mode 100644 src/main/java/tum/dpid/AntiPatternDetector.java delete mode 100644 src/main/java/tum/dpid/App.java diff --git a/pom.xml b/pom.xml index 2c02afe..a7f128e 100644 --- a/pom.xml +++ b/pom.xml @@ -24,6 +24,11 @@ 4.13.1 test + + org.eclipse.jdt + org.eclipse.jdt.core + 3.28.0 + diff --git a/src/main/java/tum/dpid/AntiPatternDetector.java b/src/main/java/tum/dpid/AntiPatternDetector.java new file mode 100644 index 0000000..cb9db8b --- /dev/null +++ b/src/main/java/tum/dpid/AntiPatternDetector.java @@ -0,0 +1,125 @@ +package tum.dpid; + +import org.eclipse.jdt.core.dom.*; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class AntiPatternDetector { + + public static void main(String[] args) { + String projectDirectoryPath = "/Users/melisuensal/Desktop/LoopAntiPattern"; + + File projectDirectory = new File(projectDirectoryPath); + if (!projectDirectory.exists() || !projectDirectory.isDirectory()) { + System.out.println("Invalid project directory path."); + return; + } + + processProjectDirectory(projectDirectory); + } + + private static void processProjectDirectory(File projectDirectory) { + System.out.println("processProjectDirectory"); + File[] javaFiles = projectDirectory.listFiles((dir, name) -> name.endsWith(".java")); + + if (javaFiles != null) { + for (File javaFile : javaFiles) { + try { + String source = new String(Files.readAllBytes(javaFile.toPath())); + analyzeSourceCode(source); + } catch (IOException e) { + System.err.println("Error reading file: " + javaFile.getName()); + e.printStackTrace(); + } + } + } + } + + private static void analyzeSourceCode(String source){ + System.out.println("analyzeSourceCode"); + ASTParser parser = ASTParser.newParser(AST.JLS_Latest); + parser.setSource(source.toCharArray()); + parser.setKind(ASTParser.K_COMPILATION_UNIT); + + CompilationUnit cu = (CompilationUnit) parser.createAST(null); + MethodCollector methodCollector = new MethodCollector(); + cu.accept(methodCollector); + + Map methodMap = methodCollector.getMethodMap(); + + cu.accept(new ForLoopVisitor(methodMap)); + } +} + +class ForLoopVisitor extends ASTVisitor { + private static final List DB_METHODS = List.of("flush", "save", "update", "persist", "remove", "find"); + private Map methodMap; + + public ForLoopVisitor(Map methodMap) { + this.methodMap = methodMap; + } + + @Override + public boolean visit(ForStatement node) { + node.accept(new ASTVisitor() { + @Override + public boolean visit(MethodInvocation methodInvocation) { + String methodName = methodInvocation.getName().getIdentifier(); + if (DB_METHODS.contains(methodName)) { + reportAntiPattern(methodInvocation); + } else if (methodMap.containsKey(methodName)) { + MethodDeclaration methodDeclaration = methodMap.get(methodName); + methodDeclaration.accept(new MethodInvocationVisitor(methodInvocation)); + } + return super.visit(methodInvocation); + } + }); + return super.visit(node); + } + + private void reportAntiPattern(MethodInvocation methodInvocation) { + System.out.println("Anti-pattern detected: " + methodInvocation.getName().getIdentifier() + + " call inside a for loop at line " + + ((CompilationUnit) methodInvocation.getRoot()).getLineNumber(methodInvocation.getStartPosition())); + } + + private class MethodInvocationVisitor extends ASTVisitor { + private MethodInvocation originalInvocation; + + public MethodInvocationVisitor(MethodInvocation originalInvocation) { + this.originalInvocation = originalInvocation; + } + + @Override + public boolean visit(MethodInvocation methodInvocation) { + String methodName = methodInvocation.getName().getIdentifier(); + if (DB_METHODS.contains(methodName)) { + reportAntiPattern(originalInvocation); + } else if (methodMap.containsKey(methodName)) { + MethodDeclaration methodDeclaration = methodMap.get(methodName); + methodDeclaration.accept(new MethodInvocationVisitor(originalInvocation)); + } + return super.visit(methodInvocation); + } + } +} + +class MethodCollector extends ASTVisitor { + private Map methodMap = new HashMap<>(); + + @Override + public boolean visit(MethodDeclaration node) { + String methodName = node.getName().getIdentifier(); + methodMap.put(methodName, node); + return super.visit(node); + } + + public Map getMethodMap() { + return methodMap; + } +} 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!" ); - } -} From 9553c06b16003610ac4a15e3c78efd75270b0367 Mon Sep 17 00:00:00 2001 From: Melis Ece Unsal Date: Thu, 23 May 2024 17:16:44 +0200 Subject: [PATCH 2/7] create method map --- pom.xml | 16 ++ .../java/tum/dpid/AntiPatternDetector.java | 185 +++++++++++------- 2 files changed, 134 insertions(+), 67 deletions(-) diff --git a/pom.xml b/pom.xml index a7f128e..192865c 100644 --- a/pom.xml +++ b/pom.xml @@ -29,8 +29,24 @@ org.eclipse.jdt.core 3.28.0 + + org.soot-oss + soot + 4.3.0 + + + + eclipse-releases + https://repo.eclipse.org/content/groups/releases/ + + + maven-central + https://repo.maven.apache.org/maven2 + + + diff --git a/src/main/java/tum/dpid/AntiPatternDetector.java b/src/main/java/tum/dpid/AntiPatternDetector.java index cb9db8b..f7dc872 100644 --- a/src/main/java/tum/dpid/AntiPatternDetector.java +++ b/src/main/java/tum/dpid/AntiPatternDetector.java @@ -5,14 +5,33 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; + +/* +* CompilationUnit +└── TypeDeclaration (class Example) + ├── MethodDeclaration (methodA) + │ └── Block + │ └── ForStatement + │ ├── Expression (int i = 0) + │ ├── Expression (i < 10) + │ ├── Expression (i++) + │ └── Block + │ └── MethodInvocation (methodB) + ├── MethodDeclaration (methodB) + │ └── Block + │ └── MethodInvocation (methodC) + └── MethodDeclaration (methodC) + └── Block + └── MethodInvocation (find) +*/ public class AntiPatternDetector { + private static final List DB_METHODS = List.of("flush", "save", "update", "persist", "remove", "find"); + public static void main(String[] args) { - String projectDirectoryPath = "/Users/melisuensal/Desktop/LoopAntiPattern"; + String projectDirectoryPath = "/Users/melisuensal/Desktop/LoopAntiPattern/src/main/java/com/example/LoopAntiPattern"; File projectDirectory = new File(projectDirectoryPath); if (!projectDirectory.exists() || !projectDirectory.isDirectory()) { @@ -20,106 +39,138 @@ public static void main(String[] args) { return; } - processProjectDirectory(projectDirectory); + Map methodMap = collectMethods(projectDirectory); + + for (MethodDeclaration method : methodMap.values()) { + method.accept(new ForLoopVisitor(methodMap)); + } } - private static void processProjectDirectory(File projectDirectory) { - System.out.println("processProjectDirectory"); - File[] javaFiles = projectDirectory.listFiles((dir, name) -> name.endsWith(".java")); + private static Map collectMethods(File projectDirectory) { + Map methodMap = new HashMap<>(); + List javaFiles = getJavaFiles(projectDirectory); if (javaFiles != null) { for (File javaFile : javaFiles) { try { String source = new String(Files.readAllBytes(javaFile.toPath())); - analyzeSourceCode(source); + ASTParser parser = ASTParser.newParser(AST.JLS_Latest); + parser.setSource(source.toCharArray()); + parser.setKind(ASTParser.K_COMPILATION_UNIT); + + CompilationUnit cu = (CompilationUnit) parser.createAST(null); + cu.accept(new MethodCollector(methodMap)); } catch (IOException e) { System.err.println("Error reading file: " + javaFile.getName()); e.printStackTrace(); } } } + + return methodMap; } - private static void analyzeSourceCode(String source){ - System.out.println("analyzeSourceCode"); - ASTParser parser = ASTParser.newParser(AST.JLS_Latest); - parser.setSource(source.toCharArray()); - parser.setKind(ASTParser.K_COMPILATION_UNIT); + private static List getJavaFiles(File directory) { + List javaFiles = new ArrayList<>(); + File[] files = directory.listFiles(); + if (files != null) { + for (File file : files) { + if (file.isDirectory()) { + javaFiles.addAll(getJavaFiles(file)); + } else if (file.isFile() && file.getName().endsWith(".java")) { + javaFiles.add(file); + } + } + } + return javaFiles; + } - CompilationUnit cu = (CompilationUnit) parser.createAST(null); - MethodCollector methodCollector = new MethodCollector(); - cu.accept(methodCollector); + private static class MethodCollector extends ASTVisitor { + private final Map methodMap; - Map methodMap = methodCollector.getMethodMap(); + public MethodCollector(Map methodMap) { + this.methodMap = methodMap; + } - cu.accept(new ForLoopVisitor(methodMap)); + @Override + public boolean visit(TypeDeclaration node) { + for (MethodDeclaration method : node.getMethods()) { + String methodName = method.getName().getIdentifier(); + methodMap.put(methodName, method); + } + return super.visit(node); + } } -} -class ForLoopVisitor extends ASTVisitor { - private static final List DB_METHODS = List.of("flush", "save", "update", "persist", "remove", "find"); - private Map methodMap; + private static class ForLoopVisitor extends ASTVisitor { + private final Map methodMap; - public ForLoopVisitor(Map methodMap) { - this.methodMap = methodMap; - } + public ForLoopVisitor(Map methodMap) { + this.methodMap = methodMap; + } - @Override - public boolean visit(ForStatement node) { - node.accept(new ASTVisitor() { - @Override - public boolean visit(MethodInvocation methodInvocation) { - String methodName = methodInvocation.getName().getIdentifier(); - if (DB_METHODS.contains(methodName)) { - reportAntiPattern(methodInvocation); - } else if (methodMap.containsKey(methodName)) { - MethodDeclaration methodDeclaration = methodMap.get(methodName); - methodDeclaration.accept(new MethodInvocationVisitor(methodInvocation)); + @Override + public boolean visit(MethodDeclaration node) { + node.accept(new ASTVisitor() { + @Override + public boolean visit(org.eclipse.jdt.core.dom.ForStatement forStatement) { + forStatement.accept(new ASTVisitor() { + @Override + public boolean visit(MethodInvocation methodInvocation) { + String methodName = methodInvocation.getName().getIdentifier(); + if (DB_METHODS.contains(methodName)) { + reportAntiPattern(methodInvocation); + } else if (methodMap.containsKey(methodName)) { + MethodDeclaration methodDeclaration = methodMap.get(methodName); + Set visitedMethods = new HashSet<>(); + visitedMethods.add(node.getName().getIdentifier()); + methodDeclaration.accept(new MethodInvocationVisitor(methodMap, methodInvocation, visitedMethods)); + } + return super.visit(methodInvocation); + } + }); + return super.visit(forStatement); } - return super.visit(methodInvocation); - } - }); - return super.visit(node); - } + }); + return super.visit(node); + } - private void reportAntiPattern(MethodInvocation methodInvocation) { - System.out.println("Anti-pattern detected: " + methodInvocation.getName().getIdentifier() + - " call inside a for loop at line " + - ((CompilationUnit) methodInvocation.getRoot()).getLineNumber(methodInvocation.getStartPosition())); + private void reportAntiPattern(MethodInvocation methodInvocation) { + System.out.println("Anti-pattern detected: " + methodInvocation.getName().getIdentifier() + + " call inside a for loop at line " + + ((CompilationUnit) methodInvocation.getRoot()).getLineNumber(methodInvocation.getStartPosition())); + } } - private class MethodInvocationVisitor extends ASTVisitor { - private MethodInvocation originalInvocation; + private static class MethodInvocationVisitor extends ASTVisitor { + private final Map methodMap; + private final MethodInvocation originalInvocation; + private final Set visitedMethods; - public MethodInvocationVisitor(MethodInvocation originalInvocation) { + public MethodInvocationVisitor(Map methodMap, MethodInvocation originalInvocation, Set visitedMethods) { + this.methodMap = methodMap; this.originalInvocation = originalInvocation; + this.visitedMethods = visitedMethods; } @Override public boolean visit(MethodInvocation methodInvocation) { String methodName = methodInvocation.getName().getIdentifier(); + if (visitedMethods.contains(methodName)) { + return false; // Skip already visited methods in this call chain + } + visitedMethods.add(methodName); + if (DB_METHODS.contains(methodName)) { - reportAntiPattern(originalInvocation); + System.out.println("Anti-pattern detected: " + originalInvocation.getName().getIdentifier() + + " indirectly calls " + methodInvocation.getName().getIdentifier() + + " inside a for loop at line " + + ((CompilationUnit) originalInvocation.getRoot()).getLineNumber(originalInvocation.getStartPosition())); } else if (methodMap.containsKey(methodName)) { MethodDeclaration methodDeclaration = methodMap.get(methodName); - methodDeclaration.accept(new MethodInvocationVisitor(originalInvocation)); + methodDeclaration.accept(new MethodInvocationVisitor(methodMap, originalInvocation, visitedMethods)); } return super.visit(methodInvocation); } } -} - -class MethodCollector extends ASTVisitor { - private Map methodMap = new HashMap<>(); - - @Override - public boolean visit(MethodDeclaration node) { - String methodName = node.getName().getIdentifier(); - methodMap.put(methodName, node); - return super.visit(node); - } - - public Map getMethodMap() { - return methodMap; - } -} +} \ No newline at end of file From f701ccb8f34c9f3af6e0f12b3aa8c6ccfa350532 Mon Sep 17 00:00:00 2001 From: Melis Ece Unsal Date: Thu, 30 May 2024 14:17:00 +0200 Subject: [PATCH 3/7] call chain analyzer --- pom.xml | 11 +- src/main/java/tum/dpid/CallChainAnalyzer.java | 128 ++++++++++++++++++ 2 files changed, 136 insertions(+), 3 deletions(-) create mode 100644 src/main/java/tum/dpid/CallChainAnalyzer.java diff --git a/pom.xml b/pom.xml index 192865c..4ac890a 100644 --- a/pom.xml +++ b/pom.xml @@ -30,9 +30,14 @@ 3.28.0 - org.soot-oss - soot - 4.3.0 + org.jgrapht + jgrapht-core + 1.5.1 + + + org.jgrapht + jgrapht-io + 1.4.0 diff --git a/src/main/java/tum/dpid/CallChainAnalyzer.java b/src/main/java/tum/dpid/CallChainAnalyzer.java new file mode 100644 index 0000000..e27411d --- /dev/null +++ b/src/main/java/tum/dpid/CallChainAnalyzer.java @@ -0,0 +1,128 @@ +package tum.dpid; + +import org.eclipse.jdt.core.dom.*; +import java.util.*; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +public class CallChainAnalyzer { + + private static final List DB_METHODS = List.of("flush", "save", "update", "persist", "remove", "find"); + + public static void main(String[] args) { + String projectDirectoryPath = "/Users/melisuensal/Desktop/LoopAntiPattern/src/main/java/com/example/LoopAntiPattern"; + + File projectDirectory = new File(projectDirectoryPath); + if (!projectDirectory.exists() || !projectDirectory.isDirectory()) { + System.out.println("Invalid project directory path."); + return; + } + + Map methodMap = collectMethods(projectDirectory); + Map> callGraph = buildCallGraph(methodMap); + + // Trace call chains for DB_METHODS + for (String targetMethod : DB_METHODS) { + System.out.println("Call chain for method: " + targetMethod); + Set visited = new HashSet<>(); + List callChain = new ArrayList<>(); + traceCallChain(targetMethod, callGraph, visited, callChain); + + // Print the call chain + if (callChain.isEmpty()) { + System.out.println("No calls found for method: " + targetMethod); + } else { + for (String method : callChain) { + System.out.println(method); + } + } + System.out.println(); + } + } + + private static Map collectMethods(File projectDirectory) { + Map methodMap = new HashMap<>(); + List javaFiles = getJavaFiles(projectDirectory); + + if (javaFiles != null) { + for (File javaFile : javaFiles) { + try { + String source = new String(Files.readAllBytes(javaFile.toPath())); + ASTParser parser = ASTParser.newParser(AST.JLS_Latest); + parser.setSource(source.toCharArray()); + parser.setKind(ASTParser.K_COMPILATION_UNIT); + + CompilationUnit cu = (CompilationUnit) parser.createAST(null); + cu.accept(new MethodCollector(methodMap)); + } catch (IOException e) { + System.err.println("Error reading file: " + javaFile.getName()); + e.printStackTrace(); + } + } + } + + return methodMap; + } + + private static List getJavaFiles(File directory) { + List javaFiles = new ArrayList<>(); + File[] files = directory.listFiles(); + + if (files != null) { + for (File file : files) { + if (file.isDirectory()) { + javaFiles.addAll(getJavaFiles(file)); + } else if (file.getName().endsWith(".java")) { + javaFiles.add(file); + } + } + } + + return javaFiles; + } + + private static Map> buildCallGraph(Map methodMap) { + Map> callGraph = new HashMap<>(); + + for (MethodDeclaration method : methodMap.values()) { + method.accept(new ASTVisitor() { + @Override + public boolean visit(MethodInvocation node) { + String caller = method.getName().toString(); + String callee = node.getName().toString(); + callGraph.computeIfAbsent(callee, k -> new HashSet<>()).add(caller); + return super.visit(node); + } + }); + } + + return callGraph; + } + + private static void traceCallChain(String methodName, Map> callGraph, Set visited, List callChain) { + if (!visited.add(methodName)) { + return; + } + callChain.add(methodName); + Set callers = callGraph.get(methodName); + if (callers != null) { + for (String caller : callers) { + traceCallChain(caller, callGraph, visited, callChain); + } + } + } + + static class MethodCollector extends ASTVisitor { + private final Map methodMap; + + MethodCollector(Map methodMap) { + this.methodMap = methodMap; + } + + @Override + public boolean visit(MethodDeclaration node) { + methodMap.put(node.getName().toString(), node); + return super.visit(node); + } + } +} From 23da6b737cadd290d37d849e906e32907f02e968 Mon Sep 17 00:00:00 2001 From: Melis Ece Unsal Date: Thu, 30 May 2024 14:18:20 +0200 Subject: [PATCH 4/7] comment out --- src/main/java/tum/dpid/CallChainAnalyzer.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/tum/dpid/CallChainAnalyzer.java b/src/main/java/tum/dpid/CallChainAnalyzer.java index e27411d..4b17e79 100644 --- a/src/main/java/tum/dpid/CallChainAnalyzer.java +++ b/src/main/java/tum/dpid/CallChainAnalyzer.java @@ -21,14 +21,12 @@ public static void main(String[] args) { Map methodMap = collectMethods(projectDirectory); Map> callGraph = buildCallGraph(methodMap); - // Trace call chains for DB_METHODS for (String targetMethod : DB_METHODS) { System.out.println("Call chain for method: " + targetMethod); Set visited = new HashSet<>(); List callChain = new ArrayList<>(); traceCallChain(targetMethod, callGraph, visited, callChain); - // Print the call chain if (callChain.isEmpty()) { System.out.println("No calls found for method: " + targetMethod); } else { From 1c02ac5ca3af6cc72f10a4dae9fdc4cb1be43aab Mon Sep 17 00:00:00 2001 From: Melis Ece Unsal Date: Thu, 30 May 2024 15:13:25 +0200 Subject: [PATCH 5/7] print call graph --- src/main/java/tum/dpid/CallChainAnalyzer.java | 35 ++++++++++++++++--- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/src/main/java/tum/dpid/CallChainAnalyzer.java b/src/main/java/tum/dpid/CallChainAnalyzer.java index 4b17e79..6edf3aa 100644 --- a/src/main/java/tum/dpid/CallChainAnalyzer.java +++ b/src/main/java/tum/dpid/CallChainAnalyzer.java @@ -22,19 +22,24 @@ public static void main(String[] args) { Map> callGraph = buildCallGraph(methodMap); for (String targetMethod : DB_METHODS) { - System.out.println("Call chain for method: " + targetMethod); + //System.out.println("Call chain for method: " + targetMethod); Set visited = new HashSet<>(); List callChain = new ArrayList<>(); - traceCallChain(targetMethod, callGraph, visited, callChain); + traceCallChainInOrder(targetMethod, callGraph, new HashSet<>(), callChain, methodMap); if (callChain.isEmpty()) { System.out.println("No calls found for method: " + targetMethod); } else { for (String method : callChain) { - System.out.println(method); + //System.out.println(method); } } + //System.out.println(); + + System.out.println("Call graph for method: " + targetMethod); + drawCallGraph(targetMethod, callGraph, 0, new HashSet<>()); System.out.println(); + } } @@ -97,19 +102,39 @@ public boolean visit(MethodInvocation node) { return callGraph; } - private static void traceCallChain(String methodName, Map> callGraph, Set visited, List callChain) { + private static void traceCallChainInOrder(String methodName, Map> callGraph, Set visited, List callChain, Map methodMap) { if (!visited.add(methodName)) { return; } callChain.add(methodName); + Set callees = callGraph.get(methodName); + if (callees != null) { + for (String callee : callees) { + traceCallChainInOrder(callee, callGraph, visited, callChain, methodMap); + } + } + } + + private static void drawCallGraph(String methodName, Map> callGraph, int level, Set visited) { + if (!visited.add(methodName)) { + return; + } + printIndented(methodName, level); Set callers = callGraph.get(methodName); if (callers != null) { for (String caller : callers) { - traceCallChain(caller, callGraph, visited, callChain); + drawCallGraph(caller, callGraph, level + 1, visited); } } } + private static void printIndented(String methodName, int level) { + for (int i = 0; i < level; i++) { + System.out.print(" "); + } + System.out.println(methodName); + } + static class MethodCollector extends ASTVisitor { private final Map methodMap; From a82cb0c8f41e8affa0ba643909ca42e9b9b74c62 Mon Sep 17 00:00:00 2001 From: Cagataygultekin Date: Thu, 6 Jun 2024 17:48:40 +0200 Subject: [PATCH 6/7] Anti-Patterns on Graph Printed some anti-patterns in graph with statically given method name. Tested if dynamically can be done. --- pom.xml | 15 ++ .../java/tum/dpid/AntiPatternDetector.java | 2 +- src/main/java/tum/dpid/CallChainAnalyzer.java | 149 ++++++++--- .../tum/dpid/DynamicCallChainAnalyzer.java | 212 +++++++++++++++ .../java/tum/dpid/NewCallChainAnalyzer.java | 124 +++++++++ .../tum/dpid/OldDynamicCallChainAnalyzer.java | 253 ++++++++++++++++++ 6 files changed, 724 insertions(+), 31 deletions(-) create mode 100644 src/main/java/tum/dpid/DynamicCallChainAnalyzer.java create mode 100644 src/main/java/tum/dpid/NewCallChainAnalyzer.java create mode 100644 src/main/java/tum/dpid/OldDynamicCallChainAnalyzer.java diff --git a/pom.xml b/pom.xml index 4ac890a..280d021 100644 --- a/pom.xml +++ b/pom.xml @@ -39,6 +39,21 @@ jgrapht-io 1.4.0 + + io.github.classgraph + classgraph + 4.8.117 + + + org.springframework + spring-context + 5.3.1 + + + org.springframework + spring-tx + 5.3.1 + diff --git a/src/main/java/tum/dpid/AntiPatternDetector.java b/src/main/java/tum/dpid/AntiPatternDetector.java index f7dc872..1e37bac 100644 --- a/src/main/java/tum/dpid/AntiPatternDetector.java +++ b/src/main/java/tum/dpid/AntiPatternDetector.java @@ -31,7 +31,7 @@ public class AntiPatternDetector { private static final List DB_METHODS = List.of("flush", "save", "update", "persist", "remove", "find"); public static void main(String[] args) { - String projectDirectoryPath = "/Users/melisuensal/Desktop/LoopAntiPattern/src/main/java/com/example/LoopAntiPattern"; + String projectDirectoryPath = "../fromItestra/LoopAntiPattern"; File projectDirectory = new File(projectDirectoryPath); if (!projectDirectory.exists() || !projectDirectory.isDirectory()) { diff --git a/src/main/java/tum/dpid/CallChainAnalyzer.java b/src/main/java/tum/dpid/CallChainAnalyzer.java index 6edf3aa..a945054 100644 --- a/src/main/java/tum/dpid/CallChainAnalyzer.java +++ b/src/main/java/tum/dpid/CallChainAnalyzer.java @@ -5,12 +5,15 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; + public class CallChainAnalyzer { - private static final List DB_METHODS = List.of("flush", "save", "update", "persist", "remove", "find"); + private static final List DB_METHODS = List.of("flush", "save", "update", "persist", "remove", "find", + "selectById", "selectExistingById", "delete", + "selectByArticleNumber", "saveWithoutFlush", "merge", "selectFrom"); public static void main(String[] args) { - String projectDirectoryPath = "/Users/melisuensal/Desktop/LoopAntiPattern/src/main/java/com/example/LoopAntiPattern"; + String projectDirectoryPath = "../fromItestra/LoopAntiPattern"; File projectDirectory = new File(projectDirectoryPath); if (!projectDirectory.exists() || !projectDirectory.isDirectory()) { @@ -22,25 +25,12 @@ public static void main(String[] args) { Map> callGraph = buildCallGraph(methodMap); for (String targetMethod : DB_METHODS) { - //System.out.println("Call chain for method: " + targetMethod); - Set visited = new HashSet<>(); - List callChain = new ArrayList<>(); - traceCallChainInOrder(targetMethod, callGraph, new HashSet<>(), callChain, methodMap); - - if (callChain.isEmpty()) { - System.out.println("No calls found for method: " + targetMethod); - } else { - for (String method : callChain) { - //System.out.println(method); - } - } - //System.out.println(); - System.out.println("Call graph for method: " + targetMethod); drawCallGraph(targetMethod, callGraph, 0, new HashSet<>()); System.out.println(); - } + + checkForDatabaseCallsInLoops(methodMap, callGraph); } private static Map collectMethods(File projectDirectory) { @@ -102,19 +92,6 @@ public boolean visit(MethodInvocation node) { return callGraph; } - private static void traceCallChainInOrder(String methodName, Map> callGraph, Set visited, List callChain, Map methodMap) { - if (!visited.add(methodName)) { - return; - } - callChain.add(methodName); - Set callees = callGraph.get(methodName); - if (callees != null) { - for (String callee : callees) { - traceCallChainInOrder(callee, callGraph, visited, callChain, methodMap); - } - } - } - private static void drawCallGraph(String methodName, Map> callGraph, int level, Set visited) { if (!visited.add(methodName)) { return; @@ -135,6 +112,118 @@ private static void printIndented(String methodName, int level) { System.out.println(methodName); } + private static void checkForDatabaseCallsInLoops(Map methodMap, Map> callGraph) { + for (MethodDeclaration method : methodMap.values()) { + method.accept(new ASTVisitor() { + @Override + public boolean visit(ForStatement node) { + System.out.println("For statement visited."); + checkMethodInvocationsInLoop(node.getBody(), methodMap, callGraph); + return super.visit(node); + } + + @Override + public boolean visit(WhileStatement node) { + System.out.println("While statement visited."); + checkMethodInvocationsInLoop(node.getBody(), methodMap, callGraph); + return super.visit(node); + } + + @Override + public boolean visit(DoStatement node) { + System.out.println("Do-While statement visited."); + checkMethodInvocationsInLoop(node.getBody(), methodMap, callGraph); + return super.visit(node); + } + + @Override + public boolean visit(EnhancedForStatement node) { + System.out.println("EnhancedForStatement statement visited."); + checkMethodInvocationsInLoop(node.getBody(), methodMap, callGraph); + return super.visit(node); + } + + @Override + public boolean visit(LambdaExpression node) { + System.out.println("Lambda statement visited."); + checkMethodInvocationsInLoop(node.getBody(), methodMap, callGraph); + return super.visit(node); + } + + @Override + public boolean visit(MethodInvocation node) { + // Check for stream method calls like map, filter, etc. + if (node.getName().getIdentifier().equals("map") || node.getName().getIdentifier().equals("forEach") || node.getName().getIdentifier().equals("stream")) { + System.out.println("Stream-related method visited: " + node.getName().getIdentifier()); + node.accept(new ASTVisitor() { + @Override + public boolean visit(LambdaExpression lambdaExpression) { + System.out.println("Lambda statement visited inside stream operation."); + checkMethodInvocationsInLoop(lambdaExpression.getBody(), methodMap, callGraph); + return super.visit(lambdaExpression); + } + + @Override + public boolean visit(MethodInvocation innerNode) { + System.out.println("MethodInvocation inside lambda visited: " + innerNode.getName().getIdentifier()); + String methodName = innerNode.getName().getIdentifier(); + if (DB_METHODS.contains(methodName)) { + reportAntiPattern(innerNode); + } else if (methodMap.containsKey(methodName)) { + System.out.println("Tracing method: " + methodName); + traceMethodCallsInLoop(methodName, methodMap, callGraph, new HashSet<>(), innerNode); + } + return super.visit(innerNode); + } + }); + } + return super.visit(node); + } + }); + } + } + + private static void checkMethodInvocationsInLoop(ASTNode loopBody, Map methodMap, Map> callGraph) { + loopBody.accept(new ASTVisitor() { + @Override + public boolean visit(MethodInvocation node) { + String methodName = node.getName().getIdentifier(); + System.out.println("MethodInvocation visited: " + methodName); + if (DB_METHODS.contains(methodName)) { + reportAntiPattern(node); + } else if (methodMap.containsKey(methodName)) { + System.out.println("Tracing method: " + methodName); + traceMethodCallsInLoop(methodName, methodMap, callGraph, new HashSet<>(), node); + } + return super.visit(node); + } + }); + } + + private static void traceMethodCallsInLoop(String methodName, Map methodMap, Map> callGraph, Set visited, MethodInvocation originalInvocation) { + if (!visited.add(methodName)) { + return; + } + Set callees = callGraph.get(methodName); + if (callees != null) { + for (String callee : callees) { + System.out.println("Callee: " + callee); + if (DB_METHODS.contains(callee)) { + System.out.println("DB method detected in call chain: " + callee); + reportAntiPattern(originalInvocation); + } else if (methodMap.containsKey(callee)) { + traceMethodCallsInLoop(callee, methodMap, callGraph, visited, originalInvocation); + } + } + } + } + + private static void reportAntiPattern(MethodInvocation methodInvocation) { + System.out.println("Anti-pattern detected: " + methodInvocation.getName().getIdentifier() + + " call inside a loop at line " + + ((CompilationUnit) methodInvocation.getRoot()).getLineNumber(methodInvocation.getStartPosition())); + } + static class MethodCollector extends ASTVisitor { private final Map methodMap; diff --git a/src/main/java/tum/dpid/DynamicCallChainAnalyzer.java b/src/main/java/tum/dpid/DynamicCallChainAnalyzer.java new file mode 100644 index 0000000..d2988f3 --- /dev/null +++ b/src/main/java/tum/dpid/DynamicCallChainAnalyzer.java @@ -0,0 +1,212 @@ +package com.itestra.callchain; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Method; +import java.nio.file.Files; +import java.util.*; + +import org.eclipse.jdt.core.dom.*; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; +import io.github.classgraph.ClassGraph; +import io.github.classgraph.ClassInfo; +import io.github.classgraph.ScanResult; +import tum.dpid.CallChainAnalyzer; + +public class DynamicCallChainAnalyzer { + + private static List dbMethods; + + public static void main(String[] args) { + DynamicCallChainAnalyzer analyzer = new DynamicCallChainAnalyzer(); + analyzer.initializeDbMethods(); + String projectDirectoryPath = "../fromItestra/LoopAntiPattern"; + + File projectDirectory = new File(projectDirectoryPath); + if (!projectDirectory.exists() || !projectDirectory.isDirectory()) { + System.out.println("Invalid project directory path."); + return; + } + + Map methodMap = collectMethods(projectDirectory); + Map> callGraph = buildCallGraph(methodMap); + + for (String dbMethod : dbMethods) { + System.out.println("Finding call chains for database method: " + dbMethod); + Set visited = new HashSet<>(); + List callChain = new ArrayList<>(); + traceCallChainsToMethod(dbMethod, callGraph, new HashSet<>(), callChain, methodMap); + } + } + + public void initializeDbMethods() { + dbMethods = new ArrayList<>(); + // Add common database methods by naming convention + addCommonDbMethods(); + + // Scan classes in the project + String packageName = "com.itestra"; // Specify your project's base package + List> classes = getClasses(packageName); + + for (Class clazz : classes) { + // Check if class is a repository + if (clazz.isAnnotationPresent(Repository.class)) { + scanClassForDbMethods(clazz); + } + + // Check if methods are transactional + for (Method method : clazz.getDeclaredMethods()) { + if (method.isAnnotationPresent(Transactional.class)) { + dbMethods.add(method.getName()); + } + } + } + } + + private void addCommonDbMethods() { + dbMethods.add("flush"); + dbMethods.add("save"); + dbMethods.add("update"); + dbMethods.add("persist"); + dbMethods.add("remove"); + dbMethods.add("find"); + dbMethods.add("selectById"); + dbMethods.add("selectExistingById"); + dbMethods.add("delete"); + dbMethods.add("selectByArticleNumber"); + dbMethods.add("saveWithoutFlush"); + dbMethods.add("merge"); + dbMethods.add("selectFrom"); + } + + private void scanClassForDbMethods(Class clazz) { + for (Method method : clazz.getDeclaredMethods()) { + if (isDatabaseMethod(method)) { + dbMethods.add(method.getName()); + } + } + } + + private boolean isDatabaseMethod(Method method) { + String methodName = method.getName().toLowerCase(); + // Check common database operation prefixes + return methodName.startsWith("save") || + methodName.startsWith("find") || + methodName.startsWith("delete") || + methodName.startsWith("update") || + methodName.startsWith("select") || + methodName.startsWith("remove") || + methodName.startsWith("persist") || + methodName.startsWith("flush") || + methodName.startsWith("merge"); + } + + private List> getClasses(String packageName) { + // Use ClassGraph to scan and retrieve all classes in the package + List> classes = new ArrayList<>(); + try (ScanResult scanResult = new ClassGraph().acceptPackages(packageName).scan()) { + for (ClassInfo classInfo : scanResult.getAllClasses()) { + classes.add(Class.forName(classInfo.getName())); + } + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + return classes; + } + + public List getDbMethods() { + return dbMethods; + } + + private static Map collectMethods(File projectDirectory) { + Map methodMap = new HashMap<>(); + List javaFiles = getJavaFiles(projectDirectory); + + if (javaFiles != null) { + for (File javaFile : javaFiles) { + try { + String source = new String(Files.readAllBytes(javaFile.toPath())); + ASTParser parser = ASTParser.newParser(AST.JLS_Latest); + parser.setSource(source.toCharArray()); + parser.setKind(ASTParser.K_COMPILATION_UNIT); + + CompilationUnit cu = (CompilationUnit) parser.createAST(null); + cu.accept(new MethodCollector(methodMap)); + } catch (IOException e) { + System.err.println("Error reading file: " + javaFile.getName()); + e.printStackTrace(); + } + } + } + + return methodMap; + } + + private static List getJavaFiles(File directory) { + List javaFiles = new ArrayList<>(); + File[] files = directory.listFiles(); + + if (files != null) { + for (File file : files) { + if (file.isDirectory()) { + javaFiles.addAll(getJavaFiles(file)); + } else if (file.getName().endsWith(".java")) { + javaFiles.add(file); + } + } + } + + return javaFiles; + } + + private static Map> buildCallGraph(Map methodMap) { + Map> callGraph = new HashMap<>(); + + for (MethodDeclaration method : methodMap.values()) { + method.accept(new ASTVisitor() { + @Override + public boolean visit(MethodInvocation node) { + String caller = method.getName().toString(); + String callee = node.getName().toString(); + callGraph.computeIfAbsent(caller, k -> new HashSet<>()).add(callee); + return super.visit(node); + } + }); + } + + return callGraph; + } + + private static void traceCallChainsToMethod(String targetMethod, Map> callGraph, Set visited, List callChain, Map methodMap) { + if (!visited.add(targetMethod)) { + return; + } + callChain.add(targetMethod); + Set callees = callGraph.get(targetMethod); + if (callees != null) { + for (String callee : callees) { + List newCallChain = new ArrayList<>(callChain); + traceCallChainsToMethod(callee, callGraph, visited, newCallChain, methodMap); + } + } else { + if (dbMethods.contains(targetMethod)) { + System.out.println("Database method call chain: " + String.join(" -> ", callChain)); + } + } + } + + public static class MethodCollector extends ASTVisitor { + private final Map methodMap; + + public MethodCollector(Map methodMap) { + this.methodMap = methodMap; + } + + @Override + public boolean visit(MethodDeclaration node) { + methodMap.put(node.getName().toString(), node); + return super.visit(node); + } + } +} diff --git a/src/main/java/tum/dpid/NewCallChainAnalyzer.java b/src/main/java/tum/dpid/NewCallChainAnalyzer.java new file mode 100644 index 0000000..8d9f35a --- /dev/null +++ b/src/main/java/tum/dpid/NewCallChainAnalyzer.java @@ -0,0 +1,124 @@ +package tum.dpid; + +import org.eclipse.jdt.core.dom.*; +import java.util.*; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +public class NewCallChainAnalyzer { + + private static final List DB_METHODS = List.of("flush", "save", "update", "persist", "remove", "find", "selectById", "selectExistingById", "delete", "selectByArticleNumber", "saveWithoutFlush", "merge", "selectFrom"); + + public static void main(String[] args) { + String projectDirectoryPath = "../fromItestra/LoopAntiPattern"; + + File projectDirectory = new File(projectDirectoryPath); + if (!projectDirectory.exists() || !projectDirectory.isDirectory()) { + System.out.println("Invalid project directory path."); + return; + } + + Map methodMap = collectMethods(projectDirectory); + Map> callGraph = buildCallGraph(methodMap); + + for (String dbMethod : DB_METHODS) { + System.out.println("Finding call chains for database method: " + dbMethod); + Set visited = new HashSet<>(); + List callChain = new ArrayList<>(); + traceCallChainsToMethod(dbMethod, callGraph, new HashSet<>(), callChain, methodMap); + } + } + + private static Map collectMethods(File projectDirectory) { + Map methodMap = new HashMap<>(); + List javaFiles = getJavaFiles(projectDirectory); + + if (javaFiles != null) { + for (File javaFile : javaFiles) { + try { + String source = new String(Files.readAllBytes(javaFile.toPath())); + ASTParser parser = ASTParser.newParser(AST.JLS_Latest); + parser.setSource(source.toCharArray()); + parser.setKind(ASTParser.K_COMPILATION_UNIT); + + CompilationUnit cu = (CompilationUnit) parser.createAST(null); + cu.accept(new MethodCollector(methodMap)); + } catch (IOException e) { + System.err.println("Error reading file: " + javaFile.getName()); + e.printStackTrace(); + } + } + } + + return methodMap; + } + + private static List getJavaFiles(File directory) { + List javaFiles = new ArrayList<>(); + File[] files = directory.listFiles(); + + if (files != null) { + for (File file : files) { + if (file.isDirectory()) { + javaFiles.addAll(getJavaFiles(file)); + } else if (file.getName().endsWith(".java")) { + javaFiles.add(file); + } + } + } + + return javaFiles; + } + + private static Map> buildCallGraph(Map methodMap) { + Map> callGraph = new HashMap<>(); + + for (MethodDeclaration method : methodMap.values()) { + method.accept(new ASTVisitor() { + @Override + public boolean visit(MethodInvocation node) { + String caller = method.getName().toString(); + String callee = node.getName().toString(); + callGraph.computeIfAbsent(caller, k -> new HashSet<>()).add(callee); + return super.visit(node); + } + }); + } + + return callGraph; + } + + private static void traceCallChainsToMethod(String targetMethod, Map> callGraph, Set visited, List callChain, Map methodMap) { + if (!visited.add(targetMethod)) { + return; + } + callChain.add(targetMethod); + Set callees = callGraph.get(targetMethod); + if (callees != null) { + for (String callee : callees) { + List newCallChain = new ArrayList<>(callChain); + traceCallChainsToMethod(callee, callGraph, visited, newCallChain, methodMap); + } + } else { + if (DB_METHODS.contains(targetMethod)) { + System.out.println("Database method call chain: " + String.join(" -> ", callChain)); + } + } + } + + static class MethodCollector extends ASTVisitor { + private final Map methodMap; + + MethodCollector(Map methodMap) { + this.methodMap = methodMap; + } + + @Override + public boolean visit(MethodDeclaration node) { + methodMap.put(node.getName().toString(), node); + return super.visit(node); + } + } +} + diff --git a/src/main/java/tum/dpid/OldDynamicCallChainAnalyzer.java b/src/main/java/tum/dpid/OldDynamicCallChainAnalyzer.java new file mode 100644 index 0000000..3456ea5 --- /dev/null +++ b/src/main/java/tum/dpid/OldDynamicCallChainAnalyzer.java @@ -0,0 +1,253 @@ +package tum.dpid; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Method; +import java.nio.file.Files; +import java.util.*; + +import org.eclipse.jdt.core.dom.*; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; +import io.github.classgraph.ClassGraph; +import io.github.classgraph.ClassInfo; +import io.github.classgraph.ScanResult; + +public class OldDynamicCallChainAnalyzer { + + private static List dbMethods; + + public static void main(String[] args) { + OldDynamicCallChainAnalyzer analyzer = new OldDynamicCallChainAnalyzer(); + analyzer.initializeDbMethods(); + + String projectDirectoryPath = "../fromItestra/LoopAntiPattern"; + + File projectDirectory = new File(projectDirectoryPath); + if (!projectDirectory.exists() || !projectDirectory.isDirectory()) { + System.out.println("Invalid project directory path."); + return; + } + + Map methodMap = collectMethods(projectDirectory); + Map> callGraph = buildCallGraph(methodMap); + + for (String dbMethod : dbMethods) { + System.out.println("Call chain for method: " + dbMethod); + Set visited = new HashSet<>(); + List callChain = new ArrayList<>(); + traceCallChainInOrder(dbMethod, callGraph, new HashSet<>(), callChain, methodMap); + + if (callChain.isEmpty()) { + System.out.println("No calls found for method: " + dbMethod); + } else { + for (String method : callChain) { + System.out.println(method); + } + } + + System.out.println("Call graph for method: " + dbMethod); + drawCallGraph(dbMethod, callGraph, 0, new HashSet<>()); + System.out.println(); + } + } + + public void initializeDbMethods() { + dbMethods = new ArrayList<>(); + // Add common database methods by naming convention + addCommonDbMethods(); + + // Scan classes in the project + String packageName = "com.example.LoopAntiPattern"; // Specify your project's base package + List> classes = getClasses(packageName); + + for (Class clazz : classes) { + // Check if class is a repository + if (clazz.isAnnotationPresent(Repository.class)) { + scanClassForDbMethods(clazz); + } + + // Check if methods are transactional + for (Method method : clazz.getDeclaredMethods()) { + if (method.isAnnotationPresent(Transactional.class)) { + dbMethods.add(method.getName()); + } + } + } + } + + private void addCommonDbMethods() { + dbMethods.add("flush"); + dbMethods.add("save"); + dbMethods.add("update"); + dbMethods.add("persist"); + dbMethods.add("remove"); + dbMethods.add("find"); + dbMethods.add("selectById"); + dbMethods.add("selectExistingById"); + dbMethods.add("delete"); + dbMethods.add("selectByArticleNumber"); + dbMethods.add("saveWithoutFlush"); + dbMethods.add("merge"); + dbMethods.add("selectFrom"); + } + + private void scanClassForDbMethods(Class clazz) { + for (Method method : clazz.getDeclaredMethods()) { + if (isDatabaseMethod(method)) { + dbMethods.add(method.getName()); + } + } + } + + private boolean isDatabaseMethod(Method method) { + String methodName = method.getName().toLowerCase(); + // Check common database operation prefixes + return methodName.startsWith("save") || + methodName.startsWith("find") || + methodName.startsWith("delete") || + methodName.startsWith("update") || + methodName.startsWith("select") || + methodName.startsWith("remove") || + methodName.startsWith("persist") || + methodName.startsWith("flush") || + methodName.startsWith("merge"); + } + + private List> getClasses(String packageName) { + // Use ClassGraph to scan and retrieve all classes in the package + List> classes = new ArrayList<>(); + try (ScanResult scanResult = new ClassGraph().acceptPackages(packageName).scan()) { + for (ClassInfo classInfo : scanResult.getAllClasses()) { + classes.add(Class.forName(classInfo.getName())); + } + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + return classes; + } + + private static Map collectMethods(File projectDirectory) { + Map methodMap = new HashMap<>(); + List javaFiles = getJavaFiles(projectDirectory); + + if (javaFiles != null) { + for (File javaFile : javaFiles) { + try { + String source = new String(Files.readAllBytes(javaFile.toPath())); + ASTParser parser = ASTParser.newParser(AST.JLS_Latest); + parser.setSource(source.toCharArray()); + parser.setKind(ASTParser.K_COMPILATION_UNIT); + + CompilationUnit cu = (CompilationUnit) parser.createAST(null); + cu.accept(new MethodCollector(methodMap)); + } catch (IOException e) { + System.err.println("Error reading file: " + javaFile.getName()); + e.printStackTrace(); + } + } + } + + return methodMap; + } + + private static List getJavaFiles(File directory) { + List javaFiles = new ArrayList<>(); + File[] files = directory.listFiles(); + + if (files != null) { + for (File file : files) { + if (file.isDirectory()) { + javaFiles.addAll(getJavaFiles(file)); + } else if (file.getName().endsWith(".java")) { + javaFiles.add(file); + } + } + } + + return javaFiles; + } + + private static Map> buildCallGraph(Map methodMap) { + Map> callGraph = new HashMap<>(); + + for (MethodDeclaration method : methodMap.values()) { + method.accept(new ASTVisitor() { + @Override + public boolean visit(MethodInvocation node) { + String caller = method.getName().toString(); + String callee = node.getName().toString(); + callGraph.computeIfAbsent(callee, k -> new HashSet<>()).add(caller); + return super.visit(node); + } + }); + } + + return callGraph; + } + + private static void traceCallChainInOrder(String methodName, Map> callGraph, Set visited, List callChain, Map methodMap) { + if (!visited.add(methodName)) { + return; + } + callChain.add(methodName); + Set callees = callGraph.get(methodName); + if (callees != null) { + for (String callee : callees) { + traceCallChainInOrder(callee, callGraph, visited, callChain, methodMap); + } + } + } + + private static void drawCallGraph(String methodName, Map> callGraph, int level, Set visited) { + if (!visited.add(methodName)) { + return; + } + printIndented(methodName, level); + Set callers = callGraph.get(methodName); + if (callers != null) { + for (String caller : callers) { + drawCallGraph(caller, callGraph, level + 1, visited); + } + } + } + + private static void printIndented(String methodName, int level) { + for (int i = 0; i < level; i++) { + System.out.print(" "); + } + System.out.println(methodName); + } + + private static void traceCallChainsToMethod(String targetMethod, Map> callGraph, Set visited, List callChain, Map methodMap) { + if (!visited.add(targetMethod)) { + return; + } + callChain.add(targetMethod); + Set callees = callGraph.get(targetMethod); + if (callees != null) { + for (String callee : callees) { + List newCallChain = new ArrayList<>(callChain); + traceCallChainsToMethod(callee, callGraph, visited, newCallChain, methodMap); + } + } else { + if (dbMethods.contains(targetMethod)) { + System.out.println("Database method call chain: " + String.join(" -> ", callChain)); + } + } + } + + public static class MethodCollector extends ASTVisitor { + private final Map methodMap; + + public MethodCollector(Map methodMap) { + this.methodMap = methodMap; + } + + @Override + public boolean visit(MethodDeclaration node) { + methodMap.put(node.getName().toString(), node); + return super.visit(node); + } + } +} From 9434beabb10d040c9224fa03676ee18c2805e5d0 Mon Sep 17 00:00:00 2001 From: Cagataygultekin Date: Mon, 1 Jul 2024 20:28:16 +0200 Subject: [PATCH 7/7] Working with config and dynamic method finder Addition to static db methods, dynamically db methods working. IN addition, general code now working with config. --- config.json | 10 ++ pom.xml | 5 + src/main/java/tum/dpid/CallChainAnalyzer.java | 139 +++++++++++++----- .../java/tum/dpid/config/AnalyzerConfig.java | 55 +++++++ src/main/resources/tester/pom.xml | 17 +++ .../tester/src/main/java/com/tum/Main.java | 7 + .../tester/src/main/java/com/tum/loop/A.java | 14 ++ .../tester/src/main/java/com/tum/loop/B.java | 14 ++ .../src/main/java/com/tum/loop/Main.java | 37 +++++ .../src/main/java/com/tum/methodchain/A.java | 12 ++ .../src/main/java/com/tum/methodchain/B.java | 12 ++ .../src/main/java/com/tum/methodchain/C.java | 11 ++ .../main/java/com/tum/methodchain/Main.java | 43 ++++++ .../src/main/java/com/tum/stream/A.java | 31 ++++ .../src/main/java/com/tum/stream/Main.java | 38 +++++ 15 files changed, 408 insertions(+), 37 deletions(-) create mode 100644 config.json create mode 100644 src/main/java/tum/dpid/config/AnalyzerConfig.java create mode 100644 src/main/resources/tester/pom.xml create mode 100644 src/main/resources/tester/src/main/java/com/tum/Main.java create mode 100644 src/main/resources/tester/src/main/java/com/tum/loop/A.java create mode 100644 src/main/resources/tester/src/main/java/com/tum/loop/B.java create mode 100644 src/main/resources/tester/src/main/java/com/tum/loop/Main.java create mode 100644 src/main/resources/tester/src/main/java/com/tum/methodchain/A.java create mode 100644 src/main/resources/tester/src/main/java/com/tum/methodchain/B.java create mode 100644 src/main/resources/tester/src/main/java/com/tum/methodchain/C.java create mode 100644 src/main/resources/tester/src/main/java/com/tum/methodchain/Main.java create mode 100644 src/main/resources/tester/src/main/java/com/tum/stream/A.java create mode 100644 src/main/resources/tester/src/main/java/com/tum/stream/Main.java diff --git a/config.json b/config.json new file mode 100644 index 0000000..59f001b --- /dev/null +++ b/config.json @@ -0,0 +1,10 @@ +{ + "projectDirectory": "../fromItestra/LoopAntiPattern/LoopAntiPattern/src", + "repositoryDirectory": "../fromItestra/LoopAntiPattern/LoopAntiPattern/src/main/java/com/example/LoopAntiPattern", + "excludedClasses": ["ExcludeThis.java", "ExcludeThat.java"], + "excludedMethods": ["someMethod"] +} + +//,"ProductRepository.java","ProductService.java" +//"repositoryDirectory": "../fromItestra/LoopAntiPattern/LoopAntiPattern/src/main/java/com/example/LoopAntiPattern/data/repository", +//"repositoryDirectory": "../fromItestra/LoopAntiPattern/LoopAntiPattern/src/main/java/com/example/LoopAntiPattern/service", \ No newline at end of file diff --git a/pom.xml b/pom.xml index 280d021..6cb8cee 100644 --- a/pom.xml +++ b/pom.xml @@ -54,6 +54,11 @@ spring-tx 5.3.1 + + com.fasterxml.jackson.core + jackson-databind + 2.17.1 + diff --git a/src/main/java/tum/dpid/CallChainAnalyzer.java b/src/main/java/tum/dpid/CallChainAnalyzer.java index a945054..929600b 100644 --- a/src/main/java/tum/dpid/CallChainAnalyzer.java +++ b/src/main/java/tum/dpid/CallChainAnalyzer.java @@ -1,55 +1,108 @@ package tum.dpid; +import com.fasterxml.jackson.databind.ObjectMapper; import org.eclipse.jdt.core.dom.*; -import java.util.*; +import tum.dpid.config.AnalyzerConfig; + import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; public class CallChainAnalyzer { private static final List DB_METHODS = List.of("flush", "save", "update", "persist", "remove", "find", "selectById", "selectExistingById", "delete", - "selectByArticleNumber", "saveWithoutFlush", "merge", "selectFrom"); + "selectByArticleNumber", "saveWithoutFlush", "merge", "selectFrom", "callSave"); + + public static void main(String[] args) throws IOException { + + ObjectMapper mapper = new ObjectMapper(); + AnalyzerConfig config; + + try { + config = mapper.readValue(new File("config.json"), AnalyzerConfig.class); + } catch (IOException e) { + System.out.println("Error reading config file:\n" + e.getMessage()); + System.exit(1); + return; + } - public static void main(String[] args) { - String projectDirectoryPath = "../fromItestra/LoopAntiPattern"; + String directoryPath = config.getRepositoryDirectory(); + Path dirPath = Paths.get(directoryPath); + List dbMethods = new ArrayList<>(DB_METHODS); + + try { + List javaFiles = Files.walk(dirPath) + .filter(Files::isRegularFile) + .filter(path -> path.toString().endsWith(".java")) + .toList(); + + for (Path javaFile : javaFiles) { + dbMethods.addAll(extractMethodNames(javaFile, config.getExcludedClasses(), config.getExcludedMethods())); + } + } catch (IOException e) { + e.printStackTrace(); + } + + String projectDirectoryPath = config.getProjectDirectory(); File projectDirectory = new File(projectDirectoryPath); if (!projectDirectory.exists() || !projectDirectory.isDirectory()) { - System.out.println("Invalid project directory path."); + System.err.println("[ERROR] Invalid project directory path."); return; } - Map methodMap = collectMethods(projectDirectory); + Map methodMap = collectMethods(projectDirectory, config.getExcludedClasses(), config.getExcludedMethods()); Map> callGraph = buildCallGraph(methodMap); - for (String targetMethod : DB_METHODS) { - System.out.println("Call graph for method: " + targetMethod); + for (String targetMethod : dbMethods) { + System.out.println("\nCall graph for method: " + targetMethod); drawCallGraph(targetMethod, callGraph, 0, new HashSet<>()); - System.out.println(); } checkForDatabaseCallsInLoops(methodMap, callGraph); } - private static Map collectMethods(File projectDirectory) { + private static List extractMethodNames(Path javaFilePath, List excludedClasses, List excludedMethods) throws IOException { + String className = javaFilePath.getFileName().toString(); + if (excludedClasses.contains(className)) { + return Collections.emptyList(); + } + String content = new String(Files.readAllBytes(javaFilePath)); + ASTParser parser = ASTParser.newParser(AST.JLS8); + parser.setSource(content.toCharArray()); + parser.setKind(ASTParser.K_COMPILATION_UNIT); + + final CompilationUnit cu = (CompilationUnit) parser.createAST(null); + MethodNameVisitor visitor = new MethodNameVisitor(excludedMethods); + cu.accept(visitor); + System.out.println("New db methods found" + visitor.getMethodNames()); + return visitor.getMethodNames(); + } + + private static Map collectMethods(File projectDirectory, List excludedClasses, List excludedMethods) { Map methodMap = new HashMap<>(); List javaFiles = getJavaFiles(projectDirectory); if (javaFiles != null) { for (File javaFile : javaFiles) { - try { - String source = new String(Files.readAllBytes(javaFile.toPath())); - ASTParser parser = ASTParser.newParser(AST.JLS_Latest); - parser.setSource(source.toCharArray()); - parser.setKind(ASTParser.K_COMPILATION_UNIT); - - CompilationUnit cu = (CompilationUnit) parser.createAST(null); - cu.accept(new MethodCollector(methodMap)); - } catch (IOException e) { - System.err.println("Error reading file: " + javaFile.getName()); - e.printStackTrace(); + String className = javaFile.getName(); + if (!excludedClasses.contains(className)) { + try { + String source = new String(Files.readAllBytes(javaFile.toPath())); + ASTParser parser = ASTParser.newParser(AST.JLS_Latest); + parser.setSource(source.toCharArray()); + parser.setKind(ASTParser.K_COMPILATION_UNIT); + + CompilationUnit cu = (CompilationUnit) parser.createAST(null); + cu.accept(new MethodCollector(methodMap, excludedMethods)); + } catch (IOException e) { + System.err.println("[ERROR] Error reading file: " + javaFile.getName()); + e.printStackTrace(); + } } } } @@ -117,35 +170,30 @@ private static void checkForDatabaseCallsInLoops(Map method.accept(new ASTVisitor() { @Override public boolean visit(ForStatement node) { - System.out.println("For statement visited."); checkMethodInvocationsInLoop(node.getBody(), methodMap, callGraph); return super.visit(node); } @Override public boolean visit(WhileStatement node) { - System.out.println("While statement visited."); checkMethodInvocationsInLoop(node.getBody(), methodMap, callGraph); return super.visit(node); } @Override public boolean visit(DoStatement node) { - System.out.println("Do-While statement visited."); checkMethodInvocationsInLoop(node.getBody(), methodMap, callGraph); return super.visit(node); } @Override public boolean visit(EnhancedForStatement node) { - System.out.println("EnhancedForStatement statement visited."); checkMethodInvocationsInLoop(node.getBody(), methodMap, callGraph); return super.visit(node); } @Override public boolean visit(LambdaExpression node) { - System.out.println("Lambda statement visited."); checkMethodInvocationsInLoop(node.getBody(), methodMap, callGraph); return super.visit(node); } @@ -154,23 +202,19 @@ public boolean visit(LambdaExpression node) { public boolean visit(MethodInvocation node) { // Check for stream method calls like map, filter, etc. if (node.getName().getIdentifier().equals("map") || node.getName().getIdentifier().equals("forEach") || node.getName().getIdentifier().equals("stream")) { - System.out.println("Stream-related method visited: " + node.getName().getIdentifier()); node.accept(new ASTVisitor() { @Override public boolean visit(LambdaExpression lambdaExpression) { - System.out.println("Lambda statement visited inside stream operation."); checkMethodInvocationsInLoop(lambdaExpression.getBody(), methodMap, callGraph); return super.visit(lambdaExpression); } @Override public boolean visit(MethodInvocation innerNode) { - System.out.println("MethodInvocation inside lambda visited: " + innerNode.getName().getIdentifier()); String methodName = innerNode.getName().getIdentifier(); if (DB_METHODS.contains(methodName)) { reportAntiPattern(innerNode); } else if (methodMap.containsKey(methodName)) { - System.out.println("Tracing method: " + methodName); traceMethodCallsInLoop(methodName, methodMap, callGraph, new HashSet<>(), innerNode); } return super.visit(innerNode); @@ -188,11 +232,9 @@ private static void checkMethodInvocationsInLoop(ASTNode loopBody, Map(), node); } return super.visit(node); @@ -207,9 +249,7 @@ private static void traceMethodCallsInLoop(String methodName, Map callees = callGraph.get(methodName); if (callees != null) { for (String callee : callees) { - System.out.println("Callee: " + callee); if (DB_METHODS.contains(callee)) { - System.out.println("DB method detected in call chain: " + callee); reportAntiPattern(originalInvocation); } else if (methodMap.containsKey(callee)) { traceMethodCallsInLoop(callee, methodMap, callGraph, visited, originalInvocation); @@ -219,22 +259,47 @@ private static void traceMethodCallsInLoop(String methodName, Map methodMap; + private final List excludedMethods; - MethodCollector(Map methodMap) { + MethodCollector(Map methodMap, List excludedMethods) { this.methodMap = methodMap; + this.excludedMethods = excludedMethods; } @Override public boolean visit(MethodDeclaration node) { - methodMap.put(node.getName().toString(), node); + if (!excludedMethods.contains(node.getName().toString())) { + methodMap.put(node.getName().toString(), node); + } return super.visit(node); } } + + static class MethodNameVisitor extends ASTVisitor { + private final List methodNames = new ArrayList<>(); + private final List excludedMethods; + + public MethodNameVisitor(List excludedMethods) { + this.excludedMethods = excludedMethods; + } + + @Override + public boolean visit(MethodDeclaration node) { + if (!excludedMethods.contains(node.getName().getIdentifier())) { + methodNames.add(node.getName().getIdentifier()); + } + return super.visit(node); + } + + public List getMethodNames() { + return methodNames; + } + } } diff --git a/src/main/java/tum/dpid/config/AnalyzerConfig.java b/src/main/java/tum/dpid/config/AnalyzerConfig.java new file mode 100644 index 0000000..0307aa9 --- /dev/null +++ b/src/main/java/tum/dpid/config/AnalyzerConfig.java @@ -0,0 +1,55 @@ +package tum.dpid.config; + +import java.util.List; + +/** + * class that represents the json config values as a java class + */ +public class AnalyzerConfig { + + private String projectDirectory; + private String repositoryDirectory; + private List excludedClasses; + private List excludedMethods; + + public AnalyzerConfig() {} + + public AnalyzerConfig(String projectDirectory, String repositoryDirectory, List excludedClasses, List excludedMethods) { + this.projectDirectory = projectDirectory; + this.repositoryDirectory = repositoryDirectory; + this.excludedClasses = excludedClasses; + this.excludedMethods = excludedMethods; + } + + public String getProjectDirectory() { + return projectDirectory; + } + + public void setProjectDirectory(String projectDirectory) { + this.projectDirectory = projectDirectory; + } + + public String getRepositoryDirectory() { + return repositoryDirectory; + } + + public void setRepositoryDirectory(String repositoryDirectory) { + this.repositoryDirectory = repositoryDirectory; + } + + public List getExcludedClasses() { + return excludedClasses; + } + + public void setExcludedClasses(List excludedClasses) { + this.excludedClasses = excludedClasses; + } + + public List getExcludedMethods() { + return excludedMethods; + } + + public void setExcludedMethods(List excludedMethods) { + this.excludedMethods = excludedMethods; + } +} diff --git a/src/main/resources/tester/pom.xml b/src/main/resources/tester/pom.xml new file mode 100644 index 0000000..80fc946 --- /dev/null +++ b/src/main/resources/tester/pom.xml @@ -0,0 +1,17 @@ + + + 4.0.0 + + com.tum + tester + 1.0-SNAPSHOT + + + 17 + 17 + UTF-8 + + + \ No newline at end of file diff --git a/src/main/resources/tester/src/main/java/com/tum/Main.java b/src/main/resources/tester/src/main/java/com/tum/Main.java new file mode 100644 index 0000000..2da4d8b --- /dev/null +++ b/src/main/resources/tester/src/main/java/com/tum/Main.java @@ -0,0 +1,7 @@ +package com.tum; + +public class Main { + public static void main(String[] args) { + System.out.println("Hello world!"); + } +} \ No newline at end of file diff --git a/src/main/resources/tester/src/main/java/com/tum/loop/A.java b/src/main/resources/tester/src/main/java/com/tum/loop/A.java new file mode 100644 index 0000000..f0fcf74 --- /dev/null +++ b/src/main/resources/tester/src/main/java/com/tum/loop/A.java @@ -0,0 +1,14 @@ +package com.tum.loop; + +public class A { + protected void first() { + System.out.println("Method first"); + second(); + } + + private void second() { + System.out.println("Method second"); + B b = new B(); + b.third(); + } +} diff --git a/src/main/resources/tester/src/main/java/com/tum/loop/B.java b/src/main/resources/tester/src/main/java/com/tum/loop/B.java new file mode 100644 index 0000000..4bc2bed --- /dev/null +++ b/src/main/resources/tester/src/main/java/com/tum/loop/B.java @@ -0,0 +1,14 @@ +package com.tum.loop; + +public class B { + protected void third() { + System.out.println("Method third"); + for (int i = 0; i < 10; i++) { + callSave(); + } + } + + protected void callSave() { + System.out.println("Method callSave"); + } +} diff --git a/src/main/resources/tester/src/main/java/com/tum/loop/Main.java b/src/main/resources/tester/src/main/java/com/tum/loop/Main.java new file mode 100644 index 0000000..85373a5 --- /dev/null +++ b/src/main/resources/tester/src/main/java/com/tum/loop/Main.java @@ -0,0 +1,37 @@ +package com.tum.loop; + +public class Main { + + /** + * This package will be used to test method calls in loops.
+ *
+ * + *

A.first() will call the methods in the following order: + * + *

    + *
  • A.first() -> A.second() -> B.third() -> B.callSave() + *
+ * + *

A.second() will call the methods in the following order: + * + *

    + *
  • A.second() -> B.third() -> B.callSave() + *
+ * + *

B.third() will call the methods in the following order: + * + *

    + *
  • B.third() -> B.callSave() + *
+ * + *

Main.main() will call the methods in the following order: + * + *

    + *
  • A.first() -> A.second() -> B.third() -> B.callSave() + *
+ */ + public static void main(String[] args) { + A a = new A(); + a.first(); + } +} diff --git a/src/main/resources/tester/src/main/java/com/tum/methodchain/A.java b/src/main/resources/tester/src/main/java/com/tum/methodchain/A.java new file mode 100644 index 0000000..bf07109 --- /dev/null +++ b/src/main/resources/tester/src/main/java/com/tum/methodchain/A.java @@ -0,0 +1,12 @@ +package com.tum.methodchain; + +public class A { + protected static void first() { + System.out.println("Method first"); + B.second(); + } + + protected static void callOne() { + System.out.println("Method callOne"); + } +} diff --git a/src/main/resources/tester/src/main/java/com/tum/methodchain/B.java b/src/main/resources/tester/src/main/java/com/tum/methodchain/B.java new file mode 100644 index 0000000..04968d3 --- /dev/null +++ b/src/main/resources/tester/src/main/java/com/tum/methodchain/B.java @@ -0,0 +1,12 @@ +package com.tum.methodchain; + +public class B { + protected static void second() { + System.out.println("Method second"); + C.third(); + } + + protected static void callTwo() { + System.out.println("Method callTwo"); + } +} diff --git a/src/main/resources/tester/src/main/java/com/tum/methodchain/C.java b/src/main/resources/tester/src/main/java/com/tum/methodchain/C.java new file mode 100644 index 0000000..f689f54 --- /dev/null +++ b/src/main/resources/tester/src/main/java/com/tum/methodchain/C.java @@ -0,0 +1,11 @@ +package com.tum.methodchain; + +public class C { + protected static void third() { + System.out.println("Method third"); + } + + protected static void callThird() { + System.out.println("Method callThird"); + } +} diff --git a/src/main/resources/tester/src/main/java/com/tum/methodchain/Main.java b/src/main/resources/tester/src/main/java/com/tum/methodchain/Main.java new file mode 100644 index 0000000..1334962 --- /dev/null +++ b/src/main/resources/tester/src/main/java/com/tum/methodchain/Main.java @@ -0,0 +1,43 @@ +package com.tum.methodchain; + +public class Main { + + /** + * This package will be used to test method chain catches.
+ *
+ * + *

A.first() will call the methods in the following order: + * + *

    + *
  • A.first() -> B.second() -> C.third() + *
+ * + *

B.second() will call the methods in the following order: + * + *

    + *
  • B.second() -> C.third() + *
+ * + *

Main.main() will call the methods in the following order: + * + *

    + *
  • A.first() -> B.second() -> C.third() + *
  • B.second() -> C.third() + *
  • C.third() + *
  • A.callOne() + *
  • B.callTwo() + *
  • C.callThird() + *
+ */ + public static void main(String[] args) { + A.first(); + B.second(); + C.third(); + + System.out.println("-----------------"); + + A.callOne(); + B.callTwo(); + C.callThird(); + } +} diff --git a/src/main/resources/tester/src/main/java/com/tum/stream/A.java b/src/main/resources/tester/src/main/java/com/tum/stream/A.java new file mode 100644 index 0000000..9192896 --- /dev/null +++ b/src/main/resources/tester/src/main/java/com/tum/stream/A.java @@ -0,0 +1,31 @@ +package com.tum.stream; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class A { + + List list = + new ArrayList<>(Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h", "i", "k", "l", "m")); + + protected void first() { + System.out.println("Method first"); + List upperCaseList = second(); + System.out.println(upperCaseList.toString()); + } + + private List second() { + return list.stream().map(this::upperCase).collect(Collectors.toList()); + } + + private String upperCase(String s) { + save(s); + return s.toUpperCase(); + } + + private void save(String s) { + System.out.println("Method save: " + s); + } +} diff --git a/src/main/resources/tester/src/main/java/com/tum/stream/Main.java b/src/main/resources/tester/src/main/java/com/tum/stream/Main.java new file mode 100644 index 0000000..2baf5f8 --- /dev/null +++ b/src/main/resources/tester/src/main/java/com/tum/stream/Main.java @@ -0,0 +1,38 @@ +package com.tum.stream; + +public class Main { + + /** + * This package will be used to test method calls in loops. Method calls that were not written by + * the developer are ignored.
+ *
+ * + *

A.first() will call the methods in the following order: + * + *

    + *
  • A.first() -> A.second() -> A.upperCase() -> A.save() + *
+ * + *

A.second() will call the methods in the following order in a stream loop: + * + *

    + *
  • A.second() -> A.upperCase() -> A.save() + *
+ * + *

B.third() will call the methods in the following order: + * + *

    + *
  • A.upperCase() -> A.save() + *
+ * + *

Main.main() will call the methods in the following order: + * + *

    + *
  • A.first() -> A.second() -> A.upperCase() -> A.save() + *
+ */ + public static void main(String[] args) { + A a = new A(); + a.first(); + } +}