Source file src/cmd/compile/internal/noder/noder.go

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

View as plain text