Skip to content

Commit c4e6d64

Browse files
Merge pull request #17 from godaddy/add-copy-buffers-and-null-check
Add CopyBuffers option and IsBufferAllNulls diagnostic
2 parents e714208 + 9216806 commit c4e6d64

File tree

3 files changed

+160
-9
lines changed

3 files changed

+160
-9
lines changed

.github/workflows/test.yml

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,23 @@ jobs:
1313
timeout-minutes: 15
1414
runs-on: ubuntu-latest
1515
steps:
16-
- uses: actions/checkout@v2
16+
- uses: actions/checkout@v4
1717
- name: Set up Go
18-
uses: actions/setup-go@v2
18+
uses: actions/setup-go@v5
1919
with:
20-
go-version: 1.17
20+
go-version: 1.24
21+
check-latest: 'true'
2122
- name: Test Cobhan
2223
run: scripts/test.sh
2324
test-macos:
2425
timeout-minutes: 15
2526
runs-on: 'macos-latest'
2627
steps:
27-
- uses: actions/checkout@v2
28+
- uses: actions/checkout@v4
2829
- name: Set up Go
29-
uses: actions/setup-go@v2
30+
uses: actions/setup-go@v5
3031
with:
31-
go-version: 1.17
32+
go-version: 1.24
33+
check-latest: 'true'
3234
- name: Test Cobhan
3335
run: scripts/test.sh

cobhan.go

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ var bufferMaximum = math.MaxInt32
4545

4646
var allowTempFileBuffers = true
4747

48+
var copyBuffers = false
49+
4850
func SetDefaultBufferMaximum(max int) {
4951
bufferMaximum = max
5052
}
@@ -53,6 +55,10 @@ func AllowTempFileBuffers(flag bool) {
5355
allowTempFileBuffers = flag
5456
}
5557

58+
func CopyBuffers(flag bool) {
59+
copyBuffers = flag
60+
}
61+
5662
func CPtr(buf *[]byte) *C.char {
5763
return (*C.char)(Ptr(buf))
5864
}
@@ -102,8 +108,16 @@ func bufferPtrToString(bufferPtr unsafe.Pointer, length C.int) string {
102108
return C.GoStringN((*C.char)(dataPtr), length)
103109
}
104110

105-
func bufferPtrToBytes(bufferPtr unsafe.Pointer, length C.int) []byte {
106-
return unsafe.Slice((*byte)(bufferPtrToDataPtr(bufferPtr)), length)
111+
func bufferPtrToBytes(bufferPtr unsafe.Pointer, length C.int) ([]byte, int32) {
112+
src := unsafe.Slice((*byte)(bufferPtrToDataPtr(bufferPtr)), length)
113+
114+
if copyBuffers {
115+
dst := make([]byte, length)
116+
copy(dst, src)
117+
return dst, ERR_NONE
118+
}
119+
120+
return src, ERR_NONE
107121
}
108122

109123
func updateBufferPtrLength(bufferPtr unsafe.Pointer, length int) {
@@ -194,6 +208,48 @@ func Int32ToBufferSafe(value int32, dst *[]byte) int32 {
194208
return 0
195209
}
196210

211+
func BufferLength(srcPtr unsafe.Pointer) int32 {
212+
if srcPtr == nil {
213+
return 0
214+
}
215+
return int32(bufferPtrToLength(srcPtr))
216+
}
217+
218+
func BufferLengthSafe(src *[]byte) int32 {
219+
if src == nil {
220+
return 0
221+
}
222+
return BufferLength(Ptr(src))
223+
}
224+
225+
func IsBufferAllNulls(srcPtr unsafe.Pointer) bool {
226+
if srcPtr == nil {
227+
return false
228+
}
229+
length := bufferPtrToLength(srcPtr)
230+
if length <= 0 {
231+
return false
232+
}
233+
checkLen := int(length)
234+
if checkLen > 64 {
235+
checkLen = 64
236+
}
237+
src := unsafe.Slice((*byte)(bufferPtrToDataPtr(srcPtr)), checkLen)
238+
for _, b := range src {
239+
if b != 0 {
240+
return false
241+
}
242+
}
243+
return true
244+
}
245+
246+
func IsBufferAllNullsSafe(src *[]byte) bool {
247+
if src == nil {
248+
return false
249+
}
250+
return IsBufferAllNulls(Ptr(src))
251+
}
252+
197253
func BufferToBytesSafe(src *[]byte) ([]byte, int32) {
198254
if src == nil {
199255
return nil, ERR_NULL_PTR
@@ -212,7 +268,7 @@ func BufferToBytes(srcPtr unsafe.Pointer) ([]byte, int32) {
212268
}
213269

214270
if length >= 0 {
215-
return bufferPtrToBytes(srcPtr, length), ERR_NONE
271+
return bufferPtrToBytes(srcPtr, length)
216272
} else {
217273
return tempToBytes(srcPtr, length)
218274
}

cobhan_test.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,99 @@ func TestEnableTempFile(t *testing.T) {
342342
}
343343
}
344344

345+
func TestCopyBuffers(t *testing.T) {
346+
CopyBuffers(true)
347+
defer CopyBuffers(false)
348+
349+
input := []byte{1, 2, 3, 4}
350+
buf := testAllocateBytesBuffer(t, input)
351+
352+
output, result := BufferToBytesSafe(&buf)
353+
if result != ERR_NONE {
354+
t.Errorf("BufferToBytesSafe returned %v", result)
355+
}
356+
357+
if !bytes.Equal(input, output) {
358+
t.Error("Bytes don't match")
359+
}
360+
361+
// Verify it's a copy by modifying the output and checking the buffer is unchanged
362+
output[0] = 99
363+
output2, result := BufferToBytesSafe(&buf)
364+
if result != ERR_NONE {
365+
t.Errorf("BufferToBytesSafe returned %v", result)
366+
}
367+
if output2[0] != 1 {
368+
t.Error("Expected copy semantics but buffer was modified")
369+
}
370+
}
371+
372+
func TestBufferLength(t *testing.T) {
373+
buf := AllocateBuffer(42)
374+
if BufferLengthSafe(&buf) != 42 {
375+
t.Errorf("Expected 42, got %v", BufferLengthSafe(&buf))
376+
}
377+
378+
// After writing data, length should reflect actual data length
379+
input := "hello"
380+
buf2 := testAllocateStringBuffer(t, input)
381+
if BufferLengthSafe(&buf2) != int32(len(input)) {
382+
t.Errorf("Expected %v, got %v", len(input), BufferLengthSafe(&buf2))
383+
}
384+
385+
// Nil should return 0
386+
if BufferLength(nil) != 0 {
387+
t.Error("Expected BufferLength to return 0 for nil")
388+
}
389+
if BufferLengthSafe(nil) != 0 {
390+
t.Error("Expected BufferLengthSafe to return 0 for nil")
391+
}
392+
}
393+
394+
func TestIsBufferAllNulls(t *testing.T) {
395+
// Buffer with all nulls should return true
396+
buf := AllocateBuffer(4)
397+
if !IsBufferAllNullsSafe(&buf) {
398+
t.Error("Expected IsBufferAllNullsSafe to return true for all-null buffer")
399+
}
400+
401+
// Buffer with real data should return false
402+
input := []byte{1, 2, 3, 4}
403+
buf2 := testAllocateBytesBuffer(t, input)
404+
if IsBufferAllNullsSafe(&buf2) {
405+
t.Error("Expected IsBufferAllNullsSafe to return false for non-null buffer")
406+
}
407+
408+
// Zero-length buffer should return false (not a false positive)
409+
buf3 := AllocateBuffer(0)
410+
if IsBufferAllNullsSafe(&buf3) {
411+
t.Error("Expected IsBufferAllNullsSafe to return false for zero-length buffer")
412+
}
413+
414+
// Large buffer with nulls in first 64 bytes but data after should still return true
415+
buf4 := AllocateBuffer(128)
416+
// Set byte at offset 65 to non-zero (beyond the 64-byte check window)
417+
buf4[BUFFER_HEADER_SIZE+65] = 0xFF
418+
if !IsBufferAllNullsSafe(&buf4) {
419+
t.Error("Expected IsBufferAllNullsSafe to return true when only first 64 bytes are checked")
420+
}
421+
422+
// Buffer with non-null byte within the first 64 bytes should return false
423+
buf5 := AllocateBuffer(128)
424+
buf5[BUFFER_HEADER_SIZE+32] = 0xFF
425+
if IsBufferAllNullsSafe(&buf5) {
426+
t.Error("Expected IsBufferAllNullsSafe to return false when non-null byte is within first 64 bytes")
427+
}
428+
429+
// Nil should return false
430+
if IsBufferAllNulls(nil) {
431+
t.Error("Expected IsBufferAllNulls to return false for nil")
432+
}
433+
if IsBufferAllNullsSafe(nil) {
434+
t.Error("Expected IsBufferAllNullsSafe to return false for nil")
435+
}
436+
}
437+
345438
func TestDisableTempFile(t *testing.T) {
346439
// Disable the use of temp file buffers
347440
AllowTempFileBuffers(false)

0 commit comments

Comments
 (0)