1// Copyright 2011 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
5// This file is a lightly modified copy go/build/build.go with unused parts
6// removed.
7
8package modindex
9
10import (
11	"bytes"
12	"cmd/go/internal/fsys"
13	"cmd/go/internal/str"
14	"errors"
15	"fmt"
16	"go/ast"
17	"go/build"
18	"go/build/constraint"
19	"go/token"
20	"io"
21	"io/fs"
22	"path/filepath"
23	"sort"
24	"strings"
25	"unicode"
26	"unicode/utf8"
27)
28
29// A Context specifies the supporting context for a build.
30type Context struct {
31	GOARCH string // target architecture
32	GOOS   string // target operating system
33	GOROOT string // Go root
34	GOPATH string // Go paths
35
36	// Dir is the caller's working directory, or the empty string to use
37	// the current directory of the running process. In module mode, this is used
38	// to locate the main module.
39	//
40	// If Dir is non-empty, directories passed to Import and ImportDir must
41	// be absolute.
42	Dir string
43
44	CgoEnabled  bool   // whether cgo files are included
45	UseAllFiles bool   // use files regardless of //go:build lines, file names
46	Compiler    string // compiler to assume when computing target paths
47
48	// The build, tool, and release tags specify build constraints
49	// that should be considered satisfied when processing +build lines.
50	// Clients creating a new context may customize BuildTags, which
51	// defaults to empty, but it is usually an error to customize ToolTags or ReleaseTags.
52	// ToolTags defaults to build tags appropriate to the current Go toolchain configuration.
53	// ReleaseTags defaults to the list of Go releases the current release is compatible with.
54	// BuildTags is not set for the Default build Context.
55	// In addition to the BuildTags, ToolTags, and ReleaseTags, build constraints
56	// consider the values of GOARCH and GOOS as satisfied tags.
57	// The last element in ReleaseTags is assumed to be the current release.
58	BuildTags   []string
59	ToolTags    []string
60	ReleaseTags []string
61
62	// The install suffix specifies a suffix to use in the name of the installation
63	// directory. By default it is empty, but custom builds that need to keep
64	// their outputs separate can set InstallSuffix to do so. For example, when
65	// using the race detector, the go command uses InstallSuffix = "race", so
66	// that on a Linux/386 system, packages are written to a directory named
67	// "linux_386_race" instead of the usual "linux_386".
68	InstallSuffix string
69
70	// By default, Import uses the operating system's file system calls
71	// to read directories and files. To read from other sources,
72	// callers can set the following functions. They all have default
73	// behaviors that use the local file system, so clients need only set
74	// the functions whose behaviors they wish to change.
75
76	// JoinPath joins the sequence of path fragments into a single path.
77	// If JoinPath is nil, Import uses filepath.Join.
78	JoinPath func(elem ...string) string
79
80	// SplitPathList splits the path list into a slice of individual paths.
81	// If SplitPathList is nil, Import uses filepath.SplitList.
82	SplitPathList func(list string) []string
83
84	// IsAbsPath reports whether path is an absolute path.
85	// If IsAbsPath is nil, Import uses filepath.IsAbs.
86	IsAbsPath func(path string) bool
87
88	// IsDir reports whether the path names a directory.
89	// If IsDir is nil, Import calls os.Stat and uses the result's IsDir method.
90	IsDir func(path string) bool
91
92	// HasSubdir reports whether dir is lexically a subdirectory of
93	// root, perhaps multiple levels below. It does not try to check
94	// whether dir exists.
95	// If so, HasSubdir sets rel to a slash-separated path that
96	// can be joined to root to produce a path equivalent to dir.
97	// If HasSubdir is nil, Import uses an implementation built on
98	// filepath.EvalSymlinks.
99	HasSubdir func(root, dir string) (rel string, ok bool)
100
101	// ReadDir returns a slice of fs.FileInfo, sorted by Name,
102	// describing the content of the named directory.
103	// If ReadDir is nil, Import uses ioutil.ReadDir.
104	ReadDir func(dir string) ([]fs.FileInfo, error)
105
106	// OpenFile opens a file (not a directory) for reading.
107	// If OpenFile is nil, Import uses os.Open.
108	OpenFile func(path string) (io.ReadCloser, error)
109}
110
111// joinPath calls ctxt.JoinPath (if not nil) or else filepath.Join.
112func (ctxt *Context) joinPath(elem ...string) string {
113	if f := ctxt.JoinPath; f != nil {
114		return f(elem...)
115	}
116	return filepath.Join(elem...)
117}
118
119// splitPathList calls ctxt.SplitPathList (if not nil) or else filepath.SplitList.
120func (ctxt *Context) splitPathList(s string) []string {
121	if f := ctxt.SplitPathList; f != nil {
122		return f(s)
123	}
124	return filepath.SplitList(s)
125}
126
127// isAbsPath calls ctxt.IsAbsPath (if not nil) or else filepath.IsAbs.
128func (ctxt *Context) isAbsPath(path string) bool {
129	if f := ctxt.IsAbsPath; f != nil {
130		return f(path)
131	}
132	return filepath.IsAbs(path)
133}
134
135// isDir calls ctxt.IsDir (if not nil) or else uses fsys.Stat.
136func isDir(path string) bool {
137	fi, err := fsys.Stat(path)
138	return err == nil && fi.IsDir()
139}
140
141// hasSubdir calls ctxt.HasSubdir (if not nil) or else uses
142// the local file system to answer the question.
143func (ctxt *Context) hasSubdir(root, dir string) (rel string, ok bool) {
144	if f := ctxt.HasSubdir; f != nil {
145		return f(root, dir)
146	}
147
148	// Try using paths we received.
149	if rel, ok = hasSubdir(root, dir); ok {
150		return
151	}
152
153	// Try expanding symlinks and comparing
154	// expanded against unexpanded and
155	// expanded against expanded.
156	rootSym, _ := filepath.EvalSymlinks(root)
157	dirSym, _ := filepath.EvalSymlinks(dir)
158
159	if rel, ok = hasSubdir(rootSym, dir); ok {
160		return
161	}
162	if rel, ok = hasSubdir(root, dirSym); ok {
163		return
164	}
165	return hasSubdir(rootSym, dirSym)
166}
167
168// hasSubdir reports if dir is within root by performing lexical analysis only.
169func hasSubdir(root, dir string) (rel string, ok bool) {
170	root = str.WithFilePathSeparator(filepath.Clean(root))
171	dir = filepath.Clean(dir)
172	if !strings.HasPrefix(dir, root) {
173		return "", false
174	}
175	return filepath.ToSlash(dir[len(root):]), true
176}
177
178// gopath returns the list of Go path directories.
179func (ctxt *Context) gopath() []string {
180	var all []string
181	for _, p := range ctxt.splitPathList(ctxt.GOPATH) {
182		if p == "" || p == ctxt.GOROOT {
183			// Empty paths are uninteresting.
184			// If the path is the GOROOT, ignore it.
185			// People sometimes set GOPATH=$GOROOT.
186			// Do not get confused by this common mistake.
187			continue
188		}
189		if strings.HasPrefix(p, "~") {
190			// Path segments starting with ~ on Unix are almost always
191			// users who have incorrectly quoted ~ while setting GOPATH,
192			// preventing it from expanding to $HOME.
193			// The situation is made more confusing by the fact that
194			// bash allows quoted ~ in $PATH (most shells do not).
195			// Do not get confused by this, and do not try to use the path.
196			// It does not exist, and printing errors about it confuses
197			// those users even more, because they think "sure ~ exists!".
198			// The go command diagnoses this situation and prints a
199			// useful error.
200			// On Windows, ~ is used in short names, such as c:\progra~1
201			// for c:\program files.
202			continue
203		}
204		all = append(all, p)
205	}
206	return all
207}
208
209var defaultToolTags, defaultReleaseTags []string
210
211// NoGoError is the error used by Import to describe a directory
212// containing no buildable Go source files. (It may still contain
213// test files, files hidden by build tags, and so on.)
214type NoGoError struct {
215	Dir string
216}
217
218func (e *NoGoError) Error() string {
219	return "no buildable Go source files in " + e.Dir
220}
221
222// MultiplePackageError describes a directory containing
223// multiple buildable Go source files for multiple packages.
224type MultiplePackageError struct {
225	Dir      string   // directory containing files
226	Packages []string // package names found
227	Files    []string // corresponding files: Files[i] declares package Packages[i]
228}
229
230func (e *MultiplePackageError) Error() string {
231	// Error string limited to two entries for compatibility.
232	return fmt.Sprintf("found packages %s (%s) and %s (%s) in %s", e.Packages[0], e.Files[0], e.Packages[1], e.Files[1], e.Dir)
233}
234
235func nameExt(name string) string {
236	i := strings.LastIndex(name, ".")
237	if i < 0 {
238		return ""
239	}
240	return name[i:]
241}
242
243func fileListForExt(p *build.Package, ext string) *[]string {
244	switch ext {
245	case ".c":
246		return &p.CFiles
247	case ".cc", ".cpp", ".cxx":
248		return &p.CXXFiles
249	case ".m":
250		return &p.MFiles
251	case ".h", ".hh", ".hpp", ".hxx":
252		return &p.HFiles
253	case ".f", ".F", ".for", ".f90":
254		return &p.FFiles
255	case ".s", ".S", ".sx":
256		return &p.SFiles
257	case ".swig":
258		return &p.SwigFiles
259	case ".swigcxx":
260		return &p.SwigCXXFiles
261	case ".syso":
262		return &p.SysoFiles
263	}
264	return nil
265}
266
267var errNoModules = errors.New("not using modules")
268
269func findImportComment(data []byte) (s string, line int) {
270	// expect keyword package
271	word, data := parseWord(data)
272	if string(word) != "package" {
273		return "", 0
274	}
275
276	// expect package name
277	_, data = parseWord(data)
278
279	// now ready for import comment, a // or /* */ comment
280	// beginning and ending on the current line.
281	for len(data) > 0 && (data[0] == ' ' || data[0] == '\t' || data[0] == '\r') {
282		data = data[1:]
283	}
284
285	var comment []byte
286	switch {
287	case bytes.HasPrefix(data, slashSlash):
288		comment, _, _ = bytes.Cut(data[2:], newline)
289	case bytes.HasPrefix(data, slashStar):
290		var ok bool
291		comment, _, ok = bytes.Cut(data[2:], starSlash)
292		if !ok {
293			// malformed comment
294			return "", 0
295		}
296		if bytes.Contains(comment, newline) {
297			return "", 0
298		}
299	}
300	comment = bytes.TrimSpace(comment)
301
302	// split comment into `import`, `"pkg"`
303	word, arg := parseWord(comment)
304	if string(word) != "import" {
305		return "", 0
306	}
307
308	line = 1 + bytes.Count(data[:cap(data)-cap(arg)], newline)
309	return strings.TrimSpace(string(arg)), line
310}
311
312var (
313	slashSlash = []byte("//")
314	slashStar  = []byte("/*")
315	starSlash  = []byte("*/")
316	newline    = []byte("\n")
317)
318
319// skipSpaceOrComment returns data with any leading spaces or comments removed.
320func skipSpaceOrComment(data []byte) []byte {
321	for len(data) > 0 {
322		switch data[0] {
323		case ' ', '\t', '\r', '\n':
324			data = data[1:]
325			continue
326		case '/':
327			if bytes.HasPrefix(data, slashSlash) {
328				i := bytes.Index(data, newline)
329				if i < 0 {
330					return nil
331				}
332				data = data[i+1:]
333				continue
334			}
335			if bytes.HasPrefix(data, slashStar) {
336				data = data[2:]
337				i := bytes.Index(data, starSlash)
338				if i < 0 {
339					return nil
340				}
341				data = data[i+2:]
342				continue
343			}
344		}
345		break
346	}
347	return data
348}
349
350// parseWord skips any leading spaces or comments in data
351// and then parses the beginning of data as an identifier or keyword,
352// returning that word and what remains after the word.
353func parseWord(data []byte) (word, rest []byte) {
354	data = skipSpaceOrComment(data)
355
356	// Parse past leading word characters.
357	rest = data
358	for {
359		r, size := utf8.DecodeRune(rest)
360		if unicode.IsLetter(r) || '0' <= r && r <= '9' || r == '_' {
361			rest = rest[size:]
362			continue
363		}
364		break
365	}
366
367	word = data[:len(data)-len(rest)]
368	if len(word) == 0 {
369		return nil, nil
370	}
371
372	return word, rest
373}
374
375var dummyPkg build.Package
376
377// fileInfo records information learned about a file included in a build.
378type fileInfo struct {
379	name       string // full name including dir
380	header     []byte
381	fset       *token.FileSet
382	parsed     *ast.File
383	parseErr   error
384	imports    []fileImport
385	embeds     []fileEmbed
386	directives []build.Directive
387
388	// Additional fields added to go/build's fileinfo for the purposes of the modindex package.
389	binaryOnly           bool
390	goBuildConstraint    string
391	plusBuildConstraints []string
392}
393
394type fileImport struct {
395	path string
396	pos  token.Pos
397	doc  *ast.CommentGroup
398}
399
400type fileEmbed struct {
401	pattern string
402	pos     token.Position
403}
404
405var errNonSource = errors.New("non source file")
406
407// getFileInfo extracts the information needed from each go file for the module
408// index.
409//
410// If Name denotes a Go program, matchFile reads until the end of the
411// Imports and returns that section of the file in the FileInfo's Header field,
412// even though it only considers text until the first non-comment
413// for +build lines.
414//
415// getFileInfo will return errNonSource if the file is not a source or object
416// file and shouldn't even be added to IgnoredFiles.
417func getFileInfo(dir, name string, fset *token.FileSet) (*fileInfo, error) {
418	if strings.HasPrefix(name, "_") ||
419		strings.HasPrefix(name, ".") {
420		return nil, nil
421	}
422
423	i := strings.LastIndex(name, ".")
424	if i < 0 {
425		i = len(name)
426	}
427	ext := name[i:]
428
429	if ext != ".go" && fileListForExt(&dummyPkg, ext) == nil {
430		// skip
431		return nil, errNonSource
432	}
433
434	info := &fileInfo{name: filepath.Join(dir, name), fset: fset}
435	if ext == ".syso" {
436		// binary, no reading
437		return info, nil
438	}
439
440	f, err := fsys.Open(info.name)
441	if err != nil {
442		return nil, err
443	}
444
445	// TODO(matloob) should we decide whether to ignore binary only here or earlier
446	// when we create the index file?
447	var ignoreBinaryOnly bool
448	if strings.HasSuffix(name, ".go") {
449		err = readGoInfo(f, info)
450		if strings.HasSuffix(name, "_test.go") {
451			ignoreBinaryOnly = true // ignore //go:binary-only-package comments in _test.go files
452		}
453	} else {
454		info.header, err = readComments(f)
455	}
456	f.Close()
457	if err != nil {
458		return nil, fmt.Errorf("read %s: %v", info.name, err)
459	}
460
461	// Look for +build comments to accept or reject the file.
462	info.goBuildConstraint, info.plusBuildConstraints, info.binaryOnly, err = getConstraints(info.header)
463	if err != nil {
464		return nil, fmt.Errorf("%s: %v", name, err)
465	}
466
467	if ignoreBinaryOnly && info.binaryOnly {
468		info.binaryOnly = false // override info.binaryOnly
469	}
470
471	return info, nil
472}
473
474func cleanDecls(m map[string][]token.Position) ([]string, map[string][]token.Position) {
475	all := make([]string, 0, len(m))
476	for path := range m {
477		all = append(all, path)
478	}
479	sort.Strings(all)
480	return all, m
481}
482
483var (
484	bSlashSlash = []byte(slashSlash)
485	bStarSlash  = []byte(starSlash)
486	bSlashStar  = []byte(slashStar)
487	bPlusBuild  = []byte("+build")
488
489	goBuildComment = []byte("//go:build")
490
491	errMultipleGoBuild = errors.New("multiple //go:build comments")
492)
493
494func isGoBuildComment(line []byte) bool {
495	if !bytes.HasPrefix(line, goBuildComment) {
496		return false
497	}
498	line = bytes.TrimSpace(line)
499	rest := line[len(goBuildComment):]
500	return len(rest) == 0 || len(bytes.TrimSpace(rest)) < len(rest)
501}
502
503// Special comment denoting a binary-only package.
504// See https://golang.org/design/2775-binary-only-packages
505// for more about the design of binary-only packages.
506var binaryOnlyComment = []byte("//go:binary-only-package")
507
508func getConstraints(content []byte) (goBuild string, plusBuild []string, binaryOnly bool, err error) {
509	// Identify leading run of // comments and blank lines,
510	// which must be followed by a blank line.
511	// Also identify any //go:build comments.
512	content, goBuildBytes, sawBinaryOnly, err := parseFileHeader(content)
513	if err != nil {
514		return "", nil, false, err
515	}
516
517	// If //go:build line is present, it controls, so no need to look for +build .
518	// Otherwise, get plusBuild constraints.
519	if goBuildBytes == nil {
520		p := content
521		for len(p) > 0 {
522			line := p
523			if i := bytes.IndexByte(line, '\n'); i >= 0 {
524				line, p = line[:i], p[i+1:]
525			} else {
526				p = p[len(p):]
527			}
528			line = bytes.TrimSpace(line)
529			if !bytes.HasPrefix(line, bSlashSlash) || !bytes.Contains(line, bPlusBuild) {
530				continue
531			}
532			text := string(line)
533			if !constraint.IsPlusBuild(text) {
534				continue
535			}
536			plusBuild = append(plusBuild, text)
537		}
538	}
539
540	return string(goBuildBytes), plusBuild, sawBinaryOnly, nil
541}
542
543func parseFileHeader(content []byte) (trimmed, goBuild []byte, sawBinaryOnly bool, err error) {
544	end := 0
545	p := content
546	ended := false       // found non-blank, non-// line, so stopped accepting // +build lines
547	inSlashStar := false // in /* */ comment
548
549Lines:
550	for len(p) > 0 {
551		line := p
552		if i := bytes.IndexByte(line, '\n'); i >= 0 {
553			line, p = line[:i], p[i+1:]
554		} else {
555			p = p[len(p):]
556		}
557		line = bytes.TrimSpace(line)
558		if len(line) == 0 && !ended { // Blank line
559			// Remember position of most recent blank line.
560			// When we find the first non-blank, non-// line,
561			// this "end" position marks the latest file position
562			// where a // +build line can appear.
563			// (It must appear _before_ a blank line before the non-blank, non-// line.
564			// Yes, that's confusing, which is part of why we moved to //go:build lines.)
565			// Note that ended==false here means that inSlashStar==false,
566			// since seeing a /* would have set ended==true.
567			end = len(content) - len(p)
568			continue Lines
569		}
570		if !bytes.HasPrefix(line, slashSlash) { // Not comment line
571			ended = true
572		}
573
574		if !inSlashStar && isGoBuildComment(line) {
575			if goBuild != nil {
576				return nil, nil, false, errMultipleGoBuild
577			}
578			goBuild = line
579		}
580		if !inSlashStar && bytes.Equal(line, binaryOnlyComment) {
581			sawBinaryOnly = true
582		}
583
584	Comments:
585		for len(line) > 0 {
586			if inSlashStar {
587				if i := bytes.Index(line, starSlash); i >= 0 {
588					inSlashStar = false
589					line = bytes.TrimSpace(line[i+len(starSlash):])
590					continue Comments
591				}
592				continue Lines
593			}
594			if bytes.HasPrefix(line, bSlashSlash) {
595				continue Lines
596			}
597			if bytes.HasPrefix(line, bSlashStar) {
598				inSlashStar = true
599				line = bytes.TrimSpace(line[len(bSlashStar):])
600				continue Comments
601			}
602			// Found non-comment text.
603			break Lines
604		}
605	}
606
607	return content[:end], goBuild, sawBinaryOnly, nil
608}
609
610// saveCgo saves the information from the #cgo lines in the import "C" comment.
611// These lines set CFLAGS, CPPFLAGS, CXXFLAGS and LDFLAGS and pkg-config directives
612// that affect the way cgo's C code is built.
613func (ctxt *Context) saveCgo(filename string, di *build.Package, text string) error {
614	for _, line := range strings.Split(text, "\n") {
615		orig := line
616
617		// Line is
618		//	#cgo [GOOS/GOARCH...] LDFLAGS: stuff
619		//
620		line = strings.TrimSpace(line)
621		if len(line) < 5 || line[:4] != "#cgo" || (line[4] != ' ' && line[4] != '\t') {
622			continue
623		}
624
625		// #cgo (nocallback|noescape) <function name>
626		if fields := strings.Fields(line); len(fields) == 3 && (fields[1] == "nocallback" || fields[1] == "noescape") {
627			continue
628		}
629
630		// Split at colon.
631		line, argstr, ok := strings.Cut(strings.TrimSpace(line[4:]), ":")
632		if !ok {
633			return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig)
634		}
635
636		// Parse GOOS/GOARCH stuff.
637		f := strings.Fields(line)
638		if len(f) < 1 {
639			return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig)
640		}
641
642		cond, verb := f[:len(f)-1], f[len(f)-1]
643		if len(cond) > 0 {
644			ok := false
645			for _, c := range cond {
646				if ctxt.matchAuto(c, nil) {
647					ok = true
648					break
649				}
650			}
651			if !ok {
652				continue
653			}
654		}
655
656		args, err := splitQuoted(argstr)
657		if err != nil {
658			return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig)
659		}
660		for i, arg := range args {
661			if arg, ok = expandSrcDir(arg, di.Dir); !ok {
662				return fmt.Errorf("%s: malformed #cgo argument: %s", filename, arg)
663			}
664			args[i] = arg
665		}
666
667		switch verb {
668		case "CFLAGS", "CPPFLAGS", "CXXFLAGS", "FFLAGS", "LDFLAGS":
669			// Change relative paths to absolute.
670			ctxt.makePathsAbsolute(args, di.Dir)
671		}
672
673		switch verb {
674		case "CFLAGS":
675			di.CgoCFLAGS = append(di.CgoCFLAGS, args...)
676		case "CPPFLAGS":
677			di.CgoCPPFLAGS = append(di.CgoCPPFLAGS, args...)
678		case "CXXFLAGS":
679			di.CgoCXXFLAGS = append(di.CgoCXXFLAGS, args...)
680		case "FFLAGS":
681			di.CgoFFLAGS = append(di.CgoFFLAGS, args...)
682		case "LDFLAGS":
683			di.CgoLDFLAGS = append(di.CgoLDFLAGS, args...)
684		case "pkg-config":
685			di.CgoPkgConfig = append(di.CgoPkgConfig, args...)
686		default:
687			return fmt.Errorf("%s: invalid #cgo verb: %s", filename, orig)
688		}
689	}
690	return nil
691}
692
693// expandSrcDir expands any occurrence of ${SRCDIR}, making sure
694// the result is safe for the shell.
695func expandSrcDir(str string, srcdir string) (string, bool) {
696	// "\" delimited paths cause safeCgoName to fail
697	// so convert native paths with a different delimiter
698	// to "/" before starting (eg: on windows).
699	srcdir = filepath.ToSlash(srcdir)
700
701	chunks := strings.Split(str, "${SRCDIR}")
702	if len(chunks) < 2 {
703		return str, safeCgoName(str)
704	}
705	ok := true
706	for _, chunk := range chunks {
707		ok = ok && (chunk == "" || safeCgoName(chunk))
708	}
709	ok = ok && (srcdir == "" || safeCgoName(srcdir))
710	res := strings.Join(chunks, srcdir)
711	return res, ok && res != ""
712}
713
714// makePathsAbsolute looks for compiler options that take paths and
715// makes them absolute. We do this because through the 1.8 release we
716// ran the compiler in the package directory, so any relative -I or -L
717// options would be relative to that directory. In 1.9 we changed to
718// running the compiler in the build directory, to get consistent
719// build results (issue #19964). To keep builds working, we change any
720// relative -I or -L options to be absolute.
721//
722// Using filepath.IsAbs and filepath.Join here means the results will be
723// different on different systems, but that's OK: -I and -L options are
724// inherently system-dependent.
725func (ctxt *Context) makePathsAbsolute(args []string, srcDir string) {
726	nextPath := false
727	for i, arg := range args {
728		if nextPath {
729			if !filepath.IsAbs(arg) {
730				args[i] = filepath.Join(srcDir, arg)
731			}
732			nextPath = false
733		} else if strings.HasPrefix(arg, "-I") || strings.HasPrefix(arg, "-L") {
734			if len(arg) == 2 {
735				nextPath = true
736			} else {
737				if !filepath.IsAbs(arg[2:]) {
738					args[i] = arg[:2] + filepath.Join(srcDir, arg[2:])
739				}
740			}
741		}
742	}
743}
744
745// NOTE: $ is not safe for the shell, but it is allowed here because of linker options like -Wl,$ORIGIN.
746// We never pass these arguments to a shell (just to programs we construct argv for), so this should be okay.
747// See golang.org/issue/6038.
748// The @ is for OS X. See golang.org/issue/13720.
749// The % is for Jenkins. See golang.org/issue/16959.
750// The ! is because module paths may use them. See golang.org/issue/26716.
751// The ~ and ^ are for sr.ht. See golang.org/issue/32260.
752const safeString = "+-.,/0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz:$@%! ~^"
753
754func safeCgoName(s string) bool {
755	if s == "" {
756		return false
757	}
758	for i := 0; i < len(s); i++ {
759		if c := s[i]; c < utf8.RuneSelf && strings.IndexByte(safeString, c) < 0 {
760			return false
761		}
762	}
763	return true
764}
765
766// splitQuoted splits the string s around each instance of one or more consecutive
767// white space characters while taking into account quotes and escaping, and
768// returns an array of substrings of s or an empty list if s contains only white space.
769// Single quotes and double quotes are recognized to prevent splitting within the
770// quoted region, and are removed from the resulting substrings. If a quote in s
771// isn't closed err will be set and r will have the unclosed argument as the
772// last element. The backslash is used for escaping.
773//
774// For example, the following string:
775//
776//	a b:"c d" 'e''f'  "g\""
777//
778// Would be parsed as:
779//
780//	[]string{"a", "b:c d", "ef", `g"`}
781func splitQuoted(s string) (r []string, err error) {
782	var args []string
783	arg := make([]rune, len(s))
784	escaped := false
785	quoted := false
786	quote := '\x00'
787	i := 0
788	for _, rune := range s {
789		switch {
790		case escaped:
791			escaped = false
792		case rune == '\\':
793			escaped = true
794			continue
795		case quote != '\x00':
796			if rune == quote {
797				quote = '\x00'
798				continue
799			}
800		case rune == '"' || rune == '\'':
801			quoted = true
802			quote = rune
803			continue
804		case unicode.IsSpace(rune):
805			if quoted || i > 0 {
806				quoted = false
807				args = append(args, string(arg[:i]))
808				i = 0
809			}
810			continue
811		}
812		arg[i] = rune
813		i++
814	}
815	if quoted || i > 0 {
816		args = append(args, string(arg[:i]))
817	}
818	if quote != 0 {
819		err = errors.New("unclosed quote")
820	} else if escaped {
821		err = errors.New("unfinished escaping")
822	}
823	return args, err
824}
825
826// matchAuto interprets text as either a +build or //go:build expression (whichever works),
827// reporting whether the expression matches the build context.
828//
829// matchAuto is only used for testing of tag evaluation
830// and in #cgo lines, which accept either syntax.
831func (ctxt *Context) matchAuto(text string, allTags map[string]bool) bool {
832	if strings.ContainsAny(text, "&|()") {
833		text = "//go:build " + text
834	} else {
835		text = "// +build " + text
836	}
837	x, err := constraint.Parse(text)
838	if err != nil {
839		return false
840	}
841	return ctxt.eval(x, allTags)
842}
843
844func (ctxt *Context) eval(x constraint.Expr, allTags map[string]bool) bool {
845	return x.Eval(func(tag string) bool { return ctxt.matchTag(tag, allTags) })
846}
847
848// matchTag reports whether the name is one of:
849//
850//	cgo (if cgo is enabled)
851//	$GOOS
852//	$GOARCH
853//	boringcrypto
854//	ctxt.Compiler
855//	linux (if GOOS == android)
856//	solaris (if GOOS == illumos)
857//	tag (if tag is listed in ctxt.BuildTags or ctxt.ReleaseTags)
858//
859// It records all consulted tags in allTags.
860func (ctxt *Context) matchTag(name string, allTags map[string]bool) bool {
861	if allTags != nil {
862		allTags[name] = true
863	}
864
865	// special tags
866	if ctxt.CgoEnabled && name == "cgo" {
867		return true
868	}
869	if name == ctxt.GOOS || name == ctxt.GOARCH || name == ctxt.Compiler {
870		return true
871	}
872	if ctxt.GOOS == "android" && name == "linux" {
873		return true
874	}
875	if ctxt.GOOS == "illumos" && name == "solaris" {
876		return true
877	}
878	if ctxt.GOOS == "ios" && name == "darwin" {
879		return true
880	}
881	if name == "unix" && unixOS[ctxt.GOOS] {
882		return true
883	}
884	if name == "boringcrypto" {
885		name = "goexperiment.boringcrypto" // boringcrypto is an old name for goexperiment.boringcrypto
886	}
887
888	// other tags
889	for _, tag := range ctxt.BuildTags {
890		if tag == name {
891			return true
892		}
893	}
894	for _, tag := range ctxt.ToolTags {
895		if tag == name {
896			return true
897		}
898	}
899	for _, tag := range ctxt.ReleaseTags {
900		if tag == name {
901			return true
902		}
903	}
904
905	return false
906}
907
908// goodOSArchFile returns false if the name contains a $GOOS or $GOARCH
909// suffix which does not match the current system.
910// The recognized name formats are:
911//
912//	name_$(GOOS).*
913//	name_$(GOARCH).*
914//	name_$(GOOS)_$(GOARCH).*
915//	name_$(GOOS)_test.*
916//	name_$(GOARCH)_test.*
917//	name_$(GOOS)_$(GOARCH)_test.*
918//
919// Exceptions:
920// if GOOS=android, then files with GOOS=linux are also matched.
921// if GOOS=illumos, then files with GOOS=solaris are also matched.
922// if GOOS=ios, then files with GOOS=darwin are also matched.
923func (ctxt *Context) goodOSArchFile(name string, allTags map[string]bool) bool {
924	name, _, _ = strings.Cut(name, ".")
925
926	// Before Go 1.4, a file called "linux.go" would be equivalent to having a
927	// build tag "linux" in that file. For Go 1.4 and beyond, we require this
928	// auto-tagging to apply only to files with a non-empty prefix, so
929	// "foo_linux.go" is tagged but "linux.go" is not. This allows new operating
930	// systems, such as android, to arrive without breaking existing code with
931	// innocuous source code in "android.go". The easiest fix: cut everything
932	// in the name before the initial _.
933	i := strings.Index(name, "_")
934	if i < 0 {
935		return true
936	}
937	name = name[i:] // ignore everything before first _
938
939	l := strings.Split(name, "_")
940	if n := len(l); n > 0 && l[n-1] == "test" {
941		l = l[:n-1]
942	}
943	n := len(l)
944	if n >= 2 && knownOS[l[n-2]] && knownArch[l[n-1]] {
945		if allTags != nil {
946			// In case we short-circuit on l[n-1].
947			allTags[l[n-2]] = true
948		}
949		return ctxt.matchTag(l[n-1], allTags) && ctxt.matchTag(l[n-2], allTags)
950	}
951	if n >= 1 && (knownOS[l[n-1]] || knownArch[l[n-1]]) {
952		return ctxt.matchTag(l[n-1], allTags)
953	}
954	return true
955}
956