Skip to content

Commit e3ce312

Browse files
mdempskygopherbot
authored andcommitted
cmd/compile/internal/typecheck: fix closure field naming
When creating the struct type to hold variables captured by a function literal, we currently reuse the captured variable names as fields. However, there's no particular reason to do this: these struct types aren't visible to users, and it adds extra complexity in making sure fields belong to the correct packages. Further, it turns out we were getting that subtly wrong. If two function literals from different packages capture variables with identical names starting with an uppercase letter (and in the same order and with corresponding identical types) end up in the same function (e.g., due to inlining), then we could end up creating closure struct types that are "different" (i.e., not types.Identical) yet end up with equal LinkString representations (which violates LinkString's contract). The easy fix is to just always use simple, exported, generated field names in the struct. This should allow further struct reuse across packages too, and shrink binary sizes slightly. Fixes #62498. Change-Id: I9c973f5087bf228649a8f74f7dc1522d84a26b51 Reviewed-on: https://go-review.googlesource.com/c/go/+/527135 Auto-Submit: Matthew Dempsky <[email protected]> Reviewed-by: Cuong Manh Le <[email protected]> Reviewed-by: Keith Randall <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 9af74e7 commit e3ce312

File tree

5 files changed

+50
-31
lines changed

5 files changed

+50
-31
lines changed

src/cmd/compile/internal/typecheck/func.go

+12-28
Original file line numberDiff line numberDiff line change
@@ -91,40 +91,24 @@ func ClosureType(clo *ir.ClosureExpr) *types.Type {
9191
// and has one float64 argument and no results,
9292
// the generated code looks like:
9393
//
94-
// clos = &struct{.F uintptr; i *int; s *string}{func.1, &i, &s}
94+
// clos = &struct{F uintptr; X0 *int; X1 *string}{func.1, &i, &s}
9595
//
9696
// The use of the struct provides type information to the garbage
97-
// collector so that it can walk the closure. We could use (in this case)
98-
// [3]unsafe.Pointer instead, but that would leave the gc in the dark.
99-
// The information appears in the binary in the form of type descriptors;
100-
// the struct is unnamed so that closures in multiple packages with the
101-
// same struct type can share the descriptor.
102-
103-
// Make sure the .F field is in the same package as the rest of the
104-
// fields. This deals with closures in instantiated functions, which are
105-
// compiled as if from the source package of the generic function.
106-
var pkg *types.Pkg
107-
if len(clo.Func.ClosureVars) == 0 {
108-
pkg = types.LocalPkg
109-
} else {
110-
for _, v := range clo.Func.ClosureVars {
111-
if pkg == nil {
112-
pkg = v.Sym().Pkg
113-
} else if pkg != v.Sym().Pkg {
114-
base.Fatalf("Closure variables from multiple packages: %+v", clo)
115-
}
116-
}
117-
}
118-
119-
fields := []*types.Field{
120-
types.NewField(base.Pos, pkg.Lookup(".F"), types.Types[types.TUINTPTR]),
121-
}
122-
for _, v := range clo.Func.ClosureVars {
97+
// collector so that it can walk the closure. We could use (in this
98+
// case) [3]unsafe.Pointer instead, but that would leave the gc in
99+
// the dark. The information appears in the binary in the form of
100+
// type descriptors; the struct is unnamed and uses exported field
101+
// names so that closures in multiple packages with the same struct
102+
// type can share the descriptor.
103+
104+
fields := make([]*types.Field, 1+len(clo.Func.ClosureVars))
105+
fields[0] = types.NewField(base.AutogeneratedPos, types.LocalPkg.Lookup("F"), types.Types[types.TUINTPTR])
106+
for i, v := range clo.Func.ClosureVars {
123107
typ := v.Type()
124108
if !v.Byval() {
125109
typ = types.NewPtr(typ)
126110
}
127-
fields = append(fields, types.NewField(base.Pos, v.Sym(), typ))
111+
fields[1+i] = types.NewField(base.AutogeneratedPos, types.LocalPkg.LookupNum("X", i), typ)
128112
}
129113
typ := types.NewStruct(fields)
130114
typ.SetNoalg(true)

src/cmd/compile/internal/types/fmt.go

-3
Original file line numberDiff line numberDiff line change
@@ -633,9 +633,6 @@ func fldconv(b *bytes.Buffer, f *Field, verb rune, mode fmtMode, visited map[*Ty
633633
name = fmt.Sprint(f.Nname)
634634
} else if verb == 'L' {
635635
name = s.Name
636-
if name == ".F" {
637-
name = "F" // Hack for toolstash -cmp.
638-
}
639636
if !IsExported(name) && mode != fmtTypeIDName {
640637
name = sconv(s, 0, mode) // qualify non-exported names (used on structs, not on funarg)
641638
}

test/fixedbugs/issue62498.dir/a.go

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package a
6+
7+
func One(L any) {
8+
func() {
9+
defer F(L)
10+
}()
11+
}
12+
13+
func F(any) {}

test/fixedbugs/issue62498.dir/main.go

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package main
6+
7+
import "./a"
8+
9+
func main() {
10+
a.One(nil)
11+
Two(nil)
12+
}
13+
14+
func Two(L any) {
15+
func() {
16+
defer a.F(L)
17+
}()
18+
}

test/fixedbugs/issue62498.go

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// rundir
2+
3+
// Copyright 2023 The Go Authors. All rights reserved.
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file.
6+
7+
package ignored

0 commit comments

Comments
 (0)