1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2023 The Ebitengine Authors
3
4//go:build darwin
5
6package purego_test
7
8import (
9 "errors"
10 "fmt"
11 "os"
12 "os/exec"
13 "path/filepath"
14 "strings"
15 "testing"
16 "unsafe"
17
18 "github.com/ebitengine/purego"
19)
20
21// TestCallGoFromSharedLib is a test that checks for stack corruption on darwin/arm64
22// when C calls Go code from a non-Go thread in a dynamically loaded share library.
23func TestCallGoFromSharedLib(t *testing.T) {
24 libFileName := filepath.Join(t.TempDir(), "libcbtest.so")
25 t.Logf("Build %v", libFileName)
26
27 if err := buildSharedLib(libFileName, filepath.Join("libcbtest", "callback.c")); err != nil {
28 t.Fatal(err)
29 }
30 defer os.Remove(libFileName)
31
32 lib, err := purego.Dlopen(libFileName, purego.RTLD_NOW|purego.RTLD_GLOBAL)
33 if err != nil {
34 t.Fatalf("Dlopen(%q) faled: %v", libFileName, err)
35 }
36
37 var callCallback func(p uintptr, s string) int
38 purego.RegisterLibFunc(&callCallback, lib, "callCallback")
39
40 goFunc := func(cstr *byte, n int) int {
41 s := string(unsafe.Slice(cstr, n))
42 t.Logf("FROM Go: %s\n", s)
43 return 1
44 }
45
46 const want = 10101
47 cb := purego.NewCallback(goFunc)
48 for i := 0; i < 10; i++ {
49 got := callCallback(cb, "a test string")
50 if got != want {
51 t.Fatalf("%d: callCallback() got %v want %v", i, got, want)
52 }
53 }
54}
55
56func buildSharedLib(libFile string, sources ...string) error {
57 out, err := exec.Command("go", "env", "CC").Output()
58 if err != nil {
59 return fmt.Errorf("go env error: %w", err)
60 }
61
62 cc := strings.TrimSpace(string(out))
63 if cc == "" {
64 return errors.New("CC not found")
65 }
66
67 args := append([]string{"-shared", "-Wall", "-Werror", "-o", libFile}, sources...)
68 cmd := exec.Command(cc, args...)
69 if out, err := cmd.CombinedOutput(); err != nil {
70 return fmt.Errorf("compile lib: %w\n%q\n%s", err, cmd, string(out))
71 }
72
73 return nil
74}