Skip to content

Instantly share code, notes, and snippets.

@devdanzin
Last active March 18, 2026 10:01
Show Gist options
  • Select an option

  • Save devdanzin/69aff7ff2d64ce8c7c21622f7f36e84c to your computer and use it in GitHub Desktop.

Select an option

Save devdanzin/69aff7ff2d64ce8c7c21622f7f36e84c to your computer and use it in GitHub Desktop.
CPython bug: sqlite3 collation assertion failure + missing PyErr_NoMemory

_sqlite/connection.c: Assertion failure replacing active collation + missing PyErr_NoMemory

CPython issue (assertion): python/cpython#146090

Summary

Two bugs in _sqlite/connection.c:

  1. Assertion failure (line 2201): When sqlite3_create_collation_v2() fails with SQLITE_BUSY, error cleanup calls free_callback_context(ctx) directly — but ctx has refcount=1. The function asserts ctx->refcount == 0 → crash in debug builds.

  2. Missing PyErr_NoMemory (line 1061): create_callback_context returns NULL without exception when PyMem_Malloc fails. All 7 callers propagate NULL → SystemError.

Fixes

  1. Use decref_callback_context(ctx) instead of free_callback_context(ctx) at line 2201.
  2. Add if (ctx == NULL) { return (callback_context *)PyErr_NoMemory(); }.

Reproducer — Bug 1: Assertion failure

import sqlite3

conn = sqlite3.connect(":memory:")
conn.create_collation("mycoll", lambda a, b: (a > b) - (a < b))
conn.execute("CREATE TABLE t(x TEXT)")
for i in range(100):
    conn.execute("INSERT INTO t VALUES (?)", (f"item_{i:03d}",))
conn.commit()

cursor = conn.execute("SELECT x FROM t ORDER BY x COLLATE mycoll")
next(cursor)

conn.create_collation("mycoll", lambda a, b: 0)  # SQLITE_BUSY → assertion

Reproducer — Bug 2: Missing PyErr_NoMemory

import sqlite3, _testcapi

conn = sqlite3.connect(":memory:")
_testcapi.set_nomemory(1, 0)
try:
    conn.set_trace_callback(lambda s: None)
except (MemoryError, SystemError):
    pass
finally:
    _testcapi.remove_mem_hooks()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment