Skip to content

Commit 89ce1f0

Browse files
committed
mem: add Buffer.Slice()
Works just like Go's slice slicing.
1 parent 043a005 commit 89ce1f0

File tree

2 files changed

+187
-10
lines changed

2 files changed

+187
-10
lines changed

mem/buffers.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ type Buffer interface {
5353
Free()
5454
// Len returns the Buffer's size.
5555
Len() int
56+
// Slice returns a new Buffer that is a view into this buffer's data
57+
// from [start:end). The buffer is not modified. Panics if the buffer
58+
// has been freed or if start/end are out of bounds.
59+
Slice(start, end int) Buffer
5660

5761
split(n int) (left, right Buffer)
5862
read(buf []byte) (int, Buffer)
@@ -180,6 +184,22 @@ func (b *buffer) Len() int {
180184
return len(b.ReadOnlyData())
181185
}
182186

187+
func (b *buffer) Slice(start, end int) Buffer {
188+
if b.rootBuf == nil || b.rootBuf.refs.Add(1) <= 1 {
189+
panic("Cannot slice freed buffer")
190+
}
191+
192+
if start < 0 || end > len(b.data) || start > end {
193+
panic(fmt.Sprintf("slice bounds out of range [%d:%d] with length %d", start, end, len(b.data)))
194+
}
195+
196+
s := newBuffer()
197+
s.data = b.data[start:end]
198+
s.rootBuf = b.rootBuf
199+
s.refs.Store(1)
200+
return s
201+
}
202+
183203
func (b *buffer) split(n int) (Buffer, Buffer) {
184204
if b.rootBuf == nil || b.rootBuf.refs.Add(1) <= 1 {
185205
panic("Cannot split freed buffer")
@@ -240,6 +260,13 @@ func (e emptyBuffer) Len() int {
240260
return 0
241261
}
242262

263+
func (e emptyBuffer) Slice(start, end int) Buffer {
264+
if start != 0 || end != 0 {
265+
panic(fmt.Sprintf("slice bounds out of range [%d:%d] with length 0", start, end))
266+
}
267+
return e
268+
}
269+
243270
func (e emptyBuffer) split(int) (left, right Buffer) {
244271
return e, e
245272
}
@@ -264,6 +291,9 @@ func (s SliceBuffer) Free() {}
264291
// Len is a noop implementation of Len.
265292
func (s SliceBuffer) Len() int { return len(s) }
266293

294+
// Slice returns a new SliceBuffer that is a view into the receiver from [start:end).
295+
func (s SliceBuffer) Slice(start, end int) Buffer { return s[start:end] }
296+
267297
func (s SliceBuffer) split(n int) (left, right Buffer) {
268298
return s[:n], s[n:]
269299
}

mem/buffers_test.go

Lines changed: 157 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ package mem_test
2020

2121
import (
2222
"bytes"
23+
"fmt"
2324
"testing"
2425

2526
"google.golang.org/grpc/internal"
@@ -265,21 +266,15 @@ func (s) TestBuffer_Split(t *testing.T) {
265266
}
266267
freed = true
267268
}))
268-
checkBufData := func(b mem.Buffer, expected []byte) {
269-
t.Helper()
270-
if !bytes.Equal(b.ReadOnlyData(), expected) {
271-
t.Fatalf("Buffer did not contain expected data %v, got %v", expected, b.ReadOnlyData())
272-
}
273-
}
274269

275270
buf, split1 := mem.SplitUnsafe(buf, 2)
276-
checkBufData(buf, data[:2])
277-
checkBufData(split1, data[2:])
271+
checkBufData(t, buf, data[:2])
272+
checkBufData(t, split1, data[2:])
278273

279274
// Check that splitting the buffer more than once works as intended.
280275
split1, split2 := mem.SplitUnsafe(split1, 1)
281-
checkBufData(split1, data[2:3])
282-
checkBufData(split2, data[3:])
276+
checkBufData(t, split1, data[2:3])
277+
checkBufData(t, split2, data[3:])
283278

284279
// If any of the following frees actually free the buffer, the test will fail.
285280
buf.Free()
@@ -293,6 +288,158 @@ func (s) TestBuffer_Split(t *testing.T) {
293288
}
294289
}
295290

291+
func (s) TestBuffer_Slice(t *testing.T) {
292+
ready := false
293+
freed := false
294+
data := []byte{1, 2, 3, 4}
295+
buf := mem.NewBuffer(&data, poolFunc(func(*[]byte) {
296+
if !ready {
297+
t.Fatalf("Freed too early")
298+
}
299+
freed = true
300+
}))
301+
302+
// Slice the buffer and verify the data.
303+
slice1 := buf.Slice(1, 3)
304+
checkBufData(t, slice1, data[1:3])
305+
306+
// Verify the original buffer is not modified.
307+
checkBufData(t, buf, data)
308+
309+
// Slice the slice.
310+
slice2 := slice1.Slice(0, 1)
311+
checkBufData(t, slice2, data[1:2])
312+
313+
// Free original and first slice — root should not be freed yet.
314+
buf.Free()
315+
slice1.Free()
316+
317+
// The last slice keeps the root alive.
318+
checkBufData(t, slice2, data[1:2])
319+
320+
ready = true
321+
slice2.Free()
322+
323+
if !freed {
324+
t.Fatalf("Buffer never freed")
325+
}
326+
}
327+
328+
func (s) TestBuffer_SliceAfterFree(t *testing.T) {
329+
buf := newBuffer([]byte("abcd"), mem.NopBufferPool{})
330+
buf.Free()
331+
defer checkForPanic(t, "Cannot slice freed buffer")
332+
buf.Slice(0, 2)
333+
}
334+
335+
func (s) TestBuffer_SliceBasic(t *testing.T) {
336+
type sliceCase struct {
337+
start, end int
338+
want []byte
339+
}
340+
cases := []sliceCase{
341+
{1, 3, []byte{2, 3}},
342+
{0, 4, []byte{1, 2, 3, 4}},
343+
{0, 0, []byte{}},
344+
{4, 4, []byte{}},
345+
}
346+
ctors := map[string]func() mem.Buffer{
347+
"pooled": newPooledBuffer,
348+
"slice": newSliceBuf,
349+
}
350+
for name, newBuf := range ctors {
351+
for _, tc := range cases {
352+
t.Run(fmt.Sprintf("%s[%d:%d]", name, tc.start, tc.end), func(t *testing.T) {
353+
buf := newBuf()
354+
got := buf.Slice(tc.start, tc.end)
355+
checkBufData(t, got, tc.want)
356+
})
357+
}
358+
t.Run(name+" subslice", func(t *testing.T) {
359+
buf := newBuf()
360+
slice := buf.Slice(1, 3)
361+
slice2 := slice.Slice(0, 1)
362+
checkBufData(t, slice2, []byte{2})
363+
})
364+
}
365+
t.Run("empty buffer", func(t *testing.T) {
366+
buf := newEmptyBuf()
367+
got := buf.Slice(0, 0)
368+
checkBufData(t, got, nil)
369+
})
370+
}
371+
372+
func (s) TestBuffer_SliceBoundsCheck(t *testing.T) {
373+
type panicCase struct {
374+
name string
375+
start, end int
376+
}
377+
nonEmptyCases := []panicCase{
378+
{"end_out_of_bounds", 0, 5},
379+
{"start_negative", -1, 3},
380+
{"start_greater_than_end", 3, 0},
381+
}
382+
tests := []struct {
383+
name string
384+
buf func() mem.Buffer
385+
panicCases []panicCase
386+
}{
387+
{
388+
name: "buffer",
389+
buf: newPooledBuffer,
390+
panicCases: nonEmptyCases,
391+
},
392+
{
393+
name: "SliceBuffer",
394+
buf: newSliceBuf,
395+
panicCases: nonEmptyCases,
396+
},
397+
{
398+
name: "emptyBuffer",
399+
buf: newEmptyBuf,
400+
panicCases: []panicCase{
401+
{"end_out_of_bounds", 0, 1},
402+
{"start_negative", -1, 0},
403+
},
404+
},
405+
}
406+
for _, tt := range tests {
407+
t.Run(tt.name, func(t *testing.T) {
408+
for _, pc := range tt.panicCases {
409+
t.Run(pc.name, func(t *testing.T) {
410+
buf := tt.buf()
411+
defer func() {
412+
if recover() == nil {
413+
t.Fatalf("Slice(%d, %d) did not panic", pc.start, pc.end)
414+
}
415+
}()
416+
buf.Slice(pc.start, pc.end)
417+
})
418+
}
419+
})
420+
}
421+
}
422+
423+
func newPooledBuffer() mem.Buffer {
424+
return newBuffer([]byte{1, 2, 3, 4}, mem.NopBufferPool{})
425+
}
426+
427+
func newSliceBuf() mem.Buffer {
428+
return mem.SliceBuffer{1, 2, 3, 4}
429+
}
430+
431+
func newEmptyBuf() mem.Buffer {
432+
var bs mem.BufferSlice
433+
return bs.MaterializeToBuffer(mem.NopBufferPool{})
434+
}
435+
436+
func checkBufData(t *testing.T, b mem.Buffer, expected []byte) {
437+
t.Helper()
438+
if !bytes.Equal(b.ReadOnlyData(), expected) {
439+
t.Fatalf("Buffer did not contain expected data %v, got %v", expected, b.ReadOnlyData())
440+
}
441+
}
442+
296443
func checkForPanic(t *testing.T, wantErr string) {
297444
t.Helper()
298445
r := recover()

0 commit comments

Comments
 (0)