go/types: ensure that constructed type parameters are immutable

TypeParam.iface may mutate TypeParam.bound in the event that the type
parameter bound is not an interface.

Ensure that iface() is called before the type-checking pass returns, and
before NewTypeParam or TypeParam.SetConstraint exits.

Fixes #49788

Change-Id: I72279acf5f0223161671c04887bc2c3df4158927
Reviewed-on: https://go-review.googlesource.com/c/go/+/367614
Trust: Robert Findley <rfindley@google.com>
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
Robert Findley 2021-11-29 14:52:54 -05:00
parent c402d64f37
commit 186f375ecf
2 changed files with 32 additions and 3 deletions

View file

@ -21,7 +21,7 @@ type TypeParam struct {
id uint64 // unique id, for debugging only
obj *TypeName // corresponding type name
index int // type parameter index in source order, starting at 0
bound Type // any type, but eventually an *Interface for correct programs (see TypeParam.iface)
bound Type // any type, but underlying is eventually *Interface for correct programs (see TypeParam.iface)
}
// Obj returns the type name for the type parameter t.
@ -47,6 +47,15 @@ func (check *Checker) newTypeParam(obj *TypeName, constraint Type) *TypeParam {
if obj.typ == nil {
obj.typ = typ
}
// iface may mutate typ.bound, so we must ensure that iface() is called
// at least once before the resulting TypeParam escapes.
if check != nil {
check.later(func() {
typ.iface()
})
} else if constraint != nil {
typ.iface()
}
return typ
}
@ -62,11 +71,17 @@ func (t *TypeParam) Constraint() Type {
}
// SetConstraint sets the type constraint for t.
//
// SetConstraint should not be called concurrently, but once SetConstraint
// returns the receiver t is safe for concurrent use.
func (t *TypeParam) SetConstraint(bound Type) {
if bound == nil {
panic("nil constraint")
}
t.bound = bound
// iface may mutate t.bound (if bound is not an interface), so ensure that
// this is done before returning.
t.iface()
}
func (t *TypeParam) Underlying() Type {

View file

@ -24,7 +24,7 @@ type TypeParam struct {
id uint64 // unique id, for debugging only
obj *TypeName // corresponding type name
index int // type parameter index in source order, starting at 0
bound Type // any type, but eventually an *Interface for correct programs (see TypeParam.iface)
bound Type // any type, but underlying is eventually *Interface for correct programs (see TypeParam.iface)
}
// NewTypeParam returns a new TypeParam. Type parameters may be set on a Named
@ -47,6 +47,15 @@ func (check *Checker) newTypeParam(obj *TypeName, constraint Type) *TypeParam {
if obj.typ == nil {
obj.typ = typ
}
// iface may mutate typ.bound, so we must ensure that iface() is called
// at least once before the resulting TypeParam escapes.
if check != nil {
check.later(func() {
typ.iface()
})
} else if constraint != nil {
typ.iface()
}
return typ
}
@ -65,11 +74,17 @@ func (t *TypeParam) Constraint() Type {
}
// SetConstraint sets the type constraint for t.
//
// SetConstraint should not be called concurrently, but once SetConstraint
// returns the receiver t is safe for concurrent use.
func (t *TypeParam) SetConstraint(bound Type) {
if bound == nil {
panic("nil constraint")
}
t.bound = bound
// iface may mutate t.bound (if bound is not an interface), so ensure that
// this is done before returning.
t.iface()
}
func (t *TypeParam) Underlying() Type {
@ -104,7 +119,6 @@ func (t *TypeParam) iface() *Interface {
}
// If we don't have an interface, wrap constraint into an implicit interface.
// TODO(gri) mark it as implicit - see comment in Checker.bound
if ityp == nil {
ityp = NewInterfaceType(nil, []Type{bound})
ityp.implicit = true