internal/gcimporter: work around go/types data race in 1.23.
Work around golang/go#69912 by punching a hole through the TypeName type
to mark it black after importing. This is an unfortunate workaround, but
the fix for the long-standing data race is probably not worth
back-porting.
For golang/go#69912
Change-Id: I583305f6e893e28b881dab932c9c4825430bc4ad
Reviewed-on: https://go-review.googlesource.com/c/tools/+/621855
Reviewed-by: Alan Donovan <adonovan@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/internal/gcimporter/gcimporter_test.go b/internal/gcimporter/gcimporter_test.go
index 5519fa0..606efc4 100644
--- a/internal/gcimporter/gcimporter_test.go
+++ b/internal/gcimporter/gcimporter_test.go
@@ -1043,3 +1043,71 @@
})
}
}
+
+type importMap map[string]*types.Package
+
+func (m importMap) Import(path string) (*types.Package, error) { return m[path], nil }
+
+func TestIssue69912(t *testing.T) {
+ fset := token.NewFileSet()
+
+ check := func(pkgname, src string, imports importMap) (*types.Package, error) {
+ f, err := goparser.ParseFile(fset, "a.go", src, 0)
+ if err != nil {
+ return nil, err
+ }
+ config := &types.Config{
+ Importer: imports,
+ }
+ return config.Check(pkgname, fset, []*ast.File{f}, nil)
+ }
+
+ const libSrc = `package lib
+
+type T int
+`
+
+ lib, err := check("lib", libSrc, nil)
+ if err != nil {
+ t.Fatalf("Checking lib: %v", err)
+ }
+
+ // Export it.
+ var out bytes.Buffer
+ if err := gcimporter.IExportData(&out, fset, lib); err != nil {
+ t.Fatalf("export: %v", err) // any failure to export is a bug
+ }
+
+ // Re-import it.
+ imports := make(map[string]*types.Package)
+ _, lib2, err := gcimporter.IImportData(fset, imports, out.Bytes(), "lib")
+ if err != nil {
+ t.Fatalf("import: %v", err) // any failure of export+import is a bug.
+ }
+
+ // Use the resulting package concurrently, via dot-imports.
+
+ const pSrc = `package p
+
+import . "lib"
+
+type S struct {
+ f T
+}
+`
+ importer := importMap{
+ "lib": lib2,
+ }
+ var wg sync.WaitGroup
+ for range 10 {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ _, err := check("p", pSrc, importer)
+ if err != nil {
+ t.Errorf("check failed: %v", err)
+ }
+ }()
+ }
+ wg.Wait()
+}
diff --git a/internal/gcimporter/iimport.go b/internal/gcimporter/iimport.go
index 21908a1..e260c0e 100644
--- a/internal/gcimporter/iimport.go
+++ b/internal/gcimporter/iimport.go
@@ -558,6 +558,14 @@
prevColumn int64
}
+// markBlack is redefined in iimport_go123.go, to work around golang/go#69912.
+//
+// If TypeNames are not marked black (in the sense of go/types cycle
+// detection), they may be mutated when dot-imported. Fix this by punching a
+// hole through the type, when compiling with Go 1.23. (The bug has been fixed
+// for 1.24, but the fix was not worth back-porting).
+var markBlack = func(name *types.TypeName) {}
+
func (r *importReader) obj(name string) {
tag := r.byte()
pos := r.pos()
@@ -570,6 +578,7 @@
}
typ := r.typ()
obj := aliases.NewAlias(r.p.aliases, pos, r.currPkg, name, typ, tparams)
+ markBlack(obj) // workaround for golang/go#69912
r.declare(obj)
case constTag:
@@ -590,6 +599,9 @@
// declaration before recursing.
obj := types.NewTypeName(pos, r.currPkg, name, nil)
named := types.NewNamed(obj, nil, nil)
+
+ markBlack(obj) // workaround for golang/go#69912
+
// Declare obj before calling r.tparamList, so the new type name is recognized
// if used in the constraint of one of its own typeparams (see #48280).
r.declare(obj)
diff --git a/internal/gcimporter/iimport_go123.go b/internal/gcimporter/iimport_go123.go
new file mode 100644
index 0000000..3792aa6
--- /dev/null
+++ b/internal/gcimporter/iimport_go123.go
@@ -0,0 +1,54 @@
+// Copyright 2024 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.
+
+//go:build go1.23 && !go1.24
+
+package gcimporter
+
+import (
+ "go/token"
+ "go/types"
+ "unsafe"
+)
+
+// TODO(rfindley): delete this workaround once gopls no longer compiles with
+// go1.23.
+
+func init() {
+ // Update markBlack so that it correctly sets the color
+ // of imported TypeNames.
+ //
+ // See the doc comment for markBlack for details.
+
+ type color uint32
+ const (
+ white color = iota
+ black
+ grey
+ )
+ type object struct {
+ _ *types.Scope
+ _ token.Pos
+ _ *types.Package
+ _ string
+ _ types.Type
+ _ uint32
+ color_ color
+ _ token.Pos
+ }
+ type typeName struct {
+ object
+ }
+
+ // If the size of types.TypeName changes, this will fail to compile.
+ const delta = int64(unsafe.Sizeof(typeName{})) - int64(unsafe.Sizeof(types.TypeName{}))
+ var _ [-delta * delta]int
+
+ markBlack = func(obj *types.TypeName) {
+ type uP = unsafe.Pointer
+ var ptr *typeName
+ *(*uP)(uP(&ptr)) = uP(obj)
+ ptr.color_ = black
+ }
+}