Skip to content

Commit 16d3040

Browse files
committed
maps: fix aliasing problems with Clone
Make sure to alloc+copy large keys and values instead of aliasing them, when they might be updated by a future assignment. Fixes #64474 Change-Id: Ie2226a81cf3897e4e2ee24472f2966d397ace53f Reviewed-on: https://go-review.googlesource.com/c/go/+/546515 Reviewed-by: Cherry Mui <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Keith Randall <[email protected]> Reviewed-by: Mauri de Souza Meneguzzo <[email protected]>
1 parent 098f059 commit 16d3040

File tree

2 files changed

+76
-4
lines changed

2 files changed

+76
-4
lines changed

src/maps/maps_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,3 +182,61 @@ func TestCloneWithMapAssign(t *testing.T) {
182182
}
183183
}
184184
}
185+
186+
func TestCloneLarge(t *testing.T) {
187+
// See issue 64474.
188+
type K [17]float64 // > 128 bytes
189+
type V [17]float64
190+
191+
var zero float64
192+
negZero := -zero
193+
194+
for tst := 0; tst < 3; tst++ {
195+
// Initialize m with a key and value.
196+
m := map[K]V{}
197+
var k1 K
198+
var v1 V
199+
m[k1] = v1
200+
201+
switch tst {
202+
case 0: // nothing, just a 1-entry map
203+
case 1:
204+
// Add more entries to make it 2 buckets
205+
// 1 entry already
206+
// 7 more fill up 1 bucket
207+
// 1 more to grow to 2 buckets
208+
for i := 0; i < 7+1; i++ {
209+
m[K{float64(i) + 1}] = V{}
210+
}
211+
case 2:
212+
// Capture the map mid-grow
213+
// 1 entry already
214+
// 7 more fill up 1 bucket
215+
// 5 more (13 total) fill up 2 buckets
216+
// 13 more (26 total) fill up 4 buckets
217+
// 1 more to start the 4->8 bucket grow
218+
for i := 0; i < 7+5+13+1; i++ {
219+
m[K{float64(i) + 1}] = V{}
220+
}
221+
}
222+
223+
// Clone m, which should freeze the map's contents.
224+
c := Clone(m)
225+
226+
// Update m with new key and value.
227+
k2, v2 := k1, v1
228+
k2[0] = negZero
229+
v2[0] = 1.0
230+
m[k2] = v2
231+
232+
// Make sure c still has its old key and value.
233+
for k, v := range c {
234+
if math.Signbit(k[0]) {
235+
t.Errorf("tst%d: sign bit of key changed; got %v want %v", tst, k, k1)
236+
}
237+
if v != v1 {
238+
t.Errorf("tst%d: value changed; got %v want %v", tst, v, v1)
239+
}
240+
}
241+
}
242+
}

src/runtime/map.go

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1480,12 +1480,24 @@ func moveToBmap(t *maptype, h *hmap, dst *bmap, pos int, src *bmap) (*bmap, int)
14801480

14811481
dst.tophash[pos] = src.tophash[i]
14821482
if t.IndirectKey() {
1483-
*(*unsafe.Pointer)(dstK) = *(*unsafe.Pointer)(srcK)
1483+
srcK = *(*unsafe.Pointer)(srcK)
1484+
if t.NeedKeyUpdate() {
1485+
kStore := newobject(t.Key)
1486+
typedmemmove(t.Key, kStore, srcK)
1487+
srcK = kStore
1488+
}
1489+
// Note: if NeedKeyUpdate is false, then the memory
1490+
// used to store the key is immutable, so we can share
1491+
// it between the original map and its clone.
1492+
*(*unsafe.Pointer)(dstK) = srcK
14841493
} else {
14851494
typedmemmove(t.Key, dstK, srcK)
14861495
}
14871496
if t.IndirectElem() {
1488-
*(*unsafe.Pointer)(dstEle) = *(*unsafe.Pointer)(srcEle)
1497+
srcEle = *(*unsafe.Pointer)(srcEle)
1498+
eStore := newobject(t.Elem)
1499+
typedmemmove(t.Elem, eStore, srcEle)
1500+
*(*unsafe.Pointer)(dstEle) = eStore
14891501
} else {
14901502
typedmemmove(t.Elem, dstEle, srcEle)
14911503
}
@@ -1509,14 +1521,14 @@ func mapclone2(t *maptype, src *hmap) *hmap {
15091521
fatal("concurrent map clone and map write")
15101522
}
15111523

1512-
if src.B == 0 {
1524+
if src.B == 0 && !(t.IndirectKey() && t.NeedKeyUpdate()) && !t.IndirectElem() {
1525+
// Quick copy for small maps.
15131526
dst.buckets = newobject(t.Bucket)
15141527
dst.count = src.count
15151528
typedmemmove(t.Bucket, dst.buckets, src.buckets)
15161529
return dst
15171530
}
15181531

1519-
//src.B != 0
15201532
if dst.B == 0 {
15211533
dst.buckets = newobject(t.Bucket)
15221534
}
@@ -1564,6 +1576,8 @@ func mapclone2(t *maptype, src *hmap) *hmap {
15641576
continue
15651577
}
15661578

1579+
// oldB < dst.B, so a single source bucket may go to multiple destination buckets.
1580+
// Process entries one at a time.
15671581
for srcBmap != nil {
15681582
// move from oldBlucket to new bucket
15691583
for i := uintptr(0); i < bucketCnt; i++ {

0 commit comments

Comments
 (0)