1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3
4//go:build !windows && !plan9
5
6package purego_test
7
8import (
9 "runtime"
10 "testing"
11 "unsafe"
12
13 "github.com/bnema/purego"
14 "github.com/bnema/purego/internal/load"
15)
16
17//go:noinline
18func growCallbackStack(depth int) {
19 var pad [4096]byte
20 pad[0] = byte(depth)
21 if depth > 0 {
22 growCallbackStack(depth - 1)
23 }
24 runtime.KeepAlive(pad)
25}
26
27func TestSyscall6CallbackStackRelocation(t *testing.T) {
28 library, err := getSystemLibrary()
29 if err != nil {
30 t.Fatal(err)
31 }
32 lib, err := load.OpenLibrary(library)
33 if err != nil {
34 t.Fatal(err)
35 }
36 defer load.CloseLibrary(lib)
37 search, err := load.OpenSymbol(lib, "bsearch")
38 if err != nil {
39 t.Fatal(err)
40 }
41 for _, fast := range []bool{false, true} {
42 name := "NewCallback"
43 if fast {
44 name = "NewCallbackInts"
45 }
46 t.Run(name, func(t *testing.T) {
47 calls := 0
48 compare := func() uintptr { calls++; growCallbackStack(128); return 1 }
49 var cb uintptr
50 if fast {
51 cb = purego.NewCallbackInts(func(*purego.CallbackArgs) uintptr { return compare() })
52 } else {
53 cb = purego.NewCallback(func(_ purego.CDecl, _, _ uintptr) uintptr { return compare() })
54 }
55 // A fresh goroutine starts with a small stack for each callback kind.
56 done := make(chan uintptr, 1)
57 go func() {
58 key, data := new(uintptr), new(uintptr)
59 r, _, _ := purego.Syscall6(search, uintptr(unsafe.Pointer(key)), uintptr(unsafe.Pointer(data)), 1, unsafe.Sizeof(*data), cb, 0)
60 done <- r
61 }()
62 if got := <-done; got != 0 {
63 t.Errorf("bsearch returned %#x, want NULL (key absent)", got)
64 }
65 if calls != 1 {
66 t.Errorf("comparator called %d times, want 1", calls)
67 }
68 })
69 }
70}
71
72func TestSyscall6Allocs(t *testing.T) {
73 library, err := getSystemLibrary()
74 if err != nil {
75 t.Fatal(err)
76 }
77 lib, err := load.OpenLibrary(library)
78 if err != nil {
79 t.Fatal(err)
80 }
81 defer load.CloseLibrary(lib)
82 fn, err := load.OpenSymbol(lib, "abs")
83 if err != nil {
84 t.Fatal(err)
85 }
86 if got := testing.AllocsPerRun(100, func() { purego.Syscall6(fn, 1, 0, 0, 0, 0, 0) }); got != 0 {
87 t.Fatalf("Syscall6 allocations = %v, want 0", got)
88 }
89}