cmd/govulncheck: fail in GOPATH mode

govulncheck requires module information to find vulnerabilities.
But in GOPATH mode, there is no module information.
Instead of silently succeeding in that case, govulncheck fails with an error.

Also, fix an off-by-one bug that could result in a panic if only the top
function in a call stack is in a top package.

Fixes golang/go#51591

Change-Id: I9923c1f03aa0a101de86fe03daaeeefc1d1f5bdf
Reviewed-on: https://go-review.googlesource.com/c/exp/+/394774
Trust: Jonathan Amsterdam <jba@google.com>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Zvonimir Pavlinovic <zpavlinovic@google.com>
diff --git a/cmd/govulncheck/main.go b/cmd/govulncheck/main.go
index 8eb2794..19eeb75 100644
--- a/cmd/govulncheck/main.go
+++ b/cmd/govulncheck/main.go
@@ -88,8 +88,11 @@
 	ctx := context.Background()
 
 	patterns := flag.Args()
-	var r *vulncheck.Result
-	var pkgs []*packages.Package
+	var (
+		r              *vulncheck.Result
+		pkgs           []*packages.Package
+		moduleVersions map[string]string
+	)
 	if len(patterns) == 1 && isFile(patterns[0]) {
 		f, err := os.Open(patterns[0])
 		if err != nil {
@@ -110,6 +113,17 @@
 		if err != nil {
 			die("govulncheck: %v", err)
 		}
+		// Build a map from module paths to versions.
+		moduleVersions = map[string]string{}
+		packages.Visit(pkgs, nil, func(p *packages.Package) {
+			if m := packageModule(p); m != nil {
+				moduleVersions[m.Path] = m.Version
+			}
+		})
+
+		if len(moduleVersions) == 0 {
+			die("govulncheck: no modules found; are you in GOPATH mode? Module mode required.")
+		}
 		r, err = vulncheck.Source(ctx, vulncheck.Convert(pkgs), vcfg)
 		if err != nil {
 			die("govulncheck: %v", err)
@@ -118,7 +132,7 @@
 	if *jsonFlag {
 		writeJSON(r)
 	} else {
-		writeText(r, pkgs)
+		writeText(r, pkgs, moduleVersions)
 	}
 	exitCode := 0
 	// Following go vet, fail with 3 if there are findings (in this case, vulns).
@@ -137,17 +151,10 @@
 	fmt.Println()
 }
 
-func writeText(r *vulncheck.Result, pkgs []*packages.Package) {
+func writeText(r *vulncheck.Result, pkgs []*packages.Package, moduleVersions map[string]string) {
 	if len(r.Vulns) == 0 {
 		return
 	}
-	// Build a map from module paths to versions.
-	moduleVersions := map[string]string{}
-	packages.Visit(pkgs, nil, func(p *packages.Package) {
-		if m := packageModule(p); m != nil {
-			moduleVersions[m.Path] = m.Version
-		}
-	})
 	callStacks := vulncheck.CallStacks(r)
 
 	const labelWidth = 16
@@ -283,7 +290,7 @@
 		for _, cs := range callStacks[v] {
 			// Find the lowest function in the stack that is in
 			// one of the top packages.
-			for i := len(cs) - 1; i > 0; i-- {
+			for i := len(cs) - 1; i >= 0; i-- {
 				pkg := pkgPath(cs[i].Function)
 				if topPkgs[pkg] {
 					fns[cs[i].Function] = true