github.com/bir3/gocompiler@v0.9.2202/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 "github.com/bir3/gocompiler/src/internal/buildcfg" 11 "os" 12 "path/filepath" 13 "runtime" 14 "strconv" 15 "strings" 16 "unicode" 17 "unicode/utf8" 18 19 "github.com/bir3/gocompiler/src/cmd/compile/internal/base" 20 "github.com/bir3/gocompiler/src/cmd/compile/internal/ir" 21 "github.com/bir3/gocompiler/src/cmd/compile/internal/syntax" 22 "github.com/bir3/gocompiler/src/cmd/compile/internal/typecheck" 23 "github.com/bir3/gocompiler/src/cmd/compile/internal/types" 24 "github.com/bir3/gocompiler/src/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 }