From 21f05284c79c3e823169c62d189826f735006d43 Mon Sep 17 00:00:00 2001 From: Michael Matloob Date: Fri, 29 Apr 2022 14:45:46 -0400 Subject: cmd/go: index standard library packages Change-Id: I07594303a1e9833723522d5ff94577a5510ca6f0 Reviewed-on: https://go-review.googlesource.com/c/go/+/404714 Run-TryBot: Michael Matloob Reviewed-by: Bryan Mills TryBot-Result: Gopher Robot Reviewed-by: Michael Matloob --- src/cmd/go/internal/load/pkg.go | 3 +- src/cmd/go/internal/modindex/read.go | 128 ++++++++++++++++++++++++++++------ src/cmd/go/internal/modload/build.go | 4 +- src/cmd/go/internal/modload/import.go | 3 +- 4 files changed, 110 insertions(+), 28 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/go/internal/load/pkg.go b/src/cmd/go/internal/load/pkg.go index 8ceacec326..4c7833b4d2 100644 --- a/src/cmd/go/internal/load/pkg.go +++ b/src/cmd/go/internal/load/pkg.go @@ -14,7 +14,6 @@ import ( "go/build" "go/scanner" "go/token" - "internal/goroot" "io/fs" "os" "os/exec" @@ -3072,7 +3071,7 @@ func PackagesAndErrorsOutsideModule(ctx context.Context, opts PackageOpts, args return nil, fmt.Errorf("%s: argument must be a package path, not a meta-package", arg) case path.Clean(p) != p: return nil, fmt.Errorf("%s: argument must be a clean package path", arg) - case !strings.Contains(p, "...") && search.IsStandardImportPath(p) && goroot.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, p): + case !strings.Contains(p, "...") && search.IsStandardImportPath(p) && modindex.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, p): return nil, fmt.Errorf("%s: argument must not be a package in the standard library", arg) default: patterns[i] = p diff --git a/src/cmd/go/internal/modindex/read.go b/src/cmd/go/internal/modindex/read.go index e180ca5450..4f02ca5d10 100644 --- a/src/cmd/go/internal/modindex/read.go +++ b/src/cmd/go/internal/modindex/read.go @@ -1,31 +1,39 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package modindex import ( "bytes" - "cmd/go/internal/base" - "cmd/go/internal/cache" - "cmd/go/internal/cfg" - "cmd/go/internal/fsys" - "cmd/go/internal/imports" - "cmd/go/internal/par" - "cmd/go/internal/str" "encoding/binary" "errors" "fmt" "go/build" "go/build/constraint" "go/token" + "internal/goroot" "internal/unsafeheader" "io/fs" "math" "os" + "path" "path/filepath" + "runtime" "runtime/debug" "sort" "strconv" "strings" "sync" "unsafe" + + "cmd/go/internal/base" + "cmd/go/internal/cache" + "cmd/go/internal/cfg" + "cmd/go/internal/fsys" + "cmd/go/internal/imports" + "cmd/go/internal/par" + "cmd/go/internal/str" ) // enabled is used to flag off the behavior of the module index on tip. @@ -48,8 +56,8 @@ var fcache par.Cache func moduleHash(modroot string, ismodcache bool) (cache.ActionID, error) { h := cache.NewHash("moduleIndex") - fmt.Fprintf(h, "module index %s %v", indexVersion, modroot) - if ismodcache { + fmt.Fprintf(h, "module index %s %s %v\n", runtime.Version(), indexVersion, modroot) + if ismodcache || str.HasFilePathPrefix(modroot, cfg.GOROOT) { return h.Sum(), nil } // walkdir happens in deterministic order. @@ -97,10 +105,6 @@ func Get(modroot string) (*ModuleIndex, error) { if modroot == "" { panic("modindex.Get called with empty modroot") } - if str.HasFilePathPrefix(modroot, cfg.GOROOT) { - // TODO(matloob): add a case for stdlib here. - return nil, ErrNotIndexed - } isModCache := str.HasFilePathPrefix(modroot, cfg.GOMODCACHE) return openIndex(modroot, isModCache) } @@ -225,9 +229,6 @@ func (mi *ModuleIndex) Import(bctxt build.Context, relpath string, mode build.Im p.ImportPath = "." p.Dir = filepath.Join(mi.modroot, rp.dir) - if rp.error != "" { - return p, errors.New(rp.error) - } var pkgerr error switch ctxt.Compiler { @@ -241,6 +242,62 @@ func (mi *ModuleIndex) Import(bctxt build.Context, relpath string, mode build.Im return p, fmt.Errorf("import %q: import of unknown directory", p.Dir) } + // goroot + inTestdata := func(sub string) bool { + return strings.Contains(sub, "/testdata/") || strings.HasSuffix(sub, "/testdata") || str.HasPathPrefix(sub, "testdata") + } + if ctxt.GOROOT != "" && str.HasFilePathPrefix(mi.modroot, cfg.GOROOTsrc) && !inTestdata(relpath) { + modprefix := str.TrimFilePathPrefix(mi.modroot, cfg.GOROOTsrc) + p.Goroot = true + p.ImportPath = relpath + if modprefix != "" { + p.ImportPath = filepath.Join(modprefix, p.ImportPath) + } + // In build.go, p.Root should only be set in the non-local-import case, or in + // GOROOT or GOPATH. Since module mode only calls Import with path set to "." + // and the module index doesn't apply outside modules, the GOROOT case is + // the only case where GOROOT needs to be set. + // TODO(#37015): p.Root actually might be set in the local-import case outside + // GOROOT, if the directory is contained in GOPATH/src, even in module + // mode, but that's a bug. + p.Root = ctxt.GOROOT + + // Set GOROOT-specific fields + // The fields set below (SrcRoot, PkgRoot, BinDir, PkgTargetRoot, and PkgObj) + // are only set in build.Import if p.Root != "". As noted in the comment + // on setting p.Root above, p.Root should only be set in the GOROOT case for the + // set of packages we care about. + var pkgtargetroot string + var pkga string + suffix := "" + if ctxt.InstallSuffix != "" { + suffix = "_" + ctxt.InstallSuffix + } + switch ctxt.Compiler { + case "gccgo": + pkgtargetroot = "pkg/gccgo_" + ctxt.GOOS + "_" + ctxt.GOARCH + suffix + dir, elem := path.Split(p.ImportPath) + pkga = pkgtargetroot + "/" + dir + "lib" + elem + ".a" + case "gc": + pkgtargetroot = "pkg/" + ctxt.GOOS + "_" + ctxt.GOARCH + suffix + pkga = pkgtargetroot + "/" + p.ImportPath + ".a" + } + p.SrcRoot = ctxt.joinPath(p.Root, "src") + p.PkgRoot = ctxt.joinPath(p.Root, "pkg") + p.BinDir = ctxt.joinPath(p.Root, "bin") + if pkga != "" { + p.PkgTargetRoot = ctxt.joinPath(p.Root, pkgtargetroot) + p.PkgObj = ctxt.joinPath(p.Root, pkga) + } + } + + if rp.error != nil { + if errors.Is(rp.error, errCannotFindPackage) && ctxt.Compiler == "gccgo" && p.Goroot { + return p, nil + } + return p, rp.error + } + if mode&build.FindOnly != 0 { return p, pkgerr } @@ -444,8 +501,31 @@ func (mi *ModuleIndex) Import(bctxt build.Context, relpath string, mode build.Im return p, pkgerr } -// IsDirWithGoFiles is the equivalent of fsys.IsDirWithGoFiles using the information in the -// RawPackage. +// IsStandardPackage reports whether path is a standard package +// for the goroot and compiler using the module index if possible, +// and otherwise falling back to internal/goroot.IsStandardPackage +func IsStandardPackage(goroot_, compiler, path string) bool { + if !enabled || compiler != "gc" { + return goroot.IsStandardPackage(goroot_, compiler, path) + } + + reldir := filepath.FromSlash(path) // relative dir path in module index for package + modroot := filepath.Join(goroot_, "src") + if str.HasFilePathPrefix(reldir, "cmd") { + reldir = str.TrimFilePathPrefix(reldir, "cmd") + modroot = filepath.Join(modroot, "cmd") + } + mod, err := Get(modroot) + if err != nil { + return goroot.IsStandardPackage(goroot_, compiler, path) + } + + pkgs := mod.Packages() + i := sort.SearchStrings(pkgs, reldir) + return i != len(pkgs) && pkgs[i] == reldir +} + +// IsDirWithGoFiles is the equivalent of fsys.IsDirWithGoFiles using the information in the index. func (mi *ModuleIndex) IsDirWithGoFiles(relpath string) (_ bool, err error) { rp := mi.indexPackage(relpath) @@ -462,7 +542,7 @@ func (mi *ModuleIndex) IsDirWithGoFiles(relpath string) (_ bool, err error) { return false, nil } -// ScanDir implements imports.ScanDir using the information in the RawPackage. +// ScanDir implements imports.ScanDir using the information in the index. func (mi *ModuleIndex) ScanDir(path string, tags map[string]bool) (sortedImports []string, sortedTestImports []string, err error) { rp := mi.indexPackage(path) @@ -556,13 +636,15 @@ func shouldBuild(sf *sourceFile, tags map[string]bool) bool { // index package holds the information needed to access information in the // index about a package. type indexPackage struct { - error string + error error dir string // directory of the package relative to the modroot // Source files sourceFiles []*sourceFile } +var errCannotFindPackage = errors.New("cannot find package") + // indexPackage returns an indexPackage constructed using the information in the ModuleIndex. func (mi *ModuleIndex) indexPackage(path string) *indexPackage { defer func() { @@ -572,13 +654,15 @@ func (mi *ModuleIndex) indexPackage(path string) *indexPackage { }() offset, ok := mi.packages[path] if !ok { - return &indexPackage{error: fmt.Sprintf("cannot find package %q in:\n\t%s", path, filepath.Join(mi.modroot, path))} + return &indexPackage{error: fmt.Errorf("%w %q in:\n\t%s", errCannotFindPackage, path, filepath.Join(mi.modroot, path))} } // TODO(matloob): do we want to lock on the module index? d := mi.od.decoderAt(offset) rp := new(indexPackage) - rp.error = d.string() + if errstr := d.string(); errstr != "" { + rp.error = errors.New(errstr) + } rp.dir = d.string() numSourceFiles := d.uint32() rp.sourceFiles = make([]*sourceFile, numSourceFiles) diff --git a/src/cmd/go/internal/modload/build.go b/src/cmd/go/internal/modload/build.go index 7b1bc732fc..0799fec35c 100644 --- a/src/cmd/go/internal/modload/build.go +++ b/src/cmd/go/internal/modload/build.go @@ -9,7 +9,6 @@ import ( "encoding/hex" "errors" "fmt" - "internal/goroot" "io/fs" "os" "path/filepath" @@ -18,6 +17,7 @@ import ( "cmd/go/internal/base" "cmd/go/internal/cfg" "cmd/go/internal/modfetch" + "cmd/go/internal/modindex" "cmd/go/internal/modinfo" "cmd/go/internal/search" @@ -39,7 +39,7 @@ func findStandardImportPath(path string) string { panic("findStandardImportPath called with empty path") } if search.IsStandardImportPath(path) { - if goroot.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, path) { + if modindex.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, path) { return filepath.Join(cfg.GOROOT, "src", path) } } diff --git a/src/cmd/go/internal/modload/import.go b/src/cmd/go/internal/modload/import.go index 22286e5e2d..f7810ca5c6 100644 --- a/src/cmd/go/internal/modload/import.go +++ b/src/cmd/go/internal/modload/import.go @@ -9,7 +9,6 @@ import ( "errors" "fmt" "go/build" - "internal/goroot" "io/fs" "os" pathpkg "path" @@ -281,7 +280,7 @@ func importFromModules(ctx context.Context, path string, rs *Requirements, mg *M // Is the package in the standard library? pathIsStd := search.IsStandardImportPath(path) - if pathIsStd && goroot.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, path) { + if pathIsStd && modindex.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, path) { for _, mainModule := range MainModules.Versions() { if MainModules.InGorootSrc(mainModule) { if dir, ok, err := dirInModule(path, MainModules.PathPrefix(mainModule), MainModules.ModRoot(mainModule), true); err != nil { -- cgit v1.3-5-g9baa From 66cbf67345b1631adbee2109f5bb78fb4e321144 Mon Sep 17 00:00:00 2001 From: Stephen Eckels Date: Sat, 4 Jun 2022 20:39:36 +0000 Subject: cmd/buildid: reject rewriting legacy buildids This resolves legacy go binaries crashing the buildid tool when the -w flag is specified. Fixes #50809 Change-Id: I55a866f285a3c2cebcf2cdbb9cc30e5078e1d18f GitHub-Last-Rev: 7169a58fd7ba17fdeb8037cef3f50080169dc137 GitHub-Pull-Request: golang/go#53163 Reviewed-on: https://go-review.googlesource.com/c/go/+/409535 Run-TryBot: Ian Lance Taylor Auto-Submit: Ian Lance Taylor TryBot-Result: Gopher Robot Reviewed-by: Ian Lance Taylor Reviewed-by: Dmitri Shuralyov --- src/cmd/buildid/buildid.go | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/cmd') diff --git a/src/cmd/buildid/buildid.go b/src/cmd/buildid/buildid.go index 8e02a7ae10..72ad80dbbb 100644 --- a/src/cmd/buildid/buildid.go +++ b/src/cmd/buildid/buildid.go @@ -53,6 +53,11 @@ func main() { log.Fatal(err) } + // <= go 1.7 doesn't embed the contentID or actionID, so no slash is present + if !strings.Contains(id, "/") { + log.Fatalf("%s: build ID is a legacy format...binary too old for this tool", file) + } + newID := id[:strings.LastIndex(id, "/")] + "/" + buildid.HashToString(hash) if len(newID) != len(id) { log.Fatalf("%s: build ID length mismatch %q vs %q", file, id, newID) -- cgit v1.3-5-g9baa From 846f971daa03fda149a3d66a3fda6eb6a2a7484e Mon Sep 17 00:00:00 2001 From: Robert Findley Date: Sun, 15 May 2022 19:31:42 -0400 Subject: go/types, types2: remove Named.once in favor of monotonic state Introduce a monotonic state variable to track the lifecycle of a named type, replacing the existing sync.Once. Having a single guard for the state of underlying and methods will allow for cleaning-up when the type is fully expanded. In the future, this state may also be used for detecting access to information such as underlying or methods before the type is fully set-up, though that will require rethinking our type-checking of invalid cyclic types. Also remove support for type-type inference. If we ever support this feature in the future (inference of missing type arguments for named type instances), it will likely involve additional machinery that does not yet exist. Remove the current partial support to simplify our internal APIs. In particular, this means that Named.resolver is only used for lazy loading. As a result, we can revert the lazy loader signature to its previous form. A lot of exposition is added for how Named types work. Along the way, the terminology we use to describe them is refined. Some microbenchmarks are added that were useful in evaluating the tradeoffs between synchronization mechanisms. Updates #52728 Change-Id: I4e147360bc6e5d8cd4f37e32e86fece0530a6480 Reviewed-on: https://go-review.googlesource.com/c/go/+/404875 Run-TryBot: Robert Findley Reviewed-by: Robert Griesemer TryBot-Result: Gopher Robot --- src/cmd/compile/internal/types2/api_test.go | 2 +- src/cmd/compile/internal/types2/decl.go | 4 +- src/cmd/compile/internal/types2/instantiate.go | 3 - src/cmd/compile/internal/types2/named.go | 289 ++++++++++++++++++------- src/cmd/compile/internal/types2/named_test.go | 75 +++++++ src/cmd/compile/internal/types2/object.go | 14 +- src/cmd/compile/internal/types2/typexpr.go | 48 +--- src/go/types/api_test.go | 2 +- src/go/types/decl.go | 4 +- src/go/types/instantiate.go | 3 - src/go/types/named.go | 289 ++++++++++++++++++------- src/go/types/named_test.go | 88 ++++++++ src/go/types/object.go | 14 +- src/go/types/typexpr.go | 53 +---- 14 files changed, 609 insertions(+), 279 deletions(-) create mode 100644 src/cmd/compile/internal/types2/named_test.go create mode 100644 src/go/types/named_test.go (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/types2/api_test.go b/src/cmd/compile/internal/types2/api_test.go index e6de955a6e..f5526bb25a 100644 --- a/src/cmd/compile/internal/types2/api_test.go +++ b/src/cmd/compile/internal/types2/api_test.go @@ -38,7 +38,7 @@ func pkgFor(path, source string, info *Info) (*Package, error) { return conf.Check(f.PkgName.Value, []*syntax.File{f}, info) } -func mustTypecheck(t *testing.T, path, source string, info *Info) string { +func mustTypecheck(t testing.TB, path, source string, info *Info) string { pkg, err := pkgFor(path, source, info) if err != nil { name := path diff --git a/src/cmd/compile/internal/types2/decl.go b/src/cmd/compile/internal/types2/decl.go index b6f81aa8a5..008d3698b7 100644 --- a/src/cmd/compile/internal/types2/decl.go +++ b/src/cmd/compile/internal/types2/decl.go @@ -522,8 +522,8 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *syntax.TypeDecl, def *Named assert(rhs != nil) named.fromRHS = rhs - // If the underlying was not set while type-checking the right-hand side, it - // is invalid and an error should have been reported elsewhere. + // If the underlying type was not set while type-checking the right-hand + // side, it is invalid and an error should have been reported elsewhere. if named.underlying == nil { named.underlying = Typ[Invalid] } diff --git a/src/cmd/compile/internal/types2/instantiate.go b/src/cmd/compile/internal/types2/instantiate.go index bb90ab3736..44ed0319c8 100644 --- a/src/cmd/compile/internal/types2/instantiate.go +++ b/src/cmd/compile/internal/types2/instantiate.go @@ -79,9 +79,6 @@ func (check *Checker) instance(pos syntax.Pos, orig Type, targs []Type, ctxt *Co tname := NewTypeName(pos, orig.obj.pkg, orig.obj.name, nil) named := check.newNamed(tname, orig, nil, nil) // underlying, tparams, and methods are set when named is resolved named.targs = newTypeList(targs) - named.resolver = func(ctxt *Context, n *Named) (*TypeParamList, Type, *methodList) { - return expandNamed(ctxt, n, pos) - } res = named case *Signature: diff --git a/src/cmd/compile/internal/types2/named.go b/src/cmd/compile/internal/types2/named.go index 849398a6f4..0a2b2aa6b1 100644 --- a/src/cmd/compile/internal/types2/named.go +++ b/src/cmd/compile/internal/types2/named.go @@ -5,32 +5,116 @@ package types2 import ( - "cmd/compile/internal/syntax" "sync" + "sync/atomic" ) +// Type-checking Named types is subtle, because they may be recursively +// defined, and because their full details may be spread across multiple +// declarations (via methods). For this reason they are type-checked lazily, +// to avoid information being accessed before it is complete. +// +// Conceptually, it is helpful to think of named types as having two distinct +// sets of information: +// - "LHS" information, defining their identity: Obj() and TypeArgs() +// - "RHS" information, defining their details: TypeParams(), Underlying(), +// and methods. +// +// In this taxonomy, LHS information is available immediately, but RHS +// information is lazy. Specifically, a named type N may be constructed in any +// of the following ways: +// 1. type-checked from the source +// 2. loaded eagerly from export data +// 3. loaded lazily from export data (when using unified IR) +// 4. instantiated from a generic type +// +// In cases 1, 3, and 4, it is possible that the underlying type or methods of +// N may not be immediately available. +// - During type-checking, we allocate N before type-checking its underlying +// type or methods, so that we may resolve recursive references. +// - When loading from export data, we may load its methods and underlying +// type lazily using a provided load function. +// - After instantiating, we lazily expand the underlying type and methods +// (note that instances may be created while still in the process of +// type-checking the original type declaration). +// +// In cases 3 and 4 this lazy construction may also occur concurrently, due to +// concurrent use of the type checker API (after type checking or importing has +// finished). It is critical that we keep track of state, so that Named types +// are constructed exactly once and so that we do not access their details too +// soon. +// +// We achieve this by tracking state with an atomic state variable, and +// guarding potentially concurrent calculations with a mutex. At any point in +// time this state variable determines which data on N may be accessed. As +// state monotonically progresses, any data available at state M may be +// accessed without acquiring the mutex at state N, provided N >= M. +// +// GLOSSARY: Here are a few terms used in this file to describe Named types: +// - We say that a Named type is "instantiated" if it has been constructed by +// instantiating a generic named type with type arguments. +// - We say that a Named type is "declared" if it corresponds to a type +// declaration in the source. Instantiated named types correspond to a type +// instantiation in the source, not a declaration. But their Origin type is +// a declared type. +// - We say that a Named type is "resolved" if its RHS information has been +// loaded or fully type-checked. For Named types constructed from export +// data, this may involve invoking a loader function to extract information +// from export data. For instantiated named types this involves reading +// information from their origin. +// - We say that a Named type is "expanded" if it is an instantiated type and +// type parameters in its underlying type and methods have been substituted +// with the type arguments from the instantiation. A type may be partially +// expanded if some but not all of these details have been substituted. +// Similarly, we refer to these individual details (underlying type or +// method) as being "expanded". +// - When all information is known for a named type, we say it is "complete". +// +// Some invariants to keep in mind: each declared Named type has a single +// corresponding object, and that object's type is the (possibly generic) Named +// type. Declared Named types are identical if and only if their pointers are +// identical. On the other hand, multiple instantiated Named types may be +// identical even though their pointers are not identical. One has to use +// Identical to compare them. For instantiated named types, their obj is a +// synthetic placeholder that records their position of the corresponding +// instantiation in the source (if they were constructed during type checking). + // A Named represents a named (defined) type. type Named struct { - check *Checker - obj *TypeName // corresponding declared object for declared types; placeholder for instantiated types - orig *Named // original, uninstantiated type - fromRHS Type // type (on RHS of declaration) this *Named type is derived from (for cycle reporting) + check *Checker // non-nil during type-checking; nil otherwise + obj *TypeName // corresponding declared object for declared types; see above for instantiated types + orig *Named // origin type for instantiated types, this type for declared types + targs *TypeList // type arguments (after instantiation), or nil + + // fromRHS holds the type (on RHS of declaration) this *Named type is derived + // from (for cycle reporting). Only used by validType, and therefore does not + // require synchronization. + fromRHS Type + + mu sync.Mutex // guards all fields below + state_ uint32 // the current state of this type; must only be accessed atomically underlying Type // possibly a *Named during setup; never a *Named once set up completely tparams *TypeParamList // type parameters, or nil - targs *TypeList // type arguments (after instantiation), or nil // methods declared for this type (not the method set of this type). // Signatures are type-checked lazily. // For non-instantiated types, this is a fully populated list of methods. For - // instantiated types, this is a 'lazy' list, and methods are instantiated - // when they are first accessed. + // instantiated types, this is a 'lazy' list, and methods are individually + // expanded when they are first accessed. methods *methodList - // resolver may be provided to lazily resolve type parameters, underlying, and methods. - resolver func(*Context, *Named) (tparams *TypeParamList, underlying Type, methods *methodList) - once sync.Once // ensures that tparams, underlying, and methods are resolved before accessing + // loader may be provided to lazily load type parameters, underlying, and methods. + loader func(*Named) (tparams []*TypeParam, underlying Type, methods []*Func) } +// namedState represents the possible states that a named type may assume. +type namedState uint32 + +const ( + unresolved namedState = iota // tparams, underlying type and methods might be unavailable + resolved +) + // NewNamed returns a new named type for the given type name, underlying type, and associated methods. // If the given type name obj doesn't have a type yet, its type is set to the returned named type. // The underlying type must not be a *Named. @@ -41,24 +125,74 @@ func NewNamed(obj *TypeName, underlying Type, methods []*Func) *Named { return (*Checker)(nil).newNamed(obj, nil, underlying, newMethodList(methods)) } -func (t *Named) resolve(ctxt *Context) *Named { - if t.resolver == nil { - return t +// resolve resolves the type parameters, methods, and underlying type of n. +// This information may be loaded from a provided loader function, or computed +// from an origin type (in the case of instances). +// +// After resolution, the type parameters, methods, and underlying type of n are +// accessible; but if n is an instantiated type, its methods may still be +// unexpanded. +func (n *Named) resolve(ctxt *Context) *Named { + if n.state() >= resolved { // avoid locking below + return n } - t.once.Do(func() { - // TODO(mdempsky): Since we're passing t to the resolver anyway - // (necessary because types2 expects the receiver type for methods - // on defined interface types to be the Named rather than the - // underlying Interface), maybe it should just handle calling - // SetTypeParams, SetUnderlying, and AddMethod instead? Those - // methods would need to support reentrant calls though. It would - // also make the API more future-proof towards further extensions - // (like SetTypeParams). - t.tparams, t.underlying, t.methods = t.resolver(ctxt, t) - t.fromRHS = t.underlying // for cycle detection - }) - return t + // TODO(rfindley): if n.check is non-nil we can avoid locking here, since + // type-checking is not concurrent. Evaluate if this is worth doing. + n.mu.Lock() + defer n.mu.Unlock() + + if n.state() >= resolved { + return n + } + + if n.TypeArgs().Len() > 0 { + assert(n.underlying == nil) // n is an unresolved instance + assert(n.loader == nil) // instances are created by instantiation, in which case n.loader is nil + n.orig.resolve(ctxt) + + underlying := n.expandUnderlying(ctxt) + + n.tparams = n.orig.tparams + n.underlying = underlying + n.fromRHS = n.orig.fromRHS // for cycle detection + n.methods = newLazyMethodList(n.orig.methods.Len()) + n.setState(resolved) + return n + } + + // TODO(mdempsky): Since we're passing n to the loader anyway + // (necessary because types2 expects the receiver type for methods + // on defined interface types to be the Named rather than the + // underlying Interface), maybe it should just handle calling + // SetTypeParams, SetUnderlying, and AddMethod instead? Those + // methods would need to support reentrant calls though. It would + // also make the API more future-proof towards further extensions. + if n.loader != nil { + assert(n.underlying == nil) + + tparams, underlying, methods := n.loader(n) + + n.tparams = bindTParams(tparams) + n.underlying = underlying + n.fromRHS = underlying // for cycle detection + n.methods = newMethodList(methods) + n.loader = nil + } + + n.setState(resolved) + return n +} + +// state atomically accesses the current state of the receiver. +func (n *Named) state() namedState { + return namedState(atomic.LoadUint32(&n.state_)) +} + +// setState atomically stores the given state for n. +// Must only be called while holding n.mu. +func (n *Named) setState(state namedState) { + atomic.StoreUint32(&n.state_, uint32(state)) } // newNamed is like NewNamed but with a *Checker receiver and additional orig argument. @@ -80,16 +214,16 @@ func (check *Checker) newNamed(obj *TypeName, orig *Named, underlying Type, meth func (t *Named) cleanup() { assert(t.orig.orig == t.orig) // Ensure that every defined type created in the course of type-checking has - // either non-*Named underlying, or is unresolved. + // either non-*Named underlying type, or is unexpanded. // - // This guarantees that we don't leak any types whose underlying is *Named, - // because any unresolved instances will lazily compute their underlying by - // substituting in the underlying of their origin. The origin must have - // either been imported or type-checked and expanded here, and in either case - // its underlying will be fully expanded. + // This guarantees that we don't leak any types whose underlying type is + // *Named, because any unexpanded instances will lazily compute their + // underlying type by substituting in the underlying type of their origin. + // The origin must have either been imported or type-checked and expanded + // here, and in either case its underlying type will be fully expanded. switch t.underlying.(type) { case nil: - if t.resolver == nil { + if t.TypeArgs().Len() == 0 { panic("nil underlying") } case *Named: @@ -134,12 +268,13 @@ func (t *Named) NumMethods() int { return t.resolve(nil).methods.Len() } func (t *Named) Method(i int) *Func { t.resolve(nil) return t.methods.At(i, func() *Func { - return t.instantiateMethod(i) + return t.expandMethod(i) }) } -// instantiateMethod instantiates the i'th method for an instantiated receiver. -func (t *Named) instantiateMethod(i int) *Func { +// expandMethod substitutes type arguments in the i'th method for an +// instantiated receiver. +func (t *Named) expandMethod(i int) *Func { assert(t.TypeArgs().Len() > 0) // t must be an instance // t.orig.methods is not lazy. origm is the method instantiated with its @@ -275,7 +410,7 @@ func (n0 *Named) under() Type { check := n0.check n := n0 - seen := make(map[*Named]int) // types that need their underlying resolved + seen := make(map[*Named]int) // types that need their underlying type resolved var path []Object // objects encountered, for cycle reporting loop: @@ -352,66 +487,64 @@ func (check *Checker) bestContext(ctxt *Context) *Context { return NewContext() } -// expandNamed ensures that the underlying type of n is instantiated. -// The underlying type will be Typ[Invalid] if there was an error. -func expandNamed(ctxt *Context, n *Named, instPos syntax.Pos) (tparams *TypeParamList, underlying Type, methods *methodList) { +// expandUnderlying substitutes type arguments in the underlying type n.orig, +// returning the result. Returns Typ[Invalid] if there was an error. +func (n *Named) expandUnderlying(ctxt *Context) Type { check := n.check if check != nil && check.conf.Trace { - check.trace(instPos, "-- expandNamed %s", n) + check.trace(n.obj.pos, "-- Named.expandUnderlying %s", n) check.indent++ defer func() { check.indent-- - check.trace(instPos, "=> %s (tparams = %s, under = %s)", n, tparams.list(), underlying) + check.trace(n.obj.pos, "=> %s (tparams = %s, under = %s)", n, n.tparams.list(), n.underlying) }() } - n.orig.resolve(ctxt) assert(n.orig.underlying != nil) if _, unexpanded := n.orig.underlying.(*Named); unexpanded { - // We should only get an unexpanded underlying here during type checking + // We should only get a Named underlying type here during type checking // (for example, in recursive type declarations). assert(check != nil) } - // Mismatching arg and tparam length may be checked elsewhere. - if n.orig.tparams.Len() == n.targs.Len() { - // We must always have a context, to avoid infinite recursion. - ctxt = check.bestContext(ctxt) - h := ctxt.instanceHash(n.orig, n.targs.list()) - // ensure that an instance is recorded for h to avoid infinite recursion. - ctxt.update(h, n.orig, n.TypeArgs().list(), n) - - smap := makeSubstMap(n.orig.tparams.list(), n.targs.list()) - underlying = n.check.subst(instPos, n.orig.underlying, smap, ctxt) - // If the underlying of n is an interface, we need to set the receiver of - // its methods accurately -- we set the receiver of interface methods on - // the RHS of a type declaration to the defined type. - if iface, _ := underlying.(*Interface); iface != nil { - if methods, copied := replaceRecvType(iface.methods, n.orig, n); copied { - // If the underlying doesn't actually use type parameters, it's possible - // that it wasn't substituted. In this case we need to create a new - // *Interface before modifying receivers. - if iface == n.orig.underlying { - old := iface - iface = check.newInterface() - iface.embeddeds = old.embeddeds - iface.complete = old.complete - iface.implicit = old.implicit // should be false but be conservative - underlying = iface - } - iface.methods = methods + if n.orig.tparams.Len() != n.targs.Len() { + // Mismatching arg and tparam length may be checked elsewhere. + return Typ[Invalid] + } + + // We must always have a context, to avoid infinite recursion. + ctxt = check.bestContext(ctxt) + h := ctxt.instanceHash(n.orig, n.targs.list()) + // ensure that an instance is recorded for h to avoid infinite recursion. + ctxt.update(h, n.orig, n.TypeArgs().list(), n) + + smap := makeSubstMap(n.orig.tparams.list(), n.targs.list()) + underlying := n.check.subst(n.obj.pos, n.orig.underlying, smap, ctxt) + // If the underlying type of n is an interface, we need to set the receiver + // of its methods accurately -- we set the receiver of interface methods on + // the RHS of a type declaration to the defined type. + if iface, _ := underlying.(*Interface); iface != nil { + if methods, copied := replaceRecvType(iface.methods, n.orig, n); copied { + // If the underlying type doesn't actually use type parameters, it's + // possible that it wasn't substituted. In this case we need to create + // a new *Interface before modifying receivers. + if iface == n.orig.underlying { + old := iface + iface = check.newInterface() + iface.embeddeds = old.embeddeds + iface.complete = old.complete + iface.implicit = old.implicit // should be false but be conservative + underlying = iface } + iface.methods = methods } - } else { - underlying = Typ[Invalid] } - - return n.orig.tparams, underlying, newLazyMethodList(n.orig.methods.Len()) + return underlying } -// safeUnderlying returns the underlying of typ without expanding instances, to -// avoid infinite recursion. +// safeUnderlying returns the underlying type of typ without expanding +// instances, to avoid infinite recursion. // // TODO(rfindley): eliminate this function or give it a better name. func safeUnderlying(typ Type) Type { diff --git a/src/cmd/compile/internal/types2/named_test.go b/src/cmd/compile/internal/types2/named_test.go new file mode 100644 index 0000000000..14a982048a --- /dev/null +++ b/src/cmd/compile/internal/types2/named_test.go @@ -0,0 +1,75 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package types2_test + +import ( + "testing" + + . "cmd/compile/internal/types2" +) + +func BenchmarkNamed(b *testing.B) { + const src = ` +package p + +type T struct { + P int +} + +func (T) M(int) {} +func (T) N() (i int) { return } + +type G[P any] struct { + F P +} + +func (G[P]) M(P) {} +func (G[P]) N() (p P) { return } + +type Inst = G[int] + ` + pkg, err := pkgFor("p", src, nil) + if err != nil { + b.Fatal(err) + } + + var ( + T = pkg.Scope().Lookup("T").Type() + G = pkg.Scope().Lookup("G").Type() + SrcInst = pkg.Scope().Lookup("Inst").Type() + UserInst = mustInstantiate(b, G, Typ[Int]) + ) + + tests := []struct { + name string + typ Type + }{ + {"nongeneric", T}, + {"generic", G}, + {"src instance", SrcInst}, + {"user instance", UserInst}, + } + + b.Run("Underlying", func(b *testing.B) { + for _, test := range tests { + b.Run(test.name, func(b *testing.B) { + // Access underlying once, to trigger any lazy calculation. + _ = test.typ.Underlying() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = test.typ.Underlying() + } + }) + } + }) +} + +func mustInstantiate(tb testing.TB, orig Type, targs ...Type) Type { + inst, err := Instantiate(nil, orig, targs, true) + if err != nil { + tb.Fatal(err) + } + return inst +} diff --git a/src/cmd/compile/internal/types2/object.go b/src/cmd/compile/internal/types2/object.go index 75f7ea5b12..df080f071c 100644 --- a/src/cmd/compile/internal/types2/object.go +++ b/src/cmd/compile/internal/types2/object.go @@ -279,19 +279,7 @@ func NewTypeName(pos syntax.Pos, pkg *Package, name string, typ Type) *TypeName // lazily calls resolve to finish constructing the Named object. func NewTypeNameLazy(pos syntax.Pos, pkg *Package, name string, load func(named *Named) (tparams []*TypeParam, underlying Type, methods []*Func)) *TypeName { obj := NewTypeName(pos, pkg, name, nil) - - resolve := func(_ *Context, t *Named) (*TypeParamList, Type, *methodList) { - tparams, underlying, methods := load(t) - - switch underlying.(type) { - case nil, *Named: - panic(fmt.Sprintf("invalid underlying type %T", t.underlying)) - } - - return bindTParams(tparams), underlying, newMethodList(methods) - } - - NewNamed(obj, nil, nil).resolver = resolve + NewNamed(obj, nil, nil).loader = load return obj } diff --git a/src/cmd/compile/internal/types2/typexpr.go b/src/cmd/compile/internal/types2/typexpr.go index 1f8b40dba6..020653332d 100644 --- a/src/cmd/compile/internal/types2/typexpr.go +++ b/src/cmd/compile/internal/types2/typexpr.go @@ -432,57 +432,20 @@ func (check *Checker) instantiatedType(x syntax.Expr, xlist []syntax.Expr, def * return Typ[Invalid] } - // enableTypeTypeInference controls whether to infer missing type arguments - // using constraint type inference. See issue #51527. - const enableTypeTypeInference = false - // create the instance ctxt := check.bestContext(nil) - h := ctxt.instanceHash(orig, targs) - // targs may be incomplete, and require inference. In any case we should de-duplicate. - inst, _ := ctxt.lookup(h, orig, targs).(*Named) - // If inst is non-nil, we can't just return here. Inst may have been - // constructed via recursive substitution, in which case we wouldn't do the - // validation below. Ensure that the validation (and resulting errors) runs - // for each instantiated type in the source. - if inst == nil { - // x may be a selector for an imported type; use its start pos rather than x.Pos(). - tname := NewTypeName(syntax.StartPos(x), orig.obj.pkg, orig.obj.name, nil) - inst = check.newNamed(tname, orig, nil, nil) // underlying, methods and tparams are set when named is resolved - inst.targs = newTypeList(targs) - inst = ctxt.update(h, orig, targs, inst).(*Named) - } + inst := check.instance(x.Pos(), orig, targs, ctxt).(*Named) def.setUnderlying(inst) - inst.resolver = func(ctxt *Context, n *Named) (*TypeParamList, Type, *methodList) { - tparams := n.orig.TypeParams().list() - - targs := n.targs.list() - if enableTypeTypeInference && len(targs) < len(tparams) { - // If inference fails, len(inferred) will be 0, and inst.underlying will - // be set to Typ[Invalid] in expandNamed. - inferred := check.infer(x.Pos(), tparams, targs, nil, nil) - if len(inferred) > len(targs) { - n.targs = newTypeList(inferred) - } - } - - return expandNamed(ctxt, n, x.Pos()) - } - // orig.tparams may not be set up, so we need to do expansion later. check.later(func() { // This is an instance from the source, not from recursive substitution, // and so it must be resolved during type-checking so that we can report // errors. - inst.resolve(ctxt) - // Since check is non-nil, we can still mutate inst. Unpinning the resolver - // frees some memory. - inst.resolver = nil check.recordInstance(x, inst.TypeArgs().list(), inst) - if check.validateTArgLen(x.Pos(), inst.tparams.Len(), inst.targs.Len()) { - if i, err := check.verify(x.Pos(), inst.tparams.list(), inst.targs.list()); err != nil { + if check.validateTArgLen(x.Pos(), inst.TypeParams().Len(), inst.targs.Len()) { + if i, err := check.verify(x.Pos(), inst.TypeParams().list(), inst.targs.list()); err != nil { // best position for error reporting pos := x.Pos() if i < len(xlist) { @@ -490,10 +453,13 @@ func (check *Checker) instantiatedType(x syntax.Expr, xlist []syntax.Expr, def * } check.softErrorf(pos, "%s", err) } else { - check.mono.recordInstance(check.pkg, x.Pos(), inst.tparams.list(), inst.targs.list(), xlist) + check.mono.recordInstance(check.pkg, x.Pos(), inst.TypeParams().list(), inst.targs.list(), xlist) } } + // TODO(rfindley): remove this call: we don't need to call validType here, + // as cycles can only occur for types used inside a Named type declaration, + // and so it suffices to call validType from declared types. check.validType(inst) }).describef(x, "resolve instance %s", inst) diff --git a/src/go/types/api_test.go b/src/go/types/api_test.go index eb17f9280d..db2ace5feb 100644 --- a/src/go/types/api_test.go +++ b/src/go/types/api_test.go @@ -43,7 +43,7 @@ func pkgForMode(path, source string, info *Info, mode parser.Mode) (*Package, er return conf.Check(f.Name.Name, fset, []*ast.File{f}, info) } -func mustTypecheck(t *testing.T, path, source string, info *Info) string { +func mustTypecheck(t testing.TB, path, source string, info *Info) string { pkg, err := pkgFor(path, source, info) if err != nil { name := path diff --git a/src/go/types/decl.go b/src/go/types/decl.go index 123d296791..61c9696948 100644 --- a/src/go/types/decl.go +++ b/src/go/types/decl.go @@ -579,8 +579,8 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *ast.TypeSpec, def *Named) { assert(rhs != nil) named.fromRHS = rhs - // If the underlying was not set while type-checking the right-hand side, it - // is invalid and an error should have been reported elsewhere. + // If the underlying type was not set while type-checking the right-hand + // side, it is invalid and an error should have been reported elsewhere. if named.underlying == nil { named.underlying = Typ[Invalid] } diff --git a/src/go/types/instantiate.go b/src/go/types/instantiate.go index 964a4f907c..8be0eab407 100644 --- a/src/go/types/instantiate.go +++ b/src/go/types/instantiate.go @@ -79,9 +79,6 @@ func (check *Checker) instance(pos token.Pos, orig Type, targs []Type, ctxt *Con tname := NewTypeName(pos, orig.obj.pkg, orig.obj.name, nil) named := check.newNamed(tname, orig, nil, nil) // underlying, tparams, and methods are set when named is resolved named.targs = newTypeList(targs) - named.resolver = func(ctxt *Context, n *Named) (*TypeParamList, Type, *methodList) { - return expandNamed(ctxt, n, pos) - } res = named case *Signature: diff --git a/src/go/types/named.go b/src/go/types/named.go index a82679eb10..bfb4a11da7 100644 --- a/src/go/types/named.go +++ b/src/go/types/named.go @@ -5,32 +5,116 @@ package types import ( - "go/token" "sync" + "sync/atomic" ) +// Type-checking Named types is subtle, because they may be recursively +// defined, and because their full details may be spread across multiple +// declarations (via methods). For this reason they are type-checked lazily, +// to avoid information being accessed before it is complete. +// +// Conceptually, it is helpful to think of named types as having two distinct +// sets of information: +// - "LHS" information, defining their identity: Obj() and TypeArgs() +// - "RHS" information, defining their details: TypeParams(), Underlying(), +// and methods. +// +// In this taxonomy, LHS information is available immediately, but RHS +// information is lazy. Specifically, a named type N may be constructed in any +// of the following ways: +// 1. type-checked from the source +// 2. loaded eagerly from export data +// 3. loaded lazily from export data (when using unified IR) +// 4. instantiated from a generic type +// +// In cases 1, 3, and 4, it is possible that the underlying type or methods of +// N may not be immediately available. +// - During type-checking, we allocate N before type-checking its underlying +// type or methods, so that we may resolve recursive references. +// - When loading from export data, we may load its methods and underlying +// type lazily using a provided load function. +// - After instantiating, we lazily expand the underlying type and methods +// (note that instances may be created while still in the process of +// type-checking the original type declaration). +// +// In cases 3 and 4 this lazy construction may also occur concurrently, due to +// concurrent use of the type checker API (after type checking or importing has +// finished). It is critical that we keep track of state, so that Named types +// are constructed exactly once and so that we do not access their details too +// soon. +// +// We achieve this by tracking state with an atomic state variable, and +// guarding potentially concurrent calculations with a mutex. At any point in +// time this state variable determines which data on N may be accessed. As +// state monotonically progresses, any data available at state M may be +// accessed without acquiring the mutex at state N, provided N >= M. +// +// GLOSSARY: Here are a few terms used in this file to describe Named types: +// - We say that a Named type is "instantiated" if it has been constructed by +// instantiating a generic named type with type arguments. +// - We say that a Named type is "declared" if it corresponds to a type +// declaration in the source. Instantiated named types correspond to a type +// instantiation in the source, not a declaration. But their Origin type is +// a declared type. +// - We say that a Named type is "resolved" if its RHS information has been +// loaded or fully type-checked. For Named types constructed from export +// data, this may involve invoking a loader function to extract information +// from export data. For instantiated named types this involves reading +// information from their origin. +// - We say that a Named type is "expanded" if it is an instantiated type and +// type parameters in its underlying type and methods have been substituted +// with the type arguments from the instantiation. A type may be partially +// expanded if some but not all of these details have been substituted. +// Similarly, we refer to these individual details (underlying type or +// method) as being "expanded". +// - When all information is known for a named type, we say it is "complete". +// +// Some invariants to keep in mind: each declared Named type has a single +// corresponding object, and that object's type is the (possibly generic) Named +// type. Declared Named types are identical if and only if their pointers are +// identical. On the other hand, multiple instantiated Named types may be +// identical even though their pointers are not identical. One has to use +// Identical to compare them. For instantiated named types, their obj is a +// synthetic placeholder that records their position of the corresponding +// instantiation in the source (if they were constructed during type checking). + // A Named represents a named (defined) type. type Named struct { - check *Checker - obj *TypeName // corresponding declared object for declared types; placeholder for instantiated types - orig *Named // original, uninstantiated type - fromRHS Type // type (on RHS of declaration) this *Named type is derived of (for cycle reporting) + check *Checker // non-nil during type-checking; nil otherwise + obj *TypeName // corresponding declared object for declared types; see above for instantiated types + orig *Named // origin type for instantiated types, this type for declared types + targs *TypeList // type arguments (after instantiation), or nil + + // fromRHS holds the type (on RHS of declaration) this *Named type is derived + // from (for cycle reporting). Only used by validType, and therefore does not + // require synchronization. + fromRHS Type + + mu sync.Mutex // guards all fields below + state_ uint32 // the current state of this type; must only be accessed atomically underlying Type // possibly a *Named during setup; never a *Named once set up completely tparams *TypeParamList // type parameters, or nil - targs *TypeList // type arguments (after instantiation), or nil // methods declared for this type (not the method set of this type). // Signatures are type-checked lazily. // For non-instantiated types, this is a fully populated list of methods. For - // instantiated types, this is a 'lazy' list, and methods are instantiated - // when they are first accessed. + // instantiated types, this is a 'lazy' list, and methods are individually + // expanded when they are first accessed. methods *methodList - // resolver may be provided to lazily resolve type parameters, underlying, and methods. - resolver func(*Context, *Named) (tparams *TypeParamList, underlying Type, methods *methodList) - once sync.Once // ensures that tparams, underlying, and methods are resolved before accessing + // loader may be provided to lazily load type parameters, underlying, and methods. + loader func(*Named) (tparams []*TypeParam, underlying Type, methods []*Func) } +// namedState represents the possible states that a named type may assume. +type namedState uint32 + +const ( + unresolved namedState = iota // tparams, underlying type and methods might be unavailable + resolved +) + // NewNamed returns a new named type for the given type name, underlying type, and associated methods. // If the given type name obj doesn't have a type yet, its type is set to the returned named type. // The underlying type must not be a *Named. @@ -41,24 +125,74 @@ func NewNamed(obj *TypeName, underlying Type, methods []*Func) *Named { return (*Checker)(nil).newNamed(obj, nil, underlying, newMethodList(methods)) } -func (t *Named) resolve(ctxt *Context) *Named { - if t.resolver == nil { - return t +// resolve resolves the type parameters, methods, and underlying type of n. +// This information may be loaded from a provided loader function, or computed +// from an origin type (in the case of instances). +// +// After resolution, the type parameters, methods, and underlying type of n are +// accessible; but if n is an instantiated type, its methods may still be +// unexpanded. +func (n *Named) resolve(ctxt *Context) *Named { + if n.state() >= resolved { // avoid locking below + return n } - t.once.Do(func() { - // TODO(mdempsky): Since we're passing t to the resolver anyway - // (necessary because types2 expects the receiver type for methods - // on defined interface types to be the Named rather than the - // underlying Interface), maybe it should just handle calling - // SetTypeParams, SetUnderlying, and AddMethod instead? Those - // methods would need to support reentrant calls though. It would - // also make the API more future-proof towards further extensions - // (like SetTypeParams). - t.tparams, t.underlying, t.methods = t.resolver(ctxt, t) - t.fromRHS = t.underlying // for cycle detection - }) - return t + // TODO(rfindley): if n.check is non-nil we can avoid locking here, since + // type-checking is not concurrent. Evaluate if this is worth doing. + n.mu.Lock() + defer n.mu.Unlock() + + if n.state() >= resolved { + return n + } + + if n.TypeArgs().Len() > 0 { + assert(n.underlying == nil) // n is an unresolved instance + assert(n.loader == nil) // instances are created by instantiation, in which case n.loader is nil + n.orig.resolve(ctxt) + + underlying := n.expandUnderlying(ctxt) + + n.tparams = n.orig.tparams + n.underlying = underlying + n.fromRHS = n.orig.fromRHS // for cycle detection + n.methods = newLazyMethodList(n.orig.methods.Len()) + n.setState(resolved) + return n + } + + // TODO(mdempsky): Since we're passing n to the loader anyway + // (necessary because types2 expects the receiver type for methods + // on defined interface types to be the Named rather than the + // underlying Interface), maybe it should just handle calling + // SetTypeParams, SetUnderlying, and AddMethod instead? Those + // methods would need to support reentrant calls though. It would + // also make the API more future-proof towards further extensions. + if n.loader != nil { + assert(n.underlying == nil) + + tparams, underlying, methods := n.loader(n) + + n.tparams = bindTParams(tparams) + n.underlying = underlying + n.fromRHS = underlying // for cycle detection + n.methods = newMethodList(methods) + n.loader = nil + } + + n.setState(resolved) + return n +} + +// state atomically accesses the current state of the receiver. +func (n *Named) state() namedState { + return namedState(atomic.LoadUint32(&n.state_)) +} + +// setState atomically stores the given state for n. +// Must only be called while holding n.mu. +func (n *Named) setState(state namedState) { + atomic.StoreUint32(&n.state_, uint32(state)) } // newNamed is like NewNamed but with a *Checker receiver and additional orig argument. @@ -80,16 +214,16 @@ func (check *Checker) newNamed(obj *TypeName, orig *Named, underlying Type, meth func (t *Named) cleanup() { assert(t.orig.orig == t.orig) // Ensure that every defined type created in the course of type-checking has - // either non-*Named underlying, or is unresolved. + // either non-*Named underlying type, or is unexpanded. // - // This guarantees that we don't leak any types whose underlying is *Named, - // because any unresolved instances will lazily compute their underlying by - // substituting in the underlying of their origin. The origin must have - // either been imported or type-checked and expanded here, and in either case - // its underlying will be fully expanded. + // This guarantees that we don't leak any types whose underlying type is + // *Named, because any unexpanded instances will lazily compute their + // underlying type by substituting in the underlying type of their origin. + // The origin must have either been imported or type-checked and expanded + // here, and in either case its underlying type will be fully expanded. switch t.underlying.(type) { case nil: - if t.resolver == nil { + if t.TypeArgs().Len() == 0 { panic("nil underlying") } case *Named: @@ -136,12 +270,13 @@ func (t *Named) NumMethods() int { return t.resolve(nil).methods.Len() } func (t *Named) Method(i int) *Func { t.resolve(nil) return t.methods.At(i, func() *Func { - return t.instantiateMethod(i) + return t.expandMethod(i) }) } -// instantiateMethod instantiates the i'th method for an instantiated receiver. -func (t *Named) instantiateMethod(i int) *Func { +// expandMethod substitutes type arguments in the i'th method for an +// instantiated receiver. +func (t *Named) expandMethod(i int) *Func { assert(t.TypeArgs().Len() > 0) // t must be an instance // t.orig.methods is not lazy. origm is the method instantiated with its @@ -277,7 +412,7 @@ func (n0 *Named) under() Type { check := n0.check n := n0 - seen := make(map[*Named]int) // types that need their underlying resolved + seen := make(map[*Named]int) // types that need their underlying type resolved var path []Object // objects encountered, for cycle reporting loop: @@ -354,66 +489,64 @@ func (check *Checker) bestContext(ctxt *Context) *Context { return NewContext() } -// expandNamed ensures that the underlying type of n is instantiated. -// The underlying type will be Typ[Invalid] if there was an error. -func expandNamed(ctxt *Context, n *Named, instPos token.Pos) (tparams *TypeParamList, underlying Type, methods *methodList) { +// expandUnderlying substitutes type arguments in the underlying type n.orig, +// returning the result. Returns Typ[Invalid] if there was an error. +func (n *Named) expandUnderlying(ctxt *Context) Type { check := n.check if check != nil && trace { - check.trace(instPos, "-- expandNamed %s", n) + check.trace(n.obj.pos, "-- Named.expandUnderlying %s", n) check.indent++ defer func() { check.indent-- - check.trace(instPos, "=> %s (tparams = %s, under = %s)", n, tparams.list(), underlying) + check.trace(n.obj.pos, "=> %s (tparams = %s, under = %s)", n, n.tparams.list(), n.underlying) }() } - n.orig.resolve(ctxt) assert(n.orig.underlying != nil) if _, unexpanded := n.orig.underlying.(*Named); unexpanded { - // We should only get an unexpanded underlying here during type checking + // We should only get a Named underlying type here during type checking // (for example, in recursive type declarations). assert(check != nil) } - // Mismatching arg and tparam length may be checked elsewhere. - if n.orig.tparams.Len() == n.targs.Len() { - // We must always have a context, to avoid infinite recursion. - ctxt = check.bestContext(ctxt) - h := ctxt.instanceHash(n.orig, n.targs.list()) - // ensure that an instance is recorded for h to avoid infinite recursion. - ctxt.update(h, n.orig, n.TypeArgs().list(), n) - - smap := makeSubstMap(n.orig.tparams.list(), n.targs.list()) - underlying = n.check.subst(instPos, n.orig.underlying, smap, ctxt) - // If the underlying of n is an interface, we need to set the receiver of - // its methods accurately -- we set the receiver of interface methods on - // the RHS of a type declaration to the defined type. - if iface, _ := underlying.(*Interface); iface != nil { - if methods, copied := replaceRecvType(iface.methods, n.orig, n); copied { - // If the underlying doesn't actually use type parameters, it's possible - // that it wasn't substituted. In this case we need to create a new - // *Interface before modifying receivers. - if iface == n.orig.underlying { - old := iface - iface = check.newInterface() - iface.embeddeds = old.embeddeds - iface.complete = old.complete - iface.implicit = old.implicit // should be false but be conservative - underlying = iface - } - iface.methods = methods + if n.orig.tparams.Len() != n.targs.Len() { + // Mismatching arg and tparam length may be checked elsewhere. + return Typ[Invalid] + } + + // We must always have a context, to avoid infinite recursion. + ctxt = check.bestContext(ctxt) + h := ctxt.instanceHash(n.orig, n.targs.list()) + // ensure that an instance is recorded for h to avoid infinite recursion. + ctxt.update(h, n.orig, n.TypeArgs().list(), n) + + smap := makeSubstMap(n.orig.tparams.list(), n.targs.list()) + underlying := n.check.subst(n.obj.pos, n.orig.underlying, smap, ctxt) + // If the underlying type of n is an interface, we need to set the receiver + // of its methods accurately -- we set the receiver of interface methods on + // the RHS of a type declaration to the defined type. + if iface, _ := underlying.(*Interface); iface != nil { + if methods, copied := replaceRecvType(iface.methods, n.orig, n); copied { + // If the underlying type doesn't actually use type parameters, it's + // possible that it wasn't substituted. In this case we need to create + // a new *Interface before modifying receivers. + if iface == n.orig.underlying { + old := iface + iface = check.newInterface() + iface.embeddeds = old.embeddeds + iface.complete = old.complete + iface.implicit = old.implicit // should be false but be conservative + underlying = iface } + iface.methods = methods } - } else { - underlying = Typ[Invalid] } - - return n.orig.tparams, underlying, newLazyMethodList(n.orig.methods.Len()) + return underlying } -// safeUnderlying returns the underlying of typ without expanding instances, to -// avoid infinite recursion. +// safeUnderlying returns the underlying type of typ without expanding +// instances, to avoid infinite recursion. // // TODO(rfindley): eliminate this function or give it a better name. func safeUnderlying(typ Type) Type { diff --git a/src/go/types/named_test.go b/src/go/types/named_test.go new file mode 100644 index 0000000000..74cdb48889 --- /dev/null +++ b/src/go/types/named_test.go @@ -0,0 +1,88 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package types_test + +import ( + "testing" + + . "go/types" +) + +func BenchmarkNamed(b *testing.B) { + const src = ` +package p + +type T struct { + P int +} + +func (T) M(int) {} +func (T) N() (i int) { return } + +type G[P any] struct { + F P +} + +func (G[P]) M(P) {} +func (G[P]) N() (p P) { return } + +type Inst = G[int] + ` + pkg, err := pkgForMode("p", src, nil, 0) + if err != nil { + b.Fatal(err) + } + + var ( + T = pkg.Scope().Lookup("T").Type() + G = pkg.Scope().Lookup("G").Type() + SrcInst = pkg.Scope().Lookup("Inst").Type() + UserInst = mustInstantiate(b, G, Typ[Int]) + ) + + tests := []struct { + name string + typ Type + }{ + {"nongeneric", T}, + {"generic", G}, + {"src instance", SrcInst}, + {"user instance", UserInst}, + } + + b.Run("Underlying", func(b *testing.B) { + for _, test := range tests { + b.Run(test.name, func(b *testing.B) { + // Access underlying once, to trigger any lazy calculation. + _ = test.typ.Underlying() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = test.typ.Underlying() + } + }) + } + }) + + b.Run("NewMethodSet", func(b *testing.B) { + for _, test := range tests { + b.Run(test.name, func(b *testing.B) { + // Access underlying once, to trigger any lazy calculation. + _ = NewMethodSet(test.typ) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = NewMethodSet(test.typ) + } + }) + } + }) +} + +func mustInstantiate(tb testing.TB, orig Type, targs ...Type) Type { + inst, err := Instantiate(nil, orig, targs, true) + if err != nil { + tb.Fatal(err) + } + return inst +} diff --git a/src/go/types/object.go b/src/go/types/object.go index ae138a5879..f203b0113d 100644 --- a/src/go/types/object.go +++ b/src/go/types/object.go @@ -233,19 +233,7 @@ func NewTypeName(pos token.Pos, pkg *Package, name string, typ Type) *TypeName { // lazily calls resolve to finish constructing the Named object. func _NewTypeNameLazy(pos token.Pos, pkg *Package, name string, load func(named *Named) (tparams []*TypeParam, underlying Type, methods []*Func)) *TypeName { obj := NewTypeName(pos, pkg, name, nil) - - resolve := func(_ *Context, t *Named) (*TypeParamList, Type, *methodList) { - tparams, underlying, methods := load(t) - - switch underlying.(type) { - case nil, *Named: - panic(fmt.Sprintf("invalid underlying type %T", t.underlying)) - } - - return bindTParams(tparams), underlying, newMethodList(methods) - } - - NewNamed(obj, nil, nil).resolver = resolve + NewNamed(obj, nil, nil).loader = load return obj } diff --git a/src/go/types/typexpr.go b/src/go/types/typexpr.go index 7afc66a925..c7161e00a5 100644 --- a/src/go/types/typexpr.go +++ b/src/go/types/typexpr.go @@ -385,14 +385,13 @@ func (check *Checker) typInternal(e0 ast.Expr, def *Named) (T Type) { } func (check *Checker) instantiatedType(ix *typeparams.IndexExpr, def *Named) (res Type) { - pos := ix.X.Pos() if trace { - check.trace(pos, "-- instantiating type %s with %s", ix.X, ix.Indices) + check.trace(ix.Pos(), "-- instantiating type %s with %s", ix.X, ix.Indices) check.indent++ defer func() { check.indent-- // Don't format the underlying here. It will always be nil. - check.trace(pos, "=> %s", res) + check.trace(ix.Pos(), "=> %s", res) }() } @@ -417,57 +416,20 @@ func (check *Checker) instantiatedType(ix *typeparams.IndexExpr, def *Named) (re return Typ[Invalid] } - // enableTypeTypeInference controls whether to infer missing type arguments - // using constraint type inference. See issue #51527. - const enableTypeTypeInference = false - // create the instance ctxt := check.bestContext(nil) - h := ctxt.instanceHash(orig, targs) - // targs may be incomplete, and require inference. In any case we should de-duplicate. - inst, _ := ctxt.lookup(h, orig, targs).(*Named) - // If inst is non-nil, we can't just return here. Inst may have been - // constructed via recursive substitution, in which case we wouldn't do the - // validation below. Ensure that the validation (and resulting errors) runs - // for each instantiated type in the source. - if inst == nil { - // x may be a selector for an imported type; use its start pos rather than x.Pos(). - tname := NewTypeName(ix.Pos(), orig.obj.pkg, orig.obj.name, nil) - inst = check.newNamed(tname, orig, nil, nil) // underlying, methods and tparams are set when named is resolved - inst.targs = newTypeList(targs) - inst = ctxt.update(h, orig, targs, inst).(*Named) - } + inst := check.instance(ix.Pos(), orig, targs, ctxt).(*Named) def.setUnderlying(inst) - inst.resolver = func(ctxt *Context, n *Named) (*TypeParamList, Type, *methodList) { - tparams := n.orig.TypeParams().list() - - targs := n.targs.list() - if enableTypeTypeInference && len(targs) < len(tparams) { - // If inference fails, len(inferred) will be 0, and inst.underlying will - // be set to Typ[Invalid] in expandNamed. - inferred := check.infer(ix.Orig, tparams, targs, nil, nil) - if len(inferred) > len(targs) { - n.targs = newTypeList(inferred) - } - } - - return expandNamed(ctxt, n, pos) - } - // orig.tparams may not be set up, so we need to do expansion later. check.later(func() { // This is an instance from the source, not from recursive substitution, // and so it must be resolved during type-checking so that we can report // errors. - inst.resolve(ctxt) - // Since check is non-nil, we can still mutate inst. Unpinning the resolver - // frees some memory. - inst.resolver = nil check.recordInstance(ix.Orig, inst.TypeArgs().list(), inst) - if check.validateTArgLen(pos, inst.tparams.Len(), inst.targs.Len()) { - if i, err := check.verify(pos, inst.tparams.list(), inst.targs.list()); err != nil { + if check.validateTArgLen(ix.Pos(), inst.TypeParams().Len(), inst.targs.Len()) { + if i, err := check.verify(ix.Pos(), inst.TypeParams().list(), inst.targs.list()); err != nil { // best position for error reporting pos := ix.Pos() if i < len(ix.Indices) { @@ -475,10 +437,13 @@ func (check *Checker) instantiatedType(ix *typeparams.IndexExpr, def *Named) (re } check.softErrorf(atPos(pos), _InvalidTypeArg, err.Error()) } else { - check.mono.recordInstance(check.pkg, pos, inst.tparams.list(), inst.targs.list(), ix.Indices) + check.mono.recordInstance(check.pkg, ix.Pos(), inst.TypeParams().list(), inst.targs.list(), ix.Indices) } } + // TODO(rfindley): remove this call: we don't need to call validType here, + // as cycles can only occur for types used inside a Named type declaration, + // and so it suffices to call validType from declared types. check.validType(inst) }).describef(ix, "resolve instance %s", inst) -- cgit v1.3-5-g9baa From 1323b0e8f0c5afb72afe51d8ee3bd5f66c23e353 Mon Sep 17 00:00:00 2001 From: Robert Findley Date: Sat, 7 May 2022 18:59:14 -0400 Subject: go/types, types2: eliminate methodList in favor of just using Named.mu In order to clean up context after fully expanding a type (in subsequent CLs), we must use a common mutex. Eliminate the lazy methodList type, which keeps a sync.Once per method, in favor of Named.mu. Updates #52728 Change-Id: I2d13319276df1330ee53046ef1823b0167a258d8 Reviewed-on: https://go-review.googlesource.com/c/go/+/404883 TryBot-Result: Gopher Robot Run-TryBot: Robert Findley Reviewed-by: Robert Griesemer --- src/cmd/compile/internal/types2/decl.go | 8 +-- src/cmd/compile/internal/types2/methodlist.go | 79 ---------------------- src/cmd/compile/internal/types2/methodlist_test.go | 40 ----------- src/cmd/compile/internal/types2/named.go | 71 +++++++++++++------ src/cmd/compile/internal/types2/sizeof_test.go | 2 +- src/go/types/decl.go | 8 +-- src/go/types/methodlist.go | 79 ---------------------- src/go/types/methodlist_test.go | 41 ----------- src/go/types/named.go | 71 +++++++++++++------ src/go/types/sizeof_test.go | 2 +- 10 files changed, 112 insertions(+), 289 deletions(-) delete mode 100644 src/cmd/compile/internal/types2/methodlist.go delete mode 100644 src/cmd/compile/internal/types2/methodlist_test.go delete mode 100644 src/go/types/methodlist.go delete mode 100644 src/go/types/methodlist_test.go (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/types2/decl.go b/src/cmd/compile/internal/types2/decl.go index 008d3698b7..a5d29765c6 100644 --- a/src/cmd/compile/internal/types2/decl.go +++ b/src/cmd/compile/internal/types2/decl.go @@ -646,8 +646,8 @@ func (check *Checker) collectMethods(obj *TypeName) { // Checker.Files may be called multiple times; additional package files // may add methods to already type-checked types. Add pre-existing methods // so that we can detect redeclarations. - for i := 0; i < base.methods.Len(); i++ { - m := base.methods.At(i, nil) + for i := 0; i < base.NumMethods(); i++ { + m := base.Method(i) assert(m.name != "_") assert(mset.insert(m) == nil) } @@ -679,8 +679,8 @@ func (check *Checker) collectMethods(obj *TypeName) { func (check *Checker) checkFieldUniqueness(base *Named) { if t, _ := base.under().(*Struct); t != nil { var mset objset - for i := 0; i < base.methods.Len(); i++ { - m := base.methods.At(i, nil) + for i := 0; i < base.NumMethods(); i++ { + m := base.Method(i) assert(m.name != "_") assert(mset.insert(m) == nil) } diff --git a/src/cmd/compile/internal/types2/methodlist.go b/src/cmd/compile/internal/types2/methodlist.go deleted file mode 100644 index cd6c06c5fb..0000000000 --- a/src/cmd/compile/internal/types2/methodlist.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2022 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package types2 - -import "sync" - -// methodList holds a list of methods that may be lazily resolved by a provided -// resolution method. -type methodList struct { - methods []*Func - - // guards synchronizes the instantiation of lazy methods. For lazy method - // lists, guards is non-nil and of the length passed to newLazyMethodList. - // For non-lazy method lists, guards is nil. - guards *[]sync.Once -} - -// newMethodList creates a non-lazy method list holding the given methods. -func newMethodList(methods []*Func) *methodList { - return &methodList{methods: methods} -} - -// newLazyMethodList creates a lazy method list of the given length. Methods -// may be resolved lazily for a given index by providing a resolver function. -func newLazyMethodList(length int) *methodList { - guards := make([]sync.Once, length) - return &methodList{ - methods: make([]*Func, length), - guards: &guards, - } -} - -// isLazy reports whether the receiver is a lazy method list. -func (l *methodList) isLazy() bool { - return l != nil && l.guards != nil -} - -// Add appends a method to the method list if not not already present. Add -// panics if the receiver is lazy. -func (l *methodList) Add(m *Func) { - assert(!l.isLazy()) - if i, _ := lookupMethod(l.methods, m.pkg, m.name, false); i < 0 { - l.methods = append(l.methods, m) - } -} - -// Lookup looks up the method identified by pkg and name in the receiver. -// Lookup panics if the receiver is lazy. If foldCase is true, method names -// are considered equal if they are equal with case folding. -func (l *methodList) Lookup(pkg *Package, name string, foldCase bool) (int, *Func) { - assert(!l.isLazy()) - if l == nil { - return -1, nil - } - return lookupMethod(l.methods, pkg, name, foldCase) -} - -// Len returns the length of the method list. -func (l *methodList) Len() int { - if l == nil { - return 0 - } - return len(l.methods) -} - -// At returns the i'th method of the method list. At panics if i is out of -// bounds, or if the receiver is lazy and resolve is nil. -func (l *methodList) At(i int, resolve func() *Func) *Func { - if !l.isLazy() { - return l.methods[i] - } - assert(resolve != nil) - (*l.guards)[i].Do(func() { - l.methods[i] = resolve() - }) - return l.methods[i] -} diff --git a/src/cmd/compile/internal/types2/methodlist_test.go b/src/cmd/compile/internal/types2/methodlist_test.go deleted file mode 100644 index 7a183ac7f9..0000000000 --- a/src/cmd/compile/internal/types2/methodlist_test.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2022 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package types2 - -import ( - "testing" -) - -func TestLazyMethodList(t *testing.T) { - l := newLazyMethodList(2) - - if got := l.Len(); got != 2 { - t.Fatalf("Len() = %d, want 2", got) - } - - f0 := NewFunc(nopos, nil, "f0", nil) - f1 := NewFunc(nopos, nil, "f1", nil) - - // Verify that methodList.At is idempotent, by calling it repeatedly with a - // resolve func that returns different pointer values (f0 or f1). - steps := []struct { - index int - resolve *Func // the *Func returned by the resolver - want *Func // the actual *Func returned by methodList.At - }{ - {0, f0, f0}, - {0, f1, f0}, - {1, f1, f1}, - {1, f0, f1}, - } - - for i, step := range steps { - got := l.At(step.index, func() *Func { return step.resolve }) - if got != step.want { - t.Errorf("step %d: At(%d, ...) = %s, want %s", i, step.index, got.Name(), step.want.Name()) - } - } -} diff --git a/src/cmd/compile/internal/types2/named.go b/src/cmd/compile/internal/types2/named.go index 0a2b2aa6b1..77655bc821 100644 --- a/src/cmd/compile/internal/types2/named.go +++ b/src/cmd/compile/internal/types2/named.go @@ -96,14 +96,16 @@ type Named struct { underlying Type // possibly a *Named during setup; never a *Named once set up completely tparams *TypeParamList // type parameters, or nil - // methods declared for this type (not the method set of this type). + // methods declared for this type (not the method set of this type) // Signatures are type-checked lazily. // For non-instantiated types, this is a fully populated list of methods. For - // instantiated types, this is a 'lazy' list, and methods are individually - // expanded when they are first accessed. - methods *methodList + // instantiated types, methods are individually expanded when they are first + // accessed. + methods []*Func + // number of expanded methods (only valid for instantiated named types) + expandedMethods int // expandedMethods <= len(orig.methods) - // loader may be provided to lazily load type parameters, underlying, and methods. + // loader may be provided to lazily load type parameters, underlying type, and methods. loader func(*Named) (tparams []*TypeParam, underlying Type, methods []*Func) } @@ -112,7 +114,8 @@ type namedState uint32 const ( unresolved namedState = iota // tparams, underlying type and methods might be unavailable - resolved + resolved // resolve has run; methods might be incomplete (for instances) + complete // all data is known ) // NewNamed returns a new named type for the given type name, underlying type, and associated methods. @@ -122,7 +125,7 @@ func NewNamed(obj *TypeName, underlying Type, methods []*Func) *Named { if _, ok := underlying.(*Named); ok { panic("underlying type must not be *Named") } - return (*Checker)(nil).newNamed(obj, nil, underlying, newMethodList(methods)) + return (*Checker)(nil).newNamed(obj, nil, underlying, methods) } // resolve resolves the type parameters, methods, and underlying type of n. @@ -156,8 +159,12 @@ func (n *Named) resolve(ctxt *Context) *Named { n.tparams = n.orig.tparams n.underlying = underlying n.fromRHS = n.orig.fromRHS // for cycle detection - n.methods = newLazyMethodList(n.orig.methods.Len()) - n.setState(resolved) + + if len(n.orig.methods) == 0 { + n.setState(complete) + } else { + n.setState(resolved) + } return n } @@ -170,17 +177,18 @@ func (n *Named) resolve(ctxt *Context) *Named { // also make the API more future-proof towards further extensions. if n.loader != nil { assert(n.underlying == nil) + assert(n.TypeArgs().Len() == 0) // instances are created by instantiation, in which case n.loader is nil tparams, underlying, methods := n.loader(n) n.tparams = bindTParams(tparams) n.underlying = underlying n.fromRHS = underlying // for cycle detection - n.methods = newMethodList(methods) + n.methods = methods n.loader = nil } - n.setState(resolved) + n.setState(complete) return n } @@ -196,7 +204,7 @@ func (n *Named) setState(state namedState) { } // newNamed is like NewNamed but with a *Checker receiver and additional orig argument. -func (check *Checker) newNamed(obj *TypeName, orig *Named, underlying Type, methods *methodList) *Named { +func (check *Checker) newNamed(obj *TypeName, orig *Named, underlying Type, methods []*Func) *Named { typ := &Named{check: check, obj: obj, orig: orig, fromRHS: underlying, underlying: underlying, methods: methods} if typ.orig == nil { typ.orig = typ @@ -262,14 +270,38 @@ func (t *Named) TypeArgs() *TypeList { return t.targs } // For an ordinary or instantiated type t, the receiver base type of these // methods will be the named type t. For an uninstantiated generic type t, each // method receiver will be instantiated with its receiver type parameters. -func (t *Named) NumMethods() int { return t.resolve(nil).methods.Len() } +func (t *Named) NumMethods() int { return len(t.orig.resolve(nil).methods) } // Method returns the i'th method of named type t for 0 <= i < t.NumMethods(). func (t *Named) Method(i int) *Func { t.resolve(nil) - return t.methods.At(i, func() *Func { - return t.expandMethod(i) - }) + + if t.state() >= complete { + return t.methods[i] + } + + assert(t.TypeArgs().Len() > 0) // only instances should have incomplete methods + + t.mu.Lock() + defer t.mu.Unlock() + + if len(t.methods) != len(t.orig.methods) { + assert(len(t.methods) == 0) + t.methods = make([]*Func, len(t.orig.methods)) + } + + if t.methods[i] == nil { + t.methods[i] = t.expandMethod(i) + t.expandedMethods++ + + // Check if we've created all methods at this point. If we have, mark the + // type as fully expanded. + if t.expandedMethods == len(t.orig.methods) { + t.setState(complete) + } + } + + return t.methods[i] } // expandMethod substitutes type arguments in the i'th method for an @@ -351,10 +383,9 @@ func (t *Named) SetUnderlying(underlying Type) { func (t *Named) AddMethod(m *Func) { assert(t.targs.Len() == 0) t.resolve(nil) - if t.methods == nil { - t.methods = newMethodList(nil) + if i, _ := lookupMethod(t.methods, m.pkg, m.name, false); i < 0 { + t.methods = append(t.methods, m) } - t.methods.Add(m) } func (t *Named) Underlying() Type { return t.resolve(nil).underlying } @@ -462,7 +493,7 @@ func (n *Named) lookupMethod(pkg *Package, name string, foldCase bool) (int, *Fu // If n is an instance, we may not have yet instantiated all of its methods. // Look up the method index in orig, and only instantiate method at the // matching index (if any). - i, _ := n.orig.methods.Lookup(pkg, name, foldCase) + i, _ := lookupMethod(n.orig.methods, pkg, name, foldCase) if i < 0 { return -1, nil } diff --git a/src/cmd/compile/internal/types2/sizeof_test.go b/src/cmd/compile/internal/types2/sizeof_test.go index 7ab7abb317..3f0bf8f3c5 100644 --- a/src/cmd/compile/internal/types2/sizeof_test.go +++ b/src/cmd/compile/internal/types2/sizeof_test.go @@ -31,7 +31,7 @@ func TestSizeof(t *testing.T) { {Interface{}, 40, 80}, {Map{}, 16, 32}, {Chan{}, 12, 24}, - {Named{}, 56, 104}, + {Named{}, 68, 128}, {TypeParam{}, 28, 48}, {term{}, 12, 24}, diff --git a/src/go/types/decl.go b/src/go/types/decl.go index 61c9696948..b5ff1214dd 100644 --- a/src/go/types/decl.go +++ b/src/go/types/decl.go @@ -722,8 +722,8 @@ func (check *Checker) collectMethods(obj *TypeName) { // Checker.Files may be called multiple times; additional package files // may add methods to already type-checked types. Add pre-existing methods // so that we can detect redeclarations. - for i := 0; i < base.methods.Len(); i++ { - m := base.methods.At(i, nil) + for i := 0; i < base.NumMethods(); i++ { + m := base.Method(i) assert(m.name != "_") assert(mset.insert(m) == nil) } @@ -749,8 +749,8 @@ func (check *Checker) collectMethods(obj *TypeName) { func (check *Checker) checkFieldUniqueness(base *Named) { if t, _ := base.under().(*Struct); t != nil { var mset objset - for i := 0; i < base.methods.Len(); i++ { - m := base.methods.At(i, nil) + for i := 0; i < base.NumMethods(); i++ { + m := base.Method(i) assert(m.name != "_") assert(mset.insert(m) == nil) } diff --git a/src/go/types/methodlist.go b/src/go/types/methodlist.go deleted file mode 100644 index afe919013d..0000000000 --- a/src/go/types/methodlist.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2022 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package types - -import "sync" - -// methodList holds a list of methods that may be lazily resolved by a provided -// resolution method. -type methodList struct { - methods []*Func - - // guards synchronizes the instantiation of lazy methods. For lazy method - // lists, guards is non-nil and of the length passed to newLazyMethodList. - // For non-lazy method lists, guards is nil. - guards *[]sync.Once -} - -// newMethodList creates a non-lazy method list holding the given methods. -func newMethodList(methods []*Func) *methodList { - return &methodList{methods: methods} -} - -// newLazyMethodList creates a lazy method list of the given length. Methods -// may be resolved lazily for a given index by providing a resolver function. -func newLazyMethodList(length int) *methodList { - guards := make([]sync.Once, length) - return &methodList{ - methods: make([]*Func, length), - guards: &guards, - } -} - -// isLazy reports whether the receiver is a lazy method list. -func (l *methodList) isLazy() bool { - return l != nil && l.guards != nil -} - -// Add appends a method to the method list if not not already present. Add -// panics if the receiver is lazy. -func (l *methodList) Add(m *Func) { - assert(!l.isLazy()) - if i, _ := lookupMethod(l.methods, m.pkg, m.name, false); i < 0 { - l.methods = append(l.methods, m) - } -} - -// Lookup looks up the method identified by pkg and name in the receiver. -// Lookup panics if the receiver is lazy. If foldCase is true, method names -// are considered equal if they are equal with case folding. -func (l *methodList) Lookup(pkg *Package, name string, foldCase bool) (int, *Func) { - assert(!l.isLazy()) - if l == nil { - return -1, nil - } - return lookupMethod(l.methods, pkg, name, foldCase) -} - -// Len returns the length of the method list. -func (l *methodList) Len() int { - if l == nil { - return 0 - } - return len(l.methods) -} - -// At returns the i'th method of the method list. At panics if i is out of -// bounds, or if the receiver is lazy and resolve is nil. -func (l *methodList) At(i int, resolve func() *Func) *Func { - if !l.isLazy() { - return l.methods[i] - } - assert(resolve != nil) - (*l.guards)[i].Do(func() { - l.methods[i] = resolve() - }) - return l.methods[i] -} diff --git a/src/go/types/methodlist_test.go b/src/go/types/methodlist_test.go deleted file mode 100644 index e628bce767..0000000000 --- a/src/go/types/methodlist_test.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2022 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package types - -import ( - "go/token" - "testing" -) - -func TestLazyMethodList(t *testing.T) { - l := newLazyMethodList(2) - - if got := l.Len(); got != 2 { - t.Fatalf("Len() = %d, want 2", got) - } - - f0 := NewFunc(token.NoPos, nil, "f0", nil) - f1 := NewFunc(token.NoPos, nil, "f1", nil) - - // Verify that methodList.At is idempotent, by calling it repeatedly with a - // resolve func that returns different pointer values (f0 or f1). - steps := []struct { - index int - resolve *Func // the *Func returned by the resolver - want *Func // the actual *Func returned by methodList.At - }{ - {0, f0, f0}, - {0, f1, f0}, - {1, f1, f1}, - {1, f0, f1}, - } - - for i, step := range steps { - got := l.At(step.index, func() *Func { return step.resolve }) - if got != step.want { - t.Errorf("step %d: At(%d, ...) = %s, want %s", i, step.index, got.Name(), step.want.Name()) - } - } -} diff --git a/src/go/types/named.go b/src/go/types/named.go index bfb4a11da7..f1c5dd4f81 100644 --- a/src/go/types/named.go +++ b/src/go/types/named.go @@ -96,14 +96,16 @@ type Named struct { underlying Type // possibly a *Named during setup; never a *Named once set up completely tparams *TypeParamList // type parameters, or nil - // methods declared for this type (not the method set of this type). + // methods declared for this type (not the method set of this type) // Signatures are type-checked lazily. // For non-instantiated types, this is a fully populated list of methods. For - // instantiated types, this is a 'lazy' list, and methods are individually - // expanded when they are first accessed. - methods *methodList + // instantiated types, methods are individually expanded when they are first + // accessed. + methods []*Func + // number of expanded methods (only valid for instantiated named types) + expandedMethods int // expandedMethods <= len(orig.methods) - // loader may be provided to lazily load type parameters, underlying, and methods. + // loader may be provided to lazily load type parameters, underlying type, and methods. loader func(*Named) (tparams []*TypeParam, underlying Type, methods []*Func) } @@ -112,7 +114,8 @@ type namedState uint32 const ( unresolved namedState = iota // tparams, underlying type and methods might be unavailable - resolved + resolved // resolve has run; methods might be incomplete (for instances) + complete // all data is known ) // NewNamed returns a new named type for the given type name, underlying type, and associated methods. @@ -122,7 +125,7 @@ func NewNamed(obj *TypeName, underlying Type, methods []*Func) *Named { if _, ok := underlying.(*Named); ok { panic("underlying type must not be *Named") } - return (*Checker)(nil).newNamed(obj, nil, underlying, newMethodList(methods)) + return (*Checker)(nil).newNamed(obj, nil, underlying, methods) } // resolve resolves the type parameters, methods, and underlying type of n. @@ -156,8 +159,12 @@ func (n *Named) resolve(ctxt *Context) *Named { n.tparams = n.orig.tparams n.underlying = underlying n.fromRHS = n.orig.fromRHS // for cycle detection - n.methods = newLazyMethodList(n.orig.methods.Len()) - n.setState(resolved) + + if len(n.orig.methods) == 0 { + n.setState(complete) + } else { + n.setState(resolved) + } return n } @@ -170,17 +177,18 @@ func (n *Named) resolve(ctxt *Context) *Named { // also make the API more future-proof towards further extensions. if n.loader != nil { assert(n.underlying == nil) + assert(n.TypeArgs().Len() == 0) // instances are created by instantiation, in which case n.loader is nil tparams, underlying, methods := n.loader(n) n.tparams = bindTParams(tparams) n.underlying = underlying n.fromRHS = underlying // for cycle detection - n.methods = newMethodList(methods) + n.methods = methods n.loader = nil } - n.setState(resolved) + n.setState(complete) return n } @@ -196,7 +204,7 @@ func (n *Named) setState(state namedState) { } // newNamed is like NewNamed but with a *Checker receiver and additional orig argument. -func (check *Checker) newNamed(obj *TypeName, orig *Named, underlying Type, methods *methodList) *Named { +func (check *Checker) newNamed(obj *TypeName, orig *Named, underlying Type, methods []*Func) *Named { typ := &Named{check: check, obj: obj, orig: orig, fromRHS: underlying, underlying: underlying, methods: methods} if typ.orig == nil { typ.orig = typ @@ -264,14 +272,38 @@ func (t *Named) TypeArgs() *TypeList { return t.targs } // For an ordinary or instantiated type t, the receiver base type of these // methods will be the named type t. For an uninstantiated generic type t, each // method receiver will be instantiated with its receiver type parameters. -func (t *Named) NumMethods() int { return t.resolve(nil).methods.Len() } +func (t *Named) NumMethods() int { return len(t.orig.resolve(nil).methods) } // Method returns the i'th method of named type t for 0 <= i < t.NumMethods(). func (t *Named) Method(i int) *Func { t.resolve(nil) - return t.methods.At(i, func() *Func { - return t.expandMethod(i) - }) + + if t.state() >= complete { + return t.methods[i] + } + + assert(t.TypeArgs().Len() > 0) // only instances should have incomplete methods + + t.mu.Lock() + defer t.mu.Unlock() + + if len(t.methods) != len(t.orig.methods) { + assert(len(t.methods) == 0) + t.methods = make([]*Func, len(t.orig.methods)) + } + + if t.methods[i] == nil { + t.methods[i] = t.expandMethod(i) + t.expandedMethods++ + + // Check if we've created all methods at this point. If we have, mark the + // type as fully expanded. + if t.expandedMethods == len(t.orig.methods) { + t.setState(complete) + } + } + + return t.methods[i] } // expandMethod substitutes type arguments in the i'th method for an @@ -353,10 +385,9 @@ func (t *Named) SetUnderlying(underlying Type) { func (t *Named) AddMethod(m *Func) { assert(t.targs.Len() == 0) t.resolve(nil) - if t.methods == nil { - t.methods = newMethodList(nil) + if i, _ := lookupMethod(t.methods, m.pkg, m.name, false); i < 0 { + t.methods = append(t.methods, m) } - t.methods.Add(m) } func (t *Named) Underlying() Type { return t.resolve(nil).underlying } @@ -464,7 +495,7 @@ func (n *Named) lookupMethod(pkg *Package, name string, foldCase bool) (int, *Fu // If n is an instance, we may not have yet instantiated all of its methods. // Look up the method index in orig, and only instantiate method at the // matching index (if any). - i, _ := n.orig.methods.Lookup(pkg, name, foldCase) + i, _ := lookupMethod(n.orig.methods, pkg, name, foldCase) if i < 0 { return -1, nil } diff --git a/src/go/types/sizeof_test.go b/src/go/types/sizeof_test.go index 3428eb9191..66a69521d2 100644 --- a/src/go/types/sizeof_test.go +++ b/src/go/types/sizeof_test.go @@ -30,7 +30,7 @@ func TestSizeof(t *testing.T) { {Interface{}, 40, 80}, {Map{}, 16, 32}, {Chan{}, 12, 24}, - {Named{}, 56, 104}, + {Named{}, 68, 128}, {TypeParam{}, 28, 48}, {term{}, 12, 24}, -- cgit v1.3-5-g9baa From 02e69cfa9695f17902ff1806205c26a0d02a684f Mon Sep 17 00:00:00 2001 From: Robert Findley Date: Sat, 7 May 2022 19:26:35 -0400 Subject: go/types, types2: store Named instance information separately Separate instance information into an instance struct, to reduce memory footprint for non-instance Named types. This may induce a sense of deja-vu: we had a similar construct in the past that was removed as unnecessary. With additional new fields being added that only apply to instances, having a separate struct makes sense again. Updates #52728 Change-Id: I0bb5982d71c27e6b574bfb4f7b886a6aeb9c5390 Reviewed-on: https://go-review.googlesource.com/c/go/+/404884 Reviewed-by: Robert Griesemer Run-TryBot: Robert Findley TryBot-Result: Gopher Robot --- src/cmd/compile/internal/types2/decl.go | 4 +- src/cmd/compile/internal/types2/infer.go | 2 +- src/cmd/compile/internal/types2/instantiate.go | 5 +- src/cmd/compile/internal/types2/named.go | 131 ++++++++++++++++--------- src/cmd/compile/internal/types2/predicates.go | 4 +- src/cmd/compile/internal/types2/sizeof_test.go | 2 +- src/cmd/compile/internal/types2/subst.go | 25 ++--- src/cmd/compile/internal/types2/typestring.go | 4 +- src/cmd/compile/internal/types2/typexpr.go | 6 +- src/cmd/compile/internal/types2/unify.go | 4 +- src/cmd/compile/internal/types2/validtype.go | 2 +- src/go/types/decl.go | 4 +- src/go/types/infer.go | 2 +- src/go/types/instantiate.go | 5 +- src/go/types/named.go | 129 +++++++++++++++--------- src/go/types/predicates.go | 4 +- src/go/types/sizeof_test.go | 2 +- src/go/types/subst.go | 25 ++--- src/go/types/typestring.go | 4 +- src/go/types/typexpr.go | 6 +- src/go/types/unify.go | 4 +- src/go/types/validtype.go | 2 +- 22 files changed, 222 insertions(+), 154 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/types2/decl.go b/src/cmd/compile/internal/types2/decl.go index a5d29765c6..0bc5f9f3e1 100644 --- a/src/cmd/compile/internal/types2/decl.go +++ b/src/cmd/compile/internal/types2/decl.go @@ -508,7 +508,7 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *syntax.TypeDecl, def *Named } // type definition or generic type declaration - named := check.newNamed(obj, nil, nil, nil) + named := check.newNamed(obj, nil, nil) def.setUnderlying(named) if tdecl.TParamList != nil { @@ -635,7 +635,7 @@ func (check *Checker) collectMethods(obj *TypeName) { // and field names must be distinct." base, _ := obj.typ.(*Named) // shouldn't fail but be conservative if base != nil { - assert(base.targs.Len() == 0) // collectMethods should not be called on an instantiated type + assert(base.TypeArgs().Len() == 0) // collectMethods should not be called on an instantiated type // See issue #52529: we must delay the expansion of underlying here, as // base may not be fully set-up. diff --git a/src/cmd/compile/internal/types2/infer.go b/src/cmd/compile/internal/types2/infer.go index 9e77d67a7d..8ab568be59 100644 --- a/src/cmd/compile/internal/types2/infer.go +++ b/src/cmd/compile/internal/types2/infer.go @@ -434,7 +434,7 @@ func (w *tpWalker) isParameterized(typ Type) (res bool) { return w.isParameterized(t.elem) case *Named: - return w.isParameterizedTypeList(t.targs.list()) + return w.isParameterizedTypeList(t.TypeArgs().list()) case *TypeParam: // t must be one of w.tparams diff --git a/src/cmd/compile/internal/types2/instantiate.go b/src/cmd/compile/internal/types2/instantiate.go index 44ed0319c8..ed6206e150 100644 --- a/src/cmd/compile/internal/types2/instantiate.go +++ b/src/cmd/compile/internal/types2/instantiate.go @@ -76,10 +76,7 @@ func (check *Checker) instance(pos syntax.Pos, orig Type, targs []Type, ctxt *Co switch orig := orig.(type) { case *Named: - tname := NewTypeName(pos, orig.obj.pkg, orig.obj.name, nil) - named := check.newNamed(tname, orig, nil, nil) // underlying, tparams, and methods are set when named is resolved - named.targs = newTypeList(targs) - res = named + res = check.newNamedInstance(pos, orig, targs) case *Signature: tparams := orig.TypeParams() diff --git a/src/cmd/compile/internal/types2/named.go b/src/cmd/compile/internal/types2/named.go index 77655bc821..133fb6fa88 100644 --- a/src/cmd/compile/internal/types2/named.go +++ b/src/cmd/compile/internal/types2/named.go @@ -5,6 +5,7 @@ package types2 import ( + "cmd/compile/internal/syntax" "sync" "sync/atomic" ) @@ -83,14 +84,15 @@ import ( type Named struct { check *Checker // non-nil during type-checking; nil otherwise obj *TypeName // corresponding declared object for declared types; see above for instantiated types - orig *Named // origin type for instantiated types, this type for declared types - targs *TypeList // type arguments (after instantiation), or nil // fromRHS holds the type (on RHS of declaration) this *Named type is derived // from (for cycle reporting). Only used by validType, and therefore does not // require synchronization. fromRHS Type + // information for instantiated types; nil otherwise + inst *instance + mu sync.Mutex // guards all fields below state_ uint32 // the current state of this type; must only be accessed atomically underlying Type // possibly a *Named during setup; never a *Named once set up completely @@ -102,13 +104,19 @@ type Named struct { // instantiated types, methods are individually expanded when they are first // accessed. methods []*Func - // number of expanded methods (only valid for instantiated named types) - expandedMethods int // expandedMethods <= len(orig.methods) // loader may be provided to lazily load type parameters, underlying type, and methods. loader func(*Named) (tparams []*TypeParam, underlying Type, methods []*Func) } +// instance holds information that is only necessary for instantiated named +// types. +type instance struct { + orig *Named // original, uninstantiated type + targs *TypeList // type arguments + expandedMethods int // number of expanded methods; expandedMethods <= len(orig.methods) +} + // namedState represents the possible states that a named type may assume. type namedState uint32 @@ -125,7 +133,7 @@ func NewNamed(obj *TypeName, underlying Type, methods []*Func) *Named { if _, ok := underlying.(*Named); ok { panic("underlying type must not be *Named") } - return (*Checker)(nil).newNamed(obj, nil, underlying, methods) + return (*Checker)(nil).newNamed(obj, underlying, methods) } // resolve resolves the type parameters, methods, and underlying type of n. @@ -149,19 +157,20 @@ func (n *Named) resolve(ctxt *Context) *Named { return n } - if n.TypeArgs().Len() > 0 { + if n.inst != nil { assert(n.underlying == nil) // n is an unresolved instance assert(n.loader == nil) // instances are created by instantiation, in which case n.loader is nil - n.orig.resolve(ctxt) + orig := n.inst.orig + orig.resolve(ctxt) underlying := n.expandUnderlying(ctxt) - n.tparams = n.orig.tparams + n.tparams = orig.tparams n.underlying = underlying - n.fromRHS = n.orig.fromRHS // for cycle detection + n.fromRHS = orig.fromRHS // for cycle detection - if len(n.orig.methods) == 0 { - n.setState(complete) + if len(orig.methods) == 0 { + n.setState(complete) // nothing further to do } else { n.setState(resolved) } @@ -204,11 +213,8 @@ func (n *Named) setState(state namedState) { } // newNamed is like NewNamed but with a *Checker receiver and additional orig argument. -func (check *Checker) newNamed(obj *TypeName, orig *Named, underlying Type, methods []*Func) *Named { - typ := &Named{check: check, obj: obj, orig: orig, fromRHS: underlying, underlying: underlying, methods: methods} - if typ.orig == nil { - typ.orig = typ - } +func (check *Checker) newNamed(obj *TypeName, underlying Type, methods []*Func) *Named { + typ := &Named{check: check, obj: obj, fromRHS: underlying, underlying: underlying, methods: methods} if obj.typ == nil { obj.typ = typ } @@ -219,8 +225,22 @@ func (check *Checker) newNamed(obj *TypeName, orig *Named, underlying Type, meth return typ } +func (check *Checker) newNamedInstance(pos syntax.Pos, orig *Named, targs []Type) *Named { + assert(len(targs) > 0) + + obj := NewTypeName(pos, orig.obj.pkg, orig.obj.name, nil) + inst := &instance{orig: orig, targs: newTypeList(targs)} + typ := &Named{check: check, obj: obj, inst: inst} + obj.typ = typ + // Ensure that typ is always expanded and sanity-checked. + if check != nil { + check.needsCleanup(typ) + } + return typ +} + func (t *Named) cleanup() { - assert(t.orig.orig == t.orig) + assert(t.inst == nil || t.inst.orig.inst == nil) // Ensure that every defined type created in the course of type-checking has // either non-*Named underlying type, or is unexpanded. // @@ -242,14 +262,21 @@ func (t *Named) cleanup() { // Obj returns the type name for the declaration defining the named type t. For // instantiated types, this is same as the type name of the origin type. -func (t *Named) Obj() *TypeName { return t.orig.obj } // for non-instances this is the same as t.obj +func (t *Named) Obj() *TypeName { + if t.inst == nil { + return t.obj + } + return t.inst.orig.obj +} // Origin returns the generic type from which the named type t is // instantiated. If t is not an instantiated type, the result is t. -func (t *Named) Origin() *Named { return t.orig } - -// TODO(gri) Come up with a better representation and API to distinguish -// between parameterized instantiated and non-instantiated types. +func (t *Named) Origin() *Named { + if t.inst == nil { + return t + } + return t.inst.orig +} // TypeParams returns the type parameters of the named type t, or nil. // The result is non-nil for an (originally) generic type even if it is instantiated. @@ -258,19 +285,26 @@ func (t *Named) TypeParams() *TypeParamList { return t.resolve(nil).tparams } // SetTypeParams sets the type parameters of the named type t. // t must not have type arguments. func (t *Named) SetTypeParams(tparams []*TypeParam) { - assert(t.targs.Len() == 0) + assert(t.inst == nil) t.resolve(nil).tparams = bindTParams(tparams) } // TypeArgs returns the type arguments used to instantiate the named type t. -func (t *Named) TypeArgs() *TypeList { return t.targs } +func (t *Named) TypeArgs() *TypeList { + if t.inst == nil { + return nil + } + return t.inst.targs +} // NumMethods returns the number of explicit methods defined for t. // // For an ordinary or instantiated type t, the receiver base type of these // methods will be the named type t. For an uninstantiated generic type t, each // method receiver will be instantiated with its receiver type parameters. -func (t *Named) NumMethods() int { return len(t.orig.resolve(nil).methods) } +func (t *Named) NumMethods() int { + return len(t.Origin().resolve(nil).methods) +} // Method returns the i'th method of named type t for 0 <= i < t.NumMethods(). func (t *Named) Method(i int) *Func { @@ -280,23 +314,24 @@ func (t *Named) Method(i int) *Func { return t.methods[i] } - assert(t.TypeArgs().Len() > 0) // only instances should have incomplete methods + assert(t.inst != nil) // only instances should have incomplete methods + orig := t.inst.orig t.mu.Lock() defer t.mu.Unlock() - if len(t.methods) != len(t.orig.methods) { + if len(t.methods) != len(orig.methods) { assert(len(t.methods) == 0) - t.methods = make([]*Func, len(t.orig.methods)) + t.methods = make([]*Func, len(orig.methods)) } if t.methods[i] == nil { t.methods[i] = t.expandMethod(i) - t.expandedMethods++ + t.inst.expandedMethods++ // Check if we've created all methods at this point. If we have, mark the // type as fully expanded. - if t.expandedMethods == len(t.orig.methods) { + if t.inst.expandedMethods == len(orig.methods) { t.setState(complete) } } @@ -307,11 +342,9 @@ func (t *Named) Method(i int) *Func { // expandMethod substitutes type arguments in the i'th method for an // instantiated receiver. func (t *Named) expandMethod(i int) *Func { - assert(t.TypeArgs().Len() > 0) // t must be an instance - // t.orig.methods is not lazy. origm is the method instantiated with its // receiver type parameters (the "origin" method). - origm := t.orig.Method(i) + origm := t.inst.orig.Method(i) assert(origm != nil) check := t.check @@ -338,9 +371,9 @@ func (t *Named) expandMethod(i int) *Func { // We can only substitute if we have a correspondence between type arguments // and type parameters. This check is necessary in the presence of invalid // code. - if origSig.RecvTypeParams().Len() == t.targs.Len() { + if origSig.RecvTypeParams().Len() == t.inst.targs.Len() { ctxt := check.bestContext(nil) - smap := makeSubstMap(origSig.RecvTypeParams().list(), t.targs.list()) + smap := makeSubstMap(origSig.RecvTypeParams().list(), t.inst.targs.list()) sig = check.subst(origm.pos, origSig, smap, ctxt).(*Signature) } @@ -365,7 +398,7 @@ func (t *Named) expandMethod(i int) *Func { // SetUnderlying sets the underlying type and marks t as complete. // t must not have type arguments. func (t *Named) SetUnderlying(underlying Type) { - assert(t.targs.Len() == 0) + assert(t.inst == nil) if underlying == nil { panic("underlying type must not be nil") } @@ -381,7 +414,7 @@ func (t *Named) SetUnderlying(underlying Type) { // AddMethod adds method m unless it is already in the method list. // t must not have type arguments. func (t *Named) AddMethod(m *Func) { - assert(t.targs.Len() == 0) + assert(t.inst == nil) t.resolve(nil) if i, _ := lookupMethod(t.methods, m.pkg, m.name, false); i < 0 { t.methods = append(t.methods, m) @@ -493,7 +526,7 @@ func (n *Named) lookupMethod(pkg *Package, name string, foldCase bool) (int, *Fu // If n is an instance, we may not have yet instantiated all of its methods. // Look up the method index in orig, and only instantiate method at the // matching index (if any). - i, _ := lookupMethod(n.orig.methods, pkg, name, foldCase) + i, _ := lookupMethod(n.Origin().methods, pkg, name, foldCase) if i < 0 { return -1, nil } @@ -531,36 +564,39 @@ func (n *Named) expandUnderlying(ctxt *Context) Type { }() } - assert(n.orig.underlying != nil) + assert(n.inst.orig.underlying != nil) - if _, unexpanded := n.orig.underlying.(*Named); unexpanded { + orig := n.inst.orig + targs := n.inst.targs + + if _, unexpanded := orig.underlying.(*Named); unexpanded { // We should only get a Named underlying type here during type checking // (for example, in recursive type declarations). assert(check != nil) } - if n.orig.tparams.Len() != n.targs.Len() { + if orig.tparams.Len() != targs.Len() { // Mismatching arg and tparam length may be checked elsewhere. return Typ[Invalid] } // We must always have a context, to avoid infinite recursion. ctxt = check.bestContext(ctxt) - h := ctxt.instanceHash(n.orig, n.targs.list()) + h := ctxt.instanceHash(orig, targs.list()) // ensure that an instance is recorded for h to avoid infinite recursion. - ctxt.update(h, n.orig, n.TypeArgs().list(), n) + ctxt.update(h, orig, targs.list(), n) - smap := makeSubstMap(n.orig.tparams.list(), n.targs.list()) - underlying := n.check.subst(n.obj.pos, n.orig.underlying, smap, ctxt) + smap := makeSubstMap(orig.tparams.list(), targs.list()) + underlying := n.check.subst(n.obj.pos, orig.underlying, smap, ctxt) // If the underlying type of n is an interface, we need to set the receiver // of its methods accurately -- we set the receiver of interface methods on // the RHS of a type declaration to the defined type. if iface, _ := underlying.(*Interface); iface != nil { - if methods, copied := replaceRecvType(iface.methods, n.orig, n); copied { + if methods, copied := replaceRecvType(iface.methods, orig, n); copied { // If the underlying type doesn't actually use type parameters, it's // possible that it wasn't substituted. In this case we need to create // a new *Interface before modifying receivers. - if iface == n.orig.underlying { + if iface == orig.underlying { old := iface iface = check.newInterface() iface.embeddeds = old.embeddeds @@ -571,6 +607,7 @@ func (n *Named) expandUnderlying(ctxt *Context) Type { iface.methods = methods } } + return underlying } diff --git a/src/cmd/compile/internal/types2/predicates.go b/src/cmd/compile/internal/types2/predicates.go index 6bce26137e..6b6c21c780 100644 --- a/src/cmd/compile/internal/types2/predicates.go +++ b/src/cmd/compile/internal/types2/predicates.go @@ -102,7 +102,7 @@ func isTypeParam(t Type) bool { func isGeneric(t Type) bool { // A parameterized type is only generic if it doesn't have an instantiation already. named, _ := t.(*Named) - return named != nil && named.obj != nil && named.targs == nil && named.TypeParams() != nil + return named != nil && named.obj != nil && named.inst == nil && named.TypeParams().Len() > 0 } // Comparable reports whether values of type T are comparable. @@ -401,7 +401,7 @@ func identical(x, y Type, cmpTags bool, p *ifacePair) bool { if len(xargs) > 0 { // Instances are identical if their original type and type arguments // are identical. - if !Identical(x.orig, y.orig) { + if !Identical(x.Origin(), y.Origin()) { return false } for i, xa := range xargs { diff --git a/src/cmd/compile/internal/types2/sizeof_test.go b/src/cmd/compile/internal/types2/sizeof_test.go index 3f0bf8f3c5..17876d1f3c 100644 --- a/src/cmd/compile/internal/types2/sizeof_test.go +++ b/src/cmd/compile/internal/types2/sizeof_test.go @@ -31,7 +31,7 @@ func TestSizeof(t *testing.T) { {Interface{}, 40, 80}, {Map{}, 16, 32}, {Chan{}, 12, 24}, - {Named{}, 68, 128}, + {Named{}, 60, 112}, {TypeParam{}, 28, 48}, {term{}, 12, 24}, diff --git a/src/cmd/compile/internal/types2/subst.go b/src/cmd/compile/internal/types2/subst.go index 6e41ebdf53..9af1a71cfe 100644 --- a/src/cmd/compile/internal/types2/subst.go +++ b/src/cmd/compile/internal/types2/subst.go @@ -176,7 +176,7 @@ func (subst *subster) typ(typ Type) Type { // In this case the interface will not be substituted here, because its // method signatures do not depend on the type parameter P, but we still // need to create new interface methods to hold the instantiated - // receiver. This is handled by expandNamed. + // receiver. This is handled by Named.expandUnderlying. iface.methods, _ = replaceRecvType(methods, t, iface) return iface } @@ -207,19 +207,20 @@ func (subst *subster) typ(typ Type) Type { } } - // subst is called by expandNamed, so in this function we need to be + // subst is called during expansion, so in this function we need to be // careful not to call any methods that would cause t to be expanded: doing // so would result in deadlock. // - // So we call t.orig.TypeParams() rather than t.TypeParams() here and - // below. - if t.orig.TypeParams().Len() == 0 { + // So we call t.Origin().TypeParams() rather than t.TypeParams(). + orig := t.Origin() + n := orig.TypeParams().Len() + if n == 0 { dump(">>> %s is not parameterized", t) return t // type is not parameterized } var newTArgs []Type - if t.targs.Len() != t.orig.TypeParams().Len() { + if t.TypeArgs().Len() != n { return Typ[Invalid] // error reported elsewhere } @@ -228,14 +229,14 @@ func (subst *subster) typ(typ Type) Type { // For each (existing) type argument targ, determine if it needs // to be substituted; i.e., if it is or contains a type parameter // that has a type argument for it. - for i, targ := range t.targs.list() { + for i, targ := range t.TypeArgs().list() { dump(">>> %d targ = %s", i, targ) new_targ := subst.typ(targ) if new_targ != targ { dump(">>> substituted %d targ %s => %s", i, targ, new_targ) if newTArgs == nil { - newTArgs = make([]Type, t.orig.TypeParams().Len()) - copy(newTArgs, t.targs.list()) + newTArgs = make([]Type, n) + copy(newTArgs, t.TypeArgs().list()) } newTArgs[i] = new_targ } @@ -247,9 +248,9 @@ func (subst *subster) typ(typ Type) Type { } // before creating a new named type, check if we have this one already - h := subst.ctxt.instanceHash(t.orig, newTArgs) + h := subst.ctxt.instanceHash(orig, newTArgs) dump(">>> new type hash: %s", h) - if named := subst.ctxt.lookup(h, t.orig, newTArgs); named != nil { + if named := subst.ctxt.lookup(h, orig, newTArgs); named != nil { dump(">>> found %s", named) return named } @@ -258,7 +259,7 @@ func (subst *subster) typ(typ Type) Type { // recursion. The position used here is irrelevant because validation only // occurs on t (we don't call validType on named), but we use subst.pos to // help with debugging. - return subst.check.instance(subst.pos, t.orig, newTArgs, subst.ctxt) + return subst.check.instance(subst.pos, orig, newTArgs, subst.ctxt) // Note that if we were to expose substitution more generally (not just in // the context of a declaration), we'd have to substitute in diff --git a/src/cmd/compile/internal/types2/typestring.go b/src/cmd/compile/internal/types2/typestring.go index e0f36fcec4..c10c2d8973 100644 --- a/src/cmd/compile/internal/types2/typestring.go +++ b/src/cmd/compile/internal/types2/typestring.go @@ -284,9 +284,9 @@ func (w *typeWriter) typ(typ Type) { w.string(strconv.Itoa(w.ctxt.getID(t))) } w.typeName(t.obj) // when hashing written for readability of the hash only - if t.targs != nil { + if t.inst != nil { // instantiated type - w.typeList(t.targs.list()) + w.typeList(t.inst.targs.list()) } else if w.ctxt == nil && t.TypeParams().Len() != 0 { // For type hashing, don't need to format the TypeParams // parameterized type w.tParamList(t.TypeParams().list()) diff --git a/src/cmd/compile/internal/types2/typexpr.go b/src/cmd/compile/internal/types2/typexpr.go index 020653332d..ea13eb622d 100644 --- a/src/cmd/compile/internal/types2/typexpr.go +++ b/src/cmd/compile/internal/types2/typexpr.go @@ -444,8 +444,8 @@ func (check *Checker) instantiatedType(x syntax.Expr, xlist []syntax.Expr, def * // errors. check.recordInstance(x, inst.TypeArgs().list(), inst) - if check.validateTArgLen(x.Pos(), inst.TypeParams().Len(), inst.targs.Len()) { - if i, err := check.verify(x.Pos(), inst.TypeParams().list(), inst.targs.list()); err != nil { + if check.validateTArgLen(x.Pos(), inst.TypeParams().Len(), inst.TypeArgs().Len()) { + if i, err := check.verify(x.Pos(), inst.TypeParams().list(), inst.TypeArgs().list()); err != nil { // best position for error reporting pos := x.Pos() if i < len(xlist) { @@ -453,7 +453,7 @@ func (check *Checker) instantiatedType(x syntax.Expr, xlist []syntax.Expr, def * } check.softErrorf(pos, "%s", err) } else { - check.mono.recordInstance(check.pkg, x.Pos(), inst.TypeParams().list(), inst.targs.list(), xlist) + check.mono.recordInstance(check.pkg, x.Pos(), inst.TypeParams().list(), inst.TypeArgs().list(), xlist) } } diff --git a/src/cmd/compile/internal/types2/unify.go b/src/cmd/compile/internal/types2/unify.go index a7f68a05b1..7063789f3f 100644 --- a/src/cmd/compile/internal/types2/unify.go +++ b/src/cmd/compile/internal/types2/unify.go @@ -546,8 +546,8 @@ func (u *unifier) nify(x, y Type, p *ifacePair) (result bool) { case *Named: // TODO(gri) This code differs now from the parallel code in Checker.identical. Investigate. if y, ok := y.(*Named); ok { - xargs := x.targs.list() - yargs := y.targs.list() + xargs := x.TypeArgs().list() + yargs := y.TypeArgs().list() if len(xargs) != len(yargs) { return false diff --git a/src/cmd/compile/internal/types2/validtype.go b/src/cmd/compile/internal/types2/validtype.go index d495c6788e..b69120481b 100644 --- a/src/cmd/compile/internal/types2/validtype.go +++ b/src/cmd/compile/internal/types2/validtype.go @@ -72,7 +72,7 @@ func (check *Checker) validType0(typ Type, env *tparamEnv, path []Object) typeIn switch check.infoMap[t] { case unknown: check.infoMap[t] = marked - check.infoMap[t] = check.validType0(t.orig.fromRHS, env.push(t), append(path, t.obj)) + check.infoMap[t] = check.validType0(t.Origin().fromRHS, env.push(t), append(path, t.obj)) case marked: // We have seen type t before and thus must have a cycle. check.infoMap[t] = invalid diff --git a/src/go/types/decl.go b/src/go/types/decl.go index b5ff1214dd..c176042852 100644 --- a/src/go/types/decl.go +++ b/src/go/types/decl.go @@ -565,7 +565,7 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *ast.TypeSpec, def *Named) { } // type definition or generic type declaration - named := check.newNamed(obj, nil, nil, nil) + named := check.newNamed(obj, nil, nil) def.setUnderlying(named) if tdecl.TypeParams != nil { @@ -711,7 +711,7 @@ func (check *Checker) collectMethods(obj *TypeName) { // and field names must be distinct." base, _ := obj.typ.(*Named) // shouldn't fail but be conservative if base != nil { - assert(base.targs.Len() == 0) // collectMethods should not be called on an instantiated type + assert(base.TypeArgs().Len() == 0) // collectMethods should not be called on an instantiated type // See issue #52529: we must delay the expansion of underlying here, as // base may not be fully set-up. diff --git a/src/go/types/infer.go b/src/go/types/infer.go index 031850b8da..ebe6d8ced7 100644 --- a/src/go/types/infer.go +++ b/src/go/types/infer.go @@ -434,7 +434,7 @@ func (w *tpWalker) isParameterized(typ Type) (res bool) { return w.isParameterized(t.elem) case *Named: - return w.isParameterizedTypeList(t.targs.list()) + return w.isParameterizedTypeList(t.TypeArgs().list()) case *TypeParam: // t must be one of w.tparams diff --git a/src/go/types/instantiate.go b/src/go/types/instantiate.go index 8be0eab407..d420a61572 100644 --- a/src/go/types/instantiate.go +++ b/src/go/types/instantiate.go @@ -76,10 +76,7 @@ func (check *Checker) instance(pos token.Pos, orig Type, targs []Type, ctxt *Con switch orig := orig.(type) { case *Named: - tname := NewTypeName(pos, orig.obj.pkg, orig.obj.name, nil) - named := check.newNamed(tname, orig, nil, nil) // underlying, tparams, and methods are set when named is resolved - named.targs = newTypeList(targs) - res = named + res = check.newNamedInstance(pos, orig, targs) case *Signature: tparams := orig.TypeParams() diff --git a/src/go/types/named.go b/src/go/types/named.go index f1c5dd4f81..71a26f96a1 100644 --- a/src/go/types/named.go +++ b/src/go/types/named.go @@ -5,6 +5,7 @@ package types import ( + "go/token" "sync" "sync/atomic" ) @@ -83,14 +84,15 @@ import ( type Named struct { check *Checker // non-nil during type-checking; nil otherwise obj *TypeName // corresponding declared object for declared types; see above for instantiated types - orig *Named // origin type for instantiated types, this type for declared types - targs *TypeList // type arguments (after instantiation), or nil // fromRHS holds the type (on RHS of declaration) this *Named type is derived // from (for cycle reporting). Only used by validType, and therefore does not // require synchronization. fromRHS Type + // information for instantiated types; nil otherwise + inst *instance + mu sync.Mutex // guards all fields below state_ uint32 // the current state of this type; must only be accessed atomically underlying Type // possibly a *Named during setup; never a *Named once set up completely @@ -102,13 +104,19 @@ type Named struct { // instantiated types, methods are individually expanded when they are first // accessed. methods []*Func - // number of expanded methods (only valid for instantiated named types) - expandedMethods int // expandedMethods <= len(orig.methods) // loader may be provided to lazily load type parameters, underlying type, and methods. loader func(*Named) (tparams []*TypeParam, underlying Type, methods []*Func) } +// instance holds information that is only necessary for instantiated named +// types. +type instance struct { + orig *Named // original, uninstantiated type + targs *TypeList // type arguments + expandedMethods int // number of expanded methods; expandedMethods <= len(orig.methods) +} + // namedState represents the possible states that a named type may assume. type namedState uint32 @@ -125,7 +133,7 @@ func NewNamed(obj *TypeName, underlying Type, methods []*Func) *Named { if _, ok := underlying.(*Named); ok { panic("underlying type must not be *Named") } - return (*Checker)(nil).newNamed(obj, nil, underlying, methods) + return (*Checker)(nil).newNamed(obj, underlying, methods) } // resolve resolves the type parameters, methods, and underlying type of n. @@ -149,19 +157,20 @@ func (n *Named) resolve(ctxt *Context) *Named { return n } - if n.TypeArgs().Len() > 0 { + if n.inst != nil { assert(n.underlying == nil) // n is an unresolved instance assert(n.loader == nil) // instances are created by instantiation, in which case n.loader is nil - n.orig.resolve(ctxt) + orig := n.inst.orig + orig.resolve(ctxt) underlying := n.expandUnderlying(ctxt) - n.tparams = n.orig.tparams + n.tparams = orig.tparams n.underlying = underlying - n.fromRHS = n.orig.fromRHS // for cycle detection + n.fromRHS = orig.fromRHS // for cycle detection - if len(n.orig.methods) == 0 { - n.setState(complete) + if len(orig.methods) == 0 { + n.setState(complete) // nothing further to do } else { n.setState(resolved) } @@ -204,11 +213,8 @@ func (n *Named) setState(state namedState) { } // newNamed is like NewNamed but with a *Checker receiver and additional orig argument. -func (check *Checker) newNamed(obj *TypeName, orig *Named, underlying Type, methods []*Func) *Named { - typ := &Named{check: check, obj: obj, orig: orig, fromRHS: underlying, underlying: underlying, methods: methods} - if typ.orig == nil { - typ.orig = typ - } +func (check *Checker) newNamed(obj *TypeName, underlying Type, methods []*Func) *Named { + typ := &Named{check: check, obj: obj, fromRHS: underlying, underlying: underlying, methods: methods} if obj.typ == nil { obj.typ = typ } @@ -219,8 +225,22 @@ func (check *Checker) newNamed(obj *TypeName, orig *Named, underlying Type, meth return typ } +func (check *Checker) newNamedInstance(pos token.Pos, orig *Named, targs []Type) *Named { + assert(len(targs) > 0) + + obj := NewTypeName(pos, orig.obj.pkg, orig.obj.name, nil) + inst := &instance{orig: orig, targs: newTypeList(targs)} + typ := &Named{check: check, obj: obj, inst: inst} + obj.typ = typ + // Ensure that typ is always expanded and sanity-checked. + if check != nil { + check.needsCleanup(typ) + } + return typ +} + func (t *Named) cleanup() { - assert(t.orig.orig == t.orig) + assert(t.inst == nil || t.inst.orig.inst == nil) // Ensure that every defined type created in the course of type-checking has // either non-*Named underlying type, or is unexpanded. // @@ -243,15 +263,20 @@ func (t *Named) cleanup() { // Obj returns the type name for the declaration defining the named type t. For // instantiated types, this is same as the type name of the origin type. func (t *Named) Obj() *TypeName { - return t.orig.obj // for non-instances this is the same as t.obj + if t.inst == nil { + return t.obj + } + return t.inst.orig.obj } // Origin returns the generic type from which the named type t is // instantiated. If t is not an instantiated type, the result is t. -func (t *Named) Origin() *Named { return t.orig } - -// TODO(gri) Come up with a better representation and API to distinguish -// between parameterized instantiated and non-instantiated types. +func (t *Named) Origin() *Named { + if t.inst == nil { + return t + } + return t.inst.orig +} // TypeParams returns the type parameters of the named type t, or nil. // The result is non-nil for an (originally) generic type even if it is instantiated. @@ -260,19 +285,26 @@ func (t *Named) TypeParams() *TypeParamList { return t.resolve(nil).tparams } // SetTypeParams sets the type parameters of the named type t. // t must not have type arguments. func (t *Named) SetTypeParams(tparams []*TypeParam) { - assert(t.targs.Len() == 0) + assert(t.inst == nil) t.resolve(nil).tparams = bindTParams(tparams) } // TypeArgs returns the type arguments used to instantiate the named type t. -func (t *Named) TypeArgs() *TypeList { return t.targs } +func (t *Named) TypeArgs() *TypeList { + if t.inst == nil { + return nil + } + return t.inst.targs +} // NumMethods returns the number of explicit methods defined for t. // // For an ordinary or instantiated type t, the receiver base type of these // methods will be the named type t. For an uninstantiated generic type t, each // method receiver will be instantiated with its receiver type parameters. -func (t *Named) NumMethods() int { return len(t.orig.resolve(nil).methods) } +func (t *Named) NumMethods() int { + return len(t.Origin().resolve(nil).methods) +} // Method returns the i'th method of named type t for 0 <= i < t.NumMethods(). func (t *Named) Method(i int) *Func { @@ -282,23 +314,24 @@ func (t *Named) Method(i int) *Func { return t.methods[i] } - assert(t.TypeArgs().Len() > 0) // only instances should have incomplete methods + assert(t.inst != nil) // only instances should have incomplete methods + orig := t.inst.orig t.mu.Lock() defer t.mu.Unlock() - if len(t.methods) != len(t.orig.methods) { + if len(t.methods) != len(orig.methods) { assert(len(t.methods) == 0) - t.methods = make([]*Func, len(t.orig.methods)) + t.methods = make([]*Func, len(orig.methods)) } if t.methods[i] == nil { t.methods[i] = t.expandMethod(i) - t.expandedMethods++ + t.inst.expandedMethods++ // Check if we've created all methods at this point. If we have, mark the // type as fully expanded. - if t.expandedMethods == len(t.orig.methods) { + if t.inst.expandedMethods == len(orig.methods) { t.setState(complete) } } @@ -309,11 +342,9 @@ func (t *Named) Method(i int) *Func { // expandMethod substitutes type arguments in the i'th method for an // instantiated receiver. func (t *Named) expandMethod(i int) *Func { - assert(t.TypeArgs().Len() > 0) // t must be an instance - // t.orig.methods is not lazy. origm is the method instantiated with its // receiver type parameters (the "origin" method). - origm := t.orig.Method(i) + origm := t.inst.orig.Method(i) assert(origm != nil) check := t.check @@ -340,9 +371,9 @@ func (t *Named) expandMethod(i int) *Func { // We can only substitute if we have a correspondence between type arguments // and type parameters. This check is necessary in the presence of invalid // code. - if origSig.RecvTypeParams().Len() == t.targs.Len() { + if origSig.RecvTypeParams().Len() == t.inst.targs.Len() { ctxt := check.bestContext(nil) - smap := makeSubstMap(origSig.RecvTypeParams().list(), t.targs.list()) + smap := makeSubstMap(origSig.RecvTypeParams().list(), t.inst.targs.list()) sig = check.subst(origm.pos, origSig, smap, ctxt).(*Signature) } @@ -367,7 +398,7 @@ func (t *Named) expandMethod(i int) *Func { // SetUnderlying sets the underlying type and marks t as complete. // t must not have type arguments. func (t *Named) SetUnderlying(underlying Type) { - assert(t.targs.Len() == 0) + assert(t.inst == nil) if underlying == nil { panic("underlying type must not be nil") } @@ -383,7 +414,7 @@ func (t *Named) SetUnderlying(underlying Type) { // AddMethod adds method m unless it is already in the method list. // t must not have type arguments. func (t *Named) AddMethod(m *Func) { - assert(t.targs.Len() == 0) + assert(t.inst == nil) t.resolve(nil) if i, _ := lookupMethod(t.methods, m.pkg, m.name, false); i < 0 { t.methods = append(t.methods, m) @@ -495,7 +526,7 @@ func (n *Named) lookupMethod(pkg *Package, name string, foldCase bool) (int, *Fu // If n is an instance, we may not have yet instantiated all of its methods. // Look up the method index in orig, and only instantiate method at the // matching index (if any). - i, _ := lookupMethod(n.orig.methods, pkg, name, foldCase) + i, _ := lookupMethod(n.Origin().methods, pkg, name, foldCase) if i < 0 { return -1, nil } @@ -533,36 +564,39 @@ func (n *Named) expandUnderlying(ctxt *Context) Type { }() } - assert(n.orig.underlying != nil) + assert(n.inst.orig.underlying != nil) - if _, unexpanded := n.orig.underlying.(*Named); unexpanded { + orig := n.inst.orig + targs := n.inst.targs + + if _, unexpanded := orig.underlying.(*Named); unexpanded { // We should only get a Named underlying type here during type checking // (for example, in recursive type declarations). assert(check != nil) } - if n.orig.tparams.Len() != n.targs.Len() { + if orig.tparams.Len() != targs.Len() { // Mismatching arg and tparam length may be checked elsewhere. return Typ[Invalid] } // We must always have a context, to avoid infinite recursion. ctxt = check.bestContext(ctxt) - h := ctxt.instanceHash(n.orig, n.targs.list()) + h := ctxt.instanceHash(orig, targs.list()) // ensure that an instance is recorded for h to avoid infinite recursion. - ctxt.update(h, n.orig, n.TypeArgs().list(), n) + ctxt.update(h, orig, targs.list(), n) - smap := makeSubstMap(n.orig.tparams.list(), n.targs.list()) - underlying := n.check.subst(n.obj.pos, n.orig.underlying, smap, ctxt) + smap := makeSubstMap(orig.tparams.list(), targs.list()) + underlying := n.check.subst(n.obj.pos, orig.underlying, smap, ctxt) // If the underlying type of n is an interface, we need to set the receiver // of its methods accurately -- we set the receiver of interface methods on // the RHS of a type declaration to the defined type. if iface, _ := underlying.(*Interface); iface != nil { - if methods, copied := replaceRecvType(iface.methods, n.orig, n); copied { + if methods, copied := replaceRecvType(iface.methods, orig, n); copied { // If the underlying type doesn't actually use type parameters, it's // possible that it wasn't substituted. In this case we need to create // a new *Interface before modifying receivers. - if iface == n.orig.underlying { + if iface == orig.underlying { old := iface iface = check.newInterface() iface.embeddeds = old.embeddeds @@ -573,6 +607,7 @@ func (n *Named) expandUnderlying(ctxt *Context) Type { iface.methods = methods } } + return underlying } diff --git a/src/go/types/predicates.go b/src/go/types/predicates.go index 51d056f355..6e08b76e40 100644 --- a/src/go/types/predicates.go +++ b/src/go/types/predicates.go @@ -104,7 +104,7 @@ func isTypeParam(t Type) bool { func isGeneric(t Type) bool { // A parameterized type is only generic if it doesn't have an instantiation already. named, _ := t.(*Named) - return named != nil && named.obj != nil && named.targs == nil && named.TypeParams() != nil + return named != nil && named.obj != nil && named.inst == nil && named.TypeParams().Len() > 0 } // Comparable reports whether values of type T are comparable. @@ -403,7 +403,7 @@ func identical(x, y Type, cmpTags bool, p *ifacePair) bool { if len(xargs) > 0 { // Instances are identical if their original type and type arguments // are identical. - if !Identical(x.orig, y.orig) { + if !Identical(x.Origin(), y.Origin()) { return false } for i, xa := range xargs { diff --git a/src/go/types/sizeof_test.go b/src/go/types/sizeof_test.go index 66a69521d2..d4ce0a7fcd 100644 --- a/src/go/types/sizeof_test.go +++ b/src/go/types/sizeof_test.go @@ -30,7 +30,7 @@ func TestSizeof(t *testing.T) { {Interface{}, 40, 80}, {Map{}, 16, 32}, {Chan{}, 12, 24}, - {Named{}, 68, 128}, + {Named{}, 60, 112}, {TypeParam{}, 28, 48}, {term{}, 12, 24}, diff --git a/src/go/types/subst.go b/src/go/types/subst.go index 63849b9212..110298cbae 100644 --- a/src/go/types/subst.go +++ b/src/go/types/subst.go @@ -176,7 +176,7 @@ func (subst *subster) typ(typ Type) Type { // In this case the interface will not be substituted here, because its // method signatures do not depend on the type parameter P, but we still // need to create new interface methods to hold the instantiated - // receiver. This is handled by expandNamed. + // receiver. This is handled by Named.expandUnderlying. iface.methods, _ = replaceRecvType(methods, t, iface) return iface } @@ -207,19 +207,20 @@ func (subst *subster) typ(typ Type) Type { } } - // subst is called by expandNamed, so in this function we need to be + // subst is called during expansion, so in this function we need to be // careful not to call any methods that would cause t to be expanded: doing // so would result in deadlock. // - // So we call t.orig.TypeParams() rather than t.TypeParams() here and - // below. - if t.orig.TypeParams().Len() == 0 { + // So we call t.Origin().TypeParams() rather than t.TypeParams(). + orig := t.Origin() + n := orig.TypeParams().Len() + if n == 0 { dump(">>> %s is not parameterized", t) return t // type is not parameterized } var newTArgs []Type - if t.targs.Len() != t.orig.TypeParams().Len() { + if t.TypeArgs().Len() != n { return Typ[Invalid] // error reported elsewhere } @@ -228,14 +229,14 @@ func (subst *subster) typ(typ Type) Type { // For each (existing) type argument targ, determine if it needs // to be substituted; i.e., if it is or contains a type parameter // that has a type argument for it. - for i, targ := range t.targs.list() { + for i, targ := range t.TypeArgs().list() { dump(">>> %d targ = %s", i, targ) new_targ := subst.typ(targ) if new_targ != targ { dump(">>> substituted %d targ %s => %s", i, targ, new_targ) if newTArgs == nil { - newTArgs = make([]Type, t.orig.TypeParams().Len()) - copy(newTArgs, t.targs.list()) + newTArgs = make([]Type, n) + copy(newTArgs, t.TypeArgs().list()) } newTArgs[i] = new_targ } @@ -247,9 +248,9 @@ func (subst *subster) typ(typ Type) Type { } // before creating a new named type, check if we have this one already - h := subst.ctxt.instanceHash(t.orig, newTArgs) + h := subst.ctxt.instanceHash(orig, newTArgs) dump(">>> new type hash: %s", h) - if named := subst.ctxt.lookup(h, t.orig, newTArgs); named != nil { + if named := subst.ctxt.lookup(h, orig, newTArgs); named != nil { dump(">>> found %s", named) return named } @@ -258,7 +259,7 @@ func (subst *subster) typ(typ Type) Type { // recursion. The position used here is irrelevant because validation only // occurs on t (we don't call validType on named), but we use subst.pos to // help with debugging. - return subst.check.instance(subst.pos, t.orig, newTArgs, subst.ctxt) + return subst.check.instance(subst.pos, orig, newTArgs, subst.ctxt) // Note that if we were to expose substitution more generally (not just in // the context of a declaration), we'd have to substitute in diff --git a/src/go/types/typestring.go b/src/go/types/typestring.go index 0325d4a77f..5a2e2c171a 100644 --- a/src/go/types/typestring.go +++ b/src/go/types/typestring.go @@ -285,9 +285,9 @@ func (w *typeWriter) typ(typ Type) { w.string(strconv.Itoa(w.ctxt.getID(t))) } w.typeName(t.obj) // when hashing written for readability of the hash only - if t.targs != nil { + if t.inst != nil { // instantiated type - w.typeList(t.targs.list()) + w.typeList(t.inst.targs.list()) } else if w.ctxt == nil && t.TypeParams().Len() != 0 { // For type hashing, don't need to format the TypeParams // parameterized type w.tParamList(t.TypeParams().list()) diff --git a/src/go/types/typexpr.go b/src/go/types/typexpr.go index c7161e00a5..05bd51a82b 100644 --- a/src/go/types/typexpr.go +++ b/src/go/types/typexpr.go @@ -428,8 +428,8 @@ func (check *Checker) instantiatedType(ix *typeparams.IndexExpr, def *Named) (re // errors. check.recordInstance(ix.Orig, inst.TypeArgs().list(), inst) - if check.validateTArgLen(ix.Pos(), inst.TypeParams().Len(), inst.targs.Len()) { - if i, err := check.verify(ix.Pos(), inst.TypeParams().list(), inst.targs.list()); err != nil { + if check.validateTArgLen(ix.Pos(), inst.TypeParams().Len(), inst.TypeArgs().Len()) { + if i, err := check.verify(ix.Pos(), inst.TypeParams().list(), inst.TypeArgs().list()); err != nil { // best position for error reporting pos := ix.Pos() if i < len(ix.Indices) { @@ -437,7 +437,7 @@ func (check *Checker) instantiatedType(ix *typeparams.IndexExpr, def *Named) (re } check.softErrorf(atPos(pos), _InvalidTypeArg, err.Error()) } else { - check.mono.recordInstance(check.pkg, ix.Pos(), inst.TypeParams().list(), inst.targs.list(), ix.Indices) + check.mono.recordInstance(check.pkg, ix.Pos(), inst.TypeParams().list(), inst.TypeArgs().list(), ix.Indices) } } diff --git a/src/go/types/unify.go b/src/go/types/unify.go index 0742e40d8b..602e304b4a 100644 --- a/src/go/types/unify.go +++ b/src/go/types/unify.go @@ -546,8 +546,8 @@ func (u *unifier) nify(x, y Type, p *ifacePair) (result bool) { case *Named: // TODO(gri) This code differs now from the parallel code in Checker.identical. Investigate. if y, ok := y.(*Named); ok { - xargs := x.targs.list() - yargs := y.targs.list() + xargs := x.TypeArgs().list() + yargs := y.TypeArgs().list() if len(xargs) != len(yargs) { return false diff --git a/src/go/types/validtype.go b/src/go/types/validtype.go index edb4c02ecd..0d7a0f308c 100644 --- a/src/go/types/validtype.go +++ b/src/go/types/validtype.go @@ -71,7 +71,7 @@ func (check *Checker) validType0(typ Type, env *tparamEnv, path []Object) typeIn switch check.infoMap[t] { case unknown: check.infoMap[t] = marked - check.infoMap[t] = check.validType0(t.orig.fromRHS, env.push(t), append(path, t.obj)) + check.infoMap[t] = check.validType0(t.Origin().fromRHS, env.push(t), append(path, t.obj)) case marked: // We have seen type t before and thus must have a cycle. check.infoMap[t] = invalid -- cgit v1.3-5-g9baa From 47e34ca533b118bd47061b15fc7918563f4837a7 Mon Sep 17 00:00:00 2001 From: Robert Findley Date: Sat, 7 May 2022 21:22:17 -0400 Subject: go/types, types2: ensure that named types never expand infinitely During type-checking, newly created instances share a type checking Context which de-duplicates identical instances. However, when unexpanded types escape the type-checking pass or are created via calls to Instantiate, they lack this shared context. As reported in #52728, this may lead to infinitely many identical but distinct types that are reachable via the API. This CL introduces a new invariant that ensures we don't create such infinitely expanding chains: instances created during expansion share a context with the type that led to their creation. During expansion, the expanding type passes its Context to any newly created instances. This ensures that cycles will eventually terminate with a previously seen instance. For example, if we have an instantiation chain T1[P]->T2[P]->T3[P]->T1[P], by virtue of this Context passing the expansion of T3[P] will find the instantiation T1[P]. In general, storing a Context in a Named type could lead to pinning types in memory unnecessarily, but in this case the Context pins only those types that are reachable from the original instance. This seems like a reasonable compromise between lazy and eager expansion. Our treatment of Context was a little haphazard: Checker.bestContext made it easy to get a context at any point, but made it harder to reason about which context is being used. To fix this, replace bestContext with Checker.context, which returns the type-checking context and panics on a nil receiver. Update all call-sites to verify that the Checker is non-nil when context is called. Also make it a panic to call subst with a nil context. Instead, update subst to explicitly accept a local (=instance) context along with a global context, and require that one of them is non-nil. Thread this through to the call to Checker.instance, and handle context updating there. Fixes #52728 Change-Id: Ib7f26eb8c406290325bc3212fda25421a37a1e8e Reviewed-on: https://go-review.googlesource.com/c/go/+/404885 Reviewed-by: Robert Griesemer TryBot-Result: Gopher Robot Run-TryBot: Robert Findley --- src/cmd/compile/internal/types2/call.go | 6 +- src/cmd/compile/internal/types2/infer.go | 10 +-- src/cmd/compile/internal/types2/instantiate.go | 83 ++++++++++++++------ src/cmd/compile/internal/types2/named.go | 102 +++++++++++++++---------- src/cmd/compile/internal/types2/named_test.go | 45 +++++++++++ src/cmd/compile/internal/types2/predicates.go | 9 ++- src/cmd/compile/internal/types2/signature.go | 2 +- src/cmd/compile/internal/types2/subst.go | 37 ++++----- src/cmd/compile/internal/types2/typexpr.go | 5 +- src/go/types/call.go | 6 +- src/go/types/infer.go | 10 +-- src/go/types/instantiate.go | 83 ++++++++++++++------ src/go/types/named.go | 102 +++++++++++++++---------- src/go/types/named_test.go | 48 ++++++++++++ src/go/types/predicates.go | 9 ++- src/go/types/signature.go | 2 +- src/go/types/subst.go | 37 ++++----- src/go/types/typexpr.go | 5 +- 18 files changed, 389 insertions(+), 212 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/types2/call.go b/src/cmd/compile/internal/types2/call.go index 3ade147dfe..b1ea6917fb 100644 --- a/src/cmd/compile/internal/types2/call.go +++ b/src/cmd/compile/internal/types2/call.go @@ -72,13 +72,13 @@ func (check *Checker) instantiateSignature(pos syntax.Pos, typ *Signature, targs }() } - inst := check.instance(pos, typ, targs, check.bestContext(nil)).(*Signature) + inst := check.instance(pos, typ, targs, nil, check.context()).(*Signature) assert(len(xlist) <= len(targs)) // verify instantiation lazily (was issue #50450) check.later(func() { tparams := typ.TypeParams().list() - if i, err := check.verify(pos, tparams, targs); err != nil { + if i, err := check.verify(pos, tparams, targs, check.context()); err != nil { // best position for error reporting pos := pos if i < len(xlist) { @@ -395,7 +395,7 @@ func (check *Checker) arguments(call *syntax.CallExpr, sig *Signature, targs []T // need to compute it from the adjusted list; otherwise we can // simply use the result signature's parameter list. if adjusted { - sigParams = check.subst(call.Pos(), sigParams, makeSubstMap(sig.TypeParams().list(), targs), nil).(*Tuple) + sigParams = check.subst(call.Pos(), sigParams, makeSubstMap(sig.TypeParams().list(), targs), nil, check.context()).(*Tuple) } else { sigParams = rsig.params } diff --git a/src/cmd/compile/internal/types2/infer.go b/src/cmd/compile/internal/types2/infer.go index 8ab568be59..b0c6a4fcea 100644 --- a/src/cmd/compile/internal/types2/infer.go +++ b/src/cmd/compile/internal/types2/infer.go @@ -110,11 +110,11 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type, renameMap := makeRenameMap(tparams, tparams2) for i, tparam := range tparams { - tparams2[i].bound = check.subst(pos, tparam.bound, renameMap, nil) + tparams2[i].bound = check.subst(pos, tparam.bound, renameMap, nil, check.context()) } tparams = tparams2 - params = check.subst(pos, params, renameMap, nil).(*Tuple) + params = check.subst(pos, params, renameMap, nil, check.context()).(*Tuple) } } @@ -188,7 +188,7 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type, // but that doesn't impact the isParameterized check for now). if params.Len() > 0 { smap := makeSubstMap(tparams, targs) - params = check.subst(nopos, params, smap, nil).(*Tuple) + params = check.subst(nopos, params, smap, nil, check.context()).(*Tuple) } // Unify parameter and argument types for generic parameters with typed arguments @@ -224,7 +224,7 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type, } } smap := makeSubstMap(tparams, targs) - inferred := check.subst(arg.Pos(), tpar, smap, nil) + inferred := check.subst(arg.Pos(), tpar, smap, nil, check.context()) if inferred != tpar { check.errorf(arg, "%s %s of %s does not match inferred type %s for %s", kind, targ, arg.expr, inferred, tpar) } else { @@ -626,7 +626,7 @@ func (check *Checker) inferB(pos syntax.Pos, tparams []*TypeParam, targs []Type) n := 0 for _, index := range dirty { t0 := types[index] - if t1 := check.subst(nopos, t0, smap, nil); t1 != t0 { + if t1 := check.subst(nopos, t0, smap, nil, check.context()); t1 != t0 { types[index] = t1 dirty[n] = index n++ diff --git a/src/cmd/compile/internal/types2/instantiate.go b/src/cmd/compile/internal/types2/instantiate.go index ed6206e150..f338e28d2e 100644 --- a/src/cmd/compile/internal/types2/instantiate.go +++ b/src/cmd/compile/internal/types2/instantiate.go @@ -40,6 +40,9 @@ import ( // count is incorrect; for *Named types, a panic may occur later inside the // *Named API. func Instantiate(ctxt *Context, orig Type, targs []Type, validate bool) (Type, error) { + if ctxt == nil { + ctxt = NewContext() + } if validate { var tparams []*TypeParam switch t := orig.(type) { @@ -51,34 +54,71 @@ func Instantiate(ctxt *Context, orig Type, targs []Type, validate bool) (Type, e if len(targs) != len(tparams) { return nil, fmt.Errorf("got %d type arguments but %s has %d type parameters", len(targs), orig, len(tparams)) } - if i, err := (*Checker)(nil).verify(nopos, tparams, targs); err != nil { + if i, err := (*Checker)(nil).verify(nopos, tparams, targs, ctxt); err != nil { return nil, &ArgumentError{i, err} } } - inst := (*Checker)(nil).instance(nopos, orig, targs, ctxt) + inst := (*Checker)(nil).instance(nopos, orig, targs, nil, ctxt) return inst, nil } -// instance creates a type or function instance using the given original type -// typ and arguments targs. For Named types the resulting instance will be -// unexpanded. check may be nil. -func (check *Checker) instance(pos syntax.Pos, orig Type, targs []Type, ctxt *Context) (res Type) { - var h string - if ctxt != nil { - h = ctxt.instanceHash(orig, targs) - // typ may already have been instantiated with identical type arguments. In - // that case, re-use the existing instance. - if inst := ctxt.lookup(h, orig, targs); inst != nil { - return inst +// instance resolves a type or function instance for the given original type +// and type arguments. It looks for an existing identical instance in the given +// contexts, creating a new instance if none is found. +// +// If local is non-nil, it is the context associated with a Named instance +// type currently being expanded. If global is non-nil, it is the context +// associated with the current type-checking pass or call to Instantiate. At +// least one of local or global must be non-nil. +// +// For Named types the resulting instance may be unexpanded. +func (check *Checker) instance(pos syntax.Pos, orig Type, targs []Type, local, global *Context) (res Type) { + // The order of the contexts below matters: we always prefer instances in + // local in order to preserve reference cycles. + // + // Invariant: if local != nil, the returned instance will be the instance + // recorded in local. + var ctxts []*Context + if local != nil { + ctxts = append(ctxts, local) + } + if global != nil { + ctxts = append(ctxts, global) + } + assert(len(ctxts) > 0) + + // Compute all hashes; hashes may differ across contexts due to different + // unique IDs for Named types within the hasher. + hashes := make([]string, len(ctxts)) + for i, ctxt := range ctxts { + hashes[i] = ctxt.instanceHash(orig, targs) + } + + // If local is non-nil, updateContexts return the type recorded in + // local. + updateContexts := func(res Type) Type { + for i := len(ctxts) - 1; i >= 0; i-- { + res = ctxts[i].update(hashes[i], orig, targs, res) + } + return res + } + + // typ may already have been instantiated with identical type arguments. In + // that case, re-use the existing instance. + for i, ctxt := range ctxts { + if inst := ctxt.lookup(hashes[i], orig, targs); inst != nil { + return updateContexts(inst) } } switch orig := orig.(type) { case *Named: - res = check.newNamedInstance(pos, orig, targs) + res = check.newNamedInstance(pos, orig, targs, local) // substituted lazily case *Signature: + assert(local == nil) // function instances cannot be reached from Named types + tparams := orig.TypeParams() if !check.validateTArgLen(pos, tparams.Len(), len(targs)) { return Typ[Invalid] @@ -86,7 +126,7 @@ func (check *Checker) instance(pos syntax.Pos, orig Type, targs []Type, ctxt *Co if tparams.Len() == 0 { return orig // nothing to do (minor optimization) } - sig := check.subst(pos, orig, makeSubstMap(tparams.list(), targs), ctxt).(*Signature) + sig := check.subst(pos, orig, makeSubstMap(tparams.list(), targs), nil, global).(*Signature) // If the signature doesn't use its type parameters, subst // will not make a copy. In that case, make a copy now (so // we can set tparams to nil w/o causing side-effects). @@ -104,13 +144,8 @@ func (check *Checker) instance(pos syntax.Pos, orig Type, targs []Type, ctxt *Co panic(fmt.Sprintf("%v: cannot instantiate %v", pos, orig)) } - if ctxt != nil { - // It's possible that we've lost a race to add named to the context. - // In this case, use whichever instance is recorded in the context. - res = ctxt.update(h, orig, targs, res) - } - - return res + // Update all contexts; it's possible that we've lost a race. + return updateContexts(res) } // validateTArgLen verifies that the length of targs and tparams matches, @@ -128,7 +163,7 @@ func (check *Checker) validateTArgLen(pos syntax.Pos, ntparams, ntargs int) bool return true } -func (check *Checker) verify(pos syntax.Pos, tparams []*TypeParam, targs []Type) (int, error) { +func (check *Checker) verify(pos syntax.Pos, tparams []*TypeParam, targs []Type, ctxt *Context) (int, error) { smap := makeSubstMap(tparams, targs) for i, tpar := range tparams { // Ensure that we have a (possibly implicit) interface as type bound (issue #51048). @@ -137,7 +172,7 @@ func (check *Checker) verify(pos syntax.Pos, tparams []*TypeParam, targs []Type) // as the instantiated type; before we can use it for bounds checking we // need to instantiate it with the type arguments with which we instantiated // the parameterized type. - bound := check.subst(pos, tpar.bound, smap, nil) + bound := check.subst(pos, tpar.bound, smap, nil, ctxt) if err := check.implements(targs[i], bound); err != nil { return i, err } diff --git a/src/cmd/compile/internal/types2/named.go b/src/cmd/compile/internal/types2/named.go index 133fb6fa88..720e500cd5 100644 --- a/src/cmd/compile/internal/types2/named.go +++ b/src/cmd/compile/internal/types2/named.go @@ -79,6 +79,16 @@ import ( // Identical to compare them. For instantiated named types, their obj is a // synthetic placeholder that records their position of the corresponding // instantiation in the source (if they were constructed during type checking). +// +// To prevent infinite expansion of named instances that are created outside of +// type-checking, instances share a Context with other instances created during +// their expansion. Via the pidgeonhole principle, this guarantees that in the +// presence of a cycle of named types, expansion will eventually find an +// existing instance in the Context and short-circuit the expansion. +// +// Once an instance is complete, we can nil out this shared Context to unpin +// memory, though this Context may still be held by other incomplete instances +// in its "lineage". // A Named represents a named (defined) type. type Named struct { @@ -115,6 +125,7 @@ type instance struct { orig *Named // original, uninstantiated type targs *TypeList // type arguments expandedMethods int // number of expanded methods; expandedMethods <= len(orig.methods) + ctxt *Context // local Context; set to nil after full expansion } // namedState represents the possible states that a named type may assume. @@ -143,7 +154,7 @@ func NewNamed(obj *TypeName, underlying Type, methods []*Func) *Named { // After resolution, the type parameters, methods, and underlying type of n are // accessible; but if n is an instantiated type, its methods may still be // unexpanded. -func (n *Named) resolve(ctxt *Context) *Named { +func (n *Named) resolve() *Named { if n.state() >= resolved { // avoid locking below return n } @@ -162,8 +173,8 @@ func (n *Named) resolve(ctxt *Context) *Named { assert(n.loader == nil) // instances are created by instantiation, in which case n.loader is nil orig := n.inst.orig - orig.resolve(ctxt) - underlying := n.expandUnderlying(ctxt) + orig.resolve() + underlying := n.expandUnderlying() n.tparams = orig.tparams n.underlying = underlying @@ -171,6 +182,7 @@ func (n *Named) resolve(ctxt *Context) *Named { if len(orig.methods) == 0 { n.setState(complete) // nothing further to do + n.inst.ctxt = nil } else { n.setState(resolved) } @@ -225,11 +237,11 @@ func (check *Checker) newNamed(obj *TypeName, underlying Type, methods []*Func) return typ } -func (check *Checker) newNamedInstance(pos syntax.Pos, orig *Named, targs []Type) *Named { +func (check *Checker) newNamedInstance(pos syntax.Pos, orig *Named, targs []Type, local *Context) *Named { assert(len(targs) > 0) obj := NewTypeName(pos, orig.obj.pkg, orig.obj.name, nil) - inst := &instance{orig: orig, targs: newTypeList(targs)} + inst := &instance{orig: orig, targs: newTypeList(targs), ctxt: local} typ := &Named{check: check, obj: obj, inst: inst} obj.typ = typ // Ensure that typ is always expanded and sanity-checked. @@ -280,13 +292,13 @@ func (t *Named) Origin() *Named { // TypeParams returns the type parameters of the named type t, or nil. // The result is non-nil for an (originally) generic type even if it is instantiated. -func (t *Named) TypeParams() *TypeParamList { return t.resolve(nil).tparams } +func (t *Named) TypeParams() *TypeParamList { return t.resolve().tparams } // SetTypeParams sets the type parameters of the named type t. // t must not have type arguments. func (t *Named) SetTypeParams(tparams []*TypeParam) { assert(t.inst == nil) - t.resolve(nil).tparams = bindTParams(tparams) + t.resolve().tparams = bindTParams(tparams) } // TypeArgs returns the type arguments used to instantiate the named type t. @@ -298,17 +310,17 @@ func (t *Named) TypeArgs() *TypeList { } // NumMethods returns the number of explicit methods defined for t. -// -// For an ordinary or instantiated type t, the receiver base type of these -// methods will be the named type t. For an uninstantiated generic type t, each -// method receiver will be instantiated with its receiver type parameters. func (t *Named) NumMethods() int { - return len(t.Origin().resolve(nil).methods) + return len(t.Origin().resolve().methods) } // Method returns the i'th method of named type t for 0 <= i < t.NumMethods(). +// +// For an ordinary or instantiated type t, the receiver base type of this +// method is the named type t. For an uninstantiated generic type t, each +// method receiver is instantiated with its receiver type parameters. func (t *Named) Method(i int) *Func { - t.resolve(nil) + t.resolve() if t.state() >= complete { return t.methods[i] @@ -326,6 +338,7 @@ func (t *Named) Method(i int) *Func { } if t.methods[i] == nil { + assert(t.inst.ctxt != nil) // we should still have a context remaining from the resolution phase t.methods[i] = t.expandMethod(i) t.inst.expandedMethods++ @@ -333,6 +346,7 @@ func (t *Named) Method(i int) *Func { // type as fully expanded. if t.inst.expandedMethods == len(orig.methods) { t.setState(complete) + t.inst.ctxt = nil // no need for a context anymore } } @@ -372,9 +386,12 @@ func (t *Named) expandMethod(i int) *Func { // and type parameters. This check is necessary in the presence of invalid // code. if origSig.RecvTypeParams().Len() == t.inst.targs.Len() { - ctxt := check.bestContext(nil) smap := makeSubstMap(origSig.RecvTypeParams().list(), t.inst.targs.list()) - sig = check.subst(origm.pos, origSig, smap, ctxt).(*Signature) + var global *Context + if check != nil { + global = check.context() + } + sig = check.subst(origm.pos, origSig, smap, t.inst.ctxt, global).(*Signature) } if sig == origSig { @@ -405,7 +422,7 @@ func (t *Named) SetUnderlying(underlying Type) { if _, ok := underlying.(*Named); ok { panic("underlying type must not be *Named") } - t.resolve(nil).underlying = underlying + t.resolve().underlying = underlying if t.fromRHS == nil { t.fromRHS = underlying // for cycle detection } @@ -415,17 +432,20 @@ func (t *Named) SetUnderlying(underlying Type) { // t must not have type arguments. func (t *Named) AddMethod(m *Func) { assert(t.inst == nil) - t.resolve(nil) + t.resolve() if i, _ := lookupMethod(t.methods, m.pkg, m.name, false); i < 0 { t.methods = append(t.methods, m) } } -func (t *Named) Underlying() Type { return t.resolve(nil).underlying } +func (t *Named) Underlying() Type { return t.resolve().underlying } func (t *Named) String() string { return TypeString(t, nil) } // ---------------------------------------------------------------------------- // Implementation +// +// TODO(rfindley): reorganize the loading and expansion methods under this +// heading. // under returns the expanded underlying type of n0; possibly by following // forward chains of named types. If an underlying type is found, resolve @@ -522,7 +542,7 @@ func (n *Named) setUnderlying(typ Type) { } func (n *Named) lookupMethod(pkg *Package, name string, foldCase bool) (int, *Func) { - n.resolve(nil) + n.resolve() // If n is an instance, we may not have yet instantiated all of its methods. // Look up the method index in orig, and only instantiate method at the // matching index (if any). @@ -534,26 +554,17 @@ func (n *Named) lookupMethod(pkg *Package, name string, foldCase bool) (int, *Fu return i, n.Method(i) } -// bestContext returns the best available context. In order of preference: -// - the given ctxt, if non-nil -// - check.ctxt, if check is non-nil -// - a new Context -func (check *Checker) bestContext(ctxt *Context) *Context { - if ctxt != nil { - return ctxt +// context returns the type-checker context. +func (check *Checker) context() *Context { + if check.ctxt == nil { + check.ctxt = NewContext() } - if check != nil { - if check.ctxt == nil { - check.ctxt = NewContext() - } - return check.ctxt - } - return NewContext() + return check.ctxt } // expandUnderlying substitutes type arguments in the underlying type n.orig, // returning the result. Returns Typ[Invalid] if there was an error. -func (n *Named) expandUnderlying(ctxt *Context) Type { +func (n *Named) expandUnderlying() Type { check := n.check if check != nil && check.conf.Trace { check.trace(n.obj.pos, "-- Named.expandUnderlying %s", n) @@ -565,6 +576,9 @@ func (n *Named) expandUnderlying(ctxt *Context) Type { } assert(n.inst.orig.underlying != nil) + if n.inst.ctxt == nil { + n.inst.ctxt = NewContext() + } orig := n.inst.orig targs := n.inst.targs @@ -580,16 +594,20 @@ func (n *Named) expandUnderlying(ctxt *Context) Type { return Typ[Invalid] } - // We must always have a context, to avoid infinite recursion. - ctxt = check.bestContext(ctxt) - h := ctxt.instanceHash(orig, targs.list()) - // ensure that an instance is recorded for h to avoid infinite recursion. - ctxt.update(h, orig, targs.list(), n) + // Ensure that an instance is recorded before substituting, so that we + // resolve n for any recursive references. + h := n.inst.ctxt.instanceHash(orig, targs.list()) + n2 := n.inst.ctxt.update(h, orig, n.TypeArgs().list(), n) + assert(n == n2) smap := makeSubstMap(orig.tparams.list(), targs.list()) - underlying := n.check.subst(n.obj.pos, orig.underlying, smap, ctxt) - // If the underlying type of n is an interface, we need to set the receiver - // of its methods accurately -- we set the receiver of interface methods on + var global *Context + if check != nil { + global = check.context() + } + underlying := n.check.subst(n.obj.pos, orig.underlying, smap, n.inst.ctxt, global) + // If the underlying type of n is an interface, we need to set the receiver of + // its methods accurately -- we set the receiver of interface methods on // the RHS of a type declaration to the defined type. if iface, _ := underlying.(*Interface); iface != nil { if methods, copied := replaceRecvType(iface.methods, orig, n); copied { diff --git a/src/cmd/compile/internal/types2/named_test.go b/src/cmd/compile/internal/types2/named_test.go index 14a982048a..e5e8eddb05 100644 --- a/src/cmd/compile/internal/types2/named_test.go +++ b/src/cmd/compile/internal/types2/named_test.go @@ -7,6 +7,7 @@ package types2_test import ( "testing" + "cmd/compile/internal/syntax" . "cmd/compile/internal/types2" ) @@ -73,3 +74,47 @@ func mustInstantiate(tb testing.TB, orig Type, targs ...Type) Type { } return inst } + +// Test that types do not expand infinitely, as in golang/go#52715. +func TestFiniteTypeExpansion(t *testing.T) { + const src = ` +package p + +type Tree[T any] struct { + *Node[T] +} + +func (*Tree[R]) N(r R) R { return r } + +type Node[T any] struct { + *Tree[T] +} + +func (Node[Q]) M(Q) {} + +type Inst = *Tree[int] +` + + f, err := parseSrc("foo.go", src) + if err != nil { + t.Fatal(err) + } + pkg := NewPackage("p", f.PkgName.Value) + if err := NewChecker(nil, pkg, nil).Files([]*syntax.File{f}); err != nil { + t.Fatal(err) + } + + firstFieldType := func(n *Named) *Named { + return n.Underlying().(*Struct).Field(0).Type().(*Pointer).Elem().(*Named) + } + + Inst := pkg.Scope().Lookup("Inst").Type().(*Pointer).Elem().(*Named) + Node := firstFieldType(Inst) + Tree := firstFieldType(Node) + if !Identical(Inst, Tree) { + t.Fatalf("Not a cycle: got %v, want %v", Tree, Inst) + } + if Inst != Tree { + t.Errorf("Duplicate instances in cycle: %s (%p) -> %s (%p) -> %s (%p)", Inst, Inst, Node, Node, Tree, Tree) + } +} diff --git a/src/cmd/compile/internal/types2/predicates.go b/src/cmd/compile/internal/types2/predicates.go index 6b6c21c780..f7b5b16204 100644 --- a/src/cmd/compile/internal/types2/predicates.go +++ b/src/cmd/compile/internal/types2/predicates.go @@ -283,18 +283,19 @@ func identical(x, y Type, cmpTags bool, p *ifacePair) bool { } smap := makeSubstMap(ytparams, targs) - var check *Checker // ok to call subst on a nil *Checker + var check *Checker // ok to call subst on a nil *Checker + ctxt := NewContext() // need a non-nil Context for the substitution below // Constraints must be pair-wise identical, after substitution. for i, xtparam := range xtparams { - ybound := check.subst(nopos, ytparams[i].bound, smap, nil) + ybound := check.subst(nopos, ytparams[i].bound, smap, nil, ctxt) if !identical(xtparam.bound, ybound, cmpTags, p) { return false } } - yparams = check.subst(nopos, y.params, smap, nil).(*Tuple) - yresults = check.subst(nopos, y.results, smap, nil).(*Tuple) + yparams = check.subst(nopos, y.params, smap, nil, ctxt).(*Tuple) + yresults = check.subst(nopos, y.results, smap, nil, ctxt).(*Tuple) } return x.variadic == y.variadic && diff --git a/src/cmd/compile/internal/types2/signature.go b/src/cmd/compile/internal/types2/signature.go index 92d3aadf88..1b61b368d2 100644 --- a/src/cmd/compile/internal/types2/signature.go +++ b/src/cmd/compile/internal/types2/signature.go @@ -143,7 +143,7 @@ func (check *Checker) funcType(sig *Signature, recvPar *syntax.Field, tparams [] // recvTPar.bound is (possibly) parameterized in the context of the // receiver type declaration. Substitute parameters for the current // context. - tpar.bound = check.subst(tpar.obj.pos, recvTPar.bound, smap, nil) + tpar.bound = check.subst(tpar.obj.pos, recvTPar.bound, smap, nil, check.context()) } } else if len(tparams) < len(recvTParams) { // Reporting an error here is a stop-gap measure to avoid crashes in the diff --git a/src/cmd/compile/internal/types2/subst.go b/src/cmd/compile/internal/types2/subst.go index 9af1a71cfe..4a4c8f960a 100644 --- a/src/cmd/compile/internal/types2/subst.go +++ b/src/cmd/compile/internal/types2/subst.go @@ -49,7 +49,9 @@ func (m substMap) lookup(tpar *TypeParam) Type { // from the incoming type. // // If the given context is non-nil, it is used in lieu of check.Config.Context. -func (check *Checker) subst(pos syntax.Pos, typ Type, smap substMap, ctxt *Context) Type { +func (check *Checker) subst(pos syntax.Pos, typ Type, smap substMap, local, global *Context) Type { + assert(local != nil || global != nil) + if smap.empty() { return typ } @@ -64,19 +66,20 @@ func (check *Checker) subst(pos syntax.Pos, typ Type, smap substMap, ctxt *Conte // general case subst := subster{ - pos: pos, - smap: smap, - check: check, - ctxt: check.bestContext(ctxt), + pos: pos, + smap: smap, + check: check, + local: local, + global: global, } return subst.typ(typ) } type subster struct { - pos syntax.Pos - smap substMap - check *Checker // nil if called via Instantiate - ctxt *Context + pos syntax.Pos + smap substMap + check *Checker // nil if called via Instantiate + local, global *Context } func (subst *subster) typ(typ Type) Type { @@ -247,25 +250,11 @@ func (subst *subster) typ(typ Type) Type { return t // nothing to substitute } - // before creating a new named type, check if we have this one already - h := subst.ctxt.instanceHash(orig, newTArgs) - dump(">>> new type hash: %s", h) - if named := subst.ctxt.lookup(h, orig, newTArgs); named != nil { - dump(">>> found %s", named) - return named - } - // Create a new instance and populate the context to avoid endless // recursion. The position used here is irrelevant because validation only // occurs on t (we don't call validType on named), but we use subst.pos to // help with debugging. - return subst.check.instance(subst.pos, orig, newTArgs, subst.ctxt) - - // Note that if we were to expose substitution more generally (not just in - // the context of a declaration), we'd have to substitute in - // named.underlying as well. - // - // But this is unnecessary for now. + return subst.check.instance(subst.pos, orig, newTArgs, subst.local, subst.global) case *TypeParam: return subst.smap.lookup(t) diff --git a/src/cmd/compile/internal/types2/typexpr.go b/src/cmd/compile/internal/types2/typexpr.go index ea13eb622d..f0cd236050 100644 --- a/src/cmd/compile/internal/types2/typexpr.go +++ b/src/cmd/compile/internal/types2/typexpr.go @@ -433,8 +433,7 @@ func (check *Checker) instantiatedType(x syntax.Expr, xlist []syntax.Expr, def * } // create the instance - ctxt := check.bestContext(nil) - inst := check.instance(x.Pos(), orig, targs, ctxt).(*Named) + inst := check.instance(x.Pos(), orig, targs, nil, check.context()).(*Named) def.setUnderlying(inst) // orig.tparams may not be set up, so we need to do expansion later. @@ -445,7 +444,7 @@ func (check *Checker) instantiatedType(x syntax.Expr, xlist []syntax.Expr, def * check.recordInstance(x, inst.TypeArgs().list(), inst) if check.validateTArgLen(x.Pos(), inst.TypeParams().Len(), inst.TypeArgs().Len()) { - if i, err := check.verify(x.Pos(), inst.TypeParams().list(), inst.TypeArgs().list()); err != nil { + if i, err := check.verify(x.Pos(), inst.TypeParams().list(), inst.TypeArgs().list(), check.context()); err != nil { // best position for error reporting pos := x.Pos() if i < len(xlist) { diff --git a/src/go/types/call.go b/src/go/types/call.go index 3c7c3226f6..c580885a5a 100644 --- a/src/go/types/call.go +++ b/src/go/types/call.go @@ -73,13 +73,13 @@ func (check *Checker) instantiateSignature(pos token.Pos, typ *Signature, targs }() } - inst := check.instance(pos, typ, targs, check.bestContext(nil)).(*Signature) + inst := check.instance(pos, typ, targs, nil, check.context()).(*Signature) assert(len(xlist) <= len(targs)) // verify instantiation lazily (was issue #50450) check.later(func() { tparams := typ.TypeParams().list() - if i, err := check.verify(pos, tparams, targs); err != nil { + if i, err := check.verify(pos, tparams, targs, check.context()); err != nil { // best position for error reporting pos := pos if i < len(xlist) { @@ -400,7 +400,7 @@ func (check *Checker) arguments(call *ast.CallExpr, sig *Signature, targs []Type // need to compute it from the adjusted list; otherwise we can // simply use the result signature's parameter list. if adjusted { - sigParams = check.subst(call.Pos(), sigParams, makeSubstMap(sig.TypeParams().list(), targs), nil).(*Tuple) + sigParams = check.subst(call.Pos(), sigParams, makeSubstMap(sig.TypeParams().list(), targs), nil, check.context()).(*Tuple) } else { sigParams = rsig.params } diff --git a/src/go/types/infer.go b/src/go/types/infer.go index ebe6d8ced7..1aa2612638 100644 --- a/src/go/types/infer.go +++ b/src/go/types/infer.go @@ -110,11 +110,11 @@ func (check *Checker) infer(posn positioner, tparams []*TypeParam, targs []Type, renameMap := makeRenameMap(tparams, tparams2) for i, tparam := range tparams { - tparams2[i].bound = check.subst(posn.Pos(), tparam.bound, renameMap, nil) + tparams2[i].bound = check.subst(posn.Pos(), tparam.bound, renameMap, nil, check.context()) } tparams = tparams2 - params = check.subst(posn.Pos(), params, renameMap, nil).(*Tuple) + params = check.subst(posn.Pos(), params, renameMap, nil, check.context()).(*Tuple) } } @@ -188,7 +188,7 @@ func (check *Checker) infer(posn positioner, tparams []*TypeParam, targs []Type, // but that doesn't impact the isParameterized check for now). if params.Len() > 0 { smap := makeSubstMap(tparams, targs) - params = check.subst(token.NoPos, params, smap, nil).(*Tuple) + params = check.subst(token.NoPos, params, smap, nil, check.context()).(*Tuple) } // Unify parameter and argument types for generic parameters with typed arguments @@ -225,7 +225,7 @@ func (check *Checker) infer(posn positioner, tparams []*TypeParam, targs []Type, } smap := makeSubstMap(tparams, targs) // TODO(rFindley): pass a positioner here, rather than arg.Pos(). - inferred := check.subst(arg.Pos(), tpar, smap, nil) + inferred := check.subst(arg.Pos(), tpar, smap, nil, check.context()) // _CannotInferTypeArgs indicates a failure of inference, though the actual // error may be better attributed to a user-provided type argument (hence // _InvalidTypeArg). We can't differentiate these cases, so fall back on @@ -626,7 +626,7 @@ func (check *Checker) inferB(posn positioner, tparams []*TypeParam, targs []Type n := 0 for _, index := range dirty { t0 := types[index] - if t1 := check.subst(token.NoPos, t0, smap, nil); t1 != t0 { + if t1 := check.subst(token.NoPos, t0, smap, nil, check.context()); t1 != t0 { types[index] = t1 dirty[n] = index n++ diff --git a/src/go/types/instantiate.go b/src/go/types/instantiate.go index d420a61572..6091b0b381 100644 --- a/src/go/types/instantiate.go +++ b/src/go/types/instantiate.go @@ -40,6 +40,9 @@ import ( // count is incorrect; for *Named types, a panic may occur later inside the // *Named API. func Instantiate(ctxt *Context, orig Type, targs []Type, validate bool) (Type, error) { + if ctxt == nil { + ctxt = NewContext() + } if validate { var tparams []*TypeParam switch t := orig.(type) { @@ -51,34 +54,71 @@ func Instantiate(ctxt *Context, orig Type, targs []Type, validate bool) (Type, e if len(targs) != len(tparams) { return nil, fmt.Errorf("got %d type arguments but %s has %d type parameters", len(targs), orig, len(tparams)) } - if i, err := (*Checker)(nil).verify(token.NoPos, tparams, targs); err != nil { + if i, err := (*Checker)(nil).verify(token.NoPos, tparams, targs, ctxt); err != nil { return nil, &ArgumentError{i, err} } } - inst := (*Checker)(nil).instance(token.NoPos, orig, targs, ctxt) + inst := (*Checker)(nil).instance(token.NoPos, orig, targs, nil, ctxt) return inst, nil } -// instance creates a type or function instance using the given original type -// typ and arguments targs. For Named types the resulting instance will be -// unexpanded. check may be nil. -func (check *Checker) instance(pos token.Pos, orig Type, targs []Type, ctxt *Context) (res Type) { - var h string - if ctxt != nil { - h = ctxt.instanceHash(orig, targs) - // typ may already have been instantiated with identical type arguments. In - // that case, re-use the existing instance. - if inst := ctxt.lookup(h, orig, targs); inst != nil { - return inst +// instance resolves a type or function instance for the given original type +// and type arguments. It looks for an existing identical instance in the given +// contexts, creating a new instance if none is found. +// +// If local is non-nil, it is the context associated with a Named instance +// type currently being expanded. If global is non-nil, it is the context +// associated with the current type-checking pass or call to Instantiate. At +// least one of local or global must be non-nil. +// +// For Named types the resulting instance may be unexpanded. +func (check *Checker) instance(pos token.Pos, orig Type, targs []Type, local, global *Context) (res Type) { + // The order of the contexts below matters: we always prefer instances in + // local in order to preserve reference cycles. + // + // Invariant: if local != nil, the returned instance will be the instance + // recorded in local. + var ctxts []*Context + if local != nil { + ctxts = append(ctxts, local) + } + if global != nil { + ctxts = append(ctxts, global) + } + assert(len(ctxts) > 0) + + // Compute all hashes; hashes may differ across contexts due to different + // unique IDs for Named types within the hasher. + hashes := make([]string, len(ctxts)) + for i, ctxt := range ctxts { + hashes[i] = ctxt.instanceHash(orig, targs) + } + + // If local is non-nil, updateContexts return the type recorded in + // local. + updateContexts := func(res Type) Type { + for i := len(ctxts) - 1; i >= 0; i-- { + res = ctxts[i].update(hashes[i], orig, targs, res) + } + return res + } + + // typ may already have been instantiated with identical type arguments. In + // that case, re-use the existing instance. + for i, ctxt := range ctxts { + if inst := ctxt.lookup(hashes[i], orig, targs); inst != nil { + return updateContexts(inst) } } switch orig := orig.(type) { case *Named: - res = check.newNamedInstance(pos, orig, targs) + res = check.newNamedInstance(pos, orig, targs, local) // substituted lazily case *Signature: + assert(local == nil) // function instances cannot be reached from Named types + tparams := orig.TypeParams() if !check.validateTArgLen(pos, tparams.Len(), len(targs)) { return Typ[Invalid] @@ -86,7 +126,7 @@ func (check *Checker) instance(pos token.Pos, orig Type, targs []Type, ctxt *Con if tparams.Len() == 0 { return orig // nothing to do (minor optimization) } - sig := check.subst(pos, orig, makeSubstMap(tparams.list(), targs), ctxt).(*Signature) + sig := check.subst(pos, orig, makeSubstMap(tparams.list(), targs), nil, global).(*Signature) // If the signature doesn't use its type parameters, subst // will not make a copy. In that case, make a copy now (so // we can set tparams to nil w/o causing side-effects). @@ -104,13 +144,8 @@ func (check *Checker) instance(pos token.Pos, orig Type, targs []Type, ctxt *Con panic(fmt.Sprintf("%v: cannot instantiate %v", pos, orig)) } - if ctxt != nil { - // It's possible that we've lost a race to add named to the context. - // In this case, use whichever instance is recorded in the context. - res = ctxt.update(h, orig, targs, res) - } - - return res + // Update all contexts; it's possible that we've lost a race. + return updateContexts(res) } // validateTArgLen verifies that the length of targs and tparams matches, @@ -128,7 +163,7 @@ func (check *Checker) validateTArgLen(pos token.Pos, ntparams, ntargs int) bool return true } -func (check *Checker) verify(pos token.Pos, tparams []*TypeParam, targs []Type) (int, error) { +func (check *Checker) verify(pos token.Pos, tparams []*TypeParam, targs []Type, ctxt *Context) (int, error) { smap := makeSubstMap(tparams, targs) for i, tpar := range tparams { // Ensure that we have a (possibly implicit) interface as type bound (issue #51048). @@ -137,7 +172,7 @@ func (check *Checker) verify(pos token.Pos, tparams []*TypeParam, targs []Type) // as the instantiated type; before we can use it for bounds checking we // need to instantiate it with the type arguments with which we instantiated // the parameterized type. - bound := check.subst(pos, tpar.bound, smap, nil) + bound := check.subst(pos, tpar.bound, smap, nil, ctxt) if err := check.implements(targs[i], bound); err != nil { return i, err } diff --git a/src/go/types/named.go b/src/go/types/named.go index 71a26f96a1..63f0a22323 100644 --- a/src/go/types/named.go +++ b/src/go/types/named.go @@ -79,6 +79,16 @@ import ( // Identical to compare them. For instantiated named types, their obj is a // synthetic placeholder that records their position of the corresponding // instantiation in the source (if they were constructed during type checking). +// +// To prevent infinite expansion of named instances that are created outside of +// type-checking, instances share a Context with other instances created during +// their expansion. Via the pidgeonhole principle, this guarantees that in the +// presence of a cycle of named types, expansion will eventually find an +// existing instance in the Context and short-circuit the expansion. +// +// Once an instance is complete, we can nil out this shared Context to unpin +// memory, though this Context may still be held by other incomplete instances +// in its "lineage". // A Named represents a named (defined) type. type Named struct { @@ -115,6 +125,7 @@ type instance struct { orig *Named // original, uninstantiated type targs *TypeList // type arguments expandedMethods int // number of expanded methods; expandedMethods <= len(orig.methods) + ctxt *Context // local Context; set to nil after full expansion } // namedState represents the possible states that a named type may assume. @@ -143,7 +154,7 @@ func NewNamed(obj *TypeName, underlying Type, methods []*Func) *Named { // After resolution, the type parameters, methods, and underlying type of n are // accessible; but if n is an instantiated type, its methods may still be // unexpanded. -func (n *Named) resolve(ctxt *Context) *Named { +func (n *Named) resolve() *Named { if n.state() >= resolved { // avoid locking below return n } @@ -162,8 +173,8 @@ func (n *Named) resolve(ctxt *Context) *Named { assert(n.loader == nil) // instances are created by instantiation, in which case n.loader is nil orig := n.inst.orig - orig.resolve(ctxt) - underlying := n.expandUnderlying(ctxt) + orig.resolve() + underlying := n.expandUnderlying() n.tparams = orig.tparams n.underlying = underlying @@ -171,6 +182,7 @@ func (n *Named) resolve(ctxt *Context) *Named { if len(orig.methods) == 0 { n.setState(complete) // nothing further to do + n.inst.ctxt = nil } else { n.setState(resolved) } @@ -225,11 +237,11 @@ func (check *Checker) newNamed(obj *TypeName, underlying Type, methods []*Func) return typ } -func (check *Checker) newNamedInstance(pos token.Pos, orig *Named, targs []Type) *Named { +func (check *Checker) newNamedInstance(pos token.Pos, orig *Named, targs []Type, local *Context) *Named { assert(len(targs) > 0) obj := NewTypeName(pos, orig.obj.pkg, orig.obj.name, nil) - inst := &instance{orig: orig, targs: newTypeList(targs)} + inst := &instance{orig: orig, targs: newTypeList(targs), ctxt: local} typ := &Named{check: check, obj: obj, inst: inst} obj.typ = typ // Ensure that typ is always expanded and sanity-checked. @@ -280,13 +292,13 @@ func (t *Named) Origin() *Named { // TypeParams returns the type parameters of the named type t, or nil. // The result is non-nil for an (originally) generic type even if it is instantiated. -func (t *Named) TypeParams() *TypeParamList { return t.resolve(nil).tparams } +func (t *Named) TypeParams() *TypeParamList { return t.resolve().tparams } // SetTypeParams sets the type parameters of the named type t. // t must not have type arguments. func (t *Named) SetTypeParams(tparams []*TypeParam) { assert(t.inst == nil) - t.resolve(nil).tparams = bindTParams(tparams) + t.resolve().tparams = bindTParams(tparams) } // TypeArgs returns the type arguments used to instantiate the named type t. @@ -298,17 +310,17 @@ func (t *Named) TypeArgs() *TypeList { } // NumMethods returns the number of explicit methods defined for t. -// -// For an ordinary or instantiated type t, the receiver base type of these -// methods will be the named type t. For an uninstantiated generic type t, each -// method receiver will be instantiated with its receiver type parameters. func (t *Named) NumMethods() int { - return len(t.Origin().resolve(nil).methods) + return len(t.Origin().resolve().methods) } // Method returns the i'th method of named type t for 0 <= i < t.NumMethods(). +// +// For an ordinary or instantiated type t, the receiver base type of this +// method is the named type t. For an uninstantiated generic type t, each +// method receiver is instantiated with its receiver type parameters. func (t *Named) Method(i int) *Func { - t.resolve(nil) + t.resolve() if t.state() >= complete { return t.methods[i] @@ -326,6 +338,7 @@ func (t *Named) Method(i int) *Func { } if t.methods[i] == nil { + assert(t.inst.ctxt != nil) // we should still have a context remaining from the resolution phase t.methods[i] = t.expandMethod(i) t.inst.expandedMethods++ @@ -333,6 +346,7 @@ func (t *Named) Method(i int) *Func { // type as fully expanded. if t.inst.expandedMethods == len(orig.methods) { t.setState(complete) + t.inst.ctxt = nil // no need for a context anymore } } @@ -372,9 +386,12 @@ func (t *Named) expandMethod(i int) *Func { // and type parameters. This check is necessary in the presence of invalid // code. if origSig.RecvTypeParams().Len() == t.inst.targs.Len() { - ctxt := check.bestContext(nil) smap := makeSubstMap(origSig.RecvTypeParams().list(), t.inst.targs.list()) - sig = check.subst(origm.pos, origSig, smap, ctxt).(*Signature) + var global *Context + if check != nil { + global = check.context() + } + sig = check.subst(origm.pos, origSig, smap, t.inst.ctxt, global).(*Signature) } if sig == origSig { @@ -405,7 +422,7 @@ func (t *Named) SetUnderlying(underlying Type) { if _, ok := underlying.(*Named); ok { panic("underlying type must not be *Named") } - t.resolve(nil).underlying = underlying + t.resolve().underlying = underlying if t.fromRHS == nil { t.fromRHS = underlying // for cycle detection } @@ -415,17 +432,20 @@ func (t *Named) SetUnderlying(underlying Type) { // t must not have type arguments. func (t *Named) AddMethod(m *Func) { assert(t.inst == nil) - t.resolve(nil) + t.resolve() if i, _ := lookupMethod(t.methods, m.pkg, m.name, false); i < 0 { t.methods = append(t.methods, m) } } -func (t *Named) Underlying() Type { return t.resolve(nil).underlying } +func (t *Named) Underlying() Type { return t.resolve().underlying } func (t *Named) String() string { return TypeString(t, nil) } // ---------------------------------------------------------------------------- // Implementation +// +// TODO(rfindley): reorganize the loading and expansion methods under this +// heading. // under returns the expanded underlying type of n0; possibly by following // forward chains of named types. If an underlying type is found, resolve @@ -522,7 +542,7 @@ func (n *Named) setUnderlying(typ Type) { } func (n *Named) lookupMethod(pkg *Package, name string, foldCase bool) (int, *Func) { - n.resolve(nil) + n.resolve() // If n is an instance, we may not have yet instantiated all of its methods. // Look up the method index in orig, and only instantiate method at the // matching index (if any). @@ -534,26 +554,17 @@ func (n *Named) lookupMethod(pkg *Package, name string, foldCase bool) (int, *Fu return i, n.Method(i) } -// bestContext returns the best available context. In order of preference: -// - the given ctxt, if non-nil -// - check.ctxt, if check is non-nil -// - a new Context -func (check *Checker) bestContext(ctxt *Context) *Context { - if ctxt != nil { - return ctxt +// context returns the type-checker context. +func (check *Checker) context() *Context { + if check.ctxt == nil { + check.ctxt = NewContext() } - if check != nil { - if check.ctxt == nil { - check.ctxt = NewContext() - } - return check.ctxt - } - return NewContext() + return check.ctxt } // expandUnderlying substitutes type arguments in the underlying type n.orig, // returning the result. Returns Typ[Invalid] if there was an error. -func (n *Named) expandUnderlying(ctxt *Context) Type { +func (n *Named) expandUnderlying() Type { check := n.check if check != nil && trace { check.trace(n.obj.pos, "-- Named.expandUnderlying %s", n) @@ -565,6 +576,9 @@ func (n *Named) expandUnderlying(ctxt *Context) Type { } assert(n.inst.orig.underlying != nil) + if n.inst.ctxt == nil { + n.inst.ctxt = NewContext() + } orig := n.inst.orig targs := n.inst.targs @@ -580,16 +594,20 @@ func (n *Named) expandUnderlying(ctxt *Context) Type { return Typ[Invalid] } - // We must always have a context, to avoid infinite recursion. - ctxt = check.bestContext(ctxt) - h := ctxt.instanceHash(orig, targs.list()) - // ensure that an instance is recorded for h to avoid infinite recursion. - ctxt.update(h, orig, targs.list(), n) + // Ensure that an instance is recorded before substituting, so that we + // resolve n for any recursive references. + h := n.inst.ctxt.instanceHash(orig, targs.list()) + n2 := n.inst.ctxt.update(h, orig, n.TypeArgs().list(), n) + assert(n == n2) smap := makeSubstMap(orig.tparams.list(), targs.list()) - underlying := n.check.subst(n.obj.pos, orig.underlying, smap, ctxt) - // If the underlying type of n is an interface, we need to set the receiver - // of its methods accurately -- we set the receiver of interface methods on + var global *Context + if check != nil { + global = check.context() + } + underlying := n.check.subst(n.obj.pos, orig.underlying, smap, n.inst.ctxt, global) + // If the underlying type of n is an interface, we need to set the receiver of + // its methods accurately -- we set the receiver of interface methods on // the RHS of a type declaration to the defined type. if iface, _ := underlying.(*Interface); iface != nil { if methods, copied := replaceRecvType(iface.methods, orig, n); copied { diff --git a/src/go/types/named_test.go b/src/go/types/named_test.go index 74cdb48889..0fe17418f4 100644 --- a/src/go/types/named_test.go +++ b/src/go/types/named_test.go @@ -5,6 +5,9 @@ package types_test import ( + "go/ast" + "go/parser" + "go/token" "testing" . "go/types" @@ -86,3 +89,48 @@ func mustInstantiate(tb testing.TB, orig Type, targs ...Type) Type { } return inst } + +// Test that types do not expand infinitely, as in golang/go#52715. +func TestFiniteTypeExpansion(t *testing.T) { + const src = ` +package p + +type Tree[T any] struct { + *Node[T] +} + +func (*Tree[R]) N(r R) R { return r } + +type Node[T any] struct { + *Tree[T] +} + +func (Node[Q]) M(Q) {} + +type Inst = *Tree[int] +` + + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "foo.go", src, 0) + if err != nil { + t.Fatal(err) + } + pkg := NewPackage("p", f.Name.Name) + if err := NewChecker(nil, fset, pkg, nil).Files([]*ast.File{f}); err != nil { + t.Fatal(err) + } + + firstFieldType := func(n *Named) *Named { + return n.Underlying().(*Struct).Field(0).Type().(*Pointer).Elem().(*Named) + } + + Inst := pkg.Scope().Lookup("Inst").Type().(*Pointer).Elem().(*Named) + Node := firstFieldType(Inst) + Tree := firstFieldType(Node) + if !Identical(Inst, Tree) { + t.Fatalf("Not a cycle: got %v, want %v", Tree, Inst) + } + if Inst != Tree { + t.Errorf("Duplicate instances in cycle: %s (%p) -> %s (%p) -> %s (%p)", Inst, Inst, Node, Node, Tree, Tree) + } +} diff --git a/src/go/types/predicates.go b/src/go/types/predicates.go index 6e08b76e40..25db4acf4a 100644 --- a/src/go/types/predicates.go +++ b/src/go/types/predicates.go @@ -285,18 +285,19 @@ func identical(x, y Type, cmpTags bool, p *ifacePair) bool { } smap := makeSubstMap(ytparams, targs) - var check *Checker // ok to call subst on a nil *Checker + var check *Checker // ok to call subst on a nil *Checker + ctxt := NewContext() // need a non-nil Context for the substitution below // Constraints must be pair-wise identical, after substitution. for i, xtparam := range xtparams { - ybound := check.subst(token.NoPos, ytparams[i].bound, smap, nil) + ybound := check.subst(token.NoPos, ytparams[i].bound, smap, nil, ctxt) if !identical(xtparam.bound, ybound, cmpTags, p) { return false } } - yparams = check.subst(token.NoPos, y.params, smap, nil).(*Tuple) - yresults = check.subst(token.NoPos, y.results, smap, nil).(*Tuple) + yparams = check.subst(token.NoPos, y.params, smap, nil, ctxt).(*Tuple) + yresults = check.subst(token.NoPos, y.results, smap, nil, ctxt).(*Tuple) } return x.variadic == y.variadic && diff --git a/src/go/types/signature.go b/src/go/types/signature.go index 4b63f0e6f0..82177a1c58 100644 --- a/src/go/types/signature.go +++ b/src/go/types/signature.go @@ -150,7 +150,7 @@ func (check *Checker) funcType(sig *Signature, recvPar *ast.FieldList, ftyp *ast // recvTPar.bound is (possibly) parameterized in the context of the // receiver type declaration. Substitute parameters for the current // context. - tpar.bound = check.subst(tpar.obj.pos, recvTPar.bound, smap, nil) + tpar.bound = check.subst(tpar.obj.pos, recvTPar.bound, smap, nil, check.context()) } } else if len(tparams) < len(recvTParams) { // Reporting an error here is a stop-gap measure to avoid crashes in the diff --git a/src/go/types/subst.go b/src/go/types/subst.go index 110298cbae..36987a4c95 100644 --- a/src/go/types/subst.go +++ b/src/go/types/subst.go @@ -49,7 +49,9 @@ func (m substMap) lookup(tpar *TypeParam) Type { // result type is different from the incoming type. // // If the given context is non-nil, it is used in lieu of check.Config.Context -func (check *Checker) subst(pos token.Pos, typ Type, smap substMap, ctxt *Context) Type { +func (check *Checker) subst(pos token.Pos, typ Type, smap substMap, local, global *Context) Type { + assert(local != nil || global != nil) + if smap.empty() { return typ } @@ -64,19 +66,20 @@ func (check *Checker) subst(pos token.Pos, typ Type, smap substMap, ctxt *Contex // general case subst := subster{ - pos: pos, - smap: smap, - check: check, - ctxt: check.bestContext(ctxt), + pos: pos, + smap: smap, + check: check, + local: local, + global: global, } return subst.typ(typ) } type subster struct { - pos token.Pos - smap substMap - check *Checker // nil if called via Instantiate - ctxt *Context + pos token.Pos + smap substMap + check *Checker // nil if called via Instantiate + local, global *Context } func (subst *subster) typ(typ Type) Type { @@ -247,25 +250,11 @@ func (subst *subster) typ(typ Type) Type { return t // nothing to substitute } - // before creating a new named type, check if we have this one already - h := subst.ctxt.instanceHash(orig, newTArgs) - dump(">>> new type hash: %s", h) - if named := subst.ctxt.lookup(h, orig, newTArgs); named != nil { - dump(">>> found %s", named) - return named - } - // Create a new instance and populate the context to avoid endless // recursion. The position used here is irrelevant because validation only // occurs on t (we don't call validType on named), but we use subst.pos to // help with debugging. - return subst.check.instance(subst.pos, orig, newTArgs, subst.ctxt) - - // Note that if we were to expose substitution more generally (not just in - // the context of a declaration), we'd have to substitute in - // named.underlying as well. - // - // But this is unnecessary for now. + return subst.check.instance(subst.pos, orig, newTArgs, subst.local, subst.global) case *TypeParam: return subst.smap.lookup(t) diff --git a/src/go/types/typexpr.go b/src/go/types/typexpr.go index 05bd51a82b..a881d33654 100644 --- a/src/go/types/typexpr.go +++ b/src/go/types/typexpr.go @@ -417,8 +417,7 @@ func (check *Checker) instantiatedType(ix *typeparams.IndexExpr, def *Named) (re } // create the instance - ctxt := check.bestContext(nil) - inst := check.instance(ix.Pos(), orig, targs, ctxt).(*Named) + inst := check.instance(ix.Pos(), orig, targs, nil, check.context()).(*Named) def.setUnderlying(inst) // orig.tparams may not be set up, so we need to do expansion later. @@ -429,7 +428,7 @@ func (check *Checker) instantiatedType(ix *typeparams.IndexExpr, def *Named) (re check.recordInstance(ix.Orig, inst.TypeArgs().list(), inst) if check.validateTArgLen(ix.Pos(), inst.TypeParams().Len(), inst.TypeArgs().Len()) { - if i, err := check.verify(ix.Pos(), inst.TypeParams().list(), inst.TypeArgs().list()); err != nil { + if i, err := check.verify(ix.Pos(), inst.TypeParams().list(), inst.TypeArgs().list(), check.context()); err != nil { // best position for error reporting pos := ix.Pos() if i < len(ix.Indices) { -- cgit v1.3-5-g9baa From 07eca49055f7ef0d73be2ca28dcc5d489db129b9 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Tue, 31 May 2022 20:59:55 -0700 Subject: go/types, types2: use type nest to detect type cycles (fix validType) validType was using a global type info map to detect invalid recursive types, which was incorrect. Instead, change the algorithm as follows: - Rather than using a "seen" (or typeInfo) map which is cumbersome to update correctly, use the stack of embedding types (the type nest) to check whether a type is embedded within itself, directly or indirectly. - Use Identical for type comparisons which correctly considers identity of instantiated generic types. - As before, maintain the full path of types leading to a cycle. But unlike before, track the named types rather than their objects, for a smaller slice ([]*Named rather than []Object), and convert to an object list only when needed for error reporting. - As an optimization, keep track of valid *Named types (Checker.valids). This prevents pathological cases from consuming excessive computation time. - Add clarifying comments and document invariants. Based on earlier insights by David Chase (see also CL 408818). Fixes #52698. Change-Id: I5e4598c58afcf4ab987a426c5c4b7b28bdfcf5ea Reviewed-on: https://go-review.googlesource.com/c/go/+/409694 Reviewed-by: Robert Findley Run-TryBot: Robert Griesemer Reviewed-by: David Chase TryBot-Result: Gopher Robot --- src/cmd/compile/internal/types2/check.go | 3 +- .../types2/testdata/fixedbugs/issue52698.go | 62 ++++++ src/cmd/compile/internal/types2/validtype.go | 210 +++++++++++++++----- src/go/types/check.go | 3 +- src/go/types/testdata/fixedbugs/issue52698.go | 50 +++++ src/go/types/validtype.go | 211 ++++++++++++++++----- 6 files changed, 445 insertions(+), 94 deletions(-) create mode 100644 src/cmd/compile/internal/types2/testdata/fixedbugs/issue52698.go create mode 100644 src/go/types/testdata/fixedbugs/issue52698.go (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/types2/check.go b/src/cmd/compile/internal/types2/check.go index 5cf8454aa4..ff8ae3bc7e 100644 --- a/src/cmd/compile/internal/types2/check.go +++ b/src/cmd/compile/internal/types2/check.go @@ -98,7 +98,7 @@ type Checker struct { nextID uint64 // unique Id for type parameters (first valid Id is 1) objMap map[Object]*declInfo // maps package-level objects and (non-interface) methods to declaration info impMap map[importKey]*Package // maps (import path, source directory) to (complete or fake) package - infoMap map[*Named]typeInfo // maps named types to their associated type info (for cycle detection) + valids instanceLookup // valid *Named (incl. instantiated) types per the validType check // pkgPathMap maps package names to the set of distinct import paths we've // seen for that name, anywhere in the import graph. It is used for @@ -241,7 +241,6 @@ func NewChecker(conf *Config, pkg *Package, info *Info) *Checker { version: version, objMap: make(map[Object]*declInfo), impMap: make(map[importKey]*Package), - infoMap: make(map[*Named]typeInfo), } } diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue52698.go b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue52698.go new file mode 100644 index 0000000000..d1b06a210d --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue52698.go @@ -0,0 +1,62 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +// correctness check: ensure that cycles through generic instantiations are detected +type T[P any] struct { + _ P +} + +type S /* ERROR illegal cycle */ struct { + _ T[S] +} + +// simplified test 1 + +var _ A1[A1[string]] + +type A1[P any] struct { + _ B1[P] +} + +type B1[P any] struct { + _ P +} + +// simplified test 2 +var _ B2[A2] + +type A2 struct { + _ B2[string] +} + +type B2[P any] struct { + _ C2[P] +} + +type C2[P any] struct { + _ P +} + +// test case from issue +type T23 interface { + ~struct { + Field0 T13[T15] + } +} + +type T1[P1 interface { +}] struct { + Field2 P1 +} + +type T13[P2 interface { +}] struct { + Field2 T1[P2] +} + +type T15 struct { + Field0 T13[string] +} diff --git a/src/cmd/compile/internal/types2/validtype.go b/src/cmd/compile/internal/types2/validtype.go index b69120481b..4ea29551ab 100644 --- a/src/cmd/compile/internal/types2/validtype.go +++ b/src/cmd/compile/internal/types2/validtype.go @@ -5,29 +5,24 @@ package types2 // validType verifies that the given type does not "expand" indefinitely -// producing a cycle in the type graph. Cycles are detected by marking -// defined types. +// producing a cycle in the type graph. // (Cycles involving alias types, as in "type A = [10]A" are detected // earlier, via the objDecl cycle detection mechanism.) func (check *Checker) validType(typ *Named) { - check.validType0(typ, nil, nil) + check.validType0(typ, nil, nil, nil) } -type typeInfo uint - // validType0 checks if the given type is valid. If typ is a type parameter // its value is looked up in the provided environment. The environment is // nil if typ is not part of (the RHS of) an instantiated type, in that case // any type parameter encountered must be from an enclosing function and can -// be ignored. The path is the list of type names that lead to the current typ. -func (check *Checker) validType0(typ Type, env *tparamEnv, path []Object) typeInfo { - const ( - unknown typeInfo = iota - marked - valid - invalid - ) - +// be ignored. The nest list describes the stack (the "nest in memory") of +// types which contain (or embed in the case of interfaces) other types. For +// instance, a struct named S which contains a field of named type F contains +// (the memory of) F in S, leading to the nest S->F. If a type appears in its +// own nest (say S->F->S) we have an invalid recursive type. The path list is +// the full path of named types in a cycle, it is only needed for error reporting. +func (check *Checker) validType0(typ Type, env *tparamEnv, nest, path []*Named) bool { switch t := typ.(type) { case nil: // We should never see a nil type but be conservative and panic @@ -37,60 +32,79 @@ func (check *Checker) validType0(typ Type, env *tparamEnv, path []Object) typeIn } case *Array: - return check.validType0(t.elem, env, path) + return check.validType0(t.elem, env, nest, path) case *Struct: for _, f := range t.fields { - if check.validType0(f.typ, env, path) == invalid { - return invalid + if !check.validType0(f.typ, env, nest, path) { + return false } } case *Union: for _, t := range t.terms { - if check.validType0(t.typ, env, path) == invalid { - return invalid + if !check.validType0(t.typ, env, nest, path) { + return false } } case *Interface: for _, etyp := range t.embeddeds { - if check.validType0(etyp, env, path) == invalid { - return invalid + if !check.validType0(etyp, env, nest, path) { + return false } } case *Named: + // Exit early if we already know t is valid. + // This is purely an optimization but it prevents excessive computation + // times in pathological cases such as testdata/fixedbugs/issue6977.go. + // (Note: The valids map could also be allocated locally, once for each + // validType call.) + if check.valids.lookup(t) != nil { + break + } + // Don't report a 2nd error if we already know the type is invalid // (e.g., if a cycle was detected earlier, via under). // Note: ensure that t.orig is fully resolved by calling Underlying(). if t.Underlying() == Typ[Invalid] { - check.infoMap[t] = invalid - return invalid + return false } - switch check.infoMap[t] { - case unknown: - check.infoMap[t] = marked - check.infoMap[t] = check.validType0(t.Origin().fromRHS, env.push(t), append(path, t.obj)) - case marked: - // We have seen type t before and thus must have a cycle. - check.infoMap[t] = invalid - // t cannot be in an imported package otherwise that package - // would have reported a type cycle and couldn't have been - // imported in the first place. - assert(t.obj.pkg == check.pkg) - t.underlying = Typ[Invalid] // t is in the current package (no race possibility) - // Find the starting point of the cycle and report it. - for i, tn := range path { - if tn == t.obj { - check.cycleError(path[i:]) - return invalid + // If the current type t is also found in nest, (the memory of) t is + // embedded in itself, indicating an invalid recursive type. + for _, e := range nest { + if Identical(e, t) { + // t cannot be in an imported package otherwise that package + // would have reported a type cycle and couldn't have been + // imported in the first place. + assert(t.obj.pkg == check.pkg) + t.underlying = Typ[Invalid] // t is in the current package (no race possibility) + // Find the starting point of the cycle and report it. + // Because each type in nest must also appear in path (see invariant below), + // type t must be in path since it was found in nest. But not every type in path + // is in nest. Specifically t may appear in path with an earlier index than the + // index of t in nest. Search again. + for start, p := range path { + if Identical(p, t) { + check.cycleError(makeObjList(path[start:])) + return false + } } + panic("cycle start not found") } - panic("cycle start not found") } - return check.infoMap[t] + + // No cycle was found. Check the RHS of t. + // Every type added to nest is also added to path; thus every type that is in nest + // must also be in path (invariant). But not every type in path is in nest, since + // nest may be pruned (see below, *TypeParam case). + if !check.validType0(t.Origin().fromRHS, env.push(t), append(nest, t), append(path, t)) { + return false + } + + check.valids.add(t) // t is valid case *TypeParam: // A type parameter stands for the type (argument) it was instantiated with. @@ -98,13 +112,29 @@ func (check *Checker) validType0(typ Type, env *tparamEnv, path []Object) typeIn if env != nil { if targ := env.tmap[t]; targ != nil { // Type arguments found in targ must be looked - // up in the enclosing environment env.link. - return check.validType0(targ, env.link, path) + // up in the enclosing environment env.link. The + // type argument must be valid in the enclosing + // type (where the current type was instantiated), + // hence we must check targ's validity in the type + // nest excluding the current (instantiated) type + // (see the example at the end of this file). + // For error reporting we keep the full path. + return check.validType0(targ, env.link, nest[:len(nest)-1], path) } } } - return valid + return true +} + +// makeObjList returns the list of type name objects for the given +// list of named types. +func makeObjList(tlist []*Named) []Object { + olist := make([]Object, len(tlist)) + for i, t := range tlist { + olist[i] = t.obj + } + return olist } // A tparamEnv provides the environment for looking up the type arguments @@ -146,3 +176,93 @@ func (env *tparamEnv) push(typ *Named) *tparamEnv { // same information should be available via the path: // We should be able to just walk the path backwards // and find the type arguments in the instance objects. + +// Here is an example illustrating why we need to exclude the +// instantiated type from nest when evaluating the validity of +// a type parameter. Given the declarations +// +// var _ A[A[string]] +// +// type A[P any] struct { _ B[P] } +// type B[P any] struct { _ P } +// +// we want to determine if the type A[A[string]] is valid. +// We start evaluating A[A[string]] outside any type nest: +// +// A[A[string]] +// nest = +// path = +// +// The RHS of A is now evaluated in the A[A[string]] nest: +// +// struct{_ B[P₁]} +// nest = A[A[string]] +// path = A[A[string]] +// +// The struct has a single field of type B[P₁] with which +// we continue: +// +// B[P₁] +// nest = A[A[string]] +// path = A[A[string]] +// +// struct{_ P₂} +// nest = A[A[string]]->B[P] +// path = A[A[string]]->B[P] +// +// Eventutally we reach the type parameter P of type B (P₂): +// +// P₂ +// nest = A[A[string]]->B[P] +// path = A[A[string]]->B[P] +// +// The type argument for P of B is the type parameter P of A (P₁). +// It must be evaluated in the type nest that existed when B was +// instantiated: +// +// P₁ +// nest = A[A[string]] <== type nest at B's instantiation time +// path = A[A[string]]->B[P] +// +// If we'd use the current nest it would correspond to the path +// which will be wrong as we will see shortly. P's type argument +// is A[string], which again must be evaluated in the type nest +// that existed when A was instantiated with A[string]. That type +// nest is empty: +// +// A[string] +// nest = <== type nest at A's instantiation time +// path = A[A[string]]->B[P] +// +// Evaluation then proceeds as before for A[string]: +// +// struct{_ B[P₁]} +// nest = A[string] +// path = A[A[string]]->B[P]->A[string] +// +// Now we reach B[P] again. If we had not adjusted nest, it would +// correspond to path, and we would find B[P] in nest, indicating +// a cycle, which would clearly be wrong since there's no cycle in +// A[string]: +// +// B[P₁] +// nest = A[string] +// path = A[A[string]]->B[P]->A[string] <== path contains B[P]! +// +// But because we use the correct type nest, evaluation proceeds without +// errors and we get the evaluation sequence: +// +// struct{_ P₂} +// nest = A[string]->B[P] +// path = A[A[string]]->B[P]->A[string]->B[P] +// P₂ +// nest = A[string]->B[P] +// path = A[A[string]]->B[P]->A[string]->B[P] +// P₁ +// nest = A[string] +// path = A[A[string]]->B[P]->A[string]->B[P] +// string +// nest = +// path = A[A[string]]->B[P]->A[string]->B[P] +// +// At this point we're done and A[A[string]] and is valid. diff --git a/src/go/types/check.go b/src/go/types/check.go index d920d9c080..b787c5c38b 100644 --- a/src/go/types/check.go +++ b/src/go/types/check.go @@ -105,7 +105,7 @@ type Checker struct { nextID uint64 // unique Id for type parameters (first valid Id is 1) objMap map[Object]*declInfo // maps package-level objects and (non-interface) methods to declaration info impMap map[importKey]*Package // maps (import path, source directory) to (complete or fake) package - infoMap map[*Named]typeInfo // maps named types to their associated type info (for cycle detection) + valids instanceLookup // valid *Named (incl. instantiated) types per the validType check // pkgPathMap maps package names to the set of distinct import paths we've // seen for that name, anywhere in the import graph. It is used for @@ -249,7 +249,6 @@ func NewChecker(conf *Config, fset *token.FileSet, pkg *Package, info *Info) *Ch version: version, objMap: make(map[Object]*declInfo), impMap: make(map[importKey]*Package), - infoMap: make(map[*Named]typeInfo), } } diff --git a/src/go/types/testdata/fixedbugs/issue52698.go b/src/go/types/testdata/fixedbugs/issue52698.go new file mode 100644 index 0000000000..3babc21d92 --- /dev/null +++ b/src/go/types/testdata/fixedbugs/issue52698.go @@ -0,0 +1,50 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +// sanity check +type T[P any] struct { + _ P +} + +type S /* ERROR illegal cycle */ struct { + _ T[S] +} + +// simplified test +var _ B[A] + +type A struct { + _ B[string] +} + +type B[P any] struct { + _ C[P] +} + +type C[P any] struct { + _ P +} + +// test case from issue +type T23 interface { + ~struct { + Field0 T13[T15] + } +} + +type T1[P1 interface { +}] struct { + Field2 P1 +} + +type T13[P2 interface { +}] struct { + Field2 T1[P2] +} + +type T15 struct { + Field0 T13[string] +} diff --git a/src/go/types/validtype.go b/src/go/types/validtype.go index 0d7a0f308c..712508670f 100644 --- a/src/go/types/validtype.go +++ b/src/go/types/validtype.go @@ -5,29 +5,24 @@ package types // validType verifies that the given type does not "expand" indefinitely -// producing a cycle in the type graph. Cycles are detected by marking -// defined types. +// producing a cycle in the type graph. // (Cycles involving alias types, as in "type A = [10]A" are detected // earlier, via the objDecl cycle detection mechanism.) func (check *Checker) validType(typ *Named) { - check.validType0(typ, nil, nil) + check.validType0(typ, nil, nil, nil) } -type typeInfo uint - // validType0 checks if the given type is valid. If typ is a type parameter // its value is looked up in the provided environment. The environment is // nil if typ is not part of (the RHS of) an instantiated type, in that case // any type parameter encountered must be from an enclosing function and can -// be ignored. The path is the list of type names that lead to the current typ. -func (check *Checker) validType0(typ Type, env *tparamEnv, path []Object) typeInfo { - const ( - unknown typeInfo = iota - marked - valid - invalid - ) - +// be ignored. The nest list describes the stack (the "nest in memory") of +// types which contain (or embed in the case of interfaces) other types. For +// instance, a struct named S which contains a field of named type F contains +// (the memory of) F in S, leading to the nest S->F. If a type appears in its +// own nest (say S->F->S) we have an invalid recursive type. The path list is +// the full path of named types in a cycle, it is only needed for error reporting. +func (check *Checker) validType0(typ Type, env *tparamEnv, nest, path []*Named) bool { switch t := typ.(type) { case nil: // We should never see a nil type but be conservative and panic @@ -37,59 +32,79 @@ func (check *Checker) validType0(typ Type, env *tparamEnv, path []Object) typeIn } case *Array: - return check.validType0(t.elem, env, path) + return check.validType0(t.elem, env, nest, path) case *Struct: for _, f := range t.fields { - if check.validType0(f.typ, env, path) == invalid { - return invalid + if !check.validType0(f.typ, env, nest, path) { + return false } } case *Union: for _, t := range t.terms { - if check.validType0(t.typ, env, path) == invalid { - return invalid + if !check.validType0(t.typ, env, nest, path) { + return false } } case *Interface: for _, etyp := range t.embeddeds { - if check.validType0(etyp, env, path) == invalid { - return invalid + if !check.validType0(etyp, env, nest, path) { + return false } } case *Named: + // Exit early if we already know t is valid. + // This is purely an optimization but it prevents excessive computation + // times in pathological cases such as testdata/fixedbugs/issue6977.go. + // (Note: The valids map could also be allocated locally, once for each + // validType call.) + if check.valids.lookup(t) != nil { + break + } + // Don't report a 2nd error if we already know the type is invalid + // (e.g., if a cycle was detected earlier, via under). // Note: ensure that t.orig is fully resolved by calling Underlying(). if t.Underlying() == Typ[Invalid] { - check.infoMap[t] = invalid - return invalid + return false } - switch check.infoMap[t] { - case unknown: - check.infoMap[t] = marked - check.infoMap[t] = check.validType0(t.Origin().fromRHS, env.push(t), append(path, t.obj)) - case marked: - // We have seen type t before and thus must have a cycle. - check.infoMap[t] = invalid - // t cannot be in an imported package otherwise that package - // would have reported a type cycle and couldn't have been - // imported in the first place. - assert(t.obj.pkg == check.pkg) - t.underlying = Typ[Invalid] // t is in the current package (no race possibility) - // Find the starting point of the cycle and report it. - for i, tn := range path { - if tn == t.obj { - check.cycleError(path[i:]) - return invalid + // If the current type t is also found in nest, (the memory of) t is + // embedded in itself, indicating an invalid recursive type. + for _, e := range nest { + if Identical(e, t) { + // t cannot be in an imported package otherwise that package + // would have reported a type cycle and couldn't have been + // imported in the first place. + assert(t.obj.pkg == check.pkg) + t.underlying = Typ[Invalid] // t is in the current package (no race possibility) + // Find the starting point of the cycle and report it. + // Because each type in nest must also appear in path (see invariant below), + // type t must be in path since it was found in nest. But not every type in path + // is in nest. Specifically t may appear in path with an earlier index than the + // index of t in nest. Search again. + for start, p := range path { + if Identical(p, t) { + check.cycleError(makeObjList(path[start:])) + return false + } } + panic("cycle start not found") } - panic("cycle start not found") } - return check.infoMap[t] + + // No cycle was found. Check the RHS of t. + // Every type added to nest is also added to path; thus every type that is in nest + // must also be in path (invariant). But not every type in path is in nest, since + // nest may be pruned (see below, *TypeParam case). + if !check.validType0(t.Origin().fromRHS, env.push(t), append(nest, t), append(path, t)) { + return false + } + + check.valids.add(t) // t is valid case *TypeParam: // A type parameter stands for the type (argument) it was instantiated with. @@ -97,13 +112,29 @@ func (check *Checker) validType0(typ Type, env *tparamEnv, path []Object) typeIn if env != nil { if targ := env.tmap[t]; targ != nil { // Type arguments found in targ must be looked - // up in the enclosing environment env.link. - return check.validType0(targ, env.link, path) + // up in the enclosing environment env.link. The + // type argument must be valid in the enclosing + // type (where the current type was instantiated), + // hence we must check targ's validity in the type + // nest excluding the current (instantiated) type + // (see the example at the end of this file). + // For error reporting we keep the full path. + return check.validType0(targ, env.link, nest[:len(nest)-1], path) } } } - return valid + return true +} + +// makeObjList returns the list of type name objects for the given +// list of named types. +func makeObjList(tlist []*Named) []Object { + olist := make([]Object, len(tlist)) + for i, t := range tlist { + olist[i] = t.obj + } + return olist } // A tparamEnv provides the environment for looking up the type arguments @@ -145,3 +176,93 @@ func (env *tparamEnv) push(typ *Named) *tparamEnv { // same information should be available via the path: // We should be able to just walk the path backwards // and find the type arguments in the instance objects. + +// Here is an example illustrating why we need to exclude the +// instantiated type from nest when evaluating the validity of +// a type parameter. Given the declarations +// +// var _ A[A[string]] +// +// type A[P any] struct { _ B[P] } +// type B[P any] struct { _ P } +// +// we want to determine if the type A[A[string]] is valid. +// We start evaluating A[A[string]] outside any type nest: +// +// A[A[string]] +// nest = +// path = +// +// The RHS of A is now evaluated in the A[A[string]] nest: +// +// struct{_ B[P₁]} +// nest = A[A[string]] +// path = A[A[string]] +// +// The struct has a single field of type B[P₁] with which +// we continue: +// +// B[P₁] +// nest = A[A[string]] +// path = A[A[string]] +// +// struct{_ P₂} +// nest = A[A[string]]->B[P] +// path = A[A[string]]->B[P] +// +// Eventutally we reach the type parameter P of type B (P₂): +// +// P₂ +// nest = A[A[string]]->B[P] +// path = A[A[string]]->B[P] +// +// The type argument for P of B is the type parameter P of A (P₁). +// It must be evaluated in the type nest that existed when B was +// instantiated: +// +// P₁ +// nest = A[A[string]] <== type nest at B's instantiation time +// path = A[A[string]]->B[P] +// +// If we'd use the current nest it would correspond to the path +// which will be wrong as we will see shortly. P's type argument +// is A[string], which again must be evaluated in the type nest +// that existed when A was instantiated with A[string]. That type +// nest is empty: +// +// A[string] +// nest = <== type nest at A's instantiation time +// path = A[A[string]]->B[P] +// +// Evaluation then proceeds as before for A[string]: +// +// struct{_ B[P₁]} +// nest = A[string] +// path = A[A[string]]->B[P]->A[string] +// +// Now we reach B[P] again. If we had not adjusted nest, it would +// correspond to path, and we would find B[P] in nest, indicating +// a cycle, which would clearly be wrong since there's no cycle in +// A[string]: +// +// B[P₁] +// nest = A[string] +// path = A[A[string]]->B[P]->A[string] <== path contains B[P]! +// +// But because we use the correct type nest, evaluation proceeds without +// errors and we get the evaluation sequence: +// +// struct{_ P₂} +// nest = A[string]->B[P] +// path = A[A[string]]->B[P]->A[string]->B[P] +// P₂ +// nest = A[string]->B[P] +// path = A[A[string]]->B[P]->A[string]->B[P] +// P₁ +// nest = A[string] +// path = A[A[string]]->B[P]->A[string]->B[P] +// string +// nest = +// path = A[A[string]]->B[P]->A[string]->B[P] +// +// At this point we're done and A[A[string]] and is valid. -- cgit v1.3-5-g9baa From fc9707594910452cce3fba794fa9ffe541e8cefa Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Thu, 2 Jun 2022 20:40:17 -0700 Subject: go/types, types2: simplify implementation of validType (fix TODO) Now that validType is using the correct type nest (CL 409694), the top entry of the type nest corresponds to the instantiated type. Thus we can use that type instance to look up the value of type parameters, there's no need anymore to create an environment to look up type arguments. Remove the need to pass around the environment and remove all associated types and functions. Updates #52698. Change-Id: Ie37eace88896386e667ef93c77a4fc3cd0be6eb9 Reviewed-on: https://go-review.googlesource.com/c/go/+/410294 Reviewed-by: Robert Findley TryBot-Result: Gopher Robot Reviewed-by: Robert Griesemer Run-TryBot: Robert Griesemer --- src/cmd/compile/internal/types2/validtype.go | 103 +++++++++------------------ src/go/types/validtype.go | 103 +++++++++------------------ 2 files changed, 70 insertions(+), 136 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/types2/validtype.go b/src/cmd/compile/internal/types2/validtype.go index 4ea29551ab..99fdebc978 100644 --- a/src/cmd/compile/internal/types2/validtype.go +++ b/src/cmd/compile/internal/types2/validtype.go @@ -9,20 +9,20 @@ package types2 // (Cycles involving alias types, as in "type A = [10]A" are detected // earlier, via the objDecl cycle detection mechanism.) func (check *Checker) validType(typ *Named) { - check.validType0(typ, nil, nil, nil) + check.validType0(typ, nil, nil) } // validType0 checks if the given type is valid. If typ is a type parameter -// its value is looked up in the provided environment. The environment is -// nil if typ is not part of (the RHS of) an instantiated type, in that case -// any type parameter encountered must be from an enclosing function and can -// be ignored. The nest list describes the stack (the "nest in memory") of -// types which contain (or embed in the case of interfaces) other types. For -// instance, a struct named S which contains a field of named type F contains -// (the memory of) F in S, leading to the nest S->F. If a type appears in its -// own nest (say S->F->S) we have an invalid recursive type. The path list is -// the full path of named types in a cycle, it is only needed for error reporting. -func (check *Checker) validType0(typ Type, env *tparamEnv, nest, path []*Named) bool { +// its value is looked up in the type argument list of the instantiated +// (enclosing) type, if it exists. Otherwise the type parameter must be from +// an enclosing function and can be ignored. +// The nest list describes the stack (the "nest in memory") of types which +// contain (or embed in the case of interfaces) other types. For instance, a +// struct named S which contains a field of named type F contains (the memory +// of) F in S, leading to the nest S->F. If a type appears in its own nest +// (say S->F->S) we have an invalid recursive type. The path list is the full +// path of named types in a cycle, it is only needed for error reporting. +func (check *Checker) validType0(typ Type, nest, path []*Named) bool { switch t := typ.(type) { case nil: // We should never see a nil type but be conservative and panic @@ -32,25 +32,25 @@ func (check *Checker) validType0(typ Type, env *tparamEnv, nest, path []*Named) } case *Array: - return check.validType0(t.elem, env, nest, path) + return check.validType0(t.elem, nest, path) case *Struct: for _, f := range t.fields { - if !check.validType0(f.typ, env, nest, path) { + if !check.validType0(f.typ, nest, path) { return false } } case *Union: for _, t := range t.terms { - if !check.validType0(t.typ, env, nest, path) { + if !check.validType0(t.typ, nest, path) { return false } } case *Interface: for _, etyp := range t.embeddeds { - if !check.validType0(etyp, env, nest, path) { + if !check.validType0(etyp, nest, path) { return false } } @@ -100,7 +100,7 @@ func (check *Checker) validType0(typ Type, env *tparamEnv, nest, path []*Named) // Every type added to nest is also added to path; thus every type that is in nest // must also be in path (invariant). But not every type in path is in nest, since // nest may be pruned (see below, *TypeParam case). - if !check.validType0(t.Origin().fromRHS, env.push(t), append(nest, t), append(path, t)) { + if !check.validType0(t.Origin().fromRHS, append(nest, t), append(path, t)) { return false } @@ -108,18 +108,25 @@ func (check *Checker) validType0(typ Type, env *tparamEnv, nest, path []*Named) case *TypeParam: // A type parameter stands for the type (argument) it was instantiated with. - // Check the corresponding type argument for validity if we have one. - if env != nil { - if targ := env.tmap[t]; targ != nil { - // Type arguments found in targ must be looked - // up in the enclosing environment env.link. The - // type argument must be valid in the enclosing - // type (where the current type was instantiated), - // hence we must check targ's validity in the type - // nest excluding the current (instantiated) type - // (see the example at the end of this file). - // For error reporting we keep the full path. - return check.validType0(targ, env.link, nest[:len(nest)-1], path) + // Check the corresponding type argument for validity if we are in an + // instantiated type. + if len(nest) > 0 { + inst := nest[len(nest)-1] // the type instance + // Find the corresponding type argument for the type parameter + // and proceed with checking that type argument. + for i, tparam := range inst.TypeParams().list() { + // The type parameter and type argument lists should + // match in length but be careful in case of errors. + if t == tparam && i < inst.TypeArgs().Len() { + targ := inst.TypeArgs().At(i) + // The type argument must be valid in the enclosing + // type (where inst was instantiated), hence we must + // check targ's validity in the type nest excluding + // the current (instantiated) type (see the example + // at the end of this file). + // For error reporting we keep the full path. + return check.validType0(targ, nest[:len(nest)-1], path) + } } } } @@ -137,46 +144,6 @@ func makeObjList(tlist []*Named) []Object { return olist } -// A tparamEnv provides the environment for looking up the type arguments -// with which type parameters for a given instance were instantiated. -// If we don't have an instance, the corresponding tparamEnv is nil. -type tparamEnv struct { - tmap substMap - link *tparamEnv -} - -func (env *tparamEnv) push(typ *Named) *tparamEnv { - // If typ is not an instantiated type there are no typ-specific - // type parameters to look up and we don't need an environment. - targs := typ.TypeArgs() - if targs == nil { - return nil // no instance => nil environment - } - - // Populate tmap: remember the type argument for each type parameter. - // We cannot use makeSubstMap because the number of type parameters - // and arguments may not match due to errors in the source (too many - // or too few type arguments). Populate tmap "manually". - tparams := typ.TypeParams() - n, m := targs.Len(), tparams.Len() - if n > m { - n = m // too many targs - } - tmap := make(substMap, n) - for i := 0; i < n; i++ { - tmap[tparams.At(i)] = targs.At(i) - } - - return &tparamEnv{tmap: tmap, link: env} -} - -// TODO(gri) Alternative implementation: -// We may not need to build a stack of environments to -// look up the type arguments for type parameters. The -// same information should be available via the path: -// We should be able to just walk the path backwards -// and find the type arguments in the instance objects. - // Here is an example illustrating why we need to exclude the // instantiated type from nest when evaluating the validity of // a type parameter. Given the declarations diff --git a/src/go/types/validtype.go b/src/go/types/validtype.go index 712508670f..34c9533a05 100644 --- a/src/go/types/validtype.go +++ b/src/go/types/validtype.go @@ -9,20 +9,20 @@ package types // (Cycles involving alias types, as in "type A = [10]A" are detected // earlier, via the objDecl cycle detection mechanism.) func (check *Checker) validType(typ *Named) { - check.validType0(typ, nil, nil, nil) + check.validType0(typ, nil, nil) } // validType0 checks if the given type is valid. If typ is a type parameter -// its value is looked up in the provided environment. The environment is -// nil if typ is not part of (the RHS of) an instantiated type, in that case -// any type parameter encountered must be from an enclosing function and can -// be ignored. The nest list describes the stack (the "nest in memory") of -// types which contain (or embed in the case of interfaces) other types. For -// instance, a struct named S which contains a field of named type F contains -// (the memory of) F in S, leading to the nest S->F. If a type appears in its -// own nest (say S->F->S) we have an invalid recursive type. The path list is -// the full path of named types in a cycle, it is only needed for error reporting. -func (check *Checker) validType0(typ Type, env *tparamEnv, nest, path []*Named) bool { +// its value is looked up in the type argument list of the instantiated +// (enclosing) type, if it exists. Otherwise the type parameter must be from +// an enclosing function and can be ignored. +// The nest list describes the stack (the "nest in memory") of types which +// contain (or embed in the case of interfaces) other types. For instance, a +// struct named S which contains a field of named type F contains (the memory +// of) F in S, leading to the nest S->F. If a type appears in its own nest +// (say S->F->S) we have an invalid recursive type. The path list is the full +// path of named types in a cycle, it is only needed for error reporting. +func (check *Checker) validType0(typ Type, nest, path []*Named) bool { switch t := typ.(type) { case nil: // We should never see a nil type but be conservative and panic @@ -32,25 +32,25 @@ func (check *Checker) validType0(typ Type, env *tparamEnv, nest, path []*Named) } case *Array: - return check.validType0(t.elem, env, nest, path) + return check.validType0(t.elem, nest, path) case *Struct: for _, f := range t.fields { - if !check.validType0(f.typ, env, nest, path) { + if !check.validType0(f.typ, nest, path) { return false } } case *Union: for _, t := range t.terms { - if !check.validType0(t.typ, env, nest, path) { + if !check.validType0(t.typ, nest, path) { return false } } case *Interface: for _, etyp := range t.embeddeds { - if !check.validType0(etyp, env, nest, path) { + if !check.validType0(etyp, nest, path) { return false } } @@ -100,7 +100,7 @@ func (check *Checker) validType0(typ Type, env *tparamEnv, nest, path []*Named) // Every type added to nest is also added to path; thus every type that is in nest // must also be in path (invariant). But not every type in path is in nest, since // nest may be pruned (see below, *TypeParam case). - if !check.validType0(t.Origin().fromRHS, env.push(t), append(nest, t), append(path, t)) { + if !check.validType0(t.Origin().fromRHS, append(nest, t), append(path, t)) { return false } @@ -108,18 +108,25 @@ func (check *Checker) validType0(typ Type, env *tparamEnv, nest, path []*Named) case *TypeParam: // A type parameter stands for the type (argument) it was instantiated with. - // Check the corresponding type argument for validity if we have one. - if env != nil { - if targ := env.tmap[t]; targ != nil { - // Type arguments found in targ must be looked - // up in the enclosing environment env.link. The - // type argument must be valid in the enclosing - // type (where the current type was instantiated), - // hence we must check targ's validity in the type - // nest excluding the current (instantiated) type - // (see the example at the end of this file). - // For error reporting we keep the full path. - return check.validType0(targ, env.link, nest[:len(nest)-1], path) + // Check the corresponding type argument for validity if we are in an + // instantiated type. + if len(nest) > 0 { + inst := nest[len(nest)-1] // the type instance + // Find the corresponding type argument for the type parameter + // and proceed with checking that type argument. + for i, tparam := range inst.TypeParams().list() { + // The type parameter and type argument lists should + // match in length but be careful in case of errors. + if t == tparam && i < inst.TypeArgs().Len() { + targ := inst.TypeArgs().At(i) + // The type argument must be valid in the enclosing + // type (where inst was instantiated), hence we must + // check targ's validity in the type nest excluding + // the current (instantiated) type (see the example + // at the end of this file). + // For error reporting we keep the full path. + return check.validType0(targ, nest[:len(nest)-1], path) + } } } } @@ -137,46 +144,6 @@ func makeObjList(tlist []*Named) []Object { return olist } -// A tparamEnv provides the environment for looking up the type arguments -// with which type parameters for a given instance were instantiated. -// If we don't have an instance, the corresponding tparamEnv is nil. -type tparamEnv struct { - tmap substMap - link *tparamEnv -} - -func (env *tparamEnv) push(typ *Named) *tparamEnv { - // If typ is not an instantiated type there are no typ-specific - // type parameters to look up and we don't need an environment. - targs := typ.TypeArgs() - if targs == nil { - return nil // no instance => nil environment - } - - // Populate tmap: remember the type argument for each type parameter. - // We cannot use makeSubstMap because the number of type parameters - // and arguments may not match due to errors in the source (too many - // or too few type arguments). Populate tmap "manually". - tparams := typ.TypeParams() - n, m := targs.Len(), tparams.Len() - if n > m { - n = m // too many targs - } - tmap := make(substMap, n) - for i := 0; i < n; i++ { - tmap[tparams.At(i)] = targs.At(i) - } - - return &tparamEnv{tmap: tmap, link: env} -} - -// TODO(gri) Alternative implementation: -// We may not need to build a stack of environments to -// look up the type arguments for type parameters. The -// same information should be available via the path: -// We should be able to just walk the path backwards -// and find the type arguments in the instance objects. - // Here is an example illustrating why we need to exclude the // instantiated type from nest when evaluating the validity of // a type parameter. Given the declarations -- cgit v1.3-5-g9baa From 2fa45a4fcd57070967f081f3a5c33014c3f29fea Mon Sep 17 00:00:00 2001 From: Than McIntosh Date: Fri, 3 Jun 2022 07:50:58 -0400 Subject: cmd/link/internal/loadpe: handle _main reference properly When building CGO internal linking on windows 386, make sure to avoid rewriting references to "_main" to "main" when reading symbols during host object loading; the main routine defined by the Go runtime is still named "_main" (not "main"). If we don't do this, we wind up with an SXREF symbol named "main", which can then cause the loader to pull an actual "main" symbol out of a host archive, which is undesirable. Updates #35006. Change-Id: I3768e3617b560552f4522e9e72af879c6adf7705 Reviewed-on: https://go-review.googlesource.com/c/go/+/410124 TryBot-Result: Gopher Robot Reviewed-by: Alex Brainman Auto-Submit: Than McIntosh Run-TryBot: Than McIntosh Reviewed-by: Cherry Mui --- src/cmd/link/internal/loadpe/ldpe.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'src/cmd') diff --git a/src/cmd/link/internal/loadpe/ldpe.go b/src/cmd/link/internal/loadpe/ldpe.go index bfe2e837c9..bc66252cfa 100644 --- a/src/cmd/link/internal/loadpe/ldpe.go +++ b/src/cmd/link/internal/loadpe/ldpe.go @@ -551,7 +551,13 @@ func (state *peLoaderState) readpesym(pesym *pe.COFFSymbol) (*loader.SymbolBuild name = strings.TrimPrefix(name, "__imp_") // __imp_Name => Name } } - if state.arch.Family == sys.I386 && name[0] == '_' { + // A note on the "_main" exclusion below: the main routine + // defined by the Go runtime is named "_main", not "main", so + // when reading references to _main from a host object we want + // to avoid rewriting "_main" to "main" in this specific + // instance. See #issuecomment-1143698749 on #35006 for more + // details on this problem. + if state.arch.Family == sys.I386 && name[0] == '_' && name != "_main" { name = name[1:] // _Name => Name } } -- cgit v1.3-5-g9baa From 95547aee8c6377e73919d6f0b99484152fb3de04 Mon Sep 17 00:00:00 2001 From: Joel Sing Date: Thu, 2 Jun 2022 05:09:09 +1000 Subject: cmd/compile: cast riscv64 rewrite shifts to unsigned int This appeases Go 1.4, making it possible to bootstrap GOARCH=riscv64 with a Go 1.4 compiler. Fixes #52583 Change-Id: Ib13c2afeb095b2bb1464dcd7f1502574209bc7ab Reviewed-on: https://go-review.googlesource.com/c/go/+/409974 TryBot-Result: Gopher Robot Run-TryBot: Joel Sing Reviewed-by: Bryan Mills Reviewed-by: Cherry Mui --- src/cmd/compile/internal/ssa/gen/RISCV64.rules | 6 +++--- src/cmd/compile/internal/ssa/rewriteRISCV64.go | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/ssa/gen/RISCV64.rules b/src/cmd/compile/internal/ssa/gen/RISCV64.rules index 7aea622c5e..dd20be2aeb 100644 --- a/src/cmd/compile/internal/ssa/gen/RISCV64.rules +++ b/src/cmd/compile/internal/ssa/gen/RISCV64.rules @@ -735,9 +735,9 @@ (NEGW (MOVDconst [x])) => (MOVDconst [int64(int32(-x))]) // Shift of a constant. -(SLLI [x] (MOVDconst [y])) && is32Bit(y << x) => (MOVDconst [y << x]) -(SRLI [x] (MOVDconst [y])) => (MOVDconst [int64(uint64(y) >> x)]) -(SRAI [x] (MOVDconst [y])) => (MOVDconst [int64(y) >> x]) +(SLLI [x] (MOVDconst [y])) && is32Bit(y << uint32(x)) => (MOVDconst [y << uint32(x)]) +(SRLI [x] (MOVDconst [y])) => (MOVDconst [int64(uint64(y) >> uint32(x))]) +(SRAI [x] (MOVDconst [y])) => (MOVDconst [int64(y) >> uint32(x)]) // SLTI/SLTIU with constants. (SLTI [x] (MOVDconst [y])) => (MOVDconst [b2i(int64(y) < int64(x))]) diff --git a/src/cmd/compile/internal/ssa/rewriteRISCV64.go b/src/cmd/compile/internal/ssa/rewriteRISCV64.go index 6828d97ff8..2677e99dc0 100644 --- a/src/cmd/compile/internal/ssa/rewriteRISCV64.go +++ b/src/cmd/compile/internal/ssa/rewriteRISCV64.go @@ -4843,19 +4843,19 @@ func rewriteValueRISCV64_OpRISCV64SLL(v *Value) bool { func rewriteValueRISCV64_OpRISCV64SLLI(v *Value) bool { v_0 := v.Args[0] // match: (SLLI [x] (MOVDconst [y])) - // cond: is32Bit(y << x) - // result: (MOVDconst [y << x]) + // cond: is32Bit(y << uint32(x)) + // result: (MOVDconst [y << uint32(x)]) for { x := auxIntToInt64(v.AuxInt) if v_0.Op != OpRISCV64MOVDconst { break } y := auxIntToInt64(v_0.AuxInt) - if !(is32Bit(y << x)) { + if !(is32Bit(y << uint32(x))) { break } v.reset(OpRISCV64MOVDconst) - v.AuxInt = int64ToAuxInt(y << x) + v.AuxInt = int64ToAuxInt(y << uint32(x)) return true } return false @@ -4913,7 +4913,7 @@ func rewriteValueRISCV64_OpRISCV64SRA(v *Value) bool { func rewriteValueRISCV64_OpRISCV64SRAI(v *Value) bool { v_0 := v.Args[0] // match: (SRAI [x] (MOVDconst [y])) - // result: (MOVDconst [int64(y) >> x]) + // result: (MOVDconst [int64(y) >> uint32(x)]) for { x := auxIntToInt64(v.AuxInt) if v_0.Op != OpRISCV64MOVDconst { @@ -4921,7 +4921,7 @@ func rewriteValueRISCV64_OpRISCV64SRAI(v *Value) bool { } y := auxIntToInt64(v_0.AuxInt) v.reset(OpRISCV64MOVDconst) - v.AuxInt = int64ToAuxInt(int64(y) >> x) + v.AuxInt = int64ToAuxInt(int64(y) >> uint32(x)) return true } return false @@ -4947,7 +4947,7 @@ func rewriteValueRISCV64_OpRISCV64SRL(v *Value) bool { func rewriteValueRISCV64_OpRISCV64SRLI(v *Value) bool { v_0 := v.Args[0] // match: (SRLI [x] (MOVDconst [y])) - // result: (MOVDconst [int64(uint64(y) >> x)]) + // result: (MOVDconst [int64(uint64(y) >> uint32(x))]) for { x := auxIntToInt64(v.AuxInt) if v_0.Op != OpRISCV64MOVDconst { @@ -4955,7 +4955,7 @@ func rewriteValueRISCV64_OpRISCV64SRLI(v *Value) bool { } y := auxIntToInt64(v_0.AuxInt) v.reset(OpRISCV64MOVDconst) - v.AuxInt = int64ToAuxInt(int64(uint64(y) >> x)) + v.AuxInt = int64ToAuxInt(int64(uint64(y) >> uint32(x))) return true } return false -- cgit v1.3-5-g9baa From ea5d7cbc2644643331bd675b1ebdf0aaac7419f1 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Wed, 4 May 2022 08:58:02 -0400 Subject: all: boringcrypto post-merge cleanup This CL addresses the comments on CL 403154. For #51940. Change-Id: I99bb3530916d469077bfbd53095bfcd1d2aa82ef Reviewed-on: https://go-review.googlesource.com/c/go/+/403976 Reviewed-by: Roland Shoemaker TryBot-Result: Gopher Robot Run-TryBot: Russ Cox Reviewed-by: Ian Lance Taylor --- src/cmd/go/internal/modindex/build.go | 4 +-- src/cmd/internal/notsha256/example_test.go | 41 ------------------------------ src/crypto/aes/cipher.go | 3 +-- src/crypto/aes/cipher_asm.go | 3 +-- src/crypto/ecdsa/ecdsa.go | 3 +-- src/crypto/hmac/hmac.go | 3 +-- src/crypto/internal/boring/aes.go | 7 ++--- src/crypto/internal/boring/notboring.go | 1 + src/crypto/rand/rand_unix.go | 3 +-- src/crypto/rsa/pkcs1v15.go | 34 ++++++++++++------------- src/crypto/rsa/pss.go | 4 +-- src/crypto/tls/cipher_suites.go | 10 +++----- src/go/build/build.go | 6 +++-- 13 files changed, 35 insertions(+), 87 deletions(-) delete mode 100644 src/cmd/internal/notsha256/example_test.go (limited to 'src/cmd') diff --git a/src/cmd/go/internal/modindex/build.go b/src/cmd/go/internal/modindex/build.go index 78bd12636d..9d52be851b 100644 --- a/src/cmd/go/internal/modindex/build.go +++ b/src/cmd/go/internal/modindex/build.go @@ -887,8 +887,8 @@ func (ctxt *Context) eval(x constraint.Expr, allTags map[string]bool) bool { // $GOARCH // boringcrypto // ctxt.Compiler -// linux (if GOOS = android) -// solaris (if GOOS = illumos) +// linux (if GOOS == android) +// solaris (if GOOS == illumos) // tag (if tag is listed in ctxt.BuildTags or ctxt.ReleaseTags) // // It records all consulted tags in allTags. diff --git a/src/cmd/internal/notsha256/example_test.go b/src/cmd/internal/notsha256/example_test.go deleted file mode 100644 index 06e9c379c9..0000000000 --- a/src/cmd/internal/notsha256/example_test.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package notsha256_test - -import ( - "crypto/sha256" - "fmt" - "io" - "log" - "os" -) - -func ExampleSum256() { - sum := sha256.Sum256([]byte("hello world\n")) - fmt.Printf("%x", sum) - // Output: a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447 -} - -func ExampleNew() { - h := sha256.New() - h.Write([]byte("hello world\n")) - fmt.Printf("%x", h.Sum(nil)) - // Output: a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447 -} - -func ExampleNew_file() { - f, err := os.Open("file.txt") - if err != nil { - log.Fatal(err) - } - defer f.Close() - - h := sha256.New() - if _, err := io.Copy(h, f); err != nil { - log.Fatal(err) - } - - fmt.Printf("%x", h.Sum(nil)) -} diff --git a/src/crypto/aes/cipher.go b/src/crypto/aes/cipher.go index 29d01796eb..db0ee38b78 100644 --- a/src/crypto/aes/cipher.go +++ b/src/crypto/aes/cipher.go @@ -6,12 +6,11 @@ package aes import ( "crypto/cipher" + "crypto/internal/boring" "crypto/internal/subtle" "strconv" ) -import "crypto/internal/boring" - // The AES block size in bytes. const BlockSize = 16 diff --git a/src/crypto/aes/cipher_asm.go b/src/crypto/aes/cipher_asm.go index b7e59d7edb..1482b22d08 100644 --- a/src/crypto/aes/cipher_asm.go +++ b/src/crypto/aes/cipher_asm.go @@ -8,13 +8,12 @@ package aes import ( "crypto/cipher" + "crypto/internal/boring" "crypto/internal/subtle" "internal/cpu" "internal/goarch" ) -import "crypto/internal/boring" - // defined in asm_*.s //go:noescape diff --git a/src/crypto/ecdsa/ecdsa.go b/src/crypto/ecdsa/ecdsa.go index 7ce7542872..d0e52ad864 100644 --- a/src/crypto/ecdsa/ecdsa.go +++ b/src/crypto/ecdsa/ecdsa.go @@ -24,6 +24,7 @@ import ( "crypto/aes" "crypto/cipher" "crypto/elliptic" + "crypto/internal/boring" "crypto/internal/boring/bbig" "crypto/internal/randutil" "crypto/sha512" @@ -31,8 +32,6 @@ import ( "io" "math/big" - "crypto/internal/boring" - "golang.org/x/crypto/cryptobyte" "golang.org/x/crypto/cryptobyte/asn1" ) diff --git a/src/crypto/hmac/hmac.go b/src/crypto/hmac/hmac.go index 34805765d5..ed3ebc0602 100644 --- a/src/crypto/hmac/hmac.go +++ b/src/crypto/hmac/hmac.go @@ -22,12 +22,11 @@ timing side-channels: package hmac import ( + "crypto/internal/boring" "crypto/subtle" "hash" ) -import "crypto/internal/boring" - // FIPS 198-1: // https://csrc.nist.gov/publications/fips/fips198-1/FIPS-198-1_final.pdf diff --git a/src/crypto/internal/boring/aes.go b/src/crypto/internal/boring/aes.go index 515b60bb8a..eaa1adc892 100644 --- a/src/crypto/internal/boring/aes.go +++ b/src/crypto/internal/boring/aes.go @@ -72,9 +72,6 @@ type extraModes interface { NewCBCDecrypter(iv []byte) cipher.BlockMode NewCTR(iv []byte) cipher.Stream NewGCM(nonceSize, tagSize int) (cipher.AEAD, error) - - // Invented for BoringCrypto. - NewGCMTLS() (cipher.AEAD, error) } var _ extraModes = (*aesCipher)(nil) @@ -235,8 +232,8 @@ func (c *aesCipher) NewGCM(nonceSize, tagSize int) (cipher.AEAD, error) { return c.newGCM(false) } -func (c *aesCipher) NewGCMTLS() (cipher.AEAD, error) { - return c.newGCM(true) +func NewGCMTLS(c cipher.Block) (cipher.AEAD, error) { + return c.(*aesCipher).newGCM(true) } func (c *aesCipher) newGCM(tls bool) (cipher.AEAD, error) { diff --git a/src/crypto/internal/boring/notboring.go b/src/crypto/internal/boring/notboring.go index bb88fb0004..53096a68d1 100644 --- a/src/crypto/internal/boring/notboring.go +++ b/src/crypto/internal/boring/notboring.go @@ -50,6 +50,7 @@ func SHA512([]byte) [64]byte { panic("boringcrypto: not available") } func NewHMAC(h func() hash.Hash, key []byte) hash.Hash { panic("boringcrypto: not available") } func NewAESCipher(key []byte) (cipher.Block, error) { panic("boringcrypto: not available") } +func NewGCMTLS(cipher.Block) (cipher.AEAD, error) { panic("boringcrypto: not available") } type PublicKeyECDSA struct{ _ int } type PrivateKeyECDSA struct{ _ int } diff --git a/src/crypto/rand/rand_unix.go b/src/crypto/rand/rand_unix.go index 830983c74a..746e90cc91 100644 --- a/src/crypto/rand/rand_unix.go +++ b/src/crypto/rand/rand_unix.go @@ -10,6 +10,7 @@ package rand import ( + "crypto/internal/boring" "errors" "io" "os" @@ -19,8 +20,6 @@ import ( "time" ) -import "crypto/internal/boring" - const urandomDevice = "/dev/urandom" func init() { diff --git a/src/crypto/rsa/pkcs1v15.go b/src/crypto/rsa/pkcs1v15.go index 8cf3b6e255..ab19229a6c 100644 --- a/src/crypto/rsa/pkcs1v15.go +++ b/src/crypto/rsa/pkcs1v15.go @@ -6,16 +6,14 @@ package rsa import ( "crypto" + "crypto/internal/boring" + "crypto/internal/randutil" "crypto/subtle" "errors" "io" "math/big" - - "crypto/internal/randutil" ) -import "crypto/internal/boring" - // This file implements encryption and decryption using PKCS #1 v1.5 padding. // PKCS1v15DecrypterOpts is for passing options to PKCS #1 v1.5 decryption using @@ -32,7 +30,7 @@ type PKCS1v15DecryptOptions struct { // scheme from PKCS #1 v1.5. The message must be no longer than the // length of the public modulus minus 11 bytes. // -// The rand parameter is used as a source of entropy to ensure that +// The random parameter is used as a source of entropy to ensure that // encrypting the same message twice doesn't result in the same // ciphertext. // @@ -84,14 +82,14 @@ func EncryptPKCS1v15(random io.Reader, pub *PublicKey, msg []byte) ([]byte, erro } // DecryptPKCS1v15 decrypts a plaintext using RSA and the padding scheme from PKCS #1 v1.5. -// If rand != nil, it uses RSA blinding to avoid timing side-channel attacks. +// If random != nil, it uses RSA blinding to avoid timing side-channel attacks. // // Note that whether this function returns an error or not discloses secret // information. If an attacker can cause this function to run repeatedly and // learn whether each instance returned an error then they can decrypt and // forge signatures as if they had the private key. See // DecryptPKCS1v15SessionKey for a way of solving this problem. -func DecryptPKCS1v15(rand io.Reader, priv *PrivateKey, ciphertext []byte) ([]byte, error) { +func DecryptPKCS1v15(random io.Reader, priv *PrivateKey, ciphertext []byte) ([]byte, error) { if err := checkPub(&priv.PublicKey); err != nil { return nil, err } @@ -108,7 +106,7 @@ func DecryptPKCS1v15(rand io.Reader, priv *PrivateKey, ciphertext []byte) ([]byt return out, nil } - valid, out, index, err := decryptPKCS1v15(rand, priv, ciphertext) + valid, out, index, err := decryptPKCS1v15(random, priv, ciphertext) if err != nil { return nil, err } @@ -119,7 +117,7 @@ func DecryptPKCS1v15(rand io.Reader, priv *PrivateKey, ciphertext []byte) ([]byt } // DecryptPKCS1v15SessionKey decrypts a session key using RSA and the padding scheme from PKCS #1 v1.5. -// If rand != nil, it uses RSA blinding to avoid timing side-channel attacks. +// If random != nil, it uses RSA blinding to avoid timing side-channel attacks. // It returns an error if the ciphertext is the wrong length or if the // ciphertext is greater than the public modulus. Otherwise, no error is // returned. If the padding is valid, the resulting plaintext message is copied @@ -137,7 +135,7 @@ func DecryptPKCS1v15(rand io.Reader, priv *PrivateKey, ciphertext []byte) ([]byt // a random value was used (because it'll be different for the same ciphertext) // and thus whether the padding was correct. This defeats the point of this // function. Using at least a 16-byte key will protect against this attack. -func DecryptPKCS1v15SessionKey(rand io.Reader, priv *PrivateKey, ciphertext []byte, key []byte) error { +func DecryptPKCS1v15SessionKey(random io.Reader, priv *PrivateKey, ciphertext []byte, key []byte) error { if err := checkPub(&priv.PublicKey); err != nil { return err } @@ -146,7 +144,7 @@ func DecryptPKCS1v15SessionKey(rand io.Reader, priv *PrivateKey, ciphertext []by return ErrDecryption } - valid, em, index, err := decryptPKCS1v15(rand, priv, ciphertext) + valid, em, index, err := decryptPKCS1v15(random, priv, ciphertext) if err != nil { return err } @@ -163,12 +161,12 @@ func DecryptPKCS1v15SessionKey(rand io.Reader, priv *PrivateKey, ciphertext []by } // decryptPKCS1v15 decrypts ciphertext using priv and blinds the operation if -// rand is not nil. It returns one or zero in valid that indicates whether the +// random is not nil. It returns one or zero in valid that indicates whether the // plaintext was correctly structured. In either case, the plaintext is // returned in em so that it may be read independently of whether it was valid // in order to maintain constant memory access patterns. If the plaintext was // valid then index contains the index of the original message in em. -func decryptPKCS1v15(rand io.Reader, priv *PrivateKey, ciphertext []byte) (valid int, em []byte, index int, err error) { +func decryptPKCS1v15(random io.Reader, priv *PrivateKey, ciphertext []byte) (valid int, em []byte, index int, err error) { k := priv.Size() if k < 11 { err = ErrDecryption @@ -188,7 +186,7 @@ func decryptPKCS1v15(rand io.Reader, priv *PrivateKey, ciphertext []byte) (valid } else { c := new(big.Int).SetBytes(ciphertext) var m *big.Int - m, err = decrypt(rand, priv, c) + m, err = decrypt(random, priv, c) if err != nil { return } @@ -220,15 +218,15 @@ func decryptPKCS1v15(rand io.Reader, priv *PrivateKey, ciphertext []byte) (valid } // nonZeroRandomBytes fills the given slice with non-zero random octets. -func nonZeroRandomBytes(s []byte, rand io.Reader) (err error) { - _, err = io.ReadFull(rand, s) +func nonZeroRandomBytes(s []byte, random io.Reader) (err error) { + _, err = io.ReadFull(random, s) if err != nil { return } for i := 0; i < len(s); i++ { for s[i] == 0 { - _, err = io.ReadFull(rand, s[i:i+1]) + _, err = io.ReadFull(random, s[i:i+1]) if err != nil { return } @@ -268,7 +266,7 @@ var hashPrefixes = map[crypto.Hash][]byte{ // function. If hash is zero, hashed is signed directly. This isn't // advisable except for interoperability. // -// If rand is not nil then RSA blinding will be used to avoid timing +// If random is not nil then RSA blinding will be used to avoid timing // side-channel attacks. // // This function is deterministic. Thus, if the set of possible diff --git a/src/crypto/rsa/pss.go b/src/crypto/rsa/pss.go index 16ebc0e6a7..29e79bd342 100644 --- a/src/crypto/rsa/pss.go +++ b/src/crypto/rsa/pss.go @@ -9,14 +9,13 @@ package rsa import ( "bytes" "crypto" + "crypto/internal/boring" "errors" "hash" "io" "math/big" ) -import "crypto/internal/boring" - // Per RFC 8017, Section 9.1 // // EM = MGF1 xor DB || H( 8*0x00 || mHash || salt ) || 0xbc @@ -298,6 +297,7 @@ func SignPSS(rand io.Reader, priv *PrivateKey, hash crypto.Hash, digest []byte, } return boring.SignRSAPSS(bkey, hash, digest, saltLength) } + boring.UnreachableExceptTests() salt := make([]byte, saltLength) if _, err := io.ReadFull(rand, salt); err != nil { diff --git a/src/crypto/tls/cipher_suites.go b/src/crypto/tls/cipher_suites.go index 3004b31698..9a1fa3104b 100644 --- a/src/crypto/tls/cipher_suites.go +++ b/src/crypto/tls/cipher_suites.go @@ -4,14 +4,13 @@ package tls -import "crypto/internal/boring" - import ( "crypto" "crypto/aes" "crypto/cipher" "crypto/des" "crypto/hmac" + "crypto/internal/boring" "crypto/rc4" "crypto/sha1" "crypto/sha256" @@ -517,12 +516,9 @@ func aeadAESGCM(key, noncePrefix []byte) aead { if err != nil { panic(err) } - type gcmtls interface { - NewGCMTLS() (cipher.AEAD, error) - } var aead cipher.AEAD - if aesTLS, ok := aes.(gcmtls); ok { - aead, err = aesTLS.NewGCMTLS() + if boring.Enabled { + aead, err = boring.NewGCMTLS(aes) } else { boring.Unreachable() aead, err = cipher.NewGCM(aes) diff --git a/src/go/build/build.go b/src/go/build/build.go index 039b422dab..bfe3f444ca 100644 --- a/src/go/build/build.go +++ b/src/go/build/build.go @@ -1883,11 +1883,13 @@ func (ctxt *Context) eval(x constraint.Expr, allTags map[string]bool) bool { // cgo (if cgo is enabled) // $GOOS // $GOARCH -// boringcrypto // ctxt.Compiler // linux (if GOOS = android) // solaris (if GOOS = illumos) -// tag (if tag is listed in ctxt.BuildTags or ctxt.ReleaseTags) +// darwin (if GOOS = ios) +// unix (if this is a Unix GOOS) +// boringcrypto (if GOEXPERIMENT=boringcrypto is enabled) +// tag (if tag is listed in ctxt.BuildTags, ctxt.ToolTags, or ctxt.ReleaseTags) // // It records all consulted tags in allTags. func (ctxt *Context) matchTag(name string, allTags map[string]bool) bool { -- cgit v1.3-5-g9baa From 11195c60e6197016c0d5d32b04d4cb0ca7594014 Mon Sep 17 00:00:00 2001 From: Michael Matloob Date: Tue, 3 May 2022 13:17:08 -0400 Subject: cmd/go: use index to match packages in dependency modules If we're trying to search in a module in the module cache, instead iterate over the packages in the index. Change-Id: Ia94cbe6e9690110c28b93dbb33810680e3010381 Reviewed-on: https://go-review.googlesource.com/c/go/+/403756 Reviewed-by: Michael Matloob Reviewed-by: Peter Weinberger Run-TryBot: Michael Matloob TryBot-Result: Gopher Robot --- src/cmd/go/internal/modindex/read.go | 2 +- src/cmd/go/internal/modindex/scan.go | 7 ++-- src/cmd/go/internal/modload/search.go | 63 +++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 4 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/go/internal/modindex/read.go b/src/cmd/go/internal/modindex/read.go index 4f02ca5d10..f259a8dbe3 100644 --- a/src/cmd/go/internal/modindex/read.go +++ b/src/cmd/go/internal/modindex/read.go @@ -210,7 +210,7 @@ func (mi *ModuleIndex) Packages() []string { // RelPath returns the path relative to the module's root. func (mi *ModuleIndex) RelPath(path string) string { - return filepath.Clean(str.TrimFilePathPrefix(path, mi.modroot)) + return str.TrimFilePathPrefix(path, mi.modroot) } // ImportPackage is the equivalent of build.Import given the information in ModuleIndex. diff --git a/src/cmd/go/internal/modindex/scan.go b/src/cmd/go/internal/modindex/scan.go index e40d3e0f53..d1f73dbb53 100644 --- a/src/cmd/go/internal/modindex/scan.go +++ b/src/cmd/go/internal/modindex/scan.go @@ -4,6 +4,7 @@ import ( "cmd/go/internal/base" "cmd/go/internal/fsys" "cmd/go/internal/par" + "cmd/go/internal/str" "encoding/json" "errors" "fmt" @@ -54,10 +55,10 @@ func indexModule(modroot string) ([]byte, error) { if !info.IsDir() { return nil } - rel, err := filepath.Rel(modroot, path) - if err != nil { - panic(err) + if !str.HasFilePathPrefix(path, modroot) { + panic(fmt.Errorf("path %v in walk doesn't have modroot %v as prefix:", path, modroot)) } + rel := str.TrimFilePathPrefix(path, modroot) packages = append(packages, importRaw(modroot, rel)) return nil }) diff --git a/src/cmd/go/internal/modload/search.go b/src/cmd/go/internal/modload/search.go index cddb9f8067..60c68860ed 100644 --- a/src/cmd/go/internal/modload/search.go +++ b/src/cmd/go/internal/modload/search.go @@ -6,15 +6,18 @@ package modload import ( "context" + "errors" "fmt" "io/fs" "os" + "path" "path/filepath" "strings" "cmd/go/internal/cfg" "cmd/go/internal/fsys" "cmd/go/internal/imports" + "cmd/go/internal/modindex" "cmd/go/internal/search" "golang.org/x/mod/module" @@ -165,6 +168,12 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f } modPrefix = mod.Path } + if mi, err := modindex.Get(root); err == nil { + walkFromIndex(ctx, m, tags, root, mi, have, modPrefix) + continue + } else if !errors.Is(err, modindex.ErrNotIndexed) { + m.AddError(err) + } prune := pruneVendor if isLocal { @@ -176,6 +185,60 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f return } +// walkFromIndex matches packages in a module using the module index. modroot +// is the module's root directory on disk, index is the ModuleIndex for the +// module, and importPathRoot is the module's path prefix. +func walkFromIndex(ctx context.Context, m *search.Match, tags map[string]bool, modroot string, index *modindex.ModuleIndex, have map[string]bool, importPathRoot string) { + isMatch := func(string) bool { return true } + treeCanMatch := func(string) bool { return true } + if !m.IsMeta() { + isMatch = search.MatchPattern(m.Pattern()) + treeCanMatch = search.TreeCanMatchPattern(m.Pattern()) + } +loopPackages: + for _, reldir := range index.Packages() { + // Avoid .foo, _foo, and testdata subdirectory trees. + p := reldir + for { + elem, rest, found := strings.Cut(p, string(filepath.Separator)) + if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" { + continue loopPackages + } + if found && elem == "vendor" { + // Ignore this path if it contains the element "vendor" anywhere + // except for the last element (packages named vendor are allowed + // for historical reasons). Note that found is true when this + // isn't the last path element. + continue loopPackages + } + if !found { + // Didn't find the separator, so we're considering the last element. + break + } + p = rest + } + + // Don't use GOROOT/src. + if reldir == "" && importPathRoot == "" { + continue + } + + name := path.Join(importPathRoot, filepath.ToSlash(reldir)) + if !treeCanMatch(name) { + continue + } + + if !have[name] { + have[name] = true + if isMatch(name) { + if _, _, err := index.ScanDir(reldir, tags); err != imports.ErrNoGo { + m.Pkgs = append(m.Pkgs, name) + } + } + } + } +} + // MatchInModule identifies the packages matching the given pattern within the // given module version, which does not need to be in the build list or module // requirement graph. -- cgit v1.3-5-g9baa From 38607c553878da21b5042e63997ecb3b7201e684 Mon Sep 17 00:00:00 2001 From: Motiejus Jakštys Date: Tue, 7 Jun 2022 05:55:40 +0000 Subject: cmd/link: specify -Wl,-z params as documented Both GNU and LLVM linkers de facto accept `-zPARAM`, and Go sometimes does it. Inconsistently: there are more uses of `-z PARAM` than `-zPARAM`: $ git grep -E -- '-Wl,-z[^,]' master | wc -l 4 $ git grep -E -- '-Wl,-z,' master | wc -l 7 However, not adding a space between `-z` and the param is not documented: llvm-13: $ man ld.lld-13 | grep -E -A1 -w -- "^ +-z" -z option Linker option extensions. gnu ld: $ man ld | grep -E -A1 -w -- "^ +-z" -z keyword The recognized keywords are: -- -z defs Report unresolved symbol references from regular object files. This is done even if the linker is creating a non-symbolic -- -z muldefs Normally when a symbol is defined multiple times, the linker will report a fatal error. These options allow multiple definitions -- -z --imagic ... and thus should be avoided. `zig cc`, when used as the C compiler (`CC="zig cc" go build ...`), will bark, because `zig cc` accepts only `-z PARAM`, as documented. Closes ziglang/zig#11669 Change-Id: I758054ecaa3ce01a72600bf65d7f7b5c3ec46d09 GitHub-Last-Rev: e068e007da9f2b0441ee0aa8b198a7ba3cd93ed3 GitHub-Pull-Request: golang/go#53030 Reviewed-on: https://go-review.googlesource.com/c/go/+/407834 TryBot-Result: Gopher Robot Reviewed-by: Alan Donovan Reviewed-by: Cherry Mui Run-TryBot: Cherry Mui --- src/cmd/link/internal/ld/lib.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go index 19678adbd5..9a5d89a6f7 100644 --- a/src/cmd/link/internal/ld/lib.go +++ b/src/cmd/link/internal/ld/lib.go @@ -1463,12 +1463,12 @@ func (ctxt *Link) hostlink() { // We force all symbol resolution to be done at program startup // because lazy PLT resolution can use large amounts of stack at // times we cannot allow it to do so. - argv = append(argv, "-Wl,-znow") + argv = append(argv, "-Wl,-z,now") // Do not let the host linker generate COPY relocations. These // can move symbols out of sections that rely on stable offsets // from the beginning of the section (like sym.STYPE). - argv = append(argv, "-Wl,-znocopyreloc") + argv = append(argv, "-Wl,-z,nocopyreloc") if buildcfg.GOOS == "android" { // Use lld to avoid errors from default linker (issue #38838) -- cgit v1.3-5-g9baa From d4fb93be87c38aaf0f68ad91852f9f83be726262 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Tue, 7 Jun 2022 10:33:01 -0700 Subject: go/types, types2: use | rather than ∪ when printing term lists MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With this change, the termlist String() function prints termlists in the usual Go notation and thus we can use it in error reporting. Preparation for fixing #40350. For #40350. Change-Id: Ia28318841305de234a71af3146ce0c59f5e601a5 Reviewed-on: https://go-review.googlesource.com/c/go/+/410894 Reviewed-by: Robert Griesemer Reviewed-by: Robert Findley --- src/cmd/compile/internal/types2/termlist.go | 2 +- src/cmd/compile/internal/types2/termlist_test.go | 140 +++++++++++------------ src/cmd/compile/internal/types2/typeset_test.go | 6 +- src/go/types/termlist.go | 2 +- src/go/types/termlist_test.go | 140 +++++++++++------------ src/go/types/typeset_test.go | 6 +- 6 files changed, 148 insertions(+), 148 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/types2/termlist.go b/src/cmd/compile/internal/types2/termlist.go index a0108c4638..43e43ce87c 100644 --- a/src/cmd/compile/internal/types2/termlist.go +++ b/src/cmd/compile/internal/types2/termlist.go @@ -25,7 +25,7 @@ func (xl termlist) String() string { var buf bytes.Buffer for i, x := range xl { if i > 0 { - buf.WriteString(" ∪ ") + buf.WriteString(" | ") } buf.WriteString(x.String()) } diff --git a/src/cmd/compile/internal/types2/termlist_test.go b/src/cmd/compile/internal/types2/termlist_test.go index d1e3bdf88e..3005d0edea 100644 --- a/src/cmd/compile/internal/types2/termlist_test.go +++ b/src/cmd/compile/internal/types2/termlist_test.go @@ -12,7 +12,7 @@ import ( // maketl makes a term list from a string of the term list. func maketl(s string) termlist { s = strings.ReplaceAll(s, " ", "") - names := strings.Split(s, "∪") + names := strings.Split(s, "|") r := make(termlist, len(names)) for i, n := range names { r[i] = testTerm(n) @@ -33,10 +33,10 @@ func TestTermlistString(t *testing.T) { "int", "~int", "myInt", - "∅ ∪ ∅", - "𝓤 ∪ 𝓤", - "∅ ∪ 𝓤 ∪ int", - "∅ ∪ 𝓤 ∪ int ∪ myInt", + "∅ | ∅", + "𝓤 | 𝓤", + "∅ | 𝓤 | int", + "∅ | 𝓤 | int | myInt", } { if got := maketl(want).String(); got != want { t.Errorf("(%v).String() == %v", want, got) @@ -47,12 +47,12 @@ func TestTermlistString(t *testing.T) { func TestTermlistIsEmpty(t *testing.T) { for test, want := range map[string]bool{ "∅": true, - "∅ ∪ ∅": true, - "∅ ∪ ∅ ∪ 𝓤": false, - "∅ ∪ ∅ ∪ myInt": false, + "∅ | ∅": true, + "∅ | ∅ | 𝓤": false, + "∅ | ∅ | myInt": false, "𝓤": false, - "𝓤 ∪ int": false, - "𝓤 ∪ myInt ∪ ∅": false, + "𝓤 | int": false, + "𝓤 | myInt | ∅": false, } { xl := maketl(test) got := xl.isEmpty() @@ -65,13 +65,13 @@ func TestTermlistIsEmpty(t *testing.T) { func TestTermlistIsAll(t *testing.T) { for test, want := range map[string]bool{ "∅": false, - "∅ ∪ ∅": false, - "int ∪ ~string": false, - "~int ∪ myInt": false, - "∅ ∪ ∅ ∪ 𝓤": true, + "∅ | ∅": false, + "int | ~string": false, + "~int | myInt": false, + "∅ | ∅ | 𝓤": true, "𝓤": true, - "𝓤 ∪ int": true, - "myInt ∪ 𝓤": true, + "𝓤 | int": true, + "myInt | 𝓤": true, } { xl := maketl(test) got := xl.isAll() @@ -86,17 +86,17 @@ func TestTermlistNorm(t *testing.T) { xl, want string }{ {"∅", "∅"}, - {"∅ ∪ ∅", "∅"}, - {"∅ ∪ int", "int"}, - {"∅ ∪ myInt", "myInt"}, - {"𝓤 ∪ int", "𝓤"}, - {"𝓤 ∪ myInt", "𝓤"}, - {"int ∪ myInt", "int ∪ myInt"}, - {"~int ∪ int", "~int"}, - {"~int ∪ myInt", "~int"}, - {"int ∪ ~string ∪ int", "int ∪ ~string"}, - {"~int ∪ string ∪ 𝓤 ∪ ~string ∪ int", "𝓤"}, - {"~int ∪ string ∪ myInt ∪ ~string ∪ int", "~int ∪ ~string"}, + {"∅ | ∅", "∅"}, + {"∅ | int", "int"}, + {"∅ | myInt", "myInt"}, + {"𝓤 | int", "𝓤"}, + {"𝓤 | myInt", "𝓤"}, + {"int | myInt", "int | myInt"}, + {"~int | int", "~int"}, + {"~int | myInt", "~int"}, + {"int | ~string | int", "int | ~string"}, + {"~int | string | 𝓤 | ~string | int", "𝓤"}, + {"~int | string | myInt | ~string | int", "~int | ~string"}, } { xl := maketl(test.xl) got := maketl(test.xl).norm() @@ -116,15 +116,15 @@ func TestTermlistUnion(t *testing.T) { {"∅", "int", "int"}, {"𝓤", "~int", "𝓤"}, {"int", "~int", "~int"}, - {"int", "string", "int ∪ string"}, - {"int", "myInt", "int ∪ myInt"}, + {"int", "string", "int | string"}, + {"int", "myInt", "int | myInt"}, {"~int", "myInt", "~int"}, - {"int ∪ string", "~string", "int ∪ ~string"}, - {"~int ∪ string", "~string ∪ int", "~int ∪ ~string"}, - {"~int ∪ string ∪ ∅", "~string ∪ int", "~int ∪ ~string"}, - {"~int ∪ myInt ∪ ∅", "~string ∪ int", "~int ∪ ~string"}, - {"~int ∪ string ∪ 𝓤", "~string ∪ int", "𝓤"}, - {"~int ∪ string ∪ myInt", "~string ∪ int", "~int ∪ ~string"}, + {"int | string", "~string", "int | ~string"}, + {"~int | string", "~string | int", "~int | ~string"}, + {"~int | string | ∅", "~string | int", "~int | ~string"}, + {"~int | myInt | ∅", "~string | int", "~int | ~string"}, + {"~int | string | 𝓤", "~string | int", "𝓤"}, + {"~int | string | myInt", "~string | int", "~int | ~string"}, } { xl := maketl(test.xl) yl := maketl(test.yl) @@ -150,12 +150,12 @@ func TestTermlistIntersect(t *testing.T) { {"int", "string", "∅"}, {"int", "myInt", "∅"}, {"~int", "myInt", "myInt"}, - {"int ∪ string", "~string", "string"}, - {"~int ∪ string", "~string ∪ int", "int ∪ string"}, - {"~int ∪ string ∪ ∅", "~string ∪ int", "int ∪ string"}, - {"~int ∪ myInt ∪ ∅", "~string ∪ int", "int"}, - {"~int ∪ string ∪ 𝓤", "~string ∪ int", "int ∪ ~string"}, - {"~int ∪ string ∪ myInt", "~string ∪ int", "int ∪ string"}, + {"int | string", "~string", "string"}, + {"~int | string", "~string | int", "int | string"}, + {"~int | string | ∅", "~string | int", "int | string"}, + {"~int | myInt | ∅", "~string | int", "int"}, + {"~int | string | 𝓤", "~string | int", "int | ~string"}, + {"~int | string | myInt", "~string | int", "int | string"}, } { xl := maketl(test.xl) yl := maketl(test.yl) @@ -174,12 +174,12 @@ func TestTermlistEqual(t *testing.T) { {"∅", "∅", true}, {"∅", "𝓤", false}, {"𝓤", "𝓤", true}, - {"𝓤 ∪ int", "𝓤", true}, - {"𝓤 ∪ int", "string ∪ 𝓤", true}, - {"𝓤 ∪ myInt", "string ∪ 𝓤", true}, - {"int ∪ ~string", "string ∪ int", false}, - {"~int ∪ string", "string ∪ myInt", false}, - {"int ∪ ~string ∪ ∅", "string ∪ int ∪ ~string", true}, + {"𝓤 | int", "𝓤", true}, + {"𝓤 | int", "string | 𝓤", true}, + {"𝓤 | myInt", "string | 𝓤", true}, + {"int | ~string", "string | int", false}, + {"~int | string", "string | myInt", false}, + {"int | ~string | ∅", "string | int | ~string", true}, } { xl := maketl(test.xl) yl := maketl(test.yl) @@ -201,11 +201,11 @@ func TestTermlistIncludes(t *testing.T) { {"int", "string", false}, {"~int", "string", false}, {"~int", "myInt", true}, - {"int ∪ string", "string", true}, - {"~int ∪ string", "int", true}, - {"~int ∪ string", "myInt", true}, - {"~int ∪ myInt ∪ ∅", "myInt", true}, - {"myInt ∪ ∅ ∪ 𝓤", "int", true}, + {"int | string", "string", true}, + {"~int | string", "int", true}, + {"~int | string", "myInt", true}, + {"~int | myInt | ∅", "myInt", true}, + {"myInt | ∅ | 𝓤", "int", true}, } { xl := maketl(test.xl) yl := testTerm(test.typ).typ @@ -236,12 +236,12 @@ func TestTermlistSupersetOf(t *testing.T) { {"myInt", "~int", false}, {"int", "string", false}, {"~int", "string", false}, - {"int ∪ string", "string", true}, - {"int ∪ string", "~string", false}, - {"~int ∪ string", "int", true}, - {"~int ∪ string", "myInt", true}, - {"~int ∪ string ∪ ∅", "string", true}, - {"~string ∪ ∅ ∪ 𝓤", "myInt", true}, + {"int | string", "string", true}, + {"int | string", "~string", false}, + {"~int | string", "int", true}, + {"~int | string", "myInt", true}, + {"~int | string | ∅", "string", true}, + {"~string | ∅ | 𝓤", "myInt", true}, } { xl := maketl(test.xl) y := testTerm(test.typ) @@ -261,18 +261,18 @@ func TestTermlistSubsetOf(t *testing.T) { {"∅", "𝓤", true}, {"𝓤", "∅", false}, {"𝓤", "𝓤", true}, - {"int", "int ∪ string", true}, - {"~int", "int ∪ string", false}, - {"~int", "myInt ∪ string", false}, - {"myInt", "~int ∪ string", true}, - {"~int", "string ∪ string ∪ int ∪ ~int", true}, - {"myInt", "string ∪ string ∪ ~int", true}, - {"int ∪ string", "string", false}, - {"int ∪ string", "string ∪ int", true}, - {"int ∪ ~string", "string ∪ int", false}, - {"myInt ∪ ~string", "string ∪ int ∪ 𝓤", true}, - {"int ∪ ~string", "string ∪ int ∪ ∅ ∪ string", false}, - {"int ∪ myInt", "string ∪ ~int ∪ ∅ ∪ string", true}, + {"int", "int | string", true}, + {"~int", "int | string", false}, + {"~int", "myInt | string", false}, + {"myInt", "~int | string", true}, + {"~int", "string | string | int | ~int", true}, + {"myInt", "string | string | ~int", true}, + {"int | string", "string", false}, + {"int | string", "string | int", true}, + {"int | ~string", "string | int", false}, + {"myInt | ~string", "string | int | 𝓤", true}, + {"int | ~string", "string | int | ∅ | string", false}, + {"int | myInt", "string | ~int | ∅ | string", true}, } { xl := maketl(test.xl) yl := maketl(test.yl) diff --git a/src/cmd/compile/internal/types2/typeset_test.go b/src/cmd/compile/internal/types2/typeset_test.go index 69eaff741f..40ca28e525 100644 --- a/src/cmd/compile/internal/types2/typeset_test.go +++ b/src/cmd/compile/internal/types2/typeset_test.go @@ -21,13 +21,13 @@ func TestTypeSetString(t *testing.T) { "{}": "𝓤", "{int}": "{int}", "{~int}": "{~int}", - "{int|string}": "{int ∪ string}", + "{int|string}": "{int | string}", "{int; string}": "∅", "{comparable}": "{comparable}", "{comparable; int}": "{int}", "{~int; comparable}": "{~int}", - "{int|string; comparable}": "{int ∪ string}", + "{int|string; comparable}": "{int | string}", "{comparable; int; string}": "∅", "{m()}": "{func (p.T).m()}", @@ -37,7 +37,7 @@ func TestTypeSetString(t *testing.T) { "{m1(); comparable; m2() int }": "{comparable; func (p.T).m1(); func (p.T).m2() int}", "{comparable; error}": "{comparable; func (error).Error() string}", - "{m(); comparable; int|float32|string}": "{func (p.T).m(); int ∪ float32 ∪ string}", + "{m(); comparable; int|float32|string}": "{func (p.T).m(); int | float32 | string}", "{m1(); int; m2(); comparable }": "{func (p.T).m1(); func (p.T).m2(); int}", "{E}; type E interface{}": "𝓤", diff --git a/src/go/types/termlist.go b/src/go/types/termlist.go index 94e49caee0..6d08ddb397 100644 --- a/src/go/types/termlist.go +++ b/src/go/types/termlist.go @@ -25,7 +25,7 @@ func (xl termlist) String() string { var buf bytes.Buffer for i, x := range xl { if i > 0 { - buf.WriteString(" ∪ ") + buf.WriteString(" | ") } buf.WriteString(x.String()) } diff --git a/src/go/types/termlist_test.go b/src/go/types/termlist_test.go index f0d58ac1bc..0ff687ebda 100644 --- a/src/go/types/termlist_test.go +++ b/src/go/types/termlist_test.go @@ -12,7 +12,7 @@ import ( // maketl makes a term list from a string of the term list. func maketl(s string) termlist { s = strings.ReplaceAll(s, " ", "") - names := strings.Split(s, "∪") + names := strings.Split(s, "|") r := make(termlist, len(names)) for i, n := range names { r[i] = testTerm(n) @@ -33,10 +33,10 @@ func TestTermlistString(t *testing.T) { "int", "~int", "myInt", - "∅ ∪ ∅", - "𝓤 ∪ 𝓤", - "∅ ∪ 𝓤 ∪ int", - "∅ ∪ 𝓤 ∪ int ∪ myInt", + "∅ | ∅", + "𝓤 | 𝓤", + "∅ | 𝓤 | int", + "∅ | 𝓤 | int | myInt", } { if got := maketl(want).String(); got != want { t.Errorf("(%v).String() == %v", want, got) @@ -47,12 +47,12 @@ func TestTermlistString(t *testing.T) { func TestTermlistIsEmpty(t *testing.T) { for test, want := range map[string]bool{ "∅": true, - "∅ ∪ ∅": true, - "∅ ∪ ∅ ∪ 𝓤": false, - "∅ ∪ ∅ ∪ myInt": false, + "∅ | ∅": true, + "∅ | ∅ | 𝓤": false, + "∅ | ∅ | myInt": false, "𝓤": false, - "𝓤 ∪ int": false, - "𝓤 ∪ myInt ∪ ∅": false, + "𝓤 | int": false, + "𝓤 | myInt | ∅": false, } { xl := maketl(test) got := xl.isEmpty() @@ -65,13 +65,13 @@ func TestTermlistIsEmpty(t *testing.T) { func TestTermlistIsAll(t *testing.T) { for test, want := range map[string]bool{ "∅": false, - "∅ ∪ ∅": false, - "int ∪ ~string": false, - "~int ∪ myInt": false, - "∅ ∪ ∅ ∪ 𝓤": true, + "∅ | ∅": false, + "int | ~string": false, + "~int | myInt": false, + "∅ | ∅ | 𝓤": true, "𝓤": true, - "𝓤 ∪ int": true, - "myInt ∪ 𝓤": true, + "𝓤 | int": true, + "myInt | 𝓤": true, } { xl := maketl(test) got := xl.isAll() @@ -86,17 +86,17 @@ func TestTermlistNorm(t *testing.T) { xl, want string }{ {"∅", "∅"}, - {"∅ ∪ ∅", "∅"}, - {"∅ ∪ int", "int"}, - {"∅ ∪ myInt", "myInt"}, - {"𝓤 ∪ int", "𝓤"}, - {"𝓤 ∪ myInt", "𝓤"}, - {"int ∪ myInt", "int ∪ myInt"}, - {"~int ∪ int", "~int"}, - {"~int ∪ myInt", "~int"}, - {"int ∪ ~string ∪ int", "int ∪ ~string"}, - {"~int ∪ string ∪ 𝓤 ∪ ~string ∪ int", "𝓤"}, - {"~int ∪ string ∪ myInt ∪ ~string ∪ int", "~int ∪ ~string"}, + {"∅ | ∅", "∅"}, + {"∅ | int", "int"}, + {"∅ | myInt", "myInt"}, + {"𝓤 | int", "𝓤"}, + {"𝓤 | myInt", "𝓤"}, + {"int | myInt", "int | myInt"}, + {"~int | int", "~int"}, + {"~int | myInt", "~int"}, + {"int | ~string | int", "int | ~string"}, + {"~int | string | 𝓤 | ~string | int", "𝓤"}, + {"~int | string | myInt | ~string | int", "~int | ~string"}, } { xl := maketl(test.xl) got := maketl(test.xl).norm() @@ -116,15 +116,15 @@ func TestTermlistUnion(t *testing.T) { {"∅", "int", "int"}, {"𝓤", "~int", "𝓤"}, {"int", "~int", "~int"}, - {"int", "string", "int ∪ string"}, - {"int", "myInt", "int ∪ myInt"}, + {"int", "string", "int | string"}, + {"int", "myInt", "int | myInt"}, {"~int", "myInt", "~int"}, - {"int ∪ string", "~string", "int ∪ ~string"}, - {"~int ∪ string", "~string ∪ int", "~int ∪ ~string"}, - {"~int ∪ string ∪ ∅", "~string ∪ int", "~int ∪ ~string"}, - {"~int ∪ myInt ∪ ∅", "~string ∪ int", "~int ∪ ~string"}, - {"~int ∪ string ∪ 𝓤", "~string ∪ int", "𝓤"}, - {"~int ∪ string ∪ myInt", "~string ∪ int", "~int ∪ ~string"}, + {"int | string", "~string", "int | ~string"}, + {"~int | string", "~string | int", "~int | ~string"}, + {"~int | string | ∅", "~string | int", "~int | ~string"}, + {"~int | myInt | ∅", "~string | int", "~int | ~string"}, + {"~int | string | 𝓤", "~string | int", "𝓤"}, + {"~int | string | myInt", "~string | int", "~int | ~string"}, } { xl := maketl(test.xl) yl := maketl(test.yl) @@ -150,12 +150,12 @@ func TestTermlistIntersect(t *testing.T) { {"int", "string", "∅"}, {"int", "myInt", "∅"}, {"~int", "myInt", "myInt"}, - {"int ∪ string", "~string", "string"}, - {"~int ∪ string", "~string ∪ int", "int ∪ string"}, - {"~int ∪ string ∪ ∅", "~string ∪ int", "int ∪ string"}, - {"~int ∪ myInt ∪ ∅", "~string ∪ int", "int"}, - {"~int ∪ string ∪ 𝓤", "~string ∪ int", "int ∪ ~string"}, - {"~int ∪ string ∪ myInt", "~string ∪ int", "int ∪ string"}, + {"int | string", "~string", "string"}, + {"~int | string", "~string | int", "int | string"}, + {"~int | string | ∅", "~string | int", "int | string"}, + {"~int | myInt | ∅", "~string | int", "int"}, + {"~int | string | 𝓤", "~string | int", "int | ~string"}, + {"~int | string | myInt", "~string | int", "int | string"}, } { xl := maketl(test.xl) yl := maketl(test.yl) @@ -174,12 +174,12 @@ func TestTermlistEqual(t *testing.T) { {"∅", "∅", true}, {"∅", "𝓤", false}, {"𝓤", "𝓤", true}, - {"𝓤 ∪ int", "𝓤", true}, - {"𝓤 ∪ int", "string ∪ 𝓤", true}, - {"𝓤 ∪ myInt", "string ∪ 𝓤", true}, - {"int ∪ ~string", "string ∪ int", false}, - {"~int ∪ string", "string ∪ myInt", false}, - {"int ∪ ~string ∪ ∅", "string ∪ int ∪ ~string", true}, + {"𝓤 | int", "𝓤", true}, + {"𝓤 | int", "string | 𝓤", true}, + {"𝓤 | myInt", "string | 𝓤", true}, + {"int | ~string", "string | int", false}, + {"~int | string", "string | myInt", false}, + {"int | ~string | ∅", "string | int | ~string", true}, } { xl := maketl(test.xl) yl := maketl(test.yl) @@ -201,11 +201,11 @@ func TestTermlistIncludes(t *testing.T) { {"int", "string", false}, {"~int", "string", false}, {"~int", "myInt", true}, - {"int ∪ string", "string", true}, - {"~int ∪ string", "int", true}, - {"~int ∪ string", "myInt", true}, - {"~int ∪ myInt ∪ ∅", "myInt", true}, - {"myInt ∪ ∅ ∪ 𝓤", "int", true}, + {"int | string", "string", true}, + {"~int | string", "int", true}, + {"~int | string", "myInt", true}, + {"~int | myInt | ∅", "myInt", true}, + {"myInt | ∅ | 𝓤", "int", true}, } { xl := maketl(test.xl) yl := testTerm(test.typ).typ @@ -236,12 +236,12 @@ func TestTermlistSupersetOf(t *testing.T) { {"myInt", "~int", false}, {"int", "string", false}, {"~int", "string", false}, - {"int ∪ string", "string", true}, - {"int ∪ string", "~string", false}, - {"~int ∪ string", "int", true}, - {"~int ∪ string", "myInt", true}, - {"~int ∪ string ∪ ∅", "string", true}, - {"~string ∪ ∅ ∪ 𝓤", "myInt", true}, + {"int | string", "string", true}, + {"int | string", "~string", false}, + {"~int | string", "int", true}, + {"~int | string", "myInt", true}, + {"~int | string | ∅", "string", true}, + {"~string | ∅ | 𝓤", "myInt", true}, } { xl := maketl(test.xl) y := testTerm(test.typ) @@ -261,18 +261,18 @@ func TestTermlistSubsetOf(t *testing.T) { {"∅", "𝓤", true}, {"𝓤", "∅", false}, {"𝓤", "𝓤", true}, - {"int", "int ∪ string", true}, - {"~int", "int ∪ string", false}, - {"~int", "myInt ∪ string", false}, - {"myInt", "~int ∪ string", true}, - {"~int", "string ∪ string ∪ int ∪ ~int", true}, - {"myInt", "string ∪ string ∪ ~int", true}, - {"int ∪ string", "string", false}, - {"int ∪ string", "string ∪ int", true}, - {"int ∪ ~string", "string ∪ int", false}, - {"myInt ∪ ~string", "string ∪ int ∪ 𝓤", true}, - {"int ∪ ~string", "string ∪ int ∪ ∅ ∪ string", false}, - {"int ∪ myInt", "string ∪ ~int ∪ ∅ ∪ string", true}, + {"int", "int | string", true}, + {"~int", "int | string", false}, + {"~int", "myInt | string", false}, + {"myInt", "~int | string", true}, + {"~int", "string | string | int | ~int", true}, + {"myInt", "string | string | ~int", true}, + {"int | string", "string", false}, + {"int | string", "string | int", true}, + {"int | ~string", "string | int", false}, + {"myInt | ~string", "string | int | 𝓤", true}, + {"int | ~string", "string | int | ∅ | string", false}, + {"int | myInt", "string | ~int | ∅ | string", true}, } { xl := maketl(test.xl) yl := maketl(test.yl) diff --git a/src/go/types/typeset_test.go b/src/go/types/typeset_test.go index 2bbe611376..5156092483 100644 --- a/src/go/types/typeset_test.go +++ b/src/go/types/typeset_test.go @@ -22,13 +22,13 @@ func TestTypeSetString(t *testing.T) { "{}": "𝓤", "{int}": "{int}", "{~int}": "{~int}", - "{int|string}": "{int ∪ string}", + "{int|string}": "{int | string}", "{int; string}": "∅", "{comparable}": "{comparable}", "{comparable; int}": "{int}", "{~int; comparable}": "{~int}", - "{int|string; comparable}": "{int ∪ string}", + "{int|string; comparable}": "{int | string}", "{comparable; int; string}": "∅", "{m()}": "{func (p.T).m()}", @@ -38,7 +38,7 @@ func TestTypeSetString(t *testing.T) { "{m1(); comparable; m2() int }": "{comparable; func (p.T).m1(); func (p.T).m2() int}", "{comparable; error}": "{comparable; func (error).Error() string}", - "{m(); comparable; int|float32|string}": "{func (p.T).m(); int ∪ float32 ∪ string}", + "{m(); comparable; int|float32|string}": "{func (p.T).m(); int | float32 | string}", "{m1(); int; m2(); comparable }": "{func (p.T).m1(); func (p.T).m2(); int}", "{E}; type E interface{}": "𝓤", -- cgit v1.3-5-g9baa From 269bf7e855da04e664fe8d7ffb654c4d0b1439f5 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Tue, 7 Jun 2022 10:43:51 -0700 Subject: go/types, types2: better error message if type is not in type set Fixes #40350. Change-Id: Ia654d6b854971700ca618692a864265557122b23 Reviewed-on: https://go-review.googlesource.com/c/go/+/410876 Reviewed-by: Robert Findley Reviewed-by: Robert Griesemer --- src/cmd/compile/internal/types2/instantiate.go | 2 +- .../internal/types2/testdata/fixedbugs/issue40350.go | 16 ++++++++++++++++ src/go/types/instantiate.go | 2 +- src/go/types/testdata/fixedbugs/issue40350.go | 16 ++++++++++++++++ 4 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 src/cmd/compile/internal/types2/testdata/fixedbugs/issue40350.go create mode 100644 src/go/types/testdata/fixedbugs/issue40350.go (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/types2/instantiate.go b/src/cmd/compile/internal/types2/instantiate.go index f338e28d2e..45f7e43ccf 100644 --- a/src/cmd/compile/internal/types2/instantiate.go +++ b/src/cmd/compile/internal/types2/instantiate.go @@ -277,7 +277,7 @@ func (check *Checker) implements(V, T Type) error { if alt != nil { return errorf("%s does not implement %s (possibly missing ~ for %s in constraint %s)", V, T, alt, T) } else { - return errorf("%s does not implement %s", V, T) + return errorf("%s does not implement %s (%s missing in %s)", V, T, V, Ti.typeSet().terms) } } diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue40350.go b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue40350.go new file mode 100644 index 0000000000..7ffd551c2e --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue40350.go @@ -0,0 +1,16 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +type number interface { + ~float64 | ~int | ~int32 + float64 | ~int32 +} + +func f[T number]() {} + +func _() { + _ = f[int /* ERROR int does not implement number \(int missing in float64 | ~int32\)*/] +} diff --git a/src/go/types/instantiate.go b/src/go/types/instantiate.go index 6091b0b381..e6b731f241 100644 --- a/src/go/types/instantiate.go +++ b/src/go/types/instantiate.go @@ -277,7 +277,7 @@ func (check *Checker) implements(V, T Type) error { if alt != nil { return errorf("%s does not implement %s (possibly missing ~ for %s in constraint %s)", V, T, alt, T) } else { - return errorf("%s does not implement %s", V, T) + return errorf("%s does not implement %s (%s missing in %s)", V, T, V, Ti.typeSet().terms) } } diff --git a/src/go/types/testdata/fixedbugs/issue40350.go b/src/go/types/testdata/fixedbugs/issue40350.go new file mode 100644 index 0000000000..7ffd551c2e --- /dev/null +++ b/src/go/types/testdata/fixedbugs/issue40350.go @@ -0,0 +1,16 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +type number interface { + ~float64 | ~int | ~int32 + float64 | ~int32 +} + +func f[T number]() {} + +func _() { + _ = f[int /* ERROR int does not implement number \(int missing in float64 | ~int32\)*/] +} -- cgit v1.3-5-g9baa From 3507805bcdcd6674c842e25fdb5f07f5ce47ba87 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Tue, 7 Jun 2022 13:39:56 -0700 Subject: go/types, types2: better error message for invalid use of constraint type Fixes #42881. Change-Id: If800c5f90c0034d192bf8b6649e5cfda96df48cb Reviewed-on: https://go-review.googlesource.com/c/go/+/410954 Reviewed-by: Robert Griesemer Reviewed-by: Robert Findley --- .../internal/types2/testdata/fixedbugs/issue42881.go | 16 ++++++++++++++++ src/cmd/compile/internal/types2/typexpr.go | 4 ++-- src/go/types/testdata/fixedbugs/issue42881.go | 16 ++++++++++++++++ src/go/types/typexpr.go | 4 ++-- 4 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 src/cmd/compile/internal/types2/testdata/fixedbugs/issue42881.go create mode 100644 src/go/types/testdata/fixedbugs/issue42881.go (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue42881.go b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue42881.go new file mode 100644 index 0000000000..7122d1c787 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue42881.go @@ -0,0 +1,16 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +type ( + T1 interface{ comparable } + T2 interface{ int } +) + +var ( + _ comparable // ERROR cannot use type comparable outside a type constraint: interface is \(or embeds\) comparable + _ T1 // ERROR cannot use type T1 outside a type constraint: interface is \(or embeds\) comparable + _ T2 // ERROR cannot use type T2 outside a type constraint: interface contains type constraints +) diff --git a/src/cmd/compile/internal/types2/typexpr.go b/src/cmd/compile/internal/types2/typexpr.go index f0cd236050..692feb9751 100644 --- a/src/cmd/compile/internal/types2/typexpr.go +++ b/src/cmd/compile/internal/types2/typexpr.go @@ -167,9 +167,9 @@ func (check *Checker) validVarType(e syntax.Expr, typ Type) { tset := computeInterfaceTypeSet(check, pos, t) // TODO(gri) is this the correct position? if !tset.IsMethodSet() { if tset.comparable { - check.softErrorf(pos, "interface is (or embeds) comparable") + check.softErrorf(pos, "cannot use type %s outside a type constraint: interface is (or embeds) comparable", typ) } else { - check.softErrorf(pos, "interface contains type constraints") + check.softErrorf(pos, "cannot use type %s outside a type constraint: interface contains type constraints", typ) } } } diff --git a/src/go/types/testdata/fixedbugs/issue42881.go b/src/go/types/testdata/fixedbugs/issue42881.go new file mode 100644 index 0000000000..7122d1c787 --- /dev/null +++ b/src/go/types/testdata/fixedbugs/issue42881.go @@ -0,0 +1,16 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +type ( + T1 interface{ comparable } + T2 interface{ int } +) + +var ( + _ comparable // ERROR cannot use type comparable outside a type constraint: interface is \(or embeds\) comparable + _ T1 // ERROR cannot use type T1 outside a type constraint: interface is \(or embeds\) comparable + _ T2 // ERROR cannot use type T2 outside a type constraint: interface contains type constraints +) diff --git a/src/go/types/typexpr.go b/src/go/types/typexpr.go index a881d33654..b02929df22 100644 --- a/src/go/types/typexpr.go +++ b/src/go/types/typexpr.go @@ -163,9 +163,9 @@ func (check *Checker) validVarType(e ast.Expr, typ Type) { tset := computeInterfaceTypeSet(check, e.Pos(), t) // TODO(gri) is this the correct position? if !tset.IsMethodSet() { if tset.comparable { - check.softErrorf(e, _MisplacedConstraintIface, "interface is (or embeds) comparable") + check.softErrorf(e, _MisplacedConstraintIface, "cannot use type %s outside a type constraint: interface is (or embeds) comparable", typ) } else { - check.softErrorf(e, _MisplacedConstraintIface, "interface contains type constraints") + check.softErrorf(e, _MisplacedConstraintIface, "cannot use type %s outside a type constraint: interface contains type constraints", typ) } } } -- cgit v1.3-5-g9baa From d65166024f3969289be5c74fd8be7d06a93264f1 Mon Sep 17 00:00:00 2001 From: Michael Matloob Date: Tue, 7 Jun 2022 15:31:20 -0400 Subject: cmd/go: set Root and target fields for packages in GOPATH This change replicates the behavior filed in issue #37015 for packages imported from the module index. That behavior is that packages that happen to exist in a GOPATH src directory have p.Root and p.Target set even when the packages are loaded from modules. This is likely unintentional behavior because in module mode, packages shouldn't behave differently depending on whether their directories exist in GOPATH. But for uniformity, (and because two of our tests depend on this behavior), this CL will implement this behavior. We can remove it from the module index when we remove it from the go/build logic. Change-Id: I3f501c92fbb76eaf86b6b9275539f2129b67f884 Reviewed-on: https://go-review.googlesource.com/c/go/+/410822 Reviewed-by: Michael Matloob Run-TryBot: Michael Matloob TryBot-Result: Gopher Robot Reviewed-by: Russ Cox --- src/cmd/go/internal/modindex/build.go | 31 ++++++++++++++++++++++++ src/cmd/go/internal/modindex/read.go | 44 ++++++++++++++++++++++++----------- 2 files changed, 61 insertions(+), 14 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/go/internal/modindex/build.go b/src/cmd/go/internal/modindex/build.go index 9d52be851b..d6d4ea371a 100644 --- a/src/cmd/go/internal/modindex/build.go +++ b/src/cmd/go/internal/modindex/build.go @@ -177,6 +177,37 @@ func hasSubdir(root, dir string) (rel string, ok bool) { return filepath.ToSlash(dir[len(root):]), true } +// gopath returns the list of Go path directories. +func (ctxt *Context) gopath() []string { + var all []string + for _, p := range ctxt.splitPathList(ctxt.GOPATH) { + if p == "" || p == ctxt.GOROOT { + // Empty paths are uninteresting. + // If the path is the GOROOT, ignore it. + // People sometimes set GOPATH=$GOROOT. + // Do not get confused by this common mistake. + continue + } + if strings.HasPrefix(p, "~") { + // Path segments starting with ~ on Unix are almost always + // users who have incorrectly quoted ~ while setting GOPATH, + // preventing it from expanding to $HOME. + // The situation is made more confusing by the fact that + // bash allows quoted ~ in $PATH (most shells do not). + // Do not get confused by this, and do not try to use the path. + // It does not exist, and printing errors about it confuses + // those users even more, because they think "sure ~ exists!". + // The go command diagnoses this situation and prints a + // useful error. + // On Windows, ~ is used in short names, such as c:\progra~1 + // for c:\program files. + continue + } + all = append(all, p) + } + return all +} + var defaultToolTags, defaultReleaseTags []string // A Package describes the Go package found in a directory. diff --git a/src/cmd/go/internal/modindex/read.go b/src/cmd/go/internal/modindex/read.go index f259a8dbe3..0ed480fbd0 100644 --- a/src/cmd/go/internal/modindex/read.go +++ b/src/cmd/go/internal/modindex/read.go @@ -242,31 +242,47 @@ func (mi *ModuleIndex) Import(bctxt build.Context, relpath string, mode build.Im return p, fmt.Errorf("import %q: import of unknown directory", p.Dir) } - // goroot + // goroot and gopath inTestdata := func(sub string) bool { return strings.Contains(sub, "/testdata/") || strings.HasSuffix(sub, "/testdata") || str.HasPathPrefix(sub, "testdata") } - if ctxt.GOROOT != "" && str.HasFilePathPrefix(mi.modroot, cfg.GOROOTsrc) && !inTestdata(relpath) { - modprefix := str.TrimFilePathPrefix(mi.modroot, cfg.GOROOTsrc) - p.Goroot = true - p.ImportPath = relpath - if modprefix != "" { - p.ImportPath = filepath.Join(modprefix, p.ImportPath) - } + if !inTestdata(relpath) { // In build.go, p.Root should only be set in the non-local-import case, or in // GOROOT or GOPATH. Since module mode only calls Import with path set to "." // and the module index doesn't apply outside modules, the GOROOT case is // the only case where GOROOT needs to be set. - // TODO(#37015): p.Root actually might be set in the local-import case outside - // GOROOT, if the directory is contained in GOPATH/src, even in module - // mode, but that's a bug. - p.Root = ctxt.GOROOT + // But: p.Root is actually set in the local-import case outside GOROOT, if + // the directory is contained in GOPATH/src + // TODO(#37015): fix that behavior in go/build and remove the gopath case + // below. + if ctxt.GOROOT != "" && str.HasFilePathPrefix(p.Dir, cfg.GOROOTsrc) && p.Dir != cfg.GOROOTsrc { + p.Root = ctxt.GOROOT + p.Goroot = true + modprefix := str.TrimFilePathPrefix(mi.modroot, cfg.GOROOTsrc) + p.ImportPath = relpath + if modprefix != "" { + p.ImportPath = filepath.Join(modprefix, p.ImportPath) + } + } + for _, root := range ctxt.gopath() { + // TODO(matloob): do we need to reimplement the conflictdir logic? - // Set GOROOT-specific fields + // TODO(matloob): ctxt.hasSubdir evaluates symlinks, so it + // can be slower than we'd like. Find out if we can drop this + // logic before the release. + if sub, ok := ctxt.hasSubdir(filepath.Join(root, "src"), p.Dir); ok { + p.ImportPath = sub + p.Root = root + } + } + } + if p.Root != "" { + // Set GOROOT-specific fields (sometimes for modules in a GOPATH directory). // The fields set below (SrcRoot, PkgRoot, BinDir, PkgTargetRoot, and PkgObj) // are only set in build.Import if p.Root != "". As noted in the comment // on setting p.Root above, p.Root should only be set in the GOROOT case for the - // set of packages we care about. + // set of packages we care about, but is also set for modules in a GOPATH src + // directory. var pkgtargetroot string var pkga string suffix := "" -- cgit v1.3-5-g9baa From f862280e30300017292b24a0fca088628d7b8065 Mon Sep 17 00:00:00 2001 From: Michael Matloob Date: Tue, 7 Jun 2022 13:54:53 -0400 Subject: cmd/go: properly call PackageModuleRoot to get modroot for index PackageModuleRoot needs to be called with the package's path, not its directory on disk. Change-Id: I080fe8ce2aeb72e1466624db81595a00915606bb Reviewed-on: https://go-review.googlesource.com/c/go/+/410820 Reviewed-by: Russ Cox Reviewed-by: Michael Matloob TryBot-Result: Gopher Robot Run-TryBot: Michael Matloob --- src/cmd/go/internal/load/pkg.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/cmd') diff --git a/src/cmd/go/internal/load/pkg.go b/src/cmd/go/internal/load/pkg.go index 4c7833b4d2..394a4a4383 100644 --- a/src/cmd/go/internal/load/pkg.go +++ b/src/cmd/go/internal/load/pkg.go @@ -871,7 +871,7 @@ func loadPackageData(ctx context.Context, path, parentPath, parentDir, parentRoo if !cfg.ModulesEnabled { buildMode = build.ImportComment } - if modroot := modload.PackageModRoot(ctx, r.dir); modroot != "" { + if modroot := modload.PackageModRoot(ctx, r.path); modroot != "" { if mi, err := modindex.Get(modroot); err == nil { data.p, data.err = mi.Import(cfg.BuildContext, mi.RelPath(r.dir), buildMode) goto Happy -- cgit v1.3-5-g9baa From 899f0a29c7be2bba3f8f0bc2987f7c2d70a6c4ec Mon Sep 17 00:00:00 2001 From: Michael Matloob Date: Tue, 7 Jun 2022 13:53:40 -0400 Subject: cmd/go: enable module index by default This changes the module index to be enabled by default, rather than disabled by default. The index can still be disabled by setting GODEBUG=index=0. Fixes #53290. Change-Id: Ic3728fc69d96bb6ef56b56e8c9f2dce35f2923cc Reviewed-on: https://go-review.googlesource.com/c/go/+/410821 Reviewed-by: Dmitri Shuralyov Reviewed-by: Russ Cox Reviewed-by: Michael Matloob Reviewed-by: Dmitri Shuralyov --- src/cmd/go/internal/modindex/read.go | 13 ++++++++++--- src/cmd/go/script_test.go | 1 - 2 files changed, 10 insertions(+), 4 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/go/internal/modindex/read.go b/src/cmd/go/internal/modindex/read.go index 0ed480fbd0..daa85762be 100644 --- a/src/cmd/go/internal/modindex/read.go +++ b/src/cmd/go/internal/modindex/read.go @@ -22,7 +22,6 @@ import ( "runtime" "runtime/debug" "sort" - "strconv" "strings" "sync" "unsafe" @@ -40,7 +39,15 @@ import ( // It will be removed before the release. // TODO(matloob): Remove enabled once we have more confidence on the // module index. -var enabled, _ = strconv.ParseBool(os.Getenv("GOINDEX")) +var enabled = func() bool { + debug := strings.Split(os.Getenv("GODEBUG"), ",") + for _, f := range debug { + if f == "goindex=0" { + return false + } + } + return true +}() // ModuleIndex represents and encoded module index file. It is used to // do the equivalent of build.Import of packages in the module and answer other @@ -125,7 +132,7 @@ func openIndex(modroot string, ismodcache bool) (*ModuleIndex, error) { data, _, err := cache.Default().GetMmap(id) if err != nil { // Couldn't read from modindex. Assume we couldn't read from - // the index because the module has't been indexed yet. + // the index because the module hasn't been indexed yet. data, err = indexModule(modroot) if err != nil { return result{nil, err} diff --git a/src/cmd/go/script_test.go b/src/cmd/go/script_test.go index d1fe36ec21..04bc8d581a 100644 --- a/src/cmd/go/script_test.go +++ b/src/cmd/go/script_test.go @@ -170,7 +170,6 @@ func (ts *testScript) setup() { "GOCACHE=" + testGOCACHE, "GODEBUG=" + os.Getenv("GODEBUG"), "GOEXE=" + cfg.ExeSuffix, - "GOINDEX=true", "GOOS=" + runtime.GOOS, "GOPATH=" + filepath.Join(ts.workdir, "gopath"), "GOPROXY=" + proxyURL, -- cgit v1.3-5-g9baa From 1292176bc98be4b7b9d24abec05e88b3dbd89e21 Mon Sep 17 00:00:00 2001 From: Michael Matloob Date: Wed, 8 Jun 2022 14:26:44 -0400 Subject: cmd/go: clean paths before using them form index functions We use str.TrimFilePathPrefix to trim the module root prefix and get the relative path of each package in the module when scanning the module and in the RelPath function. Make sure to clean the path before indexing and in RelPath to ensure that each path starts with that prefix, because walk will clean the root path before joining each subdirectory path to it. Change-Id: I1dc1eddbd42030eb6d5d8e76a8675f94216447c3 Reviewed-on: https://go-review.googlesource.com/c/go/+/411118 Run-TryBot: Michael Matloob TryBot-Result: Gopher Robot Reviewed-by: Russ Cox Reviewed-by: Michael Matloob --- src/cmd/go/internal/modindex/read.go | 3 +- .../script/list_replace_absolute_windows.txt | 37 ++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 src/cmd/go/testdata/script/list_replace_absolute_windows.txt (limited to 'src/cmd') diff --git a/src/cmd/go/internal/modindex/read.go b/src/cmd/go/internal/modindex/read.go index daa85762be..6ec3a6b3af 100644 --- a/src/cmd/go/internal/modindex/read.go +++ b/src/cmd/go/internal/modindex/read.go @@ -112,6 +112,7 @@ func Get(modroot string) (*ModuleIndex, error) { if modroot == "" { panic("modindex.Get called with empty modroot") } + modroot = filepath.Clean(modroot) isModCache := str.HasFilePathPrefix(modroot, cfg.GOMODCACHE) return openIndex(modroot, isModCache) } @@ -217,7 +218,7 @@ func (mi *ModuleIndex) Packages() []string { // RelPath returns the path relative to the module's root. func (mi *ModuleIndex) RelPath(path string) string { - return str.TrimFilePathPrefix(path, mi.modroot) + return str.TrimFilePathPrefix(filepath.Clean(path), mi.modroot) // mi.modroot is already clean } // ImportPackage is the equivalent of build.Import given the information in ModuleIndex. diff --git a/src/cmd/go/testdata/script/list_replace_absolute_windows.txt b/src/cmd/go/testdata/script/list_replace_absolute_windows.txt new file mode 100644 index 0000000000..6f5d737ade --- /dev/null +++ b/src/cmd/go/testdata/script/list_replace_absolute_windows.txt @@ -0,0 +1,37 @@ +# Test a replacement with an absolute path (so the path isn't +# cleaned by having filepath.Abs called on it). This checks +# whether the modindex logic cleans the modroot path before using +# it. + +[!windows] [short] skip + +go run print_go_mod.go # use this program to write a go.mod with an absolute path +cp stdout go.mod + +go list -modfile=go.mod all +-- print_go_mod.go -- +//go:build ignore +package main + +import ( + "fmt" + "os" +) + +func main() { + work := os.Getenv("WORK") +fmt.Printf(`module example.com/mod + +require b.com v0.0.0 + +replace b.com => %s\gopath\src/modb +`, work) +} +-- a.go -- +package a + +import _ "b.com/b" +-- modb/go.mod -- +module b.com +-- modb/b/b.go -- +package b -- cgit v1.3-5-g9baa From b51d44c6dd9d6f3ac3e1d275bc118aae23a5a482 Mon Sep 17 00:00:00 2001 From: Michael Matloob Date: Wed, 8 Jun 2022 19:14:11 -0400 Subject: cmd/go/testdata/script: fix skip on list_replace_absolute_windows The test should skip if it's not on windows *or* it's a short test, but instead is now skipping if it's not on windows *and* it's a short test, causing it to be run on non-windows longtest builders. Change-Id: Ica011bab632b713b0564fefabd5b42878d401844 Reviewed-on: https://go-review.googlesource.com/c/go/+/411122 Reviewed-by: Heschi Kreinick TryBot-Result: Gopher Robot Reviewed-by: Cherry Mui Run-TryBot: Michael Matloob Auto-Submit: Michael Matloob --- src/cmd/go/testdata/script/list_replace_absolute_windows.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/cmd') diff --git a/src/cmd/go/testdata/script/list_replace_absolute_windows.txt b/src/cmd/go/testdata/script/list_replace_absolute_windows.txt index 6f5d737ade..b3ff2a7c2d 100644 --- a/src/cmd/go/testdata/script/list_replace_absolute_windows.txt +++ b/src/cmd/go/testdata/script/list_replace_absolute_windows.txt @@ -3,7 +3,8 @@ # whether the modindex logic cleans the modroot path before using # it. -[!windows] [short] skip +[!windows] skip +[short] skip go run print_go_mod.go # use this program to write a go.mod with an absolute path cp stdout go.mod -- cgit v1.3-5-g9baa From 1a2ca95ad2d0e6599ab8b9772c30afbb743abc89 Mon Sep 17 00:00:00 2001 From: Robert Findley Date: Sat, 4 Jun 2022 17:01:47 -0400 Subject: go/types, types2: only set instance context if packages match In CL 404885, we avoid infinite expansion of type instances by sharing a context between the expanding type and new instances created during expansion. This ensures that we do not create an infinite number of identical but distinct instances in the presence of reference cycles. This pins additional memory to the new instance, but no more (approximately) than would be pinned by the original expanding instance. However, we can do better: since type cycles are only possible within a single package, we only need to share the local context if the two types are in the same package. This reduces the scope of the shared local context, and in particular can avoid pinning the package of the expanding type to the package of the newly created instance. Updates #52728 Change-Id: Iad2c85f4ecf60125f1da0ba22a7fdec7423e0338 Reviewed-on: https://go-review.googlesource.com/c/go/+/410416 Run-TryBot: Robert Findley Reviewed-by: Robert Griesemer TryBot-Result: Gopher Robot --- src/cmd/compile/internal/types2/instantiate.go | 39 +++++++++++++------------- src/cmd/compile/internal/types2/named.go | 35 ++++++++++++++++------- src/cmd/compile/internal/types2/subst.go | 28 +++++++++--------- src/go/types/instantiate.go | 39 +++++++++++++------------- src/go/types/named.go | 35 ++++++++++++++++------- src/go/types/subst.go | 28 +++++++++--------- 6 files changed, 120 insertions(+), 84 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/types2/instantiate.go b/src/cmd/compile/internal/types2/instantiate.go index 45f7e43ccf..5833f8db7e 100644 --- a/src/cmd/compile/internal/types2/instantiate.go +++ b/src/cmd/compile/internal/types2/instantiate.go @@ -63,28 +63,29 @@ func Instantiate(ctxt *Context, orig Type, targs []Type, validate bool) (Type, e return inst, nil } -// instance resolves a type or function instance for the given original type -// and type arguments. It looks for an existing identical instance in the given -// contexts, creating a new instance if none is found. +// instance instantiates the given original (generic) function or type with the +// provided type arguments and returns the resulting instance. If an identical +// instance exists already in the given contexts, it returns that instance, +// otherwise it creates a new one. // -// If local is non-nil, it is the context associated with a Named instance -// type currently being expanded. If global is non-nil, it is the context -// associated with the current type-checking pass or call to Instantiate. At -// least one of local or global must be non-nil. +// If expanding is non-nil, it is the Named instance type currently being +// expanded. If ctxt is non-nil, it is the context associated with the current +// type-checking pass or call to Instantiate. At least one of expanding or ctxt +// must be non-nil. // // For Named types the resulting instance may be unexpanded. -func (check *Checker) instance(pos syntax.Pos, orig Type, targs []Type, local, global *Context) (res Type) { - // The order of the contexts below matters: we always prefer instances in - // local in order to preserve reference cycles. +func (check *Checker) instance(pos syntax.Pos, orig Type, targs []Type, expanding *Named, ctxt *Context) (res Type) { + // The order of the contexts below matters: we always prefer instances in the + // expanding instance context in order to preserve reference cycles. // - // Invariant: if local != nil, the returned instance will be the instance - // recorded in local. + // Invariant: if expanding != nil, the returned instance will be the instance + // recorded in expanding.inst.ctxt. var ctxts []*Context - if local != nil { - ctxts = append(ctxts, local) + if expanding != nil { + ctxts = append(ctxts, expanding.inst.ctxt) } - if global != nil { - ctxts = append(ctxts, global) + if ctxt != nil { + ctxts = append(ctxts, ctxt) } assert(len(ctxts) > 0) @@ -114,10 +115,10 @@ func (check *Checker) instance(pos syntax.Pos, orig Type, targs []Type, local, g switch orig := orig.(type) { case *Named: - res = check.newNamedInstance(pos, orig, targs, local) // substituted lazily + res = check.newNamedInstance(pos, orig, targs, expanding) // substituted lazily case *Signature: - assert(local == nil) // function instances cannot be reached from Named types + assert(expanding == nil) // function instances cannot be reached from Named types tparams := orig.TypeParams() if !check.validateTArgLen(pos, tparams.Len(), len(targs)) { @@ -126,7 +127,7 @@ func (check *Checker) instance(pos syntax.Pos, orig Type, targs []Type, local, g if tparams.Len() == 0 { return orig // nothing to do (minor optimization) } - sig := check.subst(pos, orig, makeSubstMap(tparams.list(), targs), nil, global).(*Signature) + sig := check.subst(pos, orig, makeSubstMap(tparams.list(), targs), nil, ctxt).(*Signature) // If the signature doesn't use its type parameters, subst // will not make a copy. In that case, make a copy now (so // we can set tparams to nil w/o causing side-effects). diff --git a/src/cmd/compile/internal/types2/named.go b/src/cmd/compile/internal/types2/named.go index 720e500cd5..2cf6d3871f 100644 --- a/src/cmd/compile/internal/types2/named.go +++ b/src/cmd/compile/internal/types2/named.go @@ -230,21 +230,36 @@ func (check *Checker) newNamed(obj *TypeName, underlying Type, methods []*Func) if obj.typ == nil { obj.typ = typ } - // Ensure that typ is always expanded and sanity-checked. + // Ensure that typ is always sanity-checked. if check != nil { check.needsCleanup(typ) } return typ } -func (check *Checker) newNamedInstance(pos syntax.Pos, orig *Named, targs []Type, local *Context) *Named { +// newNamedInstance creates a new named instance for the given origin and type +// arguments, recording pos as the position of its synthetic object (for error +// reporting). +// +// If set, expanding is the named type instance currently being expanded, that +// led to the creation of this instance. +func (check *Checker) newNamedInstance(pos syntax.Pos, orig *Named, targs []Type, expanding *Named) *Named { assert(len(targs) > 0) obj := NewTypeName(pos, orig.obj.pkg, orig.obj.name, nil) - inst := &instance{orig: orig, targs: newTypeList(targs), ctxt: local} + inst := &instance{orig: orig, targs: newTypeList(targs)} + + // Only pass the expanding context to the new instance if their packages + // match. Since type reference cycles are only possible within a single + // package, this is sufficient for the purposes of short-circuiting cycles. + // Avoiding passing the context in other cases prevents unnecessary coupling + // of types across packages. + if expanding != nil && expanding.Obj().pkg == obj.pkg { + inst.ctxt = expanding.inst.ctxt + } typ := &Named{check: check, obj: obj, inst: inst} obj.typ = typ - // Ensure that typ is always expanded and sanity-checked. + // Ensure that typ is always sanity-checked. if check != nil { check.needsCleanup(typ) } @@ -387,11 +402,11 @@ func (t *Named) expandMethod(i int) *Func { // code. if origSig.RecvTypeParams().Len() == t.inst.targs.Len() { smap := makeSubstMap(origSig.RecvTypeParams().list(), t.inst.targs.list()) - var global *Context + var ctxt *Context if check != nil { - global = check.context() + ctxt = check.context() } - sig = check.subst(origm.pos, origSig, smap, t.inst.ctxt, global).(*Signature) + sig = check.subst(origm.pos, origSig, smap, t, ctxt).(*Signature) } if sig == origSig { @@ -601,11 +616,11 @@ func (n *Named) expandUnderlying() Type { assert(n == n2) smap := makeSubstMap(orig.tparams.list(), targs.list()) - var global *Context + var ctxt *Context if check != nil { - global = check.context() + ctxt = check.context() } - underlying := n.check.subst(n.obj.pos, orig.underlying, smap, n.inst.ctxt, global) + underlying := n.check.subst(n.obj.pos, orig.underlying, smap, n, ctxt) // If the underlying type of n is an interface, we need to set the receiver of // its methods accurately -- we set the receiver of interface methods on // the RHS of a type declaration to the defined type. diff --git a/src/cmd/compile/internal/types2/subst.go b/src/cmd/compile/internal/types2/subst.go index 4a4c8f960a..d5a48c6995 100644 --- a/src/cmd/compile/internal/types2/subst.go +++ b/src/cmd/compile/internal/types2/subst.go @@ -48,9 +48,10 @@ func (m substMap) lookup(tpar *TypeParam) Type { // incoming type. If a substitution took place, the result type is different // from the incoming type. // -// If the given context is non-nil, it is used in lieu of check.Config.Context. -func (check *Checker) subst(pos syntax.Pos, typ Type, smap substMap, local, global *Context) Type { - assert(local != nil || global != nil) +// If expanding is non-nil, it is the instance type currently being expanded. +// One of expanding or ctxt must be non-nil. +func (check *Checker) subst(pos syntax.Pos, typ Type, smap substMap, expanding *Named, ctxt *Context) Type { + assert(expanding != nil || ctxt != nil) if smap.empty() { return typ @@ -66,20 +67,21 @@ func (check *Checker) subst(pos syntax.Pos, typ Type, smap substMap, local, glob // general case subst := subster{ - pos: pos, - smap: smap, - check: check, - local: local, - global: global, + pos: pos, + smap: smap, + check: check, + expanding: expanding, + ctxt: ctxt, } return subst.typ(typ) } type subster struct { - pos syntax.Pos - smap substMap - check *Checker // nil if called via Instantiate - local, global *Context + pos syntax.Pos + smap substMap + check *Checker // nil if called via Instantiate + expanding *Named // if non-nil, the instance that is being expanded + ctxt *Context } func (subst *subster) typ(typ Type) Type { @@ -254,7 +256,7 @@ func (subst *subster) typ(typ Type) Type { // recursion. The position used here is irrelevant because validation only // occurs on t (we don't call validType on named), but we use subst.pos to // help with debugging. - return subst.check.instance(subst.pos, orig, newTArgs, subst.local, subst.global) + return subst.check.instance(subst.pos, orig, newTArgs, subst.expanding, subst.ctxt) case *TypeParam: return subst.smap.lookup(t) diff --git a/src/go/types/instantiate.go b/src/go/types/instantiate.go index e6b731f241..f7505854d1 100644 --- a/src/go/types/instantiate.go +++ b/src/go/types/instantiate.go @@ -63,28 +63,29 @@ func Instantiate(ctxt *Context, orig Type, targs []Type, validate bool) (Type, e return inst, nil } -// instance resolves a type or function instance for the given original type -// and type arguments. It looks for an existing identical instance in the given -// contexts, creating a new instance if none is found. +// instance instantiates the given original (generic) function or type with the +// provided type arguments and returns the resulting instance. If an identical +// instance exists already in the given contexts, it returns that instance, +// otherwise it creates a new one. // -// If local is non-nil, it is the context associated with a Named instance -// type currently being expanded. If global is non-nil, it is the context -// associated with the current type-checking pass or call to Instantiate. At -// least one of local or global must be non-nil. +// If expanding is non-nil, it is the Named instance type currently being +// expanded. If ctxt is non-nil, it is the context associated with the current +// type-checking pass or call to Instantiate. At least one of expanding or ctxt +// must be non-nil. // // For Named types the resulting instance may be unexpanded. -func (check *Checker) instance(pos token.Pos, orig Type, targs []Type, local, global *Context) (res Type) { - // The order of the contexts below matters: we always prefer instances in - // local in order to preserve reference cycles. +func (check *Checker) instance(pos token.Pos, orig Type, targs []Type, expanding *Named, ctxt *Context) (res Type) { + // The order of the contexts below matters: we always prefer instances in the + // expanding instance context in order to preserve reference cycles. // - // Invariant: if local != nil, the returned instance will be the instance - // recorded in local. + // Invariant: if expanding != nil, the returned instance will be the instance + // recorded in expanding.inst.ctxt. var ctxts []*Context - if local != nil { - ctxts = append(ctxts, local) + if expanding != nil { + ctxts = append(ctxts, expanding.inst.ctxt) } - if global != nil { - ctxts = append(ctxts, global) + if ctxt != nil { + ctxts = append(ctxts, ctxt) } assert(len(ctxts) > 0) @@ -114,10 +115,10 @@ func (check *Checker) instance(pos token.Pos, orig Type, targs []Type, local, gl switch orig := orig.(type) { case *Named: - res = check.newNamedInstance(pos, orig, targs, local) // substituted lazily + res = check.newNamedInstance(pos, orig, targs, expanding) // substituted lazily case *Signature: - assert(local == nil) // function instances cannot be reached from Named types + assert(expanding == nil) // function instances cannot be reached from Named types tparams := orig.TypeParams() if !check.validateTArgLen(pos, tparams.Len(), len(targs)) { @@ -126,7 +127,7 @@ func (check *Checker) instance(pos token.Pos, orig Type, targs []Type, local, gl if tparams.Len() == 0 { return orig // nothing to do (minor optimization) } - sig := check.subst(pos, orig, makeSubstMap(tparams.list(), targs), nil, global).(*Signature) + sig := check.subst(pos, orig, makeSubstMap(tparams.list(), targs), nil, ctxt).(*Signature) // If the signature doesn't use its type parameters, subst // will not make a copy. In that case, make a copy now (so // we can set tparams to nil w/o causing side-effects). diff --git a/src/go/types/named.go b/src/go/types/named.go index 63f0a22323..c08997aa77 100644 --- a/src/go/types/named.go +++ b/src/go/types/named.go @@ -230,21 +230,36 @@ func (check *Checker) newNamed(obj *TypeName, underlying Type, methods []*Func) if obj.typ == nil { obj.typ = typ } - // Ensure that typ is always expanded and sanity-checked. + // Ensure that typ is always sanity-checked. if check != nil { check.needsCleanup(typ) } return typ } -func (check *Checker) newNamedInstance(pos token.Pos, orig *Named, targs []Type, local *Context) *Named { +// newNamedInstance creates a new named instance for the given origin and type +// arguments, recording pos as the position of its synthetic object (for error +// reporting). +// +// If set, expanding is the named type instance currently being expanded, that +// led to the creation of this instance. +func (check *Checker) newNamedInstance(pos token.Pos, orig *Named, targs []Type, expanding *Named) *Named { assert(len(targs) > 0) obj := NewTypeName(pos, orig.obj.pkg, orig.obj.name, nil) - inst := &instance{orig: orig, targs: newTypeList(targs), ctxt: local} + inst := &instance{orig: orig, targs: newTypeList(targs)} + + // Only pass the expanding context to the new instance if their packages + // match. Since type reference cycles are only possible within a single + // package, this is sufficient for the purposes of short-circuiting cycles. + // Avoiding passing the context in other cases prevents unnecessary coupling + // of types across packages. + if expanding != nil && expanding.Obj().pkg == obj.pkg { + inst.ctxt = expanding.inst.ctxt + } typ := &Named{check: check, obj: obj, inst: inst} obj.typ = typ - // Ensure that typ is always expanded and sanity-checked. + // Ensure that typ is always sanity-checked. if check != nil { check.needsCleanup(typ) } @@ -387,11 +402,11 @@ func (t *Named) expandMethod(i int) *Func { // code. if origSig.RecvTypeParams().Len() == t.inst.targs.Len() { smap := makeSubstMap(origSig.RecvTypeParams().list(), t.inst.targs.list()) - var global *Context + var ctxt *Context if check != nil { - global = check.context() + ctxt = check.context() } - sig = check.subst(origm.pos, origSig, smap, t.inst.ctxt, global).(*Signature) + sig = check.subst(origm.pos, origSig, smap, t, ctxt).(*Signature) } if sig == origSig { @@ -601,11 +616,11 @@ func (n *Named) expandUnderlying() Type { assert(n == n2) smap := makeSubstMap(orig.tparams.list(), targs.list()) - var global *Context + var ctxt *Context if check != nil { - global = check.context() + ctxt = check.context() } - underlying := n.check.subst(n.obj.pos, orig.underlying, smap, n.inst.ctxt, global) + underlying := n.check.subst(n.obj.pos, orig.underlying, smap, n, ctxt) // If the underlying type of n is an interface, we need to set the receiver of // its methods accurately -- we set the receiver of interface methods on // the RHS of a type declaration to the defined type. diff --git a/src/go/types/subst.go b/src/go/types/subst.go index 36987a4c95..42f3619f88 100644 --- a/src/go/types/subst.go +++ b/src/go/types/subst.go @@ -48,9 +48,10 @@ func (m substMap) lookup(tpar *TypeParam) Type { // that it doesn't modify the incoming type. If a substitution took place, the // result type is different from the incoming type. // -// If the given context is non-nil, it is used in lieu of check.Config.Context -func (check *Checker) subst(pos token.Pos, typ Type, smap substMap, local, global *Context) Type { - assert(local != nil || global != nil) +// If expanding is non-nil, it is the instance type currently being expanded. +// One of expanding or ctxt must be non-nil. +func (check *Checker) subst(pos token.Pos, typ Type, smap substMap, expanding *Named, ctxt *Context) Type { + assert(expanding != nil || ctxt != nil) if smap.empty() { return typ @@ -66,20 +67,21 @@ func (check *Checker) subst(pos token.Pos, typ Type, smap substMap, local, globa // general case subst := subster{ - pos: pos, - smap: smap, - check: check, - local: local, - global: global, + pos: pos, + smap: smap, + check: check, + expanding: expanding, + ctxt: ctxt, } return subst.typ(typ) } type subster struct { - pos token.Pos - smap substMap - check *Checker // nil if called via Instantiate - local, global *Context + pos token.Pos + smap substMap + check *Checker // nil if called via Instantiate + expanding *Named // if non-nil, the instance that is being expanded + ctxt *Context } func (subst *subster) typ(typ Type) Type { @@ -254,7 +256,7 @@ func (subst *subster) typ(typ Type) Type { // recursion. The position used here is irrelevant because validation only // occurs on t (we don't call validType on named), but we use subst.pos to // help with debugging. - return subst.check.instance(subst.pos, orig, newTArgs, subst.local, subst.global) + return subst.check.instance(subst.pos, orig, newTArgs, subst.expanding, subst.ctxt) case *TypeParam: return subst.smap.lookup(t) -- cgit v1.3-5-g9baa From 2cfbef438049fd4c3f73d1562773ad1f93900897 Mon Sep 17 00:00:00 2001 From: Dmitri Goutnik Date: Fri, 20 May 2022 08:07:03 -0500 Subject: cmd/cgo: recognize clang 14 DWARF type names Fixes #53013 Change-Id: I169d4eb2420a6da52cc9abe17da98c3092a91be6 Reviewed-on: https://go-review.googlesource.com/c/go/+/407514 Auto-Submit: Ian Lance Taylor Reviewed-by: Cherry Mui Reviewed-by: Ian Lance Taylor Run-TryBot: Ian Lance Taylor TryBot-Result: Gopher Robot --- src/cmd/cgo/gcc.go | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/cmd') diff --git a/src/cmd/cgo/gcc.go b/src/cmd/cgo/gcc.go index 4d1a5bd8de..8ce5d4de73 100644 --- a/src/cmd/cgo/gcc.go +++ b/src/cmd/cgo/gcc.go @@ -2242,6 +2242,8 @@ var dwarfToName = map[string]string{ "long long unsigned int": "ulonglong", "signed char": "schar", "unsigned char": "uchar", + "unsigned long": "ulong", // Used by Clang 14; issue 53013. + "unsigned long long": "ulonglong", // Used by Clang 14; issue 53013. } const signedDelta = 64 -- cgit v1.3-5-g9baa From fb75c2da91b9cccf05ec6baad2636325c5d96751 Mon Sep 17 00:00:00 2001 From: Cherry Mui Date: Thu, 9 Jun 2022 17:30:05 -0400 Subject: cmd/dist, cmd/internal/metadata: don't install metadata binary It is only needed by cmd/dist, no need to build and install the binary. Change-Id: I6aba6b81496406077a0efba255c35020cff9d351 Reviewed-on: https://go-review.googlesource.com/c/go/+/411534 TryBot-Result: Gopher Robot Reviewed-by: Michael Pratt Run-TryBot: Cherry Mui --- src/cmd/dist/test.go | 2 +- src/cmd/internal/metadata/main.go | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) (limited to 'src/cmd') diff --git a/src/cmd/dist/test.go b/src/cmd/dist/test.go index 677be336ac..26d7fe0f73 100644 --- a/src/cmd/dist/test.go +++ b/src/cmd/dist/test.go @@ -290,7 +290,7 @@ func (t *tester) maybeLogMetadata() error { // // TODO(prattmic): If we split dist bootstrap and dist test then this // could be simplified to directly use internal/sysinfo here. - return t.dirCmd(filepath.Join(goroot, "src/cmd/internal/metadata"), "go", []string{"run", "."}).Run() + return t.dirCmd(filepath.Join(goroot, "src/cmd/internal/metadata"), "go", []string{"run", "main.go"}).Run() } // short returns a -short flag value to use with 'go test' diff --git a/src/cmd/internal/metadata/main.go b/src/cmd/internal/metadata/main.go index 157226e890..7478eec1c9 100644 --- a/src/cmd/internal/metadata/main.go +++ b/src/cmd/internal/metadata/main.go @@ -5,6 +5,12 @@ // Metadata prints basic system metadata to include in test logs. This is // separate from cmd/dist so it does not need to build with the bootstrap // toolchain. + +// This program is only used by cmd/dist. Add an "ignore" build tag so it +// is not installed. cmd/dist does "go run main.go" directly. + +//go:build ignore + package main import ( -- cgit v1.3-5-g9baa From 7eeec1f6e4b9359381e9aeffdb87c59308ecbb7e Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Sat, 11 Jun 2022 01:33:11 +0700 Subject: cmd/compile: fix missing dict pass for type assertions For type assertions, if either src or dst type has shape, we must convert them to dynamic type assertions. Fixes #53309 Change-Id: Ia3362fa67c011febcbdb5b26f856d081b5c366de Reviewed-on: https://go-review.googlesource.com/c/go/+/411617 Run-TryBot: Cuong Manh Le TryBot-Result: Gopher Robot Reviewed-by: Matthew Dempsky Reviewed-by: Cherry Mui Reviewed-by: Keith Randall --- src/cmd/compile/internal/noder/stencil.go | 4 +-- test/fixedbugs/issue53309.go | 42 +++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 test/fixedbugs/issue53309.go (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/noder/stencil.go b/src/cmd/compile/internal/noder/stencil.go index 88e4961666..3f12aa3cbd 100644 --- a/src/cmd/compile/internal/noder/stencil.go +++ b/src/cmd/compile/internal/noder/stencil.go @@ -1330,10 +1330,10 @@ func (g *genInst) dictPass(info *instInfo) { m = convertUsingDictionary(info, info.dictParam, m.Pos(), mce.X, m, m.Type()) } case ir.ODOTTYPE, ir.ODOTTYPE2: - if !m.Type().HasShape() { + dt := m.(*ir.TypeAssertExpr) + if !dt.Type().HasShape() && !dt.X.Type().HasShape() { break } - dt := m.(*ir.TypeAssertExpr) var rtype, itab ir.Node if dt.Type().IsInterface() || dt.X.Type().IsEmptyInterface() { // TODO(mdempsky): Investigate executing this block unconditionally. diff --git a/test/fixedbugs/issue53309.go b/test/fixedbugs/issue53309.go new file mode 100644 index 0000000000..2b752fe161 --- /dev/null +++ b/test/fixedbugs/issue53309.go @@ -0,0 +1,42 @@ +// run + +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +type TaskInput interface { + deps() []*taskDefinition +} + +type Value[T any] interface { + metaValue +} + +type metaValue interface { + TaskInput +} + +type taskDefinition struct { +} + +type taskResult struct { + task *taskDefinition +} + +func (tr *taskResult) deps() []*taskDefinition { + return nil +} + +func use[T any](v Value[T]) { + _, ok := v.(*taskResult) + if !ok { + panic("output must be *taskResult") + } +} + +func main() { + tr := &taskResult{&taskDefinition{}} + use(Value[string](tr)) +} -- cgit v1.3-5-g9baa From fbc75dff2fa5bac474936e611ff1b7e778617be3 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Fri, 10 Jun 2022 16:49:51 -0700 Subject: cmd/cgo: remove -fsanitize=hwaddress hardware tags No test because this isn't support on any of the builders. Fixes #53285 Change-Id: If8d17bdcdac81a6ce404a35a289bf83f07f02855 Reviewed-on: https://go-review.googlesource.com/c/go/+/411698 Auto-Submit: Ian Lance Taylor Reviewed-by: Ian Lance Taylor Reviewed-by: Cherry Mui Run-TryBot: Ian Lance Taylor TryBot-Result: Gopher Robot --- src/cmd/cgo/gcc.go | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/cgo/gcc.go b/src/cmd/cgo/gcc.go index 8ce5d4de73..4dff5e2b1c 100644 --- a/src/cmd/cgo/gcc.go +++ b/src/cmd/cgo/gcc.go @@ -1831,6 +1831,23 @@ func (p *Package) gccDebug(stdin []byte, nnames int) (d *dwarf.Data, ints []int6 bo := f.ByteOrder symtab, err := f.Symbols() if err == nil { + // Check for use of -fsanitize=hwaddress (issue 53285). + removeTag := func(v uint64) uint64 { return v } + if goarch == "arm64" { + for i := range symtab { + if symtab[i].Name == "__hwasan_init" { + // -fsanitize=hwaddress on ARM + // uses the upper byte of a + // memory address as a hardware + // tag. Remove it so that + // we can find the associated + // data. + removeTag = func(v uint64) uint64 { return v &^ (0xff << (64 - 8)) } + break + } + } + } + for i := range symtab { s := &symtab[i] switch { @@ -1838,9 +1855,10 @@ func (p *Package) gccDebug(stdin []byte, nnames int) (d *dwarf.Data, ints []int6 // Found it. Now find data section. if i := int(s.Section); 0 <= i && i < len(f.Sections) { sect := f.Sections[i] - if sect.Addr <= s.Value && s.Value < sect.Addr+sect.Size { + val := removeTag(s.Value) + if sect.Addr <= val && val < sect.Addr+sect.Size { if sdat, err := sect.Data(); err == nil { - data := sdat[s.Value-sect.Addr:] + data := sdat[val-sect.Addr:] ints = make([]int64, len(data)/8) for i := range ints { ints[i] = int64(bo.Uint64(data[i*8:])) @@ -1852,9 +1870,10 @@ func (p *Package) gccDebug(stdin []byte, nnames int) (d *dwarf.Data, ints []int6 // Found it. Now find data section. if i := int(s.Section); 0 <= i && i < len(f.Sections) { sect := f.Sections[i] - if sect.Addr <= s.Value && s.Value < sect.Addr+sect.Size { + val := removeTag(s.Value) + if sect.Addr <= val && val < sect.Addr+sect.Size { if sdat, err := sect.Data(); err == nil { - data := sdat[s.Value-sect.Addr:] + data := sdat[val-sect.Addr:] floats = make([]float64, len(data)/8) for i := range floats { floats[i] = math.Float64frombits(bo.Uint64(data[i*8:])) @@ -1867,9 +1886,10 @@ func (p *Package) gccDebug(stdin []byte, nnames int) (d *dwarf.Data, ints []int6 // Found it. Now find data section. if i := int(s.Section); 0 <= i && i < len(f.Sections) { sect := f.Sections[i] - if sect.Addr <= s.Value && s.Value < sect.Addr+sect.Size { + val := removeTag(s.Value) + if sect.Addr <= val && val < sect.Addr+sect.Size { if sdat, err := sect.Data(); err == nil { - data := sdat[s.Value-sect.Addr:] + data := sdat[val-sect.Addr:] strdata[n] = string(data) } } @@ -1880,9 +1900,10 @@ func (p *Package) gccDebug(stdin []byte, nnames int) (d *dwarf.Data, ints []int6 // Found it. Now find data section. if i := int(s.Section); 0 <= i && i < len(f.Sections) { sect := f.Sections[i] - if sect.Addr <= s.Value && s.Value < sect.Addr+sect.Size { + val := removeTag(s.Value) + if sect.Addr <= val && val < sect.Addr+sect.Size { if sdat, err := sect.Data(); err == nil { - data := sdat[s.Value-sect.Addr:] + data := sdat[val-sect.Addr:] strlen := bo.Uint64(data[:8]) if strlen > (1<<(uint(p.IntSize*8)-1) - 1) { // greater than MaxInt? fatalf("string literal too big") -- cgit v1.3-5-g9baa From cb9bf93078c54187f5be9d8a65c81c249d12d3c5 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Fri, 10 Jun 2022 15:46:43 -0700 Subject: cmd/go: quote package directory when calling glob Fixes #53314 Change-Id: I4933b59ee247daec5cf96eb15c52ff54d3ec26a9 Reviewed-on: https://go-review.googlesource.com/c/go/+/411696 Run-TryBot: Ian Lance Taylor Reviewed-by: Bryan Mills Reviewed-by: Ian Lance Taylor TryBot-Result: Gopher Robot Auto-Submit: Ian Lance Taylor Run-TryBot: Ian Lance Taylor --- src/cmd/go/internal/clean/clean.go | 3 ++- src/cmd/go/internal/load/pkg.go | 2 +- src/cmd/go/internal/modfetch/fetch.go | 5 +++-- src/cmd/go/internal/str/path.go | 18 ++++++++++++++++++ src/cmd/go/testdata/script/embed_brackets.txt | 18 ++++++++++++++++++ 5 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 src/cmd/go/testdata/script/embed_brackets.txt (limited to 'src/cmd') diff --git a/src/cmd/go/internal/clean/clean.go b/src/cmd/go/internal/clean/clean.go index 8564411fb6..019d36490f 100644 --- a/src/cmd/go/internal/clean/clean.go +++ b/src/cmd/go/internal/clean/clean.go @@ -22,6 +22,7 @@ import ( "cmd/go/internal/lockedfile" "cmd/go/internal/modfetch" "cmd/go/internal/modload" + "cmd/go/internal/str" "cmd/go/internal/work" ) @@ -141,7 +142,7 @@ func runClean(ctx context.Context, cmd *base.Command, args []string) { // The top cache directory may have been created with special permissions // and not something that we want to remove. Also, we'd like to preserve // the access log for future analysis, even if the cache is cleared. - subdirs, _ := filepath.Glob(filepath.Join(dir, "[0-9a-f][0-9a-f]")) + subdirs, _ := filepath.Glob(filepath.Join(str.QuoteGlob(dir), "[0-9a-f][0-9a-f]")) printedErrors := false if len(subdirs) > 0 { if cfg.BuildN || cfg.BuildX { diff --git a/src/cmd/go/internal/load/pkg.go b/src/cmd/go/internal/load/pkg.go index 394a4a4383..fe4a82472d 100644 --- a/src/cmd/go/internal/load/pkg.go +++ b/src/cmd/go/internal/load/pkg.go @@ -2063,7 +2063,7 @@ func resolveEmbed(pkgdir string, patterns []string) (files []string, pmap map[st } // Glob to find matches. - match, err := fsys.Glob(pkgdir + string(filepath.Separator) + filepath.FromSlash(glob)) + match, err := fsys.Glob(str.QuoteGlob(pkgdir) + string(filepath.Separator) + filepath.FromSlash(glob)) if err != nil { return nil, nil, err } diff --git a/src/cmd/go/internal/modfetch/fetch.go b/src/cmd/go/internal/modfetch/fetch.go index 21d5f54688..a7c8c2c769 100644 --- a/src/cmd/go/internal/modfetch/fetch.go +++ b/src/cmd/go/internal/modfetch/fetch.go @@ -26,6 +26,7 @@ import ( "cmd/go/internal/lockedfile" "cmd/go/internal/par" "cmd/go/internal/robustio" + "cmd/go/internal/str" "cmd/go/internal/trace" "golang.org/x/mod/module" @@ -102,7 +103,7 @@ func download(ctx context.Context, mod module.Version) (dir string, err error) { // active. parentDir := filepath.Dir(dir) tmpPrefix := filepath.Base(dir) + ".tmp-" - if old, err := filepath.Glob(filepath.Join(parentDir, tmpPrefix+"*")); err == nil { + if old, err := filepath.Glob(filepath.Join(str.QuoteGlob(parentDir), str.QuoteGlob(tmpPrefix)+"*")); err == nil { for _, path := range old { RemoveAll(path) // best effort } @@ -224,7 +225,7 @@ func downloadZip(ctx context.Context, mod module.Version, zipfile string) (err e // This is only safe to do because the lock file ensures that their // writers are no longer active. tmpPattern := filepath.Base(zipfile) + "*.tmp" - if old, err := filepath.Glob(filepath.Join(filepath.Dir(zipfile), tmpPattern)); err == nil { + if old, err := filepath.Glob(filepath.Join(str.QuoteGlob(filepath.Dir(zipfile)), tmpPattern)); err == nil { for _, path := range old { os.Remove(path) // best effort } diff --git a/src/cmd/go/internal/str/path.go b/src/cmd/go/internal/str/path.go index a69e171f8c..c165b91785 100644 --- a/src/cmd/go/internal/str/path.go +++ b/src/cmd/go/internal/str/path.go @@ -66,3 +66,21 @@ func TrimFilePathPrefix(s, prefix string) string { } return trimmed[1:] } + +// QuoteGlob returns s with all Glob metacharacters quoted. +// We don't try to handle backslash here, as that can appear in a +// file path on Windows. +func QuoteGlob(s string) string { + if !strings.ContainsAny(s, `*?[]`) { + return s + } + var sb strings.Builder + for _, c := range s { + switch c { + case '*', '?', '[', ']': + sb.WriteByte('\\') + } + sb.WriteRune(c) + } + return sb.String() +} diff --git a/src/cmd/go/testdata/script/embed_brackets.txt b/src/cmd/go/testdata/script/embed_brackets.txt new file mode 100644 index 0000000000..7093a8497e --- /dev/null +++ b/src/cmd/go/testdata/script/embed_brackets.txt @@ -0,0 +1,18 @@ +# issue 53314 +[windows] skip +cd [pkg] +go build + +-- [pkg]/go.mod -- +module m + +go 1.19 +-- [pkg]/x.go -- +package p + +import _ "embed" + +//go:embed t.txt +var S string + +-- [pkg]//t.txt -- -- cgit v1.3-5-g9baa From e1e66a03a6bb3210034b640923fa253d7def1a26 Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Tue, 14 Jun 2022 13:38:02 -0700 Subject: cmd/compile,runtime,reflect: move embedded bit from offset to name Previously we stole a bit from the field offset to encode whether a struct field was embedded. Instead, encode that bit in the name field, where we already have some unused bits to play with. The bit associates naturally with the name in any case. This leaves a full uintptr to specify field offsets. This will make the fix for #52740 cleaner. Change-Id: I0bfb85564dc26e8c18101bc8b432f332176d7836 Reviewed-on: https://go-review.googlesource.com/c/go/+/412138 Reviewed-by: Cherry Mui Run-TryBot: Keith Randall TryBot-Result: Gopher Robot Reviewed-by: Keith Randall --- src/cmd/compile/internal/reflectdata/reflect.go | 33 ++++++----- src/cmd/link/internal/ld/decodesym.go | 17 +++++- src/cmd/link/internal/ld/dwarf.go | 6 +- src/internal/reflectlite/export_test.go | 2 +- src/internal/reflectlite/type.go | 21 ++++--- src/reflect/abi.go | 2 +- src/reflect/export_test.go | 2 +- src/reflect/type.go | 76 +++++++++++++------------ src/reflect/value.go | 2 +- src/runtime/alg.go | 2 +- src/runtime/cgocall.go | 2 +- src/runtime/syscall_windows.go | 2 +- src/runtime/type.go | 19 ++++--- 13 files changed, 104 insertions(+), 82 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/reflectdata/reflect.go b/src/cmd/compile/internal/reflectdata/reflect.go index 3ffb7dcefa..21301ab149 100644 --- a/src/cmd/compile/internal/reflectdata/reflect.go +++ b/src/cmd/compile/internal/reflectdata/reflect.go @@ -412,7 +412,7 @@ func dimportpath(p *types.Pkg) { } s := base.Ctxt.Lookup("type..importpath." + p.Prefix + ".") - ot := dnameData(s, 0, p.Path, "", nil, false) + ot := dnameData(s, 0, p.Path, "", nil, false, false) objw.Global(s, int32(ot), obj.DUPOK|obj.RODATA) s.Set(obj.AttrContentAddressable, true) p.Pathsym = s @@ -461,12 +461,12 @@ func dnameField(lsym *obj.LSym, ot int, spkg *types.Pkg, ft *types.Field) int { if !types.IsExported(ft.Sym.Name) && ft.Sym.Pkg != spkg { base.Fatalf("package mismatch for %v", ft.Sym) } - nsym := dname(ft.Sym.Name, ft.Note, nil, types.IsExported(ft.Sym.Name)) + nsym := dname(ft.Sym.Name, ft.Note, nil, types.IsExported(ft.Sym.Name), ft.Embedded != 0) return objw.SymPtr(lsym, ot, nsym, 0) } // dnameData writes the contents of a reflect.name into s at offset ot. -func dnameData(s *obj.LSym, ot int, name, tag string, pkg *types.Pkg, exported bool) int { +func dnameData(s *obj.LSym, ot int, name, tag string, pkg *types.Pkg, exported, embedded bool) int { if len(name) >= 1<<29 { base.Fatalf("name too long: %d %s...", len(name), name[:1024]) } @@ -491,6 +491,9 @@ func dnameData(s *obj.LSym, ot int, name, tag string, pkg *types.Pkg, exported b if pkg != nil { bits |= 1 << 2 } + if embedded { + bits |= 1 << 3 + } b := make([]byte, l) b[0] = bits copy(b[1:], nameLen[:nameLenLen]) @@ -513,7 +516,7 @@ func dnameData(s *obj.LSym, ot int, name, tag string, pkg *types.Pkg, exported b var dnameCount int // dname creates a reflect.name for a struct field or method. -func dname(name, tag string, pkg *types.Pkg, exported bool) *obj.LSym { +func dname(name, tag string, pkg *types.Pkg, exported, embedded bool) *obj.LSym { // Write out data as "type.." to signal two things to the // linker, first that when dynamically linking, the symbol // should be moved to a relro section, and second that the @@ -538,11 +541,14 @@ func dname(name, tag string, pkg *types.Pkg, exported bool) *obj.LSym { sname = fmt.Sprintf(`%s"".%d`, sname, dnameCount) dnameCount++ } + if embedded { + sname += ".embedded" + } s := base.Ctxt.Lookup(sname) if len(s.P) > 0 { return s } - ot := dnameData(s, 0, name, tag, pkg, exported) + ot := dnameData(s, 0, name, tag, pkg, exported, embedded) objw.Global(s, int32(ot), obj.DUPOK|obj.RODATA) s.Set(obj.AttrContentAddressable, true) return s @@ -610,7 +616,7 @@ func dextratypeData(lsym *obj.LSym, ot int, t *types.Type) int { if !exported && a.name.Pkg != typePkg(t) { pkg = a.name.Pkg } - nsym := dname(a.name.Name, "", pkg, exported) + nsym := dname(a.name.Name, "", pkg, exported, false) ot = objw.SymPtrOff(lsym, ot, nsym) ot = dmethodptrOff(lsym, ot, writeType(a.mtype)) @@ -775,7 +781,7 @@ func dcommontype(lsym *obj.LSym, t *types.Type) int { } ot = objw.SymPtr(lsym, ot, gcsym, 0) // gcdata - nsym := dname(p, "", nil, exported) + nsym := dname(p, "", nil, exported, false) ot = objw.SymPtrOff(lsym, ot, nsym) // str // ptrToThis if sptr == nil { @@ -1074,7 +1080,7 @@ func writeType(t *types.Type) *obj.LSym { if !exported && a.name.Pkg != tpkg { pkg = a.name.Pkg } - nsym := dname(a.name.Name, "", pkg, exported) + nsym := dname(a.name.Name, "", pkg, exported, false) ot = objw.SymPtrOff(lsym, ot, nsym) ot = objw.SymPtrOff(lsym, ot, writeType(a.type_)) @@ -1180,14 +1186,7 @@ func writeType(t *types.Type) *obj.LSym { // ../../../../runtime/type.go:/structField ot = dnameField(lsym, ot, spkg, f) ot = objw.SymPtr(lsym, ot, writeType(f.Type), 0) - offsetAnon := uint64(f.Offset) << 1 - if offsetAnon>>1 != uint64(f.Offset) { - base.Fatalf("%v: bad field offset for %s", t, f.Sym.Name) - } - if f.Embedded != 0 { - offsetAnon |= 1 - } - ot = objw.Uintptr(lsym, ot, offsetAnon) + ot = objw.Uintptr(lsym, ot, uint64(f.Offset)) } } @@ -1356,7 +1355,7 @@ func WriteTabs() { // name nameOff // typ typeOff // pointer to symbol // } - nsym := dname(p.Sym().Name, "", nil, true) + nsym := dname(p.Sym().Name, "", nil, true, false) t := p.Type() if p.Class != ir.PFUNC { t = types.NewPtr(t) diff --git a/src/cmd/link/internal/ld/decodesym.go b/src/cmd/link/internal/ld/decodesym.go index a6ae202859..b0f4b87563 100644 --- a/src/cmd/link/internal/ld/decodesym.go +++ b/src/cmd/link/internal/ld/decodesym.go @@ -132,6 +132,15 @@ func decodetypeName(ldr *loader.Loader, symIdx loader.Sym, relocs *loader.Relocs return string(data[1+nameLenLen : 1+nameLenLen+int(nameLen)]) } +func decodetypeNameEmbedded(ldr *loader.Loader, symIdx loader.Sym, relocs *loader.Relocs, off int) bool { + r := decodeRelocSym(ldr, symIdx, relocs, int32(off)) + if r == 0 { + return false + } + data := ldr.Data(r) + return data[0]&(1<<3) != 0 +} + func decodetypeFuncInType(ldr *loader.Loader, arch *sys.Arch, symIdx loader.Sym, relocs *loader.Relocs, i int) loader.Sym { uadd := commonsize(arch) + 4 if arch.PtrSize == 8 { @@ -204,12 +213,18 @@ func decodetypeStructFieldType(ldr *loader.Loader, arch *sys.Arch, symIdx loader return decodeRelocSym(ldr, symIdx, &relocs, int32(off+arch.PtrSize)) } -func decodetypeStructFieldOffsAnon(ldr *loader.Loader, arch *sys.Arch, symIdx loader.Sym, i int) int64 { +func decodetypeStructFieldOffset(ldr *loader.Loader, arch *sys.Arch, symIdx loader.Sym, i int) int64 { off := decodetypeStructFieldArrayOff(ldr, arch, symIdx, i) data := ldr.Data(symIdx) return int64(decodeInuxi(arch, data[off+2*arch.PtrSize:], arch.PtrSize)) } +func decodetypeStructFieldEmbedded(ldr *loader.Loader, arch *sys.Arch, symIdx loader.Sym, i int) bool { + off := decodetypeStructFieldArrayOff(ldr, arch, symIdx, i) + relocs := ldr.Relocs(symIdx) + return decodetypeNameEmbedded(ldr, symIdx, &relocs, off) +} + // decodetypeStr returns the contents of an rtype's str field (a nameOff). func decodetypeStr(ldr *loader.Loader, arch *sys.Arch, symIdx loader.Sym) string { relocs := ldr.Relocs(symIdx) diff --git a/src/cmd/link/internal/ld/dwarf.go b/src/cmd/link/internal/ld/dwarf.go index 6ed9697aec..c42511ea3f 100644 --- a/src/cmd/link/internal/ld/dwarf.go +++ b/src/cmd/link/internal/ld/dwarf.go @@ -682,9 +682,9 @@ func (d *dwctxt) newtype(gotype loader.Sym) *dwarf.DWDie { } fld := d.newdie(die, dwarf.DW_ABRV_STRUCTFIELD, f) d.newrefattr(fld, dwarf.DW_AT_type, d.defgotype(s)) - offsetAnon := decodetypeStructFieldOffsAnon(d.ldr, d.arch, gotype, i) - newmemberoffsetattr(fld, int32(offsetAnon>>1)) - if offsetAnon&1 != 0 { // is embedded field + offset := decodetypeStructFieldOffset(d.ldr, d.arch, gotype, i) + newmemberoffsetattr(fld, int32(offset)) + if decodetypeStructFieldEmbedded(d.ldr, d.arch, gotype, i) { newattr(fld, dwarf.DW_AT_go_embedded_field, dwarf.DW_CLS_FLAG, 1, 0) } } diff --git a/src/internal/reflectlite/export_test.go b/src/internal/reflectlite/export_test.go index 3e5c258fb1..e9a928bdc6 100644 --- a/src/internal/reflectlite/export_test.go +++ b/src/internal/reflectlite/export_test.go @@ -36,7 +36,7 @@ func Field(v Value, i int) Value { // In the former case, we want v.ptr + offset. // In the latter case, we must have field.offset = 0, // so v.ptr + field.offset is still the correct address. - ptr := add(v.ptr, field.offset(), "same as non-reflect &v.field") + ptr := add(v.ptr, field.offset, "same as non-reflect &v.field") return Value{typ, ptr, fl} } diff --git a/src/internal/reflectlite/type.go b/src/internal/reflectlite/type.go index bc6fc94773..21e3c1278d 100644 --- a/src/internal/reflectlite/type.go +++ b/src/internal/reflectlite/type.go @@ -269,17 +269,13 @@ type sliceType struct { // Struct field type structField struct { - name name // name is always non-empty - typ *rtype // type of field - offsetEmbed uintptr // byte offset of field<<1 | isEmbedded -} - -func (f *structField) offset() uintptr { - return f.offsetEmbed >> 1 + name name // name is always non-empty + typ *rtype // type of field + offset uintptr // byte offset of field } func (f *structField) embedded() bool { - return f.offsetEmbed&1 != 0 + return f.name.embedded() } // structType represents a struct type. @@ -328,6 +324,10 @@ func (n name) hasTag() bool { return (*n.bytes)&(1<<1) != 0 } +func (n name) embedded() bool { + return (*n.bytes)&(1<<3) != 0 +} + // readVarint parses a varint as encoded by encoding/binary. // It returns the number of encoded bytes and the encoded value. func (n name) readVarint(off int) (int, int) { @@ -947,7 +947,10 @@ func haveIdenticalUnderlyingType(T, V *rtype, cmpTags bool) bool { if cmpTags && tf.name.tag() != vf.name.tag() { return false } - if tf.offsetEmbed != vf.offsetEmbed { + if tf.offset != vf.offset { + return false + } + if tf.embedded() != vf.embedded() { return false } } diff --git a/src/reflect/abi.go b/src/reflect/abi.go index 9957d23768..32cb314188 100644 --- a/src/reflect/abi.go +++ b/src/reflect/abi.go @@ -237,7 +237,7 @@ func (a *abiSeq) regAssign(t *rtype, offset uintptr) bool { st := (*structType)(unsafe.Pointer(t)) for i := range st.fields { f := &st.fields[i] - if !a.regAssign(f.typ, offset+f.offset()) { + if !a.regAssign(f.typ, offset+f.offset) { return false } } diff --git a/src/reflect/export_test.go b/src/reflect/export_test.go index a5a3c1c271..f7d2cc362d 100644 --- a/src/reflect/export_test.go +++ b/src/reflect/export_test.go @@ -140,7 +140,7 @@ func IsExported(t Type) bool { } func ResolveReflectName(s string) { - resolveReflectName(newName(s, "", false)) + resolveReflectName(newName(s, "", false, false)) } type Buffer struct { diff --git a/src/reflect/type.go b/src/reflect/type.go index 97040b5188..7b8cf0ee62 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -433,17 +433,13 @@ type sliceType struct { // Struct field type structField struct { - name name // name is always non-empty - typ *rtype // type of field - offsetEmbed uintptr // byte offset of field<<1 | isEmbedded -} - -func (f *structField) offset() uintptr { - return f.offsetEmbed >> 1 + name name // name is always non-empty + typ *rtype // type of field + offset uintptr // byte offset of field } func (f *structField) embedded() bool { - return f.offsetEmbed&1 != 0 + return f.name.embedded() } // structType represents a struct type. @@ -460,6 +456,7 @@ type structType struct { // 1<<0 the name is exported // 1<<1 tag data follows the name // 1<<2 pkgPath nameOff follows the name and tag +// 1<<3 the name is of an embedded (a.k.a. anonymous) field // // Following that, there is a varint-encoded length of the name, // followed by the name itself. @@ -496,6 +493,10 @@ func (n name) hasTag() bool { return (*n.bytes)&(1<<1) != 0 } +func (n name) embedded() bool { + return (*n.bytes)&(1<<3) != 0 +} + // readVarint parses a varint as encoded by encoding/binary. // It returns the number of encoded bytes and the encoded value. func (n name) readVarint(off int) (int, int) { @@ -565,7 +566,7 @@ func (n name) pkgPath() string { return pkgPathName.name() } -func newName(n, tag string, exported bool) name { +func newName(n, tag string, exported, embedded bool) name { if len(n) >= 1<<29 { panic("reflect.nameFrom: name too long: " + n[:1024] + "...") } @@ -586,6 +587,9 @@ func newName(n, tag string, exported bool) name { l += tagLenLen + len(tag) bits |= 1 << 1 } + if embedded { + bits |= 1 << 3 + } b := make([]byte, l) b[0] = bits @@ -1256,7 +1260,7 @@ func (t *structType) Field(i int) (f StructField) { if tag := p.name.tag(); tag != "" { f.Tag = StructTag(tag) } - f.Offset = p.offset() + f.Offset = p.offset // NOTE(rsc): This is the only allocation in the interface // presented by a reflect.Type. It would be nice to avoid, @@ -1472,7 +1476,7 @@ func (t *rtype) ptrTo() *rtype { prototype := *(**ptrType)(unsafe.Pointer(&iptr)) pp := *prototype - pp.str = resolveReflectName(newName(s, "", false)) + pp.str = resolveReflectName(newName(s, "", false, false)) pp.ptrToThis = 0 // For the type structures linked into the binary, the @@ -1739,7 +1743,10 @@ func haveIdenticalUnderlyingType(T, V *rtype, cmpTags bool) bool { if cmpTags && tf.name.tag() != vf.name.tag() { return false } - if tf.offsetEmbed != vf.offsetEmbed { + if tf.offset != vf.offset { + return false + } + if tf.embedded() != vf.embedded() { return false } } @@ -1891,7 +1898,7 @@ func ChanOf(dir ChanDir, t Type) Type { ch := *prototype ch.tflag = tflagRegularMemory ch.dir = uintptr(dir) - ch.str = resolveReflectName(newName(s, "", false)) + ch.str = resolveReflectName(newName(s, "", false, false)) ch.hash = fnv1(typ.hash, 'c', byte(dir)) ch.elem = typ @@ -1934,7 +1941,7 @@ func MapOf(key, elem Type) Type { // in ../cmd/compile/internal/reflectdata/reflect.go:writeType. var imap any = (map[unsafe.Pointer]unsafe.Pointer)(nil) mt := **(**mapType)(unsafe.Pointer(&imap)) - mt.str = resolveReflectName(newName(s, "", false)) + mt.str = resolveReflectName(newName(s, "", false, false)) mt.tflag = 0 mt.hash = fnv1(etyp.hash, 'm', byte(ktyp.hash>>24), byte(ktyp.hash>>16), byte(ktyp.hash>>8), byte(ktyp.hash)) mt.key = ktyp @@ -2113,7 +2120,7 @@ func FuncOf(in, out []Type, variadic bool) Type { } // Populate the remaining fields of ft and store in cache. - ft.str = resolveReflectName(newName(str, "", false)) + ft.str = resolveReflectName(newName(str, "", false, false)) ft.ptrToThis = 0 return addToCache(&ft.rtype) } @@ -2290,7 +2297,7 @@ func bucketOf(ktyp, etyp *rtype) *rtype { gcdata: gcdata, } s := "bucket(" + ktyp.String() + "," + etyp.String() + ")" - b.str = resolveReflectName(newName(s, "", false)) + b.str = resolveReflectName(newName(s, "", false, false)) return b } @@ -2369,7 +2376,7 @@ func SliceOf(t Type) Type { prototype := *(**sliceType)(unsafe.Pointer(&islice)) slice := *prototype slice.tflag = 0 - slice.str = resolveReflectName(newName(s, "", false)) + slice.str = resolveReflectName(newName(s, "", false, false)) slice.hash = fnv1(typ.hash, '[') slice.elem = typ slice.ptrToThis = 0 @@ -2632,7 +2639,7 @@ func StructOf(fields []StructField) Type { typalign = ft.align } size = offset + ft.size - f.offsetEmbed |= offset << 1 + f.offset = offset if ft.size == 0 { lastzero = size @@ -2698,7 +2705,7 @@ func StructOf(fields []StructField) Type { *typ = *prototype typ.fields = fs if pkgpath != "" { - typ.pkgPath = newName(pkgpath, "", false) + typ.pkgPath = newName(pkgpath, "", false, false) } // Look in cache. @@ -2742,7 +2749,7 @@ func StructOf(fields []StructField) Type { } } - typ.str = resolveReflectName(newName(str, "", false)) + typ.str = resolveReflectName(newName(str, "", false, false)) typ.tflag = 0 // TODO: set tflagRegularMemory typ.hash = hash typ.size = size @@ -2774,14 +2781,14 @@ func StructOf(fields []StructField) Type { continue } // Pad to start of this field with zeros. - if ft.offset() > off { - n := (ft.offset() - off) / goarch.PtrSize + if ft.offset > off { + n := (ft.offset - off) / goarch.PtrSize prog = append(prog, 0x01, 0x00) // emit a 0 bit if n > 1 { prog = append(prog, 0x81) // repeat previous bit prog = appendVarint(prog, n-1) // n-1 times } - off = ft.offset() + off = ft.offset } prog = appendGCProg(prog, ft.typ) @@ -2803,8 +2810,8 @@ func StructOf(fields []StructField) Type { if comparable { typ.equal = func(p, q unsafe.Pointer) bool { for _, ft := range typ.fields { - pi := add(p, ft.offset(), "&x.field safe") - qi := add(q, ft.offset(), "&x.field safe") + pi := add(p, ft.offset, "&x.field safe") + qi := add(q, ft.offset, "&x.field safe") if !ft.typ.equal(pi, qi) { return false } @@ -2841,16 +2848,11 @@ func runtimeStructField(field StructField) (structField, string) { } } - offsetEmbed := uintptr(0) - if field.Anonymous { - offsetEmbed |= 1 - } - resolveReflectType(field.Type.common()) // install in runtime f := structField{ - name: newName(field.Name, string(field.Tag), field.IsExported()), - typ: field.Type.common(), - offsetEmbed: offsetEmbed, + name: newName(field.Name, string(field.Tag), field.IsExported(), field.Anonymous), + typ: field.Type.common(), + offset: 0, } return f, field.PkgPath } @@ -2874,7 +2876,7 @@ func typeptrdata(t *rtype) uintptr { return 0 } f := st.fields[field] - return f.offset() + f.typ.ptrdata + return f.offset + f.typ.ptrdata default: panic("reflect.typeptrdata: unexpected type, " + t.String()) @@ -2917,7 +2919,7 @@ func ArrayOf(length int, elem Type) Type { prototype := *(**arrayType)(unsafe.Pointer(&iarray)) array := *prototype array.tflag = typ.tflag & tflagRegularMemory - array.str = resolveReflectName(newName(s, "", false)) + array.str = resolveReflectName(newName(s, "", false, false)) array.hash = fnv1(typ.hash, '[') for n := uint32(length); n > 0; n >>= 8 { array.hash = fnv1(array.hash, byte(n)) @@ -3097,7 +3099,7 @@ func funcLayout(t *funcType, rcvr *rtype) (frametype *rtype, framePool *sync.Poo } else { s = "funcargs(" + t.String() + ")" } - x.str = resolveReflectName(newName(s, "", false)) + x.str = resolveReflectName(newName(s, "", false, false)) // cache result for future callers framePool = &sync.Pool{New: func() any { @@ -3165,7 +3167,7 @@ func addTypeBits(bv *bitVector, offset uintptr, t *rtype) { tt := (*structType)(unsafe.Pointer(t)) for i := range tt.fields { f := &tt.fields[i] - addTypeBits(bv, offset+f.offset(), f.typ) + addTypeBits(bv, offset+f.offset, f.typ) } } } diff --git a/src/reflect/value.go b/src/reflect/value.go index 5abdca2820..74554a3ac8 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -1287,7 +1287,7 @@ func (v Value) Field(i int) Value { // In the former case, we want v.ptr + offset. // In the latter case, we must have field.offset = 0, // so v.ptr + field.offset is still the correct address. - ptr := add(v.ptr, field.offset(), "same as non-reflect &v.field") + ptr := add(v.ptr, field.offset, "same as non-reflect &v.field") return Value{typ, ptr, fl} } diff --git a/src/runtime/alg.go b/src/runtime/alg.go index 5d7d1c77f4..2a413eeef3 100644 --- a/src/runtime/alg.go +++ b/src/runtime/alg.go @@ -182,7 +182,7 @@ func typehash(t *_type, p unsafe.Pointer, h uintptr) uintptr { if f.name.isBlank() { continue } - h = typehash(f.typ, add(p, f.offset()), h) + h = typehash(f.typ, add(p, f.offset), h) } return h default: diff --git a/src/runtime/cgocall.go b/src/runtime/cgocall.go index 977d049378..892654ed5b 100644 --- a/src/runtime/cgocall.go +++ b/src/runtime/cgocall.go @@ -536,7 +536,7 @@ func cgoCheckArg(t *_type, p unsafe.Pointer, indir, top bool, msg string) { if f.typ.ptrdata == 0 { continue } - cgoCheckArg(f.typ, add(p, f.offset()), true, top, msg) + cgoCheckArg(f.typ, add(p, f.offset), true, top, msg) } case kindPtr, kindUnsafePointer: if indir { diff --git a/src/runtime/syscall_windows.go b/src/runtime/syscall_windows.go index a841a31a27..e42d71ad65 100644 --- a/src/runtime/syscall_windows.go +++ b/src/runtime/syscall_windows.go @@ -174,7 +174,7 @@ func (p *abiDesc) tryRegAssignArg(t *_type, offset uintptr) bool { st := (*structtype)(unsafe.Pointer(t)) for i := range st.fields { f := &st.fields[i] - if !p.tryRegAssignArg(f.typ, offset+f.offset()) { + if !p.tryRegAssignArg(f.typ, offset+f.offset) { return false } } diff --git a/src/runtime/type.go b/src/runtime/type.go index b650d6d795..e8e7819ecf 100644 --- a/src/runtime/type.go +++ b/src/runtime/type.go @@ -414,13 +414,9 @@ type ptrtype struct { } type structfield struct { - name name - typ *_type - offsetAnon uintptr -} - -func (f *structfield) offset() uintptr { - return f.offsetAnon >> 1 + name name + typ *_type + offset uintptr } type structtype struct { @@ -443,6 +439,10 @@ func (n name) isExported() bool { return (*n.bytes)&(1<<0) != 0 } +func (n name) isEmbedded() bool { + return (*n.bytes)&(1<<3) != 0 +} + func (n name) readvarint(off int) (int, int) { v := 0 for i := 0; ; i++ { @@ -703,7 +703,10 @@ func typesEqual(t, v *_type, seen map[_typePair]struct{}) bool { if tf.name.tag() != vf.name.tag() { return false } - if tf.offsetAnon != vf.offsetAnon { + if tf.offset != vf.offset { + return false + } + if tf.name.isEmbedded() != vf.name.isEmbedded() { return false } } -- cgit v1.3-5-g9baa From 36147dd1e8d8e21affbf5d8a758608e63304e4a7 Mon Sep 17 00:00:00 2001 From: "Bryan C. Mills" Date: Wed, 15 Jun 2022 11:50:27 -0400 Subject: cmd/go/internal/modindex: disable indexing for modules outside GOROOT and the module cache MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since CL 410821 we were indexing these modules with a cache key based on the mtimes of the files within the module. However, that seems to be causing test failures (#53269 and maybe #53371). In addition, indexing these modules caused a potentially-expensive operation (re-indexing a whole module) whenever any individual file within the module is changed, even if it isn't relevant to the package(s) being loaded from that module. In some cases, that could cause a significant performance regression for 'go' commands invoked on a small subset of the packages in the module (such as running 'go test' on a single changed package — a common case during development). Instead, we now index only those modules found within the module cache and within GOROOT. In addition, we now check mtimes when indexing GOROOT modules if the Go version begins with the string "devel ", which indicates a non-released Go version that may include local file edits within GOROOT. For #53371. For #53269. Change-Id: Id3aa81b55ecfc478e47dd420148d39d2cf476f2d Reviewed-on: https://go-review.googlesource.com/c/go/+/412394 Reviewed-by: Michael Matloob Auto-Submit: Bryan Mills TryBot-Result: Gopher Robot Run-TryBot: Bryan Mills --- src/cmd/go/internal/modindex/read.go | 68 +++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 25 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/go/internal/modindex/read.go b/src/cmd/go/internal/modindex/read.go index 6ec3a6b3af..ffa091df41 100644 --- a/src/cmd/go/internal/modindex/read.go +++ b/src/cmd/go/internal/modindex/read.go @@ -62,38 +62,56 @@ type ModuleIndex struct { var fcache par.Cache func moduleHash(modroot string, ismodcache bool) (cache.ActionID, error) { + // We expect modules stored within the module cache to be checksummed and + // immutable, and we expect released Go modules to change only infrequently + // (when the Go version changes). + if !ismodcache || !str.HasFilePathPrefix(modroot, cfg.GOROOT) { + return cache.ActionID{}, ErrNotIndexed + } + h := cache.NewHash("moduleIndex") fmt.Fprintf(h, "module index %s %s %v\n", runtime.Version(), indexVersion, modroot) - if ismodcache || str.HasFilePathPrefix(modroot, cfg.GOROOT) { - return h.Sum(), nil - } - // walkdir happens in deterministic order. - err := fsys.Walk(modroot, func(path string, info fs.FileInfo, err error) error { - if modroot == path { - // Check for go.mod in root directory, and return ErrNotIndexed - // if it doesn't exist. Outside the module cache, it's not a module - // if it doesn't have a go.mod file. - } - if err := moduleWalkErr(modroot, path, info, err); err != nil { - return err - } - if info.IsDir() { - return nil - } - fmt.Fprintf(h, "file %v %v\n", info.Name(), info.ModTime()) - if info.Mode()&fs.ModeSymlink != 0 { - targ, err := fsys.Stat(path) - if err != nil { + if strings.HasPrefix(runtime.Version(), "devel ") { + // This copy of the standard library is a development version, not a + // release. It could be based on a Git commit (like "devel go1.19-2a78e8afc0 + // Wed Jun 15 00:06:24 2022 +0000") with or without changes on top of that + // commit, or it could be completly artificial due to lacking a `git` binary + // (like "devel gomote.XXXXX", as synthesized by "gomote push" as of + // 2022-06-15). Compute an inexpensive hash of its files using mtimes so + // that during development we can continue to exercise the logic for cached + // GOROOT indexes. + // + // mtimes may be granular, imprecise, and loosely updated (see + // https://apenwarr.ca/log/20181113), but we don't expect Go contributors to + // be mucking around with the import graphs in GOROOT often enough for mtime + // collisions to matter essentially ever. + // + // Note that fsys.Walk walks paths in deterministic order, so this hash + // should be completely deterministic if the files are unchanged. + err := fsys.Walk(modroot, func(path string, info fs.FileInfo, err error) error { + if err := moduleWalkErr(modroot, path, info, err); err != nil { return err } - fmt.Fprintf(h, "target %v %v\n", targ.Name(), targ.ModTime()) + + if info.IsDir() { + return nil + } + fmt.Fprintf(h, "file %v %v\n", info.Name(), info.ModTime()) + if info.Mode()&fs.ModeSymlink != 0 { + targ, err := fsys.Stat(path) + if err != nil { + return err + } + fmt.Fprintf(h, "target %v %v\n", targ.Name(), targ.ModTime()) + } + return nil + }) + if err != nil { + return cache.ActionID{}, err } - return nil - }) - if err != nil { - return cache.ActionID{}, err } + return h.Sum(), nil } -- cgit v1.3-5-g9baa From c2c76c6f198480f3c9aece4aa5d9b8de044d8457 Mon Sep 17 00:00:00 2001 From: Cherry Mui Date: Tue, 14 Jun 2022 16:47:57 -0400 Subject: cmd/link: set alignment for carrier symbols For carrier symbols like type.*, currently we don't set its alignment. Normally it doesn't actually matter as we still align the inner symbols. But in some cases it does make the symbol table a bit weird, e.g. on darwin/arm64, 0000000000070000 s _runtime.types 0000000000070001 s _type.* The address of the symbol _type.* is a bit weird. And the new darwin linker from Xcode 14 beta doesn't like that (see issue 53372). This CL aligns them. Fixes #53372. Change-Id: I1cb19dcf172e9a6bca248d85a7e54da76cbbc8a4 Reviewed-on: https://go-review.googlesource.com/c/go/+/411912 Reviewed-by: Than McIntosh Run-TryBot: Cherry Mui TryBot-Result: Gopher Robot --- src/cmd/link/internal/ld/symtab.go | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/cmd') diff --git a/src/cmd/link/internal/ld/symtab.go b/src/cmd/link/internal/ld/symtab.go index cc6a2c0e10..ee963bc366 100644 --- a/src/cmd/link/internal/ld/symtab.go +++ b/src/cmd/link/internal/ld/symtab.go @@ -475,16 +475,19 @@ func (ctxt *Link) symtab(pcln *pclntab) []sym.SymKind { s = ldr.CreateSymForUpdate("type.*", 0) s.SetType(sym.STYPE) s.SetSize(0) + s.SetAlign(int32(ctxt.Arch.PtrSize)) symtype = s.Sym() s = ldr.CreateSymForUpdate("typerel.*", 0) s.SetType(sym.STYPERELRO) s.SetSize(0) + s.SetAlign(int32(ctxt.Arch.PtrSize)) symtyperel = s.Sym() } else { s = ldr.CreateSymForUpdate("type.*", 0) s.SetType(sym.STYPE) s.SetSize(0) + s.SetAlign(int32(ctxt.Arch.PtrSize)) symtype = s.Sym() symtyperel = s.Sym() } @@ -496,6 +499,7 @@ func (ctxt *Link) symtab(pcln *pclntab) []sym.SymKind { s := ldr.CreateSymForUpdate(name, 0) s.SetType(t) s.SetSize(0) + s.SetAlign(int32(ctxt.Arch.PtrSize)) s.SetLocal(true) setCarrierSym(t, s.Sym()) return s.Sym() -- cgit v1.3-5-g9baa From 74bf90c779b3d4a4babd3e3de38e3d3e5d9dd7de Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Tue, 14 Jun 2022 19:01:04 -0700 Subject: go/types, types2: add test case for issue for coverage The specific error doesn't occur anymore. Add a test to prevent regressions. For #50729. Change-Id: Ibf6ef6009b3d226b4f345b5a5657939915f19633 Reviewed-on: https://go-review.googlesource.com/c/go/+/412235 Reviewed-by: Robert Griesemer Reviewed-by: Ian Lance Taylor --- .../internal/types2/testdata/fixedbugs/issue50729.go | 19 +++++++++++++++++++ src/go/types/testdata/fixedbugs/issue50729.go | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 src/cmd/compile/internal/types2/testdata/fixedbugs/issue50729.go create mode 100644 src/go/types/testdata/fixedbugs/issue50729.go (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue50729.go b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue50729.go new file mode 100644 index 0000000000..fe19fdfa68 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue50729.go @@ -0,0 +1,19 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +// version 1 +var x1 T1[B1] + +type T1[_ any] struct{} +type A1 T1[B1] +type B1 = T1[A1] + +// version 2 +type T2[_ any] struct{} +type A2 T2[B2] +type B2 = T2[A2] + +var x2 T2[B2] diff --git a/src/go/types/testdata/fixedbugs/issue50729.go b/src/go/types/testdata/fixedbugs/issue50729.go new file mode 100644 index 0000000000..fe19fdfa68 --- /dev/null +++ b/src/go/types/testdata/fixedbugs/issue50729.go @@ -0,0 +1,19 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +// version 1 +var x1 T1[B1] + +type T1[_ any] struct{} +type A1 T1[B1] +type B1 = T1[A1] + +// version 2 +type T2[_ any] struct{} +type A2 T2[B2] +type B2 = T2[A2] + +var x2 T2[B2] -- cgit v1.3-5-g9baa From 1d9d99b7ce279f2af928f79cbc5906d99f29bb67 Mon Sep 17 00:00:00 2001 From: Cherry Mui Date: Thu, 16 Jun 2022 11:35:40 -0400 Subject: cmd/link: consider alignment in carrier symbol size calculation Currently, when we calculate the size of a carrier symbol, we use the previous symbol's end address as the start. But the symbol actually starts after applying the alignment. Do this in the size calculation. Should fix AIX build. Updates #53372. Change-Id: I17942b1fe8027dce12b78c8e8c80ea6cebcee240 Reviewed-on: https://go-review.googlesource.com/c/go/+/412734 Run-TryBot: Cherry Mui TryBot-Result: Gopher Robot Reviewed-by: Than McIntosh --- src/cmd/link/internal/ld/data.go | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src/cmd') diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go index f12cb78fb8..cb2afeaa9a 100644 --- a/src/cmd/link/internal/ld/data.go +++ b/src/cmd/link/internal/ld/data.go @@ -1854,6 +1854,9 @@ func (state *dodataState) allocateDataSections(ctxt *Link) { } for _, symn := range sym.ReadOnly { symnStartValue := state.datsize + if len(state.data[symn]) != 0 { + symnStartValue = aligndatsize(state, symnStartValue, state.data[symn][0]) + } state.assignToSection(sect, symn, sym.SRODATA) setCarrierSize(symn, state.datsize-symnStartValue) if ctxt.HeadType == objabi.Haix { @@ -1935,6 +1938,9 @@ func (state *dodataState) allocateDataSections(ctxt *Link) { symn := sym.RelROMap[symnro] symnStartValue := state.datsize + if len(state.data[symn]) != 0 { + symnStartValue = aligndatsize(state, symnStartValue, state.data[symn][0]) + } for _, s := range state.data[symn] { outer := ldr.OuterSym(s) -- cgit v1.3-5-g9baa From 74f1fa6ecbf79c778fc18d2a6b563fbb94f4b740 Mon Sep 17 00:00:00 2001 From: Michael Matloob Date: Wed, 4 May 2022 17:11:35 -0400 Subject: cmd/go: parallelize matchPackages work in each module In each module matchPackages looks in, when doing the walk, do the scanDir call in a par.Queue so all the read work can be done in parallel. Change-Id: I27153dbb3a2ed417ca24972f47134e9e914a55d1 Reviewed-on: https://go-review.googlesource.com/c/go/+/404097 Reviewed-by: Michael Matloob Run-TryBot: Michael Matloob TryBot-Result: Gopher Robot Reviewed-by: Bryan Mills --- src/cmd/go/internal/modload/search.go | 38 ++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 12 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/go/internal/modload/search.go b/src/cmd/go/internal/modload/search.go index 60c68860ed..4b90392d94 100644 --- a/src/cmd/go/internal/modload/search.go +++ b/src/cmd/go/internal/modload/search.go @@ -12,12 +12,16 @@ import ( "os" "path" "path/filepath" + "runtime" + "sort" "strings" + "sync" "cmd/go/internal/cfg" "cmd/go/internal/fsys" "cmd/go/internal/imports" "cmd/go/internal/modindex" + "cmd/go/internal/par" "cmd/go/internal/search" "golang.org/x/mod/module" @@ -43,9 +47,15 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f treeCanMatch = search.TreeCanMatchPattern(m.Pattern()) } + var mu sync.Mutex have := map[string]bool{ "builtin": true, // ignore pseudo-package that exists only for documentation } + addPkg := func(p string) { + mu.Lock() + m.Pkgs = append(m.Pkgs, p) + mu.Unlock() + } if !cfg.BuildContext.CgoEnabled { have["runtime/cgo"] = true // ignore during walk } @@ -56,6 +66,8 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f pruneGoMod ) + q := par.NewQueue(runtime.GOMAXPROCS(0)) + walkPkgs := func(root, importPathRoot string, prune pruning) { root = filepath.Clean(root) err := fsys.Walk(root, func(path string, fi fs.FileInfo, err error) error { @@ -110,9 +122,11 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f if !have[name] { have[name] = true if isMatch(name) { - if _, _, err := scanDir(root, path, tags); err != imports.ErrNoGo { - m.Pkgs = append(m.Pkgs, name) - } + q.Add(func() { + if _, _, err := scanDir(root, path, tags); err != imports.ErrNoGo { + addPkg(name) + } + }) } } @@ -126,6 +140,12 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f } } + // Wait for all in-flight operations to complete before returning. + defer func() { + <-q.Idle() + sort.Strings(m.Pkgs) // sort everything we added for determinism + }() + if filter == includeStd { walkPkgs(cfg.GOROOTsrc, "", pruneGoMod) if treeCanMatch("cmd") { @@ -169,7 +189,7 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f modPrefix = mod.Path } if mi, err := modindex.Get(root); err == nil { - walkFromIndex(ctx, m, tags, root, mi, have, modPrefix) + walkFromIndex(mi, modPrefix, isMatch, treeCanMatch, tags, have, addPkg) continue } else if !errors.Is(err, modindex.ErrNotIndexed) { m.AddError(err) @@ -188,13 +208,7 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f // walkFromIndex matches packages in a module using the module index. modroot // is the module's root directory on disk, index is the ModuleIndex for the // module, and importPathRoot is the module's path prefix. -func walkFromIndex(ctx context.Context, m *search.Match, tags map[string]bool, modroot string, index *modindex.ModuleIndex, have map[string]bool, importPathRoot string) { - isMatch := func(string) bool { return true } - treeCanMatch := func(string) bool { return true } - if !m.IsMeta() { - isMatch = search.MatchPattern(m.Pattern()) - treeCanMatch = search.TreeCanMatchPattern(m.Pattern()) - } +func walkFromIndex(index *modindex.ModuleIndex, importPathRoot string, isMatch, treeCanMatch func(string) bool, tags, have map[string]bool, addPkg func(string)) { loopPackages: for _, reldir := range index.Packages() { // Avoid .foo, _foo, and testdata subdirectory trees. @@ -232,7 +246,7 @@ loopPackages: have[name] = true if isMatch(name) { if _, _, err := index.ScanDir(reldir, tags); err != imports.ErrNoGo { - m.Pkgs = append(m.Pkgs, name) + addPkg(name) } } } -- cgit v1.3-5-g9baa From 635b1244aa7671bcd665613680f527452cac7555 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Thu, 16 Jun 2022 15:43:57 -0400 Subject: cmd/go: pass GOEXPERIMENT through to subtests This fixes: export GOEXPERIMENT=unified go install cmd go install std cmd go install std cmd go test -short cmd/go -run=TestScript/test_relative_import_dash_i That script test checks that runtime is non-stale, but whether it's stale depends on the setting of GOEXPERIMENT. Stop filtering that variable out. Change-Id: I71bdbca495c16981cdcddf4ab4d87a38ca72a389 Reviewed-on: https://go-review.googlesource.com/c/go/+/412874 Run-TryBot: Russ Cox TryBot-Result: Gopher Robot Reviewed-by: Matthew Dempsky Auto-Submit: Russ Cox --- src/cmd/go/script_test.go | 1 + 1 file changed, 1 insertion(+) (limited to 'src/cmd') diff --git a/src/cmd/go/script_test.go b/src/cmd/go/script_test.go index 04bc8d581a..3ad0608725 100644 --- a/src/cmd/go/script_test.go +++ b/src/cmd/go/script_test.go @@ -170,6 +170,7 @@ func (ts *testScript) setup() { "GOCACHE=" + testGOCACHE, "GODEBUG=" + os.Getenv("GODEBUG"), "GOEXE=" + cfg.ExeSuffix, + "GOEXPERIMENT=" + os.Getenv("GOEXPERIMENT"), "GOOS=" + runtime.GOOS, "GOPATH=" + filepath.Join(ts.workdir, "gopath"), "GOPROXY=" + proxyURL, -- cgit v1.3-5-g9baa