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

View as plain text