detached a66c0b0

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 UnverifiedB5690EEEBB952194 The key is unknown
3 files · +35 -2
M func.go +3 -2
@@ -409,8 +409,9 @@ func RegisterFunc(fptr any, cfn uintptr) {
409409 v = reflect.NewAt(outType, unsafe.Pointer(&a1)).Elem()
410410 case reflect.Func:
411411 // 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+ }
414415 case reflect.String:
415416 v.SetString(strings.GoString(syscall.a1))
416417 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 function411 // 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) {
239239 t.Fatalf("%s: got %q, want %q", cName, res, want)
240240 }
241241 }
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+ }
242260 }
243261
244262 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,
197197 return (double)a1 * 1 + (double)a2 * 2 + (double)a3 * 3 + (double)a4 * 4 +
198198 (double)a5 * 5 + a6;
199199 }
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+}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 102

↑↓ moveenter openesc close