From 655eada5e7bbc6e0cbeee08c29974fd8f7b5fc39 Mon Sep 17 00:00:00 2001 From: Wolfgang Klenk Date: Mon, 20 Nov 2023 10:43:37 +0100 Subject: [PATCH] fix(analyzer): Support uppercase-letters in Go module version Uppercase-letters in version strings of dependent Go modules caused the analyzer to crash. With this fix, uppercase-letters are now properly escaped as any other paths of the Go modules in the file system. Fixes #7880. Signed-off-by: Wolfgang Klenk --- .../go/src/main/kotlin/GoMod.kt | 19 ++++++++++++++++++- .../go/src/test/kotlin/GoModTest.kt | 19 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/plugins/package-managers/go/src/main/kotlin/GoMod.kt b/plugins/package-managers/go/src/main/kotlin/GoMod.kt index 53347dfa96375..306386a18812c 100644 --- a/plugins/package-managers/go/src/main/kotlin/GoMod.kt +++ b/plugins/package-managers/go/src/main/kotlin/GoMod.kt @@ -454,7 +454,8 @@ private fun ModuleInfo.toSourceArtifact(): RemoteArtifact { } private fun ModuleInfo.toVcsInfo(): VcsInfo? { - val infoFile = goMod?.let { File(it).resolveSibling("$version.info") } ?: return null + val escapedVersion = escapeVersion(version) + val infoFile = goMod?.let { File(it).resolveSibling("$escapedVersion.info") } ?: return null val info = infoFile.inputStream().use { JSON.decodeFromStream(it) } val type = info.origin.vcs?.let { VcsType.forName(it) }.takeIf { it == VcsType.GIT } ?: return null @@ -484,3 +485,19 @@ internal fun parseWhyOutput(output: String): Set { return usedModules } + +/** + * Module paths appear as substrings of file system paths in the module cache on the file system. + * Go does not rely on the file system to be case-sensitive. For this reason, Go has decided to + * replace every uppercase letter in file system paths with an exclamation mark followed by the + * letter's lowercase equivalent. + * + * Details behind the reasoning and implementation in Go can be found in the Go source code at + * [module.go](https://github.com/golang/go/blob/5b6d3dea8744311825fd544a73edb8d26d9c7e98/src/cmd/vendor/golang.org/x/mod/module/module.go#L33-L42C64) + */ +internal fun escapeVersion(version: String): String { + require("!" !in version) { "Module versions must not contain exclamation marks: $version" } + return version.replace(upperCaseCharRegex) { "!${it.value.lowercase()}" } +} + +private val upperCaseCharRegex = Regex("[A-Z]") diff --git a/plugins/package-managers/go/src/test/kotlin/GoModTest.kt b/plugins/package-managers/go/src/test/kotlin/GoModTest.kt index 29566e9627db6..76fb83b6f3913 100644 --- a/plugins/package-managers/go/src/test/kotlin/GoModTest.kt +++ b/plugins/package-managers/go/src/test/kotlin/GoModTest.kt @@ -19,10 +19,12 @@ package org.ossreviewtoolkit.plugins.packagemanagers.go +import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.WordSpec import io.kotest.matchers.collections.beEmpty import io.kotest.matchers.collections.containExactlyInAnyOrder import io.kotest.matchers.should +import io.kotest.matchers.shouldBe class GoModTest : WordSpec({ "parseWhyOutput" should { @@ -69,4 +71,21 @@ class GoModTest : WordSpec({ ) } } + + "escapeVersion" should { + "escape uppercase letters" { + val version = "v0.1.0-MS4.0.20231102094829-08e0c3cd016c" + val escapedVersion = escapeVersion(version) + + escapedVersion shouldBe "v0.1.0-!m!s4.0.20231102094829-08e0c3cd016c" + } + + "throw exception if version string contains an exclamation mark" { + val version = "v1.0.0!" + + shouldThrow { + escapeVersion(version) + } + } + } })