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

View as plain text