From 843bbc49dbf90bb94307fd486b7cccdc710a4930 Mon Sep 17 00:00:00 2001 From: stringintech Date: Mon, 4 May 2026 17:44:27 +0330 Subject: [PATCH] Store cgo handles in valid callback userdata pointers The previous code converted `cgo.Handle` values directly to `unsafe.Pointer`. That worked here because `cgo.Handle` is an integer token and kernel only stores the userdata value and passes it back to Go; it does not dereference it. However, Go documents that a `cgo.Handle` integer must not be coerced to `unsafe.Pointer` for a `void*` argument. For synchronous writer callbacks, the code now passes the address of the handle value for the duration of the C call. For retained callbacks, the code stores the handle value in C-allocated memory, reads it back in Go callback bridges, and frees that storage when kernel calls the destroy callback. --- Makefile | 3 +- kernel/cgo_handle.go | 37 ++++++++++++++++++++++++ kernel/common.go | 6 ---- kernel/context_options.go | 12 +++----- kernel/logging_connection.go | 14 ++++----- kernel/notification_callbacks.go | 22 +++++--------- kernel/validation_interface_callbacks.go | 13 +++------ kernel/writer_helper.go | 4 +-- 8 files changed, 61 insertions(+), 50 deletions(-) create mode 100644 kernel/cgo_handle.go diff --git a/Makefile b/Makefile index c4fa05a3..09207539 100644 --- a/Makefile +++ b/Makefile @@ -31,8 +31,7 @@ build: go build ./... test: - go test -v ./... - go test -race ./kernel -run TestTransactionHandlePtrRaceDetector + go test -race -v ./... clean: rm -rf depend/bitcoin/build diff --git a/kernel/cgo_handle.go b/kernel/cgo_handle.go new file mode 100644 index 00000000..3a90e71e --- /dev/null +++ b/kernel/cgo_handle.go @@ -0,0 +1,37 @@ +package kernel + +/* +#include +*/ +import "C" +import ( + "runtime/cgo" + "unsafe" +) + +func newCgoHandlePointer(value any) unsafe.Pointer { + userData := C.malloc(C.size_t(unsafe.Sizeof(cgo.Handle(0)))) + if userData == nil { + panic("Failed to allocate callback handle storage") + } + handlePtr := (*cgo.Handle)(userData) + *handlePtr = cgo.NewHandle(value) + return userData +} + +func cgoHandleFromPointer(userData unsafe.Pointer) cgo.Handle { + handlePtr := (*cgo.Handle)(userData) + return *handlePtr +} + +func deleteCgoHandlePointer(userData unsafe.Pointer) { + if userData != nil { + cgoHandleFromPointer(userData).Delete() + C.free(userData) + } +} + +//export go_delete_handle +func go_delete_handle(userData unsafe.Pointer) { + deleteCgoHandlePointer(userData) +} diff --git a/kernel/common.go b/kernel/common.go index 05c1d379..ea8eec32 100644 --- a/kernel/common.go +++ b/kernel/common.go @@ -3,7 +3,6 @@ package kernel import "C" import ( "runtime" - "runtime/cgo" "unsafe" ) @@ -88,11 +87,6 @@ func (h *handle) Destroy() { h.destroy() } -//export go_delete_handle -func go_delete_handle(handle unsafe.Pointer) { - cgo.Handle(handle).Delete() -} - func ReverseBytes(data []byte) []byte { result := make([]byte, len(data)) for i, b := range data { diff --git a/kernel/context_options.go b/kernel/context_options.go index d70e819e..e63c9260 100644 --- a/kernel/context_options.go +++ b/kernel/context_options.go @@ -4,8 +4,8 @@ package kernel #include "bitcoinkernel.h" #include -// Bridge functions: exported Go functions that C library can call -// user_data contains the cgo.Handle ID as void* for callback identification +// Bridge functions exported from Go for kernel callbacks. +// user_data points to cgo.Handle storage. extern void go_notify_block_tip_bridge(void* user_data, btck_SynchronizationState state, btck_BlockTreeEntry* entry, double verification_progress); extern void go_notify_header_tip_bridge(void* user_data, btck_SynchronizationState state, int64_t height, int64_t timestamp, int presync); extern void go_notify_progress_bridge(void* user_data, const char* title, size_t title_len, int progress_percent, int resume_possible); @@ -21,10 +21,6 @@ extern void go_validation_interface_block_disconnected_bridge(void* user_data, b extern void go_delete_handle(void* user_data); */ import "C" -import ( - "runtime/cgo" - "unsafe" -) // ContextOption is a functional option for configuring context options. type ContextOption func(*C.btck_ContextOptions) error @@ -54,7 +50,7 @@ func WithChainType(chainType ChainType) ContextOption { func WithNotifications(callbacks *NotificationCallbacks) ContextOption { return func(opts *C.btck_ContextOptions) error { notificationCallbacks := C.btck_NotificationInterfaceCallbacks{ - user_data: unsafe.Pointer(cgo.NewHandle(callbacks)), + user_data: newCgoHandlePointer(callbacks), user_data_destroy: C.btck_DestroyCallback(C.go_delete_handle), block_tip: C.btck_NotifyBlockTip(C.go_notify_block_tip_bridge), header_tip: C.btck_NotifyHeaderTip(C.go_notify_header_tip_bridge), @@ -78,7 +74,7 @@ func WithNotifications(callbacks *NotificationCallbacks) ContextOption { func WithValidationInterface(callbacks *ValidationInterfaceCallbacks) ContextOption { return func(opts *C.btck_ContextOptions) error { validationCallbacks := C.btck_ValidationInterfaceCallbacks{ - user_data: unsafe.Pointer(cgo.NewHandle(callbacks)), + user_data: newCgoHandlePointer(callbacks), user_data_destroy: C.btck_DestroyCallback(C.go_delete_handle), block_checked: C.btck_ValidationInterfaceBlockChecked(C.go_validation_interface_block_checked_bridge), pow_valid_block: C.btck_ValidationInterfacePoWValidBlock(C.go_validation_interface_pow_valid_block_bridge), diff --git a/kernel/logging_connection.go b/kernel/logging_connection.go index 2a56e43f..74dd62f3 100644 --- a/kernel/logging_connection.go +++ b/kernel/logging_connection.go @@ -5,15 +5,14 @@ package kernel #include #include -// Bridge function: exported Go function that C library can call -// user_data contains the cgo.Handle ID as void* for callback identification +// Bridge function exported from Go for kernel log callbacks. +// user_data points to cgo.Handle storage. extern void go_log_callback_bridge(void* user_data, char* message, size_t message_len); extern void go_delete_handle(void* user_data); */ import "C" import ( - "runtime/cgo" "unsafe" ) @@ -38,8 +37,7 @@ type LoggingConnection struct { //export go_log_callback_bridge func go_log_callback_bridge(user_data unsafe.Pointer, message *C.char, message_len C.size_t) { - handle := cgo.Handle(user_data) - callback := handle.Value().(LogCallback) + callback := cgoHandleFromPointer(user_data).Value().(LogCallback) goMessage := C.GoStringN(message, C.int(message_len)) callback(goMessage) } @@ -54,11 +52,11 @@ func go_log_callback_bridge(user_data unsafe.Pointer, message *C.char, message_l // // Returns an error if the logging connection cannot be created. func NewLoggingConnection(callback LogCallback) (*LoggingConnection, error) { - callbackHandle := cgo.NewHandle(callback) + userData := newCgoHandlePointer(callback) ptr := C.btck_logging_connection_create((C.btck_LogCallback)(C.go_log_callback_bridge), - unsafe.Pointer(callbackHandle), C.btck_DestroyCallback(C.go_delete_handle)) + userData, C.btck_DestroyCallback(C.go_delete_handle)) if ptr == nil { - callbackHandle.Delete() + deleteCgoHandlePointer(userData) return nil, &InternalError{"Failed to create logging connection"} } h := newUniqueHandle(unsafe.Pointer(ptr), loggingConnectionCFuncs{}) diff --git a/kernel/notification_callbacks.go b/kernel/notification_callbacks.go index 8030d183..48d8dd2a 100644 --- a/kernel/notification_callbacks.go +++ b/kernel/notification_callbacks.go @@ -5,7 +5,6 @@ package kernel */ import "C" import ( - "runtime/cgo" "unsafe" ) @@ -39,8 +38,7 @@ const ( //export go_notify_block_tip_bridge func go_notify_block_tip_bridge(user_data unsafe.Pointer, state C.btck_SynchronizationState, entry *C.btck_BlockTreeEntry, verification_progress C.double) { - handle := cgo.Handle(user_data) - callbacks := handle.Value().(*NotificationCallbacks) + callbacks := cgoHandleFromPointer(user_data).Value().(*NotificationCallbacks) if callbacks.OnBlockTip != nil { goState := SynchronizationState(state) @@ -51,8 +49,7 @@ func go_notify_block_tip_bridge(user_data unsafe.Pointer, state C.btck_Synchroni //export go_notify_header_tip_bridge func go_notify_header_tip_bridge(user_data unsafe.Pointer, state C.btck_SynchronizationState, height C.int64_t, timestamp C.int64_t, presync C.int) { - handle := cgo.Handle(user_data) - callbacks := handle.Value().(*NotificationCallbacks) + callbacks := cgoHandleFromPointer(user_data).Value().(*NotificationCallbacks) if callbacks.OnHeaderTip != nil { goState := SynchronizationState(state) @@ -62,8 +59,7 @@ func go_notify_header_tip_bridge(user_data unsafe.Pointer, state C.btck_Synchron //export go_notify_progress_bridge func go_notify_progress_bridge(user_data unsafe.Pointer, title *C.char, title_len C.size_t, progress_percent C.int, resume_possible C.int) { - handle := cgo.Handle(user_data) - callbacks := handle.Value().(*NotificationCallbacks) + callbacks := cgoHandleFromPointer(user_data).Value().(*NotificationCallbacks) if callbacks.OnProgress != nil { goTitle := C.GoStringN(title, C.int(title_len)) @@ -73,8 +69,7 @@ func go_notify_progress_bridge(user_data unsafe.Pointer, title *C.char, title_le //export go_notify_warning_set_bridge func go_notify_warning_set_bridge(user_data unsafe.Pointer, warning C.btck_Warning, message *C.char, message_len C.size_t) { - handle := cgo.Handle(user_data) - callbacks := handle.Value().(*NotificationCallbacks) + callbacks := cgoHandleFromPointer(user_data).Value().(*NotificationCallbacks) if callbacks.OnWarningSet != nil { goWarning := Warning(warning) @@ -85,8 +80,7 @@ func go_notify_warning_set_bridge(user_data unsafe.Pointer, warning C.btck_Warni //export go_notify_warning_unset_bridge func go_notify_warning_unset_bridge(user_data unsafe.Pointer, warning C.btck_Warning) { - handle := cgo.Handle(user_data) - callbacks := handle.Value().(*NotificationCallbacks) + callbacks := cgoHandleFromPointer(user_data).Value().(*NotificationCallbacks) if callbacks.OnWarningUnset != nil { goWarning := Warning(warning) @@ -96,8 +90,7 @@ func go_notify_warning_unset_bridge(user_data unsafe.Pointer, warning C.btck_War //export go_notify_flush_error_bridge func go_notify_flush_error_bridge(user_data unsafe.Pointer, message *C.char, message_len C.size_t) { - handle := cgo.Handle(user_data) - callbacks := handle.Value().(*NotificationCallbacks) + callbacks := cgoHandleFromPointer(user_data).Value().(*NotificationCallbacks) if callbacks.OnFlushError != nil { goMessage := C.GoStringN(message, C.int(message_len)) @@ -107,8 +100,7 @@ func go_notify_flush_error_bridge(user_data unsafe.Pointer, message *C.char, mes //export go_notify_fatal_error_bridge func go_notify_fatal_error_bridge(user_data unsafe.Pointer, message *C.char, message_len C.size_t) { - handle := cgo.Handle(user_data) - callbacks := handle.Value().(*NotificationCallbacks) + callbacks := cgoHandleFromPointer(user_data).Value().(*NotificationCallbacks) if callbacks.OnFatalError != nil { goMessage := C.GoStringN(message, C.int(message_len)) diff --git a/kernel/validation_interface_callbacks.go b/kernel/validation_interface_callbacks.go index ac4eef5f..3f436246 100644 --- a/kernel/validation_interface_callbacks.go +++ b/kernel/validation_interface_callbacks.go @@ -5,7 +5,6 @@ package kernel */ import "C" import ( - "runtime/cgo" "unsafe" ) @@ -21,8 +20,7 @@ type ValidationInterfaceCallbacks struct { //export go_validation_interface_block_checked_bridge func go_validation_interface_block_checked_bridge(user_data unsafe.Pointer, block *C.btck_Block, state *C.btck_BlockValidationState) { - handle := cgo.Handle(user_data) - callbacks := handle.Value().(*ValidationInterfaceCallbacks) + callbacks := cgoHandleFromPointer(user_data).Value().(*ValidationInterfaceCallbacks) if callbacks.OnBlockChecked != nil { callbacks.OnBlockChecked(newBlock(block, true), newBlockValidationStateView(state)) } @@ -30,8 +28,7 @@ func go_validation_interface_block_checked_bridge(user_data unsafe.Pointer, bloc //export go_validation_interface_pow_valid_block_bridge func go_validation_interface_pow_valid_block_bridge(user_data unsafe.Pointer, block *C.btck_Block, entry *C.btck_BlockTreeEntry) { - handle := cgo.Handle(user_data) - callbacks := handle.Value().(*ValidationInterfaceCallbacks) + callbacks := cgoHandleFromPointer(user_data).Value().(*ValidationInterfaceCallbacks) if callbacks.OnPoWValidBlock != nil { callbacks.OnPoWValidBlock(newBlock(block, true), &BlockTreeEntry{ptr: entry}) } @@ -39,8 +36,7 @@ func go_validation_interface_pow_valid_block_bridge(user_data unsafe.Pointer, bl //export go_validation_interface_block_connected_bridge func go_validation_interface_block_connected_bridge(user_data unsafe.Pointer, block *C.btck_Block, entry *C.btck_BlockTreeEntry) { - handle := cgo.Handle(user_data) - callbacks := handle.Value().(*ValidationInterfaceCallbacks) + callbacks := cgoHandleFromPointer(user_data).Value().(*ValidationInterfaceCallbacks) if callbacks.OnBlockConnected != nil { callbacks.OnBlockConnected(newBlock(block, true), &BlockTreeEntry{ptr: entry}) } @@ -48,8 +44,7 @@ func go_validation_interface_block_connected_bridge(user_data unsafe.Pointer, bl //export go_validation_interface_block_disconnected_bridge func go_validation_interface_block_disconnected_bridge(user_data unsafe.Pointer, block *C.btck_Block, entry *C.btck_BlockTreeEntry) { - handle := cgo.Handle(user_data) - callbacks := handle.Value().(*ValidationInterfaceCallbacks) + callbacks := cgoHandleFromPointer(user_data).Value().(*ValidationInterfaceCallbacks) if callbacks.OnBlockDisconnected != nil { callbacks.OnBlockDisconnected(newBlock(block, true), &BlockTreeEntry{ptr: entry}) } diff --git a/kernel/writer_helper.go b/kernel/writer_helper.go index c45c1160..80852ed0 100644 --- a/kernel/writer_helper.go +++ b/kernel/writer_helper.go @@ -21,7 +21,7 @@ type writerCallbackData struct { //export go_writer_callback_bridge func go_writer_callback_bridge(bytes unsafe.Pointer, size C.size_t, userdata unsafe.Pointer) C.int { if size > 0 { - data := cgo.Handle(userdata).Value().(*writerCallbackData) + data := cgoHandleFromPointer(userdata).Value().(*writerCallbackData) // Create a Go slice view of the C memory cBytes := unsafe.Slice((*byte)(bytes), int(size)) data.buffer = append(data.buffer, cBytes...) @@ -36,7 +36,7 @@ func writeToBytes(writerFunc func(C.btck_WriteBytes, unsafe.Pointer) C.int) (byt handle := cgo.NewHandle(callbackData) defer handle.Delete() - result := writerFunc((C.btck_WriteBytes)(C.go_writer_callback_bridge), unsafe.Pointer(handle)) + result := writerFunc((C.btck_WriteBytes)(C.go_writer_callback_bridge), unsafe.Pointer(&handle)) if result != 0 { return nil, false }