diff -pruN 0.0~git20220613.4eb5ba4-2/AUTHORS 0.0~git20220725.4151a5a-1/AUTHORS
--- 0.0~git20220613.4eb5ba4-2/AUTHORS	2022-06-18 09:00:29.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/AUTHORS	1970-01-01 00:00:00.000000000 +0000
@@ -1,3 +0,0 @@
-# This source code refers to The Go Authors for copyright purposes.
-# The master list of authors is in the main Go distribution,
-# visible at https://tip.golang.org/AUTHORS.
diff -pruN 0.0~git20220613.4eb5ba4-2/client/client.go 0.0~git20220725.4151a5a-1/client/client.go
--- 0.0~git20220613.4eb5ba4-2/client/client.go	2022-06-18 09:00:29.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/client/client.go	2022-08-02 19:02:47.000000000 +0000
@@ -32,7 +32,7 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"net/url"
 	"os"
@@ -84,7 +84,7 @@ func (*localSource) unexported() {}
 
 func (ls *localSource) GetByModule(_ context.Context, module string) (_ []*osv.Entry, err error) {
 	defer derrors.Wrap(&err, "GetByModule(%q)", module)
-	content, err := ioutil.ReadFile(filepath.Join(ls.dir, module+".json"))
+	content, err := os.ReadFile(filepath.Join(ls.dir, module+".json"))
 	if os.IsNotExist(err) {
 		return nil, nil
 	} else if err != nil {
@@ -99,7 +99,7 @@ func (ls *localSource) GetByModule(_ con
 
 func (ls *localSource) GetByID(_ context.Context, id string) (_ *osv.Entry, err error) {
 	defer derrors.Wrap(&err, "GetByID(%q)", id)
-	content, err := ioutil.ReadFile(filepath.Join(ls.dir, internal.IDDirectory, id+".json"))
+	content, err := os.ReadFile(filepath.Join(ls.dir, internal.IDDirectory, id+".json"))
 	if os.IsNotExist(err) {
 		return nil, nil
 	} else if err != nil {
@@ -114,7 +114,7 @@ func (ls *localSource) GetByID(_ context
 
 func (ls *localSource) ListIDs(context.Context) (_ []string, err error) {
 	defer derrors.Wrap(&err, "ListIDs()")
-	content, err := ioutil.ReadFile(filepath.Join(ls.dir, internal.IDDirectory, "index.json"))
+	content, err := os.ReadFile(filepath.Join(ls.dir, internal.IDDirectory, "index.json"))
 	if err != nil {
 		return nil, err
 	}
@@ -139,7 +139,7 @@ func (ls *localSource) LastModifiedTime(
 func (ls *localSource) Index(context.Context) (_ DBIndex, err error) {
 	defer derrors.Wrap(&err, "Index()")
 	var index DBIndex
-	b, err := ioutil.ReadFile(filepath.Join(ls.dir, "index.json"))
+	b, err := os.ReadFile(filepath.Join(ls.dir, "index.json"))
 	if err != nil {
 		return nil, err
 	}
@@ -202,7 +202,7 @@ func (hs *httpSource) Index(ctx context.
 	if resp.StatusCode != http.StatusOK {
 		return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
 	}
-	b, err := ioutil.ReadAll(resp.Body)
+	b, err := io.ReadAll(resp.Body)
 	if err != nil {
 		return nil, err
 	}
@@ -337,7 +337,7 @@ func (hs *httpSource) readBody(ctx conte
 		return nil, nil
 	}
 	// might want this to be a LimitedReader
-	return ioutil.ReadAll(resp.Body)
+	return io.ReadAll(resp.Body)
 }
 
 type client struct {
diff -pruN 0.0~git20220613.4eb5ba4-2/client/client_test.go 0.0~git20220725.4151a5a-1/client/client_test.go
--- 0.0~git20220613.4eb5ba4-2/client/client_test.go	2022-06-18 09:00:29.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/client/client_test.go	2022-08-02 19:02:47.000000000 +0000
@@ -9,7 +9,6 @@ import (
 	"encoding/json"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
 	"net/url"
@@ -116,7 +115,7 @@ func createDirAndFile(dir, file, content
 	if err := os.MkdirAll(dir, 0755); err != nil {
 		return err
 	}
-	return ioutil.WriteFile(path.Join(dir, file), []byte(content), 0644)
+	return os.WriteFile(path.Join(dir, file), []byte(content), 0644)
 }
 
 // localDB creates a local db with testVulns and index as contents.
diff -pruN 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/binary_118.go 0.0~git20220725.4151a5a-1/cmd/govulncheck/binary_118.go
--- 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/binary_118.go	1970-01-01 00:00:00.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/cmd/govulncheck/binary_118.go	2022-08-02 19:02:47.000000000 +0000
@@ -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 -pruN 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/binary_not118.go 0.0~git20220725.4151a5a-1/cmd/govulncheck/binary_not118.go
--- 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/binary_not118.go	1970-01-01 00:00:00.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/cmd/govulncheck/binary_not118.go	2022-08-02 19:02:47.000000000 +0000
@@ -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 -pruN 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/doc.go 0.0~git20220725.4151a5a-1/cmd/govulncheck/doc.go
--- 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/doc.go	2022-06-18 09:00:29.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/cmd/govulncheck/doc.go	2022-08-02 19:02:47.000000000 +0000
@@ -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 @@ Govulncheck requires Go version 1.18 or
 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 @@ Govulncheck uses the binary's symbol inf
 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 -html flag outputs HTML instead of p
 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 -pruN 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/formatting.go 0.0~git20220725.4151a5a-1/cmd/govulncheck/formatting.go
--- 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/formatting.go	2022-06-18 09:00:29.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/cmd/govulncheck/formatting.go	2022-08-02 19:02:47.000000000 +0000
@@ -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 -pruN 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/formatting_test.go 0.0~git20220725.4151a5a-1/cmd/govulncheck/formatting_test.go
--- 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/formatting_test.go	2022-06-18 09:00:29.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/cmd/govulncheck/formatting_test.go	2022-08-02 19:02:47.000000000 +0000
@@ -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 -pruN 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/html.go 0.0~git20220725.4151a5a-1/cmd/govulncheck/html.go
--- 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/html.go	2022-06-18 09:00:29.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/cmd/govulncheck/html.go	2022-08-02 19:02:47.000000000 +0000
@@ -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 -pruN 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/integration/k8s/k8s.go 0.0~git20220725.4151a5a-1/cmd/govulncheck/integration/k8s/k8s.go
--- 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/integration/k8s/k8s.go	2022-06-18 09:00:29.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/cmd/govulncheck/integration/k8s/k8s.go	2022-08-02 19:02:47.000000000 +0000
@@ -6,7 +6,6 @@ package main
 
 import (
 	"encoding/json"
-	"io/ioutil"
 	"log"
 	"os"
 
@@ -25,7 +24,7 @@ func main() {
 	}
 	out := os.Args[1]
 
-	outJson, err := ioutil.ReadFile(out)
+	outJson, err := os.ReadFile(out)
 	if err != nil {
 		log.Fatal("Failed to read:", out)
 	}
@@ -35,8 +34,8 @@ func main() {
 		log.Fatal("Failed to load json into vulncheck.Result:", err)
 	}
 
-	if len(r.Vulns) != 19 {
-		log.Fatal("want 19 vulns; got", len(r.Vulns))
+	if len(r.Vulns) != 55 {
+		log.Fatalf("want 55 vulns; got %d", len(r.Vulns))
 	}
 
 	type vuln struct {
@@ -49,28 +48,64 @@ func main() {
 	}
 
 	want := map[vuln]bool{
-		{"github.com/evanphx/json-patch", "partialArray.add"}:                         true,
-		{"github.com/opencontainers/selinux/go-selinux", "CurrentLabel"}:              true,
-		{"github.com/opencontainers/selinux/go-selinux", "FileLabel"}:                 true,
-		{"github.com/opencontainers/selinux/go-selinux", "GetEnabled"}:                true,
-		{"github.com/opencontainers/selinux/go-selinux", "SetFileLabel"}:              true,
-		{"github.com/opencontainers/selinux/go-selinux", "getSelinuxMountPoint"}:      true,
-		{"github.com/opencontainers/selinux/go-selinux", "lgetxattr"}:                 true,
-		{"github.com/opencontainers/selinux/go-selinux", "lsetxattr"}:                 true,
-		{"github.com/opencontainers/selinux/go-selinux", "readCon"}:                   true,
-		{"github.com/opencontainers/selinux/go-selinux", "selinuxState.getEnabled"}:   true,
-		{"github.com/opencontainers/selinux/go-selinux", "selinuxState.getSELinuxfs"}: true,
-		{"github.com/opencontainers/selinux/go-selinux", "selinuxState.setEnable"}:    true,
-		{"github.com/opencontainers/selinux/go-selinux", "selinuxState.setSELinuxfs"}: true,
-		{"github.com/satori/go.uuid", "init"}:                                         true,
-		{"golang.org/x/crypto/ssh", "NewPublicKey"}:                                   true,
-		{"golang.org/x/crypto/ssh", "ed25519PublicKey.Verify"}:                        true,
-		{"golang.org/x/crypto/ssh", "parseED25519"}:                                   true,
-		{"golang.org/x/text/encoding/unicode", "bomOverride.Transform"}:               true,
-		{"golang.org/x/text/encoding/unicode", "utf16Decoder.Transform"}:              true,
+		{"github.com/containernetworking/cni/pkg/invoke", "FindInPath"}:                                    true,
+		{"github.com/evanphx/json-patch", "partialArray.add"}:                                              true,
+		{"github.com/opencontainers/selinux/go-selinux", "CurrentLabel"}:                                   true,
+		{"github.com/opencontainers/selinux/go-selinux", "FileLabel"}:                                      true,
+		{"github.com/opencontainers/selinux/go-selinux", "GetEnabled"}:                                     true,
+		{"github.com/opencontainers/selinux/go-selinux", "SetFileLabel"}:                                   true,
+		{"github.com/opencontainers/selinux/go-selinux", "getSelinuxMountPoint"}:                           true,
+		{"github.com/opencontainers/selinux/go-selinux", "lgetxattr"}:                                      true,
+		{"github.com/opencontainers/selinux/go-selinux", "lsetxattr"}:                                      true,
+		{"github.com/opencontainers/selinux/go-selinux", "readCon"}:                                        true,
+		{"github.com/opencontainers/selinux/go-selinux", "selinuxState.getEnabled"}:                        true,
+		{"github.com/opencontainers/selinux/go-selinux", "selinuxState.getSELinuxfs"}:                      true,
+		{"github.com/opencontainers/selinux/go-selinux", "selinuxState.setEnable"}:                         true,
+		{"github.com/opencontainers/selinux/go-selinux", "selinuxState.setSELinuxfs"}:                      true,
+		{"github.com/prometheus/client_golang/prometheus/promhttp", "Handler"}:                             true,
+		{"github.com/prometheus/client_golang/prometheus/promhttp", "HandlerFor"}:                          true,
+		{"github.com/prometheus/client_golang/prometheus/promhttp", "InstrumentHandlerCounter"}:            true,
+		{"github.com/prometheus/client_golang/prometheus/promhttp", "InstrumentMetricHandler"}:             true,
+		{"github.com/prometheus/client_golang/prometheus/promhttp", "flusherDelegator.Flush"}:              true,
+		{"github.com/prometheus/client_golang/prometheus/promhttp", "init"}:                                true,
+		{"github.com/prometheus/client_golang/prometheus/promhttp", "readerFromDelegator.ReadFrom"}:        true,
+		{"github.com/prometheus/client_golang/prometheus/promhttp", "responseWriterDelegator.Write"}:       true,
+		{"github.com/prometheus/client_golang/prometheus/promhttp", "responseWriterDelegator.WriteHeader"}: true,
+		{"github.com/prometheus/client_golang/prometheus/promhttp", "sanitizeMethod"}:                      true,
+		{"github.com/satori/go.uuid", "init"}:                                                              true,
+		{"golang.org/x/crypto/cryptobyte", "Builder.AddBytes"}:                                             true,
+		{"golang.org/x/crypto/cryptobyte", "Builder.AddUint16LengthPrefixed"}:                              true,
+		{"golang.org/x/crypto/cryptobyte", "Builder.Bytes"}:                                                true,
+		{"golang.org/x/crypto/cryptobyte", "Builder.add"}:                                                  true,
+		{"golang.org/x/crypto/cryptobyte", "Builder.addLengthPrefixed"}:                                    true,
+		{"golang.org/x/crypto/cryptobyte", "Builder.callContinuation"}:                                     true,
+		{"golang.org/x/crypto/cryptobyte", "Builder.flushChild"}:                                           true,
+		{"golang.org/x/crypto/cryptobyte", "NewBuilder"}:                                                   true,
+		{"golang.org/x/crypto/cryptobyte", "String.Empty"}:                                                 true,
+		{"golang.org/x/crypto/cryptobyte", "String.PeekASN1Tag"}:                                           true,
+		{"golang.org/x/crypto/cryptobyte", "String.ReadASN1"}:                                              true,
+		{"golang.org/x/crypto/cryptobyte", "String.ReadAnyASN1"}:                                           true,
+		{"golang.org/x/crypto/cryptobyte", "String.ReadBytes"}:                                             true,
+		{"golang.org/x/crypto/cryptobyte", "String.ReadOptionalASN1"}:                                      true,
+		{"golang.org/x/crypto/cryptobyte", "String.ReadUint16LengthPrefixed"}:                              true,
+		{"golang.org/x/crypto/cryptobyte", "String.Skip"}:                                                  true,
+		{"golang.org/x/crypto/cryptobyte", "String.read"}:                                                  true,
+		{"golang.org/x/crypto/cryptobyte", "String.readASN1"}:                                              true,
+		{"golang.org/x/crypto/cryptobyte", "String.readLengthPrefixed"}:                                    true,
+		{"golang.org/x/crypto/cryptobyte", "String.readUnsigned"}:                                          true,
+		{"golang.org/x/crypto/salsa20/salsa", "XORKeyStream"}:                                              true,
+		{"golang.org/x/crypto/ssh", "NewPublicKey"}:                                                        true,
+		{"golang.org/x/crypto/ssh", "ed25519PublicKey.Verify"}:                                             true,
+		{"golang.org/x/crypto/ssh", "parseED25519"}:                                                        true,
+		{"golang.org/x/net/http/httpguts", "HeaderValuesContainsToken"}:                                    true,
+		{"golang.org/x/net/http/httpguts", "headerValueContainsToken"}:                                     true,
+		{"golang.org/x/net/http2", "Server.ServeConn"}:                                                     true,
+		{"golang.org/x/net/http2", "serverConn.canonicalHeader"}:                                           true,
+		{"golang.org/x/text/encoding/unicode", "bomOverride.Transform"}:                                    true,
+		{"golang.org/x/text/encoding/unicode", "utf16Decoder.Transform"}:                                   true,
 	}
 
-	if !cmp.Equal(calledVulns, want) {
-		log.Fatalf("want %v called symbols;\ngot %v\n", want, calledVulns)
+	if diff := cmp.Diff(want, calledVulns); diff != "" {
+		log.Fatalf("reachable vulnerable symbols mismatch (-want, +got):\n%s", diff)
 	}
 }
diff -pruN 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/internal/govulncheck/cache.go 0.0~git20220725.4151a5a-1/cmd/govulncheck/internal/govulncheck/cache.go
--- 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/internal/govulncheck/cache.go	2022-06-18 09:00:29.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/cmd/govulncheck/internal/govulncheck/cache.go	2022-08-02 19:02:47.000000000 +0000
@@ -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
-
 // Package govulncheck supports the govulncheck command.
 package govulncheck
 
 import (
 	"encoding/json"
 	"go/build"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"sync"
@@ -66,7 +62,7 @@ func (c *FSCache) ReadIndex(dbName strin
 	c.mu.Lock()
 	defer c.mu.Unlock()
 
-	b, err := ioutil.ReadFile(filepath.Join(c.rootDir, dbName, "index.json"))
+	b, err := os.ReadFile(filepath.Join(c.rootDir, dbName, "index.json"))
 	if err != nil {
 		if os.IsNotExist(err) {
 			return nil, time.Time{}, nil
@@ -95,7 +91,7 @@ func (c *FSCache) WriteIndex(dbName stri
 	if err != nil {
 		return err
 	}
-	if err := ioutil.WriteFile(filepath.Join(path, "index.json"), j, 0666); err != nil {
+	if err := os.WriteFile(filepath.Join(path, "index.json"), j, 0666); err != nil {
 		return err
 	}
 	return nil
@@ -105,7 +101,7 @@ func (c *FSCache) ReadEntries(dbName str
 	c.mu.Lock()
 	defer c.mu.Unlock()
 
-	b, err := ioutil.ReadFile(filepath.Join(c.rootDir, dbName, p, "vulns.json"))
+	b, err := os.ReadFile(filepath.Join(c.rootDir, dbName, p, "vulns.json"))
 	if err != nil {
 		if os.IsNotExist(err) {
 			return nil, nil
@@ -131,7 +127,7 @@ func (c *FSCache) WriteEntries(dbName st
 	if err != nil {
 		return err
 	}
-	if err := ioutil.WriteFile(filepath.Join(path, "vulns.json"), j, 0666); err != nil {
+	if err := os.WriteFile(filepath.Join(path, "vulns.json"), j, 0666); err != nil {
 		return err
 	}
 	return nil
diff -pruN 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/internal/govulncheck/cache_test.go 0.0~git20220725.4151a5a-1/cmd/govulncheck/internal/govulncheck/cache_test.go
--- 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/internal/govulncheck/cache_test.go	2022-06-18 09:00:29.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/cmd/govulncheck/internal/govulncheck/cache_test.go	2022-08-02 19:02:47.000000000 +0000
@@ -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 -pruN 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/internal/govulncheck/source.go 0.0~git20220725.4151a5a-1/cmd/govulncheck/internal/govulncheck/source.go
--- 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/internal/govulncheck/source.go	2022-06-18 09:00:29.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/cmd/govulncheck/internal/govulncheck/source.go	2022-08-02 19:02:47.000000000 +0000
@@ -2,19 +2,14 @@
 // 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 (
-	"context"
 	"fmt"
 	"sort"
 	"strings"
 
 	"golang.org/x/tools/go/packages"
-	"golang.org/x/vuln/client"
 	"golang.org/x/vuln/vulncheck"
 )
 
@@ -57,26 +52,6 @@ func LoadPackages(cfg *packages.Config,
 	return vpkgs, err
 }
 
-// Source calls vulncheck.Source on the Go source in pkgs. It returns the result
-// with Vulns trimmed to those that are actually called.
-//
-// This function is being used by the Go IDE team.
-func Source(ctx context.Context, pkgs []*vulncheck.Package, c client.Client) (*vulncheck.Result, error) {
-	r, err := vulncheck.Source(ctx, pkgs, &vulncheck.Config{Client: c})
-	if err != nil {
-		return nil, err
-	}
-	// Keep only the vulns that are called.
-	var vulns []*vulncheck.Vuln
-	for _, v := range r.Vulns {
-		if v.CallSink != 0 {
-			vulns = append(vulns, v)
-		}
-	}
-	r.Vulns = vulns
-	return r, nil
-}
-
 // CallInfo is information about calls to vulnerable functions.
 type CallInfo struct {
 	// CallStacks contains all call stacks to vulnerable functions.
diff -pruN 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/internal/govulncheck/util.go 0.0~git20220725.4151a5a-1/cmd/govulncheck/internal/govulncheck/util.go
--- 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/internal/govulncheck/util.go	2022-06-18 09:00:29.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/cmd/govulncheck/internal/govulncheck/util.go	2022-08-02 19:02:47.000000000 +0000
@@ -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 -pruN 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/internal/govulncheck/util_test.go 0.0~git20220725.4151a5a-1/cmd/govulncheck/internal/govulncheck/util_test.go
--- 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/internal/govulncheck/util_test.go	2022-06-18 09:00:29.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/cmd/govulncheck/internal/govulncheck/util_test.go	2022-08-02 19:02:47.000000000 +0000
@@ -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 -pruN 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/main_command_118_test.go 0.0~git20220725.4151a5a-1/cmd/govulncheck/main_command_118_test.go
--- 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/main_command_118_test.go	1970-01-01 00:00:00.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/cmd/govulncheck/main_command_118_test.go	2022-08-02 19:02:47.000000000 +0000
@@ -0,0 +1,104 @@
+// 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")
+		}
+		// We set GOVERSION to always get the same results regardless of the underlying Go build system.
+		cmd.Env = append(os.Environ(), "GOVULNDB=file://"+testDir+"/testdata/vulndb", "GOVERSION=go1.18")
+		out, err := cmd.CombinedOutput()
+		out = filterGoFilePaths(out)
+		out = filterStdlibVersions(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":]`)
+	stdlibVersionRegexp = regexp.MustCompile(`("Path": "stdlib",\s*"Version": ")v[^\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]))
+	})
+}
+
+// filterStdlibVersions removes Go standard library versions from JSON output,
+// since they depend on the system running the test. Some have different
+// versions than others, and on some we are unable to extract a version from
+// the binary so the version is empty.
+func filterStdlibVersions(data []byte) []byte {
+	return stdlibVersionRegexp.ReplaceAll(data, []byte(`${1}"`))
+}
diff -pruN 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/main.go 0.0~git20220725.4151a5a-1/cmd/govulncheck/main.go
--- 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/main.go	2022-06-18 09:00:29.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/cmd/govulncheck/main.go	2022-08-02 19:02:47.000000000 +0000
@@ -2,18 +2,17 @@
 // 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 (
+	"bytes"
 	"context"
 	"encoding/json"
 	"flag"
 	"fmt"
 	"go/build"
 	"os"
+	"os/exec"
 	"sort"
 	"strings"
 
@@ -37,7 +36,7 @@ Usage:
 
 	govulncheck [flags] {package pattern...}
 
-	govulncheck [flags] {binary path}
+	govulncheck [flags] {binary path} (if built with Go 1.18 or higher)
 
 Flags:
 
@@ -80,7 +79,7 @@ func main() {
 	if err != nil {
 		die("govulncheck: %s", err)
 	}
-	vcfg := &vulncheck.Config{Client: dbClient}
+	vcfg := &vulncheck.Config{Client: dbClient, SourceGoVersion: goVersion()}
 
 	patterns := flag.Args()
 	if !(*jsonFlag || *htmlFlag) {
@@ -101,7 +100,7 @@ Scanning for dependencies with known vul
 			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)
 		}
@@ -114,7 +113,7 @@ Scanning for dependencies with known vul
 		if err != nil {
 			die("govulncheck: %v", err)
 		}
-		r, err = vulncheck.Source(ctx, pkgs, &vulncheck.Config{Client: dbClient})
+		r, err = vulncheck.Source(ctx, pkgs, vcfg)
 		if err != nil {
 			die("govulncheck: %v", err)
 		}
@@ -228,12 +227,17 @@ func writeText(r *vulncheck.Result, ci *
 		} else {
 			writeCallStacksDefault(vg, ci)
 		}
-		fmt.Printf(`
-Found in:  %s@%s
-Fixed in:  %s@v%s
-More info: https://pkg.go.dev/vuln/%s
-
-`, v0.PkgPath, ci.ModuleVersions[v0.ModPath], v0.PkgPath, govulncheck.LatestFixed(v0.OSV.Affected), v0.OSV.ID)
+		fmt.Println()
+		found := v0.PkgPath
+		if v := ci.ModuleVersions[v0.ModPath]; v != "" {
+			found = packageVersionString(v0.PkgPath, v[1:])
+		}
+		fmt.Printf("Found in:  %v\n", found)
+		if fixed := govulncheck.LatestFixed(v0.OSV.Affected); fixed != "" {
+			fmt.Printf("Fixed in:  %s\n", packageVersionString(v0.PkgPath, fixed))
+		}
+		fmt.Printf("More info: https://pkg.go.dev/vuln/%s\n", v0.OSV.ID)
+		fmt.Println()
 	}
 	if len(unaffectedMods) > 0 {
 		fmt.Println()
@@ -328,6 +332,27 @@ func compact(s []string) []string {
 	return s[:i]
 }
 
+func goVersion() string {
+	if v := os.Getenv("GOVERSION"); v != "" {
+		// Unlikely to happen in practice, mostly used for testing.
+		return v
+	}
+	out, err := exec.Command("go", "env", "GOVERSION").Output()
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "failed to determine go version; skipping stdlib scanning: %v\n", err)
+		return ""
+	}
+	return string(bytes.TrimSpace(out))
+}
+
+func packageVersionString(packagePath, version string) string {
+	v := "v" + version
+	if importPathInStdlib(packagePath) {
+		v = semverToGoTag(v)
+	}
+	return fmt.Sprintf("%s@%s", packagePath, v)
+}
+
 func die(format string, args ...interface{}) {
 	fmt.Fprintf(os.Stderr, format+"\n", args...)
 	os.Exit(1)
diff -pruN 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/main_test.go 0.0~git20220725.4151a5a-1/cmd/govulncheck/main_test.go
--- 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/main_test.go	2022-06-18 09:00:29.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/cmd/govulncheck/main_test.go	2022-08-02 19:02:47.000000000 +0000
@@ -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 -pruN 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/stdlib.go 0.0~git20220725.4151a5a-1/cmd/govulncheck/stdlib.go
--- 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/stdlib.go	1970-01-01 00:00:00.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/cmd/govulncheck/stdlib.go	2022-08-02 19:02:47.000000000 +0000
@@ -0,0 +1,80 @@
+// 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 main
+
+import (
+	"fmt"
+	"strings"
+
+	"golang.org/x/mod/semver"
+)
+
+// Support functions for standard library packages.
+// These are copied from the internal/stdlib package in the pkgsite repo.
+
+// semverToGoTag returns the Go standard library repository tag corresponding
+// to semver, a version string without the initial "v".
+// Go tags differ from standard semantic versions in a few ways,
+// such as beginning with "go" instead of "v".
+func semverToGoTag(v string) string {
+	if strings.HasPrefix(v, "v0.0.0") {
+		return "master"
+	}
+	// Special case: v1.0.0 => go1.
+	if v == "v1.0.0" {
+		return "go1"
+	}
+	if !semver.IsValid(v) {
+		return fmt.Sprintf("<!%s:invalid semver>", v)
+	}
+	goVersion := semver.Canonical(v)
+	prerelease := semver.Prerelease(goVersion)
+	versionWithoutPrerelease := strings.TrimSuffix(goVersion, prerelease)
+	patch := strings.TrimPrefix(versionWithoutPrerelease, semver.MajorMinor(goVersion)+".")
+	if patch == "0" {
+		versionWithoutPrerelease = strings.TrimSuffix(versionWithoutPrerelease, ".0")
+	}
+	goVersion = fmt.Sprintf("go%s", strings.TrimPrefix(versionWithoutPrerelease, "v"))
+	if prerelease != "" {
+		// Go prereleases look like  "beta1" instead of "beta.1".
+		// "beta1" is bad for sorting (since beta10 comes before beta9), so
+		// require the dot form.
+		i := finalDigitsIndex(prerelease)
+		if i >= 1 {
+			if prerelease[i-1] != '.' {
+				return fmt.Sprintf("<!%s:final digits in a prerelease must follow a period>", v)
+			}
+			// Remove the dot.
+			prerelease = prerelease[:i-1] + prerelease[i:]
+		}
+		goVersion += strings.TrimPrefix(prerelease, "-")
+	}
+	return goVersion
+}
+
+// finalDigitsIndex returns the index of the first digit in the sequence of digits ending s.
+// If s doesn't end in digits, it returns -1.
+func finalDigitsIndex(s string) int {
+	// Assume ASCII (since the semver package does anyway).
+	var i int
+	for i = len(s) - 1; i >= 0; i-- {
+		if s[i] < '0' || s[i] > '9' {
+			break
+		}
+	}
+	if i == len(s)-1 {
+		return -1
+	}
+	return i + 1
+}
+
+// importPathInStdlib reports whether the given import path could be part of the Go standard library,
+// by reporting whether the first component lacks a '.'.
+func importPathInStdlib(path string) bool {
+	if i := strings.IndexByte(path, '/'); i != -1 {
+		path = path[:i]
+	}
+	return !strings.Contains(path, ".")
+}
diff -pruN 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/testdata/default.ct 0.0~git20220725.4151a5a-1/cmd/govulncheck/testdata/default.ct
--- 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/testdata/default.ct	2022-06-18 09:00:29.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/cmd/govulncheck/testdata/default.ct	2022-08-02 19:02:47.000000000 +0000
@@ -22,7 +22,7 @@ to panic via an out of bounds read. If P
 this may be used as a vector for a denial of service attack.
 
 Call stacks in your code:
- vuln.main calls golang.org/x/text/language.Parse
+ golang.org/vuln.main calls golang.org/x/text/language.Parse
 
 Found in:  golang.org/x/text/language@v0.3.0
 Fixed in:  golang.org/x/text/language@v0.3.7
diff -pruN 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/testdata/html.ct 0.0~git20220725.4151a5a-1/cmd/govulncheck/testdata/html.ct
--- 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/testdata/html.ct	2022-06-18 09:00:29.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/cmd/govulncheck/testdata/html.ct	2022-08-02 19:02:47.000000000 +0000
@@ -58,10 +58,10 @@ this may be used as a vector for a denia
   </table>
   
     <details>
-      <summary>vuln.main calls golang.org/x/text/language.Parse</summary>
+      <summary>golang.org/vuln.main calls golang.org/x/text/language.Parse</summary>
       <ul>
         
-          <li>vuln.main</li>
+          <li>golang.org/vuln.main</li>
         
           <li>golang.org/x/text/language.Parse</li>
         
diff -pruN 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/testdata/json-binary.ct 0.0~git20220725.4151a5a-1/cmd/govulncheck/testdata/json-binary.ct
--- 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/testdata/json-binary.ct	2022-06-18 09:00:29.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/cmd/govulncheck/testdata/json-binary.ct	2022-08-02 19:02:47.000000000 +0000
@@ -10,6 +10,12 @@ $ govulncheck -json ${novuln_binary}
 			"Version": "v0.3.7",
 			"Dir": "",
 			"Replace": null
+		},
+		{
+			"Path": "stdlib",
+			"Version": "",
+			"Dir": "",
+			"Replace": null
 		}
 	]
 }
@@ -86,6 +92,12 @@ $ govulncheck -json ${vuln_binary} --> F
 			"Version": "v0.3.0",
 			"Dir": "",
 			"Replace": null
+		},
+		{
+			"Path": "stdlib",
+			"Version": "",
+			"Dir": "",
+			"Replace": null
 		}
 	]
 }
diff -pruN 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/testdata/json.ct 0.0~git20220725.4151a5a-1/cmd/govulncheck/testdata/json.ct
--- 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/testdata/json.ct	2022-06-18 09:00:29.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/cmd/govulncheck/testdata/json.ct	2022-08-02 19:02:47.000000000 +0000
@@ -1,4 +1,6 @@
 # Test of the -json flag.
+# TODO(zpavlinovic): add test for stdlib that works
+# on all underlying Go build systems.
 
 $ cdmodule novuln
 $ govulncheck -json .
@@ -18,13 +20,19 @@ $ govulncheck -json .
 	"Vulns": null,
 	"Modules": [
 		{
+			"Path": "golang.org/novuln",
+			"Version": "",
+			"Dir": "",
+			"Replace": null
+		},
+		{
 			"Path": "golang.org/x/text",
 			"Version": "v0.3.7",
 			"Dir": "",
 			"Replace": null
 		},
 		{
-			"Path": "novuln",
+			"Path": "stdlib",
 			"Version": "",
 			"Dir": "",
 			"Replace": null
@@ -41,7 +49,7 @@ $ govulncheck -json . --> FAIL 3
 				"ID": 1,
 				"Name": "main",
 				"RecvType": "",
-				"PkgPath": "vuln",
+				"PkgPath": "golang.org/vuln",
 				"Pos": {
 					"Filename": ".../vuln.go",
 					"Offset": 69,
@@ -95,7 +103,7 @@ $ govulncheck -json . --> FAIL 3
 			"2": {
 				"ID": 2,
 				"Name": "main",
-				"Path": "vuln",
+				"Path": "golang.org/vuln",
 				"Module": 2,
 				"ImportedBy": null
 			}
@@ -117,7 +125,7 @@ $ govulncheck -json . --> FAIL 3
 			},
 			"2": {
 				"ID": 2,
-				"Path": "vuln",
+				"Path": "golang.org/vuln",
 				"Version": "",
 				"Replace": 0,
 				"RequiredBy": null
@@ -190,13 +198,19 @@ $ govulncheck -json . --> FAIL 3
 	],
 	"Modules": [
 		{
+			"Path": "golang.org/vuln",
+			"Version": "",
+			"Dir": "",
+			"Replace": null
+		},
+		{
 			"Path": "golang.org/x/text",
 			"Version": "v0.3.0",
 			"Dir": "",
 			"Replace": null
 		},
 		{
-			"Path": "vuln",
+			"Path": "stdlib",
 			"Version": "",
 			"Dir": "",
 			"Replace": null
diff -pruN 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/testdata/modules/novuln/go.mod 0.0~git20220725.4151a5a-1/cmd/govulncheck/testdata/modules/novuln/go.mod
--- 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/testdata/modules/novuln/go.mod	2022-06-18 09:00:29.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/cmd/govulncheck/testdata/modules/novuln/go.mod	2022-08-02 19:02:47.000000000 +0000
@@ -1,6 +1,6 @@
-module novuln
+module golang.org/novuln
 
 go 1.18
 
 // This version does not have a vulnerability.
-require golang.org/x/text v0.3.7 // indirect
+require golang.org/x/text v0.3.7
diff -pruN 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/testdata/modules/stdvuln/go.mod 0.0~git20220725.4151a5a-1/cmd/govulncheck/testdata/modules/stdvuln/go.mod
--- 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/testdata/modules/stdvuln/go.mod	1970-01-01 00:00:00.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/cmd/govulncheck/testdata/modules/stdvuln/go.mod	2022-08-02 19:02:47.000000000 +0000
@@ -0,0 +1,3 @@
+module golang.org/stdvuln
+
+go 1.17
diff -pruN 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/testdata/modules/stdvuln/stdvuln.go 0.0~git20220725.4151a5a-1/cmd/govulncheck/testdata/modules/stdvuln/stdvuln.go
--- 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/testdata/modules/stdvuln/stdvuln.go	1970-01-01 00:00:00.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/cmd/govulncheck/testdata/modules/stdvuln/stdvuln.go	2022-08-02 19:02:47.000000000 +0000
@@ -0,0 +1,11 @@
+package main
+
+import (
+	"archive/zip"
+	"fmt"
+)
+
+func main() {
+	_, err := zip.OpenReader("file.zip")
+	fmt.Println(err)
+}
diff -pruN 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/testdata/modules/vuln/go.mod 0.0~git20220725.4151a5a-1/cmd/govulncheck/testdata/modules/vuln/go.mod
--- 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/testdata/modules/vuln/go.mod	2022-06-18 09:00:29.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/cmd/govulncheck/testdata/modules/vuln/go.mod	2022-08-02 19:02:47.000000000 +0000
@@ -1,7 +1,6 @@
-module vuln
+module golang.org/vuln
 
 go 1.18
 
 // This version has a vulnerability.
 require golang.org/x/text v0.3.0
-
diff -pruN 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/testdata/modules/vuln/go.sum 0.0~git20220725.4151a5a-1/cmd/govulncheck/testdata/modules/vuln/go.sum
--- 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/testdata/modules/vuln/go.sum	2022-06-18 09:00:29.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/cmd/govulncheck/testdata/modules/vuln/go.sum	2022-08-02 19:02:47.000000000 +0000
@@ -1,4 +1,2 @@
 golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
-golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
diff -pruN 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/testdata/stdlib.ct 0.0~git20220725.4151a5a-1/cmd/govulncheck/testdata/stdlib.ct
--- 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/testdata/stdlib.ct	1970-01-01 00:00:00.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/cmd/govulncheck/testdata/stdlib.ct	2022-08-02 19:02:47.000000000 +0000
@@ -0,0 +1,17 @@
+# Test of stdlib vuln detection.
+
+$ cdmodule stdvuln
+$ govulncheck . --> FAIL 3
+govulncheck is an experimental tool. Share feedback at https://go.dev/s/govulncheck-feedback.
+
+Scanning for dependencies with known vulnerabilities...
+Found 1 known vulnerability.
+-------------------------------------------------------
+
+STD
+
+Call stacks in your code:
+ golang.org/stdvuln.main calls archive/zip.OpenReader
+
+Found in:  archive/zip@go1.18
+More info: https://pkg.go.dev/vuln/STD
diff -pruN 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/testdata/usage.ct 0.0~git20220725.4151a5a-1/cmd/govulncheck/testdata/usage.ct
--- 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/testdata/usage.ct	2022-06-18 09:00:29.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/cmd/govulncheck/testdata/usage.ct	2022-08-02 19:02:47.000000000 +0000
@@ -5,7 +5,7 @@ Usage:
 
 	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 @@ Usage:
 
 	govulncheck [flags] {package pattern...}
 
-	govulncheck [flags] {binary path}
+	govulncheck [flags] {binary path} (if built with Go 1.18 or higher)
 
 Flags:
 
diff -pruN 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/testdata/verbose.ct 0.0~git20220725.4151a5a-1/cmd/govulncheck/testdata/verbose.ct
--- 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/testdata/verbose.ct	2022-06-18 09:00:29.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/cmd/govulncheck/testdata/verbose.ct	2022-08-02 19:02:47.000000000 +0000
@@ -23,7 +23,7 @@ this may be used as a vector for a denia
 
 Call stacks in your code:
     #1: for function Parse
-        vuln.main
+        golang.org/vuln.main
             .../vuln.go:11:16
         golang.org/x/text/language.Parse
 
diff -pruN 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/testdata/vulndb/stdlib.json 0.0~git20220725.4151a5a-1/cmd/govulncheck/testdata/vulndb/stdlib.json
--- 0.0~git20220613.4eb5ba4-2/cmd/govulncheck/testdata/vulndb/stdlib.json	1970-01-01 00:00:00.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/cmd/govulncheck/testdata/vulndb/stdlib.json	2022-08-02 19:02:47.000000000 +0000
@@ -0,0 +1 @@
+[{"id":"STD","affected":[{"package":{"name":"archive/zip"},"ranges":[{"type":"SEMVER","events":[{"introduced":"1.18.0"}]}],"ecosystem_specific":{"symbols":["OpenReader"]}}]}]
diff -pruN 0.0~git20220613.4eb5ba4-2/CONTRIBUTORS 0.0~git20220725.4151a5a-1/CONTRIBUTORS
--- 0.0~git20220613.4eb5ba4-2/CONTRIBUTORS	2022-06-18 09:00:29.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/CONTRIBUTORS	1970-01-01 00:00:00.000000000 +0000
@@ -1,3 +0,0 @@
-# This source code was written by the Go contributors.
-# The master list of contributors is in the main Go distribution,
-# visible at https://tip.golang.org/CONTRIBUTORS.
diff -pruN 0.0~git20220613.4eb5ba4-2/debian/changelog 0.0~git20220725.4151a5a-1/debian/changelog
--- 0.0~git20220613.4eb5ba4-2/debian/changelog	2022-06-25 12:26:48.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/debian/changelog	2022-08-02 19:10:29.000000000 +0000
@@ -1,3 +1,9 @@
+golang-golang-x-vuln (0.0~git20220725.4151a5a-1) unstable; urgency=medium
+
+  * New upstream version 0.0~git20220725.4151a5a
+
+ -- Shengjing Zhu <zhsj@debian.org>  Wed, 03 Aug 2022 03:10:29 +0800
+
 golang-golang-x-vuln (0.0~git20220613.4eb5ba4-2) unstable; urgency=medium
 
   * Source only upload for testing migration
diff -pruN 0.0~git20220613.4eb5ba4-2/debian/patches/0001-Skip-cmd-test.patch 0.0~git20220725.4151a5a-1/debian/patches/0001-Skip-cmd-test.patch
--- 0.0~git20220613.4eb5ba4-2/debian/patches/0001-Skip-cmd-test.patch	2022-06-25 12:26:48.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/debian/patches/0001-Skip-cmd-test.patch	1970-01-01 00:00:00.000000000 +0000
@@ -1,24 +0,0 @@
-From: Shengjing Zhu <zhsj@debian.org>
-Date: Sat, 18 Jun 2022 17:27:59 +0800
-Subject: Skip cmd test
-
-github.com/google/go-cmdtest is not packaged.
----
- cmd/govulncheck/main_test.go | 4 ++--
- 1 file changed, 2 insertions(+), 2 deletions(-)
-
-diff --git a/cmd/govulncheck/main_test.go b/cmd/govulncheck/main_test.go
-index 32dd1c2..3023fe9 100644
---- a/cmd/govulncheck/main_test.go
-+++ b/cmd/govulncheck/main_test.go
-@@ -2,8 +2,8 @@
- // 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
-+//go:build ignore
-+// +build ignore
- 
- package main
- 
diff -pruN 0.0~git20220613.4eb5ba4-2/debian/patches/0002-Skip-lint-test.patch 0.0~git20220725.4151a5a-1/debian/patches/0002-Skip-lint-test.patch
--- 0.0~git20220613.4eb5ba4-2/debian/patches/0002-Skip-lint-test.patch	2022-06-25 12:26:48.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/debian/patches/0002-Skip-lint-test.patch	1970-01-01 00:00:00.000000000 +0000
@@ -1,20 +0,0 @@
-From: Shengjing Zhu <zhsj@debian.org>
-Date: Sat, 18 Jun 2022 17:29:29 +0800
-Subject: Skip lint test
-
----
- all_test.go | 1 +
- 1 file changed, 1 insertion(+)
-
-diff --git a/all_test.go b/all_test.go
-index f4e4026..24e74a2 100644
---- a/all_test.go
-+++ b/all_test.go
-@@ -14,6 +14,7 @@ import (
- )
- 
- func Test(t *testing.T) {
-+	t.Skip("Not need to run lint for Debian package")
- 	bash, err := exec.LookPath("bash")
- 	if err != nil {
- 		t.Skipf("skipping: %v", err)
diff -pruN 0.0~git20220613.4eb5ba4-2/debian/patches/series 0.0~git20220725.4151a5a-1/debian/patches/series
--- 0.0~git20220613.4eb5ba4-2/debian/patches/series	2022-06-25 12:26:48.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/debian/patches/series	1970-01-01 00:00:00.000000000 +0000
@@ -1,2 +0,0 @@
-0001-Skip-cmd-test.patch
-0002-Skip-lint-test.patch
diff -pruN 0.0~git20220613.4eb5ba4-2/debian/rules 0.0~git20220725.4151a5a-1/debian/rules
--- 0.0~git20220613.4eb5ba4-2/debian/rules	2022-06-25 12:26:48.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/debian/rules	2022-08-02 19:10:29.000000000 +0000
@@ -5,3 +5,9 @@ export DH_GOLANG_EXCLUDES := cmd/govulnc
 
 %:
 	dh $@ --builddirectory=_build --buildsystem=golang
+
+execute_after_dh_auto_configure:
+	# github.com/google/go-cmdtest is not packaged.
+	rm -v _build/src/golang.org/x/vuln/cmd/govulncheck/main_command_118_test.go
+	# No need to run lint
+	rm -v _build/src/golang.org/x/vuln/all_test.go
diff -pruN 0.0~git20220613.4eb5ba4-2/vulncheck/binary.go 0.0~git20220725.4151a5a-1/vulncheck/binary.go
--- 0.0~git20220613.4eb5ba4-2/vulncheck/binary.go	2022-06-18 09:00:29.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/vulncheck/binary.go	2022-08-02 19:02:47.000000000 +0000
@@ -23,11 +23,18 @@ import (
 func Binary(ctx context.Context, exe io.ReaderAt, cfg *Config) (_ *Result, err error) {
 	defer derrors.Wrap(&err, "vulncheck.Binary")
 
-	mods, packageSymbols, err := binscan.ExtractPackagesAndSymbols(exe)
+	mods, packageSymbols, goVersion, err := binscan.ExtractPackagesAndSymbols(exe)
 	if err != nil {
 		return nil, err
 	}
+
 	cmods := convertModules(mods)
+	// set the stdlib version for detection of vulns in the standard library
+	// TODO(#53740): what if Go version is not in semver format?
+	stdlibModule.Version = goTagToSemver(goVersion)
+	// Add "stdlib" module.
+	cmods = append(cmods, stdlibModule)
+
 	modVulns, err := fetchVulnerabilities(ctx, cfg.Client, cmods)
 	if err != nil {
 		return nil, err
@@ -115,6 +122,10 @@ func convertModules(mods []*packages.Mod
 // to match two or more different module paths. We just take the first one.
 // If no module path matches, findPackageModule returns the empty string.
 func findPackageModule(pkg string, mods []*Module) string {
+	if isStdPackage(pkg) {
+		return stdlibModule.Path
+	}
+
 	for _, m := range mods {
 		if pkg == m.Path || strings.HasPrefix(pkg, m.Path+"/") {
 			return m.Path
diff -pruN 0.0~git20220613.4eb5ba4-2/vulncheck/binary_test.go 0.0~git20220725.4151a5a-1/vulncheck/binary_test.go
--- 0.0~git20220613.4eb5ba4-2/vulncheck/binary_test.go	2022-06-18 09:00:29.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/vulncheck/binary_test.go	2022-08-02 19:02:47.000000000 +0000
@@ -9,6 +9,7 @@ package vulncheck
 
 import (
 	"context"
+	"io"
 	"os"
 	"os/exec"
 	"path/filepath"
@@ -19,6 +20,7 @@ import (
 	"github.com/google/go-cmp/cmp"
 	"github.com/google/go-cmp/cmp/cmpopts"
 	"golang.org/x/tools/go/packages/packagestest"
+	"golang.org/x/vuln/vulncheck/internal/binscan"
 )
 
 // TODO: we build binary programatically, so what if the underlying tool chain changes?
@@ -36,6 +38,7 @@ func TestBinary(t *testing.T) {
 			package main
 
 			import (
+				"archive/zip"
 				"golang.org/cmod/c"
 				"golang.org/bmod/bvuln"
 			)
@@ -43,7 +46,9 @@ func TestBinary(t *testing.T) {
 			func main() {
 				c.C()
 				bvuln.NoVuln() // no vuln use
-				print("done")
+
+				_, err := zip.OpenReader("file.zip")
+				print(err)
 			}
 			`,
 			}},
@@ -115,7 +120,9 @@ func TestBinary(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	// In importsOnly mode, all three vulnerable symbols
+
+	hasGo := hasGoVersion(bin)
+	// In importsOnly mode, vulnerable symbols
 	// {avuln.VulnData.Vuln1, avuln.VulnData.Vuln2, bvuln.Vuln}
 	// should be detected.
 	wantVulns := []*Vuln{
@@ -123,6 +130,12 @@ func TestBinary(t *testing.T) {
 		{Symbol: "VulnData.Vuln1", PkgPath: "golang.org/amod/avuln", ModPath: "golang.org/amod"},
 		{Symbol: "VulnData.Vuln2", PkgPath: "golang.org/amod/avuln", ModPath: "golang.org/amod"},
 	}
+	if hasGo {
+		// If binary has recognizable Go version available,
+		// then archive/zip.OpenReader should be detected too.
+		wantVulns = append(wantVulns, &Vuln{Symbol: "OpenReader", PkgPath: "archive/zip", ModPath: "stdlib"})
+	}
+
 	diff := cmp.Diff(wantVulns, res.Vulns,
 		cmpopts.IgnoreFields(Vuln{}, "OSV"),
 		cmpopts.SortSlices(func(v1, v2 *Vuln) bool { return v1.Symbol < v2.Symbol }))
@@ -136,9 +149,21 @@ func TestBinary(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	// In non-importsOnly mode, only one symbol avuln.VulnData.Vuln1 should be detected.
-	if len(res.Vulns) != 1 {
-		t.Errorf("expected 1 vuln symbols got %d", len(res.Vulns))
+
+	wantVulns = []*Vuln{
+		{Symbol: "VulnData.Vuln1", PkgPath: "golang.org/amod/avuln", ModPath: "golang.org/amod"},
+	}
+	if hasGo {
+		// If binary has recognizable Go version available,
+		// then archive/zip.OpenReader should be detected too.
+		wantVulns = append(wantVulns, &Vuln{Symbol: "OpenReader", PkgPath: "archive/zip", ModPath: "stdlib"})
+	}
+
+	diff = cmp.Diff(wantVulns, res.Vulns,
+		cmpopts.IgnoreFields(Vuln{}, "OSV"),
+		cmpopts.SortSlices(func(v1, v2 *Vuln) bool { return v1.Symbol < v2.Symbol }))
+	if diff != "" {
+		t.Errorf("vulns mismatch (-want, +got)\n%s", diff)
 	}
 
 	// Check that the binary's modules are returned.
@@ -147,6 +172,7 @@ func TestBinary(t *testing.T) {
 		{Path: "golang.org/amod", Version: "v1.1.3"},
 		{Path: "golang.org/bmod", Version: "v0.5.0"},
 		{Path: "golang.org/cmod", Version: "v1.1.3"},
+		stdlibModule,
 	}
 	gotMods := res.Modules
 	sort.Slice(gotMods, func(i, j int) bool { return gotMods[i].Path < gotMods[j].Path })
@@ -169,3 +195,8 @@ func hasGoBuild() bool {
 	}
 	return true
 }
+
+func hasGoVersion(exe io.ReaderAt) bool {
+	_, _, goVersion, _ := binscan.ExtractPackagesAndSymbols(exe)
+	return goTagToSemver(goVersion) != ""
+}
diff -pruN 0.0~git20220613.4eb5ba4-2/vulncheck/fetch.go 0.0~git20220725.4151a5a-1/vulncheck/fetch.go
--- 0.0~git20220613.4eb5ba4-2/vulncheck/fetch.go	2022-06-18 09:00:29.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/vulncheck/fetch.go	2022-08-02 19:02:47.000000000 +0000
@@ -7,10 +7,60 @@ package vulncheck
 import (
 	"context"
 	"fmt"
+	"regexp"
+	"strings"
 
 	"golang.org/x/vuln/client"
 )
 
+var stdlibModule = &Module{
+	Path: "stdlib",
+	// Version is populated by Source and Binary based on user input
+}
+
+var (
+	// Regexp for matching go tags. The groups are:
+	// 1  the major.minor version
+	// 2  the patch version, or empty if none
+	// 3  the entire prerelease, if present
+	// 4  the prerelease type ("beta" or "rc")
+	// 5  the prerelease number
+	tagRegexp = regexp.MustCompile(`^go(\d+\.\d+)(\.\d+|)((beta|rc|-pre)(\d+))?$`)
+)
+
+// This is a modified copy of pkgsite/internal/stdlib:VersionForTag.
+func goTagToSemver(tag string) string {
+	if tag == "" {
+		return ""
+	}
+
+	tag = strings.Fields(tag)[0]
+	// Special cases for go1.
+	if tag == "go1" {
+		return "v1.0.0"
+	}
+	if tag == "go1.0" {
+		return ""
+	}
+	m := tagRegexp.FindStringSubmatch(tag)
+	if m == nil {
+		return ""
+	}
+	version := "v" + m[1]
+	if m[2] != "" {
+		version += m[2]
+	} else {
+		version += ".0"
+	}
+	if m[3] != "" {
+		if !strings.HasPrefix(m[4], "-") {
+			version += "-"
+		}
+		version += m[4] + "." + m[5]
+	}
+	return version
+}
+
 // modKey creates a unique string identifier for mod.
 func modKey(mod *Module) string {
 	if mod == nil {
@@ -24,6 +74,11 @@ func modKey(mod *Module) string {
 func extractModules(pkgs []*Package) []*Module {
 	modMap := map[string]*Module{}
 
+	// Add "stdlib" module. Even if stdlib is not used, which
+	// is unlikely, it won't appear in vulncheck.Modules nor
+	// other results.
+	modMap[stdlibModule.Path] = stdlibModule
+
 	seen := map[*Package]bool{}
 	var extract func(*Package, map[string]*Module)
 	extract = func(pkg *Package, modMap map[string]*Module) {
diff -pruN 0.0~git20220613.4eb5ba4-2/vulncheck/helpers_test.go 0.0~git20220725.4151a5a-1/vulncheck/helpers_test.go
--- 0.0~git20220613.4eb5ba4-2/vulncheck/helpers_test.go	2022-06-18 09:00:29.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/vulncheck/helpers_test.go	2022-08-02 19:02:47.000000000 +0000
@@ -7,6 +7,7 @@ package vulncheck
 import (
 	"context"
 	"fmt"
+	"runtime"
 	"sort"
 
 	"golang.org/x/tools/go/packages"
@@ -27,7 +28,8 @@ func (mc *mockClient) GetByModule(ctx co
 // testClient contains the following test vulnerabilities
 //
 //	golang.org/amod/avuln.{VulnData.Vuln1, vulnData.Vuln2}
-//	golang.org/bmod/bvuln.{Vuln}
+//	golang.org/bmod/bvuln.Vuln
+//	archive/zip.OpenReader
 var testClient = &mockClient{
 	ret: map[string][]*osv.Entry{
 		"golang.org/amod": []*osv.Entry{
@@ -50,6 +52,18 @@ var testClient = &mockClient{
 				}},
 			},
 		},
+		"stdlib": []*osv.Entry{
+			{
+				ID: "STD",
+				Affected: []osv.Affected{{
+					Package: osv.Package{Name: "archive/zip"},
+					// Range is populated also using runtime info for testing binaries since
+					// setting fixed Go version for binaries is very difficult.
+					Ranges:            osv.Affects{{Type: osv.TypeSemver, Events: []osv.RangeEvent{{Introduced: "1.18"}, {Introduced: goTagToSemver(runtime.Version())}}}},
+					EcosystemSpecific: osv.EcosystemSpecific{Symbols: []string{"OpenReader"}},
+				}},
+			},
+		},
 	},
 }
 
@@ -175,9 +189,23 @@ func sortStrMap(m map[string][]string) {
 	}
 }
 
+// loadPackages loads packages for patterns. Returns error if the loading failed
+// or some of the specified packages have issues. In the latter case, the error
+// message will contain information only for the first observed package with issues.
 func loadPackages(e *packagestest.Exported, patterns ...string) ([]*packages.Package, error) {
 	e.Config.Mode |= packages.NeedModule | packages.NeedName | packages.NeedFiles |
 		packages.NeedCompiledGoFiles | packages.NeedImports | packages.NeedTypes |
 		packages.NeedTypesSizes | packages.NeedSyntax | packages.NeedTypesInfo | packages.NeedDeps
-	return packages.Load(e.Config, patterns...)
+	pkgs, err := packages.Load(e.Config, patterns...)
+	if err != nil {
+		return pkgs, err
+	}
+
+	for _, p := range pkgs {
+		if len(p.Errors) > 0 {
+			return pkgs, fmt.Errorf("%v", p.Errors)
+		}
+	}
+
+	return pkgs, nil
 }
diff -pruN 0.0~git20220613.4eb5ba4-2/vulncheck/internal/binscan/scan.go 0.0~git20220725.4151a5a-1/vulncheck/internal/binscan/scan.go
--- 0.0~git20220613.4eb5ba4-2/vulncheck/internal/binscan/scan.go	2022-06-18 09:00:29.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/vulncheck/internal/binscan/scan.go	2022-08-02 19:02:47.000000000 +0000
@@ -42,32 +42,33 @@ func debugModulesToPackagesModules(debug
 	return packagesModules
 }
 
-// ExtractPackagesAndSymbols extracts the symbols, packages, and their
-// associated module versions from a Go binary.
-func ExtractPackagesAndSymbols(bin io.ReaderAt) ([]*packages.Module, map[string][]string, error) {
+// ExtractPackagesAndSymbols extracts the symbols, packages, their
+// associated module versions from a Go binary, and Go version used
+// to build the binary.
+func ExtractPackagesAndSymbols(bin io.ReaderAt) ([]*packages.Module, map[string][]string, string, error) {
 	bi, err := buildinfo.Read(bin)
 	if err != nil {
-		return nil, nil, err
+		return nil, nil, "", err
 	}
 
 	x, err := openExe(bin)
 	if err != nil {
-		return nil, nil, err
+		return nil, nil, "", err
 	}
 
 	pclntab, textOffset := x.PCLNTab()
 	if pclntab == nil {
 		// TODO(roland): if we have build information, but not PCLN table, we should be able to
 		// fall back to much higher granularity vulnerability checking.
-		return nil, nil, errors.New("unable to load the PCLN table")
+		return nil, nil, "", errors.New("unable to load the PCLN table")
 	}
 	lineTab := gosym.NewLineTable(pclntab, textOffset)
 	if lineTab == nil {
-		return nil, nil, errors.New("invalid line table")
+		return nil, nil, "", errors.New("invalid line table")
 	}
 	tab, err := gosym.NewTable(nil, lineTab)
 	if err != nil {
-		return nil, nil, err
+		return nil, nil, "", err
 	}
 
 	packageSymbols := map[string][]string{}
@@ -77,27 +78,27 @@ func ExtractPackagesAndSymbols(bin io.Re
 		}
 		pkgName, symName, err := parseName(f.Func.Sym)
 		if err != nil {
-			return nil, nil, err
+			return nil, nil, "", err
 		}
 		packageSymbols[pkgName] = append(packageSymbols[pkgName], symName)
 		value, base, r, err := x.SymbolInfo("go.func.*")
 		if err != nil {
-			return nil, nil, fmt.Errorf("reading go.func.*: %v", err)
+			return nil, nil, "", fmt.Errorf("reading go.func.*: %v", err)
 		}
 		it, err := lineTab.InlineTree(&f, value, base, r)
 		if err != nil {
-			return nil, nil, fmt.Errorf("InlineTree: %v", err)
+			return nil, nil, "", fmt.Errorf("InlineTree: %v", err)
 		}
 		for _, ic := range it {
 			pkgName, symName, err := parseName(&gosym.Sym{Name: ic.Name})
 			if err != nil {
-				return nil, nil, err
+				return nil, nil, "", err
 			}
 			packageSymbols[pkgName] = append(packageSymbols[pkgName], symName)
 		}
 	}
 
-	return debugModulesToPackagesModules(bi.Deps), packageSymbols, nil
+	return debugModulesToPackagesModules(bi.Deps), packageSymbols, bi.GoVersion, nil
 }
 
 func parseName(s *gosym.Sym) (pkg, sym string, err error) {
diff -pruN 0.0~git20220613.4eb5ba4-2/vulncheck/internal/binscan/scan_test.go 0.0~git20220725.4151a5a-1/vulncheck/internal/binscan/scan_test.go
--- 0.0~git20220613.4eb5ba4-2/vulncheck/internal/binscan/scan_test.go	2022-06-18 09:00:29.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/vulncheck/internal/binscan/scan_test.go	2022-08-02 19:02:47.000000000 +0000
@@ -28,7 +28,7 @@ func TestExtractPackagesAndSymbols(t *te
 				t.Fatal(err)
 			}
 			defer f.Close()
-			_, syms, err := ExtractPackagesAndSymbols(f)
+			_, syms, _, err := ExtractPackagesAndSymbols(f)
 			if err != nil {
 				t.Fatal(err)
 			}
diff -pruN 0.0~git20220613.4eb5ba4-2/vulncheck/source.go 0.0~git20220725.4151a5a-1/vulncheck/source.go
--- 0.0~git20220613.4eb5ba4-2/vulncheck/source.go	2022-06-18 09:00:29.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/vulncheck/source.go	2022-08-02 19:02:47.000000000 +0000
@@ -45,6 +45,14 @@ func Source(ctx context.Context, pkgs []
 		}
 	}
 
+	// set the stdlib version for detection of vulns in the standard library
+	// TODO(#53740): what if Go version is not in semver format?
+	if cfg.SourceGoVersion != "" {
+		stdlibModule.Version = goTagToSemver(cfg.SourceGoVersion)
+	} else {
+		stdlibModule.Version = goTagToSemver(runtime.Version())
+	}
+
 	mods := extractModules(pkgs)
 	modVulns, err := fetchVulnerabilities(ctx, cfg.Client, mods)
 	if err != nil {
@@ -60,7 +68,10 @@ func Source(ctx context.Context, pkgs []
 
 	vulnPkgModSlice(pkgs, modVulns, result)
 	setModules(result, mods)
-	if cfg.ImportsOnly {
+	// Return result immediately if in ImportsOnly mode or
+	// if there are no vulnerable packages, as there is no
+	// need to build the call graph.
+	if cfg.ImportsOnly || len(result.Imports.Packages) == 0 {
 		return result, nil
 	}
 
@@ -82,7 +93,7 @@ func setModules(r *Result, mods []*Modul
 	}
 	// Sort for determinism.
 	sort.Slice(mods, func(i, j int) bool { return mods[i].Path < mods[j].Path })
-	r.Modules = mods
+	r.Modules = append(r.Modules, mods...)
 }
 
 // pkgID is an id counter for nodes of Imports graph.
@@ -205,16 +216,25 @@ func vulnModuleSlice(result *Result) {
 	for _, id := range pkgIDs {
 		pkgNode := result.Imports.Packages[id]
 		// Create or get module node for pkgNode.
-		pkgModID := moduleNodeID(pkgNode, result, modNodeIDs)
-		pkgNode.Module = pkgModID
+		modID := moduleNodeID(pkgNode, result, modNodeIDs)
+		pkgNode.Module = modID
+
+		// Update the set of predecessors.
+		if _, ok := modPredRelation[modID]; !ok {
+			modPredRelation[modID] = make(map[int]bool)
+		}
+		predSet := modPredRelation[modID]
 
-		// Get the set of predecessors.
-		predSet := make(map[int]bool)
 		for _, predPkgID := range pkgNode.ImportedBy {
 			predModID := moduleNodeID(result.Imports.Packages[predPkgID], result, modNodeIDs)
+			// We don't add module edges for imports
+			// of packages in the same module as that
+			// will create self-loops in Requires graphs.
+			if predModID == modID {
+				continue
+			}
 			predSet[predModID] = true
 		}
-		modPredRelation[pkgModID] = predSet
 	}
 
 	// Add entry module IDs.
@@ -265,6 +285,10 @@ func nextModID() int {
 // node is stored to result.
 func moduleNodeID(pkgNode *PkgNode, result *Result, modNodeIDs map[string]int) int {
 	mod := pkgNode.pkg.Module
+	if isStdPackage(pkgNode.Path) {
+		// standard library packages don't have a module.
+		mod = stdlibModule
+	}
 	if mod == nil {
 		return 0
 	}
@@ -427,9 +451,6 @@ func vulnCallGraph(sources []*callgraph.
 		}
 		visited[n] = true
 
-		// make the resulting graph deterministic
-		// in the ordering of call graph edges.
-		sortEdges(n.In)
 		for _, edge := range n.In {
 			nCallee := createNode(edge.Callee.Func)
 			nCaller := createNode(edge.Caller.Func)
@@ -453,15 +474,6 @@ func vulnCallGraph(sources []*callgraph.
 	}
 }
 
-// sortEdges sorts edges by their string representation that takes
-// into account caller, callee, and the call site.
-func sortEdges(edges []*callgraph.Edge) {
-	str := func(e *callgraph.Edge) string {
-		return fmt.Sprintf("%v[%v]%v", e.Caller, e.Site, e.Callee)
-	}
-	sort.SliceStable(edges, func(i, j int) bool { return str(edges[i]) < str(edges[j]) })
-}
-
 // vulnFuncs returns vulnerability information for vulnerable functions in cg.
 func vulnFuncs(cg *callgraph.Graph, modVulns moduleVulnerabilities) map[*callgraph.Node][]*osv.Entry {
 	m := make(map[*callgraph.Node][]*osv.Entry)
diff -pruN 0.0~git20220613.4eb5ba4-2/vulncheck/source_test.go 0.0~git20220725.4151a5a-1/vulncheck/source_test.go
--- 0.0~git20220613.4eb5ba4-2/vulncheck/source_test.go	2022-06-18 09:00:29.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/vulncheck/source_test.go	2022-08-02 19:02:47.000000000 +0000
@@ -18,20 +18,19 @@ import (
 	"golang.org/x/vuln/osv"
 )
 
-// TestImportsOnly checks for module and imports graph correctness
-// for the Config.ImportsOnly=true mode. The inlined test code has
-// the following package (left) and module (right) imports graphs:
+// TestImports checks for imports graph correctness. The inlined
+// test code has the following package imports graphs:
 //
-//	entry/x        entry/y                     entry
-//	       \     /        \                   /     \
-//	     amod/avuln      zmod/z           amod       zmod
-//	         |                              |
-//	       wmod/w                         wmod
-//	         |                              |
-//	     bmod/bvuln                       bmod
+//	entry/x        entry/y
+//	       \     /        \
+//	     amod/avuln      zmod/z
+//	         |
+//	       wmod/w
+//	         |
+//	     bmod/bvuln
 //
 // Packages ending in "vuln" have some known vulnerabilities.
-func TestImportsOnly(t *testing.T) {
+func TestImports(t *testing.T) {
 	e := packagestest.Export(t, packagestest.Modules, []packagestest.Module{
 		{
 			Name: "golang.org/entry",
@@ -63,7 +62,11 @@ func TestImportsOnly(t *testing.T) {
 			Files: map[string]interface{}{"z/z.go": `
 			package z
 
-			func Z() {}
+			import "archive/zip"
+
+			func Z() {
+				_, _ = zip.OpenReader("filename")
+			}
 			`},
 		},
 		{
@@ -83,10 +86,18 @@ func TestImportsOnly(t *testing.T) {
 			Files: map[string]interface{}{"bvuln/bvuln.go": `
 			package bvuln
 
+			import _ "golang.org/cmod/c"
+
 			func Vuln() {}
 			`},
 		},
 		{
+			Name: "golang.org/cmod@v0.3.0",
+			Files: map[string]interface{}{"c/c.go": `
+			package c
+			`},
+		},
+		{
 			Name: "golang.org/wmod@v0.0.0",
 			Files: map[string]interface{}{"w/w.go": `
 			package w
@@ -104,66 +115,186 @@ func TestImportsOnly(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-
 	if len(pkgs) != 2 {
 		t.Fatal("failed to load x and y test packages")
 	}
 
 	cfg := &Config{
-		Client:      testClient,
-		ImportsOnly: true,
+		Client:          testClient,
+		ImportsOnly:     true,
+		SourceGoVersion: "go1.18",
 	}
+
 	result, err := Source(context.Background(), Convert(pkgs), cfg)
 	if err != nil {
 		t.Fatal(err)
 	}
 
 	// Check that we find the right number of vulnerabilities.
-	// There should be three entries as there are three vulnerable
-	// symbols in the two import-reachable OSVs.
-	if len(result.Vulns) != 3 {
-		t.Errorf("want 3 Vulns, got %d", len(result.Vulns))
+	// There should be four entries as there are three vulnerable
+	// symbols in the two import-reachable OSVs and one standard
+	// library vulnerability.
+	if len(result.Vulns) != 4 {
+		t.Errorf("want 4 Vulns, got %d", len(result.Vulns))
 	}
 
-	// Check that vulnerabilities are connected to the imports
-	// and requires graph.
+	// Check that vulnerabilities are connected to the imports graph.
 	for _, v := range result.Vulns {
-		if v.ImportSink == 0 || v.RequireSink == 0 {
-			t.Errorf("want ImportSink !=0 and RequireSink !=0 for %v:%v; got %v and %v", v.Symbol, v.PkgPath, v.ImportSink, v.RequireSink)
+		if v.ImportSink == 0 {
+			t.Errorf("want ImportSink !=0 for %v:%v; got %v", v.Symbol, v.PkgPath, v.ImportSink)
 		}
 	}
 
-	// Check that module and package entry points are collected.
+	// Check that the package entry points are collected.
 	if got := len(result.Imports.Entries); got != 2 {
 		t.Errorf("want 2 package entry points; got %v", got)
 	}
-	if got := len(result.Requires.Entries); got != 1 {
-		t.Errorf("want 1 module entry point; got %v", got)
-	}
 
 	// The imports slice should include import chains:
 	//   x -> avuln -> w -> bvuln
 	//         |
-	//   y ---->
-	// That is, z package shoud not appear in the slice.
+	//   y ---- ------> z
+	// That is, c package shoud not appear in the slice.
 	wantImports := map[string][]string{
 		"golang.org/entry/x":    {"golang.org/amod/avuln"},
-		"golang.org/entry/y":    {"golang.org/amod/avuln"},
+		"golang.org/entry/y":    {"golang.org/amod/avuln", "golang.org/zmod/z"},
 		"golang.org/amod/avuln": {"golang.org/wmod/w"},
 		"golang.org/wmod/w":     {"golang.org/bmod/bvuln"},
+		"golang.org/zmod/z":     {"archive/zip"},
 	}
 
 	if igStrMap := impGraphToStrMap(result.Imports); !reflect.DeepEqual(wantImports, igStrMap) {
 		t.Errorf("want %v imports graph; got %v", wantImports, igStrMap)
 	}
 
+	// Check that the source's modules are returned.
+	wantMods := []*Module{
+		{Path: "golang.org/amod", Version: "v1.1.3"},
+		{Path: "golang.org/bmod", Version: "v0.5.0"},
+		{Path: "golang.org/cmod", Version: "v0.3.0"},
+		{Path: "golang.org/entry"},
+		{Path: "golang.org/wmod", Version: "v0.0.0"},
+		{Path: "golang.org/zmod", Version: "v0.0.0"},
+		{Path: "stdlib", Version: "v1.18.0"},
+	}
+	gotMods := result.Modules
+	sort.Slice(gotMods, func(i, j int) bool { return gotMods[i].Path < gotMods[j].Path })
+	if diff := cmp.Diff(wantMods, gotMods, cmpopts.IgnoreFields(Module{}, "Dir")); diff != "" {
+		t.Errorf("modules mismatch (-want, +got):\n%s", diff)
+	}
+}
+
+// TestRequires checks for module requires graph correctness. The
+// inlined test code has the following import/requires graphs:
+//
+//		entry/x		        entry
+//		/     \                 /   \
+//	 imod1/i    imod2/i         imod1   imod2
+//	    |          |                \   /
+//	  amod/a1 -> amod/a2            amod
+//		       |                  |
+//	            bmod/bvuln          bmod
+//
+// Packages ending in "vuln" have some known vulnerabilities.
+func TestRequires(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/imod1/i"
+				_ "golang.org/imod2/i"
+			)
+			`},
+		},
+		{
+			Name: "golang.org/imod1@v0.0.0",
+			Files: map[string]interface{}{
+				"i/i.go": `
+			package i
+
+			import _ "golang.org/amod/a1"
+			`},
+		},
+		{
+			Name: "golang.org/imod2@v0.0.0",
+			Files: map[string]interface{}{
+				"i/i.go": `
+			package i
+
+			import _ "golang.org/amod/a2"
+			`},
+		},
+		{
+			Name: "golang.org/amod@v0.0.1",
+			Files: map[string]interface{}{"a1/a1.go": `
+			package a1
+
+			import _ "golang.org/amod/a2"
+
+			`,
+				"a2/a2.go": `
+			package a2
+
+			import _ "golang.org/bmod/bvuln"
+		`}},
+		{
+			Name: "golang.org/bmod@v0.5.0",
+			Files: map[string]interface{}{"bvuln/bvuln.go": `
+			package bvuln
+
+			func Vuln() {}
+			`},
+		},
+	})
+	defer e.Cleanup()
+
+	// Load x as entry package.
+	pkgs, err := loadPackages(e, path.Join(e.Temp(), "entry/x"))
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(pkgs) != 1 {
+		t.Fatal("failed to load x test package")
+	}
+
+	cfg := &Config{
+		Client:      testClient,
+		ImportsOnly: true,
+	}
+	result, err := Source(context.Background(), Convert(pkgs), cfg)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// There should be only one vulnerability bvuln.Vuln.
+	if len(result.Vulns) != 1 {
+		t.Errorf("want 1 Vuln, got %d", len(result.Vulns))
+	}
+
+	// Check that vulnerabilities are connected to the requires graph.
+	if v := result.Vulns[0]; v.RequireSink == 0 {
+		t.Errorf("want RequireSink !=0 for %v:%v; got %v", v.Symbol, v.PkgPath, v.RequireSink)
+	}
+
+	// Check that the module entry points are collected.
+	if got := len(result.Requires.Entries); got != 1 {
+		t.Errorf("want 1 module entry point; got %v", got)
+	}
+
 	// The requires slice should include requires chains:
-	//   entry -> amod -> wmod -> bmod
+	//   entry -> imod1 -> amod -> bmod
+	//     |                |
+	//     -----> imod2 ---->
 	// That is, zmod module shoud not appear in the slice.
 	wantRequires := map[string][]string{
-		"golang.org/entry": {"golang.org/amod"},
-		"golang.org/amod":  {"golang.org/wmod"},
-		"golang.org/wmod":  {"golang.org/bmod"},
+		"golang.org/entry": {"golang.org/imod1", "golang.org/imod2"},
+		"golang.org/imod1": {"golang.org/amod"},
+		"golang.org/imod2": {"golang.org/amod"},
+		"golang.org/amod":  {"golang.org/bmod"},
 	}
 
 	if rgStrMap := reqGraphToStrMap(result.Requires); !reflect.DeepEqual(wantRequires, rgStrMap) {
@@ -172,11 +303,12 @@ func TestImportsOnly(t *testing.T) {
 
 	// Check that the source's modules are returned.
 	wantMods := []*Module{
-		{Path: "golang.org/amod", Version: "v1.1.3"},
+		{Path: "golang.org/amod", Version: "v0.0.1"},
 		{Path: "golang.org/bmod", Version: "v0.5.0"},
 		{Path: "golang.org/entry"},
-		{Path: "golang.org/wmod", Version: "v0.0.0"},
-		{Path: "golang.org/zmod", Version: "v0.0.0"},
+		{Path: "golang.org/imod1", Version: "v0.0.0"},
+		{Path: "golang.org/imod2", Version: "v0.0.0"},
+		stdlibModule,
 	}
 	gotMods := result.Modules
 	sort.Slice(gotMods, func(i, j int) bool { return gotMods[i].Path < gotMods[j].Path })
@@ -185,7 +317,7 @@ func TestImportsOnly(t *testing.T) {
 	}
 }
 
-// TestCallGraph checks for call graph vuln slicing correctness.
+// TestCalls checks for call graph vuln slicing correctness.
 // The inlined test code has the following call graph
 //
 //	        x.X
@@ -221,7 +353,7 @@ func TestImportsOnly(t *testing.T) {
 //	   e.E
 //
 // related to avuln.VulnData.{Vuln1, Vuln2} and bvuln.Vuln vulnerabilities.
-func TestCallGraph(t *testing.T) {
+func TestCalls(t *testing.T) {
 	e := packagestest.Export(t, packagestest.Modules, []packagestest.Module{
 		{
 			Name: "golang.org/entry",
@@ -362,7 +494,6 @@ func TestCallGraph(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-
 	if len(pkgs) != 2 {
 		t.Fatal("failed to load x and y test packages")
 	}
diff -pruN 0.0~git20220613.4eb5ba4-2/vulncheck/vulncheck.go 0.0~git20220725.4151a5a-1/vulncheck/vulncheck.go
--- 0.0~git20220613.4eb5ba4-2/vulncheck/vulncheck.go	2022-06-18 09:00:29.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/vulncheck/vulncheck.go	2022-08-02 19:02:47.000000000 +0000
@@ -16,14 +16,26 @@ import (
 	"golang.org/x/vuln/osv"
 )
 
+// ResultVersion should change when the results of this package change
+// for any input. It is intended to be used to cache results.
+// The field should begin with a date in YYYY-MM-DD format.
+// Experimental versions of the package should follow that with
+// a brief description. E.g. "2022-08-03 CHA only".
+const ResultVersion = "2022-07-21"
+
 // Config is used for configuring vulncheck algorithms.
 type Config struct {
-	// If ImportsOnly is true, vulncheck analyzes import chains only.
+	// ImportsOnly instructs vulncheck to analyze import chains only.
 	// Otherwise, call chains are analyzed too.
 	ImportsOnly bool
 
 	// Client is used for querying data from a vulnerability database.
 	Client client.Client
+
+	// SourceGoVersion is Go version used to build Source inputs passed
+	// to vulncheck. If not provided, the current underlying Go version
+	// is used to detect vulnerabilities in Go standard library.
+	SourceGoVersion string
 }
 
 // Package is a Go package for vulncheck analysis. It is a version of
@@ -99,7 +111,9 @@ type Result struct {
 
 	// Requires is a module dependency graph whose roots are entry user modules
 	// and sinks are modules with some vulnerable packages. It is empty when no
-	// modules with vulnerabilities are required by the program.
+	// modules with vulnerabilities are required by the program. If used, the
+	// standard library is modeled as an artificial "stdlib" module whose version
+	// is the Go version used to build the code under analysis.
 	Requires *RequireGraph
 
 	// Vulns contains information on detected vulnerabilities and their place in
@@ -359,10 +373,15 @@ func matchesPlatform(os, arch string, e
 // specific prefix of importPath, or nil if there is no matching module with
 // vulnerabilities.
 func (mv moduleVulnerabilities) vulnsForPackage(importPath string) []*osv.Entry {
+	isStd := isStdPackage(importPath)
 	var mostSpecificMod *modVulns
 	for _, mod := range mv {
 		md := mod
-		if strings.HasPrefix(importPath, md.mod.Path) {
+		if isStd && mod.mod == stdlibModule {
+			// standard library packages do not have an associated module,
+			// so we relate them to the artificial stdlib module.
+			mostSpecificMod = &md
+		} else if strings.HasPrefix(importPath, md.mod.Path) {
 			if mostSpecificMod == nil || len(mostSpecificMod.mod.Path) < len(md.mod.Path) {
 				mostSpecificMod = &md
 			}
@@ -374,6 +393,7 @@ func (mv moduleVulnerabilities) vulnsFor
 	}
 
 	if mostSpecificMod.mod.Replace != nil {
+		// standard libraries do not have a module nor replace module
 		importPath = fmt.Sprintf("%s%s", mostSpecificMod.mod.Replace.Path, strings.TrimPrefix(importPath, mostSpecificMod.mod.Path))
 	}
 	vulns := mostSpecificMod.vulns
diff -pruN 0.0~git20220613.4eb5ba4-2/vulncheck/vulncheck_test.go 0.0~git20220725.4151a5a-1/vulncheck/vulncheck_test.go
--- 0.0~git20220613.4eb5ba4-2/vulncheck/vulncheck_test.go	2022-06-18 09:00:29.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/vulncheck/vulncheck_test.go	2022-08-02 19:02:47.000000000 +0000
@@ -226,7 +226,7 @@ func TestConvert(t *testing.T) {
 				"x/x.go": `
 			package x
 
-			import "golang.org/amod/avuln"
+			import _ "golang.org/amod/avuln"
 		`}},
 		{
 			Name: "golang.org/zmod@v0.0.0",
@@ -239,7 +239,7 @@ func TestConvert(t *testing.T) {
 			Files: map[string]interface{}{"avuln/avuln.go": `
 			package avuln
 
-			import "golang.org/wmod/w"
+			import _ "golang.org/wmod/w"
 			`},
 		},
 		{
@@ -253,14 +253,14 @@ func TestConvert(t *testing.T) {
 			Files: map[string]interface{}{"w/w.go": `
 			package w
 
-			import "golang.org/bmod/bvuln"
+			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"))
+	// Load x as entry package.
+	pkgs, err := loadPackages(e, path.Join(e.Temp(), "entry/x"))
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -271,7 +271,6 @@ func TestConvert(t *testing.T) {
 		"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) {
diff -pruN 0.0~git20220613.4eb5ba4-2/vulncheck/witness.go 0.0~git20220725.4151a5a-1/vulncheck/witness.go
--- 0.0~git20220613.4eb5ba4-2/vulncheck/witness.go	2022-06-18 09:00:29.000000000 +0000
+++ 0.0~git20220725.4151a5a-1/vulncheck/witness.go	2022-08-02 19:02:47.000000000 +0000
@@ -6,6 +6,8 @@ package vulncheck
 
 import (
 	"container/list"
+	"fmt"
+	"go/token"
 	"sort"
 	"strings"
 	"sync"
@@ -194,7 +196,9 @@ func callStacks(vulnSinkID int, res *Res
 		}
 		seen[f.ID] = true
 
-		for _, cs := range f.CallSites {
+		// Pick a single call site for each function in determinstic order.
+		// A single call site is sufficient as we visit a function only once.
+		for _, cs := range callsites(f.CallSites, res, seen) {
 			callee := res.Calls.Functions[cs.Parent]
 			nStack := &callChain{f: callee, call: cs, child: c}
 			if entries[callee.ID] {
@@ -206,6 +210,34 @@ func callStacks(vulnSinkID int, res *Res
 	return stacks
 }
 
+// callsites picks a call site from sites for each non-visited function.
+// For each such function, the smallest (posLess) call site is chosen. The
+// returned slice is sorted by caller functions (funcLess). Assumes callee
+// of each call site is the same.
+func callsites(sites []*CallSite, result *Result, visited map[int]bool) []*CallSite {
+	minCs := make(map[int]*CallSite)
+	for _, cs := range sites {
+		if visited[cs.Parent] {
+			continue
+		}
+		if csLess(cs, minCs[cs.Parent]) {
+			minCs[cs.Parent] = cs
+		}
+	}
+
+	var fs []*FuncNode
+	for id := range minCs {
+		fs = append(fs, result.Calls.Functions[id])
+	}
+	sort.SliceStable(fs, func(i, j int) bool { return funcLess(fs[i], fs[j]) })
+
+	var css []*CallSite
+	for _, f := range fs {
+		css = append(css, minCs[f.ID])
+	}
+	return css
+}
+
 // callChain models a chain of function calls.
 type callChain struct {
 	call  *CallSite // nil for entry points
@@ -286,3 +318,77 @@ func stackLess(s1, s2 CallStack) bool {
 	// search algorithm.
 	return true
 }
+
+// csLess compares two call sites by their locations and, if needed,
+// their string representation.
+func csLess(cs1, cs2 *CallSite) bool {
+	if cs2 == nil {
+		return true
+	}
+
+	// fast code path
+	if p1, p2 := cs1.Pos, cs2.Pos; p1 != nil && p2 != nil {
+		if posLess(*p1, *p2) {
+			return true
+		}
+		if posLess(*p2, *p1) {
+			return false
+		}
+		// for sanity, should not occur in practice
+		return fmt.Sprintf("%v.%v", cs1.RecvType, cs2.Name) < fmt.Sprintf("%v.%v", cs2.RecvType, cs2.Name)
+	}
+
+	// code path rarely exercised
+	if cs2.Pos == nil {
+		return true
+	}
+	if cs1.Pos == nil {
+		return false
+	}
+	// should very rarely occur in practice
+	return fmt.Sprintf("%v.%v", cs1.RecvType, cs2.Name) < fmt.Sprintf("%v.%v", cs2.RecvType, cs2.Name)
+}
+
+// posLess compares two positions by their line and column number,
+// and filename if needed.
+func posLess(p1, p2 token.Position) bool {
+	if p1.Line < p2.Line {
+		return true
+	}
+	if p2.Line < p1.Line {
+		return false
+	}
+
+	if p1.Column < p2.Column {
+		return true
+	}
+	if p2.Column < p1.Column {
+		return false
+	}
+
+	return strings.Compare(p1.Filename, p2.Filename) == -1
+}
+
+// funcLess compares two function nodes by locations of
+// corresponding functions and, if needed, their string representation.
+func funcLess(f1, f2 *FuncNode) bool {
+	if p1, p2 := f1.Pos, f2.Pos; p1 != nil && p2 != nil {
+		if posLess(*p1, *p2) {
+			return true
+		}
+		if posLess(*p2, *p1) {
+			return false
+		}
+		// for sanity, should not occur in practice
+		return f1.String() < f2.String()
+	}
+
+	if f2.Pos == nil {
+		return true
+	}
+	if f1.Pos == nil {
+		return false
+	}
+	// should happen only for inits
+	return f1.String() < f2.String()
+}
