internal/govulncheck: fail in GOPATH mode

Follows https://go-review.git.corp.google.com/c/vuln/+/395241

Fixes golang/go#53741

Change-Id: I9d751cd40530fd31c1d86c26dd4f718681b7719c
Reviewed-on: https://go-review.googlesource.com/c/vuln/+/443455
Run-TryBot: Zvonimir Pavlinovic <zpavlinovic@google.com>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/cmd/govulncheck/testdata/nogomod.ct b/cmd/govulncheck/testdata/nogomod.ct
index 4390707..8f4a255 100644
--- a/cmd/govulncheck/testdata/nogomod.ct
+++ b/cmd/govulncheck/testdata/nogomod.ct
@@ -6,6 +6,7 @@
 Scanning for dependencies with known vulnerabilities...
 govulncheck: no go.mod file
 
-govulncheck only works Go with modules. To make your project a module, run go mod init.
+govulncheck only works Go with modules. Try navigating to your module directory.
+Otherwise, run go mod init to make your project a module.
 
 See https://go.dev/doc/modules/managing-dependencies for more information.
diff --git a/internal/govulncheck/errors.go b/internal/govulncheck/errors.go
index ab63872..100ac75 100644
--- a/internal/govulncheck/errors.go
+++ b/internal/govulncheck/errors.go
@@ -8,6 +8,8 @@
 	"errors"
 	"os"
 	"strings"
+
+	"golang.org/x/vuln/vulncheck"
 )
 
 var (
@@ -30,7 +32,8 @@
 	// ErrNoGoSum indicates that a go.mod file was not found in this module.
 	ErrNoGoMod = errors.New(`no go.mod file
 
-govulncheck only works Go with modules. To make your project a module, run go mod init.
+govulncheck only works Go with modules. Try navigating to your module directory.
+Otherwise, run go mod init to make your project a module.
 
 See https://go.dev/doc/modules/managing-dependencies for more information.`)
 
@@ -40,6 +43,14 @@
 Your module is missing a go.sum file. Try running go mod tidy.
 
 See https://go.dev/doc/modules/managing-dependencies for more information.`)
+
+	// ErrNoModVersion indicates that govulncheck cannot access module version information.
+	ErrNoModVersion = errors.New(`no module version information
+
+This can happen when running govulncheck in GOPATH mode. govulncheck needs module
+versions to correctly identify vulnerabilities.
+
+See https://go.dev/doc/modules/managing-dependencies for more information.`)
 )
 
 // fileExists checks if file path exists. Returns true
@@ -65,3 +76,34 @@
 	return strings.Contains(msg, "This application uses version go") &&
 		strings.Contains(msg, "It may fail to process source files")
 }
+
+// inGoPathMode checks if govulncheck is running in GOPATH mode by checking
+// if module information is available.
+func inGoPathMode(pkgs []*vulncheck.Package) bool {
+	packageModule := func(p *vulncheck.Package) *vulncheck.Module {
+		m := p.Module
+		if m == nil {
+			return nil
+		}
+		if r := m.Replace; r != nil {
+			return r
+		}
+		return m
+	}
+
+	hasModuleInfo := false
+	var visit func(p *vulncheck.Package)
+	visit = func(p *vulncheck.Package) {
+		if packageModule(p) != nil {
+			hasModuleInfo = true
+			return
+		}
+		for _, i := range p.Imports {
+			visit(i)
+		}
+	}
+	for _, p := range pkgs {
+		visit(p)
+	}
+	return !hasModuleInfo
+}
diff --git a/internal/govulncheck/legacy_run.go b/internal/govulncheck/legacy_run.go
index 52292fb..97e080d 100644
--- a/internal/govulncheck/legacy_run.go
+++ b/internal/govulncheck/legacy_run.go
@@ -69,6 +69,10 @@
 			}
 			return nil, err
 		}
+		// If we are in GOPATH mode, then no version information will be available.
+		if inGoPathMode(pkgs) {
+			return nil, ErrNoModVersion
+		}
 
 		// Sort pkgs so that the PkgNodes returned by vulncheck.Source will be
 		// deterministic.