Skip to content

Commit

Permalink
Merge pull request #119 from codellm-devkit/113-call-graph-is-missing…
Browse files Browse the repository at this point in the history
…-edges-to-implementations-of-interfaces-merge-to-v1.X.X

Fix Issue 113: call graph is missing edges to implementations of interfaces. Merge to v1.X.X.
  • Loading branch information
rahlk authored Feb 17, 2025
2 parents 286fa0f + 7614c2a commit 807b321
Show file tree
Hide file tree
Showing 13 changed files with 510 additions and 25 deletions.
2 changes: 1 addition & 1 deletion src/main/java/com/ibm/cldk/utils/AnalysisUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ public static Iterable<Entrypoint> getEntryPoints(IClassHierarchy cha) {
System.exit(1);
return Stream.empty();
}
}).filter(method -> method.isPublic() || method.isPrivate() || method.isProtected() || method.isStatic()).map(method -> new DefaultEntrypoint(method, cha)).collect(Collectors.toList());
}).map(method -> new DefaultEntrypoint(method, cha)).collect(Collectors.toList());

Log.info("Registered " + entrypoints.size() + " entrypoints.");
return entrypoints;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
Expand Down Expand Up @@ -37,7 +38,7 @@ public static List<Path> jarFilesStream(String projectPath) throws IOException {
.collect(Collectors.toList());
}
}
return null;
return new ArrayList<>();
}

public static List<Path> sourceFilesStream(String projectPath) throws IOException {
Expand Down
89 changes: 66 additions & 23 deletions src/test/java/com/ibm/cldk/CodeAnalyzerIntegrationTest.java
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
package com.ibm.cldk;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.json.JSONArray;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Assertions;
import org.testcontainers.containers.BindMode;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.MountableFile;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.MessageFormat;
import java.time.Duration;
import java.util.Properties;
import java.util.stream.StreamSupport;

import static org.junit.jupiter.api.Assertions.assertThrows;

@Testcontainers
@SuppressWarnings("resource")
Expand All @@ -28,7 +33,7 @@ public class CodeAnalyzerIntegrationTest {
*/
static String codeanalyzerVersion;
static final String javaVersion = "17";

static String javaHomePath;
static {
// Build project first
try {
Expand All @@ -44,15 +49,14 @@ public class CodeAnalyzerIntegrationTest {
}

@Container
static final GenericContainer<?> container = new GenericContainer<>("openjdk:17-jdk")
static final GenericContainer<?> container = new GenericContainer<>("ubuntu:latest")
.withCreateContainerCmdModifier(cmd -> cmd.withEntrypoint("sh"))
.withCommand("-c", "while true; do sleep 1; done")
.withFileSystemBind(
String.valueOf(Paths.get(System.getProperty("user.dir")).resolve("build/libs")),
"/opt/jars",
BindMode.READ_WRITE)
.withCopyFileToContainer(MountableFile.forHostPath(Paths.get(System.getProperty("user.dir")).resolve("build/libs")), "/opt/jars")
.withCopyFileToContainer(MountableFile.forHostPath(Paths.get(System.getProperty("user.dir")).resolve("build/libs")), "/opt/jars")
.withCopyFileToContainer(MountableFile.forHostPath(Paths.get(System.getProperty("user.dir")).resolve("src/test/resources/test-applications/mvnw-corrupt-test")), "/test-applications/mvnw-corrupt-test")
.withCopyFileToContainer(MountableFile.forHostPath(Paths.get(System.getProperty("user.dir")).resolve("src/test/resources/test-applications/plantsbywebsphere")), "/test-applications/plantsbywebsphere")
.withCopyFileToContainer(MountableFile.forHostPath(Paths.get(System.getProperty("user.dir")).resolve("src/test/resources/test-applications/call-graph-test")), "/test-applications/call-graph-test")
.withCopyFileToContainer(MountableFile.forHostPath(Paths.get(System.getProperty("user.dir")).resolve("src/test/resources/test-applications/mvnw-working-test")), "/test-applications/mvnw-working-test");

@Container
Expand All @@ -61,11 +65,32 @@ public class CodeAnalyzerIntegrationTest {
.withCommand("-c", "while true; do sleep 1; done")
.withCopyFileToContainer(MountableFile.forHostPath(Paths.get(System.getProperty("user.dir")).resolve("build/libs")), "/opt/jars")
.withCopyFileToContainer(MountableFile.forHostPath(Paths.get(System.getProperty("user.dir")).resolve("src/test/resources/test-applications/mvnw-corrupt-test")), "/test-applications/mvnw-corrupt-test")
.withCopyFileToContainer(MountableFile.forHostPath(Paths.get(System.getProperty("user.dir")).resolve("src/test/resources/test-applications/mvnw-working-test")), "/test-applications/mvnw-working-test");
.withCopyFileToContainer(MountableFile.forHostPath(Paths.get(System.getProperty("user.dir")).resolve("src/test/resources/test-applications/mvnw-working-test")), "/test-applications/mvnw-working-test")
.withCopyFileToContainer(MountableFile.forHostPath(Paths.get(System.getProperty("user.dir")).resolve("src/test/resources/test-applications/daytrader8")), "/test-applications/daytrader8");

public CodeAnalyzerIntegrationTest() throws IOException, InterruptedException {
}

@BeforeAll
static void setUp() {
// Install Java 17 in the base container
try {
container.execInContainer("apt-get", "update");
container.execInContainer("apt-get", "install", "-y", "openjdk-17-jdk");

// Get JAVA_HOME dynamically
var javaHomeResult = container.execInContainer("bash", "-c",
"dirname $(dirname $(readlink -f $(which java)))"
);
javaHomePath = javaHomeResult.getStdout().trim();
Assertions.assertFalse(javaHomePath.isEmpty(), "Failed to determine JAVA_HOME");

} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}


// Get the version of the codeanalyzer jar
Properties properties = new Properties();
try (FileInputStream fis = new FileInputStream(
Paths.get(System.getProperty("user.dir"), "gradle.properties").toFile())) {
Expand Down Expand Up @@ -94,18 +119,36 @@ void shouldHaveCodeAnalyzerJar() throws Exception {
@Test
void shouldBeAbleToRunCodeAnalyzer() throws Exception {
var runCodeAnalyzerJar = container.execInContainer(
"java",
"-jar",
String.format("/opt/jars/codeanalyzer-%s.jar", codeanalyzerVersion),
"--help"
);
"bash", "-c",
String.format("export JAVA_HOME=%s && java -jar /opt/jars/codeanalyzer-%s.jar --help",
javaHomePath, codeanalyzerVersion
));

Assertions.assertEquals(0, runCodeAnalyzerJar.getExitCode(),
"Command should execute successfully");
Assertions.assertTrue(runCodeAnalyzerJar.getStdout().length() > 0,
"Should have some output");
}

@Test
void callGraphShouldHaveKnownEdges() throws Exception {
var whatIsInTheTestAppFolder = container.execInContainer("ls", "/test-applications/call-graph-test");
var runCodeAnalyzerOnCallGraphTest = container.execInContainer(
"bash", "-c",
String.format(
"export JAVA_HOME=%s && java -jar /opt/jars/codeanalyzer-%s.jar --input=/test-applications/call-graph-test --analysis-level=2",
javaHomePath, codeanalyzerVersion
)
);

// Read the output JSON
Gson gson = new Gson();
JsonObject jsonObject = gson.fromJson(runCodeAnalyzerOnCallGraphTest.getStdout(), JsonObject.class);
JsonArray systemDepGraph = jsonObject.getAsJsonArray("system_dependency_graph");
Assertions.assertEquals(4, StreamSupport.stream(systemDepGraph.spliterator(), false).count(),
"Expected exactly 4 entries in the system dependency graph");
}

@Test
void corruptMavenShouldNotBuildWithWrapper() throws IOException, InterruptedException {
// Make executable
Expand All @@ -129,19 +172,19 @@ void corruptMavenShouldProduceAnalysisArtifactsWhenMVNCommandIsInPath() throws I
Assertions.assertTrue(runCodeAnalyzer.getStdout().contains("[ERROR]\tCannot run program \"/test-applications/mvnw-corrupt-test/mvnw\"") && runCodeAnalyzer.getStdout().contains("/mvn."));
// We should correctly identify the build tool used in the mvn command from the system path.
Assertions.assertTrue(runCodeAnalyzer.getStdout().contains("[INFO]\tBuilding the project using /usr/bin/mvn."));
}
}

@Test
void corruptMavenShouldNotTerminateWithErrorWhenMavenIsNotPresentUnlessAnalysisLevel2() throws IOException, InterruptedException {
// When analysis level 2, we should get a Runtime Exception
var runCodeAnalyzer = container.execInContainer(
"java",
"-jar",
String.format("/opt/jars/codeanalyzer-%s.jar", codeanalyzerVersion),
"--input=/test-applications/mvnw-corrupt-test",
"--output=/tmp/",
"--analysis-level=2"
);
"bash", "-c",
String.format(
"export JAVA_HOME=%s && java -jar /opt/jars/codeanalyzer-%s.jar --input=/test-applications/mvnw-corrupt-test --output=/tmp/ --analysis-level=2",
javaHomePath, codeanalyzerVersion
)
);

Assertions.assertEquals(1, runCodeAnalyzer.getExitCode());
Assertions.assertTrue(runCodeAnalyzer.getStderr().contains("java.lang.RuntimeException"));
}
Expand Down
2 changes: 2 additions & 0 deletions src/test/resources/test-applications/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ target/
*.iws
*.ipr

!gradle-wrapper.jar

# Ignore Eclipse files
.settings/
*.classpath
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# Linux start script should use lf
/gradlew text eol=lf

# These are Windows script files and should use crlf
*.bat text eol=crlf

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Ignore Gradle project-specific cache directory
.gradle

# Ignore Gradle build output directory
build
30 changes: 30 additions & 0 deletions src/test/resources/test-applications/call-graph-test/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
plugins {
id 'application'
}

repositories {
mavenCentral()
}

java {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}

if (project.hasProperty('mainClass')) {
mainClassName = project.getProperty('mainClass')
} else {
// use a default
mainClassName =("org.example.User")
}

sourceSets {
main {
java {
srcDirs = ["src/main/java"]
}
resources {
srcDirs = ["src/main/resources"]
}
}
}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Loading

0 comments on commit 807b321

Please sign in to comment.