Possible memory leak of cname in (*Conn).CreateFunction
I found a possible memory leak in (*Conn).CreateFunction. The SQL function name is allocated with
C.CString and passed to sqlite3_create_function_v2, which documents zFunctionName as a
const char * that it copies into the connection's function table — the caller retains ownership.
No release exists on any path, and the allocating line already carries the author's own
// TODO: free? marker. Since UDFs are typically registered per connection, a pooled server leaks
one block per function per connection for the process lifetime.
File: func.go
Function: github.com/go-llsqlite/crawshaw.(*Conn).CreateFunction (func.go:144-187)
func (conn *Conn) CreateFunction(name string, deterministic bool, numArgs int, xFunc, xStep func(Context, ...Value), xFinal func(Context)) error {
cname := C.CString(name) // TODO: free?
eTextRep := C.int(C.SQLITE_UTF8)
res := C.go_sqlite3_create_function_v2(
conn.conn,
cname,
C.int(numArgs),
eTextRep,
pApp,
funcfn,
stepfn,
finalfn,
(*[0]byte)(C.c_destroy_tramp),
)
return conn.reserr("Conn.CreateFunction", name, res)
}
The cgo shim shows the name goes straight through to SQLite (func.go:22-43):
// static int go_sqlite3_create_function_v2(
// sqlite3 *db,
// const char *zFunctionName,
// int nArg,
// int eTextRep,
// uintptr_t pApp,
// void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
// void (*xStep)(sqlite3_context*,int,sqlite3_value**),
// void (*xFinal)(sqlite3_context*),
// void(*xDestroy)(void*)
// ) {
// return sqlite3_create_function_v2(
// db,
// zFunctionName,
// nArg,
// eTextRep,
// (void *)pApp,
// xFunc,
// xStep,
// xFinal,
// xDestroy);
// }
and the repository's own idiom elsewhere, extension.go:47-52:
cext := C.CString(ext)
defer C.free(unsafe.Pointer(cext))
var centry *C.char
if entry != "" {
centry = C.CString(entry)
defer C.free(unsafe.Pointer(centry))
}
- Application code calls
conn.CreateFunction(name, ...) to register a user-defined SQL function.
- Line 145 calls
C.CString(name), allocating len(name)+1 bytes with malloc.
- Lines 175-185 pass
cname as zFunctionName through the shim to sqlite3_create_function_v2.
SQLite copies the name into its function table; the caller's buffer is not adopted.
cname is a plain local. It is not stored on conn, not returned, and not placed into the
xfuncs registry — that registry holds only the Go-side *xfunc keyed by an integer id
(lines 155-163), and the *xfunc stores name as a Go string, not the C pointer. The
c_destroy_tramp destructor passed at line 184 is SQLite's xDestroy for the application-data
pointer (pApp, an integer id), not for zFunctionName.
- There is no
defer C.free(unsafe.Pointer(cname)) anywhere in the function and no early return
between line 145 and the sole exit at line 186, so every call leaks one malloc block.
Go trigger (if applicable):
conn, _ := sqlite.OpenConn(":memory:", 0)
defer conn.Close()
for i := 0; i < 1_000_000; i++ {
// Each registration malloc's len(name)+1 bytes and never frees them.
_ = conn.CreateFunction("myfunc", true, 1,
func(ctx sqlite.Context, values ...sqlite.Value) { ctx.ResultInt(1) },
nil, nil)
}
Re-registering the same name is legal in SQLite (it replaces the existing entry) and leaks on every
call. The leak is synchronous, needs no GC step, and conn.Close() releases only SQLite's own
structures. It is platform-independent.
Suggested fix: release the name after the registration call, matching extension.go:48:
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
Possible memory leak of
cnamein(*Conn).CreateFunctionI found a possible memory leak in
(*Conn).CreateFunction. The SQL function name is allocated withC.CStringand passed tosqlite3_create_function_v2, which documentszFunctionNameas aconst char *that it copies into the connection's function table — the caller retains ownership.No release exists on any path, and the allocating line already carries the author's own
// TODO: free?marker. Since UDFs are typically registered per connection, a pooled server leaksone block per function per connection for the process lifetime.
File:
func.goFunction:
github.com/go-llsqlite/crawshaw.(*Conn).CreateFunction(func.go:144-187)The cgo shim shows the name goes straight through to SQLite (
func.go:22-43):and the repository's own idiom elsewhere,
extension.go:47-52:conn.CreateFunction(name, ...)to register a user-defined SQL function.C.CString(name), allocatinglen(name)+1bytes withmalloc.cnameaszFunctionNamethrough the shim tosqlite3_create_function_v2.SQLite copies the name into its function table; the caller's buffer is not adopted.
cnameis a plain local. It is not stored onconn, not returned, and not placed into thexfuncsregistry — that registry holds only the Go-side*xfunckeyed by an integer id(lines 155-163), and the
*xfuncstoresnameas a Go string, not the C pointer. Thec_destroy_trampdestructor passed at line 184 is SQLite'sxDestroyfor the application-datapointer (
pApp, an integer id), not forzFunctionName.defer C.free(unsafe.Pointer(cname))anywhere in the function and no early returnbetween line 145 and the sole exit at line 186, so every call leaks one
mallocblock.Go trigger (if applicable):
Re-registering the same name is legal in SQLite (it replaces the existing entry) and leaks on every
call. The leak is synchronous, needs no GC step, and
conn.Close()releases only SQLite's ownstructures. It is platform-independent.
Suggested fix: release the name after the registration call, matching
extension.go:48: