cmd/go/internal/modload: fix boundary conditions in matchPackages

This makes the boundary logic of matchPackages consistent with
modload.dirInModule.

Previously, matchPackages always stopped at go.mod file, even within
the vendor tree. However, we do not guarantee that the vendor tree is
free of such files in general.

matchPackages also issued needless stat operations for modules in the
module cach, which we already know to be free of nested modules. On
systems with slow filesystems (such as macOS), those extra calls could
potentially slow package matching considerably.

Change-Id: I71979ab752e1d3971b370b37085d30502690413b
Reviewed-on: https://go-review.googlesource.com/c/go/+/172985
Run-TryBot: Bryan C. Mills <bcmills@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Jay Conrod <jayconrod@google.com>
diff --git a/src/cmd/go/internal/modload/search.go b/src/cmd/go/internal/modload/search.go
index 3af3974..d82386e 100644
--- a/src/cmd/go/internal/modload/search.go
+++ b/src/cmd/go/internal/modload/search.go
@@ -35,7 +35,13 @@
 	}
 	var pkgs []string
 
-	walkPkgs := func(root, importPathRoot string, includeVendor bool) {
+	type pruning int8
+	const (
+		pruneVendor = pruning(1 << iota)
+		pruneGoMod
+	)
+
+	walkPkgs := func(root, importPathRoot string, prune pruning) {
 		root = filepath.Clean(root)
 		filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
 			if err != nil {
@@ -75,7 +81,7 @@
 				return filepath.SkipDir
 			}
 			// Stop at module boundaries.
-			if path != root {
+			if (prune&pruneGoMod != 0) && path != root {
 				if fi, err := os.Stat(filepath.Join(path, "go.mod")); err == nil && !fi.IsDir() {
 					return filepath.SkipDir
 				}
@@ -90,7 +96,7 @@
 				}
 			}
 
-			if elem == "vendor" && !includeVendor {
+			if elem == "vendor" && (prune&pruneVendor != 0) {
 				return filepath.SkipDir
 			}
 			return nil
@@ -98,16 +104,16 @@
 	}
 
 	if useStd {
-		walkPkgs(cfg.GOROOTsrc, "", true)
+		walkPkgs(cfg.GOROOTsrc, "", pruneGoMod)
 		if treeCanMatch("cmd") {
-			walkPkgs(filepath.Join(cfg.GOROOTsrc, "cmd"), "cmd", true)
+			walkPkgs(filepath.Join(cfg.GOROOTsrc, "cmd"), "cmd", pruneGoMod)
 		}
 	}
 
 	if cfg.BuildMod == "vendor" {
 		if HasModRoot() {
-			walkPkgs(ModRoot(), targetPrefix, false)
-			walkPkgs(filepath.Join(ModRoot(), "vendor"), "", false)
+			walkPkgs(ModRoot(), targetPrefix, pruneGoMod|pruneVendor)
+			walkPkgs(filepath.Join(ModRoot(), "vendor"), "", pruneVendor)
 		}
 		return pkgs
 	}
@@ -116,23 +122,33 @@
 		if !treeCanMatch(mod.Path) {
 			continue
 		}
-		var root, modPrefix string
+
+		var (
+			root, modPrefix string
+			isLocal         bool
+		)
 		if mod == Target {
 			if !HasModRoot() {
 				continue // If there is no main module, we can't search in it.
 			}
 			root = ModRoot()
 			modPrefix = targetPrefix
+			isLocal = true
 		} else {
 			var err error
-			root, _, err = fetch(mod)
+			root, isLocal, err = fetch(mod)
 			if err != nil {
 				base.Errorf("go: %v", err)
 				continue
 			}
 			modPrefix = mod.Path
 		}
-		walkPkgs(root, modPrefix, false)
+
+		prune := pruneVendor
+		if isLocal {
+			prune |= pruneGoMod
+		}
+		walkPkgs(root, modPrefix, prune)
 	}
 
 	return pkgs
diff --git a/src/cmd/go/testdata/script/mod_patterns_vendor.txt b/src/cmd/go/testdata/script/mod_patterns_vendor.txt
new file mode 100644
index 0000000..b4dc401
--- /dev/null
+++ b/src/cmd/go/testdata/script/mod_patterns_vendor.txt
@@ -0,0 +1,28 @@
+env GO111MODULE=on
+
+go list -mod=vendor example.com/...
+stdout ^example.com/x$
+stdout ^example.com/x/y$
+! stdout ^example.com/x/vendor
+
+-- go.mod --
+module example.com/m
+
+-- vendor/modules.txt --
+# example.com/x v0.0.0
+example.com/x
+# example.com/x/y v0.1.0
+example.com/x/y
+
+-- vendor/example.com/x/go.mod --
+module example.com/x
+-- vendor/example.com/x/x.go --
+package x
+
+-- vendor/example.com/x/y/go.mod --
+module example.com/x/y
+-- vendor/example.com/x/y/y.go --
+package y
+
+-- vendor/example.com/x/vendor/z/z.go --
+package z