1// Copyright 2017 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package search
6
7import (
8	"cmd/go/internal/base"
9	"cmd/go/internal/cfg"
10	"fmt"
11	"go/build"
12	"log"
13	"os"
14	"path"
15	"path/filepath"
16	"regexp"
17	"strings"
18)
19
20// A Match represents the result of matching a single package pattern.
21type Match struct {
22	Pattern string   // the pattern itself
23	Literal bool     // whether it is a literal (no wildcards)
24	Pkgs    []string // matching packages (dirs or import paths)
25}
26
27// MatchPackages returns all the packages that can be found
28// under the $GOPATH directories and $GOROOT matching pattern.
29// The pattern is either "all" (all packages), "std" (standard packages),
30// "cmd" (standard commands), or a path including "...".
31func MatchPackages(pattern string) *Match {
32	m := &Match{
33		Pattern: pattern,
34		Literal: false,
35	}
36	match := func(string) bool { return true }
37	treeCanMatch := func(string) bool { return true }
38	if !IsMetaPackage(pattern) {
39		match = MatchPattern(pattern)
40		treeCanMatch = TreeCanMatchPattern(pattern)
41	}
42
43	have := map[string]bool{
44		"builtin": true, // ignore pseudo-package that exists only for documentation
45	}
46	if !cfg.BuildContext.CgoEnabled {
47		have["runtime/cgo"] = true // ignore during walk
48	}
49
50	for _, src := range cfg.BuildContext.SrcDirs() {
51		if (pattern == "std" || pattern == "cmd") && src != cfg.GOROOTsrc {
52			continue
53		}
54		src = filepath.Clean(src) + string(filepath.Separator)
55		root := src
56		if pattern == "cmd" {
57			root += "cmd" + string(filepath.Separator)
58		}
59		filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
60			if err != nil || path == src {
61				return nil
62			}
63
64			want := true
65			// Avoid .foo, _foo, and testdata directory trees.
66			_, elem := filepath.Split(path)
67			if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
68				want = false
69			}
70
71			name := filepath.ToSlash(path[len(src):])
72			if pattern == "std" && (!IsStandardImportPath(name) || name == "cmd") {
73				// The name "std" is only the standard library.
74				// If the name is cmd, it's the root of the command tree.
75				want = false
76			}
77			if !treeCanMatch(name) {
78				want = false
79			}
80
81			if !fi.IsDir() {
82				if fi.Mode()&os.ModeSymlink != 0 && want {
83					if target, err := os.Stat(path); err == nil && target.IsDir() {
84						fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path)
85					}
86				}
87				return nil
88			}
89			if !want {
90				return filepath.SkipDir
91			}
92
93			if have[name] {
94				return nil
95			}
96			have[name] = true
97			if !match(name) {
98				return nil
99			}
100			pkg, err := cfg.BuildContext.ImportDir(path, 0)
101			if err != nil {
102				if _, noGo := err.(*build.NoGoError); noGo {
103					return nil
104				}
105			}
106
107			// If we are expanding "cmd", skip main
108			// packages under cmd/vendor. At least as of
109			// March, 2017, there is one there for the
110			// vendored pprof tool.
111			if pattern == "cmd" && strings.HasPrefix(pkg.ImportPath, "cmd/vendor") && pkg.Name == "main" {
112				return nil
113			}
114
115			m.Pkgs = append(m.Pkgs, name)
116			return nil
117		})
118	}
119	return m
120}
121
122var modRoot string
123
124func SetModRoot(dir string) {
125	modRoot = dir
126}
127
128// MatchPackagesInFS is like MatchPackages but is passed a pattern that
129// begins with an absolute path or "./" or "../". On Windows, the pattern may
130// use slash or backslash separators or a mix of both.
131//
132// MatchPackagesInFS scans the tree rooted at the directory that contains the
133// first "..." wildcard and returns a match with packages that
134func MatchPackagesInFS(pattern string) *Match {
135	m := &Match{
136		Pattern: pattern,
137		Literal: false,
138	}
139
140	// Clean the path and create a matching predicate.
141	// filepath.Clean removes "./" prefixes (and ".\" on Windows). We need to
142	// preserve these, since they are meaningful in MatchPattern and in
143	// returned import paths.
144	cleanPattern := filepath.Clean(pattern)
145	isLocal := strings.HasPrefix(pattern, "./") || (os.PathSeparator == '\\' && strings.HasPrefix(pattern, `.\`))
146	prefix := ""
147	if cleanPattern != "." && isLocal {
148		prefix = "./"
149		cleanPattern = "." + string(os.PathSeparator) + cleanPattern
150	}
151	slashPattern := filepath.ToSlash(cleanPattern)
152	match := MatchPattern(slashPattern)
153
154	// Find directory to begin the scan.
155	// Could be smarter but this one optimization
156	// is enough for now, since ... is usually at the
157	// end of a path.
158	i := strings.Index(cleanPattern, "...")
159	dir, _ := filepath.Split(cleanPattern[:i])
160
161	// pattern begins with ./ or ../.
162	// path.Clean will discard the ./ but not the ../.
163	// We need to preserve the ./ for pattern matching
164	// and in the returned import paths.
165
166	if modRoot != "" {
167		abs, err := filepath.Abs(dir)
168		if err != nil {
169			base.Fatalf("go: %v", err)
170		}
171		if !hasFilepathPrefix(abs, modRoot) {
172			base.Fatalf("go: pattern %s refers to dir %s, outside module root %s", pattern, abs, modRoot)
173			return nil
174		}
175	}
176
177	filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
178		if err != nil || !fi.IsDir() {
179			return nil
180		}
181		top := false
182		if path == dir {
183			// filepath.Walk starts at dir and recurses. For the recursive case,
184			// the path is the result of filepath.Join, which calls filepath.Clean.
185			// The initial case is not Cleaned, though, so we do this explicitly.
186			//
187			// This converts a path like "./io/" to "io". Without this step, running
188			// "cd $GOROOT/src; go list ./io/..." would incorrectly skip the io
189			// package, because prepending the prefix "./" to the unclean path would
190			// result in "././io", and match("././io") returns false.
191			top = true
192			path = filepath.Clean(path)
193		}
194
195		// Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..".
196		_, elem := filepath.Split(path)
197		dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".."
198		if dot || strings.HasPrefix(elem, "_") || elem == "testdata" {
199			return filepath.SkipDir
200		}
201
202		if !top && cfg.ModulesEnabled {
203			// Ignore other modules found in subdirectories.
204			if fi, err := os.Stat(filepath.Join(path, "go.mod")); err == nil && !fi.IsDir() {
205				return filepath.SkipDir
206			}
207		}
208
209		name := prefix + filepath.ToSlash(path)
210		if !match(name) {
211			return nil
212		}
213
214		// We keep the directory if we can import it, or if we can't import it
215		// due to invalid Go source files. This means that directories containing
216		// parse errors will be built (and fail) instead of being silently skipped
217		// as not matching the pattern. Go 1.5 and earlier skipped, but that
218		// behavior means people miss serious mistakes.
219		// See golang.org/issue/11407.
220		if p, err := cfg.BuildContext.ImportDir(path, 0); err != nil && (p == nil || len(p.InvalidGoFiles) == 0) {
221			if _, noGo := err.(*build.NoGoError); !noGo {
222				log.Print(err)
223			}
224			return nil
225		}
226		m.Pkgs = append(m.Pkgs, name)
227		return nil
228	})
229	return m
230}
231
232// TreeCanMatchPattern(pattern)(name) reports whether
233// name or children of name can possibly match pattern.
234// Pattern is the same limited glob accepted by matchPattern.
235func TreeCanMatchPattern(pattern string) func(name string) bool {
236	wildCard := false
237	if i := strings.Index(pattern, "..."); i >= 0 {
238		wildCard = true
239		pattern = pattern[:i]
240	}
241	return func(name string) bool {
242		return len(name) <= len(pattern) && hasPathPrefix(pattern, name) ||
243			wildCard && strings.HasPrefix(name, pattern)
244	}
245}
246
247// MatchPattern(pattern)(name) reports whether
248// name matches pattern. Pattern is a limited glob
249// pattern in which '...' means 'any string' and there
250// is no other special syntax.
251// Unfortunately, there are two special cases. Quoting "go help packages":
252//
253// First, /... at the end of the pattern can match an empty string,
254// so that net/... matches both net and packages in its subdirectories, like net/http.
255// Second, any slash-separated pattern element containing a wildcard never
256// participates in a match of the "vendor" element in the path of a vendored
257// package, so that ./... does not match packages in subdirectories of
258// ./vendor or ./mycode/vendor, but ./vendor/... and ./mycode/vendor/... do.
259// Note, however, that a directory named vendor that itself contains code
260// is not a vendored package: cmd/vendor would be a command named vendor,
261// and the pattern cmd/... matches it.
262func MatchPattern(pattern string) func(name string) bool {
263	// Convert pattern to regular expression.
264	// The strategy for the trailing /... is to nest it in an explicit ? expression.
265	// The strategy for the vendor exclusion is to change the unmatchable
266	// vendor strings to a disallowed code point (vendorChar) and to use
267	// "(anything but that codepoint)*" as the implementation of the ... wildcard.
268	// This is a bit complicated but the obvious alternative,
269	// namely a hand-written search like in most shell glob matchers,
270	// is too easy to make accidentally exponential.
271	// Using package regexp guarantees linear-time matching.
272
273	const vendorChar = "\x00"
274
275	if strings.Contains(pattern, vendorChar) {
276		return func(name string) bool { return false }
277	}
278
279	re := regexp.QuoteMeta(pattern)
280	re = replaceVendor(re, vendorChar)
281	switch {
282	case strings.HasSuffix(re, `/`+vendorChar+`/\.\.\.`):
283		re = strings.TrimSuffix(re, `/`+vendorChar+`/\.\.\.`) + `(/vendor|/` + vendorChar + `/\.\.\.)`
284	case re == vendorChar+`/\.\.\.`:
285		re = `(/vendor|/` + vendorChar + `/\.\.\.)`
286	case strings.HasSuffix(re, `/\.\.\.`):
287		re = strings.TrimSuffix(re, `/\.\.\.`) + `(/\.\.\.)?`
288	}
289	re = strings.ReplaceAll(re, `\.\.\.`, `[^`+vendorChar+`]*`)
290
291	reg := regexp.MustCompile(`^` + re + `$`)
292
293	return func(name string) bool {
294		if strings.Contains(name, vendorChar) {
295			return false
296		}
297		return reg.MatchString(replaceVendor(name, vendorChar))
298	}
299}
300
301// replaceVendor returns the result of replacing
302// non-trailing vendor path elements in x with repl.
303func replaceVendor(x, repl string) string {
304	if !strings.Contains(x, "vendor") {
305		return x
306	}
307	elem := strings.Split(x, "/")
308	for i := 0; i < len(elem)-1; i++ {
309		if elem[i] == "vendor" {
310			elem[i] = repl
311		}
312	}
313	return strings.Join(elem, "/")
314}
315
316// WarnUnmatched warns about patterns that didn't match any packages.
317func WarnUnmatched(matches []*Match) {
318	for _, m := range matches {
319		if len(m.Pkgs) == 0 {
320			fmt.Fprintf(os.Stderr, "go: warning: %q matched no packages\n", m.Pattern)
321		}
322	}
323}
324
325// ImportPaths returns the matching paths to use for the given command line.
326// It calls ImportPathsQuiet and then WarnUnmatched.
327func ImportPaths(patterns []string) []*Match {
328	matches := ImportPathsQuiet(patterns)
329	WarnUnmatched(matches)
330	return matches
331}
332
333// ImportPathsQuiet is like ImportPaths but does not warn about patterns with no matches.
334func ImportPathsQuiet(patterns []string) []*Match {
335	var out []*Match
336	for _, a := range CleanPatterns(patterns) {
337		if IsMetaPackage(a) {
338			out = append(out, MatchPackages(a))
339			continue
340		}
341
342		if build.IsLocalImport(a) || filepath.IsAbs(a) {
343			var m *Match
344			if strings.Contains(a, "...") {
345				m = MatchPackagesInFS(a)
346			} else {
347				m = &Match{Pattern: a, Literal: true, Pkgs: []string{a}}
348			}
349
350			// Change the file import path to a regular import path if the package
351			// is in GOPATH or GOROOT. We don't report errors here; LoadImport
352			// (or something similar) will report them later.
353			for i, dir := range m.Pkgs {
354				if !filepath.IsAbs(dir) {
355					dir = filepath.Join(base.Cwd, dir)
356				}
357				if bp, _ := cfg.BuildContext.ImportDir(dir, build.FindOnly); bp.ImportPath != "" && bp.ImportPath != "." {
358					m.Pkgs[i] = bp.ImportPath
359				}
360			}
361			out = append(out, m)
362			continue
363		}
364
365		if strings.Contains(a, "...") {
366			out = append(out, MatchPackages(a))
367			continue
368		}
369
370		out = append(out, &Match{Pattern: a, Literal: true, Pkgs: []string{a}})
371	}
372	return out
373}
374
375// CleanPatterns returns the patterns to use for the given command line. It
376// canonicalizes the patterns but does not evaluate any matches. For patterns
377// that are not local or absolute paths, it preserves text after '@' to avoid
378// modifying version queries.
379func CleanPatterns(patterns []string) []string {
380	if len(patterns) == 0 {
381		return []string{"."}
382	}
383	var out []string
384	for _, a := range patterns {
385		var p, v string
386		if build.IsLocalImport(a) || filepath.IsAbs(a) {
387			p = a
388		} else if i := strings.IndexByte(a, '@'); i < 0 {
389			p = a
390		} else {
391			p = a[:i]
392			v = a[i:]
393		}
394
395		// Arguments may be either file paths or import paths.
396		// As a courtesy to Windows developers, rewrite \ to /
397		// in arguments that look like import paths.
398		// Don't replace slashes in absolute paths.
399		if filepath.IsAbs(p) {
400			p = filepath.Clean(p)
401		} else {
402			if filepath.Separator == '\\' {
403				p = strings.ReplaceAll(p, `\`, `/`)
404			}
405
406			// Put argument in canonical form, but preserve leading ./.
407			if strings.HasPrefix(p, "./") {
408				p = "./" + path.Clean(p)
409				if p == "./." {
410					p = "."
411				}
412			} else {
413				p = path.Clean(p)
414			}
415		}
416
417		out = append(out, p+v)
418	}
419	return out
420}
421
422// IsMetaPackage checks if name is a reserved package name that expands to multiple packages.
423func IsMetaPackage(name string) bool {
424	return name == "std" || name == "cmd" || name == "all"
425}
426
427// hasPathPrefix reports whether the path s begins with the
428// elements in prefix.
429func hasPathPrefix(s, prefix string) bool {
430	switch {
431	default:
432		return false
433	case len(s) == len(prefix):
434		return s == prefix
435	case len(s) > len(prefix):
436		if prefix != "" && prefix[len(prefix)-1] == '/' {
437			return strings.HasPrefix(s, prefix)
438		}
439		return s[len(prefix)] == '/' && s[:len(prefix)] == prefix
440	}
441}
442
443// hasFilepathPrefix reports whether the path s begins with the
444// elements in prefix.
445func hasFilepathPrefix(s, prefix string) bool {
446	switch {
447	default:
448		return false
449	case len(s) == len(prefix):
450		return s == prefix
451	case len(s) > len(prefix):
452		if prefix != "" && prefix[len(prefix)-1] == filepath.Separator {
453			return strings.HasPrefix(s, prefix)
454		}
455		return s[len(prefix)] == filepath.Separator && s[:len(prefix)] == prefix
456	}
457}
458
459// IsStandardImportPath reports whether $GOROOT/src/path should be considered
460// part of the standard distribution. For historical reasons we allow people to add
461// their own code to $GOROOT instead of using $GOPATH, but we assume that
462// code will start with a domain name (dot in the first element).
463//
464// Note that this function is meant to evaluate whether a directory found in GOROOT
465// should be treated as part of the standard library. It should not be used to decide
466// that a directory found in GOPATH should be rejected: directories in GOPATH
467// need not have dots in the first element, and they just take their chances
468// with future collisions in the standard library.
469func IsStandardImportPath(path string) bool {
470	i := strings.Index(path, "/")
471	if i < 0 {
472		i = len(path)
473	}
474	elem := path[:i]
475	return !strings.Contains(elem, ".")
476}
477
478// IsRelativePath reports whether pattern should be interpreted as a directory
479// path relative to the current directory, as opposed to a pattern matching
480// import paths.
481func IsRelativePath(pattern string) bool {
482	return strings.HasPrefix(pattern, "./") || strings.HasPrefix(pattern, "../") || pattern == "." || pattern == ".."
483}
484
485// InDir checks whether path is in the file tree rooted at dir.
486// If so, InDir returns an equivalent path relative to dir.
487// If not, InDir returns an empty string.
488// InDir makes some effort to succeed even in the presence of symbolic links.
489// TODO(rsc): Replace internal/test.inDir with a call to this function for Go 1.12.
490func InDir(path, dir string) string {
491	if rel := inDirLex(path, dir); rel != "" {
492		return rel
493	}
494	xpath, err := filepath.EvalSymlinks(path)
495	if err != nil || xpath == path {
496		xpath = ""
497	} else {
498		if rel := inDirLex(xpath, dir); rel != "" {
499			return rel
500		}
501	}
502
503	xdir, err := filepath.EvalSymlinks(dir)
504	if err == nil && xdir != dir {
505		if rel := inDirLex(path, xdir); rel != "" {
506			return rel
507		}
508		if xpath != "" {
509			if rel := inDirLex(xpath, xdir); rel != "" {
510				return rel
511			}
512		}
513	}
514	return ""
515}
516
517// inDirLex is like inDir but only checks the lexical form of the file names.
518// It does not consider symbolic links.
519// TODO(rsc): This is a copy of str.HasFilePathPrefix, modified to
520// return the suffix. Most uses of str.HasFilePathPrefix should probably
521// be calling InDir instead.
522func inDirLex(path, dir string) string {
523	pv := strings.ToUpper(filepath.VolumeName(path))
524	dv := strings.ToUpper(filepath.VolumeName(dir))
525	path = path[len(pv):]
526	dir = dir[len(dv):]
527	switch {
528	default:
529		return ""
530	case pv != dv:
531		return ""
532	case len(path) == len(dir):
533		if path == dir {
534			return "."
535		}
536		return ""
537	case dir == "":
538		return path
539	case len(path) > len(dir):
540		if dir[len(dir)-1] == filepath.Separator {
541			if path[:len(dir)] == dir {
542				return path[len(dir):]
543			}
544			return ""
545		}
546		if path[len(dir)] == filepath.Separator && path[:len(dir)] == dir {
547			if len(path) == len(dir)+1 {
548				return "."
549			}
550			return path[len(dir)+1:]
551		}
552		return ""
553	}
554}
555