go/types, types2: instantiated interfaces must be concurrency safe

It is the responsibility of go/types to complete any interface it
creates, except for those created by the user using NewInterface.
However, this was not being done for interfaces created during
instantiation.

Fix this by (rather carefully) ensuring that all newly created
interfaces are eventually completed.

Fixes golang/go#61561

Change-Id: I3926e7c9cf80714838d2c1b5f36a2d3221c60c41
Reviewed-on: https://go-review.googlesource.com/c/go/+/513015
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Auto-Submit: Robert Findley <rfindley@google.com>
Reviewed-by: Robert Griesemer <gri@google.com>
This commit is contained in:
Rob Findley 2023-07-25 11:57:37 -04:00 committed by Gopher Robot
parent 8597d052b2
commit b6898dde3d
8 changed files with 138 additions and 0 deletions

View File

@ -13,6 +13,7 @@ import (
"regexp"
"sort"
"strings"
"sync"
"testing"
. "cmd/compile/internal/types2"
@ -2295,6 +2296,60 @@ func TestInstantiate(t *testing.T) {
}
}
func TestInstantiateConcurrent(t *testing.T) {
const src = `package p
type I[P any] interface {
m(P)
n() P
}
type J = I[int]
type Nested[P any] *interface{b(P)}
type K = Nested[string]
`
pkg := mustTypecheck(src, nil, nil)
insts := []*Interface{
pkg.Scope().Lookup("J").Type().Underlying().(*Interface),
pkg.Scope().Lookup("K").Type().Underlying().(*Pointer).Elem().(*Interface),
}
// Use the interface instances concurrently.
for _, inst := range insts {
var (
counts [2]int // method counts
methods [2][]string // method strings
)
var wg sync.WaitGroup
for i := 0; i < 2; i++ {
i := i
wg.Add(1)
go func() {
defer wg.Done()
counts[i] = inst.NumMethods()
for mi := 0; mi < counts[i]; mi++ {
methods[i] = append(methods[i], inst.Method(mi).String())
}
}()
}
wg.Wait()
if counts[0] != counts[1] {
t.Errorf("mismatching method counts for %s: %d vs %d", inst, counts[0], counts[1])
continue
}
for i := 0; i < counts[0]; i++ {
if m0, m1 := methods[0][i], methods[1][i]; m0 != m1 {
t.Errorf("mismatching methods for %s: %s vs %s", inst, m0, m1)
}
}
}
}
func TestInstantiateErrors(t *testing.T) {
tests := []struct {
src string // by convention, T must be the type being instantiated

View File

@ -112,6 +112,7 @@ func (t *Interface) String() string { return TypeString(t, nil) }
// Implementation
func (t *Interface) cleanup() {
t.typeSet() // any interface that escapes type checking must be safe for concurrent use
t.check = nil
t.embedPos = nil
}

View File

@ -633,11 +633,18 @@ func (n *Named) expandUnderlying() Type {
old := iface
iface = check.newInterface()
iface.embeddeds = old.embeddeds
assert(old.complete) // otherwise we are copying incomplete data
iface.complete = old.complete
iface.implicit = old.implicit // should be false but be conservative
underlying = iface
}
iface.methods = methods
iface.tset = nil // recompute type set with new methods
// If check != nil, check.newInterface will have saved the interface for later completion.
if check == nil { // golang/go#61561: all newly created interfaces must be fully evaluated
iface.typeSet()
}
}
}

View File

@ -170,6 +170,7 @@ func (subst *subster) typ(typ Type) Type {
iface := subst.check.newInterface()
iface.embeddeds = embeddeds
iface.implicit = t.implicit
assert(t.complete) // otherwise we are copying incomplete data
iface.complete = t.complete
// If we've changed the interface type, we may need to replace its
// receiver if the receiver type is the original interface. Receivers of
@ -185,6 +186,11 @@ func (subst *subster) typ(typ Type) Type {
// need to create new interface methods to hold the instantiated
// receiver. This is handled by Named.expandUnderlying.
iface.methods, _ = replaceRecvType(methods, t, iface)
// If check != nil, check.newInterface will have saved the interface for later completion.
if subst.check == nil { // golang/go#61561: all newly created interfaces must be completed
iface.typeSet()
}
return iface
}

View File

@ -16,6 +16,7 @@ import (
"regexp"
"sort"
"strings"
"sync"
"testing"
. "go/types"
@ -2300,6 +2301,60 @@ func TestInstantiate(t *testing.T) {
}
}
func TestInstantiateConcurrent(t *testing.T) {
const src = `package p
type I[P any] interface {
m(P)
n() P
}
type J = I[int]
type Nested[P any] *interface{b(P)}
type K = Nested[string]
`
pkg := mustTypecheck(src, nil, nil)
insts := []*Interface{
pkg.Scope().Lookup("J").Type().Underlying().(*Interface),
pkg.Scope().Lookup("K").Type().Underlying().(*Pointer).Elem().(*Interface),
}
// Use the interface instances concurrently.
for _, inst := range insts {
var (
counts [2]int // method counts
methods [2][]string // method strings
)
var wg sync.WaitGroup
for i := 0; i < 2; i++ {
i := i
wg.Add(1)
go func() {
defer wg.Done()
counts[i] = inst.NumMethods()
for mi := 0; mi < counts[i]; mi++ {
methods[i] = append(methods[i], inst.Method(mi).String())
}
}()
}
wg.Wait()
if counts[0] != counts[1] {
t.Errorf("mismatching method counts for %s: %d vs %d", inst, counts[0], counts[1])
continue
}
for i := 0; i < counts[0]; i++ {
if m0, m1 := methods[0][i], methods[1][i]; m0 != m1 {
t.Errorf("mismatching methods for %s: %s vs %s", inst, m0, m1)
}
}
}
}
func TestInstantiateErrors(t *testing.T) {
tests := []struct {
src string // by convention, T must be the type being instantiated

View File

@ -151,6 +151,7 @@ func (t *Interface) String() string { return TypeString(t, nil) }
// Implementation
func (t *Interface) cleanup() {
t.typeSet() // any interface that escapes type checking must be safe for concurrent use
t.check = nil
t.embedPos = nil
}

View File

@ -635,11 +635,18 @@ func (n *Named) expandUnderlying() Type {
old := iface
iface = check.newInterface()
iface.embeddeds = old.embeddeds
assert(old.complete) // otherwise we are copying incomplete data
iface.complete = old.complete
iface.implicit = old.implicit // should be false but be conservative
underlying = iface
}
iface.methods = methods
iface.tset = nil // recompute type set with new methods
// If check != nil, check.newInterface will have saved the interface for later completion.
if check == nil { // golang/go#61561: all newly created interfaces must be fully evaluated
iface.typeSet()
}
}
}

View File

@ -172,6 +172,7 @@ func (subst *subster) typ(typ Type) Type {
iface := subst.check.newInterface()
iface.embeddeds = embeddeds
iface.implicit = t.implicit
assert(t.complete) // otherwise we are copying incomplete data
iface.complete = t.complete
// If we've changed the interface type, we may need to replace its
// receiver if the receiver type is the original interface. Receivers of
@ -187,6 +188,11 @@ func (subst *subster) typ(typ Type) Type {
// need to create new interface methods to hold the instantiated
// receiver. This is handled by Named.expandUnderlying.
iface.methods, _ = replaceRecvType(methods, t, iface)
// If check != nil, check.newInterface will have saved the interface for later completion.
if subst.check == nil { // golang/go#61561: all newly created interfaces must be completed
iface.typeSet()
}
return iface
}