purego: fix returning a C function pointer as a Go func (#500)
The RegisterFunc type table has func <=> C function in it, but that only holds in the argument direction. Declare a C function as returning a func, and calling it panics: panic: reflect.MakeFunc: value of type *func(int32, int32) int32 is not assignable to type func(int32, int32) int32 Registering is fine, it's the call that goes, and it goes for every signature. Every other case in that return switch leaves the result as the declared type. The reflect.Func case swapped in a *func, registered into that, and handed the pointer back to MakeFunc, which wont take it. If the C function returns NULL the inner RegisterFunc still panics purego: cfn is nil. Left that alone, since whether it should hand you a nil Go func instead looked like your call rather than a bug. Test is a block in TestABI plus a return_func_ptr helper in abi_test.c, and it fails on main with teh panic above. Been broken since #49 back in 2022, which is probably why nobody's hit it: you'd normally take the pointer back as a uintptr and register it youself, and nothing in the repo declares a func return. Closes #501
- commit
- a66c0b0d41190bc8a18e87f30da7d18453d7d40a
- author
- Iain · · 2026-08-24 12:41 UTC
- committer
- GitHub · · 2026-08-24 12:41 UTC
- parent
- f2f4202
- signature
- GPG Unverified
B5690EEEBB952194The key is unknown
3 files · +35 -2
M func.go +3 -2
| @@ -409,8 +409,9 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 409 | 409 | v = reflect.NewAt(outType, unsafe.Pointer(&a1)).Elem() | |
| 410 | 410 | case reflect.Func: | |
| 411 | 411 | // wrap this C function in a nicely typed Go function | |
| 412 | - | v = reflect.New(outType) | |
| 413 | - | RegisterFunc(v.Interface(), syscall.a1) | |
| 412 | + | if syscall.a1 != 0 { | |
| 413 | + | RegisterFunc(v.Addr().Interface(), syscall.a1) | |
| 414 | + | } | |
| 414 | 415 | case reflect.String: | |
| 415 | 416 | v.SetString(strings.GoString(syscall.a1)) | |
| 416 | 417 | case reflect.Float32: | |
| @@ -409,8 +409,9 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 409 | v = reflect.NewAt(outType, unsafe.Pointer(&a1)).Elem() | 409 | v = reflect.NewAt(outType, unsafe.Pointer(&a1)).Elem() | ||
| 410 | case reflect.Func: | 410 | case reflect.Func: | ||
| 411 | // wrap this C function in a nicely typed Go function | 411 | // wrap this C function in a nicely typed Go function | ||
| 412 | - | v = reflect.New(outType) | 412 | + | if syscall.a1 != 0 { |
| 413 | - | RegisterFunc(v.Interface(), syscall.a1) | 413 | + | RegisterFunc(v.Addr().Interface(), syscall.a1) |
| 414 | + | } | |||
| 414 | case reflect.String: | 415 | case reflect.String: | ||
| 415 | v.SetString(strings.GoString(syscall.a1)) | 416 | v.SetString(strings.GoString(syscall.a1)) | ||
| 416 | case reflect.Float32: | 417 | case reflect.Float32: | ||
M func_test.go +18 -0
| @@ -239,6 +239,24 @@ func TestABI(t *testing.T) { | |||
| 239 | 239 | t.Fatalf("%s: got %q, want %q", cName, res, want) | |
| 240 | 240 | } | |
| 241 | 241 | } | |
| 242 | + | { | |
| 243 | + | const cName = "return_func_ptr" | |
| 244 | + | var fn func() func(a, b int32) int32 | |
| 245 | + | purego.RegisterLibFunc(&fn, lib, cName) | |
| 246 | + | add := fn() | |
| 247 | + | const expect = 5 | |
| 248 | + | if res := add(2, 3); res != expect { | |
| 249 | + | t.Fatalf("%s: got %d, want %d", cName, res, expect) | |
| 250 | + | } | |
| 251 | + | } | |
| 252 | + | { | |
| 253 | + | const cName = "return_null_func_ptr" | |
| 254 | + | var fn func() func(a, b int32) int32 | |
| 255 | + | purego.RegisterLibFunc(&fn, lib, cName) | |
| 256 | + | if fn() != nil { | |
| 257 | + | t.Fatalf("%s: got a non-nil func, want nil", cName) | |
| 258 | + | } | |
| 259 | + | } | |
| 242 | 260 | } | |
| 243 | 261 | ||
| 244 | 262 | func TestABI_ArgumentPassing(t *testing.T) { | |
| @@ -239,6 +239,24 @@ func TestABI(t *testing.T) { | |||||
| 239 | t.Fatalf("%s: got %q, want %q", cName, res, want) | 239 | t.Fatalf("%s: got %q, want %q", cName, res, want) | ||
| 240 | } | 240 | } | ||
| 241 | } | 241 | } | ||
| 242 | + | { | |||
| 243 | + | const cName = "return_func_ptr" | |||
| 244 | + | var fn func() func(a, b int32) int32 | |||
| 245 | + | purego.RegisterLibFunc(&fn, lib, cName) | |||
| 246 | + | add := fn() | |||
| 247 | + | const expect = 5 | |||
| 248 | + | if res := add(2, 3); res != expect { | |||
| 249 | + | t.Fatalf("%s: got %d, want %d", cName, res, expect) | |||
| 250 | + | } | |||
| 251 | + | } | |||
| 252 | + | { | |||
| 253 | + | const cName = "return_null_func_ptr" | |||
| 254 | + | var fn func() func(a, b int32) int32 | |||
| 255 | + | purego.RegisterLibFunc(&fn, lib, cName) | |||
| 256 | + | if fn() != nil { | |||
| 257 | + | t.Fatalf("%s: got a non-nil func, want nil", cName) | |||
| 258 | + | } | |||
| 259 | + | } | |||
| 242 | } | 260 | } | ||
| 243 | 261 | ||||
| 244 | func TestABI_ArgumentPassing(t *testing.T) { | 262 | func TestABI_ArgumentPassing(t *testing.T) { | ||
M testdata/abitest/abi_test.c +14 -0
| @@ -197,3 +197,17 @@ double arm_float64_unaligned_on_stack(uintptr_t a1, uintptr_t a2, uintptr_t a3, | |||
| 197 | 197 | return (double)a1 * 1 + (double)a2 * 2 + (double)a3 * 3 + (double)a4 * 4 + | |
| 198 | 198 | (double)a5 * 5 + a6; | |
| 199 | 199 | } | |
| 200 | + | ||
| 201 | + | typedef int32_t (*AddFunc)(int32_t, int32_t); | |
| 202 | + | ||
| 203 | + | int32_t returned_add(int32_t a, int32_t b) { | |
| 204 | + | return a + b; | |
| 205 | + | } | |
| 206 | + | ||
| 207 | + | AddFunc return_func_ptr(void) { | |
| 208 | + | return returned_add; | |
| 209 | + | } | |
| 210 | + | ||
| 211 | + | AddFunc return_null_func_ptr(void) { | |
| 212 | + | return NULL; | |
| 213 | + | } | |
| @@ -197,3 +197,17 @@ double arm_float64_unaligned_on_stack(uintptr_t a1, uintptr_t a2, uintptr_t a3, | |||||
| 197 | return (double)a1 * 1 + (double)a2 * 2 + (double)a3 * 3 + (double)a4 * 4 + | 197 | return (double)a1 * 1 + (double)a2 * 2 + (double)a3 * 3 + (double)a4 * 4 + | ||
| 198 | (double)a5 * 5 + a6; | 198 | (double)a5 * 5 + a6; | ||
| 199 | } | 199 | } | ||
| 200 | + | ||||
| 201 | + | typedef int32_t (*AddFunc)(int32_t, int32_t); | |||
| 202 | + | ||||
| 203 | + | int32_t returned_add(int32_t a, int32_t b) { | |||
| 204 | + | return a + b; | |||
| 205 | + | } | |||
| 206 | + | ||||
| 207 | + | AddFunc return_func_ptr(void) { | |||
| 208 | + | return returned_add; | |||
| 209 | + | } | |||
| 210 | + | ||||
| 211 | + | AddFunc return_null_func_ptr(void) { | |||
| 212 | + | return NULL; | |||
| 213 | + | } | |||