vulncheck: introduce vulncheck.Package
This change introduces vulncheck.Package representation of input
packages and adds conversion API from packages.Package.
This change will be followed by a change that replaces uses of
packages.Package with those of vulncheck.Package.
Change-Id: I4e72bc8e934ebca86d3039ae27ae05820b73d9d6
Reviewed-on: https://go-review.googlesource.com/c/exp/+/369134
Run-TryBot: Zvonimir Pavlinovic <zpavlinovic@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Julie Qiu <julie@golang.org>
Trust: Zvonimir Pavlinovic <zpavlinovic@google.com>
diff --git a/vulncheck/helpers_test.go b/vulncheck/helpers_test.go
index 58afe38..1c63f8e 100644
--- a/vulncheck/helpers_test.go
+++ b/vulncheck/helpers_test.go
@@ -132,6 +132,49 @@
return m
}
+func pkgPathToImports(pkgs []*Package) map[string][]string {
+ m := make(map[string][]string)
+ seen := make(map[*Package]bool)
+ var visit func(*Package)
+ visit = func(p *Package) {
+ if seen[p] {
+ return
+ }
+ seen[p] = true
+ var imports []string
+ for _, i := range p.Imports {
+ imports = append(imports, i.PkgPath)
+ visit(i)
+ }
+ m[p.PkgPath] = imports
+ }
+ for _, p := range pkgs {
+ visit(p)
+ }
+ sortStrMap(m)
+ return m
+}
+
+func modulePathToVersion(pkgs []*Package) map[string]string {
+ m := make(map[string]string)
+ seen := make(map[*Package]bool)
+ var visit func(*Package)
+ visit = func(p *Package) {
+ if seen[p] || p.Module == nil {
+ return
+ }
+ seen[p] = true
+ for _, i := range p.Imports {
+ visit(i)
+ }
+ m[p.Module.Path] = p.Module.Version
+ }
+ for _, p := range pkgs {
+ visit(p)
+ }
+ return m
+}
+
// sortStrMap sorts the map string slice values to make them deterministic.
func sortStrMap(m map[string][]string) {
for _, strs := range m {
diff --git a/vulncheck/vulncheck.go b/vulncheck/vulncheck.go
index cd9b698..38b6c2c 100644
--- a/vulncheck/vulncheck.go
+++ b/vulncheck/vulncheck.go
@@ -8,7 +8,9 @@
import (
"fmt"
+ "go/ast"
"go/token"
+ "go/types"
"strings"
"golang.org/x/tools/go/packages"
@@ -25,6 +27,80 @@
Client client.Client
}
+// Package models Go package for vulncheck analysis. A version
+// of packages.Package trimmed down to reduce memory consumption.
+type Package struct {
+ Name string
+ PkgPath string
+ Imports []*Package
+ Pkg *types.Package
+ Fset *token.FileSet
+ Syntax []*ast.File
+ TypesInfo *types.Info
+ Module *Module
+}
+
+// Module models Go module for vulncheck analysis.
+type Module struct {
+ Path string
+ Version string
+ Dir string
+ Replace *Module
+}
+
+// Convert converts a slice of packages.Package to
+// a slice of corresponding vulncheck.Package.
+func Convert(pkgs []*packages.Package) []*Package {
+ ms := make(map[*packages.Module]*Module)
+ var mod func(*packages.Module) *Module
+ mod = func(m *packages.Module) *Module {
+ if m == nil {
+ return nil
+ }
+ if vm, ok := ms[m]; ok {
+ return vm
+ }
+ vm := &Module{
+ Path: m.Path,
+ Version: m.Version,
+ Dir: m.Dir,
+ Replace: mod(m.Replace),
+ }
+ ms[m] = vm
+ return vm
+ }
+
+ ps := make(map[*packages.Package]*Package)
+ var pkg func(*packages.Package) *Package
+ pkg = func(p *packages.Package) *Package {
+ if vp, ok := ps[p]; ok {
+ return vp
+ }
+
+ vp := &Package{
+ Name: p.Name,
+ PkgPath: p.PkgPath,
+ Pkg: p.Types,
+ Fset: p.Fset,
+ Syntax: p.Syntax,
+ TypesInfo: p.TypesInfo,
+ Module: mod(p.Module),
+ }
+ ps[p] = vp
+
+ for _, i := range p.Imports {
+ vp.Imports = append(vp.Imports, pkg(i))
+ }
+ return vp
+ }
+
+ var vpkgs []*Package
+ for _, p := range pkgs {
+ vpkgs = append(vpkgs, pkg(p))
+ }
+ return vpkgs
+}
+
// Result contains information on which vulnerabilities are potentially affecting
// user code and how are they affecting it via call graph, package imports graph,
// and module requires graph.
diff --git a/vulncheck/vulncheck_test.go b/vulncheck/vulncheck_test.go
index d4441bc..d68a1ee 100644
--- a/vulncheck/vulncheck_test.go
+++ b/vulncheck/vulncheck_test.go
@@ -5,10 +5,12 @@
package vulncheck
import (
+ "path"
"reflect"
"testing"
"golang.org/x/tools/go/packages"
+ "golang.org/x/tools/go/packages/packagestest"
"golang.org/x/vulndb/osv"
)
@@ -216,3 +218,74 @@
t.Fatalf("VulnsForPackage returned unexpected results, got:\n%s\nwant:\n%s", vulnsToString(filtered), vulnsToString(expected))
}
}
+
+func TestConvert(t *testing.T) {
+ e := packagestest.Export(t, packagestest.Modules, []packagestest.Module{
+ {
+ Name: "golang.org/entry",
+ Files: map[string]interface{}{
+ "x/x.go": `
+ package x
+
+ import "golang.org/amod/avuln"
+ `}},
+ {
+ Name: "golang.org/zmod@v0.0.0",
+ Files: map[string]interface{}{"z/z.go": `
+ package z
+ `},
+ },
+ {
+ Name: "golang.org/amod@v1.1.3",
+ Files: map[string]interface{}{"avuln/avuln.go": `
+ package avuln
+
+ import "golang.org/wmod/w"
+ `},
+ },
+ {
+ Name: "golang.org/bmod@v0.5.0",
+ Files: map[string]interface{}{"bvuln/bvuln.go": `
+ package bvuln
+ `},
+ },
+ {
+ Name: "golang.org/wmod@v0.0.0",
+ Files: map[string]interface{}{"w/w.go": `
+ package w
+
+ import "golang.org/bmod/bvuln"
+ `},
+ },
+ })
+ defer e.Cleanup()
+
+ // Load x and y as entry packages.
+ pkgs, err := loadPackages(e, path.Join(e.Temp(), "entry/x"), path.Join(e.Temp(), "entry/y"))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ vpkgs := Convert(pkgs)
+
+ wantPkgs := map[string][]string{
+ "golang.org/amod/avuln": {"golang.org/wmod/w"},
+ "golang.org/bmod/bvuln": nil,
+ "golang.org/entry/x": {"golang.org/amod/avuln"},
+ "golang.org/entry/y": nil,
+ "golang.org/wmod/w": {"golang.org/bmod/bvuln"},
+ }
+ if got := pkgPathToImports(vpkgs); !reflect.DeepEqual(got, wantPkgs) {
+ t.Errorf("want %v;got %v", wantPkgs, got)
+ }
+
+ wantMods := map[string]string{
+ "golang.org/amod": "v1.1.3",
+ "golang.org/bmod": "v0.5.0",
+ "golang.org/entry": "",
+ "golang.org/wmod": "v0.0.0",
+ }
+ if got := modulePathToVersion(vpkgs); !reflect.DeepEqual(got, wantMods) {
+ t.Errorf("want %v;got %v", wantMods, got)
+ }
+}