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
+	}
+}