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

View as plain text