diff options
| author | Damien Neil <dneil@google.com> | 2025-06-05 13:47:06 -0700 |
|---|---|---|
| committer | Gopher Robot <gobot@golang.org> | 2025-06-10 15:02:26 -0700 |
| commit | 4f86f2267167a63b673c4a2a2994e008b32c90ea (patch) | |
| tree | 24dd8afd3e0e54897f199a95a151ccd5be83fab7 /src/runtime | |
| parent | 773701a853a3105696c59c2b92b2eff35e0e055b (diff) | |
| download | go-4f86f2267167a63b673c4a2a2994e008b32c90ea.tar.xz | |
testing/synctest, runtime: avoid panic when using linker-alloc WG from bubble
We associate WaitGroups with synctest bubbles by attaching a
special to the WaitGroup. It is not possible to attach a special
to a linker-allocated value, such as:
var wg sync.WaitGroup
Avoid panicking when accessing a linker-allocated WaitGroup
from a bubble. We have no way to associate these WaitGroups
with a bubble, so just treat them as always unbubbled.
This is probably fine, since the WaitGroup was always
created outside the bubble in this case.
Fixes #74005
Change-Id: Ic71514b0b8d0cecd62e45cc929ffcbeb16f54a55
Reviewed-on: https://go-review.googlesource.com/c/go/+/679695
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Auto-Submit: Damien Neil <dneil@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Diffstat (limited to 'src/runtime')
| -rw-r--r-- | src/runtime/export_test.go | 6 | ||||
| -rw-r--r-- | src/runtime/synctest.go | 29 | ||||
| -rw-r--r-- | src/runtime/synctest_test.go | 12 |
3 files changed, 39 insertions, 8 deletions
diff --git a/src/runtime/export_test.go b/src/runtime/export_test.go index a9cc767e30..83cf301be4 100644 --- a/src/runtime/export_test.go +++ b/src/runtime/export_test.go @@ -1911,3 +1911,9 @@ func (b BitCursor) Write(data *byte, cnt uintptr) { func (b BitCursor) Offset(cnt uintptr) BitCursor { return BitCursor{b: b.b.offset(cnt)} } + +const ( + BubbleAssocUnbubbled = bubbleAssocUnbubbled + BubbleAssocCurrentBubble = bubbleAssocCurrentBubble + BubbleAssocOtherBubble = bubbleAssocOtherBubble +) diff --git a/src/runtime/synctest.go b/src/runtime/synctest.go index 08a0e5d444..16af1209b4 100644 --- a/src/runtime/synctest.go +++ b/src/runtime/synctest.go @@ -363,6 +363,13 @@ type specialBubble struct { bubbleid uint64 } +// Keep these in sync with internal/synctest. +const ( + bubbleAssocUnbubbled = iota // not associated with any bubble + bubbleAssocCurrentBubble // associated with the current bubble + bubbleAssocOtherBubble // associated with a different bubble +) + // getOrSetBubbleSpecial checks the special record for p's bubble membership. // // If add is true and p is not associated with any bubble, @@ -371,10 +378,12 @@ type specialBubble struct { // It returns ok==true if p is associated with bubbleid // (including if a new association was added), // and ok==false if not. -func getOrSetBubbleSpecial(p unsafe.Pointer, bubbleid uint64, add bool) (ok bool) { +func getOrSetBubbleSpecial(p unsafe.Pointer, bubbleid uint64, add bool) (assoc int) { span := spanOfHeap(uintptr(p)) if span == nil { - throw("getOrSetBubbleSpecial on invalid pointer") + // This is probably a package var. + // We can't attach a special to it, so always consider it unbubbled. + return bubbleAssocUnbubbled } // Ensure that the span is swept. @@ -393,7 +402,11 @@ func getOrSetBubbleSpecial(p unsafe.Pointer, bubbleid uint64, add bool) (ok bool // p is already associated with a bubble. // Return true iff it's the same bubble. s := (*specialBubble)((unsafe.Pointer)(*iter)) - ok = s.bubbleid == bubbleid + if s.bubbleid == bubbleid { + assoc = bubbleAssocCurrentBubble + } else { + assoc = bubbleAssocOtherBubble + } } else if add { // p is not associated with a bubble, // and we've been asked to add an association. @@ -404,23 +417,23 @@ func getOrSetBubbleSpecial(p unsafe.Pointer, bubbleid uint64, add bool) (ok bool s.special.next = *iter *iter = (*special)(unsafe.Pointer(s)) spanHasSpecials(span) - ok = true + assoc = bubbleAssocCurrentBubble } else { // p is not associated with a bubble. - ok = false + assoc = bubbleAssocUnbubbled } unlock(&span.speciallock) releasem(mp) - return ok + return assoc } // synctest_associate associates p with the current bubble. // It returns false if p is already associated with a different bubble. // //go:linkname synctest_associate internal/synctest.associate -func synctest_associate(p unsafe.Pointer) (ok bool) { +func synctest_associate(p unsafe.Pointer) int { return getOrSetBubbleSpecial(p, getg().bubble.id, true) } @@ -435,5 +448,5 @@ func synctest_disassociate(p unsafe.Pointer) { // //go:linkname synctest_isAssociated internal/synctest.isAssociated func synctest_isAssociated(p unsafe.Pointer) bool { - return getOrSetBubbleSpecial(p, getg().bubble.id, false) + return getOrSetBubbleSpecial(p, getg().bubble.id, false) == bubbleAssocCurrentBubble } diff --git a/src/runtime/synctest_test.go b/src/runtime/synctest_test.go index 0fdd032fc9..1059d629d3 100644 --- a/src/runtime/synctest_test.go +++ b/src/runtime/synctest_test.go @@ -5,6 +5,8 @@ package runtime_test import ( + "internal/synctest" + "runtime" "testing" ) @@ -15,3 +17,13 @@ func TestSynctest(t *testing.T) { t.Fatalf("output:\n%s\n\nwanted:\n%s", output, want) } } + +// TestSynctestAssocConsts verifies that constants defined +// in both runtime and internal/synctest match. +func TestSynctestAssocConsts(t *testing.T) { + if runtime.BubbleAssocUnbubbled != synctest.Unbubbled || + runtime.BubbleAssocCurrentBubble != synctest.CurrentBubble || + runtime.BubbleAssocOtherBubble != synctest.OtherBubble { + t.Fatal("mismatch: runtime.BubbleAssoc? != synctest.*") + } +} |
