From 1a57267a17c2fc17fb6e104846fabc3e363c326c Mon Sep 17 00:00:00 2001 From: Emile Date: Fri, 16 Aug 2024 19:50:26 +0200 Subject: initial commit --- vendor/modernc.org/gc/v3/gc.go | 761 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 761 insertions(+) create mode 100644 vendor/modernc.org/gc/v3/gc.go (limited to 'vendor/modernc.org/gc/v3/gc.go') diff --git a/vendor/modernc.org/gc/v3/gc.go b/vendor/modernc.org/gc/v3/gc.go new file mode 100644 index 0000000..1723bd5 --- /dev/null +++ b/vendor/modernc.org/gc/v3/gc.go @@ -0,0 +1,761 @@ +// Copyright 2022 The Gc Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:generate stringer -output stringer.go -linecomment -type=Kind,ScopeKind,ChanDir,TypeCheck + +package gc // modernc.org/gc/v3 + +import ( + "fmt" + "go/build" + "go/build/constraint" + "go/token" + "io" + "io/fs" + "os" + "path/filepath" + "runtime" + "sort" + "strconv" + "strings" + "sync" + "unicode" + + "github.com/hashicorp/golang-lru/v2" +) + +var ( + trcErrors bool +) + +type FileFilter func(cfg *Config, importPath string, matchedFSPaths []string, withTestFiles bool) (pkgFiles []string, err error) + +type TypeCheck int + +const ( + TypeCheckNone TypeCheck = iota + TypeCheckAll +) + +type cacheKey struct { + buildTagsKey string + cfg *Config + fsPath string + goarch string + goos string + gopathKey string + goroot string + importPath string + typeCheck TypeCheck + + withTestFiles bool +} + +type cacheItem struct { + pkg *Package + ch chan struct{} +} + +func newCacheItem() *cacheItem { return &cacheItem{ch: make(chan struct{})} } + +func (c *cacheItem) set(pkg *Package) { + c.pkg = pkg + close(c.ch) +} + +func (c *cacheItem) wait() *Package { + <-c.ch + return c.pkg +} + +type Cache struct { + sync.Mutex + lru *lru.TwoQueueCache[cacheKey, *cacheItem] +} + +func NewCache(size int) (*Cache, error) { + c, err := lru.New2Q[cacheKey, *cacheItem](size) + if err != nil { + return nil, err + } + + return &Cache{lru: c}, nil +} + +func MustNewCache(size int) *Cache { + c, err := NewCache(size) + if err != nil { + panic(todo("", err)) + } + + return c +} + +type ConfigOption func(*Config) error + +// Config configures NewPackage +// +// Config instances can be shared, they are not mutated once created and +// configured. +type Config struct { + abi *ABI + buildTagMap map[string]bool + buildTags []string + buildTagsKey string // Zero byte separated + builtin *Package + cache *Cache + cmp *Package // Go 1.21 + env map[string]string + fs fs.FS + goarch string + gocompiler string // "gc", "gccgo" + goos string + gopath string + gopathKey string // Zero byte separated + goroot string + goversion string + lookup func(rel, importPath, version string) (fsPath string, err error) + parallel *parallel + searchGoPaths []string + searchGoroot []string + + int Type // Set by NewConfig + uint Type // Set by NewConfig + + arch32bit bool + configured bool +} + +// NewConfig returns a newly created config or an error, if any. +func NewConfig(opts ...ConfigOption) (r *Config, err error) { + r = &Config{ + buildTagMap: map[string]bool{}, + env: map[string]string{}, + parallel: newParallel(), + } + + defer func() { + if r != nil { + r.configured = true + } + }() + + r.lookup = r.DefaultLookup + ctx := build.Default + r.goos = r.getenv("GOOS", ctx.GOOS) + r.goarch = r.getenv("GOARCH", ctx.GOARCH) + r.goroot = r.getenv("GOROOT", ctx.GOROOT) + r.gopath = r.getenv("GOPATH", ctx.GOPATH) + r.buildTags = append(r.buildTags, r.goos, r.goarch) + r.gocompiler = runtime.Compiler + for _, opt := range opts { + if err := opt(r); err != nil { + return nil, err + } + } + if r.abi, err = NewABI(r.goos, r.goarch); err != nil { + return nil, err + } + switch r.goarch { + case "386", "arm": + r.arch32bit = true + } + + // During a particular build, the following build tags are satisfied: + // + // the target operating system, as spelled by runtime.GOOS, set with the GOOS environment variable. + // the target architecture, as spelled by runtime.GOARCH, set with the GOARCH environment variable. + // "unix", if GOOS is a Unix or Unix-like system. + // the compiler being used, either "gc" or "gccgo" + // "cgo", if the cgo command is supported (see CGO_ENABLED in 'go help environment'). + // a term for each Go major release, through the current version: "go1.1" from Go version 1.1 onward, "go1.12" from Go 1.12, and so on. + // any additional tags given by the -tags flag (see 'go help build'). + // There are no separate build tags for beta or minor releases. + if r.goversion == "" { + r.goversion = runtime.Version() + } + if !strings.HasPrefix(r.goversion, "go") || !strings.Contains(r.goversion, ".") { + return nil, fmt.Errorf("cannot parse Go version: %s", r.goversion) + } + + ver := strings.SplitN(r.goversion[len("go"):], ".", 2) + verMajor, err := strconv.Atoi(ver[0]) + if err != nil { + return nil, fmt.Errorf("cannot parse Go version %s: %v", r.goversion, err) + } + + if verMajor != 1 { + return nil, fmt.Errorf("unsupported Go version: %s", r.goversion) + } + + switch x, x2 := strings.IndexByte(ver[1], '.'), strings.Index(ver[1], "rc"); { + case x >= 0: + ver[1] = ver[1][:x] + case x2 >= 0: + ver[1] = ver[1][:x2] + } + verMinor, err := strconv.Atoi(ver[1]) + if err != nil { + return nil, fmt.Errorf("cannot parse Go version %s: %v", r.goversion, err) + } + + for i := 1; i <= verMinor; i++ { + r.buildTags = append(r.buildTags, fmt.Sprintf("go%d.%d", verMajor, i)) + } + r.buildTags = append(r.buildTags, r.gocompiler) + r.buildTags = append(r.buildTags, extraTags(verMajor, verMinor, r.goos, r.goarch)...) + if r.getenv("CGO_ENABLED", "1") == "1" { + r.buildTags = append(r.buildTags, "cgo") + } + for i, v := range r.buildTags { + tag := strings.TrimSpace(v) + r.buildTags[i] = tag + r.buildTagMap[tag] = true + } + sort.Strings(r.buildTags) + r.buildTagsKey = strings.Join(r.buildTags, "\x00") + r.searchGoroot = []string{filepath.Join(r.goroot, "src")} + r.searchGoPaths = filepath.SplitList(r.gopath) + r.gopathKey = strings.Join(r.searchGoPaths, "\x00") + for i, v := range r.searchGoPaths { + r.searchGoPaths[i] = filepath.Join(v, "src") + } + + switch r.cmp, err = r.NewPackage("", "cmp", "", nil, false, TypeCheckNone); { + case err != nil: + r.cmp = nil + default: + //TODO r.cmp.Scope.kind = UniverseScope + } + if r.builtin, err = r.NewPackage("", "builtin", "", nil, false, TypeCheckNone); err != nil { + return nil, err + } + + r.builtin.Scope.kind = UniverseScope + if err := r.builtin.check(newCtx(r)); err != nil { + return nil, err + } + + return r, nil +} + +func (c *Config) universe() *Scope { + if c.builtin != nil { + return c.builtin.Scope + } + + return nil +} + +func (c *Config) stat(name string) (fs.FileInfo, error) { + if c.fs == nil { + return os.Stat(name) + } + + name = filepath.ToSlash(name) + if x, ok := c.fs.(fs.StatFS); ok { + return x.Stat(name) + } + + f, err := c.fs.Open(name) + if err != nil { + return nil, err + } + + defer f.Close() + + return f.Stat() +} + +func (c *Config) open(name string) (fs.File, error) { + if c.fs == nil { + return os.Open(name) + } + + name = filepath.ToSlash(name) + return c.fs.Open(name) +} + +func (c *Config) glob(pattern string) (matches []string, err error) { + if c.fs == nil { + return filepath.Glob(pattern) + } + + pattern = filepath.ToSlash(pattern) + return fs.Glob(c.fs, pattern) +} + +func (c *Config) checkConstraints(pos token.Position, sep string) (r bool) { + if !strings.Contains(sep, "//go:build") && !strings.Contains(sep, "+build") { + return true + } + + // defer func() { trc("", r) }() + + lines := strings.Split(sep, "\n") + var build, plusBuild []string + for i, line := range lines { + if constraint.IsGoBuild(line) && i < len(lines)-1 && lines[i+1] == "" { + build = append(build, line) + } + if constraint.IsPlusBuild(line) { + plusBuild = append(plusBuild, line) + } + } + switch len(build) { + case 0: + // ok + case 1: + expr, err := constraint.Parse(build[0]) + if err != nil { + return true + } + + return expr.Eval(func(tag string) (r bool) { + // defer func() { trc("%q: %v", tag, r) }() + switch tag { + case "unix": + return unixOS[c.goos] + default: + return c.buildTagMap[tag] + } + }) + default: + panic(todo("%v: %q", pos, build)) + } + + for _, line := range plusBuild { + expr, err := constraint.Parse(line) + if err != nil { + return true + } + + if !expr.Eval(func(tag string) (r bool) { + // defer func() { trc("%q: %v", tag, r) }() + switch tag { + case "unix": + return unixOS[c.goos] + default: + return c.buildTagMap[tag] + } + }) { + return false + } + } + return true +} + +// Default lookup translates import paths, possibly relative to rel, to file system paths. +func (c *Config) DefaultLookup(rel, importPath, version string) (fsPath string, err error) { + if importPath == "" { + return "", fmt.Errorf("import path cannot be emtpy") + } + + // Implementation restriction: A compiler may restrict ImportPaths to non-empty + // strings using only characters belonging to Unicode's L, M, N, P, and S + // general categories (the Graphic characters without spaces) and may also + // exclude the characters !"#$%&'()*,:;<=>?[\]^`{|} and the Unicode replacement + // character U+FFFD. + if strings.ContainsAny(importPath, "!\"#$%&'()*,:;<=>?[\\]^`{|}\ufffd") { + return "", fmt.Errorf("invalid import path: %s", importPath) + } + + for _, r := range importPath { + if !unicode.Is(unicode.L, r) && + !unicode.Is(unicode.M, r) && + !unicode.Is(unicode.N, r) && + !unicode.Is(unicode.P, r) && + !unicode.Is(unicode.S, r) { + return "", fmt.Errorf("invalid import path: %s", importPath) + } + } + var search []string + ip0 := importPath + switch slash := strings.IndexByte(importPath, '/'); { + case strings.HasPrefix(importPath, "./"): + if rel != "" { + panic(todo("")) + } + + return "", fmt.Errorf("invalid import path: %s", importPath) + case strings.HasPrefix(importPath, "/"): + return importPath, nil + case slash > 0: + ip0 = importPath[:slash] + default: + ip0 = importPath + } + if ip0 != "" { + switch { + case strings.Contains(ip0, "."): + search = c.searchGoPaths + default: + search = c.searchGoroot + } + } + for _, v := range search { + fsPath = filepath.Join(v, importPath) + dir, err := c.open(fsPath) + if err != nil { + continue + } + + fi, err := dir.Stat() + dir.Close() + if err != nil { + continue + } + + if fi.IsDir() { + return fsPath, nil + } + } + + return "", fmt.Errorf("cannot find package %s, searched %v", importPath, search) +} + +func (c *Config) getenv(nm, deflt string) (r string) { + if r = c.env[nm]; r != "" { + return r + } + + if r = os.Getenv(nm); r != "" { + return r + } + + return deflt +} + +func DefaultFileFilter(cfg *Config, importPath string, matchedFSPaths []string, withTestFiles bool) (pkgFiles []string, err error) { + w := 0 + for _, v := range matchedFSPaths { + base := filepath.Base(v) + base = base[:len(base)-len(filepath.Ext(base))] + const testSuffix = "_test" + if strings.HasSuffix(base, testSuffix) { + if !withTestFiles { + continue + } + + base = base[:len(base)-len(testSuffix)] + } + if x := strings.LastIndexByte(base, '_'); x > 0 { + last := base[x+1:] + base = base[:x] + var prevLast string + if x := strings.LastIndexByte(base, '_'); x > 0 { + prevLast = base[x+1:] + } + if last != "" && prevLast != "" { + // *_GOOS_GOARCH + if knownOS[prevLast] && prevLast != cfg.goos { + continue + } + + if knownArch[last] && last != cfg.goarch { + continue + } + } + + if last != "" { + // *_GOOS or *_GOARCH + if knownOS[last] && last != cfg.goos { + continue + } + + if knownArch[last] && last != cfg.goarch { + continue + } + } + } + + matchedFSPaths[w] = v + w++ + } + return matchedFSPaths[:w], nil +} + +// ConfigBuildTags configures build tags. +func ConfigBuildTags(tags []string) ConfigOption { + return func(cfg *Config) error { + if cfg.configured { + return fmt.Errorf("ConfigBuildTags: Config instance already configured") + } + + cfg.buildTags = append(cfg.buildTags, tags...) + return nil + } +} + +// ConfigEnviron configures environment variables. +func ConfigEnviron(env []string) ConfigOption { + return func(cfg *Config) error { + if cfg.configured { + return fmt.Errorf("ConfigEnviron: Config instance already configured") + } + + for _, v := range env { + switch x := strings.IndexByte(v, '='); { + case x < 0: + cfg.env[v] = "" + default: + cfg.env[v[:x]] = v[x+1:] + } + } + return nil + } +} + +// ConfigFS configures a file system used for opening Go source files. If not +// explicitly configured, a default os.DirFS("/") is used on Unix-like +// operating systems. On Windows it will be rooted on the volume where +// runtime.GOROOT() is. +func ConfigFS(fs fs.FS) ConfigOption { + return func(cfg *Config) error { + if cfg.configured { + return fmt.Errorf("ConfigFS: Config instance already configured") + } + + cfg.fs = fs + return nil + } +} + +// ConfigLookup configures a lookup function. +func ConfigLookup(f func(dir, importPath, version string) (fsPath string, err error)) ConfigOption { + return func(cfg *Config) error { + if cfg.configured { + return fmt.Errorf("ConfigLookup: Config instance already configured") + } + + cfg.lookup = f + return nil + } +} + +// ConfigCache configures a cache. +func ConfigCache(c *Cache) ConfigOption { + return func(cfg *Config) error { + if cfg.configured { + return fmt.Errorf("ConfigCache: Config instance already configured") + } + + cfg.cache = c + return nil + } +} + +type importGuard struct { + m map[string]struct{} + stack []string +} + +func newImportGuard() *importGuard { return &importGuard{m: map[string]struct{}{}} } + +// Package represents a Go package. The instance must not be mutated. +type Package struct { + AST map[string]*AST // AST maps fsPaths of individual files to their respective ASTs + FSPath string + GoFiles []fs.FileInfo + ImportPath string + InvalidGoFiles map[string]error // errors for particular files, if any + Name Token + Scope *Scope // Package scope. + Version string + cfg *Config + guard *importGuard + mu sync.Mutex + typeCheck TypeCheck + + isUnsafe bool // ImportPath == "usnafe" + // isChecked bool +} + +// NewPackage returns a Package, possibly cached, for importPath@version or an +// error, if any. The fileFilter argument can be nil, in such case +// DefaultFileFilter is used, which ignores Files with suffix _test.go unless +// withTestFiles is true. +// +// NewPackage is safe for concurrent use by multiple goroutines. +func (c *Config) NewPackage(dir, importPath, version string, fileFilter FileFilter, withTestFiles bool, typeCheck TypeCheck) (pkg *Package, err error) { + return c.newPackage(dir, importPath, version, fileFilter, withTestFiles, typeCheck, newImportGuard()) +} + +func (c *Config) newPackage(dir, importPath, version string, fileFilter FileFilter, withTestFiles bool, typeCheck TypeCheck, guard *importGuard) (pkg *Package, err error) { + if _, ok := guard.m[importPath]; ok { + return nil, fmt.Errorf("import cycle %v", guard.stack) + } + + guard.stack = append(guard.stack, importPath) + fsPath, err := c.lookup(dir, importPath, version) + if err != nil { + return nil, fmt.Errorf("lookup %s: %v", importPath, err) + } + + pat := filepath.Join(fsPath, "*.go") + matches, err := c.glob(pat) + if err != nil { + return nil, fmt.Errorf("glob %s: %v", pat, err) + } + + if len(matches) == 0 { + return nil, fmt.Errorf("no Go files in %s", fsPath) + } + + if fileFilter == nil { + fileFilter = DefaultFileFilter + } + if matches, err = fileFilter(c, importPath, matches, withTestFiles); err != nil { + return nil, fmt.Errorf("matching Go files in %s: %v", fsPath, err) + } + + var k cacheKey + if c.cache != nil { + k = cacheKey{ + buildTagsKey: c.buildTagsKey, + cfg: c, + fsPath: fsPath, + goarch: c.goarch, + goos: c.goos, + gopathKey: c.gopathKey, + goroot: c.goroot, + importPath: importPath, + typeCheck: typeCheck, + withTestFiles: withTestFiles, + } + + c.cache.Lock() // ---------------------------------------- lock + item, ok := c.cache.lru.Get(k) + if ok { + c.cache.Unlock() // ---------------------------- unlock + if pkg = item.wait(); pkg != nil && pkg.matches(&k, matches) { + return pkg, nil + } + } + + item = newCacheItem() + c.cache.lru.Add(k, item) + c.cache.Unlock() // ------------------------------------ unlock + + defer func() { + if pkg != nil && err == nil { + item.set(pkg) + } + }() + } + + r := &Package{ + AST: map[string]*AST{}, + FSPath: fsPath, + ImportPath: importPath, + Scope: newScope(c.universe(), PackageScope), + Version: version, + cfg: c, + guard: guard, + isUnsafe: importPath == "unsafe", + typeCheck: typeCheck, + } + + defer func() { r.guard = nil }() + + sort.Strings(matches) + + defer func() { + sort.Slice(r.GoFiles, func(i, j int) bool { return r.GoFiles[i].Name() < r.GoFiles[j].Name() }) + if err != nil || len(r.InvalidGoFiles) != 0 || typeCheck == TypeCheckNone { + return + } + + //TODO err = r.check(newCtx(c)) + }() + + c.parallel.throttle(func() { + for _, path := range matches { + if err = c.newPackageFile(r, path); err != nil { + return + } + } + }) + return r, err +} + +func (c *Config) newPackageFile(pkg *Package, path string) (err error) { + f, err := c.open(path) + if err != nil { + return fmt.Errorf("opening file %q: %v", path, err) + } + + defer func() { + f.Close() + if err != nil { + if pkg.InvalidGoFiles == nil { + pkg.InvalidGoFiles = map[string]error{} + } + pkg.InvalidGoFiles[path] = err + } + }() + + var fi fs.FileInfo + if fi, err = f.Stat(); err != nil { + return fmt.Errorf("stat %s: %v", path, err) + } + + if !fi.Mode().IsRegular() { + return nil + } + + var b []byte + if b, err = io.ReadAll(f); err != nil { + return fmt.Errorf("reading %s: %v", path, err) + } + + p := newParser(pkg.Scope, path, b, false) + if p.peek(0) == PACKAGE { + tok := Token{p.s.source, p.s.toks[p.ix].ch, int32(p.ix)} + if !c.checkConstraints(tok.Position(), tok.Sep()) { + return nil + } + } + + pkg.GoFiles = append(pkg.GoFiles, fi) + var ast *AST + if ast, err = p.parse(); err != nil { + return nil + } + + pkg.AST[path] = ast + return nil +} + +func (p *Package) matches(k *cacheKey, matches []string) bool { + matched := map[string]struct{}{} + for _, match := range matches { + matched[match] = struct{}{} + } + for _, cachedInfo := range p.GoFiles { + name := cachedInfo.Name() + path := filepath.Join(p.FSPath, name) + if _, ok := matched[path]; !ok { + return false + } + + info, err := k.cfg.stat(path) + if err != nil { + return false + } + + if info.IsDir() || + info.Size() != cachedInfo.Size() || + info.ModTime().After(cachedInfo.ModTime()) || + info.Mode() != cachedInfo.Mode() { + return false + } + } + return true +} + +// ParseFile parses 'b', assuming it comes from 'path' and returns an AST or error, if any. +func ParseFile(path string, b []byte) (*AST, error) { + return newParser(newScope(nil, PackageScope), path, b, false).parse() +} -- cgit 1.4.1