summaryrefslogtreecommitdiffstats
path: root/src/cmd/compile/internal/noder/noder.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/compile/internal/noder/noder.go')
-rw-r--r--src/cmd/compile/internal/noder/noder.go496
1 files changed, 496 insertions, 0 deletions
diff --git a/src/cmd/compile/internal/noder/noder.go b/src/cmd/compile/internal/noder/noder.go
new file mode 100644
index 0000000..c99c085
--- /dev/null
+++ b/src/cmd/compile/internal/noder/noder.go
@@ -0,0 +1,496 @@
+// Copyright 2016 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 noder
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "path/filepath"
+ "runtime"
+ "strconv"
+ "strings"
+ "unicode"
+ "unicode/utf8"
+
+ "cmd/compile/internal/base"
+ "cmd/compile/internal/ir"
+ "cmd/compile/internal/syntax"
+ "cmd/compile/internal/typecheck"
+ "cmd/compile/internal/types"
+ "cmd/internal/objabi"
+ "cmd/internal/src"
+)
+
+func LoadPackage(filenames []string) {
+ base.Timer.Start("fe", "parse")
+
+ // Limit the number of simultaneously open files.
+ sem := make(chan struct{}, runtime.GOMAXPROCS(0)+10)
+
+ noders := make([]*noder, len(filenames))
+ for i := range noders {
+ p := noder{
+ err: make(chan syntax.Error),
+ }
+ noders[i] = &p
+ }
+
+ // Move the entire syntax processing logic into a separate goroutine to avoid blocking on the "sem".
+ go func() {
+ for i, filename := range filenames {
+ filename := filename
+ p := noders[i]
+ sem <- struct{}{}
+ go func() {
+ defer func() { <-sem }()
+ defer close(p.err)
+ fbase := syntax.NewFileBase(filename)
+
+ f, err := os.Open(filename)
+ if err != nil {
+ p.error(syntax.Error{Msg: err.Error()})
+ return
+ }
+ defer f.Close()
+
+ p.file, _ = syntax.Parse(fbase, f, p.error, p.pragma, syntax.CheckBranches) // errors are tracked via p.error
+ }()
+ }
+ }()
+
+ var lines uint
+ for _, p := range noders {
+ for e := range p.err {
+ p.errorAt(e.Pos, "%s", e.Msg)
+ }
+ if p.file == nil {
+ base.ErrorExit()
+ }
+ lines += p.file.EOF.Line()
+ }
+ base.Timer.AddEvent(int64(lines), "lines")
+
+ if base.Debug.Unified != 0 {
+ unified(noders)
+ return
+ }
+
+ // Use types2 to type-check and generate IR.
+ check2(noders)
+}
+
+func (p *noder) errorAt(pos syntax.Pos, format string, args ...interface{}) {
+ base.ErrorfAt(p.makeXPos(pos), format, args...)
+}
+
+// trimFilename returns the "trimmed" filename of b, which is the
+// absolute filename after applying -trimpath processing. This
+// filename form is suitable for use in object files and export data.
+//
+// If b's filename has already been trimmed (i.e., because it was read
+// in from an imported package's export data), then the filename is
+// returned unchanged.
+func trimFilename(b *syntax.PosBase) string {
+ filename := b.Filename()
+ if !b.Trimmed() {
+ dir := ""
+ if b.IsFileBase() {
+ dir = base.Ctxt.Pathname
+ }
+ filename = objabi.AbsFile(dir, filename, base.Flag.TrimPath)
+ }
+ return filename
+}
+
+// noder transforms package syntax's AST into a Node tree.
+type noder struct {
+ posMap
+
+ file *syntax.File
+ linknames []linkname
+ pragcgobuf [][]string
+ err chan syntax.Error
+ importedUnsafe bool
+ importedEmbed bool
+}
+
+// linkname records a //go:linkname directive.
+type linkname struct {
+ pos syntax.Pos
+ local string
+ remote string
+}
+
+func (p *noder) processPragmas() {
+ for _, l := range p.linknames {
+ if !p.importedUnsafe {
+ p.errorAt(l.pos, "//go:linkname only allowed in Go files that import \"unsafe\"")
+ continue
+ }
+ n := ir.AsNode(typecheck.Lookup(l.local).Def)
+ if n == nil || n.Op() != ir.ONAME {
+ if types.AllowsGoVersion(1, 18) {
+ p.errorAt(l.pos, "//go:linkname must refer to declared function or variable")
+ }
+ continue
+ }
+ if n.Sym().Linkname != "" {
+ p.errorAt(l.pos, "duplicate //go:linkname for %s", l.local)
+ continue
+ }
+ n.Sym().Linkname = l.remote
+ }
+ typecheck.Target.CgoPragmas = append(typecheck.Target.CgoPragmas, p.pragcgobuf...)
+}
+
+var unOps = [...]ir.Op{
+ syntax.Recv: ir.ORECV,
+ syntax.Mul: ir.ODEREF,
+ syntax.And: ir.OADDR,
+
+ syntax.Not: ir.ONOT,
+ syntax.Xor: ir.OBITNOT,
+ syntax.Add: ir.OPLUS,
+ syntax.Sub: ir.ONEG,
+}
+
+var binOps = [...]ir.Op{
+ syntax.OrOr: ir.OOROR,
+ syntax.AndAnd: ir.OANDAND,
+
+ syntax.Eql: ir.OEQ,
+ syntax.Neq: ir.ONE,
+ syntax.Lss: ir.OLT,
+ syntax.Leq: ir.OLE,
+ syntax.Gtr: ir.OGT,
+ syntax.Geq: ir.OGE,
+
+ syntax.Add: ir.OADD,
+ syntax.Sub: ir.OSUB,
+ syntax.Or: ir.OOR,
+ syntax.Xor: ir.OXOR,
+
+ syntax.Mul: ir.OMUL,
+ syntax.Div: ir.ODIV,
+ syntax.Rem: ir.OMOD,
+ syntax.And: ir.OAND,
+ syntax.AndNot: ir.OANDNOT,
+ syntax.Shl: ir.OLSH,
+ syntax.Shr: ir.ORSH,
+}
+
+func wrapname(pos src.XPos, x ir.Node) ir.Node {
+ // These nodes do not carry line numbers.
+ // Introduce a wrapper node to give them the correct line.
+ switch x.Op() {
+ case ir.OTYPE, ir.OLITERAL:
+ if x.Sym() == nil {
+ break
+ }
+ fallthrough
+ case ir.ONAME, ir.ONONAME:
+ p := ir.NewParenExpr(pos, x)
+ p.SetImplicit(true)
+ return p
+ }
+ return x
+}
+
+// error is called concurrently if files are parsed concurrently.
+func (p *noder) error(err error) {
+ p.err <- err.(syntax.Error)
+}
+
+// pragmas that are allowed in the std lib, but don't have
+// a syntax.Pragma value (see lex.go) associated with them.
+var allowedStdPragmas = map[string]bool{
+ "go:cgo_export_static": true,
+ "go:cgo_export_dynamic": true,
+ "go:cgo_import_static": true,
+ "go:cgo_import_dynamic": true,
+ "go:cgo_ldflag": true,
+ "go:cgo_dynamic_linker": true,
+ "go:embed": true,
+ "go:generate": true,
+}
+
+// *pragmas is the value stored in a syntax.pragmas during parsing.
+type pragmas struct {
+ Flag ir.PragmaFlag // collected bits
+ Pos []pragmaPos // position of each individual flag
+ Embeds []pragmaEmbed
+}
+
+type pragmaPos struct {
+ Flag ir.PragmaFlag
+ Pos syntax.Pos
+}
+
+type pragmaEmbed struct {
+ Pos syntax.Pos
+ Patterns []string
+}
+
+func (p *noder) checkUnusedDuringParse(pragma *pragmas) {
+ for _, pos := range pragma.Pos {
+ if pos.Flag&pragma.Flag != 0 {
+ p.error(syntax.Error{Pos: pos.Pos, Msg: "misplaced compiler directive"})
+ }
+ }
+ if len(pragma.Embeds) > 0 {
+ for _, e := range pragma.Embeds {
+ p.error(syntax.Error{Pos: e.Pos, Msg: "misplaced go:embed directive"})
+ }
+ }
+}
+
+// pragma is called concurrently if files are parsed concurrently.
+func (p *noder) pragma(pos syntax.Pos, blankLine bool, text string, old syntax.Pragma) syntax.Pragma {
+ pragma, _ := old.(*pragmas)
+ if pragma == nil {
+ pragma = new(pragmas)
+ }
+
+ if text == "" {
+ // unused pragma; only called with old != nil.
+ p.checkUnusedDuringParse(pragma)
+ return nil
+ }
+
+ if strings.HasPrefix(text, "line ") {
+ // line directives are handled by syntax package
+ panic("unreachable")
+ }
+
+ if !blankLine {
+ // directive must be on line by itself
+ p.error(syntax.Error{Pos: pos, Msg: "misplaced compiler directive"})
+ return pragma
+ }
+
+ switch {
+ case strings.HasPrefix(text, "go:linkname "):
+ f := strings.Fields(text)
+ if !(2 <= len(f) && len(f) <= 3) {
+ p.error(syntax.Error{Pos: pos, Msg: "usage: //go:linkname localname [linkname]"})
+ break
+ }
+ // The second argument is optional. If omitted, we use
+ // the default object symbol name for this and
+ // linkname only serves to mark this symbol as
+ // something that may be referenced via the object
+ // symbol name from another package.
+ var target string
+ if len(f) == 3 {
+ target = f[2]
+ } else if base.Ctxt.Pkgpath != "" {
+ // Use the default object symbol name if the
+ // user didn't provide one.
+ target = objabi.PathToPrefix(base.Ctxt.Pkgpath) + "." + f[1]
+ } else {
+ p.error(syntax.Error{Pos: pos, Msg: "//go:linkname requires linkname argument or -p compiler flag"})
+ break
+ }
+ p.linknames = append(p.linknames, linkname{pos, f[1], target})
+
+ case text == "go:embed", strings.HasPrefix(text, "go:embed "):
+ args, err := parseGoEmbed(text[len("go:embed"):])
+ if err != nil {
+ p.error(syntax.Error{Pos: pos, Msg: err.Error()})
+ }
+ if len(args) == 0 {
+ p.error(syntax.Error{Pos: pos, Msg: "usage: //go:embed pattern..."})
+ break
+ }
+ pragma.Embeds = append(pragma.Embeds, pragmaEmbed{pos, args})
+
+ case strings.HasPrefix(text, "go:cgo_import_dynamic "):
+ // This is permitted for general use because Solaris
+ // code relies on it in golang.org/x/sys/unix and others.
+ fields := pragmaFields(text)
+ if len(fields) >= 4 {
+ lib := strings.Trim(fields[3], `"`)
+ if lib != "" && !safeArg(lib) && !isCgoGeneratedFile(pos) {
+ p.error(syntax.Error{Pos: pos, Msg: fmt.Sprintf("invalid library name %q in cgo_import_dynamic directive", lib)})
+ }
+ p.pragcgo(pos, text)
+ pragma.Flag |= pragmaFlag("go:cgo_import_dynamic")
+ break
+ }
+ fallthrough
+ case strings.HasPrefix(text, "go:cgo_"):
+ // For security, we disallow //go:cgo_* directives other
+ // than cgo_import_dynamic outside cgo-generated files.
+ // Exception: they are allowed in the standard library, for runtime and syscall.
+ if !isCgoGeneratedFile(pos) && !base.Flag.Std {
+ p.error(syntax.Error{Pos: pos, Msg: fmt.Sprintf("//%s only allowed in cgo-generated code", text)})
+ }
+ p.pragcgo(pos, text)
+ fallthrough // because of //go:cgo_unsafe_args
+ default:
+ verb := text
+ if i := strings.Index(text, " "); i >= 0 {
+ verb = verb[:i]
+ }
+ flag := pragmaFlag(verb)
+ const runtimePragmas = ir.Systemstack | ir.Nowritebarrier | ir.Nowritebarrierrec | ir.Yeswritebarrierrec
+ if !base.Flag.CompilingRuntime && flag&runtimePragmas != 0 {
+ p.error(syntax.Error{Pos: pos, Msg: fmt.Sprintf("//%s only allowed in runtime", verb)})
+ }
+ if flag == ir.UintptrKeepAlive && !base.Flag.Std {
+ p.error(syntax.Error{Pos: pos, Msg: fmt.Sprintf("//%s is only allowed in the standard library", verb)})
+ }
+ if flag == 0 && !allowedStdPragmas[verb] && base.Flag.Std {
+ p.error(syntax.Error{Pos: pos, Msg: fmt.Sprintf("//%s is not allowed in the standard library", verb)})
+ }
+ pragma.Flag |= flag
+ pragma.Pos = append(pragma.Pos, pragmaPos{flag, pos})
+ }
+
+ return pragma
+}
+
+// isCgoGeneratedFile reports whether pos is in a file
+// generated by cgo, which is to say a file with name
+// beginning with "_cgo_". Such files are allowed to
+// contain cgo directives, and for security reasons
+// (primarily misuse of linker flags), other files are not.
+// See golang.org/issue/23672.
+// Note that cmd/go ignores files whose names start with underscore,
+// so the only _cgo_ files we will see from cmd/go are generated by cgo.
+// It's easy to bypass this check by calling the compiler directly;
+// we only protect against uses by cmd/go.
+func isCgoGeneratedFile(pos syntax.Pos) bool {
+ // We need the absolute file, independent of //line directives,
+ // so we call pos.Base().Pos().
+ return strings.HasPrefix(filepath.Base(trimFilename(pos.Base().Pos().Base())), "_cgo_")
+}
+
+// safeArg reports whether arg is a "safe" command-line argument,
+// meaning that when it appears in a command-line, it probably
+// doesn't have some special meaning other than its own name.
+// This is copied from SafeArg in cmd/go/internal/load/pkg.go.
+func safeArg(name string) bool {
+ if name == "" {
+ return false
+ }
+ c := name[0]
+ return '0' <= c && c <= '9' || 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || c == '.' || c == '_' || c == '/' || c >= utf8.RuneSelf
+}
+
+// parseGoEmbed parses the text following "//go:embed" to extract the glob patterns.
+// It accepts unquoted space-separated patterns as well as double-quoted and back-quoted Go strings.
+// go/build/read.go also processes these strings and contains similar logic.
+func parseGoEmbed(args string) ([]string, error) {
+ var list []string
+ for args = strings.TrimSpace(args); args != ""; args = strings.TrimSpace(args) {
+ var path string
+ Switch:
+ switch args[0] {
+ default:
+ i := len(args)
+ for j, c := range args {
+ if unicode.IsSpace(c) {
+ i = j
+ break
+ }
+ }
+ path = args[:i]
+ args = args[i:]
+
+ case '`':
+ i := strings.Index(args[1:], "`")
+ if i < 0 {
+ return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args)
+ }
+ path = args[1 : 1+i]
+ args = args[1+i+1:]
+
+ case '"':
+ i := 1
+ for ; i < len(args); i++ {
+ if args[i] == '\\' {
+ i++
+ continue
+ }
+ if args[i] == '"' {
+ q, err := strconv.Unquote(args[:i+1])
+ if err != nil {
+ return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args[:i+1])
+ }
+ path = q
+ args = args[i+1:]
+ break Switch
+ }
+ }
+ if i >= len(args) {
+ return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args)
+ }
+ }
+
+ if args != "" {
+ r, _ := utf8.DecodeRuneInString(args)
+ if !unicode.IsSpace(r) {
+ return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args)
+ }
+ }
+ list = append(list, path)
+ }
+ return list, nil
+}
+
+// A function named init is a special case.
+// It is called by the initialization before main is run.
+// To make it unique within a package and also uncallable,
+// the name, normally "pkg.init", is altered to "pkg.init.0".
+var renameinitgen int
+
+func Renameinit() *types.Sym {
+ s := typecheck.LookupNum("init.", renameinitgen)
+ renameinitgen++
+ return s
+}
+
+func varEmbed(makeXPos func(syntax.Pos) src.XPos, name *ir.Name, decl *syntax.VarDecl, pragma *pragmas, haveEmbed bool) {
+ pragmaEmbeds := pragma.Embeds
+ pragma.Embeds = nil
+ if len(pragmaEmbeds) == 0 {
+ return
+ }
+
+ if err := checkEmbed(decl, haveEmbed, typecheck.DeclContext != ir.PEXTERN); err != nil {
+ base.ErrorfAt(makeXPos(pragmaEmbeds[0].Pos), "%s", err)
+ return
+ }
+
+ var embeds []ir.Embed
+ for _, e := range pragmaEmbeds {
+ embeds = append(embeds, ir.Embed{Pos: makeXPos(e.Pos), Patterns: e.Patterns})
+ }
+ typecheck.Target.Embeds = append(typecheck.Target.Embeds, name)
+ name.Embed = &embeds
+}
+
+func checkEmbed(decl *syntax.VarDecl, haveEmbed, withinFunc bool) error {
+ switch {
+ case !haveEmbed:
+ return errors.New("go:embed only allowed in Go files that import \"embed\"")
+ case len(decl.NameList) > 1:
+ return errors.New("go:embed cannot apply to multiple vars")
+ case decl.Values != nil:
+ return errors.New("go:embed cannot apply to var with initializer")
+ case decl.Type == nil:
+ // Should not happen, since Values == nil now.
+ return errors.New("go:embed cannot apply to var without type")
+ case withinFunc:
+ return errors.New("go:embed cannot apply to var inside func")
+ case !types.AllowsGoVersion(1, 16):
+ return fmt.Errorf("go:embed requires go1.16 or later (-lang was set to %s; check go.mod)", base.Flag.Lang)
+
+ default:
+ return nil
+ }
+}