1// Copyright 2016 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 noder
6
7import (
8	"errors"
9	"fmt"
10	"internal/buildcfg"
11	"os"
12	"path/filepath"
13	"runtime"
14	"strconv"
15	"strings"
16	"unicode"
17	"unicode/utf8"
18
19	"cmd/compile/internal/base"
20	"cmd/compile/internal/ir"
21	"cmd/compile/internal/syntax"
22	"cmd/compile/internal/typecheck"
23	"cmd/compile/internal/types"
24	"cmd/internal/objabi"
25)
26
27func LoadPackage(filenames []string) {
28	base.Timer.Start("fe", "parse")
29
30	// Limit the number of simultaneously open files.
31	sem := make(chan struct{}, runtime.GOMAXPROCS(0)+10)
32
33	noders := make([]*noder, len(filenames))
34	for i := range noders {
35		p := noder{
36			err: make(chan syntax.Error),
37		}
38		noders[i] = &p
39	}
40
41	// Move the entire syntax processing logic into a separate goroutine to avoid blocking on the "sem".
42	go func() {
43		for i, filename := range filenames {
44			filename := filename
45			p := noders[i]
46			sem <- struct{}{}
47			go func() {
48				defer func() { <-sem }()
49				defer close(p.err)
50				fbase := syntax.NewFileBase(filename)
51
52				f, err := os.Open(filename)
53				if err != nil {
54					p.error(syntax.Error{Msg: err.Error()})
55					return
56				}
57				defer f.Close()
58
59				p.file, _ = syntax.Parse(fbase, f, p.error, p.pragma, syntax.CheckBranches) // errors are tracked via p.error
60			}()
61		}
62	}()
63
64	var lines uint
65	var m posMap
66	for _, p := range noders {
67		for e := range p.err {
68			base.ErrorfAt(m.makeXPos(e.Pos), 0, "%s", e.Msg)
69		}
70		if p.file == nil {
71			base.ErrorExit()
72		}
73		lines += p.file.EOF.Line()
74	}
75	base.Timer.AddEvent(int64(lines), "lines")
76
77	unified(m, noders)
78}
79
80// trimFilename returns the "trimmed" filename of b, which is the
81// absolute filename after applying -trimpath processing. This
82// filename form is suitable for use in object files and export data.
83//
84// If b's filename has already been trimmed (i.e., because it was read
85// in from an imported package's export data), then the filename is
86// returned unchanged.
87func trimFilename(b *syntax.PosBase) string {
88	filename := b.Filename()
89	if !b.Trimmed() {
90		dir := ""
91		if b.IsFileBase() {
92			dir = base.Ctxt.Pathname
93		}
94		filename = objabi.AbsFile(dir, filename, base.Flag.TrimPath)
95	}
96	return filename
97}
98
99// noder transforms package syntax's AST into a Node tree.
100type noder struct {
101	file       *syntax.File
102	linknames  []linkname
103	pragcgobuf [][]string
104	err        chan syntax.Error
105}
106
107// linkname records a //go:linkname directive.
108type linkname struct {
109	pos    syntax.Pos
110	local  string
111	remote string
112}
113
114var unOps = [...]ir.Op{
115	syntax.Recv: ir.ORECV,
116	syntax.Mul:  ir.ODEREF,
117	syntax.And:  ir.OADDR,
118
119	syntax.Not: ir.ONOT,
120	syntax.Xor: ir.OBITNOT,
121	syntax.Add: ir.OPLUS,
122	syntax.Sub: ir.ONEG,
123}
124
125var binOps = [...]ir.Op{
126	syntax.OrOr:   ir.OOROR,
127	syntax.AndAnd: ir.OANDAND,
128
129	syntax.Eql: ir.OEQ,
130	syntax.Neq: ir.ONE,
131	syntax.Lss: ir.OLT,
132	syntax.Leq: ir.OLE,
133	syntax.Gtr: ir.OGT,
134	syntax.Geq: ir.OGE,
135
136	syntax.Add: ir.OADD,
137	syntax.Sub: ir.OSUB,
138	syntax.Or:  ir.OOR,
139	syntax.Xor: ir.OXOR,
140
141	syntax.Mul:    ir.OMUL,
142	syntax.Div:    ir.ODIV,
143	syntax.Rem:    ir.OMOD,
144	syntax.And:    ir.OAND,
145	syntax.AndNot: ir.OANDNOT,
146	syntax.Shl:    ir.OLSH,
147	syntax.Shr:    ir.ORSH,
148}
149
150// error is called concurrently if files are parsed concurrently.
151func (p *noder) error(err error) {
152	p.err <- err.(syntax.Error)
153}
154
155// pragmas that are allowed in the std lib, but don't have
156// a syntax.Pragma value (see lex.go) associated with them.
157var allowedStdPragmas = map[string]bool{
158	"go:cgo_export_static":  true,
159	"go:cgo_export_dynamic": true,
160	"go:cgo_import_static":  true,
161	"go:cgo_import_dynamic": true,
162	"go:cgo_ldflag":         true,
163	"go:cgo_dynamic_linker": true,
164	"go:embed":              true,
165	"go:generate":           true,
166}
167
168// *pragmas is the value stored in a syntax.pragmas during parsing.
169type pragmas struct {
170	Flag       ir.PragmaFlag // collected bits
171	Pos        []pragmaPos   // position of each individual flag
172	Embeds     []pragmaEmbed
173	WasmImport *WasmImport
174}
175
176// WasmImport stores metadata associated with the //go:wasmimport pragma
177type WasmImport struct {
178	Pos    syntax.Pos
179	Module string
180	Name   string
181}
182
183type pragmaPos struct {
184	Flag ir.PragmaFlag
185	Pos  syntax.Pos
186}
187
188type pragmaEmbed struct {
189	Pos      syntax.Pos
190	Patterns []string
191}
192
193func (p *noder) checkUnusedDuringParse(pragma *pragmas) {
194	for _, pos := range pragma.Pos {
195		if pos.Flag&pragma.Flag != 0 {
196			p.error(syntax.Error{Pos: pos.Pos, Msg: "misplaced compiler directive"})
197		}
198	}
199	if len(pragma.Embeds) > 0 {
200		for _, e := range pragma.Embeds {
201			p.error(syntax.Error{Pos: e.Pos, Msg: "misplaced go:embed directive"})
202		}
203	}
204	if pragma.WasmImport != nil {
205		p.error(syntax.Error{Pos: pragma.WasmImport.Pos, Msg: "misplaced go:wasmimport directive"})
206	}
207}
208
209// pragma is called concurrently if files are parsed concurrently.
210func (p *noder) pragma(pos syntax.Pos, blankLine bool, text string, old syntax.Pragma) syntax.Pragma {
211	pragma, _ := old.(*pragmas)
212	if pragma == nil {
213		pragma = new(pragmas)
214	}
215
216	if text == "" {
217		// unused pragma; only called with old != nil.
218		p.checkUnusedDuringParse(pragma)
219		return nil
220	}
221
222	if strings.HasPrefix(text, "line ") {
223		// line directives are handled by syntax package
224		panic("unreachable")
225	}
226
227	if !blankLine {
228		// directive must be on line by itself
229		p.error(syntax.Error{Pos: pos, Msg: "misplaced compiler directive"})
230		return pragma
231	}
232
233	switch {
234	case strings.HasPrefix(text, "go:wasmimport "):
235		f := strings.Fields(text)
236		if len(f) != 3 {
237			p.error(syntax.Error{Pos: pos, Msg: "usage: //go:wasmimport importmodule importname"})
238			break
239		}
240
241		if buildcfg.GOARCH == "wasm" {
242			// Only actually use them if we're compiling to WASM though.
243			pragma.WasmImport = &WasmImport{
244				Pos:    pos,
245				Module: f[1],
246				Name:   f[2],
247			}
248		}
249	case strings.HasPrefix(text, "go:linkname "):
250		f := strings.Fields(text)
251		if !(2 <= len(f) && len(f) <= 3) {
252			p.error(syntax.Error{Pos: pos, Msg: "usage: //go:linkname localname [linkname]"})
253			break
254		}
255		// The second argument is optional. If omitted, we use
256		// the default object symbol name for this and
257		// linkname only serves to mark this symbol as
258		// something that may be referenced via the object
259		// symbol name from another package.
260		var target string
261		if len(f) == 3 {
262			target = f[2]
263		} else if base.Ctxt.Pkgpath != "" {
264			// Use the default object symbol name if the
265			// user didn't provide one.
266			target = objabi.PathToPrefix(base.Ctxt.Pkgpath) + "." + f[1]
267		} else {
268			panic("missing pkgpath")
269		}
270		p.linknames = append(p.linknames, linkname{pos, f[1], target})
271
272	case text == "go:embed", strings.HasPrefix(text, "go:embed "):
273		args, err := parseGoEmbed(text[len("go:embed"):])
274		if err != nil {
275			p.error(syntax.Error{Pos: pos, Msg: err.Error()})
276		}
277		if len(args) == 0 {
278			p.error(syntax.Error{Pos: pos, Msg: "usage: //go:embed pattern..."})
279			break
280		}
281		pragma.Embeds = append(pragma.Embeds, pragmaEmbed{pos, args})
282
283	case strings.HasPrefix(text, "go:cgo_import_dynamic "):
284		// This is permitted for general use because Solaris
285		// code relies on it in golang.org/x/sys/unix and others.
286		fields := pragmaFields(text)
287		if len(fields) >= 4 {
288			lib := strings.Trim(fields[3], `"`)
289			if lib != "" && !safeArg(lib) && !isCgoGeneratedFile(pos) {
290				p.error(syntax.Error{Pos: pos, Msg: fmt.Sprintf("invalid library name %q in cgo_import_dynamic directive", lib)})
291			}
292			p.pragcgo(pos, text)
293			pragma.Flag |= pragmaFlag("go:cgo_import_dynamic")
294			break
295		}
296		fallthrough
297	case strings.HasPrefix(text, "go:cgo_"):
298		// For security, we disallow //go:cgo_* directives other
299		// than cgo_import_dynamic outside cgo-generated files.
300		// Exception: they are allowed in the standard library, for runtime and syscall.
301		if !isCgoGeneratedFile(pos) && !base.Flag.Std {
302			p.error(syntax.Error{Pos: pos, Msg: fmt.Sprintf("//%s only allowed in cgo-generated code", text)})
303		}
304		p.pragcgo(pos, text)
305		fallthrough // because of //go:cgo_unsafe_args
306	default:
307		verb := text
308		if i := strings.Index(text, " "); i >= 0 {
309			verb = verb[:i]
310		}
311		flag := pragmaFlag(verb)
312		const runtimePragmas = ir.Systemstack | ir.Nowritebarrier | ir.Nowritebarrierrec | ir.Yeswritebarrierrec
313		if !base.Flag.CompilingRuntime && flag&runtimePragmas != 0 {
314			p.error(syntax.Error{Pos: pos, Msg: fmt.Sprintf("//%s only allowed in runtime", verb)})
315		}
316		if flag == ir.UintptrKeepAlive && !base.Flag.Std {
317			p.error(syntax.Error{Pos: pos, Msg: fmt.Sprintf("//%s is only allowed in the standard library", verb)})
318		}
319		if flag == 0 && !allowedStdPragmas[verb] && base.Flag.Std {
320			p.error(syntax.Error{Pos: pos, Msg: fmt.Sprintf("//%s is not allowed in the standard library", verb)})
321		}
322		pragma.Flag |= flag
323		pragma.Pos = append(pragma.Pos, pragmaPos{flag, pos})
324	}
325
326	return pragma
327}
328
329// isCgoGeneratedFile reports whether pos is in a file
330// generated by cgo, which is to say a file with name
331// beginning with "_cgo_". Such files are allowed to
332// contain cgo directives, and for security reasons
333// (primarily misuse of linker flags), other files are not.
334// See golang.org/issue/23672.
335// Note that cmd/go ignores files whose names start with underscore,
336// so the only _cgo_ files we will see from cmd/go are generated by cgo.
337// It's easy to bypass this check by calling the compiler directly;
338// we only protect against uses by cmd/go.
339func isCgoGeneratedFile(pos syntax.Pos) bool {
340	// We need the absolute file, independent of //line directives,
341	// so we call pos.Base().Pos().
342	return strings.HasPrefix(filepath.Base(trimFilename(pos.Base().Pos().Base())), "_cgo_")
343}
344
345// safeArg reports whether arg is a "safe" command-line argument,
346// meaning that when it appears in a command-line, it probably
347// doesn't have some special meaning other than its own name.
348// This is copied from SafeArg in cmd/go/internal/load/pkg.go.
349func safeArg(name string) bool {
350	if name == "" {
351		return false
352	}
353	c := name[0]
354	return '0' <= c && c <= '9' || 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || c == '.' || c == '_' || c == '/' || c >= utf8.RuneSelf
355}
356
357// parseGoEmbed parses the text following "//go:embed" to extract the glob patterns.
358// It accepts unquoted space-separated patterns as well as double-quoted and back-quoted Go strings.
359// go/build/read.go also processes these strings and contains similar logic.
360func parseGoEmbed(args string) ([]string, error) {
361	var list []string
362	for args = strings.TrimSpace(args); args != ""; args = strings.TrimSpace(args) {
363		var path string
364	Switch:
365		switch args[0] {
366		default:
367			i := len(args)
368			for j, c := range args {
369				if unicode.IsSpace(c) {
370					i = j
371					break
372				}
373			}
374			path = args[:i]
375			args = args[i:]
376
377		case '`':
378			i := strings.Index(args[1:], "`")
379			if i < 0 {
380				return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args)
381			}
382			path = args[1 : 1+i]
383			args = args[1+i+1:]
384
385		case '"':
386			i := 1
387			for ; i < len(args); i++ {
388				if args[i] == '\\' {
389					i++
390					continue
391				}
392				if args[i] == '"' {
393					q, err := strconv.Unquote(args[:i+1])
394					if err != nil {
395						return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args[:i+1])
396					}
397					path = q
398					args = args[i+1:]
399					break Switch
400				}
401			}
402			if i >= len(args) {
403				return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args)
404			}
405		}
406
407		if args != "" {
408			r, _ := utf8.DecodeRuneInString(args)
409			if !unicode.IsSpace(r) {
410				return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args)
411			}
412		}
413		list = append(list, path)
414	}
415	return list, nil
416}
417
418// A function named init is a special case.
419// It is called by the initialization before main is run.
420// To make it unique within a package and also uncallable,
421// the name, normally "pkg.init", is altered to "pkg.init.0".
422var renameinitgen int
423
424func Renameinit() *types.Sym {
425	s := typecheck.LookupNum("init.", renameinitgen)
426	renameinitgen++
427	return s
428}
429
430func checkEmbed(decl *syntax.VarDecl, haveEmbed, withinFunc bool) error {
431	switch {
432	case !haveEmbed:
433		return errors.New("go:embed only allowed in Go files that import \"embed\"")
434	case len(decl.NameList) > 1:
435		return errors.New("go:embed cannot apply to multiple vars")
436	case decl.Values != nil:
437		return errors.New("go:embed cannot apply to var with initializer")
438	case decl.Type == nil:
439		// Should not happen, since Values == nil now.
440		return errors.New("go:embed cannot apply to var without type")
441	case withinFunc:
442		return errors.New("go:embed cannot apply to var inside func")
443	case !types.AllowsGoVersion(1, 16):
444		return fmt.Errorf("go:embed requires go1.16 or later (-lang was set to %s; check go.mod)", base.Flag.Lang)
445
446	default:
447		return nil
448	}
449}
450