Skip to content

Possible memory leak of cname in (*Conn).CreateFunction #18

Description

@OvOhao

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))
	}
  1. Application code calls conn.CreateFunction(name, ...) to register a user-defined SQL function.
  2. Line 145 calls C.CString(name), allocating len(name)+1 bytes with malloc.
  3. 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.
  4. 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.
  5. 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))

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions