Skip to content

Commit e21dc70

Browse files
Deleplacegopherbot
authored andcommitted
slices: zero the slice elements discarded by Delete, DeleteFunc, Compact, CompactFunc, Replace.
To avoid memory leaks in slices that contain pointers, clear the elements between the new length and the original length. Fixes #63393 Change-Id: Ic65709726f4479d70c6bce14aa367feb753d41da Reviewed-on: https://go-review.googlesource.com/c/go/+/541477 Reviewed-by: Keith Randall <khr@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Keith Randall <khr@golang.org> Reviewed-by: Keith Randall <khr@golang.org>
1 parent e14b96c commit e21dc70

File tree

2 files changed

+144
-12
lines changed

2 files changed

+144
-12
lines changed

src/slices/slices.go

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -213,23 +213,21 @@ func Insert[S ~[]E, E any](s S, i int, v ...E) S {
213213

214214
// Delete removes the elements s[i:j] from s, returning the modified slice.
215215
// Delete panics if j > len(s) or s[i:j] is not a valid slice of s.
216-
// Delete is O(len(s)-j), so if many items must be deleted, it is better to
216+
// Delete is O(len(s)-i), so if many items must be deleted, it is better to
217217
// make a single call deleting them all together than to delete one at a time.
218-
// Delete might not modify the elements s[len(s)-(j-i):len(s)]. If those
219-
// elements contain pointers you might consider zeroing those elements so that
220-
// objects they reference can be garbage collected.
218+
// Delete zeroes the elements s[len(s)-(j-i):len(s)].
221219
func Delete[S ~[]E, E any](s S, i, j int) S {
222220
_ = s[i:j] // bounds check
223221

224-
return append(s[:i], s[j:]...)
222+
oldlen := len(s)
223+
s = append(s[:i], s[j:]...)
224+
clear(s[len(s):oldlen]) // zero/nil out the obsolete elements, for GC
225+
return s
225226
}
226227

227228
// DeleteFunc removes any elements from s for which del returns true,
228229
// returning the modified slice.
229-
// When DeleteFunc removes m elements, it might not modify the elements
230-
// s[len(s)-m:len(s)]. If those elements contain pointers you might consider
231-
// zeroing those elements so that objects they reference can be garbage
232-
// collected.
230+
// DeleteFunc zeroes the elements between the new length and the original length.
233231
func DeleteFunc[S ~[]E, E any](s S, del func(E) bool) S {
234232
i := IndexFunc(s, del)
235233
if i == -1 {
@@ -242,12 +240,14 @@ func DeleteFunc[S ~[]E, E any](s S, del func(E) bool) S {
242240
i++
243241
}
244242
}
243+
clear(s[i:]) // zero/nil out the obsolete elements, for GC
245244
return s[:i]
246245
}
247246

248247
// Replace replaces the elements s[i:j] by the given v, and returns the
249248
// modified slice.
250249
// Replace panics if j > len(s) or s[i:j] is not a valid slice of s.
250+
// When len(v) < (j-i), Replace zeroes the elements between the new length and the original length.
251251
func Replace[S ~[]E, E any](s S, i, j int, v ...E) S {
252252
_ = s[i:j] // bounds check
253253

@@ -273,6 +273,7 @@ func Replace[S ~[]E, E any](s S, i, j int, v ...E) S {
273273
// Easy, as v fits in the deleted portion.
274274
copy(r[i:], v)
275275
copy(r[i+len(v):], s[j:])
276+
clear(s[tot:]) // zero/nil out the obsolete elements, for GC
276277
return r
277278
}
278279

@@ -343,9 +344,7 @@ func Clone[S ~[]E, E any](s S) S {
343344
// This is like the uniq command found on Unix.
344345
// Compact modifies the contents of the slice s and returns the modified slice,
345346
// which may have a smaller length.
346-
// When Compact discards m elements in total, it might not modify the elements
347-
// s[len(s)-m:len(s)]. If those elements contain pointers you might consider
348-
// zeroing those elements so that objects they reference can be garbage collected.
347+
// Compact zeroes the elements between the new length and the original length.
349348
func Compact[S ~[]E, E comparable](s S) S {
350349
if len(s) < 2 {
351350
return s
@@ -359,11 +358,13 @@ func Compact[S ~[]E, E comparable](s S) S {
359358
i++
360359
}
361360
}
361+
clear(s[i:]) // zero/nil out the obsolete elements, for GC
362362
return s[:i]
363363
}
364364

365365
// CompactFunc is like [Compact] but uses an equality function to compare elements.
366366
// For runs of elements that compare equal, CompactFunc keeps the first one.
367+
// CompactFunc zeroes the elements between the new length and the original length.
367368
func CompactFunc[S ~[]E, E any](s S, eq func(E, E) bool) S {
368369
if len(s) < 2 {
369370
return s
@@ -377,6 +378,7 @@ func CompactFunc[S ~[]E, E any](s S, eq func(E, E) bool) S {
377378
i++
378379
}
379380
}
381+
clear(s[i:]) // zero/nil out the obsolete elements, for GC
380382
return s[:i]
381383
}
382384

src/slices/slices_test.go

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,39 @@ func TestDeletePanics(t *testing.T) {
681681
}
682682
}
683683

684+
func TestDeleteClearTail(t *testing.T) {
685+
mem := []*int{new(int), new(int), new(int), new(int), new(int), new(int)}
686+
s := mem[0:5] // there is 1 element beyond len(s), within cap(s)
687+
688+
s = Delete(s, 2, 4)
689+
690+
if mem[3] != nil || mem[4] != nil {
691+
// Check that potential memory leak is avoided
692+
t.Errorf("Delete: want nil discarded elements, got %v, %v", mem[3], mem[4])
693+
}
694+
if mem[5] == nil {
695+
t.Errorf("Delete: want unchanged elements beyond original len, got nil")
696+
}
697+
}
698+
699+
func TestDeleteFuncClearTail(t *testing.T) {
700+
mem := []*int{new(int), new(int), new(int), new(int), new(int), new(int)}
701+
*mem[2], *mem[3] = 42, 42
702+
s := mem[0:5] // there is 1 element beyond len(s), within cap(s)
703+
704+
s = DeleteFunc(s, func(i *int) bool {
705+
return i != nil && *i == 42
706+
})
707+
708+
if mem[3] != nil || mem[4] != nil {
709+
// Check that potential memory leak is avoided
710+
t.Errorf("DeleteFunc: want nil discarded elements, got %v, %v", mem[3], mem[4])
711+
}
712+
if mem[5] == nil {
713+
t.Errorf("DeleteFunc: want unchanged elements beyond original len, got nil")
714+
}
715+
}
716+
684717
func TestClone(t *testing.T) {
685718
s1 := []int{1, 2, 3}
686719
s2 := Clone(s1)
@@ -784,6 +817,53 @@ func TestCompactFunc(t *testing.T) {
784817
}
785818
}
786819

820+
func TestCompactClearTail(t *testing.T) {
821+
one, two, three, four := 1, 2, 3, 4
822+
mem := []*int{&one, &one, &two, &two, &three, &four}
823+
s := mem[0:5] // there is 1 element beyond len(s), within cap(s)
824+
copy := Clone(s)
825+
826+
s = Compact(s)
827+
828+
if want := []*int{&one, &two, &three}; !Equal(s, want) {
829+
t.Errorf("Compact(%v) = %v, want %v", copy, s, want)
830+
}
831+
832+
if mem[3] != nil || mem[4] != nil {
833+
// Check that potential memory leak is avoided
834+
t.Errorf("Compact: want nil discarded elements, got %v, %v", mem[3], mem[4])
835+
}
836+
if mem[5] != &four {
837+
t.Errorf("Compact: want unchanged element beyond original len, got %v", mem[5])
838+
}
839+
}
840+
841+
func TestCompactFuncClearTail(t *testing.T) {
842+
a, b, c, d, e, f := 1, 1, 2, 2, 3, 4
843+
mem := []*int{&a, &b, &c, &d, &e, &f}
844+
s := mem[0:5] // there is 1 element beyond len(s), within cap(s)
845+
copy := Clone(s)
846+
847+
s = CompactFunc(s, func(x, y *int) bool {
848+
if x == nil || y == nil {
849+
return x == y
850+
}
851+
return *x == *y
852+
})
853+
854+
if want := []*int{&a, &c, &e}; !Equal(s, want) {
855+
t.Errorf("CompactFunc(%v) = %v, want %v", copy, s, want)
856+
}
857+
858+
if mem[3] != nil || mem[4] != nil {
859+
// Check that potential memory leak is avoided
860+
t.Errorf("CompactFunc: want nil discarded elements, got %v, %v", mem[3], mem[4])
861+
}
862+
if mem[5] != &f {
863+
t.Errorf("CompactFunc: want unchanged elements beyond original len, got %v", mem[5])
864+
}
865+
}
866+
787867
func BenchmarkCompactFunc_Large(b *testing.B) {
788868
type Large [4 * 1024]byte
789869

@@ -954,6 +1034,56 @@ func TestReplacePanics(t *testing.T) {
9541034
}
9551035
}
9561036

1037+
func TestReplaceGrow(t *testing.T) {
1038+
// When Replace needs to allocate a new slice, we want the original slice
1039+
// to not be changed.
1040+
a, b, c, d, e, f := 1, 2, 3, 4, 5, 6
1041+
mem := []*int{&a, &b, &c, &d, &e, &f}
1042+
memcopy := Clone(mem)
1043+
s := mem[0:5] // there is 1 element beyond len(s), within cap(s)
1044+
copy := Clone(s)
1045+
original := s
1046+
1047+
// The new elements don't fit within cap(s), so Replace will allocate.
1048+
z := 99
1049+
s = Replace(s, 1, 3, &z, &z, &z, &z)
1050+
1051+
if want := []*int{&a, &z, &z, &z, &z, &d, &e}; !Equal(s, want) {
1052+
t.Errorf("Replace(%v, 1, 3, %v, %v, %v, %v) = %v, want %v", copy, &z, &z, &z, &z, s, want)
1053+
}
1054+
1055+
if !Equal(original, copy) {
1056+
t.Errorf("original slice has changed, got %v, want %v", original, copy)
1057+
}
1058+
1059+
if !Equal(mem, memcopy) {
1060+
// Changing the original tail s[len(s):cap(s)] is unwanted
1061+
t.Errorf("original backing memory has changed, got %v, want %v", mem, memcopy)
1062+
}
1063+
}
1064+
1065+
func TestReplaceClearTail(t *testing.T) {
1066+
a, b, c, d, e, f := 1, 2, 3, 4, 5, 6
1067+
mem := []*int{&a, &b, &c, &d, &e, &f}
1068+
s := mem[0:5] // there is 1 element beyond len(s), within cap(s)
1069+
copy := Clone(s)
1070+
1071+
y, z := 8, 9
1072+
s = Replace(s, 1, 4, &y, &z)
1073+
1074+
if want := []*int{&a, &y, &z, &e}; !Equal(s, want) {
1075+
t.Errorf("Replace(%v) = %v, want %v", copy, s, want)
1076+
}
1077+
1078+
if mem[4] != nil {
1079+
// Check that potential memory leak is avoided
1080+
t.Errorf("Replace: want nil discarded element, got %v", mem[4])
1081+
}
1082+
if mem[5] != &f {
1083+
t.Errorf("Replace: want unchanged elements beyond original len, got %v", mem[5])
1084+
}
1085+
}
1086+
9571087
func TestReplaceOverlap(t *testing.T) {
9581088
const N = 10
9591089
a := make([]int, N)

0 commit comments

Comments
 (0)