From 18d287e5b50301dd36171e4f3fb0760b23a95021 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sat, 21 Mar 2026 07:46:13 +0000 Subject: [PATCH 1/4] ext/sqlite3: fix wrong pointer types passed to the free list comparator. SQLite3Stmt::close() and SQLite3Result::finalize() passed php_sqlite3_stmt pointer types instead of sqlite3_stmt pointers to zend_llist_del_element, causing the comparator to never match and both methods to silently become no-ops. Regression introduced in 5eae6d14052 ("Don't store the object zval directly"). --- ext/sqlite3/sqlite3.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/sqlite3/sqlite3.c b/ext/sqlite3/sqlite3.c index cea3ca0e3fb00..be28e2bff4d92 100644 --- a/ext/sqlite3/sqlite3.c +++ b/ext/sqlite3/sqlite3.c @@ -1415,7 +1415,7 @@ PHP_METHOD(SQLite3Stmt, close) SQLITE3_CHECK_INITIALIZED(stmt_obj->db_obj, stmt_obj->initialised, SQLite3); - zend_llist_del_element(&(stmt_obj->db_obj->free_list), stmt_obj, (int (*)(void *, void *)) php_sqlite3_compare_stmt_free); + zend_llist_del_element(&(stmt_obj->db_obj->free_list), stmt_obj->stmt, (int (*)(void *, void *)) php_sqlite3_compare_stmt_free); RETURN_TRUE; } @@ -2145,7 +2145,7 @@ PHP_METHOD(SQLite3Result, finalize) /* We need to finalize an internal statement */ if (!result_obj->is_prepared_statement) { - zend_llist_del_element(&(result_obj->db_obj->free_list), &result_obj->stmt_obj, + zend_llist_del_element(&(result_obj->db_obj->free_list), result_obj->stmt_obj->stmt, (int (*)(void *, void *)) php_sqlite3_compare_stmt_free); } else { sqlite3_reset(result_obj->stmt_obj->stmt); From c66dde1bcb15540625157b925bbf6c8cad1bc852 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sat, 21 Mar 2026 11:10:08 +0000 Subject: [PATCH 2/4] ext/sqlite3: compare php_sqlite3_stmt pointers consistently. The comparator and all call sites now use php_sqlite3_stmt pointers, matching the type stored in the free list. --- ext/sqlite3/sqlite3.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ext/sqlite3/sqlite3.c b/ext/sqlite3/sqlite3.c index be28e2bff4d92..7aef25bf73b5c 100644 --- a/ext/sqlite3/sqlite3.c +++ b/ext/sqlite3/sqlite3.c @@ -38,7 +38,7 @@ ZEND_DECLARE_MODULE_GLOBALS(sqlite3) static PHP_GINIT_FUNCTION(sqlite3); static int php_sqlite3_authorizer(void *autharg, int action, const char *arg1, const char *arg2, const char *arg3, const char *arg4); static void sqlite3_param_dtor(zval *data); -static int php_sqlite3_compare_stmt_free(php_sqlite3_stmt **stmt_obj_ptr, sqlite3_stmt *statement); +static int php_sqlite3_compare_stmt_free(php_sqlite3_stmt **stmt_obj_ptr, php_sqlite3_stmt *statement); static zend_always_inline void php_sqlite3_fetch_one(int n_cols, php_sqlite3_result *result_obj, zend_long mode, zval *result); #define SQLITE3_CHECK_INITIALIZED(db_obj, member, class_name) \ @@ -1415,7 +1415,7 @@ PHP_METHOD(SQLite3Stmt, close) SQLITE3_CHECK_INITIALIZED(stmt_obj->db_obj, stmt_obj->initialised, SQLite3); - zend_llist_del_element(&(stmt_obj->db_obj->free_list), stmt_obj->stmt, (int (*)(void *, void *)) php_sqlite3_compare_stmt_free); + zend_llist_del_element(&(stmt_obj->db_obj->free_list), stmt_obj, (int (*)(void *, void *)) php_sqlite3_compare_stmt_free); RETURN_TRUE; } @@ -2145,7 +2145,7 @@ PHP_METHOD(SQLite3Result, finalize) /* We need to finalize an internal statement */ if (!result_obj->is_prepared_statement) { - zend_llist_del_element(&(result_obj->db_obj->free_list), result_obj->stmt_obj->stmt, + zend_llist_del_element(&(result_obj->db_obj->free_list), result_obj->stmt_obj, (int (*)(void *, void *)) php_sqlite3_compare_stmt_free); } else { sqlite3_reset(result_obj->stmt_obj->stmt); @@ -2260,9 +2260,9 @@ static void php_sqlite3_free_list_dtor(void **item) } /* }}} */ -static int php_sqlite3_compare_stmt_free(php_sqlite3_stmt **stmt_obj_ptr, sqlite3_stmt *statement ) /* {{{ */ +static int php_sqlite3_compare_stmt_free(php_sqlite3_stmt **stmt_obj_ptr, php_sqlite3_stmt *statement ) /* {{{ */ { - return ((*stmt_obj_ptr)->initialised && statement == (*stmt_obj_ptr)->stmt); + return ((*stmt_obj_ptr)->initialised && statement == *stmt_obj_ptr); } /* }}} */ @@ -2376,7 +2376,7 @@ static void php_sqlite3_stmt_object_free_storage(zend_object *object) /* {{{ */ } if (intern->initialised) { - zend_llist_del_element(&(intern->db_obj->free_list), intern->stmt, + zend_llist_del_element(&(intern->db_obj->free_list), intern, (int (*)(void *, void *)) php_sqlite3_compare_stmt_free); } From 0e0368df510063503c5aa17c1dd5859077f71c85 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Mon, 6 Apr 2026 14:19:00 +0100 Subject: [PATCH 3/4] Add test for the free list comparator fix. --- ext/sqlite3/tests/gh21483.phpt | 61 ++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 ext/sqlite3/tests/gh21483.phpt diff --git a/ext/sqlite3/tests/gh21483.phpt b/ext/sqlite3/tests/gh21483.phpt new file mode 100644 index 0000000000000..d10e4e11e1544 --- /dev/null +++ b/ext/sqlite3/tests/gh21483.phpt @@ -0,0 +1,61 @@ +--TEST-- +GH-21483 (SQLite3 free list comparator uses correct pointer types) +--EXTENSIONS-- +sqlite3 +--FILE-- +exec('CREATE TABLE test (id INTEGER PRIMARY KEY, val TEXT)'); +$db->exec("INSERT INTO test VALUES (1, 'a')"); +$db->exec("INSERT INTO test VALUES (2, 'b')"); +$db->exec("INSERT INTO test VALUES (3, 'c')"); + +$r1 = $db->query("SELECT * FROM test WHERE id = 1"); +$r2 = $db->query("SELECT * FROM test WHERE id = 2"); +$r3 = $db->query("SELECT * FROM test WHERE id = 3"); + +var_dump($r1->fetchArray(SQLITE3_ASSOC)); +var_dump($r2->fetchArray(SQLITE3_ASSOC)); +var_dump($r3->fetchArray(SQLITE3_ASSOC)); + +$r2->finalize(); +$r1->finalize(); +$r3->finalize(); + +$stmt = $db->prepare("SELECT * FROM test WHERE id = ?"); +$stmt->bindValue(1, 2, SQLITE3_INTEGER); +$result = $stmt->execute(); +var_dump($result->fetchArray(SQLITE3_ASSOC)); +$result->finalize(); +$stmt->close(); + +$db->close(); +echo "Done\n"; +?> +--EXPECT-- +array(2) { + ["id"]=> + int(1) + ["val"]=> + string(1) "a" +} +array(2) { + ["id"]=> + int(2) + ["val"]=> + string(1) "b" +} +array(2) { + ["id"]=> + int(3) + ["val"]=> + string(1) "c" +} +array(2) { + ["id"]=> + int(2) + ["val"]=> + string(1) "b" +} +Done From b4d49872f3cac76b50212974009a45327f392281 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Tue, 7 Apr 2026 18:20:13 +0100 Subject: [PATCH 4/4] remove pointless test --- ext/sqlite3/tests/gh21483.phpt | 61 ---------------------------------- 1 file changed, 61 deletions(-) delete mode 100644 ext/sqlite3/tests/gh21483.phpt diff --git a/ext/sqlite3/tests/gh21483.phpt b/ext/sqlite3/tests/gh21483.phpt deleted file mode 100644 index d10e4e11e1544..0000000000000 --- a/ext/sqlite3/tests/gh21483.phpt +++ /dev/null @@ -1,61 +0,0 @@ ---TEST-- -GH-21483 (SQLite3 free list comparator uses correct pointer types) ---EXTENSIONS-- -sqlite3 ---FILE-- -exec('CREATE TABLE test (id INTEGER PRIMARY KEY, val TEXT)'); -$db->exec("INSERT INTO test VALUES (1, 'a')"); -$db->exec("INSERT INTO test VALUES (2, 'b')"); -$db->exec("INSERT INTO test VALUES (3, 'c')"); - -$r1 = $db->query("SELECT * FROM test WHERE id = 1"); -$r2 = $db->query("SELECT * FROM test WHERE id = 2"); -$r3 = $db->query("SELECT * FROM test WHERE id = 3"); - -var_dump($r1->fetchArray(SQLITE3_ASSOC)); -var_dump($r2->fetchArray(SQLITE3_ASSOC)); -var_dump($r3->fetchArray(SQLITE3_ASSOC)); - -$r2->finalize(); -$r1->finalize(); -$r3->finalize(); - -$stmt = $db->prepare("SELECT * FROM test WHERE id = ?"); -$stmt->bindValue(1, 2, SQLITE3_INTEGER); -$result = $stmt->execute(); -var_dump($result->fetchArray(SQLITE3_ASSOC)); -$result->finalize(); -$stmt->close(); - -$db->close(); -echo "Done\n"; -?> ---EXPECT-- -array(2) { - ["id"]=> - int(1) - ["val"]=> - string(1) "a" -} -array(2) { - ["id"]=> - int(2) - ["val"]=> - string(1) "b" -} -array(2) { - ["id"]=> - int(3) - ["val"]=> - string(1) "c" -} -array(2) { - ["id"]=> - int(2) - ["val"]=> - string(1) "b" -} -Done