Skip to content

Commit 453a721

Browse files
committed
arenaskl,batchskl: add test ensuring node contains no pointer types
Add a test asserting that the node structs used in both skiplist implementations does not contain any pointer types. We reuse memory backing both of these types without zeroing it. If we introduce a pointer type, we could expose ourselves to an issue similar to the one fixed in #5085: garbage memory prior could be misinterpreted as a pointer value before the pointer value has been appropriately initialized.
1 parent 258531b commit 453a721

File tree

3 files changed

+94
-0
lines changed

3 files changed

+94
-0
lines changed

internal/arenaskl/skl_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,15 @@ import (
2222
"encoding/binary"
2323
"fmt"
2424
"math/rand/v2"
25+
"reflect"
2526
"strconv"
2627
"sync"
2728
"sync/atomic"
2829
"testing"
2930
"time"
3031

3132
"github.com/cockroachdb/pebble/internal/base"
33+
"github.com/cockroachdb/pebble/internal/testutils"
3234
"github.com/stretchr/testify/require"
3335
)
3436

@@ -77,6 +79,15 @@ func lengthRev(s *Skiplist) int {
7779
return count
7880
}
7981

82+
// TestNoPointers tests that the node struct does not contain any pointers. No
83+
// struct that is backed by the arena may contain pointers (at least without
84+
// zeroing the backing memory) *before* type casting the pointer. Otherwise, the
85+
// Go GC may observe the pointer (while a GC write barrier is in effect) and
86+
// complain that the uninitialized value is a bad pointer. See 273e2665.
87+
func TestNoPointers(t *testing.T) {
88+
require.False(t, testutils.AnyPointers(reflect.TypeOf(node{})))
89+
}
90+
8091
func TestEmpty(t *testing.T) {
8192
key := makeKey("aaa")
8293
l := NewSkiplist(newArena(arenaSize), bytes.Compare)

internal/batchskl/skl_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@ import (
2121
"encoding/binary"
2222
"fmt"
2323
"math/rand/v2"
24+
"reflect"
2425
"testing"
2526
"time"
2627

2728
"github.com/cockroachdb/errors"
2829
"github.com/cockroachdb/pebble/internal/base"
30+
"github.com/cockroachdb/pebble/internal/testutils"
2931
"github.com/stretchr/testify/require"
3032
)
3133

@@ -82,6 +84,15 @@ func newTestSkiplist(storage *testStorage) *Skiplist {
8284
base.DefaultComparer.AbbreviatedKey)
8385
}
8486

87+
// TestNoPointers tests that the node struct does not contain any pointers. No
88+
// struct that is backed by the arena may contain pointers (at least without
89+
// zeroing the backing memory) *before* type casting the pointer. Otherwise, the
90+
// Go GC may observe the pointer (while a GC write barrier is in effect) and
91+
// complain that the uninitialized value is a bad pointer. See 273e2665.
92+
func TestNoPointers(t *testing.T) {
93+
require.False(t, testutils.AnyPointers(reflect.TypeOf(node{})))
94+
}
95+
8596
func TestEmpty(t *testing.T) {
8697
key := makeKey("aaa")
8798
l := newTestSkiplist(&testStorage{})

internal/testutils/reflect.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright 2024 The LevelDB-Go and Pebble Authors. All rights reserved. Use
2+
// of this source code is governed by a BSD-style license that can be found in
3+
// the LICENSE file.
4+
5+
package testutils
6+
7+
import (
8+
"fmt"
9+
"reflect"
10+
)
11+
12+
// AnyPointers returns true if the provided type contains any pointers.
13+
func AnyPointers(typ reflect.Type) bool {
14+
kind := typ.Kind()
15+
switch kindPointers[kind] {
16+
case kindHasPointer:
17+
return true
18+
case kindNoPointer:
19+
return false
20+
}
21+
switch kind {
22+
case reflect.Struct:
23+
for i := range typ.NumField() {
24+
if AnyPointers(typ.Field(i).Type) {
25+
return true
26+
}
27+
}
28+
return false
29+
case reflect.Array:
30+
return AnyPointers(typ.Elem())
31+
default:
32+
panic(fmt.Sprintf("unexpected kind: %s", kind))
33+
}
34+
}
35+
36+
type anyPointers int8
37+
38+
const (
39+
kindNoPointer anyPointers = iota
40+
kindHasPointer
41+
kindMaybeHasPointer
42+
)
43+
44+
var kindPointers = []anyPointers{
45+
reflect.Invalid: kindNoPointer,
46+
reflect.Bool: kindNoPointer,
47+
reflect.Int: kindNoPointer,
48+
reflect.Int8: kindNoPointer,
49+
reflect.Int16: kindNoPointer,
50+
reflect.Int32: kindNoPointer,
51+
reflect.Int64: kindNoPointer,
52+
reflect.Uint: kindNoPointer,
53+
reflect.Uint8: kindNoPointer,
54+
reflect.Uint16: kindNoPointer,
55+
reflect.Uint32: kindNoPointer,
56+
reflect.Uint64: kindNoPointer,
57+
reflect.Uintptr: kindNoPointer,
58+
reflect.Float32: kindNoPointer,
59+
reflect.Float64: kindNoPointer,
60+
reflect.Complex64: kindNoPointer,
61+
reflect.Complex128: kindNoPointer,
62+
reflect.Array: kindMaybeHasPointer,
63+
reflect.Chan: kindHasPointer,
64+
reflect.Func: kindHasPointer,
65+
reflect.Interface: kindHasPointer,
66+
reflect.Map: kindHasPointer,
67+
reflect.Pointer: kindHasPointer,
68+
reflect.Slice: kindHasPointer,
69+
reflect.String: kindHasPointer,
70+
reflect.Struct: kindMaybeHasPointer,
71+
reflect.UnsafePointer: kindHasPointer,
72+
}

0 commit comments

Comments
 (0)