cmd/govulncheck: fix version output for stdlib
When writing the version of a package path in the standard library,
use the Go tag (e.g. go1.16), not the semantic version (e.g. v1.16.0).
Fixes #53948.
Change-Id: Iee00b5b48005da2150239e456dd007731c151e61
Reviewed-on: https://go-review.googlesource.com/c/vuln/+/418455
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
Reviewed-by: Julie Qiu <julieqiu@google.com>
diff --git a/cmd/govulncheck/main.go b/cmd/govulncheck/main.go
index 043e78b..474ac63 100644
--- a/cmd/govulncheck/main.go
+++ b/cmd/govulncheck/main.go
@@ -230,11 +230,11 @@
fmt.Println()
found := v0.PkgPath
if v := ci.ModuleVersions[v0.ModPath]; v != "" {
- found += "@" + v
+ found = packageVersionString(v0.PkgPath, v[1:])
}
fmt.Printf("Found in: %v\n", found)
if fixed := govulncheck.LatestFixed(v0.OSV.Affected); fixed != "" {
- fmt.Printf("Fixed in: %s@v%s\n", v0.PkgPath, fixed)
+ fmt.Printf("Fixed in: %s\n", packageVersionString(v0.PkgPath, fixed))
}
fmt.Printf("More info: https://pkg.go.dev/vuln/%s\n", v0.OSV.ID)
fmt.Println()
@@ -345,6 +345,14 @@
return string(bytes.TrimSpace(out))
}
+func packageVersionString(packagePath, version string) string {
+ v := "v" + version
+ if importPathInStdlib(packagePath) {
+ v = semverToGoTag(v)
+ }
+ return fmt.Sprintf("%s@%s", packagePath, v)
+}
+
func die(format string, args ...interface{}) {
fmt.Fprintf(os.Stderr, format+"\n", args...)
os.Exit(1)
diff --git a/cmd/govulncheck/stdlib.go b/cmd/govulncheck/stdlib.go
new file mode 100644
index 0000000..a7b5be8
--- /dev/null
+++ b/cmd/govulncheck/stdlib.go
@@ -0,0 +1,80 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "fmt"
+ "strings"
+
+ "golang.org/x/mod/semver"
+)
+
+// Support functions for standard library packages.
+// These are copied from the internal/stdlib package in the pkgsite repo.
+
+// semverToGoTag returns the Go standard library repository tag corresponding
+// to semver, a version string without the initial "v".
+// Go tags differ from standard semantic versions in a few ways,
+// such as beginning with "go" instead of "v".
+func semverToGoTag(v string) string {
+ if strings.HasPrefix(v, "v0.0.0") {
+ return "master"
+ }
+ // Special case: v1.0.0 => go1.
+ if v == "v1.0.0" {
+ return "go1"
+ }
+ if !semver.IsValid(v) {
+ return fmt.Sprintf("<!%s:invalid semver>", v)
+ }
+ goVersion := semver.Canonical(v)
+ prerelease := semver.Prerelease(goVersion)
+ versionWithoutPrerelease := strings.TrimSuffix(goVersion, prerelease)
+ patch := strings.TrimPrefix(versionWithoutPrerelease, semver.MajorMinor(goVersion)+".")
+ if patch == "0" {
+ versionWithoutPrerelease = strings.TrimSuffix(versionWithoutPrerelease, ".0")
+ }
+ goVersion = fmt.Sprintf("go%s", strings.TrimPrefix(versionWithoutPrerelease, "v"))
+ if prerelease != "" {
+ // Go prereleases look like "beta1" instead of "beta.1".
+ // "beta1" is bad for sorting (since beta10 comes before beta9), so
+ // require the dot form.
+ i := finalDigitsIndex(prerelease)
+ if i >= 1 {
+ if prerelease[i-1] != '.' {
+ return fmt.Sprintf("<!%s:final digits in a prerelease must follow a period>", v)
+ }
+ // Remove the dot.
+ prerelease = prerelease[:i-1] + prerelease[i:]
+ }
+ goVersion += strings.TrimPrefix(prerelease, "-")
+ }
+ return goVersion
+}
+
+// finalDigitsIndex returns the index of the first digit in the sequence of digits ending s.
+// If s doesn't end in digits, it returns -1.
+func finalDigitsIndex(s string) int {
+ // Assume ASCII (since the semver package does anyway).
+ var i int
+ for i = len(s) - 1; i >= 0; i-- {
+ if s[i] < '0' || s[i] > '9' {
+ break
+ }
+ }
+ if i == len(s)-1 {
+ return -1
+ }
+ return i + 1
+}
+
+// importPathInStdlib reports whether the given import path could be part of the Go standard library,
+// by reporting whether the first component lacks a '.'.
+func importPathInStdlib(path string) bool {
+ if i := strings.IndexByte(path, '/'); i != -1 {
+ path = path[:i]
+ }
+ return !strings.Contains(path, ".")
+}
diff --git a/cmd/govulncheck/testdata/stdlib.ct b/cmd/govulncheck/testdata/stdlib.ct
index 743a4c6..2657350 100644
--- a/cmd/govulncheck/testdata/stdlib.ct
+++ b/cmd/govulncheck/testdata/stdlib.ct
@@ -13,5 +13,5 @@
Call stacks in your code:
golang.org/stdvuln.main calls archive/zip.OpenReader
-Found in: archive/zip@v1.18.0
+Found in: archive/zip@go1.18
More info: https://pkg.go.dev/vuln/STD