cmd/govulncheck: move more functions to internal package
Move more govulncheck functionality to a separate package
so gopls can use it.
Change-Id: I48c86935f23cb9fe54eb0897f32d8be29eece4c3
Reviewed-on: https://go-review.googlesource.com/c/vuln/+/406937
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
Reviewed-by: Zvonimir Pavlinovic <zpavlinovic@google.com>
diff --git a/cmd/govulncheck/html.go b/cmd/govulncheck/html.go
index 090a464..2f7b678 100644
--- a/cmd/govulncheck/html.go
+++ b/cmd/govulncheck/html.go
@@ -22,7 +22,7 @@
func html(w io.Writer, r *vulncheck.Result, ci *govulncheck.CallInfo) error {
tmpl, err := template.New("govulncheck.tmpl").Funcs(template.FuncMap{
- "funcName": funcName,
+ "funcName": govulncheck.FuncName,
}).ParseFS(staticContent, "static/govulncheck.tmpl")
if err != nil {
return err
@@ -50,7 +50,7 @@
ID: v0.OSV.ID,
PkgPath: v0.PkgPath,
CurrentVersion: ci.ModuleVersions[v0.ModPath],
- FixedVersion: "v" + latestFixed(v0.OSV.Affected),
+ FixedVersion: "v" + govulncheck.LatestFixed(v0.OSV.Affected),
Reference: fmt.Sprintf("https://pkg.go.dev/vuln/%s", v0.OSV.ID),
Details: v0.OSV.Details,
}
@@ -58,7 +58,7 @@
for _, v := range vg {
if css := ci.CallStacks[v]; len(css) > 0 {
vn.Stacks = append(vn.Stacks, callstack{
- Summary: summarizeCallStack(css[0], ci.TopPackages, v.PkgPath),
+ Summary: govulncheck.SummarizeCallStack(css[0], ci.TopPackages, v.PkgPath),
Stack: css[0],
})
}
diff --git a/cmd/govulncheck/internal/govulncheck/util.go b/cmd/govulncheck/internal/govulncheck/util.go
new file mode 100644
index 0000000..6699236
--- /dev/null
+++ b/cmd/govulncheck/internal/govulncheck/util.go
@@ -0,0 +1,106 @@
+// 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 govulncheck
+
+import (
+ "fmt"
+ "strings"
+
+ "golang.org/x/mod/semver"
+ "golang.org/x/vuln/osv"
+ "golang.org/x/vuln/vulncheck"
+)
+
+// LatestFixed returns the latest fixed version in the list of affected ranges,
+// or the empty string if there are no fixed versions.
+func LatestFixed(as []osv.Affected) string {
+ v := ""
+ for _, a := range as {
+ for _, r := range a.Ranges {
+ if r.Type == osv.TypeSemver {
+ for _, e := range r.Events {
+ if e.Fixed != "" && (v == "" || semver.Compare(e.Fixed, v) > 0) {
+ v = e.Fixed
+ }
+ }
+ }
+ }
+ }
+ return v
+}
+
+// SummarizeCallStack returns a short description of the call stack.
+// It uses one of two forms, depending on what the lowest function F in topPkgs
+// calls:
+// - If it calls a function V from the vulnerable package, then summarizeCallStack
+// returns "F calls V".
+// - If it calls a function G in some other package, which eventually calls V,
+// it returns "F calls G, which eventually calls V".
+//
+// If it can't find any of these functions, summarizeCallStack returns the empty string.
+func SummarizeCallStack(cs vulncheck.CallStack, topPkgs map[string]bool, vulnPkg string) string {
+ // Find the lowest function in the top packages.
+ iTop := lowest(cs, func(e vulncheck.StackEntry) bool {
+ return topPkgs[PkgPath(e.Function)]
+ })
+ if iTop < 0 {
+ return ""
+ }
+ // Find the highest function in the vulnerable package that is below iTop.
+ iVuln := highest(cs[iTop+1:], func(e vulncheck.StackEntry) bool {
+ return PkgPath(e.Function) == vulnPkg
+ })
+ if iVuln < 0 {
+ return ""
+ }
+ iVuln += iTop + 1 // adjust for slice in call to highest.
+ topName := FuncName(cs[iTop].Function)
+ vulnName := FuncName(cs[iVuln].Function)
+ if iVuln == iTop+1 {
+ return fmt.Sprintf("%s calls %s", topName, vulnName)
+ }
+ return fmt.Sprintf("%s calls %s, which eventually calls %s",
+ topName, FuncName(cs[iTop+1].Function), vulnName)
+}
+
+// highest returns the highest (one with the smallest index) entry in the call
+// stack for which f returns true.
+func highest(cs vulncheck.CallStack, f func(e vulncheck.StackEntry) bool) int {
+ for i := 0; i < len(cs); i++ {
+ if f(cs[i]) {
+ return i
+ }
+ }
+ return -1
+}
+
+// lowest returns the lowest (one with the largets index) entry in the call
+// stack for which f returns true.
+func lowest(cs vulncheck.CallStack, f func(e vulncheck.StackEntry) bool) int {
+ for i := len(cs) - 1; i >= 0; i-- {
+ if f(cs[i]) {
+ return i
+ }
+ }
+ return -1
+}
+
+// PkgPath returns the package path from fn.
+func PkgPath(fn *vulncheck.FuncNode) string {
+ if fn.PkgPath != "" {
+ return fn.PkgPath
+ }
+ s := strings.TrimPrefix(fn.RecvType, "*")
+ if i := strings.LastIndexByte(s, '.'); i > 0 {
+ s = s[:i]
+ }
+ return s
+}
+
+// FuncName returns the function name from fn, adjusted
+// to remove pointer annotations.
+func FuncName(fn *vulncheck.FuncNode) string {
+ return strings.TrimPrefix(fn.String(), "*")
+}
diff --git a/cmd/govulncheck/internal/govulncheck/util_test.go b/cmd/govulncheck/internal/govulncheck/util_test.go
new file mode 100644
index 0000000..67e7772
--- /dev/null
+++ b/cmd/govulncheck/internal/govulncheck/util_test.go
@@ -0,0 +1,82 @@
+// 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 govulncheck
+
+import (
+ "strings"
+ "testing"
+
+ "golang.org/x/vuln/vulncheck"
+)
+
+func TestPkgPath(t *testing.T) {
+ for _, test := range []struct {
+ in vulncheck.FuncNode
+ want string
+ }{
+ {
+ vulncheck.FuncNode{PkgPath: "math", Name: "Floor"},
+ "math",
+ },
+ {
+ vulncheck.FuncNode{RecvType: "a.com/b.T", Name: "M"},
+ "a.com/b",
+ },
+ {
+ vulncheck.FuncNode{RecvType: "*a.com/b.T", Name: "M"},
+ "a.com/b",
+ },
+ } {
+ got := PkgPath(&test.in)
+ if got != test.want {
+ t.Errorf("%+v: got %q, want %q", test.in, got, test.want)
+ }
+ }
+}
+
+func TestSummarizeCallStack(t *testing.T) {
+ topPkgs := map[string]bool{"t1": true, "t2": true}
+ vulnPkg := "v"
+
+ for _, test := range []struct {
+ in, want string
+ }{
+ {"a.F", ""},
+ {"t1.F", ""},
+ {"v.V", ""},
+ {
+ "t1.F v.V",
+ "t1.F calls v.V",
+ },
+ {
+ "t1.F t2.G v.V1 v.v2",
+ "t2.G calls v.V1",
+ },
+ {
+ "t1.F x.Y t2.G a.H b.I c.J v.V",
+ "t2.G calls a.H, which eventually calls v.V",
+ },
+ } {
+ in := stringToCallStack(test.in)
+ got := SummarizeCallStack(in, topPkgs, vulnPkg)
+ if got != test.want {
+ t.Errorf("%s:\ngot %s\nwant %s", test.in, got, test.want)
+ }
+ }
+}
+
+func stringToCallStack(s string) vulncheck.CallStack {
+ var cs vulncheck.CallStack
+ for _, e := range strings.Fields(s) {
+ parts := strings.Split(e, ".")
+ cs = append(cs, vulncheck.StackEntry{
+ Function: &vulncheck.FuncNode{
+ PkgPath: parts[0],
+ Name: parts[1],
+ },
+ })
+ }
+ return cs
+}
diff --git a/cmd/govulncheck/main.go b/cmd/govulncheck/main.go
index 050a142..7e8a0f0 100644
--- a/cmd/govulncheck/main.go
+++ b/cmd/govulncheck/main.go
@@ -28,12 +28,10 @@
"sort"
"strings"
- "golang.org/x/mod/semver"
"golang.org/x/tools/go/buildutil"
"golang.org/x/tools/go/packages"
"golang.org/x/vuln/client"
"golang.org/x/vuln/cmd/govulncheck/internal/govulncheck"
- "golang.org/x/vuln/osv"
"golang.org/x/vuln/vulncheck"
)
@@ -167,11 +165,11 @@
v0 := vg[0]
line("package:", v0.PkgPath)
line("your version:", ci.ModuleVersions[v0.ModPath])
- line("fixed version:", "v"+latestFixed(v0.OSV.Affected))
+ line("fixed version:", "v"+govulncheck.LatestFixed(v0.OSV.Affected))
var summaries []string
for _, v := range vg {
if css := ci.CallStacks[v]; len(css) > 0 {
- if sum := summarizeCallStack(css[0], ci.TopPackages, v.PkgPath); sum != "" {
+ if sum := govulncheck.SummarizeCallStack(css[0], ci.TopPackages, v.PkgPath); sum != "" {
summaries = append(summaries, sum)
}
}
@@ -219,95 +217,6 @@
return pkgs, nil
}
-// latestFixed returns the latest fixed version in the list of affected ranges,
-// or the empty string if there are no fixed versions.
-func latestFixed(as []osv.Affected) string {
- v := ""
- for _, a := range as {
- for _, r := range a.Ranges {
- if r.Type == osv.TypeSemver {
- for _, e := range r.Events {
- if e.Fixed != "" && (v == "" || semver.Compare(e.Fixed, v) > 0) {
- v = e.Fixed
- }
- }
- }
- }
- }
- return v
-}
-
-// summarizeCallStack returns a short description of the call stack.
-// It uses one of two forms, depending on what the lowest function F in topPkgs
-// calls:
-// - If it calls a function V from the vulnerable package, then summarizeCallStack
-// returns "F calls V".
-// - If it calls a function G in some other package, which eventually calls V,
-// it returns "F calls G, which eventually calls V".
-//
-// If it can't find any of these functions, summarizeCallStack returns the empty string.
-func summarizeCallStack(cs vulncheck.CallStack, topPkgs map[string]bool, vulnPkg string) string {
- // Find the lowest function in the top packages.
- iTop := lowest(cs, func(e vulncheck.StackEntry) bool {
- return topPkgs[pkgPath(e.Function)]
- })
- if iTop < 0 {
- return ""
- }
- // Find the highest function in the vulnerable package that is below iTop.
- iVuln := highest(cs[iTop+1:], func(e vulncheck.StackEntry) bool {
- return pkgPath(e.Function) == vulnPkg
- })
- if iVuln < 0 {
- return ""
- }
- iVuln += iTop + 1 // adjust for slice in call to highest.
- topName := funcName(cs[iTop].Function)
- vulnName := funcName(cs[iVuln].Function)
- if iVuln == iTop+1 {
- return fmt.Sprintf("%s calls %s", topName, vulnName)
- }
- return fmt.Sprintf("%s calls %s, which eventually calls %s",
- topName, funcName(cs[iTop+1].Function), vulnName)
-}
-
-// highest returns the highest (one with the smallest index) entry in the call
-// stack for which f returns true.
-func highest(cs vulncheck.CallStack, f func(e vulncheck.StackEntry) bool) int {
- for i := 0; i < len(cs); i++ {
- if f(cs[i]) {
- return i
- }
- }
- return -1
-}
-
-// lowest returns the lowest (one with the largets index) entry in the call
-// stack for which f returns true.
-func lowest(cs vulncheck.CallStack, f func(e vulncheck.StackEntry) bool) int {
- for i := len(cs) - 1; i >= 0; i-- {
- if f(cs[i]) {
- return i
- }
- }
- return -1
-}
-
-func pkgPath(fn *vulncheck.FuncNode) string {
- if fn.PkgPath != "" {
- return fn.PkgPath
- }
- s := strings.TrimPrefix(fn.RecvType, "*")
- if i := strings.LastIndexByte(s, '.'); i > 0 {
- s = s[:i]
- }
- return s
-}
-
-func funcName(fn *vulncheck.FuncNode) string {
- return strings.TrimPrefix(fn.String(), "*")
-}
-
// compact replaces consecutive runs of equal elements with a single copy.
// This is like the uniq command found on Unix.
// compact modifies the contents of the slice s; it does not create a new slice.
diff --git a/cmd/govulncheck/main_test.go b/cmd/govulncheck/main_test.go
index 9f0a6d8..28f3553 100644
--- a/cmd/govulncheck/main_test.go
+++ b/cmd/govulncheck/main_test.go
@@ -15,13 +15,12 @@
"os/exec"
"path/filepath"
"regexp"
- "strings"
"testing"
"github.com/google/go-cmdtest"
+ "golang.org/x/vuln/cmd/govulncheck/internal/govulncheck"
"golang.org/x/vuln/internal/buildtest"
"golang.org/x/vuln/osv"
- "golang.org/x/vuln/vulncheck"
)
var update = flag.Bool("update", false, "update test files with results")
@@ -158,80 +157,10 @@
},
} {
t.Run(test.name, func(t *testing.T) {
- got := latestFixed(test.in)
+ got := govulncheck.LatestFixed(test.in)
if got != test.want {
t.Errorf("got %q, want %q", got, test.want)
}
})
}
}
-
-func TestPkgPath(t *testing.T) {
- for _, test := range []struct {
- in vulncheck.FuncNode
- want string
- }{
- {
- vulncheck.FuncNode{PkgPath: "math", Name: "Floor"},
- "math",
- },
- {
- vulncheck.FuncNode{RecvType: "a.com/b.T", Name: "M"},
- "a.com/b",
- },
- {
- vulncheck.FuncNode{RecvType: "*a.com/b.T", Name: "M"},
- "a.com/b",
- },
- } {
- got := pkgPath(&test.in)
- if got != test.want {
- t.Errorf("%+v: got %q, want %q", test.in, got, test.want)
- }
- }
-}
-
-func TestSummarizeCallStack(t *testing.T) {
- topPkgs := map[string]bool{"t1": true, "t2": true}
- vulnPkg := "v"
-
- for _, test := range []struct {
- in, want string
- }{
- {"a.F", ""},
- {"t1.F", ""},
- {"v.V", ""},
- {
- "t1.F v.V",
- "t1.F calls v.V",
- },
- {
- "t1.F t2.G v.V1 v.v2",
- "t2.G calls v.V1",
- },
- {
- "t1.F x.Y t2.G a.H b.I c.J v.V",
- "t2.G calls a.H, which eventually calls v.V",
- },
- } {
- in := stringToCallStack(test.in)
- got := summarizeCallStack(in, topPkgs, vulnPkg)
- if got != test.want {
- t.Errorf("%s:\ngot %s\nwant %s", test.in, got, test.want)
- }
- }
-}
-
-func stringToCallStack(s string) vulncheck.CallStack {
- var cs vulncheck.CallStack
- for _, e := range strings.Fields(s) {
- parts := strings.Split(e, ".")
- cs = append(cs, vulncheck.StackEntry{
- Function: &vulncheck.FuncNode{
- PkgPath: parts[0],
- Name: parts[1],
- },
- })
- }
- return cs
-}