This is an automated email from the ASF dual-hosted git repository.

lidavidm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-adbc.git


The following commit(s) were added to refs/heads/main by this push:
     new 257e3a211 fix(go/adbc/pkg): cgo handle pattern (#4287)
257e3a211 is described below

commit 257e3a211b64c7b5509c301fcee30a178e43dfdf
Author: Matt Topol <[email protected]>
AuthorDate: Thu Apr 30 20:11:25 2026 -0400

    fix(go/adbc/pkg): cgo handle pattern (#4287)
    
    Looking into
    https://github.com/adbc-drivers/mysql/issues/99#issuecomment-4351135775
    resulted in finding some of these issues, along with
    https://github.com/apache/arrow-go/pull/793
    
    ## Summary
    Three bugs found and fixed in the CGO driver template
    (`_tmpl/driver.go.tmpl`) and all generated drivers
    (`flightsql`, `snowflake`, `panicdummy`).
    ### Bug 1 — off-by-one in `exportStringOption` (buffer overwrite)
    `exportStringOption` wrote the null terminator to
    `sink[lenWithTerminator]`
    (`= sink[len(val)+1]`) instead of `sink[len(val)]`. When the caller
    supplied
    exactly the minimum buffer size (`len(val)+1`), this wrote one byte past
    the
    end of the allocated buffer.
    ### Bug 2 — fragile `cgo.Handle` recovery in Release functions
    
    The four Release functions (`ArrayStreamRelease`, `DatabaseRelease`,
      `ConnectionRelease`, `StatementRelease`) recovered the handle from
    `private_data` using `(*(*cgo.Handle)(ptr))` — reinterpreting the
    C-allocated `uintptr_t` wrapper as a `*cgo.Handle`. This worked by
    coincidence (both are `uintptr`-sized) but was inconsistent with
    `getFromHandle` and relied on an undocumented type-size coincidence.
    Introduces `handleFromPtr(ptr unsafe.Pointer) cgo.Handle` as the single
    canonical read-back path, used by both `getFromHandle` and all Release
    functions. The misleading comment claiming the GC would corrupt the
    handle
    is replaced with an accurate explanation of the actual CGO rule being
    satisfied.
    ### Bug 3 — unnecessary C allocation for handle storage; wrong `Delete`
    ordering
    
    `createHandle` allocated a `uintptr_t` via `C.calloc` to hold the
    handle's
    numeric value, then stored a pointer to that allocation in
    `private_data`.
    This was not necessary: `cgo.Handle` is `type Handle uintptr` — an
    integer,
    not a Go heap pointer — so the CGO checker does not object to storing it
    directly in a pointer-sized `void*` field.
    The C allocation is eliminated entirely. `createHandle` now stores the
    handle
    value directly via `unsafe.Pointer(uintptr(hndl))`, and `handleFromPtr`
    casts
    it back with `cgo.Handle(uintptr(ptr))`. This removes a `calloc`/`free`
    pair
    from every New/Release call path and eliminates any possibility of a
    leak from
    an early return between allocation and free.
    Additionally, the Release functions were calling `h.Delete()` before
    `h.Value()` in some paths, which would panic — `Delete` removes the
    entry from
    the handle map, invalidating any subsequent `Value` call. The correct
    sequence
    is now applied consistently in all four Release functions:
    1. Nil `private_data` (idempotence guard for double-release)
    2. `h.Value()` — extract the Go object while the handle is still live
    3. `h.Delete()` — remove from the map
      4. Use the extracted object
---
 go/adbc/pkg/_tmpl/driver.go.tmpl | 56 ++++++++++++++++++----------------------
 go/adbc/pkg/flightsql/driver.go  | 56 ++++++++++++++++++----------------------
 go/adbc/pkg/panicdummy/driver.go | 56 ++++++++++++++++++----------------------
 go/adbc/pkg/snowflake/driver.go  | 56 ++++++++++++++++++----------------------
 4 files changed, 100 insertions(+), 124 deletions(-)

diff --git a/go/adbc/pkg/_tmpl/driver.go.tmpl b/go/adbc/pkg/_tmpl/driver.go.tmpl
index d68170f2d..05b2b11a3 100644
--- a/go/adbc/pkg/_tmpl/driver.go.tmpl
+++ b/go/adbc/pkg/_tmpl/driver.go.tmpl
@@ -230,22 +230,22 @@ func printLoggingHelp() {
 }
 
 
-// Allocate a new cgo.Handle and store its address in a heap-allocated
-// uintptr_t.  Experimentally, this was found to be necessary, else
-// something (the Go runtime?) would corrupt (garbage-collect?) the
-// handle.
+// cgo.Handle is a uintptr integer (not a pointer). Packing it directly into
+// a void* field is safe: the CGO checker only rejects Go heap pointers, and
+// handle values (small non-zero integers from a global counter) never alias
+// Go-allocated memory. The GC does not scan C-managed memory, so it will
+// never misinterpret the stored integer as a live pointer. No C allocation
+// is needed — the handle value itself fits in the pointer-sized field.
 func createHandle(hndl cgo.Handle) unsafe.Pointer {
-       // uintptr_t* hptr = malloc(sizeof(uintptr_t));
-       hptr := (*C.uintptr_t)(C.calloc(C.sizeof_uintptr_t, C.size_t(1)))
-       // *hptr = (uintptr)hndl;
-       *hptr = C.uintptr_t(uintptr(hndl))
-       return unsafe.Pointer(hptr)
+       return unsafe.Pointer(uintptr(hndl))
+}
+
+func handleFromPtr(ptr unsafe.Pointer) cgo.Handle {
+       return cgo.Handle(uintptr(ptr))
 }
 
 func getFromHandle[T any](ptr unsafe.Pointer) *T {
-       // uintptr_t* hptr = (uintptr_t*)ptr;
-       hptr := (*C.uintptr_t)(ptr)
-       return cgo.Handle((uintptr)(*hptr)).Value().(*T)
+       return handleFromPtr(ptr).Value().(*T)
 }
 
 func exportStringOption(val string, out *C.char, length *C.size_t) 
C.AdbcStatusCode {
@@ -253,7 +253,7 @@ func exportStringOption(val string, out *C.char, length 
*C.size_t) C.AdbcStatusC
        if lenWithTerminator <= *length {
                sink := fromCArr[byte]((*byte)(unsafe.Pointer(out)), 
int(*length))
                copy(sink, val)
-               sink[lenWithTerminator] = 0
+               sink[len(val)] = 0
        }
        *length = lenWithTerminator
        return C.ADBC_STATUS_OK
@@ -417,17 +417,16 @@ func {{.Prefix}}ArrayStreamRelease(stream 
*C.struct_ArrowArrayStream) {
        if stream == nil || stream.release != 
(*[0]byte)(C.{{.Prefix}}ArrayStreamRelease) || stream.private_data == nil  {
                return
        }
-       h := (*(*cgo.Handle)(stream.private_data))
+       h := handleFromPtr(stream.private_data)
+       stream.private_data = nil
 
        cStream := h.Value().(*cArrayStream)
+       h.Delete()
        cStream.rdr.Release()
        if cStream.adbcErr != nil {
                C.{{.Prefix}}errRelease(cStream.adbcErr)
                C.free(unsafe.Pointer(cStream.adbcErr))
        }
-       C.free(unsafe.Pointer(stream.private_data))
-       stream.private_data = nil
-       h.Delete()
        runtime.GC()
 }
 
@@ -614,19 +613,16 @@ func {{.Prefix}}DatabaseRelease(db 
*C.struct_AdbcDatabase, err *C.struct_AdbcErr
        if !checkDBAlloc(db, err, "AdbcDatabaseRelease") {
                return C.ADBC_STATUS_INVALID_STATE
        }
-       h := (*(*cgo.Handle)(db.private_data))
+       h := handleFromPtr(db.private_data)
+       db.private_data = nil
 
        cdb := h.Value().(*cDatabase)
+       h.Delete()
        if cdb.db != nil {
                cdb.db.Close()
                cdb.db = nil
        }
        cdb.opts = nil
-       if db.private_data != nil {
-               C.free(unsafe.Pointer(db.private_data))
-               db.private_data = nil
-       }
-       h.Delete()
        // manually trigger GC for two reasons:
        //  1. ASAN expects the release callback to be called before
        //     the process ends, but GC is not deterministic. So by manually
@@ -1029,15 +1025,14 @@ func {{.Prefix}}ConnectionRelease(cnxn 
*C.struct_AdbcConnection, err *C.struct_A
        if !checkConnAlloc(cnxn, err, "AdbcConnectionRelease") {
                return C.ADBC_STATUS_INVALID_STATE
        }
-       h := (*(*cgo.Handle)(cnxn.private_data))
+       h := handleFromPtr(cnxn.private_data)
+       cnxn.private_data = nil
 
        conn := h.Value().(*cConn)
+       h.Delete()
        defer func() {
                conn.cancelContext()
                conn.cnxn = nil
-               C.free(cnxn.private_data)
-               cnxn.private_data = nil
-               h.Delete()
                // manually trigger GC for two reasons:
                //  1. ASAN expects the release callback to be called before
                //     the process ends, but GC is not deterministic. So by 
manually
@@ -1479,15 +1474,14 @@ func {{.Prefix}}StatementRelease(stmt 
*C.struct_AdbcStatement, err *C.struct_Adb
        if !checkStmtAlloc(stmt, err, "AdbcStatementRelease") {
                return C.ADBC_STATUS_INVALID_STATE
        }
-       h := (*(*cgo.Handle)(stmt.private_data))
+       h := handleFromPtr(stmt.private_data)
+       stmt.private_data = nil
 
        st := h.Value().(*cStmt)
+       h.Delete()
        defer func() {
                st.cancelContext()
                st.stmt = nil
-               C.free(stmt.private_data)
-               stmt.private_data = nil
-               h.Delete()
                // manually trigger GC for two reasons:
                //  1. ASAN expects the release callback to be called before
                //     the process ends, but GC is not deterministic. So by 
manually
diff --git a/go/adbc/pkg/flightsql/driver.go b/go/adbc/pkg/flightsql/driver.go
index defe870ff..3cecf4f79 100644
--- a/go/adbc/pkg/flightsql/driver.go
+++ b/go/adbc/pkg/flightsql/driver.go
@@ -233,22 +233,22 @@ func printLoggingHelp() {
        fmt.Fprintf(os.Stderr, "FlightSQL: to enable logging, set %s to 
'debug', 'info', 'warn', or 'error'", logLevelEnvVar)
 }
 
-// Allocate a new cgo.Handle and store its address in a heap-allocated
-// uintptr_t.  Experimentally, this was found to be necessary, else
-// something (the Go runtime?) would corrupt (garbage-collect?) the
-// handle.
+// cgo.Handle is a uintptr integer (not a pointer). Packing it directly into
+// a void* field is safe: the CGO checker only rejects Go heap pointers, and
+// handle values (small non-zero integers from a global counter) never alias
+// Go-allocated memory. The GC does not scan C-managed memory, so it will
+// never misinterpret the stored integer as a live pointer. No C allocation
+// is needed — the handle value itself fits in the pointer-sized field.
 func createHandle(hndl cgo.Handle) unsafe.Pointer {
-       // uintptr_t* hptr = malloc(sizeof(uintptr_t));
-       hptr := (*C.uintptr_t)(C.calloc(C.sizeof_uintptr_t, C.size_t(1)))
-       // *hptr = (uintptr)hndl;
-       *hptr = C.uintptr_t(uintptr(hndl))
-       return unsafe.Pointer(hptr)
+       return unsafe.Pointer(uintptr(hndl))
+}
+
+func handleFromPtr(ptr unsafe.Pointer) cgo.Handle {
+       return cgo.Handle(uintptr(ptr))
 }
 
 func getFromHandle[T any](ptr unsafe.Pointer) *T {
-       // uintptr_t* hptr = (uintptr_t*)ptr;
-       hptr := (*C.uintptr_t)(ptr)
-       return cgo.Handle((uintptr)(*hptr)).Value().(*T)
+       return handleFromPtr(ptr).Value().(*T)
 }
 
 func exportStringOption(val string, out *C.char, length *C.size_t) 
C.AdbcStatusCode {
@@ -256,7 +256,7 @@ func exportStringOption(val string, out *C.char, length 
*C.size_t) C.AdbcStatusC
        if lenWithTerminator <= *length {
                sink := fromCArr[byte]((*byte)(unsafe.Pointer(out)), 
int(*length))
                copy(sink, val)
-               sink[lenWithTerminator] = 0
+               sink[len(val)] = 0
        }
        *length = lenWithTerminator
        return C.ADBC_STATUS_OK
@@ -420,17 +420,16 @@ func FlightSQLArrayStreamRelease(stream 
*C.struct_ArrowArrayStream) {
        if stream == nil || stream.release != 
(*[0]byte)(C.FlightSQLArrayStreamRelease) || stream.private_data == nil {
                return
        }
-       h := (*(*cgo.Handle)(stream.private_data))
+       h := handleFromPtr(stream.private_data)
+       stream.private_data = nil
 
        cStream := h.Value().(*cArrayStream)
+       h.Delete()
        cStream.rdr.Release()
        if cStream.adbcErr != nil {
                C.FlightSQLerrRelease(cStream.adbcErr)
                C.free(unsafe.Pointer(cStream.adbcErr))
        }
-       C.free(unsafe.Pointer(stream.private_data))
-       stream.private_data = nil
-       h.Delete()
        runtime.GC()
 }
 
@@ -617,19 +616,16 @@ func FlightSQLDatabaseRelease(db *C.struct_AdbcDatabase, 
err *C.struct_AdbcError
        if !checkDBAlloc(db, err, "AdbcDatabaseRelease") {
                return C.ADBC_STATUS_INVALID_STATE
        }
-       h := (*(*cgo.Handle)(db.private_data))
+       h := handleFromPtr(db.private_data)
+       db.private_data = nil
 
        cdb := h.Value().(*cDatabase)
+       h.Delete()
        if cdb.db != nil {
                cdb.db.Close()
                cdb.db = nil
        }
        cdb.opts = nil
-       if db.private_data != nil {
-               C.free(unsafe.Pointer(db.private_data))
-               db.private_data = nil
-       }
-       h.Delete()
        // manually trigger GC for two reasons:
        //  1. ASAN expects the release callback to be called before
        //     the process ends, but GC is not deterministic. So by manually
@@ -1032,15 +1028,14 @@ func FlightSQLConnectionRelease(cnxn 
*C.struct_AdbcConnection, err *C.struct_Adb
        if !checkConnAlloc(cnxn, err, "AdbcConnectionRelease") {
                return C.ADBC_STATUS_INVALID_STATE
        }
-       h := (*(*cgo.Handle)(cnxn.private_data))
+       h := handleFromPtr(cnxn.private_data)
+       cnxn.private_data = nil
 
        conn := h.Value().(*cConn)
+       h.Delete()
        defer func() {
                conn.cancelContext()
                conn.cnxn = nil
-               C.free(cnxn.private_data)
-               cnxn.private_data = nil
-               h.Delete()
                // manually trigger GC for two reasons:
                //  1. ASAN expects the release callback to be called before
                //     the process ends, but GC is not deterministic. So by 
manually
@@ -1482,15 +1477,14 @@ func FlightSQLStatementRelease(stmt 
*C.struct_AdbcStatement, err *C.struct_AdbcE
        if !checkStmtAlloc(stmt, err, "AdbcStatementRelease") {
                return C.ADBC_STATUS_INVALID_STATE
        }
-       h := (*(*cgo.Handle)(stmt.private_data))
+       h := handleFromPtr(stmt.private_data)
+       stmt.private_data = nil
 
        st := h.Value().(*cStmt)
+       h.Delete()
        defer func() {
                st.cancelContext()
                st.stmt = nil
-               C.free(stmt.private_data)
-               stmt.private_data = nil
-               h.Delete()
                // manually trigger GC for two reasons:
                //  1. ASAN expects the release callback to be called before
                //     the process ends, but GC is not deterministic. So by 
manually
diff --git a/go/adbc/pkg/panicdummy/driver.go b/go/adbc/pkg/panicdummy/driver.go
index f7b55eda1..07bbc4ac6 100644
--- a/go/adbc/pkg/panicdummy/driver.go
+++ b/go/adbc/pkg/panicdummy/driver.go
@@ -233,22 +233,22 @@ func printLoggingHelp() {
        fmt.Fprintf(os.Stderr, "PanicDummy: to enable logging, set %s to 
'debug', 'info', 'warn', or 'error'", logLevelEnvVar)
 }
 
-// Allocate a new cgo.Handle and store its address in a heap-allocated
-// uintptr_t.  Experimentally, this was found to be necessary, else
-// something (the Go runtime?) would corrupt (garbage-collect?) the
-// handle.
+// cgo.Handle is a uintptr integer (not a pointer). Packing it directly into
+// a void* field is safe: the CGO checker only rejects Go heap pointers, and
+// handle values (small non-zero integers from a global counter) never alias
+// Go-allocated memory. The GC does not scan C-managed memory, so it will
+// never misinterpret the stored integer as a live pointer. No C allocation
+// is needed — the handle value itself fits in the pointer-sized field.
 func createHandle(hndl cgo.Handle) unsafe.Pointer {
-       // uintptr_t* hptr = malloc(sizeof(uintptr_t));
-       hptr := (*C.uintptr_t)(C.calloc(C.sizeof_uintptr_t, C.size_t(1)))
-       // *hptr = (uintptr)hndl;
-       *hptr = C.uintptr_t(uintptr(hndl))
-       return unsafe.Pointer(hptr)
+       return unsafe.Pointer(uintptr(hndl))
+}
+
+func handleFromPtr(ptr unsafe.Pointer) cgo.Handle {
+       return cgo.Handle(uintptr(ptr))
 }
 
 func getFromHandle[T any](ptr unsafe.Pointer) *T {
-       // uintptr_t* hptr = (uintptr_t*)ptr;
-       hptr := (*C.uintptr_t)(ptr)
-       return cgo.Handle((uintptr)(*hptr)).Value().(*T)
+       return handleFromPtr(ptr).Value().(*T)
 }
 
 func exportStringOption(val string, out *C.char, length *C.size_t) 
C.AdbcStatusCode {
@@ -256,7 +256,7 @@ func exportStringOption(val string, out *C.char, length 
*C.size_t) C.AdbcStatusC
        if lenWithTerminator <= *length {
                sink := fromCArr[byte]((*byte)(unsafe.Pointer(out)), 
int(*length))
                copy(sink, val)
-               sink[lenWithTerminator] = 0
+               sink[len(val)] = 0
        }
        *length = lenWithTerminator
        return C.ADBC_STATUS_OK
@@ -420,17 +420,16 @@ func PanicDummyArrayStreamRelease(stream 
*C.struct_ArrowArrayStream) {
        if stream == nil || stream.release != 
(*[0]byte)(C.PanicDummyArrayStreamRelease) || stream.private_data == nil {
                return
        }
-       h := (*(*cgo.Handle)(stream.private_data))
+       h := handleFromPtr(stream.private_data)
+       stream.private_data = nil
 
        cStream := h.Value().(*cArrayStream)
+       h.Delete()
        cStream.rdr.Release()
        if cStream.adbcErr != nil {
                C.PanicDummyerrRelease(cStream.adbcErr)
                C.free(unsafe.Pointer(cStream.adbcErr))
        }
-       C.free(unsafe.Pointer(stream.private_data))
-       stream.private_data = nil
-       h.Delete()
        runtime.GC()
 }
 
@@ -617,19 +616,16 @@ func PanicDummyDatabaseRelease(db *C.struct_AdbcDatabase, 
err *C.struct_AdbcErro
        if !checkDBAlloc(db, err, "AdbcDatabaseRelease") {
                return C.ADBC_STATUS_INVALID_STATE
        }
-       h := (*(*cgo.Handle)(db.private_data))
+       h := handleFromPtr(db.private_data)
+       db.private_data = nil
 
        cdb := h.Value().(*cDatabase)
+       h.Delete()
        if cdb.db != nil {
                cdb.db.Close()
                cdb.db = nil
        }
        cdb.opts = nil
-       if db.private_data != nil {
-               C.free(unsafe.Pointer(db.private_data))
-               db.private_data = nil
-       }
-       h.Delete()
        // manually trigger GC for two reasons:
        //  1. ASAN expects the release callback to be called before
        //     the process ends, but GC is not deterministic. So by manually
@@ -1032,15 +1028,14 @@ func PanicDummyConnectionRelease(cnxn 
*C.struct_AdbcConnection, err *C.struct_Ad
        if !checkConnAlloc(cnxn, err, "AdbcConnectionRelease") {
                return C.ADBC_STATUS_INVALID_STATE
        }
-       h := (*(*cgo.Handle)(cnxn.private_data))
+       h := handleFromPtr(cnxn.private_data)
+       cnxn.private_data = nil
 
        conn := h.Value().(*cConn)
+       h.Delete()
        defer func() {
                conn.cancelContext()
                conn.cnxn = nil
-               C.free(cnxn.private_data)
-               cnxn.private_data = nil
-               h.Delete()
                // manually trigger GC for two reasons:
                //  1. ASAN expects the release callback to be called before
                //     the process ends, but GC is not deterministic. So by 
manually
@@ -1482,15 +1477,14 @@ func PanicDummyStatementRelease(stmt 
*C.struct_AdbcStatement, err *C.struct_Adbc
        if !checkStmtAlloc(stmt, err, "AdbcStatementRelease") {
                return C.ADBC_STATUS_INVALID_STATE
        }
-       h := (*(*cgo.Handle)(stmt.private_data))
+       h := handleFromPtr(stmt.private_data)
+       stmt.private_data = nil
 
        st := h.Value().(*cStmt)
+       h.Delete()
        defer func() {
                st.cancelContext()
                st.stmt = nil
-               C.free(stmt.private_data)
-               stmt.private_data = nil
-               h.Delete()
                // manually trigger GC for two reasons:
                //  1. ASAN expects the release callback to be called before
                //     the process ends, but GC is not deterministic. So by 
manually
diff --git a/go/adbc/pkg/snowflake/driver.go b/go/adbc/pkg/snowflake/driver.go
index 30b9fe04f..616858751 100644
--- a/go/adbc/pkg/snowflake/driver.go
+++ b/go/adbc/pkg/snowflake/driver.go
@@ -233,22 +233,22 @@ func printLoggingHelp() {
        fmt.Fprintf(os.Stderr, "Snowflake: to enable logging, set %s to 
'debug', 'info', 'warn', or 'error'", logLevelEnvVar)
 }
 
-// Allocate a new cgo.Handle and store its address in a heap-allocated
-// uintptr_t.  Experimentally, this was found to be necessary, else
-// something (the Go runtime?) would corrupt (garbage-collect?) the
-// handle.
+// cgo.Handle is a uintptr integer (not a pointer). Packing it directly into
+// a void* field is safe: the CGO checker only rejects Go heap pointers, and
+// handle values (small non-zero integers from a global counter) never alias
+// Go-allocated memory. The GC does not scan C-managed memory, so it will
+// never misinterpret the stored integer as a live pointer. No C allocation
+// is needed — the handle value itself fits in the pointer-sized field.
 func createHandle(hndl cgo.Handle) unsafe.Pointer {
-       // uintptr_t* hptr = malloc(sizeof(uintptr_t));
-       hptr := (*C.uintptr_t)(C.calloc(C.sizeof_uintptr_t, C.size_t(1)))
-       // *hptr = (uintptr)hndl;
-       *hptr = C.uintptr_t(uintptr(hndl))
-       return unsafe.Pointer(hptr)
+       return unsafe.Pointer(uintptr(hndl))
+}
+
+func handleFromPtr(ptr unsafe.Pointer) cgo.Handle {
+       return cgo.Handle(uintptr(ptr))
 }
 
 func getFromHandle[T any](ptr unsafe.Pointer) *T {
-       // uintptr_t* hptr = (uintptr_t*)ptr;
-       hptr := (*C.uintptr_t)(ptr)
-       return cgo.Handle((uintptr)(*hptr)).Value().(*T)
+       return handleFromPtr(ptr).Value().(*T)
 }
 
 func exportStringOption(val string, out *C.char, length *C.size_t) 
C.AdbcStatusCode {
@@ -256,7 +256,7 @@ func exportStringOption(val string, out *C.char, length 
*C.size_t) C.AdbcStatusC
        if lenWithTerminator <= *length {
                sink := fromCArr[byte]((*byte)(unsafe.Pointer(out)), 
int(*length))
                copy(sink, val)
-               sink[lenWithTerminator] = 0
+               sink[len(val)] = 0
        }
        *length = lenWithTerminator
        return C.ADBC_STATUS_OK
@@ -420,17 +420,16 @@ func SnowflakeArrayStreamRelease(stream 
*C.struct_ArrowArrayStream) {
        if stream == nil || stream.release != 
(*[0]byte)(C.SnowflakeArrayStreamRelease) || stream.private_data == nil {
                return
        }
-       h := (*(*cgo.Handle)(stream.private_data))
+       h := handleFromPtr(stream.private_data)
+       stream.private_data = nil
 
        cStream := h.Value().(*cArrayStream)
+       h.Delete()
        cStream.rdr.Release()
        if cStream.adbcErr != nil {
                C.SnowflakeerrRelease(cStream.adbcErr)
                C.free(unsafe.Pointer(cStream.adbcErr))
        }
-       C.free(unsafe.Pointer(stream.private_data))
-       stream.private_data = nil
-       h.Delete()
        runtime.GC()
 }
 
@@ -617,19 +616,16 @@ func SnowflakeDatabaseRelease(db *C.struct_AdbcDatabase, 
err *C.struct_AdbcError
        if !checkDBAlloc(db, err, "AdbcDatabaseRelease") {
                return C.ADBC_STATUS_INVALID_STATE
        }
-       h := (*(*cgo.Handle)(db.private_data))
+       h := handleFromPtr(db.private_data)
+       db.private_data = nil
 
        cdb := h.Value().(*cDatabase)
+       h.Delete()
        if cdb.db != nil {
                cdb.db.Close()
                cdb.db = nil
        }
        cdb.opts = nil
-       if db.private_data != nil {
-               C.free(unsafe.Pointer(db.private_data))
-               db.private_data = nil
-       }
-       h.Delete()
        // manually trigger GC for two reasons:
        //  1. ASAN expects the release callback to be called before
        //     the process ends, but GC is not deterministic. So by manually
@@ -1032,15 +1028,14 @@ func SnowflakeConnectionRelease(cnxn 
*C.struct_AdbcConnection, err *C.struct_Adb
        if !checkConnAlloc(cnxn, err, "AdbcConnectionRelease") {
                return C.ADBC_STATUS_INVALID_STATE
        }
-       h := (*(*cgo.Handle)(cnxn.private_data))
+       h := handleFromPtr(cnxn.private_data)
+       cnxn.private_data = nil
 
        conn := h.Value().(*cConn)
+       h.Delete()
        defer func() {
                conn.cancelContext()
                conn.cnxn = nil
-               C.free(cnxn.private_data)
-               cnxn.private_data = nil
-               h.Delete()
                // manually trigger GC for two reasons:
                //  1. ASAN expects the release callback to be called before
                //     the process ends, but GC is not deterministic. So by 
manually
@@ -1482,15 +1477,14 @@ func SnowflakeStatementRelease(stmt 
*C.struct_AdbcStatement, err *C.struct_AdbcE
        if !checkStmtAlloc(stmt, err, "AdbcStatementRelease") {
                return C.ADBC_STATUS_INVALID_STATE
        }
-       h := (*(*cgo.Handle)(stmt.private_data))
+       h := handleFromPtr(stmt.private_data)
+       stmt.private_data = nil
 
        st := h.Value().(*cStmt)
+       h.Delete()
        defer func() {
                st.cancelContext()
                st.stmt = nil
-               C.free(stmt.private_data)
-               stmt.private_data = nil
-               h.Delete()
                // manually trigger GC for two reasons:
                //  1. ASAN expects the release callback to be called before
                //     the process ends, but GC is not deterministic. So by 
manually

Reply via email to