mirror of https://github.com/golang/go.git
491 lines
13 KiB
Go
491 lines
13 KiB
Go
// 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
|
|
}
|
|
}
|