| // 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") | |
| mode := syntax.CheckBranches | |
| // 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, mode) // 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 { | |
| 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. | |
| func isCgoGeneratedFile(pos syntax.Pos) bool { | |
| return strings.HasPrefix(filepath.Base(trimFilename(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 | |
| } | |
| } |