diff options
| author | Matthew Dempsky <mdempsky@google.com> | 2022-06-16 13:08:10 -0700 |
|---|---|---|
| committer | Matthew Dempsky <mdempsky@google.com> | 2022-06-16 13:08:10 -0700 |
| commit | 1f4e8afafee02b69231ac31bfff7c7211bb41336 (patch) | |
| tree | 03ec911ee9636245f5022e6a8e986d2b946c16fd /src/cmd | |
| parent | 8a9485c023543ba688b6b316223e243fdf36b074 (diff) | |
| parent | 635b1244aa7671bcd665613680f527452cac7555 (diff) | |
| download | go-1f4e8afafee02b69231ac31bfff7c7211bb41336.tar.xz | |
[dev.unified] all: merge master (635b124) into dev.unified
Merge List:
+ 2022-06-16 635b1244aa cmd/go: pass GOEXPERIMENT through to subtests
+ 2022-06-16 ef808ae1d4 expvar: don't crash if map value set to nil
+ 2022-06-16 32510eea74 go/parser: remove unused method checkBinaryExpr
+ 2022-06-16 74f1fa6ecb cmd/go: parallelize matchPackages work in each module
+ 2022-06-16 1d9d99b7ce cmd/link: consider alignment in carrier symbol size calculation
+ 2022-06-16 bcce8ef498 spec: adjust incorrect sentence in section on rune literals
+ 2022-06-16 ecc268aa26 test: add test that gofrontend fails
+ 2022-06-15 b6c1606889 internal/goarch, internal/goos: update generators for syslist.go
+ 2022-06-15 91baf5cecc reflect: fix reference comment to runtime/map.go
+ 2022-06-15 0e3d0c9581 syscall: clarify Pdeathsig documentation on Linux
+ 2022-06-15 74bf90c779 go/types, types2: add test case for issue for coverage
+ 2022-06-15 0cd0c12f57 doc/go1.19: use matching closing tag in unix build constraint heading
+ 2022-06-15 97bfc77f38 syscall, runtime/internal/syscall: always zero the higher bits of return value on linux/loong64
+ 2022-06-15 937fa5000a net/netip: add missing ) in ParsePrefix errors
+ 2022-06-15 c2c76c6f19 cmd/link: set alignment for carrier symbols
+ 2022-06-15 36147dd1e8 cmd/go/internal/modindex: disable indexing for modules outside GOROOT and the module cache
+ 2022-06-15 2a78e8afc0 test: add tests for string/[]byte/[]rune conversions
+ 2022-06-15 f9c0264107 net: avoid infinite recursion in Windows Resolver.lookupTXT
+ 2022-06-14 0dffda1383 spec: clarify "slice of bytes" and "slice of runes" through examples
+ 2022-06-14 c22a6c3b90 reflect: when StructOf overflows computing size/offset, panic
+ 2022-06-14 e1e66a03a6 cmd/compile,runtime,reflect: move embedded bit from offset to name
+ 2022-06-14 cb9bf93078 cmd/go: quote package directory when calling glob
+ 2022-06-14 cad477c922 cpu: fix typos in test case
+ 2022-06-13 c29be2d41c runtime: add HACKING section on nosplit functions
+ 2022-06-13 c5be77b687 doc/go1.19: minor edits
+ 2022-06-13 56bc3098f4 sync: improve linearity test robustness
+ 2022-06-13 1fe2810f9c sync: move lock linearity test and treat it like a performance test
+ 2022-06-13 6130461149 internal/testmath: add two-sample Welch's t-test for performance tests
+ 2022-06-13 24b9039149 doc/go1.19: prefer relative links to other parts of the Go website
+ 2022-06-13 fbc75dff2f cmd/cgo: remove -fsanitize=hwaddress hardware tags
+ 2022-06-13 5ee939b819 spec: clarify behavior of map size hint for make built-in
+ 2022-06-13 4703546a29 spec: add missing optional type arguments after TypeName in syntax
+ 2022-06-13 2c52465cb3 net: avoid darwin_arm64 bug in TestDialParallelSpuriousConnection
+ 2022-06-13 9228d7d7d5 doc/go1.19: add a release note for module indexing
+ 2022-06-13 7eeec1f6e4 cmd/compile: fix missing dict pass for type assertions
+ 2022-06-13 d27128b065 doc/go1.19: fix crypto tags
+ 2022-06-10 55590f3a2b net/http: doc: update RFC reference for appropriate HTTP codes
+ 2022-06-10 ff3db8d12d doc: fix typos in Go memory model
+ 2022-06-10 fb75c2da91 cmd/dist, cmd/internal/metadata: don't install metadata binary
+ 2022-06-10 386245b68e runtime: fix stack split at bad time when fuzzing
+ 2022-06-09 2cfbef4380 cmd/cgo: recognize clang 14 DWARF type names
+ 2022-06-09 c7ccabf3fe runtime/cgo: retry _beginthread on EACCES
+ 2022-06-09 91019cc13d runtime/cgo: merge bodies of cgo_sys_thread_start on windows
+ 2022-06-09 840e99ed74 api: promote next to go1.19
+ 2022-06-09 1a2ca95ad2 go/types, types2: only set instance context if packages match
+ 2022-06-08 b51d44c6dd cmd/go/testdata/script: fix skip on list_replace_absolute_windows
+ 2022-06-08 80f86f706d api/next: minor reformat
+ 2022-06-08 13f6be2833 runtime: use pidleget for faketime jump
+ 2022-06-08 1292176bc9 cmd/go: clean paths before using them form index functions
+ 2022-06-08 1858ea5d85 syscall: remove unused setgroups on linux/loong64
+ 2022-06-08 bdde41e3ba runtime: skip TestGdbBacktrace on gdb bug
+ 2022-06-08 432158b69a net: fix testHookDialTCP race
+ 2022-06-08 899f0a29c7 cmd/go: enable module index by default
+ 2022-06-08 f862280e30 cmd/go: properly call PackageModuleRoot to get modroot for index
+ 2022-06-08 d65166024f cmd/go: set Root and target fields for packages in GOPATH
+ 2022-06-08 4afb0b9e53 doc/go1.19: delete remaining TODOs
+ 2022-06-08 3426b7201d runtime: gofmt
+ 2022-06-08 f330a3a987 doc/go1.19: complete most remaining TODOs
+ 2022-06-08 2882786bf4 runtime: remove unused pipe and setNonblock on linux/loong64
+ 2022-06-08 decdd87bea doc/go1.19: mention riscv64 supported regabi
+ 2022-06-07 b72a6a7b86 os: document that Chdir affects fs.FS returned by DirFS with a relative path
+ 2022-06-07 30b929b1ef syscall: remove unused accept on linux/loong64
+ 2022-06-07 a7551fe245 net: use synthetic network in TestDialParallel
+ 2022-06-07 19d71acd97 doc/go1.19: document that the assembler requires -p
+ 2022-06-07 d151134851 doc/go1.19: document linker CL that switches DWARF compressed section format
+ 2022-06-07 3507805bcd go/types, types2: better error message for invalid use of constraint type
+ 2022-06-07 269bf7e855 go/types, types2: better error message if type is not in type set
+ 2022-06-07 d4fb93be87 go/types, types2: use | rather than ∪ when printing term lists
+ 2022-06-07 346698eea7 doc/go1.19: add release notes for net/http and net/url
+ 2022-06-07 7a82c6859f doc/go1.19: adjust runtime release notes
+ 2022-06-07 f3e051a184 runtime: document GOMEMLIMIT in environment variables section
+ 2022-06-07 ef2567c7dd doc/go1.19: document loong64 port
+ 2022-06-07 69bb7c6ef5 sync/atomic: clarify that 8-byte alignment of variables is due to escape
+ 2022-06-07 81033fbd8e doc/go1.19: some platforms are still on TSAN v2
+ 2022-06-07 0c3a0543c2 doc/go1.19: compiler section is complete, modulo TODOs
+ 2022-06-07 835a946137 doc/go1.19: minor edits
+ 2022-06-07 429a4041eb doc/go1.19: complete TODOs for go/types
+ 2022-06-07 d2630aa4b2 doc/go1.19: add various crypto release notes
+ 2022-06-07 77d9252ddf runtime: fix inline assembly trampoline for arm64
+ 2022-06-07 38607c5538 cmd/link: specify -Wl,-z params as documented
+ 2022-06-07 95b68e1e02 doc/go1.19: delete boringcrypto TODO
+ 2022-06-07 a79623b019 doc/go1.19: add more TODOs from updated relnote
+ 2022-06-06 acfff42802 doc/go1.19: add release notes for the soft memory limit and idle GC
+ 2022-06-06 a71ca3dfbd runtime, sync, sync/atomic: document happens-before guarantees
+ 2022-06-06 3651a6117e go/doc/comment: add heuristics for common badly formatted comments
+ 2022-06-06 4c08260c51 doc/go_mem: update revision date
+ 2022-06-06 7271a0a287 doc/go1.19: gc requires -p=importpath
+ 2022-06-06 c1e2ecbaf9 doc/go1.19: document Resolver.PreferGo
+ 2022-06-06 11195c60e6 cmd/go: use index to match packages in dependency modules
+ 2022-06-06 ea5d7cbc26 all: boringcrypto post-merge cleanup
+ 2022-06-06 6c7b223c2b go/doc/comment: do not turn ``` into “`
+ 2022-06-06 ce757e94e0 go/doc/comment: add doc comment
+ 2022-06-06 95547aee8c cmd/compile: cast riscv64 rewrite shifts to unsigned int
+ 2022-06-06 d43ddc1f3f strconv: fix typo in atof.go
+ 2022-06-06 2fa45a4fcd cmd/link/internal/loadpe: handle _main reference properly
+ 2022-06-06 fc97075949 go/types, types2: simplify implementation of validType (fix TODO)
+ 2022-06-06 07eca49055 go/types, types2: use type nest to detect type cycles (fix validType)
+ 2022-06-06 770146d5a8 doc/go1.19: add TODOs for changes to go/types
+ 2022-06-06 1b8ca75eaa runtime: fix breakpoint in ppc64x
+ 2022-06-06 9ce28b518d text/template/parse: fix data race on lexer initialization
+ 2022-06-06 47e34ca533 go/types, types2: ensure that named types never expand infinitely
+ 2022-06-06 02e69cfa96 go/types, types2: store Named instance information separately
+ 2022-06-06 1323b0e8f0 go/types, types2: eliminate methodList in favor of just using Named.mu
+ 2022-06-06 846f971daa go/types, types2: remove Named.once in favor of monotonic state
+ 2022-06-06 66cbf67345 cmd/buildid: reject rewriting legacy buildids
+ 2022-06-04 47f806ce81 strconv: clarify ParseFloat accepts Go syntax for float literals
+ 2022-06-04 2730c6af9f runtime: fix typo in libfuzzer_arm64.s
+ 2022-06-04 a32a592c8c database/sql/driver: fix typo in driver.go
+ 2022-06-04 0293c51bc5 regexp: avoid copying each instruction executed
+ 2022-06-04 865911424d doc: update Go memory model
+ 2022-06-04 fc66cae490 doc/go1.19: remove TODO about LimitedReader
+ 2022-06-04 f8a53df314 io: revert: add an Err field to LimitedReader
+ 2022-06-04 21f05284c7 cmd/go: index standard library packages
Change-Id: Ia7595c77a555fd2a0e7bb3b6b2cfbb745bd4947b
Diffstat (limited to 'src/cmd')
54 files changed, 1467 insertions, 725 deletions
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) diff --git a/src/cmd/cgo/gcc.go b/src/cmd/cgo/gcc.go index 4d1a5bd8de..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") @@ -2242,6 +2263,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 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/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/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 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/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/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/decl.go b/src/cmd/compile/internal/types2/decl.go index b6f81aa8a5..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 { @@ -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] } @@ -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. @@ -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/infer.go b/src/cmd/compile/internal/types2/infer.go index 9e77d67a7d..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 { @@ -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 @@ -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 bb90ab3736..5833f8db7e 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,40 +54,72 @@ 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 +// 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 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, 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 expanding != nil, the returned instance will be the instance + // recorded in expanding.inst.ctxt. + var ctxts []*Context + if expanding != nil { + ctxts = append(ctxts, expanding.inst.ctxt) + } 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 + ctxts = append(ctxts, ctxt) + } + 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: - 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 + res = check.newNamedInstance(pos, orig, targs, expanding) // substituted lazily case *Signature: + assert(expanding == nil) // function instances cannot be reached from Named types + tparams := orig.TypeParams() if !check.validateTArgLen(pos, tparams.Len(), len(targs)) { return Typ[Invalid] @@ -92,7 +127,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, 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). @@ -110,13 +145,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, @@ -134,7 +164,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). @@ -143,7 +173,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 } @@ -248,7 +278,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/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 849398a6f4..2cf6d3871f 100644 --- a/src/cmd/compile/internal/types2/named.go +++ b/src/cmd/compile/internal/types2/named.go @@ -7,30 +7,136 @@ 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). +// +// 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 { - 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 + + // 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 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). + // 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. - methods *methodList + // instantiated types, methods are individually expanded when they are first + // accessed. + methods []*Func - // 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 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) + ctxt *Context // local Context; set to nil after full expansion +} + +// 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 // 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. // 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. @@ -38,39 +144,122 @@ 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, underlying, 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() *Named { + if n.state() >= resolved { // avoid locking below + return n + } + + // 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 } - 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 + 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 + + orig := n.inst.orig + orig.resolve() + underlying := n.expandUnderlying() + + n.tparams = orig.tparams + n.underlying = underlying + n.fromRHS = orig.fromRHS // for cycle detection + + if len(orig.methods) == 0 { + n.setState(complete) // nothing further to do + n.inst.ctxt = nil + } else { + 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) + 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 = methods + n.loader = nil + } + + n.setState(complete) + 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. -func (check *Checker) newNamed(obj *TypeName, orig *Named, underlying Type, methods *methodList) *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 } - // Ensure that typ is always expanded and sanity-checked. + // Ensure that typ is always sanity-checked. + if check != nil { + check.needsCleanup(typ) + } + return typ +} + +// 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)} + + // 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 sanity-checked. if check != nil { check.needsCleanup(typ) } @@ -78,18 +267,18 @@ func (check *Checker) newNamed(obj *TypeName, orig *Named, underlying Type, meth } 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, 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: @@ -100,51 +289,91 @@ 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. -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.targs.Len() == 0) - t.resolve(nil).tparams = bindTParams(tparams) + assert(t.inst == nil) + t.resolve().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 t.resolve(nil).methods.Len() } +func (t *Named) NumMethods() int { + 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) - return t.methods.At(i, func() *Func { - return t.instantiateMethod(i) - }) -} + t.resolve() + + if t.state() >= complete { + return t.methods[i] + } + + assert(t.inst != nil) // only instances should have incomplete methods + orig := t.inst.orig -// instantiateMethod instantiates the i'th method for an instantiated receiver. -func (t *Named) instantiateMethod(i int) *Func { - assert(t.TypeArgs().Len() > 0) // t must be an instance + t.mu.Lock() + defer t.mu.Unlock() + if len(t.methods) != len(orig.methods) { + assert(len(t.methods) == 0) + t.methods = make([]*Func, len(orig.methods)) + } + + 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++ + + // Check if we've created all methods at this point. If we have, mark the + // type as fully expanded. + if t.inst.expandedMethods == len(orig.methods) { + t.setState(complete) + t.inst.ctxt = nil // no need for a context anymore + } + } + + return t.methods[i] +} + +// expandMethod substitutes type arguments in the i'th method for an +// instantiated receiver. +func (t *Named) expandMethod(i int) *Func { // 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 @@ -171,10 +400,13 @@ func (t *Named) instantiateMethod(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() { - ctxt := check.bestContext(nil) - smap := makeSubstMap(origSig.RecvTypeParams().list(), t.targs.list()) - sig = check.subst(origm.pos, origSig, smap, ctxt).(*Signature) + if origSig.RecvTypeParams().Len() == t.inst.targs.Len() { + smap := makeSubstMap(origSig.RecvTypeParams().list(), t.inst.targs.list()) + var ctxt *Context + if check != nil { + ctxt = check.context() + } + sig = check.subst(origm.pos, origSig, smap, t, ctxt).(*Signature) } if sig == origSig { @@ -198,14 +430,14 @@ func (t *Named) instantiateMethod(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") } 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 } @@ -214,19 +446,21 @@ 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) - t.resolve(nil) - if t.methods == nil { - t.methods = newMethodList(nil) + assert(t.inst == nil) + t.resolve() + 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 } +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 @@ -275,7 +509,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: @@ -323,11 +557,11 @@ 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). - i, _ := n.orig.methods.Lookup(pkg, name, foldCase) + i, _ := lookupMethod(n.Origin().methods, pkg, name, foldCase) if i < 0 { return -1, nil } @@ -335,83 +569,83 @@ 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 } -// 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() 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) + assert(n.inst.orig.underlying != nil) + if n.inst.ctxt == nil { + n.inst.ctxt = NewContext() + } + + orig := n.inst.orig + targs := n.inst.targs - if _, unexpanded := n.orig.underlying.(*Named); unexpanded { - // We should only get an unexpanded underlying here during type checking + 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) } - // 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) + if orig.tparams.Len() != targs.Len() { + // Mismatching arg and tparam length may be checked elsewhere. + return Typ[Invalid] + } + + // 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(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 + smap := makeSubstMap(orig.tparams.list(), targs.list()) + var ctxt *Context + if check != nil { + ctxt = check.context() + } + 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. + if iface, _ := underlying.(*Interface); iface != nil { + 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 == 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..e5e8eddb05 --- /dev/null +++ b/src/cmd/compile/internal/types2/named_test.go @@ -0,0 +1,120 @@ +// 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/syntax" + . "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 +} + +// 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/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/predicates.go b/src/cmd/compile/internal/types2/predicates.go index 6bce26137e..f7b5b16204 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. @@ -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 && @@ -401,7 +402,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/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/sizeof_test.go b/src/cmd/compile/internal/types2/sizeof_test.go index 7ab7abb317..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{}, 56, 104}, + {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..d5a48c6995 100644 --- a/src/cmd/compile/internal/types2/subst.go +++ b/src/cmd/compile/internal/types2/subst.go @@ -48,8 +48,11 @@ 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, ctxt *Context) Type { +// 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 } @@ -64,19 +67,21 @@ 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, + expanding: expanding, + ctxt: ctxt, } 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 + expanding *Named // if non-nil, the instance that is being expanded + ctxt *Context } func (subst *subster) typ(typ Type) Type { @@ -176,7 +181,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 +212,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 +234,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 } @@ -246,25 +252,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(t.orig, newTArgs) - dump(">>> new type hash: %s", h) - if named := subst.ctxt.lookup(h, t.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, t.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.expanding, subst.ctxt) case *TypeParam: return subst.smap.lookup(t) 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/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/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/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/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/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/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 1f8b40dba6..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) } } } @@ -432,57 +432,19 @@ 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, nil, check.context()).(*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.TypeArgs().Len()) { + 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) { @@ -490,10 +452,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.TypeArgs().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/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..99fdebc978 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) } -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 - ) - +// 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 @@ -37,112 +32,204 @@ 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, nest, path) case *Struct: for _, f := range t.fields { - if check.validType0(f.typ, env, path) == invalid { - return invalid + if !check.validType0(f.typ, 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, nest, path) { + return false } } case *Interface: for _, etyp := range t.embeddeds { - if check.validType0(etyp, env, path) == invalid { - return invalid + if !check.validType0(etyp, 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.orig.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, 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. - // 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. - return check.validType0(targ, env.link, 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) + } } } } - return valid -} - -// 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 + return true } -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 +// 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 } - - // 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} + return olist } -// 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 +// +// 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/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/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 8ceacec326..fe4a82472d 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" @@ -872,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 @@ -2064,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 } @@ -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/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/modindex/build.go b/src/cmd/go/internal/modindex/build.go index 78bd12636d..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. @@ -887,8 +918,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/go/internal/modindex/read.go b/src/cmd/go/internal/modindex/read.go index e180ca5450..ffa091df41 100644 --- a/src/cmd/go/internal/modindex/read.go +++ b/src/cmd/go/internal/modindex/read.go @@ -1,38 +1,53 @@ +// 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. // 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 @@ -47,38 +62,56 @@ type ModuleIndex struct { 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 { - return h.Sum(), nil + // 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 } - // 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 { + h := cache.NewHash("moduleIndex") + fmt.Fprintf(h, "module index %s %s %v\n", runtime.Version(), indexVersion, modroot) + + 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 } @@ -97,10 +130,7 @@ 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 - } + modroot = filepath.Clean(modroot) isModCache := str.HasFilePathPrefix(modroot, cfg.GOMODCACHE) return openIndex(modroot, isModCache) } @@ -121,7 +151,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} @@ -206,7 +236,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(filepath.Clean(path), mi.modroot) // mi.modroot is already clean } // ImportPackage is the equivalent of build.Import given the information in ModuleIndex. @@ -225,9 +255,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 +268,78 @@ 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 and gopath + inTestdata := func(sub string) bool { + return strings.Contains(sub, "/testdata/") || strings.HasSuffix(sub, "/testdata") || str.HasPathPrefix(sub, "testdata") + } + 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. + // 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? + + // 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, but is also set for modules in a GOPATH src + // directory. + 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 +543,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 +584,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 +678,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 +696,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/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/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 { diff --git a/src/cmd/go/internal/modload/search.go b/src/cmd/go/internal/modload/search.go index cddb9f8067..4b90392d94 100644 --- a/src/cmd/go/internal/modload/search.go +++ b/src/cmd/go/internal/modload/search.go @@ -6,15 +6,22 @@ package modload import ( "context" + "errors" "fmt" "io/fs" "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" @@ -40,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 } @@ -53,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 { @@ -107,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) + } + }) } } @@ -123,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") { @@ -165,6 +188,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(mi, modPrefix, isMatch, treeCanMatch, tags, have, addPkg) + continue + } else if !errors.Is(err, modindex.ErrNotIndexed) { + m.AddError(err) + } prune := pruneVendor if isLocal { @@ -176,6 +205,54 @@ 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(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. + 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 { + addPkg(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. 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/script_test.go b/src/cmd/go/script_test.go index d1fe36ec21..3ad0608725 100644 --- a/src/cmd/go/script_test.go +++ b/src/cmd/go/script_test.go @@ -170,7 +170,7 @@ func (ts *testScript) setup() { "GOCACHE=" + testGOCACHE, "GODEBUG=" + os.Getenv("GODEBUG"), "GOEXE=" + cfg.ExeSuffix, - "GOINDEX=true", + "GOEXPERIMENT=" + os.Getenv("GOEXPERIMENT"), "GOOS=" + runtime.GOOS, "GOPATH=" + filepath.Join(ts.workdir, "gopath"), "GOPROXY=" + proxyURL, 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 -- 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..b3ff2a7c2d --- /dev/null +++ b/src/cmd/go/testdata/script/list_replace_absolute_windows.txt @@ -0,0 +1,38 @@ +# 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] 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 + +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 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 ( 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/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) 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/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) 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() 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 } } |
