cmd/govulncheck: work with Go 1.17

The govulncheck command will now work on source if compiled with
Go 1.17. It will fail if run on a binary.

Run the tests that match command output only on 1.18 or higher.

Change-Id: Ia3e23a130f822c55dee94b95dc2a01c96b6269e5
Reviewed-on: https://go-review.googlesource.com/c/vuln/+/411454
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Zvonimir Pavlinovic <zpavlinovic@google.com>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
diff --git a/cmd/govulncheck/binary_118.go b/cmd/govulncheck/binary_118.go
new file mode 100644
index 0000000..6c13fb7
--- /dev/null
+++ b/cmd/govulncheck/binary_118.go
@@ -0,0 +1,19 @@
+// 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.
+
+//go:build go1.18
+// +build go1.18
+
+package main
+
+import (
+	"context"
+	"io"
+
+	"golang.org/x/vuln/vulncheck"
+)
+
+func binary(ctx context.Context, exe io.ReaderAt, cfg *vulncheck.Config) (_ *vulncheck.Result, err error) {
+	return vulncheck.Binary(ctx, exe, cfg)
+}
diff --git a/cmd/govulncheck/binary_not118.go b/cmd/govulncheck/binary_not118.go
new file mode 100644
index 0000000..04bd8a6
--- /dev/null
+++ b/cmd/govulncheck/binary_not118.go
@@ -0,0 +1,20 @@
+// 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.
+
+//go:build !go1.18
+// +build !go1.18
+
+package main
+
+import (
+	"context"
+	"errors"
+	"io"
+
+	"golang.org/x/vuln/vulncheck"
+)
+
+func binary(ctx context.Context, exe io.ReaderAt, cfg *vulncheck.Config) (_ *vulncheck.Result, err error) {
+	return nil, errors.New("compile with Go 1.18 or higher to analyze binary files")
+}
diff --git a/cmd/govulncheck/doc.go b/cmd/govulncheck/doc.go
index 04f097e..00bfdea 100644
--- a/cmd/govulncheck/doc.go
+++ b/cmd/govulncheck/doc.go
@@ -2,16 +2,12 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.18
-// +build go1.18
-
 /*
 Command govulncheck reports known vulnerabilities that affect Go code. It uses
 static analysis or a binary's symbol table to narrow down reports to only those
 that potentially affect the application. For more information about the API
 behind govulncheck, see https://go.dev/security/vulncheck.
 
-
 By default, govulncheck uses the Go vulnerability database at
 https://vuln.go.dev. Set the GOVULNDB environment variable to specify a different database.
 The database must follow the specification at https://go.dev/security/vulndb.
@@ -21,7 +17,7 @@
 WARNING: govulncheck is still EXPERIMENTAL and neither its output or the vulnerability
 database should be relied on to be stable or comprehensive.
 
-Usage
+# Usage
 
 To analyze source code, run govulncheck from the module directory, using the
 same package path syntax that the go command uses:
@@ -52,7 +48,7 @@
 Its output and exit codes are as described above, except that without source it cannot
 produce call stacks.
 
-Other Modes
+# Other Modes
 
 A few flags control govulncheck's output. Regardless of output, govulncheck
 exits with code 0 if there are no vulnerabilities and 3 if there are.
@@ -65,7 +61,7 @@
 The -json flag outputs a JSON object with vulnerability information. The output
 corresponds to the type golang.org/x/vuln/vulncheck.Result.
 
-Weaknesses
+# Weaknesses
 
 Govulncheck uses static analysis, which is inherently imprecise. If govulncheck
 identifies a sequence of calls in your program that leads to a vulnerable
diff --git a/cmd/govulncheck/formatting.go b/cmd/govulncheck/formatting.go
index d1a7b02..10814a4 100644
--- a/cmd/govulncheck/formatting.go
+++ b/cmd/govulncheck/formatting.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.18
-// +build go1.18
-
 package main
 
 import (
diff --git a/cmd/govulncheck/formatting_test.go b/cmd/govulncheck/formatting_test.go
index d91dd0f..48fdd9e 100644
--- a/cmd/govulncheck/formatting_test.go
+++ b/cmd/govulncheck/formatting_test.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.18
-// +build go1.18
-
 package main
 
 import (
diff --git a/cmd/govulncheck/html.go b/cmd/govulncheck/html.go
index f66751a..552479b 100644
--- a/cmd/govulncheck/html.go
+++ b/cmd/govulncheck/html.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.18
-// +build go1.18
-
 package main
 
 import (
diff --git a/cmd/govulncheck/internal/govulncheck/cache.go b/cmd/govulncheck/internal/govulncheck/cache.go
index 404c356..0948837 100644
--- a/cmd/govulncheck/internal/govulncheck/cache.go
+++ b/cmd/govulncheck/internal/govulncheck/cache.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.18
-// +build go1.18
-
 // Package govulncheck supports the govulncheck command.
 package govulncheck
 
diff --git a/cmd/govulncheck/internal/govulncheck/cache_test.go b/cmd/govulncheck/internal/govulncheck/cache_test.go
index 5a25c78..0917ae8 100644
--- a/cmd/govulncheck/internal/govulncheck/cache_test.go
+++ b/cmd/govulncheck/internal/govulncheck/cache_test.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.18
-// +build go1.18
-
 package govulncheck
 
 import (
diff --git a/cmd/govulncheck/internal/govulncheck/source.go b/cmd/govulncheck/internal/govulncheck/source.go
index 23028b9..01255fc 100644
--- a/cmd/govulncheck/internal/govulncheck/source.go
+++ b/cmd/govulncheck/internal/govulncheck/source.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.18
-// +build go1.18
-
 package govulncheck
 
 import (
diff --git a/cmd/govulncheck/internal/govulncheck/util.go b/cmd/govulncheck/internal/govulncheck/util.go
index baa2d96..6699236 100644
--- a/cmd/govulncheck/internal/govulncheck/util.go
+++ b/cmd/govulncheck/internal/govulncheck/util.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.18
-// +build go1.18
-
 package govulncheck
 
 import (
diff --git a/cmd/govulncheck/internal/govulncheck/util_test.go b/cmd/govulncheck/internal/govulncheck/util_test.go
index 3288cd8..67e7772 100644
--- a/cmd/govulncheck/internal/govulncheck/util_test.go
+++ b/cmd/govulncheck/internal/govulncheck/util_test.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.18
-// +build go1.18
-
 package govulncheck
 
 import (
diff --git a/cmd/govulncheck/main.go b/cmd/govulncheck/main.go
index 1d80457..21e5225 100644
--- a/cmd/govulncheck/main.go
+++ b/cmd/govulncheck/main.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.18
-// +build go1.18
-
 package main
 
 import (
@@ -37,7 +34,7 @@
 
 	govulncheck [flags] {package pattern...}
 
-	govulncheck [flags] {binary path}
+	govulncheck [flags] {binary path} (if built with Go 1.18 or higher)
 
 Flags:
 
@@ -101,7 +98,7 @@
 			die("govulncheck: %v", err)
 		}
 		defer f.Close()
-		r, err = vulncheck.Binary(ctx, f, vcfg)
+		r, err = binary(ctx, f, vcfg)
 		if err != nil {
 			die("govulncheck: %v", err)
 		}
diff --git a/cmd/govulncheck/main_test.go b/cmd/govulncheck/main_test.go
index 32dd1c2..fa07746 100644
--- a/cmd/govulncheck/main_test.go
+++ b/cmd/govulncheck/main_test.go
@@ -2,93 +2,15 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.18
-// +build go1.18
-
 package main
 
 import (
-	"errors"
-	"flag"
-	"fmt"
-	"os"
-	"os/exec"
-	"path/filepath"
-	"regexp"
 	"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"
 )
 
-var update = flag.Bool("update", false, "update test files with results")
-
-func TestCommand(t *testing.T) {
-	testDir, err := os.Getwd()
-	if err != nil {
-		t.Fatal(err)
-	}
-	ts, err := cmdtest.Read("testdata")
-	if err != nil {
-		t.Fatal(err)
-	}
-	ts.DisableLogging = false
-	// Define a command that lets us cd into a module directory.
-	// The modules for these tests live under testdata/modules.
-	ts.Commands["cdmodule"] = func(args []string, inputFile string) ([]byte, error) {
-		if len(args) != 1 {
-			return nil, errors.New("need exactly 1 argument")
-		}
-		return nil, os.Chdir(filepath.Join(testDir, "testdata", "modules", args[0]))
-	}
-	// Define a command that runs govulncheck with our local DB. We can't use
-	// cmdtest.Program for this because it doesn't let us set the environment,
-	// and that is the only way to tell govulncheck about an alternative vuln
-	// database.
-	binary, cleanup := buildtest.GoBuild(t, ".") // build govulncheck
-	defer cleanup()
-	ts.Commands["govulncheck"] = func(args []string, inputFile string) ([]byte, error) {
-		cmd := exec.Command(binary, args...)
-		if inputFile != "" {
-			return nil, errors.New("input redirection makes no sense")
-		}
-		cmd.Env = append(os.Environ(), "GOVULNDB=file://"+testDir+"/testdata/vulndb")
-		out, err := cmd.CombinedOutput()
-		out = filterGoFilePaths(out)
-		return out, err
-	}
-
-	// Build test module binaries.
-	moduleDirs, err := filepath.Glob("testdata/modules/*")
-	if err != nil {
-		t.Fatal(err)
-	}
-	for _, md := range moduleDirs {
-		binary, cleanup := buildtest.GoBuild(t, md)
-		defer cleanup()
-		// Set an environment variable to the path to the binary, so tests
-		// can refer to it.
-		varName := filepath.Base(md) + "_binary"
-		os.Setenv(varName, binary)
-	}
-	ts.Run(t, *update)
-}
-
-var goFileRegexp = regexp.MustCompile(`[^\s"]*\.go[\s":]`)
-
-// filterGoFilePaths  modifies paths to Go files by replacing their directory with "...".
-// For example,/a/b/c.go becomes .../c.go .
-// This makes it possible to compare govulncheck output across systems, because
-// Go filenames include setup-specific paths.
-func filterGoFilePaths(data []byte) []byte {
-	return goFileRegexp.ReplaceAllFunc(data, func(b []byte) []byte {
-		s := string(b)
-		return []byte(fmt.Sprintf(`.../%s%c`, filepath.Base(s[1:len(s)-1]), s[len(s)-1]))
-	})
-}
-
 func TestLatestFixed(t *testing.T) {
 	for _, test := range []struct {
 		name string
diff --git a/cmd/govulncheck/main_test_command_118.go b/cmd/govulncheck/main_test_command_118.go
new file mode 100644
index 0000000..ec51145
--- /dev/null
+++ b/cmd/govulncheck/main_test_command_118.go
@@ -0,0 +1,91 @@
+// 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.
+
+// Only run this on Go 1.18 or higher, because govulncheck can't
+// run on binaries before 1.18.
+
+//go:build go1.18
+// +build go1.18
+
+package main
+
+import (
+	"errors"
+	"flag"
+	"fmt"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"regexp"
+	"testing"
+
+	"github.com/google/go-cmdtest"
+	"golang.org/x/vuln/internal/buildtest"
+)
+
+var update = flag.Bool("update", false, "update test files with results")
+
+func TestCommand(t *testing.T) {
+	testDir, err := os.Getwd()
+	if err != nil {
+		t.Fatal(err)
+	}
+	ts, err := cmdtest.Read("testdata")
+	if err != nil {
+		t.Fatal(err)
+	}
+	ts.DisableLogging = false
+	// Define a command that lets us cd into a module directory.
+	// The modules for these tests live under testdata/modules.
+	ts.Commands["cdmodule"] = func(args []string, inputFile string) ([]byte, error) {
+		if len(args) != 1 {
+			return nil, errors.New("need exactly 1 argument")
+		}
+		return nil, os.Chdir(filepath.Join(testDir, "testdata", "modules", args[0]))
+	}
+	// Define a command that runs govulncheck with our local DB. We can't use
+	// cmdtest.Program for this because it doesn't let us set the environment,
+	// and that is the only way to tell govulncheck about an alternative vuln
+	// database.
+	binary, cleanup := buildtest.GoBuild(t, ".") // build govulncheck
+	defer cleanup()
+	ts.Commands["govulncheck"] = func(args []string, inputFile string) ([]byte, error) {
+		cmd := exec.Command(binary, args...)
+		if inputFile != "" {
+			return nil, errors.New("input redirection makes no sense")
+		}
+		cmd.Env = append(os.Environ(), "GOVULNDB=file://"+testDir+"/testdata/vulndb")
+		out, err := cmd.CombinedOutput()
+		out = filterGoFilePaths(out)
+		return out, err
+	}
+
+	// Build test module binaries.
+	moduleDirs, err := filepath.Glob("testdata/modules/*")
+	if err != nil {
+		t.Fatal(err)
+	}
+	for _, md := range moduleDirs {
+		binary, cleanup := buildtest.GoBuild(t, md)
+		defer cleanup()
+		// Set an environment variable to the path to the binary, so tests
+		// can refer to it.
+		varName := filepath.Base(md) + "_binary"
+		os.Setenv(varName, binary)
+	}
+	ts.Run(t, *update)
+}
+
+var goFileRegexp = regexp.MustCompile(`[^\s"]*\.go[\s":]`)
+
+// filterGoFilePaths  modifies paths to Go files by replacing their directory with "...".
+// For example,/a/b/c.go becomes .../c.go .
+// This makes it possible to compare govulncheck output across systems, because
+// Go filenames include setup-specific paths.
+func filterGoFilePaths(data []byte) []byte {
+	return goFileRegexp.ReplaceAllFunc(data, func(b []byte) []byte {
+		s := string(b)
+		return []byte(fmt.Sprintf(`.../%s%c`, filepath.Base(s[1:len(s)-1]), s[len(s)-1]))
+	})
+}
diff --git a/cmd/govulncheck/testdata/usage.ct b/cmd/govulncheck/testdata/usage.ct
index b2db75b..b5bbecc 100644
--- a/cmd/govulncheck/testdata/usage.ct
+++ b/cmd/govulncheck/testdata/usage.ct
@@ -5,7 +5,7 @@
 
 	govulncheck [flags] {package pattern...}
 
-	govulncheck [flags] {binary path}
+	govulncheck [flags] {binary path} (if built with Go 1.18 or higher)
 
 Flags:
 
@@ -34,7 +34,7 @@
 
 	govulncheck [flags] {package pattern...}
 
-	govulncheck [flags] {binary path}
+	govulncheck [flags] {binary path} (if built with Go 1.18 or higher)
 
 Flags: