Skip to content

Commit 39fd61d

Browse files
Mark Freemangopherbot
authored andcommitted
go/types, types2: guard Named.underlying with Named.mu
It appears that CL 695977 introduced a data race on Named.underlying. This fixes that race by specifying a new namedState called underlying, which, perhaps unsurprisingly, signals that Named.underlying is populated. Unfortunately, the underlying namedState is independent of the complete namedState (unsurprising since methods and the underlying type are not related). Hence, they cannot be ordered and thus do not fit the current integer-based state representation. To account for combinations of states, we introduce a bit set representation for namedState instead. The namedState field is also renamed to stateMask to reflect this new representation. Methods that operate on the stateMask are adjusted and exposition is added throughout. Fixes #75963 Change-Id: Icfa188ea2fa7916804c06f80668e99176bf4e978 Reviewed-on: https://go-review.googlesource.com/c/go/+/712720 Auto-Submit: Mark Freeman <markfreeman@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Robert Griesemer <gri@google.com>
1 parent 4a0115c commit 39fd61d

File tree

2 files changed

+184
-90
lines changed

2 files changed

+184
-90
lines changed

src/cmd/compile/internal/types2/named.go

Lines changed: 92 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,8 @@ import (
4747
// soon.
4848
//
4949
// We achieve this by tracking state with an atomic state variable, and
50-
// guarding potentially concurrent calculations with a mutex. At any point in
51-
// time this state variable determines which data on N may be accessed. As
52-
// state monotonically progresses, any data available at state M may be
53-
// accessed without acquiring the mutex at state N, provided N >= M.
50+
// guarding potentially concurrent calculations with a mutex. See [stateMask]
51+
// for details.
5452
//
5553
// GLOSSARY: Here are a few terms used in this file to describe Named types:
5654
// - We say that a Named type is "instantiated" if it has been constructed by
@@ -111,13 +109,13 @@ type Named struct {
111109
allowNilRHS bool // same as below, as well as briefly during checking of a type declaration
112110
allowNilUnderlying bool // may be true from creation via [NewNamed] until [Named.SetUnderlying]
113111

114-
underlying Type // underlying type, or nil
115-
inst *instance // information for instantiated types; nil otherwise
112+
inst *instance // information for instantiated types; nil otherwise
116113

117-
mu sync.Mutex // guards all fields below
118-
state_ uint32 // the current state of this type; must only be accessed atomically
119-
fromRHS Type // the declaration RHS this type is derived from
120-
tparams *TypeParamList // type parameters, or nil
114+
mu sync.Mutex // guards all fields below
115+
state_ uint32 // the current state of this type; must only be accessed atomically or when mu is held
116+
fromRHS Type // the declaration RHS this type is derived from
117+
tparams *TypeParamList // type parameters, or nil
118+
underlying Type // underlying type, or nil
121119

122120
// methods declared for this type (not the method set of this type)
123121
// Signatures are type-checked lazily.
@@ -139,15 +137,43 @@ type instance struct {
139137
ctxt *Context // local Context; set to nil after full expansion
140138
}
141139

142-
// namedState represents the possible states that a named type may assume.
143-
type namedState uint32
140+
// stateMask represents each state in the lifecycle of a named type.
141+
//
142+
// Each named type begins in the unresolved state. A named type may transition to a new state
143+
// according to the below diagram:
144+
//
145+
// unresolved
146+
// loaded
147+
// resolved
148+
// └── complete
149+
// └── underlying
150+
//
151+
// That is, descent down the tree is mostly linear (unresolved through resolved), except upon
152+
// reaching the leaves (complete and underlying). A type may occupy any combination of the
153+
// leaf states at once (they are independent states).
154+
//
155+
// To represent this independence, the set of active states is represented with a bit set. State
156+
// transitions are monotonic. Once a state bit is set, it remains set.
157+
//
158+
// The above constraints significantly narrow the possible bit sets for a named type. With bits
159+
// set left-to-right, they are:
160+
//
161+
// 0000 | unresolved
162+
// 1000 | loaded
163+
// 1100 | resolved, which implies loaded
164+
// 1110 | completed, which implies resolved (which in turn implies loaded)
165+
// 1101 | underlying, which implies resolved ...
166+
// 1111 | both completed and underlying which implies resolved ...
167+
//
168+
// To read the state of a named type, use [Named.stateHas]; to write, use [Named.setState].
169+
type stateMask uint32
144170

145-
// Note: the order of states is relevant
146171
const (
147-
unresolved namedState = iota // type parameters, RHS, underlying, and methods might be unavailable
148-
resolved // resolve has run; methods might be unexpanded (for instances)
149-
loaded // loader has run; constraints might be unexpanded (for generic types)
150-
complete // all data is known
172+
// before resolved, type parameters, RHS, underlying, and methods might be unavailable
173+
resolved stateMask = 1 << iota // methods might be unexpanded (for instances)
174+
complete // methods are all expanded (for instances)
175+
loaded // methods are available, but constraints might be unexpanded (for generic types)
176+
underlying // underlying type is available
151177
)
152178

153179
// NewNamed returns a new named type for the given type name, underlying type, and associated methods.
@@ -188,7 +214,7 @@ func NewNamed(obj *TypeName, underlying Type, methods []*Func) *Named {
188214
// All others:
189215
// Effectively, nothing happens.
190216
func (n *Named) resolve() *Named {
191-
if n.state() >= resolved { // avoid locking below
217+
if n.stateHas(resolved | loaded) { // avoid locking below
192218
return n
193219
}
194220

@@ -197,10 +223,14 @@ func (n *Named) resolve() *Named {
197223
n.mu.Lock()
198224
defer n.mu.Unlock()
199225

200-
if n.state() >= resolved {
226+
// only atomic for consistency; we are holding the mutex
227+
if n.stateHas(resolved | loaded) {
201228
return n
202229
}
203230

231+
// underlying comes after resolving, do not set it
232+
defer (func() { assert(!n.stateHas(underlying)) })()
233+
204234
if n.inst != nil {
205235
assert(n.fromRHS == nil) // instantiated types are not declared types
206236
assert(n.loader == nil) // cannot import an instantiation
@@ -212,7 +242,7 @@ func (n *Named) resolve() *Named {
212242
n.tparams = orig.tparams
213243

214244
if len(orig.methods) == 0 {
215-
n.setState(complete) // nothing further to do
245+
n.setState(resolved | complete) // nothing further to do
216246
n.inst.ctxt = nil
217247
} else {
218248
n.setState(resolved)
@@ -239,27 +269,24 @@ func (n *Named) resolve() *Named {
239269
n.methods = methods
240270

241271
n.setState(loaded) // avoid deadlock calling delayed functions
242-
243272
for _, f := range delayed {
244273
f()
245274
}
246275
}
247276

248-
assert(n.fromRHS != nil || n.allowNilRHS)
249-
assert(n.underlying == nil) // underlying comes after resolving
250-
n.setState(complete)
277+
n.setState(resolved | complete)
251278
return n
252279
}
253280

254-
// state atomically accesses the current state of the receiver.
255-
func (n *Named) state() namedState {
256-
return namedState(atomic.LoadUint32(&n.state_))
281+
// stateHas atomically determines whether the current state includes any active bit in sm.
282+
func (n *Named) stateHas(sm stateMask) bool {
283+
return atomic.LoadUint32(&n.state_)&uint32(sm) != 0
257284
}
258285

259-
// setState atomically stores the given state for n.
286+
// setState atomically sets the current state to include each active bit in sm.
260287
// Must only be called while holding n.mu.
261-
func (n *Named) setState(state namedState) {
262-
atomic.StoreUint32(&n.state_, uint32(state))
288+
func (n *Named) setState(sm stateMask) {
289+
atomic.OrUint32(&n.state_, uint32(sm))
263290
}
264291

265292
// newNamed is like NewNamed but with a *Checker receiver.
@@ -369,7 +396,7 @@ func (t *Named) NumMethods() int {
369396
func (t *Named) Method(i int) *Func {
370397
t.resolve()
371398

372-
if t.state() >= complete {
399+
if t.stateHas(complete) {
373400
return t.methods[i]
374401
}
375402

@@ -461,20 +488,25 @@ func (t *Named) expandMethod(i int) *Func {
461488

462489
// SetUnderlying sets the underlying type and marks t as complete.
463490
// t must not have type arguments.
464-
func (t *Named) SetUnderlying(underlying Type) {
491+
func (t *Named) SetUnderlying(u Type) {
465492
assert(t.inst == nil)
466-
if underlying == nil {
493+
if u == nil {
467494
panic("underlying type must not be nil")
468495
}
469-
if asNamed(underlying) != nil {
496+
if asNamed(u) != nil {
470497
panic("underlying type must not be *Named")
471498
}
472-
// Invariant: Presence of underlying type implies it was resolved.
473-
t.fromRHS = underlying
499+
// be careful to uphold the state invariants
500+
t.mu.Lock()
501+
defer t.mu.Unlock()
502+
503+
t.fromRHS = u
474504
t.allowNilRHS = false
475-
t.resolve()
476-
t.underlying = underlying
505+
t.setState(resolved | complete) // TODO(markfreeman): Why complete?
506+
507+
t.underlying = u
477508
t.allowNilUnderlying = false
509+
t.setState(underlying)
478510
}
479511

480512
// AddMethod adds method m unless it is already in the method list.
@@ -553,9 +585,10 @@ func (t *Named) String() string { return TypeString(t, nil) }
553585
// cycle is found, the result is Typ[Invalid]; if n.check != nil, the
554586
// cycle is also reported.
555587
func (n *Named) under() Type {
556-
assert(n.state() >= resolved)
588+
assert(n.stateHas(resolved))
557589

558-
if n.underlying != nil {
590+
// optimization for likely case
591+
if n.stateHas(underlying) {
559592
return n.underlying
560593
}
561594

@@ -569,19 +602,34 @@ func (n *Named) under() Type {
569602
switch t := rhs.(type) {
570603
case nil:
571604
u = Typ[Invalid]
605+
572606
case *Alias:
573607
rhs = unalias(t)
608+
574609
case *Named:
575610
if i, ok := seen[t]; ok {
576611
n.check.cycleError(path[i:], firstInSrc(path[i:]))
577612
u = Typ[Invalid]
578613
break
579614
}
615+
616+
// acquire the lock without checking; performance is probably fine
617+
t.resolve()
618+
t.mu.Lock()
619+
defer t.mu.Unlock()
620+
621+
// t.underlying might have been set while we were locking
622+
if t.stateHas(underlying) {
623+
u = t.underlying
624+
break
625+
}
626+
580627
seen[t] = len(seen)
581628
path = append(path, t.obj)
582-
t.resolve()
629+
583630
assert(t.fromRHS != nil || t.allowNilRHS)
584631
rhs = t.fromRHS
632+
585633
default:
586634
u = rhs // any type literal works
587635
}
@@ -590,6 +638,7 @@ func (n *Named) under() Type {
590638
// go back up the chain
591639
for t := range seen {
592640
t.underlying = u
641+
t.setState(underlying)
593642
}
594643

595644
return u
@@ -659,7 +708,8 @@ func (n *Named) expandRHS() (rhs Type) {
659708
}()
660709
}
661710

662-
assert(n.state() == unresolved)
711+
assert(!n.stateHas(resolved))
712+
assert(n.inst.orig.stateHas(resolved | loaded))
663713

664714
if n.inst.ctxt == nil {
665715
n.inst.ctxt = NewContext()
@@ -668,9 +718,6 @@ func (n *Named) expandRHS() (rhs Type) {
668718
ctxt := n.inst.ctxt
669719
orig := n.inst.orig
670720

671-
assert(orig.state() >= resolved)
672-
assert(orig.fromRHS != nil)
673-
674721
targs := n.inst.targs
675722
tpars := orig.tparams
676723

0 commit comments

Comments
 (0)