//go:build tinygo.wasm package runtime_wasi import ( "reflect" "runtime" "strconv" "testing" "unsafe" ) //export malloc func libc_malloc(size uintptr) unsafe.Pointer //export free func libc_free(ptr unsafe.Pointer) //export calloc func libc_calloc(nmemb, size uintptr) unsafe.Pointer //export realloc func libc_realloc(ptr unsafe.Pointer, size uintptr) unsafe.Pointer func getFilledBuffer_malloc() uintptr { ptr := libc_malloc(5) fillPanda(ptr) return uintptr(ptr) } func getFilledBuffer_calloc() uintptr { ptr := libc_calloc(2, 5) fillPanda(ptr) *(*byte)(unsafe.Add(ptr, 5)) = 'b' *(*byte)(unsafe.Add(ptr, 6)) = 'e' *(*byte)(unsafe.Add(ptr, 7)) = 'a' *(*byte)(unsafe.Add(ptr, 8)) = 'r' *(*byte)(unsafe.Add(ptr, 9)) = 's' return uintptr(ptr) } func getFilledBuffer_realloc() uintptr { origPtr := getFilledBuffer_malloc() ptr := libc_realloc(unsafe.Pointer(origPtr), 9) *(*byte)(unsafe.Add(ptr, 5)) = 'b' *(*byte)(unsafe.Add(ptr, 6)) = 'e' *(*byte)(unsafe.Add(ptr, 7)) = 'a' *(*byte)(unsafe.Add(ptr, 8)) = 'r' return uintptr(ptr) } func getFilledBuffer_reallocNil() uintptr { ptr := libc_realloc(nil, 5) fillPanda(ptr) return uintptr(ptr) } func fillPanda(ptr unsafe.Pointer) { *(*byte)(unsafe.Add(ptr, 0)) = 'p' *(*byte)(unsafe.Add(ptr, 1)) = 'a' *(*byte)(unsafe.Add(ptr, 2)) = 'n' *(*byte)(unsafe.Add(ptr, 3)) = 'd' *(*byte)(unsafe.Add(ptr, 4)) = 'a' } func checkFilledBuffer(t *testing.T, ptr uintptr, content string) { t.Helper() buf := *(*string)(unsafe.Pointer(&reflect.StringHeader{ Data: ptr, Len: uintptr(len(content)), })) if buf != content { t.Errorf("expected %q, got %q", content, buf) } } func TestMallocFree(t *testing.T) { tests := []struct { name string getBuffer func() uintptr content string }{ { name: "malloc", getBuffer: getFilledBuffer_malloc, content: "panda", }, { name: "calloc", getBuffer: getFilledBuffer_calloc, content: "pandabears", }, { name: "realloc", getBuffer: getFilledBuffer_realloc, content: "pandabear", }, { name: "realloc nil", getBuffer: getFilledBuffer_reallocNil, content: "panda", }, } for _, tc := range tests { tt := tc t.Run(tt.name, func(t *testing.T) { bufPtr := tt.getBuffer() // Don't use defer to free the buffer as it seems to cause the GC to track it. // Churn GC, the pointer should still be valid until free is called. for i := 0; i < 1000; i++ { a := "hello" + strconv.Itoa(i) // Some conditional logic to ensure optimization doesn't remove the loop completely. if len(a) < 0 { break } runtime.GC() } checkFilledBuffer(t, bufPtr, tt.content) libc_free(unsafe.Pointer(bufPtr)) }) } } func TestMallocEmpty(t *testing.T) { ptr := libc_malloc(0) if ptr != nil { t.Errorf("expected nil pointer, got %p", ptr) } } func TestCallocEmpty(t *testing.T) { ptr := libc_calloc(0, 1) if ptr != nil { t.Errorf("expected nil pointer, got %p", ptr) } ptr = libc_calloc(1, 0) if ptr != nil { t.Errorf("expected nil pointer, got %p", ptr) } } func TestReallocEmpty(t *testing.T) { ptr := libc_malloc(1) if ptr == nil { t.Error("expected pointer but was nil") } ptr = libc_realloc(ptr, 0) if ptr != nil { t.Errorf("expected nil pointer, got %p", ptr) } }