github.com/bir3/gocompiler@v0.3.205/src/cmd/compile/internal/noder/noder.go (about)

     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  	"os"
    11  	"path/filepath"
    12  	"runtime"
    13  	"strconv"
    14  	"strings"
    15  	"unicode"
    16  	"unicode/utf8"
    17  
    18  	"github.com/bir3/gocompiler/src/cmd/compile/internal/base"
    19  	"github.com/bir3/gocompiler/src/cmd/compile/internal/ir"
    20  	"github.com/bir3/gocompiler/src/cmd/compile/internal/syntax"
    21  	"github.com/bir3/gocompiler/src/cmd/compile/internal/typecheck"
    22  	"github.com/bir3/gocompiler/src/cmd/compile/internal/types"
    23  	"github.com/bir3/gocompiler/src/cmd/internal/objabi"
    24  	"github.com/bir3/gocompiler/src/cmd/internal/src"
    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  	for _, p := range noders {
    66  		for e := range p.err {
    67  			p.errorAt(e.Pos, "%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  	if base.Debug.Unified != 0 {
    77  		unified(noders)
    78  		return
    79  	}
    80  
    81  	// Use types2 to type-check and generate IR.
    82  	check2(noders)
    83  }
    84  
    85  func (p *noder) errorAt(pos syntax.Pos, format string, args ...interface{}) {
    86  	base.ErrorfAt(p.makeXPos(pos), format, args...)
    87  }
    88  
    89  // trimFilename returns the "trimmed" filename of b, which is the
    90  // absolute filename after applying -trimpath processing. This
    91  // filename form is suitable for use in object files and export data.
    92  //
    93  // If b's filename has already been trimmed (i.e., because it was read
    94  // in from an imported package's export data), then the filename is
    95  // returned unchanged.
    96  func trimFilename(b *syntax.PosBase) string {
    97  	filename := b.Filename()
    98  	if !b.Trimmed() {
    99  		dir := ""
   100  		if b.IsFileBase() {
   101  			dir = base.Ctxt.Pathname
   102  		}
   103  		filename = objabi.AbsFile(dir, filename, base.Flag.TrimPath)
   104  	}
   105  	return filename
   106  }
   107  
   108  // noder transforms package syntax's AST into a Node tree.
   109  type noder struct {
   110  	posMap
   111  
   112  	file           *syntax.File
   113  	linknames      []linkname
   114  	pragcgobuf     [][]string
   115  	err            chan syntax.Error
   116  	importedUnsafe bool
   117  	importedEmbed  bool
   118  }
   119  
   120  // linkname records a //go:linkname directive.
   121  type linkname struct {
   122  	pos    syntax.Pos
   123  	local  string
   124  	remote string
   125  }
   126  
   127  func (p *noder) processPragmas() {
   128  	for _, l := range p.linknames {
   129  		if !p.importedUnsafe {
   130  			p.errorAt(l.pos, "//go:linkname only allowed in Go files that import \"unsafe\"")
   131  			continue
   132  		}
   133  		n := ir.AsNode(typecheck.Lookup(l.local).Def)
   134  		if n == nil || n.Op() != ir.ONAME {
   135  			if types.AllowsGoVersion(1, 18) {
   136  				p.errorAt(l.pos, "//go:linkname must refer to declared function or variable")
   137  			}
   138  			continue
   139  		}
   140  		if n.Sym().Linkname != "" {
   141  			p.errorAt(l.pos, "duplicate //go:linkname for %s", l.local)
   142  			continue
   143  		}
   144  		n.Sym().Linkname = l.remote
   145  	}
   146  	typecheck.Target.CgoPragmas = append(typecheck.Target.CgoPragmas, p.pragcgobuf...)
   147  }
   148  
   149  var unOps = [...]ir.Op{
   150  	syntax.Recv: ir.ORECV,
   151  	syntax.Mul:  ir.ODEREF,
   152  	syntax.And:  ir.OADDR,
   153  
   154  	syntax.Not: ir.ONOT,
   155  	syntax.Xor: ir.OBITNOT,
   156  	syntax.Add: ir.OPLUS,
   157  	syntax.Sub: ir.ONEG,
   158  }
   159  
   160  var binOps = [...]ir.Op{
   161  	syntax.OrOr:   ir.OOROR,
   162  	syntax.AndAnd: ir.OANDAND,
   163  
   164  	syntax.Eql: ir.OEQ,
   165  	syntax.Neq: ir.ONE,
   166  	syntax.Lss: ir.OLT,
   167  	syntax.Leq: ir.OLE,
   168  	syntax.Gtr: ir.OGT,
   169  	syntax.Geq: ir.OGE,
   170  
   171  	syntax.Add: ir.OADD,
   172  	syntax.Sub: ir.OSUB,
   173  	syntax.Or:  ir.OOR,
   174  	syntax.Xor: ir.OXOR,
   175  
   176  	syntax.Mul:    ir.OMUL,
   177  	syntax.Div:    ir.ODIV,
   178  	syntax.Rem:    ir.OMOD,
   179  	syntax.And:    ir.OAND,
   180  	syntax.AndNot: ir.OANDNOT,
   181  	syntax.Shl:    ir.OLSH,
   182  	syntax.Shr:    ir.ORSH,
   183  }
   184  
   185  func wrapname(pos src.XPos, x ir.Node) ir.Node {
   186  	// These nodes do not carry line numbers.
   187  	// Introduce a wrapper node to give them the correct line.
   188  	switch x.Op() {
   189  	case ir.OTYPE, ir.OLITERAL:
   190  		if x.Sym() == nil {
   191  			break
   192  		}
   193  		fallthrough
   194  	case ir.ONAME, ir.ONONAME:
   195  		p := ir.NewParenExpr(pos, x)
   196  		p.SetImplicit(true)
   197  		return p
   198  	}
   199  	return x
   200  }
   201  
   202  // error is called concurrently if files are parsed concurrently.
   203  func (p *noder) error(err error) {
   204  	p.err <- err.(syntax.Error)
   205  }
   206  
   207  // pragmas that are allowed in the std lib, but don't have
   208  // a syntax.Pragma value (see lex.go) associated with them.
   209  var allowedStdPragmas = map[string]bool{
   210  	"go:cgo_export_static":  true,
   211  	"go:cgo_export_dynamic": true,
   212  	"go:cgo_import_static":  true,
   213  	"go:cgo_import_dynamic": true,
   214  	"go:cgo_ldflag":         true,
   215  	"go:cgo_dynamic_linker": true,
   216  	"go:embed":              true,
   217  	"go:generate":           true,
   218  }
   219  
   220  // *pragmas is the value stored in a syntax.pragmas during parsing.
   221  type pragmas struct {
   222  	Flag   ir.PragmaFlag // collected bits
   223  	Pos    []pragmaPos   // position of each individual flag
   224  	Embeds []pragmaEmbed
   225  }
   226  
   227  type pragmaPos struct {
   228  	Flag ir.PragmaFlag
   229  	Pos  syntax.Pos
   230  }
   231  
   232  type pragmaEmbed struct {
   233  	Pos      syntax.Pos
   234  	Patterns []string
   235  }
   236  
   237  func (p *noder) checkUnusedDuringParse(pragma *pragmas) {
   238  	for _, pos := range pragma.Pos {
   239  		if pos.Flag&pragma.Flag != 0 {
   240  			p.error(syntax.Error{Pos: pos.Pos, Msg: "misplaced compiler directive"})
   241  		}
   242  	}
   243  	if len(pragma.Embeds) > 0 {
   244  		for _, e := range pragma.Embeds {
   245  			p.error(syntax.Error{Pos: e.Pos, Msg: "misplaced go:embed directive"})
   246  		}
   247  	}
   248  }
   249  
   250  // pragma is called concurrently if files are parsed concurrently.
   251  func (p *noder) pragma(pos syntax.Pos, blankLine bool, text string, old syntax.Pragma) syntax.Pragma {
   252  	pragma, _ := old.(*pragmas)
   253  	if pragma == nil {
   254  		pragma = new(pragmas)
   255  	}
   256  
   257  	if text == "" {
   258  		// unused pragma; only called with old != nil.
   259  		p.checkUnusedDuringParse(pragma)
   260  		return nil
   261  	}
   262  
   263  	if strings.HasPrefix(text, "line ") {
   264  		// line directives are handled by syntax package
   265  		panic("unreachable")
   266  	}
   267  
   268  	if !blankLine {
   269  		// directive must be on line by itself
   270  		p.error(syntax.Error{Pos: pos, Msg: "misplaced compiler directive"})
   271  		return pragma
   272  	}
   273  
   274  	switch {
   275  	case strings.HasPrefix(text, "go:linkname "):
   276  		f := strings.Fields(text)
   277  		if !(2 <= len(f) && len(f) <= 3) {
   278  			p.error(syntax.Error{Pos: pos, Msg: "usage: //go:linkname localname [linkname]"})
   279  			break
   280  		}
   281  		// The second argument is optional. If omitted, we use
   282  		// the default object symbol name for this and
   283  		// linkname only serves to mark this symbol as
   284  		// something that may be referenced via the object
   285  		// symbol name from another package.
   286  		var target string
   287  		if len(f) == 3 {
   288  			target = f[2]
   289  		} else if base.Ctxt.Pkgpath != "" {
   290  			// Use the default object symbol name if the
   291  			// user didn't provide one.
   292  			target = objabi.PathToPrefix(base.Ctxt.Pkgpath) + "." + f[1]
   293  		} else {
   294  			p.error(syntax.Error{Pos: pos, Msg: "//go:linkname requires linkname argument or -p compiler flag"})
   295  			break
   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  func isCgoGeneratedFile(pos syntax.Pos) bool {
   363  	return strings.HasPrefix(filepath.Base(trimFilename(pos.Base())), "_cgo_")
   364  }
   365  
   366  // safeArg reports whether arg is a "safe" command-line argument,
   367  // meaning that when it appears in a command-line, it probably
   368  // doesn't have some special meaning other than its own name.
   369  // This is copied from SafeArg in cmd/go/internal/load/pkg.go.
   370  func safeArg(name string) bool {
   371  	if name == "" {
   372  		return false
   373  	}
   374  	c := name[0]
   375  	return '0' <= c && c <= '9' || 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || c == '.' || c == '_' || c == '/' || c >= utf8.RuneSelf
   376  }
   377  
   378  // parseGoEmbed parses the text following "//go:embed" to extract the glob patterns.
   379  // It accepts unquoted space-separated patterns as well as double-quoted and back-quoted Go strings.
   380  // go/build/read.go also processes these strings and contains similar logic.
   381  func parseGoEmbed(args string) ([]string, error) {
   382  	var list []string
   383  	for args = strings.TrimSpace(args); args != ""; args = strings.TrimSpace(args) {
   384  		var path string
   385  	Switch:
   386  		switch args[0] {
   387  		default:
   388  			i := len(args)
   389  			for j, c := range args {
   390  				if unicode.IsSpace(c) {
   391  					i = j
   392  					break
   393  				}
   394  			}
   395  			path = args[:i]
   396  			args = args[i:]
   397  
   398  		case '`':
   399  			i := strings.Index(args[1:], "`")
   400  			if i < 0 {
   401  				return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args)
   402  			}
   403  			path = args[1 : 1+i]
   404  			args = args[1+i+1:]
   405  
   406  		case '"':
   407  			i := 1
   408  			for ; i < len(args); i++ {
   409  				if args[i] == '\\' {
   410  					i++
   411  					continue
   412  				}
   413  				if args[i] == '"' {
   414  					q, err := strconv.Unquote(args[:i+1])
   415  					if err != nil {
   416  						return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args[:i+1])
   417  					}
   418  					path = q
   419  					args = args[i+1:]
   420  					break Switch
   421  				}
   422  			}
   423  			if i >= len(args) {
   424  				return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args)
   425  			}
   426  		}
   427  
   428  		if args != "" {
   429  			r, _ := utf8.DecodeRuneInString(args)
   430  			if !unicode.IsSpace(r) {
   431  				return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args)
   432  			}
   433  		}
   434  		list = append(list, path)
   435  	}
   436  	return list, nil
   437  }
   438  
   439  // A function named init is a special case.
   440  // It is called by the initialization before main is run.
   441  // To make it unique within a package and also uncallable,
   442  // the name, normally "pkg.init", is altered to "pkg.init.0".
   443  var renameinitgen int
   444  
   445  func Renameinit() *types.Sym {
   446  	s := typecheck.LookupNum("init.", renameinitgen)
   447  	renameinitgen++
   448  	return s
   449  }
   450  
   451  func varEmbed(makeXPos func(syntax.Pos) src.XPos, name *ir.Name, decl *syntax.VarDecl, pragma *pragmas, haveEmbed bool) {
   452  	pragmaEmbeds := pragma.Embeds
   453  	pragma.Embeds = nil
   454  	if len(pragmaEmbeds) == 0 {
   455  		return
   456  	}
   457  
   458  	if err := checkEmbed(decl, haveEmbed, typecheck.DeclContext != ir.PEXTERN); err != nil {
   459  		base.ErrorfAt(makeXPos(pragmaEmbeds[0].Pos), "%s", err)
   460  		return
   461  	}
   462  
   463  	var embeds []ir.Embed
   464  	for _, e := range pragmaEmbeds {
   465  		embeds = append(embeds, ir.Embed{Pos: makeXPos(e.Pos), Patterns: e.Patterns})
   466  	}
   467  	typecheck.Target.Embeds = append(typecheck.Target.Embeds, name)
   468  	name.Embed = &embeds
   469  }
   470  
   471  func checkEmbed(decl *syntax.VarDecl, haveEmbed, withinFunc bool) error {
   472  	switch {
   473  	case !haveEmbed:
   474  		return errors.New("go:embed only allowed in Go files that import \"embed\"")
   475  	case len(decl.NameList) > 1:
   476  		return errors.New("go:embed cannot apply to multiple vars")
   477  	case decl.Values != nil:
   478  		return errors.New("go:embed cannot apply to var with initializer")
   479  	case decl.Type == nil:
   480  		// Should not happen, since Values == nil now.
   481  		return errors.New("go:embed cannot apply to var without type")
   482  	case withinFunc:
   483  		return errors.New("go:embed cannot apply to var inside func")
   484  	case !types.AllowsGoVersion(1, 16):
   485  		return fmt.Errorf("go:embed requires go1.16 or later (-lang was set to %s; check go.mod)", base.Flag.Lang)
   486  
   487  	default:
   488  		return nil
   489  	}
   490  }