110 lines
2.1 KiB
Go
110 lines
2.1 KiB
Go
package cgo
|
|
|
|
import (
|
|
"runtime"
|
|
"sync/atomic"
|
|
"unsafe"
|
|
)
|
|
|
|
// Buffer owns byte memory that can be passed safely to C.
|
|
//
|
|
// buffer := NewBuffer(16)
|
|
// defer buffer.Free()
|
|
// n := buffer.CopyFrom([]byte("payload"))
|
|
// _ = buffer.Bytes()[:n]
|
|
type Buffer struct {
|
|
data []byte
|
|
length int
|
|
pointer unsafe.Pointer
|
|
freed atomic.Bool
|
|
isPinned bool
|
|
pinner runtime.Pinner
|
|
}
|
|
|
|
// NewBuffer allocates memory and pins it so Ptr can be used across C boundaries.
|
|
func NewBuffer(size int) *Buffer {
|
|
if size < 0 {
|
|
panic("cgo.NewBuffer: size must be non-negative")
|
|
}
|
|
|
|
data := make([]byte, size)
|
|
buffer := &Buffer{
|
|
data: data,
|
|
length: size,
|
|
}
|
|
|
|
if size > 0 {
|
|
buffer.pinner.Pin(&buffer.data[0])
|
|
buffer.isPinned = true
|
|
buffer.pointer = unsafe.Pointer(&buffer.data[0])
|
|
}
|
|
|
|
return buffer
|
|
}
|
|
|
|
// Free releases the pinned memory backing slice and marks the buffer as freed.
|
|
func (b *Buffer) Free() {
|
|
if b == nil {
|
|
return
|
|
}
|
|
|
|
if !b.freed.CompareAndSwap(false, true) {
|
|
panic("cgo.Buffer.Free: double-free detected")
|
|
}
|
|
|
|
if b.isPinned {
|
|
b.pinner.Unpin()
|
|
b.isPinned = false
|
|
}
|
|
|
|
b.pointer = nil
|
|
b.data = nil
|
|
}
|
|
|
|
// CopyFrom copies bytes from src into the buffer and returns bytes copied.
|
|
func (b *Buffer) CopyFrom(src []byte) int {
|
|
b.assertNotFreed()
|
|
if len(src) == 0 || b.length == 0 {
|
|
return 0
|
|
}
|
|
|
|
copied := len(src)
|
|
if copied > b.length {
|
|
copied = b.length
|
|
}
|
|
|
|
copy(b.data[:copied], src[:copied])
|
|
return copied
|
|
}
|
|
|
|
// Bytes returns a mutable byte slice backed by the buffer memory.
|
|
func (b *Buffer) Bytes() []byte {
|
|
b.assertNotFreed()
|
|
return b.data
|
|
}
|
|
|
|
// Ptr returns the raw pointer to the buffer.
|
|
func (b *Buffer) Ptr() unsafe.Pointer {
|
|
b.assertNotFreed()
|
|
return b.pointer
|
|
}
|
|
|
|
// Len returns the current buffer length.
|
|
func (b *Buffer) Len() int {
|
|
b.assertNotFreed()
|
|
return b.length
|
|
}
|
|
|
|
// IsFreed reports whether Free has already been called.
|
|
func (b *Buffer) IsFreed() bool {
|
|
return b.freed.Load()
|
|
}
|
|
|
|
func (b *Buffer) assertNotFreed() {
|
|
if b == nil {
|
|
panic("cgo.Buffer: use-after-free detected: buffer is nil")
|
|
}
|
|
if b.freed.Load() {
|
|
panic("cgo.Buffer: use-after-free detected")
|
|
}
|
|
}
|