From ca422f1fdcf079fc0dd0fefcb5d7d6983e29f8de Mon Sep 17 00:00:00 2001 From: Scott Kurz Date: Tue, 25 Feb 2025 10:03:29 -0500 Subject: [PATCH 1/8] Ignore blank and null entries in PATH search for mvn/gradle execs Signed-off-by: Scott Kurz --- src/main/java/com/ibm/cldk/utils/BuildProject.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/ibm/cldk/utils/BuildProject.java b/src/main/java/com/ibm/cldk/utils/BuildProject.java index b76cc4b..9a027f0 100644 --- a/src/main/java/com/ibm/cldk/utils/BuildProject.java +++ b/src/main/java/com/ibm/cldk/utils/BuildProject.java @@ -29,7 +29,7 @@ public class BuildProject { * @return the maven command */ public static String getMavenCommand() { - String mvnSystemCommand = Arrays.stream(System.getenv("PATH").split(System.getProperty("path.separator"))).map(path -> new File(path, System.getProperty("os.name").toLowerCase().contains("windows") ? "mvn.cmd" : "mvn")).filter(File::exists).findFirst().map(File::getAbsolutePath).orElse(null); + String mvnSystemCommand = Arrays.stream(System.getenv("PATH").split(System.getProperty("path.separator"))).filter(Predicate.not(String::isBlank)).filter(Predicate.not(String::isEmpty)).map(path -> new File(path, System.getProperty("os.name").toLowerCase().contains("windows") ? "mvn.cmd" : "mvn")).filter(File::exists).findFirst().map(File::getAbsolutePath).orElse(null); File mvnWrapper = System.getProperty("os.name").toLowerCase().contains("windows") ? new File(projectRootPom, "mvnw.cmd") : new File(projectRootPom, "mvnw"); return commandExists(mvnWrapper.getAbsoluteFile()).getKey() ? mvnWrapper.getAbsoluteFile().toString() : mvnSystemCommand; } @@ -40,7 +40,7 @@ public static String getMavenCommand() { * @return the gradle command */ public static String getGradleCommand() { - String gradleSystemCommand = Arrays.stream(System.getenv("PATH").split(System.getProperty("path.separator"))).map(path -> new File(path, System.getProperty("os.name").toLowerCase().contains("windows") ? "gradle.bat" : "gradle")).filter(File::exists).findFirst().map(File::getAbsolutePath).orElse(null); + String gradleSystemCommand = Arrays.stream(System.getenv("PATH").split(System.getProperty("path.separator"))).filter(Predicate.not(String::isBlank)).filter(Predicate.not(String::isEmpty)).map(path -> new File(path, System.getProperty("os.name").toLowerCase().contains("windows") ? "gradle.bat" : "gradle")).filter(File::exists).findFirst().map(File::getAbsolutePath).orElse(null); File gradleWrapper = System.getProperty("os.name").toLowerCase().contains("windows") ? new File(projectRootPom, "gradlew.bat") : new File(projectRootPom, "gradlew"); return commandExists(gradleWrapper.getAbsoluteFile()).getKey() ? gradleWrapper.getAbsoluteFile() .toString() : gradleSystemCommand; From dcdeb2ca3be7315e739df266f20afa64337aa15a Mon Sep 17 00:00:00 2001 From: Rahul Krishna Date: Wed, 12 Mar 2025 17:54:27 -0400 Subject: [PATCH 2/8] Add a separate analysis level for call graph only. Signed-off-by: Rahul Krishna --- gradle.properties | 2 +- src/main/java/com/ibm/cldk/CodeAnalyzer.java | 6 +- src/main/java/com/ibm/cldk/SymbolTable.java | 2 - .../com/ibm/cldk/SystemDependencyGraph.java | 105 ++---------------- .../ibm/cldk/CodeAnalyzerIntegrationTest.java | 4 +- 5 files changed, 14 insertions(+), 105 deletions(-) diff --git a/gradle.properties b/gradle.properties index 7ad95fa..2e81983 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=2.3.0 +version=2.3.1 diff --git a/src/main/java/com/ibm/cldk/CodeAnalyzer.java b/src/main/java/com/ibm/cldk/CodeAnalyzer.java index 1a1f54f..057fa5e 100644 --- a/src/main/java/com/ibm/cldk/CodeAnalyzer.java +++ b/src/main/java/com/ibm/cldk/CodeAnalyzer.java @@ -86,8 +86,8 @@ public class CodeAnalyzer implements Runnable { public static String projectRootPom; @Option(names = { "-a", - "--analysis-level" }, description = "Level of analysis to perform. Options: 1 (for just symbol table) or 2 (for call graph). Default: 1") - private static int analysisLevel = 1; + "--analysis-level" }, description = "Level of analysis to perform. Options: 1 (for just symbol table); 2 (for call graph). Default: 1") + public static int analysisLevel = 1; @Option(names = { "--include-test-classes" }, hidden = true, description = "Print logs to console.") public static boolean includeTestClasses = false; @@ -209,7 +209,7 @@ private static void analyze() throws Exception { // Is noBuild is true, we will not build the project build = noBuild ? null : build; List sdgEdges = SystemDependencyGraph.construct(input, dependencies, build); - combinedJsonObject.add("system_dependency_graph", gson.toJsonTree(sdgEdges)); + combinedJsonObject.add("call_graph", gson.toJsonTree(sdgEdges)); } } // Cleanup library dependencies directory diff --git a/src/main/java/com/ibm/cldk/SymbolTable.java b/src/main/java/com/ibm/cldk/SymbolTable.java index 8783594..b1219e3 100644 --- a/src/main/java/com/ibm/cldk/SymbolTable.java +++ b/src/main/java/com/ibm/cldk/SymbolTable.java @@ -71,7 +71,6 @@ import com.ibm.cldk.javaee.utils.enums.CRUDOperationType; import com.ibm.cldk.javaee.utils.enums.CRUDQueryType; import com.ibm.cldk.utils.Log; -import org.jetbrains.annotations.NotNull; @SuppressWarnings("rawtypes") public class SymbolTable { @@ -241,7 +240,6 @@ private static JavaCompilationUnit processCompilationUnit(CompilationUnit parseR } else { // TODO: handle AnnotationDeclaration, RecordDeclaration // set the common type attributes only - Log.warn("Found unsupported type declaration: " + typeDecl.toString()); typeNode = new com.ibm.cldk.entities.Type(); } diff --git a/src/main/java/com/ibm/cldk/SystemDependencyGraph.java b/src/main/java/com/ibm/cldk/SystemDependencyGraph.java index 2bed573..ff8b988 100644 --- a/src/main/java/com/ibm/cldk/SystemDependencyGraph.java +++ b/src/main/java/com/ibm/cldk/SystemDependencyGraph.java @@ -52,6 +52,7 @@ import java.util.function.Supplier; import java.util.stream.Collectors; +import static com.ibm.cldk.CodeAnalyzer.analysisLevel; import static com.ibm.cldk.utils.AnalysisUtils.*; @@ -114,76 +115,13 @@ private static JSONExporter getGraphExporter( /** * Convert SDG to a formal Graph representation. * - * @param entryPoints - * @param sdg * @param callGraph - * @param edgeLabels * @return */ - private static org.jgrapht.Graph buildGraph( - Supplier> entryPoints, - Graph sdg, CallGraph callGraph, - BiFunction edgeLabels) { + private static org.jgrapht.Graph buildOnlyCallGraph(CallGraph callGraph) { org.jgrapht.Graph graph = new DefaultDirectedGraph<>( AbstractGraphEdge.class); - - // We'll use forward and backward search on the DFS to identify which CFG nodes - // are dominant - // This is a forward DFS search (or exit time first search) - int dfsNumber = 0; - Map dfsFinish = HashMapFactory.make(); - Iterator search = DFS.iterateFinishTime(sdg, entryPoints.get()); - - while (search.hasNext()) { - dfsFinish.put(search.next(), dfsNumber++); - } - - // This is a reverse DFS search (or entry time first search) - int reverseDfsNumber = 0; - Map dfsStart = HashMapFactory.make(); - Iterator reverseSearch = DFS.iterateDiscoverTime(sdg, entryPoints.get()); - - while (reverseSearch.hasNext()) { - dfsStart.put(reverseSearch.next(), reverseDfsNumber++); - } - - // Populate graph - sdg.stream() - .filter(dfsFinish::containsKey) - .sorted(Comparator.comparingInt(dfsFinish::get)) - .forEach(p -> sdg.getSuccNodes(p).forEachRemaining(s -> { - if (dfsFinish.containsKey(s) - && dfsStart.get(p) != null && dfsStart.get(s) != null - && !((dfsStart.get(p) >= dfsStart.get(s)) - && (dfsFinish.get(p) <= dfsFinish.get(s))) - && !p.getNode().getMethod().equals(s.getNode().getMethod())) { - - // Add the source nodes to the graph as vertices - Map source = Optional.ofNullable(getCallableFromSymbolTable(p.getNode().getMethod())).orElseGet(() -> createAndPutNewCallableInSymbolTable(p.getNode().getMethod())); - // Add the target nodes to the graph as vertices - Map target = Optional.ofNullable(getCallableFromSymbolTable(s.getNode().getMethod())).orElseGet(() -> createAndPutNewCallableInSymbolTable(s.getNode().getMethod())); - - if (source != null && target != null) { - CallableVertex source_vertex = new CallableVertex(source); - CallableVertex target_vertex = new CallableVertex(target); - graph.addVertex(source_vertex); - graph.addVertex(target_vertex); - String edgeType = edgeLabels.apply(p, s); - SystemDepEdge graphEdge = new SystemDepEdge(p, s, edgeType); - SystemDepEdge cgEdge = (SystemDepEdge) graph.getEdge(source_vertex, target_vertex); - if (cgEdge == null || !cgEdge.equals(graphEdge)) { - graph.addEdge( - source_vertex, - target_vertex, - graphEdge); - } else { - graphEdge.incrementWeight(); - } - } - } - })); - callGraph.getEntrypointNodes() .forEach(p -> { // Get call statements that may execute in a given method @@ -264,7 +202,6 @@ public static List construct( try { System.setOut(new PrintStream(NullOutputStream.INSTANCE)); System.setErr(new PrintStream(NullOutputStream.INSTANCE)); -// builder = Util.makeRTABuilder(new JavaLanguage(), options, cache, cha); builder = Util.makeRTABuilder(options, cache, cha); callGraph = builder.makeCallGraph(options, null); } finally { @@ -283,40 +220,14 @@ public static List construct( } }); + org.jgrapht.Graph graph; + + graph = buildOnlyCallGraph(callGraph); - // Build SDG graph - Log.info("Building System Dependency Graph."); - SDG sdg = new SDG<>( - callGraph, - builder.getPointerAnalysis(), - new ModRef<>(), - Slicer.DataDependenceOptions.NO_HEAP_NO_EXCEPTIONS, - Slicer.ControlDependenceOptions.NO_EXCEPTIONAL_EDGES); - - // Prune the Graph to keep only application classes. - Graph prunedGraph = GraphSlicer.prune(sdg, - statement -> (statement.getNode() - .getMethod() - .getDeclaringClass() - .getClassLoader() - .getReference() - .equals(ClassLoaderReference.Application)) - ); - - // A supplier to get entries - Supplier> sdgEntryPointsSupplier = () -> callGraph.getEntrypointNodes().stream() - .map(n -> (Statement) new MethodEntryStatement(n)).iterator(); - - org.jgrapht.Graph sdgGraph = buildGraph( - sdgEntryPointsSupplier, - prunedGraph, callGraph, - (p, s) -> String.valueOf(sdg.getEdgeLabels(p, s).iterator().next()) - ); - - List edges = sdgGraph.edgeSet().stream() + List edges = graph.edgeSet().stream() .map(abstractGraphEdge -> { - CallableVertex source = sdgGraph.getEdgeSource(abstractGraphEdge); - CallableVertex target = sdgGraph.getEdgeTarget(abstractGraphEdge); + CallableVertex source = graph.getEdgeSource(abstractGraphEdge); + CallableVertex target = graph.getEdgeTarget(abstractGraphEdge); if (abstractGraphEdge instanceof CallEdge) { return new CallDependency(source, target, abstractGraphEdge); } else { diff --git a/src/test/java/com/ibm/cldk/CodeAnalyzerIntegrationTest.java b/src/test/java/com/ibm/cldk/CodeAnalyzerIntegrationTest.java index 2fcbc83..1a45910 100644 --- a/src/test/java/com/ibm/cldk/CodeAnalyzerIntegrationTest.java +++ b/src/test/java/com/ibm/cldk/CodeAnalyzerIntegrationTest.java @@ -143,8 +143,8 @@ void callGraphShouldHaveKnownEdges() throws Exception { // Read the output JSON Gson gson = new Gson(); JsonObject jsonObject = gson.fromJson(runCodeAnalyzerOnCallGraphTest.getStdout(), JsonObject.class); - JsonArray systemDepGraph = jsonObject.getAsJsonArray("system_dependency_graph"); - Assertions.assertTrue(StreamSupport.stream(systemDepGraph.spliterator(), false) + JsonArray callGraph = jsonObject.getAsJsonArray("call_graph"); + Assertions.assertTrue(StreamSupport.stream(callGraph.spliterator(), false) .map(JsonElement::getAsJsonObject) .anyMatch(entry -> "CALL_DEP".equals(entry.get("type").getAsString()) && From 545fe762ee34cb2e0acc9f97034e49b8ac9145a5 Mon Sep 17 00:00:00 2001 From: Rahul Krishna Date: Wed, 19 Mar 2025 14:13:24 -0400 Subject: [PATCH 3/8] Update copy depenedencies rule to ensure the copied JAR files have write/delete permissions. Don't clean before compile. Signed-off-by: Rahul Krishna --- build.gradle | 4 ++-- gradle.properties | 2 +- .../java/com/ibm/cldk/utils/BuildProject.java | 15 +++++++-------- .../ibm/cldk/utils/ProjectDirectoryScanner.java | 5 ++--- .../com/ibm/cldk/CodeAnalyzerIntegrationTest.java | 7 ++++--- 5 files changed, 16 insertions(+), 17 deletions(-) diff --git a/build.gradle b/build.gradle index 15f3a21..2618502 100644 --- a/build.gradle +++ b/build.gradle @@ -124,8 +124,8 @@ dependencies { implementation('com.github.javaparser:javaparser-core:3.26.3') // TestContainers - testImplementation 'org.testcontainers:testcontainers:1.19.3' - testImplementation 'org.testcontainers:junit-jupiter:1.19.3' + testImplementation 'org.testcontainers:testcontainers:1.20.6' + testImplementation 'org.testcontainers:junit-jupiter:1.20.6' // JUnit 5 testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.1' diff --git a/gradle.properties b/gradle.properties index 2e81983..2928d19 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=2.3.1 +version=2.3.2 diff --git a/src/main/java/com/ibm/cldk/utils/BuildProject.java b/src/main/java/com/ibm/cldk/utils/BuildProject.java index b76cc4b..65d2573 100644 --- a/src/main/java/com/ibm/cldk/utils/BuildProject.java +++ b/src/main/java/com/ibm/cldk/utils/BuildProject.java @@ -56,8 +56,7 @@ public static String getGradleCommand() { } } - private static final String GRADLE_DEPENDENCIES_TASK = "allprojects { afterEvaluate { project -> task downloadDependencies(type: Copy) {\n" + " def configs = project.configurations.findAll { it.canBeResolved }\n\n" + " dependsOn configs\n" + " from configs\n" + " into project.hasProperty('outputDir') ? project.property('outputDir') : \"${project.buildDir}/libs\"\n\n" + " doFirst {\n" + " println \"Downloading dependencies for project ${project.name} to: ${destinationDir}\"\n" + " configs.each { config ->\n" + " println \"Configuration: ${config.name}\"\n" + " config.resolvedConfiguration.resolvedArtifacts.each { artifact ->\n" + " println \"\t${artifact.moduleVersion.id}:${artifact.extension}\"\n" + " }\n" + " }\n" + " }\n" + " }\n" + " }\n" + "}"; - + private static final String GRADLE_DEPENDENCIES_TASK = "allprojects { afterEvaluate { project -> task downloadDependencies(type: Copy) { def configs = project.configurations.findAll { it.canBeResolved }; dependsOn configs; from configs; into project.hasProperty('outputDir') ? project.property('outputDir') : \"${project.buildDir}/libs\"; eachFile { fileCopyDetails -> fileCopyDetails.file.setWritable(true) }; doFirst { println \"Downloading dependencies for project ${project.name} to: ${destinationDir}\"; configs.each { config -> println \"Configuration: ${config.name}\"; config.resolvedConfiguration.resolvedArtifacts.each { artifact -> println \"\\t${artifact.moduleVersion.id}:${artifact.extension}\" } } } } } }"; private static AbstractMap.SimpleEntry commandExists(File command) { StringBuilder output = new StringBuilder(); if (!command.exists()) { @@ -143,10 +142,10 @@ private static boolean mavenBuild(String projectPath) { String[] mavenCommand; if (includeTestClasses) { Log.warn("Hidden flag `--include-test-classes` is turned on. We'll including test classes in WALA analysis"); - mavenCommand = new String[]{MAVEN_CMD, "clean", "test-compile", "-f", projectPath + "/pom.xml", "-B", "-V", "-e", "-Drat.skip", "-Dfindbugs.skip", "-Dcheckstyle.skip", "-Dpmd.skip=true", "-Dspotbugs.skip", "-Denforcer.skip", "-Dmaven.javadoc.skip", "-DskipTests", "-Dmaven.test.skip.exec", "-Dlicense.skip=true", "-Drat.skip=true", "-Dspotless.check.skip=true"}; + mavenCommand = new String[]{MAVEN_CMD, "test-compile", "-f", projectPath + "/pom.xml", "-B", "-V", "-e", "-Drat.skip", "-Dfindbugs.skip", "-Dcheckstyle.skip", "-Dpmd.skip=true", "-Dspotbugs.skip", "-Denforcer.skip", "-Dmaven.javadoc.skip", "-DskipTests", "-Dmaven.test.skip.exec", "-Dlicense.skip=true", "-Drat.skip=true", "-Dspotless.check.skip=true"}; } else - mavenCommand = new String[]{MAVEN_CMD, "clean", "compile", "-f", projectPath + "/pom.xml", "-B", "-V", "-e", "-Drat.skip", "-Dfindbugs.skip", "-Dcheckstyle.skip", "-Dpmd.skip=true", "-Dspotbugs.skip", "-Denforcer.skip", "-Dmaven.javadoc.skip", "-DskipTests", "-Dmaven.test.skip.exec", "-Dlicense.skip=true", "-Drat.skip=true", "-Dspotless.check.skip=true"}; + mavenCommand = new String[]{MAVEN_CMD, "compile", "-f", projectPath + "/pom.xml", "-B", "-V", "-e", "-Drat.skip", "-Dfindbugs.skip", "-Dcheckstyle.skip", "-Dpmd.skip=true", "-Dspotbugs.skip", "-Denforcer.skip", "-Dmaven.javadoc.skip", "-DskipTests", "-Dmaven.test.skip.exec", "-Dlicense.skip=true", "-Drat.skip=true", "-Dspotless.check.skip=true"}; return buildWithTool(mavenCommand); } @@ -155,14 +154,14 @@ public static boolean gradleBuild(String projectPath) { // Adjust Gradle command as needed String[] gradleCommand; if (GRADLE_CMD.equals("gradlew") || GRADLE_CMD.equals("gradlew.bat")) { - gradleCommand = new String[]{projectPath + File.separator + GRADLE_CMD, "clean", "compileJava", "-p", projectPath}; + gradleCommand = new String[]{projectPath + File.separator + GRADLE_CMD, "compileJava", "-p", projectPath}; } else { if (includeTestClasses) { Log.warn("Hidden flag `--include-test-classes` is turned on. We'll including test classes in WALA analysis"); - gradleCommand = new String[]{GRADLE_CMD, "clean", "compileTestJava", "-p", projectPath}; + gradleCommand = new String[]{GRADLE_CMD, "compileTestJava", "-p", projectPath}; } else - gradleCommand = new String[]{GRADLE_CMD, "clean", "compileJava", "-p", projectPath}; + gradleCommand = new String[]{GRADLE_CMD, "compileJava", "-p", projectPath}; } return buildWithTool(gradleCommand); } @@ -240,7 +239,7 @@ public static boolean downloadLibraryDependencies(String projectPath, String pro )); } Log.info("Found pom.xml in the project directory. Using Maven to download dependencies."); - String[] mavenCommand = {MAVEN_CMD, "--no-transfer-progress", "-f", Paths.get(projectRoot, "pom.xml").toAbsolutePath().toString(), "dependency:copy-dependencies", "-DoutputDirectory=" + libDownloadPath.toString()}; + String[] mavenCommand = {MAVEN_CMD, "--no-transfer-progress", "-f", Paths.get(projectRoot, "pom.xml").toAbsolutePath().toString(), "dependency:copy-dependencies", "-DoutputDirectory=" + libDownloadPath.toString(), "-Doverwrite=true"}; return buildWithTool(mavenCommand); } else if (new File(projectRoot, "build.gradle").exists() || new File(projectRoot, "build.gradle.kts").exists()) { libDownloadPath = Paths.get(projectPath, "build", LIB_DEPS_DOWNLOAD_DIR).toAbsolutePath(); diff --git a/src/main/java/com/ibm/cldk/utils/ProjectDirectoryScanner.java b/src/main/java/com/ibm/cldk/utils/ProjectDirectoryScanner.java index c10752e..9a64edf 100644 --- a/src/main/java/com/ibm/cldk/utils/ProjectDirectoryScanner.java +++ b/src/main/java/com/ibm/cldk/utils/ProjectDirectoryScanner.java @@ -9,15 +9,14 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static com.ibm.cldk.CodeAnalyzer.includeTestClasses; - public class ProjectDirectoryScanner { public static List classFilesStream(String projectPath) throws IOException { Path projectDir = Paths.get(projectPath).toAbsolutePath(); Log.info("Finding *.class files in " + projectDir); if (Files.exists(projectDir)) { try (Stream paths = Files.walk(projectDir)) { - return paths.filter(file -> !Files.isDirectory(file) && file.toString().endsWith(".class")).collect(Collectors.toList()); + return paths.filter(file -> !Files.isDirectory(file) && file.toString().endsWith(".class")) + .collect(Collectors.toList()); } } return null; diff --git a/src/test/java/com/ibm/cldk/CodeAnalyzerIntegrationTest.java b/src/test/java/com/ibm/cldk/CodeAnalyzerIntegrationTest.java index 1a45910..da29af1 100644 --- a/src/test/java/com/ibm/cldk/CodeAnalyzerIntegrationTest.java +++ b/src/test/java/com/ibm/cldk/CodeAnalyzerIntegrationTest.java @@ -4,9 +4,9 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.Assertions; import org.testcontainers.containers.GenericContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -31,6 +31,7 @@ public class CodeAnalyzerIntegrationTest { static String codeanalyzerVersion; static final String javaVersion = "17"; static String javaHomePath; + static { // Build project first try { @@ -120,8 +121,8 @@ void shouldBeAbleToRunCodeAnalyzer() throws Exception { var runCodeAnalyzerJar = container.execInContainer( "bash", "-c", String.format("export JAVA_HOME=%s && java -jar /opt/jars/codeanalyzer-%s.jar --help", - javaHomePath, codeanalyzerVersion - )); + javaHomePath, codeanalyzerVersion + )); Assertions.assertEquals(0, runCodeAnalyzerJar.getExitCode(), "Command should execute successfully"); From 4a4108d8107ec2a7e9fc4e673623d36ca74bfb28 Mon Sep 17 00:00:00 2001 From: Rahul Krishna Date: Wed, 16 Apr 2025 13:36:43 -0400 Subject: [PATCH 4/8] Fix issue 132 and refactor entrypoint detection logic. Signed-off-by: Rahul Krishna --- gradle.properties | 2 +- src/main/java/com/ibm/cldk/SymbolTable.java | 222 +----------------- .../cldk/javaee/EntrypointsFinderFactory.java | 13 +- .../javaee/camel/CamelEntrypointFinder.java | 42 +++- .../jakarta/JakartaEntrypointFinder.java | 43 +++- .../javaee/jax/JaxRsEntrypointFinder.java | 42 ++++ .../javaee/spring/SpringEntrypointFinder.java | 65 ++++- .../javaee/struts/StrutsEntrypointFinder.java | 59 ++++- .../interfaces/AbstractEntrypointFinder.java | 10 +- .../java/com/ibm/cldk/utils/BuildProject.java | 2 +- .../ibm/cldk/CodeAnalyzerIntegrationTest.java | 1 + .../resources/test-applications/.gitignore | 3 + 12 files changed, 258 insertions(+), 246 deletions(-) create mode 100644 src/main/java/com/ibm/cldk/javaee/jax/JaxRsEntrypointFinder.java diff --git a/gradle.properties b/gradle.properties index 2928d19..5016995 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=2.3.2 +version=2.3.3 diff --git a/src/main/java/com/ibm/cldk/SymbolTable.java b/src/main/java/com/ibm/cldk/SymbolTable.java index b1219e3..d5b5bc8 100644 --- a/src/main/java/com/ibm/cldk/SymbolTable.java +++ b/src/main/java/com/ibm/cldk/SymbolTable.java @@ -21,6 +21,7 @@ import com.github.javaparser.ast.comments.JavadocComment; import com.github.javaparser.ast.nodeTypes.NodeWithJavadoc; import com.github.javaparser.ast.stmt.*; +import com.ibm.cldk.javaee.EntrypointsFinderFactory; import org.apache.commons.lang3.tuple.Pair; import com.github.javaparser.JavaParser; @@ -72,7 +73,7 @@ import com.ibm.cldk.javaee.utils.enums.CRUDQueryType; import com.ibm.cldk.utils.Log; -@SuppressWarnings("rawtypes") +@SuppressWarnings({"unchecked", "rawtypes"}) public class SymbolTable { private static JavaSymbolSolver javaSymbolSolver; @@ -466,149 +467,8 @@ private static Map mapRecordConstructorDefaults(RecordDeclaratio } private static boolean isEntryPointClass(TypeDeclaration typeDecl) { - return isSpringEntrypointClass(typeDecl) || isStrutsEntryPointClass(typeDecl) - || isCamelEntryPointClass(typeDecl) || isJaxRSEntrypointClass(typeDecl) - || isJakartaServletEntryPointClass(typeDecl); - - } - - private static boolean isSpringEntrypointClass(TypeDeclaration typeDeclaration) { - List annotations = typeDeclaration.getAnnotations(); - for (AnnotationExpr annotation : annotations) { - // Existing checks - if (annotation.getNameAsString().contains("RestController") - || annotation.getNameAsString().contains("Controller") - || annotation.getNameAsString().contains("HandleInterceptor") - || annotation.getNameAsString().contains("HandlerInterceptor")) { - return true; - } - - // Spring Boot specific checks - if (annotation.getNameAsString().contains("SpringBootApplication") - || annotation.getNameAsString().contains("Configuration") - || annotation.getNameAsString().contains("Component") - || annotation.getNameAsString().contains("Service") - || annotation.getNameAsString().contains("Repository")) { - return true; - } - } - - // Check if class implements CommandLineRunner or ApplicationRunner - if (typeDeclaration instanceof ClassOrInterfaceDeclaration) { - ClassOrInterfaceDeclaration classDecl = (ClassOrInterfaceDeclaration) typeDeclaration; - for (ClassOrInterfaceType implementedType : classDecl.getImplementedTypes()) { - String typeName = implementedType.getNameAsString(); - if (typeName.equals("CommandLineRunner") || typeName.equals("ApplicationRunner")) { - return true; - } - } - } - - return false; - } - - private static boolean isJaxRSEntrypointClass(TypeDeclaration typeDeclaration) { - List callableDeclarations = typeDeclaration.findAll(CallableDeclaration.class); - for (CallableDeclaration callableDeclaration : callableDeclarations) { - if (callableDeclaration.getAnnotations().stream().anyMatch(a -> a.toString().contains("POST")) - || callableDeclaration.getAnnotations().stream().anyMatch(a -> a.toString().contains("PUT")) - || callableDeclaration.getAnnotations().stream().anyMatch(a -> a.toString().contains("GET")) - || callableDeclaration.getAnnotations().stream().anyMatch(a -> a.toString().contains("HEAD")) - || callableDeclaration.getAnnotations().stream().anyMatch(a -> a.toString().contains("DELETE"))) { - return true; - } - } - - return false; - } - - private static boolean isStrutsEntryPointClass(TypeDeclaration typeDeclaration) { - if (!(typeDeclaration instanceof ClassOrInterfaceDeclaration)) { - return false; - } - - ClassOrInterfaceDeclaration classDecl = (ClassOrInterfaceDeclaration) typeDeclaration; - - // Check class-level Struts annotations - if (classDecl.getAnnotations().stream().anyMatch(a -> a.getNameAsString().contains("Action") - || a.getNameAsString().contains("Namespace") || a.getNameAsString().contains("InterceptorRef"))) { - return true; - } - - // Check if extends ActionSupport or implements Interceptor - try { - ResolvedReferenceTypeDeclaration resolved = classDecl.resolve(); - return resolved.getAllAncestors().stream().anyMatch(ancestor -> { - String name = ancestor.getQualifiedName(); - return name.contains("ActionSupport") || name.contains("Interceptor"); - }); - } catch (UnsolvedSymbolException e) { - Log.warn("Could not resolve class: " + e.getMessage()); - } - - return false; - } - - private static boolean isCamelEntryPointClass(TypeDeclaration typeDeclaration) { - if (!(typeDeclaration instanceof ClassOrInterfaceDeclaration)) { - return false; - } - - ClassOrInterfaceDeclaration classDecl = (ClassOrInterfaceDeclaration) typeDeclaration; - - // Check Camel class annotations - if (classDecl.getAnnotations().stream().anyMatch(a -> a.getNameAsString().contains("Component"))) { - return true; - } - - // Check Camel parent classes and interfaces - try { - ResolvedReferenceTypeDeclaration resolved = classDecl.resolve(); - return resolved.getAllAncestors().stream().anyMatch(ancestor -> { - String name = ancestor.getQualifiedName(); - return name.contains("RouteBuilder") || name.contains("Processor") || name.contains("Producer") - || name.contains("Consumer"); - }); - } catch (UnsolvedSymbolException e) { - Log.warn("Could not resolve class: " + e.getMessage()); - } - - return false; - } - - /** - * Checks if the given class is a Jakarta Servlet entry point class. - * - * @param typeDecl Type declaration to check - * @return True if the class is a Jakarta Servlet entry point class, false - * otherwise - */ - private static boolean isJakartaServletEntryPointClass(TypeDeclaration typeDecl) { - if (!(typeDecl instanceof ClassOrInterfaceDeclaration)) { - return false; - } - - ClassOrInterfaceDeclaration classDecl = (ClassOrInterfaceDeclaration) typeDecl; - - // Check annotations - if (classDecl.getAnnotations().stream() - .anyMatch(a -> a.getNameAsString().contains("WebServlet") || a.getNameAsString().contains("WebFilter") - || a.getNameAsString().contains("WebListener") || a.getNameAsString().contains("ServerEndpoint") - || a.getNameAsString().contains("MessageDriven") - || a.getNameAsString().contains("WebService"))) { - return true; - } - - // Check types - return classDecl.getExtendedTypes().stream() - .map(ClassOrInterfaceType::getNameAsString) - .anyMatch(name -> name.contains("HttpServlet") || name.contains("GenericServlet")) - || classDecl.getImplementedTypes().stream().map( - ClassOrInterfaceType::asString).anyMatch( - name -> name.contains("ServletContextListener") - || name.contains("HttpSessionListener") - || name.contains("ServletRequestListener") - || name.contains("MessageListener")); + return EntrypointsFinderFactory.getEntrypointFinders() + .anyMatch(finder -> finder.isEntrypointClass(typeDecl)); } /** @@ -752,80 +612,10 @@ private static Pair processCallableDeclaration(CallableDeclara } private static boolean isEntryPointMethod(CallableDeclaration callableDecl) { - return isServletEntrypointMethod(callableDecl) || isJaxRsEntrypointMethod(callableDecl) - || isSpringEntrypointMethod(callableDecl) | isStrutsEntryPointMethod(callableDecl); - } - - @SuppressWarnings("unchecked") - private static boolean isServletEntrypointMethod(CallableDeclaration callableDecl) { - return ((NodeList) callableDecl.getParameters()).stream() - .anyMatch(parameter -> parameter.getType().asString().contains("HttpServletRequest") || - parameter.getType().asString().contains("HttpServletResponse")) - && callableDecl.getAnnotations().stream().anyMatch(a -> a.toString().contains("Override")); - } - - @SuppressWarnings("unchecked") - private static boolean isJaxRsEntrypointMethod(CallableDeclaration callableDecl) { - return callableDecl.getAnnotations().stream() - .anyMatch(a -> a.toString().contains("POST") || a.toString().contains("PUT") - || a.toString().contains("GET") || a.toString().contains("HEAD") - || a.toString().contains("DELETE")); + return EntrypointsFinderFactory.getEntrypointFinders() + .anyMatch(finder -> finder.isEntrypointMethod(callableDecl)); } - @SuppressWarnings("unchecked") - private static boolean isSpringEntrypointMethod(CallableDeclaration callableDecl) { - return callableDecl.getAnnotations().stream().anyMatch(a -> a.toString().contains("GetMapping") || - a.toString().contains("PostMapping") || - a.toString().contains("PutMapping") || - a.toString().contains("DeleteMapping") || - a.toString().contains("PatchMapping") || - a.toString().contains("RequestMapping") || - a.toString().contains("EventListener") || - a.toString().contains("Scheduled") || - a.toString().contains("KafkaListener") || - a.toString().contains("RabbitListener") || - a.toString().contains("JmsListener") || - a.toString().contains("PreAuthorize") || - a.toString().contains("PostAuthorize") || - a.toString().contains("PostConstruct") || - a.toString().contains("PreDestroy") || - a.toString().contains("Around") || - a.toString().contains("Before") || - a.toString().contains("After") || - a.toString().contains("JobScope") || - a.toString().contains("StepScope")); - } - - @SuppressWarnings("unchecked") - private static boolean isStrutsEntryPointMethod(CallableDeclaration callableDecl) { - // First check if this method is in a Struts Action class - Optional parentNode = callableDecl.getParentNode(); - if (parentNode.isEmpty() || !(parentNode.get() instanceof ClassOrInterfaceDeclaration)) { - return false; - } - - ClassOrInterfaceDeclaration parentClass = (ClassOrInterfaceDeclaration) parentNode.get(); - if (parentClass.getExtendedTypes().stream() - .map(ClassOrInterfaceType::asString) - .noneMatch(type -> type.contains("ActionSupport") || type.contains("Action"))) - return false; - - return callableDecl.getAnnotations().stream().anyMatch(a -> a.toString().contains("Action") || - a.toString().contains("Actions") || - a.toString().contains("ValidationMethod") || - a.toString().contains("InputConfig") || - a.toString().contains("BeforeResult") || - a.toString().contains("After") || - a.toString().contains("Before") || - a.toString().contains("Result") || - a.toString().contains("Results")) || callableDecl.getNameAsString().equals("execute"); // Check for - // execute() - // method which - // is the default - // action method - // of the Action - // class - } /** * Computes cyclomatic complexity for the given callable. diff --git a/src/main/java/com/ibm/cldk/javaee/EntrypointsFinderFactory.java b/src/main/java/com/ibm/cldk/javaee/EntrypointsFinderFactory.java index 5b08129..cee58f2 100644 --- a/src/main/java/com/ibm/cldk/javaee/EntrypointsFinderFactory.java +++ b/src/main/java/com/ibm/cldk/javaee/EntrypointsFinderFactory.java @@ -1,11 +1,10 @@ package com.ibm.cldk.javaee; -import com.ibm.cldk.javaee.utils.interfaces.AbstractCRUDFinder; +import com.ibm.cldk.javaee.camel.CamelEntrypointFinder; +import com.ibm.cldk.javaee.jax.JaxRsEntrypointFinder; +import com.ibm.cldk.javaee.spring.SpringEntrypointFinder; import com.ibm.cldk.javaee.utils.interfaces.AbstractEntrypointFinder; -import com.ibm.cldk.javaee.jakarta.JPACRUDFinder; import com.ibm.cldk.javaee.jakarta.JakartaEntrypointFinder; -import com.ibm.cldk.javaee.jdbc.JDBCCRUDFinder; -import com.ibm.cldk.javaee.spring.SpringCRUDFinder; import com.ibm.cldk.javaee.struts.StrutsEntrypointFinder; import org.apache.commons.lang3.NotImplementedException; @@ -17,7 +16,7 @@ public static AbstractEntrypointFinder getEntrypointFinder(String framework) { case "jakarta": return new JakartaEntrypointFinder(); case "spring": - return new StrutsEntrypointFinder(); + return new SpringEntrypointFinder(); case "camel": throw new NotImplementedException("Camel CRUD finder not implemented yet"); case "struts": @@ -27,7 +26,7 @@ public static AbstractEntrypointFinder getEntrypointFinder(String framework) { } } - public static Stream getEntrypointFinders() { - return Stream.of(new JPACRUDFinder(), new SpringCRUDFinder(), new JDBCCRUDFinder()); + public static Stream getEntrypointFinders() { + return Stream.of(new JakartaEntrypointFinder(), new StrutsEntrypointFinder(), new SpringEntrypointFinder(), new CamelEntrypointFinder(), new JaxRsEntrypointFinder()); } } diff --git a/src/main/java/com/ibm/cldk/javaee/camel/CamelEntrypointFinder.java b/src/main/java/com/ibm/cldk/javaee/camel/CamelEntrypointFinder.java index 4bd9f2e..82b58ad 100644 --- a/src/main/java/com/ibm/cldk/javaee/camel/CamelEntrypointFinder.java +++ b/src/main/java/com/ibm/cldk/javaee/camel/CamelEntrypointFinder.java @@ -1,17 +1,55 @@ package com.ibm.cldk.javaee.camel; +import com.github.javaparser.ast.body.CallableDeclaration; +import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; +import com.github.javaparser.ast.body.TypeDeclaration; +import com.github.javaparser.resolution.UnsolvedSymbolException; +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; import com.ibm.cldk.javaee.utils.interfaces.AbstractEntrypointFinder; +import com.ibm.cldk.utils.Log; import com.ibm.cldk.utils.annotations.NotImplemented; @NotImplemented(comment = "This class is not implemented yet. Leaving this here to refactor entrypoint detection.") public class CamelEntrypointFinder extends AbstractEntrypointFinder { + /** + * Detect if the method is an entrypoint. + * + * @param typeDecl@return True if the method is an entrypoint, false otherwise. + */ @Override - public boolean isEntrypointClass(String receiverType, String name) { + public boolean isEntrypointClass(TypeDeclaration typeDecl) { + if (!(typeDecl instanceof ClassOrInterfaceDeclaration)) { + return false; + } + + ClassOrInterfaceDeclaration classDecl = (ClassOrInterfaceDeclaration) typeDecl; + + // Check Camel class annotations + if (classDecl.getAnnotations().stream().anyMatch(a -> a.getNameAsString().contains("Component"))) { + return true; + } + + // Check Camel parent classes and interfaces + try { + ResolvedReferenceTypeDeclaration resolved = classDecl.resolve(); + return resolved.getAllAncestors().stream().anyMatch(ancestor -> { + String name = ancestor.getQualifiedName(); + return name.contains("RouteBuilder") || name.contains("Processor") || name.contains("Producer") + || name.contains("Consumer"); + }); + } catch (UnsolvedSymbolException e) { + Log.warn("Could not resolve class: " + e.getMessage()); + } + return false; } + /** + * @param callableDecl + * @return + */ @Override - public boolean isEntrypointMethod(String receiverType, String name) { + public boolean isEntrypointMethod(CallableDeclaration callableDecl) { return false; } } diff --git a/src/main/java/com/ibm/cldk/javaee/jakarta/JakartaEntrypointFinder.java b/src/main/java/com/ibm/cldk/javaee/jakarta/JakartaEntrypointFinder.java index 083a7e4..91e5e9c 100644 --- a/src/main/java/com/ibm/cldk/javaee/jakarta/JakartaEntrypointFinder.java +++ b/src/main/java/com/ibm/cldk/javaee/jakarta/JakartaEntrypointFinder.java @@ -1,17 +1,48 @@ package com.ibm.cldk.javaee.jakarta; +import com.github.javaparser.ast.NodeList; +import com.github.javaparser.ast.body.CallableDeclaration; +import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; +import com.github.javaparser.ast.body.Parameter; +import com.github.javaparser.ast.body.TypeDeclaration; +import com.github.javaparser.ast.type.ClassOrInterfaceType; import com.ibm.cldk.javaee.utils.interfaces.AbstractEntrypointFinder; -import com.ibm.cldk.utils.annotations.NotImplemented; -@NotImplemented(comment = "This class is not implemented yet. Leaving this here to refactor entrypoint detection.") +@SuppressWarnings({"unchecked", "rawtypes"}) public class JakartaEntrypointFinder extends AbstractEntrypointFinder { @Override - public boolean isEntrypointClass(String receiverType, String name) { - return false; + public boolean isEntrypointClass(TypeDeclaration typeDecl) { + if (!(typeDecl instanceof ClassOrInterfaceDeclaration)) { + return false; + } + + ClassOrInterfaceDeclaration classDecl = (ClassOrInterfaceDeclaration) typeDecl; + + // Check annotations + if (classDecl.getAnnotations().stream() + .anyMatch(a -> a.getNameAsString().contains("WebServlet") || a.getNameAsString().contains("WebFilter") + || a.getNameAsString().contains("WebListener") || a.getNameAsString().contains("ServerEndpoint") + || a.getNameAsString().contains("MessageDriven") + || a.getNameAsString().contains("WebService"))) { + return true; + } + + // Check types + return classDecl.getExtendedTypes().stream() + .map(ClassOrInterfaceType::getNameAsString) + .anyMatch(n -> n.contains("HttpServlet") || n.contains("GenericServlet")) + || classDecl.getImplementedTypes().stream().map( + ClassOrInterfaceType::asString).anyMatch( + n -> n.contains("ServletContextListener") + || n.contains("HttpSessionListener") + || n.contains("ServletRequestListener") + || n.contains("MessageListener")); } @Override - public boolean isEntrypointMethod(String receiverType, String name) { - return false; + public boolean isEntrypointMethod(CallableDeclaration callableDecl) { + return ((NodeList) callableDecl.getParameters()).stream() + .anyMatch(parameter -> parameter.getType().asString().contains("HttpServletRequest") || + parameter.getType().asString().contains("HttpServletResponse")); } } diff --git a/src/main/java/com/ibm/cldk/javaee/jax/JaxRsEntrypointFinder.java b/src/main/java/com/ibm/cldk/javaee/jax/JaxRsEntrypointFinder.java new file mode 100644 index 0000000..fe0bcfc --- /dev/null +++ b/src/main/java/com/ibm/cldk/javaee/jax/JaxRsEntrypointFinder.java @@ -0,0 +1,42 @@ +package com.ibm.cldk.javaee.jax; + +import com.github.javaparser.ast.body.CallableDeclaration; +import com.github.javaparser.ast.body.TypeDeclaration; +import com.ibm.cldk.javaee.utils.interfaces.AbstractEntrypointFinder; + +import java.util.List; + +@SuppressWarnings({"unchecked", "rawtypes"}) +public class JaxRsEntrypointFinder extends AbstractEntrypointFinder { + /** + * Detect if the method is an entrypoint. + * + * @return True if the method is an entrypoint, false otherwise. + */ + @Override + public boolean isEntrypointClass(TypeDeclaration typeDeclaration) { + List callableDeclarations = typeDeclaration.findAll(CallableDeclaration.class); + for (CallableDeclaration callableDeclaration : callableDeclarations) { + if (callableDeclaration.getAnnotations().stream().anyMatch(a -> a.toString().contains("POST")) + || callableDeclaration.getAnnotations().stream().anyMatch(a -> a.toString().contains("PUT")) + || callableDeclaration.getAnnotations().stream().anyMatch(a -> a.toString().contains("GET")) + || callableDeclaration.getAnnotations().stream().anyMatch(a -> a.toString().contains("HEAD")) + || callableDeclaration.getAnnotations().stream().anyMatch(a -> a.toString().contains("DELETE"))) { + return true; + } + } + + return false; + } + + /** + * @param callableDecl + * @return + */ + @Override + public boolean isEntrypointMethod(CallableDeclaration callableDecl) { + return callableDecl.getAnnotations().stream().anyMatch(a -> a.toString().contains("POST") || a.toString().contains("PUT") + || a.toString().contains("GET") || a.toString().contains("HEAD") + || a.toString().contains("DELETE")); + } +} diff --git a/src/main/java/com/ibm/cldk/javaee/spring/SpringEntrypointFinder.java b/src/main/java/com/ibm/cldk/javaee/spring/SpringEntrypointFinder.java index f31063f..b1137ff 100644 --- a/src/main/java/com/ibm/cldk/javaee/spring/SpringEntrypointFinder.java +++ b/src/main/java/com/ibm/cldk/javaee/spring/SpringEntrypointFinder.java @@ -1,17 +1,70 @@ package com.ibm.cldk.javaee.spring; +import com.github.javaparser.ast.body.CallableDeclaration; +import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; +import com.github.javaparser.ast.body.TypeDeclaration; +import com.github.javaparser.ast.expr.AnnotationExpr; +import com.github.javaparser.ast.type.ClassOrInterfaceType; import com.ibm.cldk.javaee.utils.interfaces.AbstractEntrypointFinder; -import com.ibm.cldk.utils.annotations.NotImplemented; -@NotImplemented(comment = "This class is not implemented yet. Leaving this here to refactor entrypoint detection.") +import java.util.List; + public class SpringEntrypointFinder extends AbstractEntrypointFinder { @Override - public boolean isEntrypointClass(String receiverType, String name) { + public boolean isEntrypointClass(TypeDeclaration typeDeclaration) { + List annotations = typeDeclaration.getAnnotations(); + for (AnnotationExpr annotation : annotations) { + // Existing checks + if (annotation.getNameAsString().contains("RestController") + || annotation.getNameAsString().contains("Controller") + || annotation.getNameAsString().contains("HandleInterceptor") + || annotation.getNameAsString().contains("HandlerInterceptor")) { + return true; + } + + // Spring Boot specific checks + if (annotation.getNameAsString().contains("SpringBootApplication") + || annotation.getNameAsString().contains("Configuration") + || annotation.getNameAsString().contains("Component") + || annotation.getNameAsString().contains("Service") + || annotation.getNameAsString().contains("Repository")) { + return true; + } + } + + // Check if class implements CommandLineRunner or ApplicationRunner + if (typeDeclaration instanceof ClassOrInterfaceDeclaration) { + ClassOrInterfaceDeclaration classDecl = (ClassOrInterfaceDeclaration) typeDeclaration; + for (ClassOrInterfaceType implementedType : classDecl.getImplementedTypes()) { + String typeName = implementedType.getNameAsString(); + if (typeName.equals("CommandLineRunner") || typeName.equals("ApplicationRunner")) { + return true; + } + } + } + return false; } @Override - public boolean isEntrypointMethod(String receiverType, String name) { - return false; - } + public boolean isEntrypointMethod(CallableDeclaration callableDecl) { return callableDecl.getAnnotations().stream().anyMatch(a -> a.toString().contains("GetMapping") || + a.toString().contains("PostMapping") || + a.toString().contains("PutMapping") || + a.toString().contains("DeleteMapping") || + a.toString().contains("PatchMapping") || + a.toString().contains("RequestMapping") || + a.toString().contains("EventListener") || + a.toString().contains("Scheduled") || + a.toString().contains("KafkaListener") || + a.toString().contains("RabbitListener") || + a.toString().contains("JmsListener") || + a.toString().contains("PreAuthorize") || + a.toString().contains("PostAuthorize") || + a.toString().contains("PostConstruct") || + a.toString().contains("PreDestroy") || + a.toString().contains("Around") || + a.toString().contains("Before") || + a.toString().contains("After") || + a.toString().contains("JobScope") || + a.toString().contains("StepScope")); } } diff --git a/src/main/java/com/ibm/cldk/javaee/struts/StrutsEntrypointFinder.java b/src/main/java/com/ibm/cldk/javaee/struts/StrutsEntrypointFinder.java index 311d248..3586ff7 100644 --- a/src/main/java/com/ibm/cldk/javaee/struts/StrutsEntrypointFinder.java +++ b/src/main/java/com/ibm/cldk/javaee/struts/StrutsEntrypointFinder.java @@ -1,15 +1,68 @@ package com.ibm.cldk.javaee.struts; +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.body.CallableDeclaration; +import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; +import com.github.javaparser.ast.body.TypeDeclaration; +import com.github.javaparser.ast.type.ClassOrInterfaceType; +import com.github.javaparser.resolution.UnsolvedSymbolException; +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; import com.ibm.cldk.javaee.utils.interfaces.AbstractEntrypointFinder; +import com.ibm.cldk.utils.Log; + +import java.util.Optional; public class StrutsEntrypointFinder extends AbstractEntrypointFinder { @Override - public boolean isEntrypointClass(String receiverType, String name) { + public boolean isEntrypointClass(TypeDeclaration typeDeclaration){ + if (!(typeDeclaration instanceof ClassOrInterfaceDeclaration)) { + return false; + } + + ClassOrInterfaceDeclaration classDecl = (ClassOrInterfaceDeclaration) typeDeclaration; + + // Check class-level Struts annotations + if (classDecl.getAnnotations().stream().anyMatch(a -> a.getNameAsString().contains("Action") + || a.getNameAsString().contains("Namespace") || a.getNameAsString().contains("InterceptorRef"))) { + return true; + } + + // Check if extends ActionSupport or implements Interceptor + try { + ResolvedReferenceTypeDeclaration resolved = classDecl.resolve(); + return resolved.getAllAncestors().stream().anyMatch(ancestor -> { + String name = ancestor.getQualifiedName(); + return name.contains("ActionSupport") || name.contains("Interceptor"); + }); + } catch (UnsolvedSymbolException e) { + Log.warn("Could not resolve class: " + e.getMessage()); + } + return false; } @Override - public boolean isEntrypointMethod(String receiverType, String name) { - return false; + public boolean isEntrypointMethod(CallableDeclaration callableDecl) { + // First check if this method is in a Struts Action class + Optional parentNode = callableDecl.getParentNode(); + if (parentNode.isEmpty() || !(parentNode.get() instanceof ClassOrInterfaceDeclaration)) { + return false; + } + + ClassOrInterfaceDeclaration parentClass = (ClassOrInterfaceDeclaration) parentNode.get(); + if (parentClass.getExtendedTypes().stream() + .map(ClassOrInterfaceType::asString) + .noneMatch(type -> type.contains("ActionSupport") || type.contains("Action"))) + return false; + + return callableDecl.getAnnotations().stream().anyMatch(a -> a.toString().contains("Action") || + a.toString().contains("Actions") || + a.toString().contains("ValidationMethod") || + a.toString().contains("InputConfig") || + a.toString().contains("BeforeResult") || + a.toString().contains("After") || + a.toString().contains("Before") || + a.toString().contains("Result") || + a.toString().contains("Results")) || callableDecl.getNameAsString().equals("execute"); } } diff --git a/src/main/java/com/ibm/cldk/javaee/utils/interfaces/AbstractEntrypointFinder.java b/src/main/java/com/ibm/cldk/javaee/utils/interfaces/AbstractEntrypointFinder.java index e3b8f32..7fca2d2 100644 --- a/src/main/java/com/ibm/cldk/javaee/utils/interfaces/AbstractEntrypointFinder.java +++ b/src/main/java/com/ibm/cldk/javaee/utils/interfaces/AbstractEntrypointFinder.java @@ -1,13 +1,15 @@ package com.ibm.cldk.javaee.utils.interfaces; +import com.github.javaparser.ast.body.CallableDeclaration; +import com.github.javaparser.ast.body.TypeDeclaration; import com.ibm.cldk.utils.annotations.NotImplemented; -@NotImplemented(comment = "This class is not implemented yet. Leaving this here to refactor entrypoint detection.") +@SuppressWarnings({"unchecked", "rawtypes"}) public abstract class AbstractEntrypointFinder { /** * Enum for rules. */ - enum Rulest{ + enum Rulset{ } /** @@ -17,7 +19,7 @@ enum Rulest{ * @param name The name of the method. * @return True if the method is an entrypoint, false otherwise. */ - public abstract boolean isEntrypointClass(String receiverType, String name); + public abstract boolean isEntrypointClass(TypeDeclaration typeDecl); - public abstract boolean isEntrypointMethod(String receiverType, String name); + public abstract boolean isEntrypointMethod(CallableDeclaration callableDecl); } diff --git a/src/main/java/com/ibm/cldk/utils/BuildProject.java b/src/main/java/com/ibm/cldk/utils/BuildProject.java index 65d2573..a2533ea 100644 --- a/src/main/java/com/ibm/cldk/utils/BuildProject.java +++ b/src/main/java/com/ibm/cldk/utils/BuildProject.java @@ -239,7 +239,7 @@ public static boolean downloadLibraryDependencies(String projectPath, String pro )); } Log.info("Found pom.xml in the project directory. Using Maven to download dependencies."); - String[] mavenCommand = {MAVEN_CMD, "--no-transfer-progress", "-f", Paths.get(projectRoot, "pom.xml").toAbsolutePath().toString(), "dependency:copy-dependencies", "-DoutputDirectory=" + libDownloadPath.toString(), "-Doverwrite=true"}; + String[] mavenCommand = {MAVEN_CMD, "--no-transfer-progress", "-f", Paths.get(projectRoot, "pom.xml").toAbsolutePath().toString(), "dependency:copy-dependencies", "-DoutputDirectory=" + libDownloadPath.toString(), "-Doverwrite=true", "--fail-never"}; return buildWithTool(mavenCommand); } else if (new File(projectRoot, "build.gradle").exists() || new File(projectRoot, "build.gradle.kts").exists()) { libDownloadPath = Paths.get(projectPath, "build", LIB_DEPS_DOWNLOAD_DIR).toAbsolutePath(); diff --git a/src/test/java/com/ibm/cldk/CodeAnalyzerIntegrationTest.java b/src/test/java/com/ibm/cldk/CodeAnalyzerIntegrationTest.java index da29af1..4188723 100644 --- a/src/test/java/com/ibm/cldk/CodeAnalyzerIntegrationTest.java +++ b/src/test/java/com/ibm/cldk/CodeAnalyzerIntegrationTest.java @@ -209,6 +209,7 @@ void shouldBeAbleToGenerateAnalysisArtifactForDaytrader8() throws Exception { Assertions.assertTrue(runCodeAnalyzerOnDaytrader8.getStdout().contains("\"is_entrypoint\": true"), "No entry point methods found"); } + @Test void shouldBeAbleToDetectCRUDOperationsAndQueriesForPlantByWebsphere() throws Exception { var runCodeAnalyzerOnPlantsByWebsphere = container.execInContainer( diff --git a/src/test/resources/test-applications/.gitignore b/src/test/resources/test-applications/.gitignore index bc07142..1160922 100644 --- a/src/test/resources/test-applications/.gitignore +++ b/src/test/resources/test-applications/.gitignore @@ -52,3 +52,6 @@ target/ # Ignore everything in codeql-db except the directory itself codeql-db/* !codeql-db/.keep + +# Ignore hidden apps +hidden-apps/ \ No newline at end of file From ec9f646a89f6813382a75e4b63f6e717c55578d1 Mon Sep 17 00:00:00 2001 From: Rahul Krishna Date: Thu, 17 Apr 2025 11:18:26 -0400 Subject: [PATCH 5/8] Add missing imports Signed-off-by: Rahul Krishna --- src/main/java/com/ibm/cldk/utils/BuildProject.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/ibm/cldk/utils/BuildProject.java b/src/main/java/com/ibm/cldk/utils/BuildProject.java index 9ec35b0..d627e78 100644 --- a/src/main/java/com/ibm/cldk/utils/BuildProject.java +++ b/src/main/java/com/ibm/cldk/utils/BuildProject.java @@ -9,6 +9,7 @@ import java.nio.file.Paths; import java.text.MessageFormat; import java.util.*; +import java.util.function.Predicate; import java.util.stream.Stream; @@ -29,7 +30,7 @@ public class BuildProject { * @return the maven command */ public static String getMavenCommand() { - String mvnSystemCommand = Arrays.stream(System.getenv("PATH").split(System.getProperty("path.separator"))).filter(Predicate.not(String::isBlank)).filter(Predicate.not(String::isEmpty)).map(path -> new File(path, System.getProperty("os.name").toLowerCase().contains("windows") ? "mvn.cmd" : "mvn")).filter(File::exists).findFirst().map(File::getAbsolutePath).orElse(null); + String mvnSystemCommand = Arrays.stream(System.getenv("PATH").split(File.pathSeparator)).filter(Predicate.not(String::isBlank)).filter(Predicate.not(String::isEmpty)).map(path -> new File(path, System.getProperty("os.name").toLowerCase().contains("windows") ? "mvn.cmd" : "mvn")).filter(File::exists).findFirst().map(File::getAbsolutePath).orElse(null); File mvnWrapper = System.getProperty("os.name").toLowerCase().contains("windows") ? new File(projectRootPom, "mvnw.cmd") : new File(projectRootPom, "mvnw"); return commandExists(mvnWrapper.getAbsoluteFile()).getKey() ? mvnWrapper.getAbsoluteFile().toString() : mvnSystemCommand; } @@ -40,7 +41,7 @@ public static String getMavenCommand() { * @return the gradle command */ public static String getGradleCommand() { - String gradleSystemCommand = Arrays.stream(System.getenv("PATH").split(System.getProperty("path.separator"))).filter(Predicate.not(String::isBlank)).filter(Predicate.not(String::isEmpty)).map(path -> new File(path, System.getProperty("os.name").toLowerCase().contains("windows") ? "gradle.bat" : "gradle")).filter(File::exists).findFirst().map(File::getAbsolutePath).orElse(null); + String gradleSystemCommand = Arrays.stream(System.getenv("PATH").split(File.pathSeparator)).filter(Predicate.not(String::isBlank)).filter(Predicate.not(String::isEmpty)).map(path -> new File(path, System.getProperty("os.name").toLowerCase().contains("windows") ? "gradle.bat" : "gradle")).filter(File::exists).findFirst().map(File::getAbsolutePath).orElse(null); File gradleWrapper = System.getProperty("os.name").toLowerCase().contains("windows") ? new File(projectRootPom, "gradlew.bat") : new File(projectRootPom, "gradlew"); return commandExists(gradleWrapper.getAbsoluteFile()).getKey() ? gradleWrapper.getAbsoluteFile() .toString() : gradleSystemCommand; From fcaadd63c87a22b95fdca47c90b082db4af76bb9 Mon Sep 17 00:00:00 2001 From: Saurabh Sinha Date: Fri, 30 May 2025 20:36:22 -0400 Subject: [PATCH 6/8] Updated callable and initializer code to be stored in lexical-preserving manner (so blank lines are not discarded); added codeStartLine attribute to callables Signed-off-by: Saurabh Sinha --- src/main/java/com/ibm/cldk/SymbolTable.java | 41 ++++--------------- .../java/com/ibm/cldk/entities/Callable.java | 3 ++ 2 files changed, 11 insertions(+), 33 deletions(-) diff --git a/src/main/java/com/ibm/cldk/SymbolTable.java b/src/main/java/com/ibm/cldk/SymbolTable.java index d5b5bc8..168aced 100644 --- a/src/main/java/com/ibm/cldk/SymbolTable.java +++ b/src/main/java/com/ibm/cldk/SymbolTable.java @@ -16,38 +16,23 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; +import com.github.javaparser.*; +import com.github.javaparser.ast.*; import com.github.javaparser.ast.body.*; import com.github.javaparser.ast.comments.Comment; import com.github.javaparser.ast.comments.JavadocComment; +import com.github.javaparser.ast.expr.*; import com.github.javaparser.ast.nodeTypes.NodeWithJavadoc; import com.github.javaparser.ast.stmt.*; +import com.github.javaparser.printer.lexicalpreservation.LexicalPreservingPrinter; +import com.ibm.cldk.entities.*; import com.ibm.cldk.javaee.EntrypointsFinderFactory; import org.apache.commons.lang3.tuple.Pair; -import com.github.javaparser.JavaParser; -import com.github.javaparser.ParseResult; -import com.github.javaparser.ParserConfiguration; -import com.github.javaparser.Problem; -import com.github.javaparser.ast.AccessSpecifier; -import com.github.javaparser.ast.CompilationUnit; -import com.github.javaparser.ast.Node; -import com.github.javaparser.ast.NodeList; -import com.github.javaparser.ast.expr.AnnotationExpr; -import com.github.javaparser.ast.expr.AssignExpr; -import com.github.javaparser.ast.expr.CastExpr; -import com.github.javaparser.ast.expr.ConditionalExpr; -import com.github.javaparser.ast.expr.Expression; -import com.github.javaparser.ast.expr.FieldAccessExpr; -import com.github.javaparser.ast.expr.MethodCallExpr; -import com.github.javaparser.ast.expr.NameExpr; -import com.github.javaparser.ast.expr.ObjectCreationExpr; import com.github.javaparser.ast.nodeTypes.NodeWithName; -import com.github.javaparser.ast.type.ClassOrInterfaceType; import com.github.javaparser.ast.type.ReferenceType; import com.github.javaparser.ast.type.Type; -import com.github.javaparser.resolution.UnsolvedSymbolException; import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration; -import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; import com.github.javaparser.resolution.types.ResolvedType; import com.github.javaparser.symbolsolver.JavaSymbolSolver; import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver; @@ -57,17 +42,6 @@ import com.github.javaparser.utils.SourceRoot; import com.google.common.collect.Table; import com.google.common.collect.Tables; -import com.ibm.cldk.entities.CRUDOperation; -import com.ibm.cldk.entities.CRUDQuery; -import com.ibm.cldk.entities.CallSite; -import com.ibm.cldk.entities.Callable; -import com.ibm.cldk.entities.EnumConstant; -import com.ibm.cldk.entities.Field; -import com.ibm.cldk.entities.InitializationBlock; -import com.ibm.cldk.entities.JavaCompilationUnit; -import com.ibm.cldk.entities.ParameterInCallable; -import com.ibm.cldk.entities.RecordComponent; -import com.ibm.cldk.entities.VariableDeclaration; import com.ibm.cldk.javaee.CRUDFinderFactory; import com.ibm.cldk.javaee.utils.enums.CRUDOperationType; import com.ibm.cldk.javaee.utils.enums.CRUDQueryType; @@ -365,7 +339,7 @@ private static InitializationBlock createInitializationBlock(InitializerDeclarat return throwStmt.asThrowStmt().getExpression().toString(); } }).collect(Collectors.toList())); - initializationBlock.setCode(initializerDeclaration.getBody().toString()); + initializationBlock.setCode(LexicalPreservingPrinter.setup(initializerDeclaration.getBody()).toString()); initializationBlock.setStartLine( initializerDeclaration.getRange().isPresent() ? initializerDeclaration.getRange().get().begin.line : -1); @@ -589,7 +563,8 @@ private static Pair processCallableDeclaration(CallableDeclara callableNode.setStartLine(callableDecl.getRange().isPresent() ? callableDecl.getRange().get().begin.line : -1); callableNode.setEndLine(callableDecl.getRange().isPresent() ? callableDecl.getRange().get().end.line : -1); callableNode.setReferencedTypes(getReferencedTypes(body)); - callableNode.setCode(body.isPresent() ? body.get().toString() : ""); + callableNode.setCode(body.isPresent() ? LexicalPreservingPrinter.setup(body.get()).toString() : ""); + callableNode.setCodeStartLine(body.isPresent()? body.get().getBegin().get().line : -1); callableNode.setAccessedFields(getAccessedFields(body, classFields, typeName)); callableNode.setCallSites(getCallSites(body)); diff --git a/src/main/java/com/ibm/cldk/entities/Callable.java b/src/main/java/com/ibm/cldk/entities/Callable.java index 04d91ee..cb4927c 100644 --- a/src/main/java/com/ibm/cldk/entities/Callable.java +++ b/src/main/java/com/ibm/cldk/entities/Callable.java @@ -70,6 +70,9 @@ public class Callable { /** The ending line number of the callable entity in the source file. */ private int endLine; + /** The starting line number of the callable code in the source file. */ + private int codeStartLine; + /** The return type of the callable entity. */ private String returnType = null; From 980d587da5b829b40b7eca0405ecaf2c6d3d6725 Mon Sep 17 00:00:00 2001 From: Saurabh Sinha Date: Sun, 1 Jun 2025 23:33:17 +0530 Subject: [PATCH 7/8] Fix for lexical preserving printing of code Signed-off-by: Saurabh Sinha --- src/main/java/com/ibm/cldk/SymbolTable.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/ibm/cldk/SymbolTable.java b/src/main/java/com/ibm/cldk/SymbolTable.java index 168aced..6fbd042 100644 --- a/src/main/java/com/ibm/cldk/SymbolTable.java +++ b/src/main/java/com/ibm/cldk/SymbolTable.java @@ -339,7 +339,7 @@ private static InitializationBlock createInitializationBlock(InitializerDeclarat return throwStmt.asThrowStmt().getExpression().toString(); } }).collect(Collectors.toList())); - initializationBlock.setCode(LexicalPreservingPrinter.setup(initializerDeclaration.getBody()).toString()); + initializationBlock.setCode(LexicalPreservingPrinter.print(initializerDeclaration.getBody())); initializationBlock.setStartLine( initializerDeclaration.getRange().isPresent() ? initializerDeclaration.getRange().get().begin.line : -1); @@ -563,7 +563,7 @@ private static Pair processCallableDeclaration(CallableDeclara callableNode.setStartLine(callableDecl.getRange().isPresent() ? callableDecl.getRange().get().begin.line : -1); callableNode.setEndLine(callableDecl.getRange().isPresent() ? callableDecl.getRange().get().end.line : -1); callableNode.setReferencedTypes(getReferencedTypes(body)); - callableNode.setCode(body.isPresent() ? LexicalPreservingPrinter.setup(body.get()).toString() : ""); + callableNode.setCode(body.isPresent() ? LexicalPreservingPrinter.print(body.get()) : ""); callableNode.setCodeStartLine(body.isPresent()? body.get().getBegin().get().line : -1); callableNode.setAccessedFields(getAccessedFields(body, classFields, typeName)); @@ -1090,7 +1090,7 @@ public static Pair, Map>> for (SourceRoot sourceRoot : projectRoot.getSourceRoots()) { for (ParseResult parseResult : sourceRoot.tryToParse()) { if (parseResult.isSuccessful()) { - CompilationUnit compilationUnit = parseResult.getResult().get(); + CompilationUnit compilationUnit = LexicalPreservingPrinter.setup(parseResult.getResult().get()); symbolTable.put(compilationUnit.getStorage().get().getPath().toString(), processCompilationUnit(compilationUnit)); } else { @@ -1116,7 +1116,7 @@ public static Pair, Map>> JavaParser javaParser = new JavaParser(parserConfiguration); ParseResult parseResult = javaParser.parse(code); if (parseResult.isSuccessful()) { - CompilationUnit compilationUnit = parseResult.getResult().get(); + CompilationUnit compilationUnit = LexicalPreservingPrinter.setup(parseResult.getResult().get()); Log.debug("Successfully parsed code. Now processing compilation unit"); symbolTable.put("", processCompilationUnit(compilationUnit)); } else { @@ -1158,7 +1158,7 @@ public static Pair, Map>> for (Path javaFilePath : javaFilePaths) { ParseResult parseResult = javaParser.parse(javaFilePath); if (parseResult.isSuccessful()) { - CompilationUnit compilationUnit = parseResult.getResult().get(); + CompilationUnit compilationUnit = LexicalPreservingPrinter.setup(parseResult.getResult().get()); System.out.println("Successfully parsed file: " + javaFilePath.toString()); symbolTable.put(compilationUnit.getStorage().get().getPath().toString(), processCompilationUnit(compilationUnit)); From 08d11b7d94a0aa3d900d6ef9f488dd125e18bcba Mon Sep 17 00:00:00 2001 From: Rahul Krishna Date: Thu, 3 Jul 2025 09:48:33 -0400 Subject: [PATCH 8/8] Bump version Signed-off-by: Rahul Krishna --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 5016995..c5f9ec0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=2.3.3 +version=2.3.4 \ No newline at end of file