Skip to content

Commit ee6e64c

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

File tree

2 files changed

+97
-10
lines changed

2 files changed

+97
-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: 67 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -265,21 +265,15 @@ func (s) TestBuffer_Split(t *testing.T) {
265265
}
266266
freed = true
267267
}))
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-
}
274268

275269
buf, split1 := mem.SplitUnsafe(buf, 2)
276-
checkBufData(buf, data[:2])
277-
checkBufData(split1, data[2:])
270+
checkBufData(t, buf, data[:2])
271+
checkBufData(t, split1, data[2:])
278272

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

284278
// If any of the following frees actually free the buffer, the test will fail.
285279
buf.Free()
@@ -293,6 +287,69 @@ func (s) TestBuffer_Split(t *testing.T) {
293287
}
294288
}
295289

290+
func (s) TestBuffer_Slice(t *testing.T) {
291+
ready := false
292+
freed := false
293+
data := []byte{1, 2, 3, 4}
294+
buf := mem.NewBuffer(&data, poolFunc(func(*[]byte) {
295+
if !ready {
296+
t.Fatalf("Freed too early")
297+
}
298+
freed = true
299+
}))
300+
301+
// Slice the buffer and verify the data.
302+
slice1 := buf.Slice(1, 3)
303+
checkBufData(t, slice1, data[1:3])
304+
305+
// Verify the original buffer is not modified.
306+
checkBufData(t, buf, data)
307+
308+
// Slice the slice.
309+
slice2 := slice1.Slice(0, 1)
310+
checkBufData(t, slice2, data[1:2])
311+
312+
// Free original and first slice — root should not be freed yet.
313+
buf.Free()
314+
slice1.Free()
315+
316+
// The last slice keeps the root alive.
317+
checkBufData(t, slice2, data[1:2])
318+
319+
ready = true
320+
slice2.Free()
321+
322+
if !freed {
323+
t.Fatalf("Buffer never freed")
324+
}
325+
}
326+
327+
func (s) TestBuffer_SliceAfterFree(t *testing.T) {
328+
buf := newBuffer([]byte("abcd"), mem.NopBufferPool{})
329+
buf.Free()
330+
defer checkForPanic(t, "Cannot slice freed buffer")
331+
buf.Slice(0, 2)
332+
}
333+
334+
func (s) TestBuffer_SliceBoundsCheck(t *testing.T) {
335+
buf := newBuffer([]byte("abcd"), mem.NopBufferPool{})
336+
defer buf.Free()
337+
defer func() {
338+
r := recover()
339+
if r == nil {
340+
t.Fatalf("Out of bounds slice did not panic")
341+
}
342+
}()
343+
buf.Slice(0, 5)
344+
}
345+
346+
func checkBufData(t *testing.T, b mem.Buffer, expected []byte) {
347+
t.Helper()
348+
if !bytes.Equal(b.ReadOnlyData(), expected) {
349+
t.Fatalf("Buffer did not contain expected data %v, got %v", expected, b.ReadOnlyData())
350+
}
351+
}
352+
296353
func checkForPanic(t *testing.T, wantErr string) {
297354
t.Helper()
298355
r := recover()

0 commit comments

Comments
 (0)