From c12c867b61986112e033c1353b4e84bcc5d2019c Mon Sep 17 00:00:00 2001 From: Aleksandr Misonizhnik Date: Tue, 4 Aug 2026 17:05:36 +0200 Subject: [PATCH 1/8] feat(project-model): tag dependencies with coordinates via ResolvedDependency --- .../kotlin/org/opentaint/project/Project.kt | 15 ++++++- .../project/ResolvedDependencyTest.kt | 39 +++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 core/opentaint-project-model/src/test/kotlin/org/opentaint/project/ResolvedDependencyTest.kt diff --git a/core/opentaint-project-model/src/main/kotlin/org/opentaint/project/Project.kt b/core/opentaint-project-model/src/main/kotlin/org/opentaint/project/Project.kt index 1fc94c7ee..d5fd52066 100644 --- a/core/opentaint-project-model/src/main/kotlin/org/opentaint/project/Project.kt +++ b/core/opentaint-project-model/src/main/kotlin/org/opentaint/project/Project.kt @@ -22,13 +22,24 @@ sealed interface CommonProject { fun sourceRoot(): Path? } +@Serializable +data class ResolvedDependency( + val path: @Serializable(with = PathAsStringSerializer::class) Path, + val group: String? = null, + val artifact: String? = null, + val version: String? = null, +) { + fun relativeTo(base: Path): ResolvedDependency = copy(path = path.relativeTo(base)) + fun resolve(base: Path): ResolvedDependency = copy(path = base.resolve(path)) +} + @Suppress("DEPRECATION") @Serializable data class JavaProject( val sourceRoot: @Serializable(with = PathAsStringSerializer::class) Path? = null, val javaToolchain: @Serializable(with = PathAsStringSerializer::class) Path? = null, val modules: List = emptyList(), - val dependencies: List<@Serializable(with = PathAsStringSerializer::class) Path> = emptyList(), + val dependencies: List = emptyList(), @Deprecated("Use top-level Project.javaProjects instead") val subProjects: List = emptyList(), ): CommonProject { @@ -46,7 +57,7 @@ data class JavaProject( sourceRoot?.let { base.resolve(it) }, javaToolchain?.let { base.resolve(it) }, modules.map { it.resolve(base) }, - dependencies.map { base.resolve(it) }, + dependencies.map { it.resolve(base) }, subProjects.map { it.resolve(base) } ) diff --git a/core/opentaint-project-model/src/test/kotlin/org/opentaint/project/ResolvedDependencyTest.kt b/core/opentaint-project-model/src/test/kotlin/org/opentaint/project/ResolvedDependencyTest.kt new file mode 100644 index 000000000..e915b5d5a --- /dev/null +++ b/core/opentaint-project-model/src/test/kotlin/org/opentaint/project/ResolvedDependencyTest.kt @@ -0,0 +1,39 @@ +package org.opentaint.project + +import kotlin.io.path.Path +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class ResolvedDependencyTest { + @Test + fun `relativeTo then resolve round-trips the path and keeps coordinates`() { + val abs = ResolvedDependency( + path = Path("/repo/model/deps/opensearch-rest-client-2.18.0.jar"), + group = "org.opensearch.client", artifact = "opensearch-rest-client", version = "2.18.0", + ) + val rel = abs.relativeTo(Path("/repo/model")) + assertEquals(Path("deps/opensearch-rest-client-2.18.0.jar"), rel.path) + assertEquals("2.18.0", rel.version) + assertEquals(abs.path, rel.resolve(Path("/repo/model")).path) + } + + @Test + fun `coordinates are optional`() { + val d = ResolvedDependency(path = Path("/x/lib.jar"), group = null, artifact = null, version = null) + assertNull(d.version) + } + + @Test + fun `a JavaProject serializes tagged dependencies and loads them back`() { + val jp = JavaProject( + sourceRoot = Path("/p"), + dependencies = listOf(ResolvedDependency(Path("/d/a.jar"), "g", "a", "1.0")), + ) + val tmp = kotlin.io.path.createTempFile(suffix = ".yaml") + jp.dump(tmp) + val loaded = JavaProject.load(tmp) + assertEquals("1.0", loaded.dependencies.single().version) + assertEquals(Path("/d/a.jar"), loaded.dependencies.single().path) + } +} From 078e58578562d3836594e8b89cd75a2526c2da7c Mon Sep 17 00:00:00 2001 From: Aleksandr Misonizhnik Date: Tue, 4 Aug 2026 17:13:25 +0200 Subject: [PATCH 2/8] feat(autobuilder): Gradle resolution-table init script and parser --- .../project/GradleProjectResolver.kt | 55 +++++++++++++++++++ .../project/GradleResolutionTableTest.kt | 28 ++++++++++ 2 files changed, 83 insertions(+) create mode 100644 core/opentaint-jvm-autobuilder/src/test/kotlin/org/opentaint/project/GradleResolutionTableTest.kt diff --git a/core/opentaint-jvm-autobuilder/src/main/kotlin/org/opentaint/project/GradleProjectResolver.kt b/core/opentaint-jvm-autobuilder/src/main/kotlin/org/opentaint/project/GradleProjectResolver.kt index 7d4d0a051..765023132 100644 --- a/core/opentaint-jvm-autobuilder/src/main/kotlin/org/opentaint/project/GradleProjectResolver.kt +++ b/core/opentaint-jvm-autobuilder/src/main/kotlin/org/opentaint/project/GradleProjectResolver.kt @@ -20,6 +20,17 @@ import kotlin.io.path.readText import kotlin.io.path.walk import kotlin.io.path.writeText +fun parseResolutionTable(reportDir: Path): Map { + val json = Json { ignoreUnknownKeys = true } + val table = HashMap() + reportDir.walk().filter { it.extension == "json" && it.name.startsWith("table-") }.forEach { f -> + json.decodeFromString>(f.readText()).forEach { e -> + table.putIfAbsent(e.coord, Path(e.file)) + } + } + return table +} + class GradleProjectResolver( private val resolverDir: Path, override val projectSourceRoot: Path @@ -187,6 +198,9 @@ class GradleProjectResolver( val classDirs: List = emptyList() ) + @Serializable + data class TableEntry(val coord: String, val file: String) + @Serializable data class GradleDependencies( val manifests: Map? = null @@ -293,6 +307,10 @@ class GradleProjectResolver( private const val CLASSES_REPORT_DIR_PROPERTY = "OPENTAINT_CLASSES_REPORT_DIR" + private const val RESOLVE_TABLE_TASK = "opentaintResolveTable" + + private const val TABLE_REPORT_DIR_PROPERTY = "opentaint.table.dir" + private val GRADLE_CLASSES_INIT_SCRIPT = """ import groovy.json.JsonOutput @@ -375,6 +393,43 @@ class GradleProjectResolver( apply plugin: GitHubDependencyGraphPlugin """.trimIndent() + private val GRADLE_RESOLUTION_TABLE_INIT_SCRIPT = """ + import groovy.json.JsonOutput + import org.gradle.api.artifacts.component.ModuleComponentIdentifier + + def reportDir = new File(System.getProperty("$TABLE_REPORT_DIR_PROPERTY")) + reportDir.mkdirs() + + gradle.rootProject { root -> root.tasks.register("$RESOLVE_TABLE_TASK") } + + allprojects { p -> + p.afterEvaluate { + def reportTask = p.tasks.register("opentaintReportTable") { + doLast { + def seen = [] as Set + def out = [] + p.configurations.findAll { it.canBeResolved }.each { cfg -> + try { + cfg.incoming.artifactView { v -> v.lenient = true }.artifacts.each { art -> + def id = art.id.componentIdentifier + if (!(id instanceof ModuleComponentIdentifier)) return + def coord = id.group + ":" + id.module + ":" + id.version + if (!seen.add(coord)) return + out.add([coord: coord, file: art.file.absolutePath]) + } + } catch (Exception e) { + println "opentaint: failed to resolve " + cfg.name + " in " + p.path + ": " + e + } + } + def name = "table-" + p.path.replace(":", "_") + ".json" + new File(reportDir, name).text = JsonOutput.toJson(out) + } + } + p.rootProject.tasks.named("$RESOLVE_TABLE_TASK").configure { dependsOn reportTask } + } + } + """.trimIndent() + private fun resolveGradleDependencyCmdArgs(workDir: Path, initScript: Path, reportDir: Path): List = listOf( "-Dorg.gradle.configureondemand=false", diff --git a/core/opentaint-jvm-autobuilder/src/test/kotlin/org/opentaint/project/GradleResolutionTableTest.kt b/core/opentaint-jvm-autobuilder/src/test/kotlin/org/opentaint/project/GradleResolutionTableTest.kt new file mode 100644 index 000000000..0f099834e --- /dev/null +++ b/core/opentaint-jvm-autobuilder/src/test/kotlin/org/opentaint/project/GradleResolutionTableTest.kt @@ -0,0 +1,28 @@ +package org.opentaint.project + +import kotlin.io.path.Path +import kotlin.io.path.createDirectories +import kotlin.io.path.writeText +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull +import java.nio.file.Files + +class GradleResolutionTableTest { + @Test + fun `folds per-project reports into one coordinate-keyed table`() { + val dir = Files.createTempDirectory("table").also { it.createDirectories() } + dir.resolve("table-_os-v2.json").writeText( + """[{"coord":"org.opensearch.client:opensearch-rest-client:2.18.0","file":"/g/os2/opensearch-rest-client-2.18.0.jar"}]""") + dir.resolve("table-_os-v3.json").writeText( + """[{"coord":"org.opensearch.client:opensearch-rest-client:3.5.0","file":"/g/os3/opensearch-rest-client-3.5.0.jar"}]""") + + val table = parseResolutionTable(dir) + + assertEquals(Path("/g/os2/opensearch-rest-client-2.18.0.jar"), + table["org.opensearch.client:opensearch-rest-client:2.18.0"]) + assertEquals(Path("/g/os3/opensearch-rest-client-3.5.0.jar"), + table["org.opensearch.client:opensearch-rest-client:3.5.0"]) + assertNull(table["org.opensearch.client:opensearch-rest-client:1.0.0"]) + } +} From 3a9ebeea164b6b4dc6f48dee016ef99060b6fbfe Mon Sep 17 00:00:00 2001 From: Aleksandr Misonizhnik Date: Tue, 4 Aug 2026 17:23:17 +0200 Subject: [PATCH 3/8] feat(autobuilder): Maven resolveDependencies carrying coordinates --- .../opentaint/project/MavenProjectResolver.kt | 9 +++++ .../project/MavenResolvedDependencyTest.kt | 35 +++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 core/opentaint-jvm-autobuilder/src/test/kotlin/org/opentaint/project/MavenResolvedDependencyTest.kt diff --git a/core/opentaint-jvm-autobuilder/src/main/kotlin/org/opentaint/project/MavenProjectResolver.kt b/core/opentaint-jvm-autobuilder/src/main/kotlin/org/opentaint/project/MavenProjectResolver.kt index 864a3595f..04624e341 100644 --- a/core/opentaint-jvm-autobuilder/src/main/kotlin/org/opentaint/project/MavenProjectResolver.kt +++ b/core/opentaint-jvm-autobuilder/src/main/kotlin/org/opentaint/project/MavenProjectResolver.kt @@ -159,6 +159,15 @@ class MavenProjectResolver( .filter { artifact -> artifact.artifactName !in buildArtifactsNames } .mapNotNull { artifact -> mavenLocalRepoPath.resolve(artifact.artifactJarPath).takeIf { it.exists() } } } + + fun resolveDependencies( + resolvePath: (MavenArtifact) -> Path? = { mavenLocalRepoPath.resolve(it.artifactJarPath).takeIf { p -> p.exists() } }, + ): List { + val buildArtifactsNames = buildArtifacts.mapTo(mutableSetOf()) { artifacts.getValue(it).artifactName } + return artifacts.values + .filter { it.artifactName !in buildArtifactsNames } + .mapNotNull { a -> resolvePath(a)?.let { ResolvedDependency(it, a.groupId, a.artifactId, a.version) } } + } } @Serializable diff --git a/core/opentaint-jvm-autobuilder/src/test/kotlin/org/opentaint/project/MavenResolvedDependencyTest.kt b/core/opentaint-jvm-autobuilder/src/test/kotlin/org/opentaint/project/MavenResolvedDependencyTest.kt new file mode 100644 index 000000000..67a361ed4 --- /dev/null +++ b/core/opentaint-jvm-autobuilder/src/test/kotlin/org/opentaint/project/MavenResolvedDependencyTest.kt @@ -0,0 +1,35 @@ +package org.opentaint.project + +import kotlin.io.path.Path +import kotlin.test.Test +import kotlin.test.assertEquals + +class MavenResolvedDependencyTest { + @Test + fun `resolved maven artifacts carry coordinates`() { + val r = MavenProjectResolver.MavenDependencyGraphResolver() + r.addDependencies(mavenGraphWith(group = "com.fasterxml.jackson.core", + artifact = "jackson-core", version = "2.17.3")) + val deps = r.resolveDependencies(resolvePath = { Path("/m2/${it.artifactId}-${it.version}.jar") }) + val jc = deps.single { it.artifact == "jackson-core" } + assertEquals("2.17.3", jc.version) + assertEquals(Path("/m2/jackson-core-2.17.3.jar"), jc.path) + } + + private fun mavenGraphWith(group: String, artifact: String, version: String): MavenProjectResolver.MavenDependencies { + val id = "$group:$artifact:jar:$version" + return MavenProjectResolver.MavenDependencies( + artifacts = listOf( + MavenProjectResolver.MavenArtifact( + id = id, + groupId = group, + artifactId = artifact, + version = version, + ) + ), + dependencies = listOf( + MavenProjectResolver.MavenDependency(from = "root", to = id), + ), + ) + } +} From 5a2ae3e3c26c64264605ef60a49343128528e38e Mon Sep 17 00:00:00 2001 From: Aleksandr Misonizhnik Date: Tue, 4 Aug 2026 17:30:03 +0200 Subject: [PATCH 4/8] feat(autobuilder): resolve Gradle deps from the resolution table, tag all deps --- .../project/GradleProjectResolver.kt | 57 ++++++++++--------- .../opentaint/project/MavenProjectResolver.kt | 11 +--- .../project/PortableProjectCreator.kt | 2 +- .../project/ProjectFromCPResolver.kt | 2 +- .../sast/project/ProjectAnalysisContext.kt | 2 +- 5 files changed, 36 insertions(+), 38 deletions(-) diff --git a/core/opentaint-jvm-autobuilder/src/main/kotlin/org/opentaint/project/GradleProjectResolver.kt b/core/opentaint-jvm-autobuilder/src/main/kotlin/org/opentaint/project/GradleProjectResolver.kt index 765023132..0ebeae15c 100644 --- a/core/opentaint-jvm-autobuilder/src/main/kotlin/org/opentaint/project/GradleProjectResolver.kt +++ b/core/opentaint-jvm-autobuilder/src/main/kotlin/org/opentaint/project/GradleProjectResolver.kt @@ -36,7 +36,9 @@ class GradleProjectResolver( override val projectSourceRoot: Path ) : ProjectResolver { private val resolvedModules = mutableListOf() - private val resolvedProjectDependencies = mutableListOf() + private val resolvedProjectDependencies = mutableListOf() + + private var resolutionTable: Map = emptyMap() private val json = Json { ignoreUnknownKeys = true } @@ -118,6 +120,23 @@ class GradleProjectResolver( } } + private val tableResolverInitScript: Path by lazy { + resolverDir.resolve("resolution-table.gradle").apply { writeText(GRADLE_RESOLUTION_TABLE_INIT_SCRIPT) } + } + + private fun resolveResolutionTable() { + val outDir = resolverDir.resolve("table-out").createDirectories() + val gradleExecutable = resolveGradleExecutable(projectSourceRoot) + val args = listOf(gradleExecutable) + gradleBuildFlags + listOf( + "--init-script", tableResolverInitScript.absolutePathString(), + "-D$TABLE_REPORT_DIR_PROPERTY=${outDir.absolutePathString()}", + RESOLVE_TABLE_TASK, + ) + val status = ProjectResolver.runCommand(projectSourceRoot, args, javaToolchain) + if (status != 0) logger.warn { "Gradle resolution-table returned $status for: $projectSourceRoot" } + resolutionTable = parseResolutionTable(outDir) + } + private fun resolveDependencies(): Boolean { val depGraphOutFolder = resolverDir.resolve("dg-out").createDirectories() @@ -131,6 +150,8 @@ class GradleProjectResolver( return false } + resolveResolutionTable() + logger.info { "Gradle resolution table size: ${resolutionTable.size} for: $projectSourceRoot" } resolveDependenciesFromGraph(depGraphOutFolder) return true @@ -145,7 +166,7 @@ class GradleProjectResolver( dependencyResolver.addDependencies(deps) } - resolvedProjectDependencies += dependencyResolver.resolveDependenciesJars() + resolvedProjectDependencies += dependencyResolver.resolveDependencies(resolutionTable) } private class GradleDependencyResolver { @@ -165,30 +186,14 @@ class GradleProjectResolver( } } - fun resolveDependenciesJars(): List { - val allDependenciesInfo = dependenciesInfo.entries.sortedBy { it.key } - - val resolvedDirectDependencies = allDependenciesInfo - .filter { it.key in directDependencies } - .mapNotNull { resolveJarPath(it.value) } - - val resolvedIndirectDependencies = allDependenciesInfo - .filter { it.key !in directDependencies } - .mapNotNull { resolveJarPath(it.value) } - - return resolvedDirectDependencies + resolvedIndirectDependencies - } - - private fun resolveJarPath(dependency: GradleDependencyInfo): Path? { - val gradlePath = gradleLocalRepoPath.resolve(dependency.gradleArtifactDir) - if (gradlePath.isDirectory()) { - gradlePath.walk().firstOrNull { it.name == dependency.artifactJarName }?.let { return it } - } - - val mavenPath = mavenLocalRepoPath.resolve(dependency.mavenArtifactDir).resolve(dependency.artifactJarName) - if (mavenPath.isRegularFile()) return mavenPath - - return null + fun resolveDependencies(table: Map): List { + val all = dependenciesInfo.entries.sortedBy { it.key } + val direct = all.filter { it.key in directDependencies } + val indirect = all.filterNot { it.key in directDependencies } + fun resolve(d: GradleDependencyInfo): ResolvedDependency? = + table["${d.groupId}:${d.artifactId}:${d.version}"] + ?.let { ResolvedDependency(it, d.groupId, d.artifactId, d.version) } + return (direct + indirect).mapNotNull { resolve(it.value) } } } diff --git a/core/opentaint-jvm-autobuilder/src/main/kotlin/org/opentaint/project/MavenProjectResolver.kt b/core/opentaint-jvm-autobuilder/src/main/kotlin/org/opentaint/project/MavenProjectResolver.kt index 04624e341..eac659c90 100644 --- a/core/opentaint-jvm-autobuilder/src/main/kotlin/org/opentaint/project/MavenProjectResolver.kt +++ b/core/opentaint-jvm-autobuilder/src/main/kotlin/org/opentaint/project/MavenProjectResolver.kt @@ -24,7 +24,7 @@ class MavenProjectResolver( private val resolverDir: Path, override val projectSourceRoot: Path ) : ProjectResolver { - private val resolvedProjectDependencies = mutableListOf() + private val resolvedProjectDependencies = mutableListOf() private val resolvedModules = mutableListOf() private var executableFound = true @@ -129,7 +129,7 @@ class MavenProjectResolver( dependencyResolver.addDependencies(deps) } - resolvedProjectDependencies += dependencyResolver.resolveDependenciesJars() + resolvedProjectDependencies += dependencyResolver.resolveDependencies() } class MavenDependencyGraphResolver { @@ -153,13 +153,6 @@ class MavenProjectResolver( } } - fun resolveDependenciesJars(): List { - val buildArtifactsNames = buildArtifacts.mapTo(mutableSetOf()) { artifacts.getValue(it).artifactName } - return artifacts.values - .filter { artifact -> artifact.artifactName !in buildArtifactsNames } - .mapNotNull { artifact -> mavenLocalRepoPath.resolve(artifact.artifactJarPath).takeIf { it.exists() } } - } - fun resolveDependencies( resolvePath: (MavenArtifact) -> Path? = { mavenLocalRepoPath.resolve(it.artifactJarPath).takeIf { p -> p.exists() } }, ): List { diff --git a/core/opentaint-jvm-autobuilder/src/main/kotlin/org/opentaint/project/PortableProjectCreator.kt b/core/opentaint-jvm-autobuilder/src/main/kotlin/org/opentaint/project/PortableProjectCreator.kt index 8575c604c..6a168f9dd 100644 --- a/core/opentaint-jvm-autobuilder/src/main/kotlin/org/opentaint/project/PortableProjectCreator.kt +++ b/core/opentaint-jvm-autobuilder/src/main/kotlin/org/opentaint/project/PortableProjectCreator.kt @@ -139,7 +139,7 @@ class PortableProjectCreator( sourceRoot = project.sourceRoot?.let { copySources(ctx, project, it) }, javaToolchain = project.javaToolchain?.let { copyToolchain(ctx, it) }, modules = project.modules.map { create(ctx, project, it) }, - dependencies = project.dependencies.map { copyDependency(ctx, it) }, + dependencies = project.dependencies.map { it.copy(path = copyDependency(ctx, it.path)) }, subProjects = project.subProjects.map { create(ctx, it) } ) diff --git a/core/opentaint-jvm-autobuilder/src/main/kotlin/org/opentaint/project/ProjectFromCPResolver.kt b/core/opentaint-jvm-autobuilder/src/main/kotlin/org/opentaint/project/ProjectFromCPResolver.kt index 736d991cf..de0fb2f5c 100644 --- a/core/opentaint-jvm-autobuilder/src/main/kotlin/org/opentaint/project/ProjectFromCPResolver.kt +++ b/core/opentaint-jvm-autobuilder/src/main/kotlin/org/opentaint/project/ProjectFromCPResolver.kt @@ -20,7 +20,7 @@ class ProjectFromCPResolver { val resolvedCp = projectCp.cp.flatMap { resolveProjectCp(it, resolverWorkDir) } val module = ProjectModuleClasses(rootDir, projectCp.pkg, resolvedCp) - return JavaProject(rootDir, projectCp.toolchain, listOf(module), projectCp.dependency) + return JavaProject(rootDir, projectCp.toolchain, listOf(module), projectCp.dependency.map { ResolvedDependency(it) }) } private var unpackId = 0 diff --git a/core/src/main/kotlin/org/opentaint/jvm/sast/project/ProjectAnalysisContext.kt b/core/src/main/kotlin/org/opentaint/jvm/sast/project/ProjectAnalysisContext.kt index 7e8c4704c..dc72e429b 100644 --- a/core/src/main/kotlin/org/opentaint/jvm/sast/project/ProjectAnalysisContext.kt +++ b/core/src/main/kotlin/org/opentaint/jvm/sast/project/ProjectAnalysisContext.kt @@ -55,7 +55,7 @@ private fun initializeProjectAnalysisContextUtil( options: ProjectAnalysisOptions, createAnalysisContext: AnalysisContextBuilder.() -> T ): T { - val dependencyFiles = project.dependencies.map { it.toFile() } + val dependencyFiles = project.dependencies.map { it.path.toFile() } val projectModulesFiles = run { val moduleFiles = mutableMapOf() for (module in project.modules) { From f32c4a57b3afa64f85d1be2cf5478c5a7cb03dad Mon Sep 17 00:00:00 2001 From: Aleksandr Misonizhnik Date: Tue, 4 Aug 2026 17:46:15 +0200 Subject: [PATCH 5/8] test(autobuilder): integration acceptance for reliable tagged resolution --- .../project/GradleTaggedResolutionIT.kt | 87 +++++++++++++++++++ .../gradle-fixture-project/build.gradle | 11 +++ .../gradle-fixture-project/settings.gradle | 1 + .../src/main/java/com/example/Fixture.java | 4 + 4 files changed, 103 insertions(+) create mode 100644 core/opentaint-jvm-autobuilder/src/test/kotlin/org/opentaint/project/GradleTaggedResolutionIT.kt create mode 100644 core/opentaint-jvm-autobuilder/src/test/resources/gradle-fixture-project/build.gradle create mode 100644 core/opentaint-jvm-autobuilder/src/test/resources/gradle-fixture-project/settings.gradle create mode 100644 core/opentaint-jvm-autobuilder/src/test/resources/gradle-fixture-project/src/main/java/com/example/Fixture.java diff --git a/core/opentaint-jvm-autobuilder/src/test/kotlin/org/opentaint/project/GradleTaggedResolutionIT.kt b/core/opentaint-jvm-autobuilder/src/test/kotlin/org/opentaint/project/GradleTaggedResolutionIT.kt new file mode 100644 index 000000000..bc7619109 --- /dev/null +++ b/core/opentaint-jvm-autobuilder/src/test/kotlin/org/opentaint/project/GradleTaggedResolutionIT.kt @@ -0,0 +1,87 @@ +package org.opentaint.project + +import java.nio.file.Files +import kotlin.io.path.createDirectories +import kotlin.io.path.deleteRecursively +import kotlin.io.path.ExperimentalPathApi +import kotlin.io.path.exists +import kotlin.io.path.isRegularFile +import kotlin.io.path.pathString +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNotNull +import kotlin.test.assertTrue +import kotlin.test.fail + +/** + * End-to-end acceptance for reliable tagged dependency resolution: runs the real autobuilder + * Gradle pipeline (init-script dependency graph + resolution table) against a small Gradle + * project with a real, versioned, external Maven Central dependency, then checks the emitted + * project model. + * + * This is a genuine integration test: it shells out to a real Gradle build and needs network + * access to Maven Central (for the fixture's `commons-lang3` dependency) and the Gradle Plugin + * Portal (for the `github-dependency-graph-gradle-plugin` init script). See + * `.superpowers/sdd/task-6-report.md` for the manual acceptance run this mirrors, and for the + * evidence captured against the full real-world repro-kit project. + */ +@OptIn(ExperimentalPathApi::class) +class GradleTaggedResolutionIT { + @Test + fun `gradle project dependencies come out tagged with coordinates and resolved to real files`() { + val fixtureResource = javaClass.classLoader.getResource("gradle-fixture-project") + ?: fail("gradle-fixture-project test resource not found on classpath") + + val workRoot = Files.createTempDirectory("gradle-tagged-resolution-it") + try { + val projectDir = workRoot.resolve("project").createDirectories() + java.nio.file.Paths.get(fixtureResource.toURI()).copyDirRecursivelyTo(projectDir) + + val buildDir = workRoot.resolve("build").createDirectories() + val resolved = ProjectResolver.resolveProject(projectDir, buildDir) + ?: fail("Autobuilder failed to resolve the fixture Gradle project") + + val modelPath = workRoot.resolve("model.yaml") + resolved.dump(modelPath) + + // Re-parse from the emitted YAML, exactly like a real consumer of the autobuilder's + // output would. + val loaded = JavaProject.load(modelPath) + + assertTrue(loaded.dependencies.isNotEmpty(), "Expected at least one resolved dependency") + + // Invariant 1: every dependency is tagged with group/artifact/version, not a bare path. + loaded.dependencies.forEach { dep -> + assertNotNull(dep.group, "Untagged dependency (missing group): $dep") + assertNotNull(dep.artifact, "Untagged dependency (missing artifact): $dep") + assertNotNull(dep.version, "Untagged dependency (missing version): $dep") + } + + // Invariant 2: every dependency path resolves to a real file on disk. + loaded.dependencies.forEach { dep -> + assertTrue( + dep.path.exists() && dep.path.isRegularFile(), + "Dependency path does not exist on disk: ${dep.path}" + ) + } + + // Invariant 3: no dependency path leaks a project/module build output directory. + loaded.dependencies.forEach { dep -> + assertFalse( + dep.path.pathString.contains("build/classes"), + "Dependency path leaks a project class directory: ${dep.path}" + ) + } + + // Invariant 4: the resolved version reflects what the build actually declares/resolves, + // not a stray cached version. + val commonsLang3 = loaded.dependencies.singleOrNull { it.artifact == "commons-lang3" } + ?: fail("Expected a resolved commons-lang3 dependency, got: ${loaded.dependencies}") + assertEquals("org.apache.commons", commonsLang3.group) + assertEquals("3.14.0", commonsLang3.version) + } finally { + workRoot.deleteRecursively() + } + } +} diff --git a/core/opentaint-jvm-autobuilder/src/test/resources/gradle-fixture-project/build.gradle b/core/opentaint-jvm-autobuilder/src/test/resources/gradle-fixture-project/build.gradle new file mode 100644 index 000000000..e723bf0d0 --- /dev/null +++ b/core/opentaint-jvm-autobuilder/src/test/resources/gradle-fixture-project/build.gradle @@ -0,0 +1,11 @@ +plugins { + id 'java' +} + +repositories { + mavenCentral() +} + +dependencies { + compileOnly 'org.apache.commons:commons-lang3:3.14.0' +} diff --git a/core/opentaint-jvm-autobuilder/src/test/resources/gradle-fixture-project/settings.gradle b/core/opentaint-jvm-autobuilder/src/test/resources/gradle-fixture-project/settings.gradle new file mode 100644 index 000000000..4e36d107f --- /dev/null +++ b/core/opentaint-jvm-autobuilder/src/test/resources/gradle-fixture-project/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'gradle-tagged-resolution-fixture' diff --git a/core/opentaint-jvm-autobuilder/src/test/resources/gradle-fixture-project/src/main/java/com/example/Fixture.java b/core/opentaint-jvm-autobuilder/src/test/resources/gradle-fixture-project/src/main/java/com/example/Fixture.java new file mode 100644 index 000000000..1762232de --- /dev/null +++ b/core/opentaint-jvm-autobuilder/src/test/resources/gradle-fixture-project/src/main/java/com/example/Fixture.java @@ -0,0 +1,4 @@ +package com.example; + +public class Fixture { +} From a9df9901d753bbde3afe0bbe738ee135a761e68e Mon Sep 17 00:00:00 2001 From: Aleksandr Misonizhnik Date: Tue, 4 Aug 2026 17:54:36 +0200 Subject: [PATCH 6/8] refactor(autobuilder): drop cache-probe members orphaned by resolveJarPath removal --- .../project/GradleProjectResolver.kt | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/core/opentaint-jvm-autobuilder/src/main/kotlin/org/opentaint/project/GradleProjectResolver.kt b/core/opentaint-jvm-autobuilder/src/main/kotlin/org/opentaint/project/GradleProjectResolver.kt index 0ebeae15c..927e56273 100644 --- a/core/opentaint-jvm-autobuilder/src/main/kotlin/org/opentaint/project/GradleProjectResolver.kt +++ b/core/opentaint-jvm-autobuilder/src/main/kotlin/org/opentaint/project/GradleProjectResolver.kt @@ -9,7 +9,6 @@ import java.nio.file.Path import kotlin.io.path.Path import kotlin.io.path.absolutePathString import kotlin.io.path.createDirectories -import kotlin.io.path.div import kotlin.io.path.exists import kotlin.io.path.extension import kotlin.io.path.isDirectory @@ -247,25 +246,9 @@ class GradleProjectResolver( } } - data class GradleDependencyInfo( - val groupId: String, - val artifactId: String, - val version: String, - ) { - val artifactJarName: String by lazy { "${artifactId}-${version}.jar" } - val mavenArtifactDir: List by lazy { groupId.split(".") + listOf(artifactId, version) } - val gradleArtifactDir: List by lazy { listOf(groupId, artifactId, version) } - } + data class GradleDependencyInfo(val groupId: String, val artifactId: String, val version: String) companion object { - private val mavenLocalRepoPath by lazy { - Path(System.getProperty("user.home")) / ".m2" / "repository" - } - - private val gradleLocalRepoPath by lazy { - Path(System.getProperty("user.home")) / ".gradle" / "caches" / "modules-2" / "files-2.1" - } - private const val GRADLE_SETTINGS_FILE = "settings.gradle" private const val GRADLE_SETTINGS_KTS_FILE = "$GRADLE_SETTINGS_FILE.kts" private const val GRADLE_BUILD_FILE = "build.gradle" From 65be3db05039453f0439c4b1cfcb5f51dafd2941 Mon Sep 17 00:00:00 2001 From: Aleksandr Misonizhnik Date: Thu, 13 Aug 2026 15:19:41 +0200 Subject: [PATCH 7/8] refactor(model): identify dependencies by purl --- .../project/GradleProjectResolver.kt | 2 +- .../opentaint/project/MavenProjectResolver.kt | 2 +- .../project/GradleTaggedResolutionIT.kt | 33 +++---- .../project/MavenResolvedDependencyTest.kt | 5 +- .../kotlin/org/opentaint/project/Project.kt | 52 +++++++++-- .../project/ResolvedDependencyTest.kt | 88 ++++++++++++++++--- 6 files changed, 139 insertions(+), 43 deletions(-) diff --git a/core/opentaint-jvm-autobuilder/src/main/kotlin/org/opentaint/project/GradleProjectResolver.kt b/core/opentaint-jvm-autobuilder/src/main/kotlin/org/opentaint/project/GradleProjectResolver.kt index 927e56273..1a76eeba4 100644 --- a/core/opentaint-jvm-autobuilder/src/main/kotlin/org/opentaint/project/GradleProjectResolver.kt +++ b/core/opentaint-jvm-autobuilder/src/main/kotlin/org/opentaint/project/GradleProjectResolver.kt @@ -191,7 +191,7 @@ class GradleProjectResolver( val indirect = all.filterNot { it.key in directDependencies } fun resolve(d: GradleDependencyInfo): ResolvedDependency? = table["${d.groupId}:${d.artifactId}:${d.version}"] - ?.let { ResolvedDependency(it, d.groupId, d.artifactId, d.version) } + ?.let { ResolvedDependency(it, mavenPurl(d.groupId, d.artifactId, d.version)) } return (direct + indirect).mapNotNull { resolve(it.value) } } } diff --git a/core/opentaint-jvm-autobuilder/src/main/kotlin/org/opentaint/project/MavenProjectResolver.kt b/core/opentaint-jvm-autobuilder/src/main/kotlin/org/opentaint/project/MavenProjectResolver.kt index eac659c90..3930ac02f 100644 --- a/core/opentaint-jvm-autobuilder/src/main/kotlin/org/opentaint/project/MavenProjectResolver.kt +++ b/core/opentaint-jvm-autobuilder/src/main/kotlin/org/opentaint/project/MavenProjectResolver.kt @@ -159,7 +159,7 @@ class MavenProjectResolver( val buildArtifactsNames = buildArtifacts.mapTo(mutableSetOf()) { artifacts.getValue(it).artifactName } return artifacts.values .filter { it.artifactName !in buildArtifactsNames } - .mapNotNull { a -> resolvePath(a)?.let { ResolvedDependency(it, a.groupId, a.artifactId, a.version) } } + .mapNotNull { a -> resolvePath(a)?.let { ResolvedDependency(it, mavenPurl(a.groupId, a.artifactId, a.version)) } } } } diff --git a/core/opentaint-jvm-autobuilder/src/test/kotlin/org/opentaint/project/GradleTaggedResolutionIT.kt b/core/opentaint-jvm-autobuilder/src/test/kotlin/org/opentaint/project/GradleTaggedResolutionIT.kt index bc7619109..f15624bdf 100644 --- a/core/opentaint-jvm-autobuilder/src/test/kotlin/org/opentaint/project/GradleTaggedResolutionIT.kt +++ b/core/opentaint-jvm-autobuilder/src/test/kotlin/org/opentaint/project/GradleTaggedResolutionIT.kt @@ -15,16 +15,9 @@ import kotlin.test.assertTrue import kotlin.test.fail /** - * End-to-end acceptance for reliable tagged dependency resolution: runs the real autobuilder - * Gradle pipeline (init-script dependency graph + resolution table) against a small Gradle - * project with a real, versioned, external Maven Central dependency, then checks the emitted - * project model. - * - * This is a genuine integration test: it shells out to a real Gradle build and needs network - * access to Maven Central (for the fixture's `commons-lang3` dependency) and the Gradle Plugin - * Portal (for the `github-dependency-graph-gradle-plugin` init script). See - * `.superpowers/sdd/task-6-report.md` for the manual acceptance run this mirrors, and for the - * evidence captured against the full real-world repro-kit project. + * Runs the real autobuilder Gradle pipeline against a small project with a versioned Maven Central + * dependency and checks the emitted model. Shells out to Gradle and needs network access (Maven + * Central + the Gradle Plugin Portal). */ @OptIn(ExperimentalPathApi::class) class GradleTaggedResolutionIT { @@ -45,17 +38,14 @@ class GradleTaggedResolutionIT { val modelPath = workRoot.resolve("model.yaml") resolved.dump(modelPath) - // Re-parse from the emitted YAML, exactly like a real consumer of the autobuilder's - // output would. + // Re-parse from the emitted YAML, like a real consumer would. val loaded = JavaProject.load(modelPath) assertTrue(loaded.dependencies.isNotEmpty(), "Expected at least one resolved dependency") - // Invariant 1: every dependency is tagged with group/artifact/version, not a bare path. + // Invariant 1: every dependency is tagged with a purl, not a bare path. loaded.dependencies.forEach { dep -> - assertNotNull(dep.group, "Untagged dependency (missing group): $dep") - assertNotNull(dep.artifact, "Untagged dependency (missing artifact): $dep") - assertNotNull(dep.version, "Untagged dependency (missing version): $dep") + assertNotNull(dep.purl, "Untagged dependency (missing purl): $dep") } // Invariant 2: every dependency path resolves to a real file on disk. @@ -74,12 +64,11 @@ class GradleTaggedResolutionIT { ) } - // Invariant 4: the resolved version reflects what the build actually declares/resolves, - // not a stray cached version. - val commonsLang3 = loaded.dependencies.singleOrNull { it.artifact == "commons-lang3" } - ?: fail("Expected a resolved commons-lang3 dependency, got: ${loaded.dependencies}") - assertEquals("org.apache.commons", commonsLang3.group) - assertEquals("3.14.0", commonsLang3.version) + // Invariant 4: the resolved version reflects what the build declares, not a stray cached one. + val commonsLang3 = loaded.dependencies.singleOrNull { + it.purl == "pkg:maven/org.apache.commons/commons-lang3@3.14.0" + } ?: fail("Expected a resolved commons-lang3 dependency, got: ${loaded.dependencies}") + assertEquals("pkg:maven/org.apache.commons/commons-lang3@3.14.0", commonsLang3.purl) } finally { workRoot.deleteRecursively() } diff --git a/core/opentaint-jvm-autobuilder/src/test/kotlin/org/opentaint/project/MavenResolvedDependencyTest.kt b/core/opentaint-jvm-autobuilder/src/test/kotlin/org/opentaint/project/MavenResolvedDependencyTest.kt index 67a361ed4..249093e24 100644 --- a/core/opentaint-jvm-autobuilder/src/test/kotlin/org/opentaint/project/MavenResolvedDependencyTest.kt +++ b/core/opentaint-jvm-autobuilder/src/test/kotlin/org/opentaint/project/MavenResolvedDependencyTest.kt @@ -6,13 +6,12 @@ import kotlin.test.assertEquals class MavenResolvedDependencyTest { @Test - fun `resolved maven artifacts carry coordinates`() { + fun `resolved maven artifacts carry a purl`() { val r = MavenProjectResolver.MavenDependencyGraphResolver() r.addDependencies(mavenGraphWith(group = "com.fasterxml.jackson.core", artifact = "jackson-core", version = "2.17.3")) val deps = r.resolveDependencies(resolvePath = { Path("/m2/${it.artifactId}-${it.version}.jar") }) - val jc = deps.single { it.artifact == "jackson-core" } - assertEquals("2.17.3", jc.version) + val jc = deps.single { it.purl == "pkg:maven/com.fasterxml.jackson.core/jackson-core@2.17.3" } assertEquals(Path("/m2/jackson-core-2.17.3.jar"), jc.path) } diff --git a/core/opentaint-project-model/src/main/kotlin/org/opentaint/project/Project.kt b/core/opentaint-project-model/src/main/kotlin/org/opentaint/project/Project.kt index d5fd52066..85492d51f 100644 --- a/core/opentaint-project-model/src/main/kotlin/org/opentaint/project/Project.kt +++ b/core/opentaint-project-model/src/main/kotlin/org/opentaint/project/Project.kt @@ -3,12 +3,17 @@ package org.opentaint.project import com.charleskorn.kaml.SingleLineStringStyle import com.charleskorn.kaml.Yaml import com.charleskorn.kaml.YamlConfiguration +import com.charleskorn.kaml.YamlInput +import com.charleskorn.kaml.YamlScalar import com.charleskorn.kaml.decodeFromStream import com.charleskorn.kaml.encodeToStream +import kotlinx.serialization.ContextualSerializer +import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import java.nio.file.Path @@ -22,17 +27,54 @@ sealed interface CommonProject { fun sourceRoot(): Path? } -@Serializable +/** + * Builds a Maven package-URL of the form `pkg:maven//@`. Maven + * group/artifact/version are simple tokens, so no percent-encoding is applied. + */ +fun mavenPurl(group: String, artifact: String, version: String): String = + "pkg:maven/$group/$artifact@$version" + +@Serializable(with = ResolvedDependencySerializer::class) data class ResolvedDependency( - val path: @Serializable(with = PathAsStringSerializer::class) Path, - val group: String? = null, - val artifact: String? = null, - val version: String? = null, + val path: Path, + val purl: String? = null, ) { fun relativeTo(base: Path): ResolvedDependency = copy(path = path.relativeTo(base)) fun resolve(base: Path): ResolvedDependency = copy(path = base.resolve(path)) } +@Serializable +private data class ResolvedDependencySurrogate( + val path: @Serializable(with = PathAsStringSerializer::class) Path, + val purl: String? = null, +) + +/** + * Decodes the tagged mapping via a private surrogate, or a bare scalar (legacy `- /path.jar`) as a + * path-only dependency. The descriptor is CONTEXTUAL so kaml routes scalar nodes here instead of + * rejecting them before [deserialize] runs. + */ +object ResolvedDependencySerializer : KSerializer { + @OptIn(ExperimentalSerializationApi::class) + override val descriptor: SerialDescriptor = + ContextualSerializer(ResolvedDependency::class).descriptor + + override fun serialize(encoder: Encoder, value: ResolvedDependency) { + encoder.encodeSerializableValue( + ResolvedDependencySurrogate.serializer(), + ResolvedDependencySurrogate(value.path, value.purl), + ) + } + + override fun deserialize(decoder: Decoder): ResolvedDependency { + if (decoder is YamlInput && decoder.node is YamlScalar) { + return ResolvedDependency(path = Path(decoder.decodeString())) + } + val surrogate = decoder.decodeSerializableValue(ResolvedDependencySurrogate.serializer()) + return ResolvedDependency(surrogate.path, surrogate.purl) + } +} + @Suppress("DEPRECATION") @Serializable data class JavaProject( diff --git a/core/opentaint-project-model/src/test/kotlin/org/opentaint/project/ResolvedDependencyTest.kt b/core/opentaint-project-model/src/test/kotlin/org/opentaint/project/ResolvedDependencyTest.kt index e915b5d5a..1b3918e8e 100644 --- a/core/opentaint-project-model/src/test/kotlin/org/opentaint/project/ResolvedDependencyTest.kt +++ b/core/opentaint-project-model/src/test/kotlin/org/opentaint/project/ResolvedDependencyTest.kt @@ -1,39 +1,105 @@ package org.opentaint.project import kotlin.io.path.Path +import kotlin.io.path.createTempFile +import kotlin.io.path.writeText import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNull class ResolvedDependencyTest { @Test - fun `relativeTo then resolve round-trips the path and keeps coordinates`() { + fun `mavenPurl formats a pkg-maven package-URL`() { + assertEquals( + "pkg:maven/org.opensearch.client/opensearch-rest-client@2.18.0", + mavenPurl("org.opensearch.client", "opensearch-rest-client", "2.18.0"), + ) + } + + @Test + fun `relativeTo then resolve round-trips the path and keeps the purl`() { val abs = ResolvedDependency( path = Path("/repo/model/deps/opensearch-rest-client-2.18.0.jar"), - group = "org.opensearch.client", artifact = "opensearch-rest-client", version = "2.18.0", + purl = mavenPurl("org.opensearch.client", "opensearch-rest-client", "2.18.0"), ) val rel = abs.relativeTo(Path("/repo/model")) assertEquals(Path("deps/opensearch-rest-client-2.18.0.jar"), rel.path) - assertEquals("2.18.0", rel.version) + assertEquals("pkg:maven/org.opensearch.client/opensearch-rest-client@2.18.0", rel.purl) assertEquals(abs.path, rel.resolve(Path("/repo/model")).path) } @Test - fun `coordinates are optional`() { - val d = ResolvedDependency(path = Path("/x/lib.jar"), group = null, artifact = null, version = null) - assertNull(d.version) + fun `purl is optional`() { + val d = ResolvedDependency(path = Path("/x/lib.jar")) + assertNull(d.purl) + } + + @Test + fun `a JavaProject serializes purl-tagged dependencies and loads them back`() { + val jp = JavaProject( + sourceRoot = Path("/p"), + dependencies = listOf( + ResolvedDependency(Path("/d/a.jar"), mavenPurl("g", "a", "1.0")), + ), + ) + val tmp = createTempFile(suffix = ".yaml") + jp.dump(tmp) + val loaded = JavaProject.load(tmp) + val dep = loaded.dependencies.single() + assertEquals(Path("/d/a.jar"), dep.path) + assertEquals("pkg:maven/g/a@1.0", dep.purl) } @Test - fun `a JavaProject serializes tagged dependencies and loads them back`() { + fun `a dependency without a purl round-trips`() { val jp = JavaProject( sourceRoot = Path("/p"), - dependencies = listOf(ResolvedDependency(Path("/d/a.jar"), "g", "a", "1.0")), + dependencies = listOf(ResolvedDependency(Path("/d/plain.jar"))), ) - val tmp = kotlin.io.path.createTempFile(suffix = ".yaml") + val tmp = createTempFile(suffix = ".yaml") jp.dump(tmp) val loaded = JavaProject.load(tmp) - assertEquals("1.0", loaded.dependencies.single().version) - assertEquals(Path("/d/a.jar"), loaded.dependencies.single().path) + val dep = loaded.dependencies.single() + assertEquals(Path("/d/plain.jar"), dep.path) + assertNull(dep.purl) + } + + private val legacyJavaProjectYaml = """ + sourceRoot: /p + dependencies: + - /path/to/a.jar + - /path/to/b.jar + """.trimIndent() + + private val legacyProjectYaml = """ + projectRoot: /p + javaProjects: + - sourceRoot: /p + dependencies: + - /some/bare/path.jar + """.trimIndent() + + @Test + fun `JavaProject load accepts a legacy bare-string dependency as a path-only dependency`() { + val tmp = createTempFile(suffix = ".yaml") + tmp.writeText(legacyJavaProjectYaml) + val loaded = JavaProject.load(tmp) + assertEquals( + listOf(Path("/path/to/a.jar"), Path("/path/to/b.jar")), + loaded.dependencies.map { it.path }, + ) + loaded.dependencies.forEach { dep -> + assertNull(dep.purl) + } + } + + @Test + fun `Project load accepts a Project-shaped legacy bare-string dependency as a path-only dependency`() { + val tmp = createTempFile(suffix = ".yaml") + tmp.writeText(legacyProjectYaml) + val loaded = Project.load(tmp) + val dep = loaded.javaProjects.single().dependencies.single() + assertEquals(Path("/some/bare/path.jar"), dep.path) + assertNull(dep.purl) } } From e9e90d146ff0612442911834a9961745afec1858 Mon Sep 17 00:00:00 2001 From: Aleksandr Misonizhnik Date: Thu, 13 Aug 2026 15:42:56 +0200 Subject: [PATCH 8/8] test(analyzer): build JavaProject test fixture with ResolvedDependency The dependency schema flip (List -> List) left this analyzer test helper passing bare paths; core:compileTestKotlin failed in CI. --- .../test/kotlin/org/opentaint/jvm/sast/ast/BasicTestUtils.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/test/kotlin/org/opentaint/jvm/sast/ast/BasicTestUtils.kt b/core/src/test/kotlin/org/opentaint/jvm/sast/ast/BasicTestUtils.kt index 5998505d3..69bc9e15b 100644 --- a/core/src/test/kotlin/org/opentaint/jvm/sast/ast/BasicTestUtils.kt +++ b/core/src/test/kotlin/org/opentaint/jvm/sast/ast/BasicTestUtils.kt @@ -22,6 +22,7 @@ import org.opentaint.common.sast.sarif.TracePathNodeEntry import org.opentaint.common.sast.sarif.TracePathNodeKind import org.opentaint.project.JavaProject import org.opentaint.project.ProjectModuleClasses +import org.opentaint.project.ResolvedDependency import java.io.File import java.nio.file.Path import java.util.jar.JarFile @@ -65,7 +66,7 @@ abstract class BasicTestUtils { moduleClasses = listOf(samplesJar) ) ), - dependencies = dependencyJars + dependencies = dependencyJars.map { ResolvedDependency(it) } ) val options = ProjectAnalysisOptions()