[php-src] master: Merge branch 'PHP-8.5' into PHP-8.6
| From: | Ilia Alshanetsky | Date: | Wed, 30 Sep 2026 17:51:09 +0000 |
| Subject: | [php-src] master: Merge branch 'PHP-8.5' into PHP-8.6 | ||
| Groups: | php.cvs | ||
| Request: | Send a blank email to php-cvs+get-141813@lists.php.net to get a copy of this message | ||
Author: Ilia Alshanetsky (iliaal)
Date: 2026-09-30T13:28:19-04:00
Commit: https://github.com/php/php-src/commit/c4a1e63074cdc61f190d7af215d16baa3ab7d09b
Raw diff: https://github.com/php/php-src/commit/c4a1e63074cdc61f190d7af215d16baa3ab7d09b.diff
Merge branch 'PHP-8.5' into PHP-8.6
* PHP-8.5:
ext/pdo: Report bound params and columns to GC
Changed paths:
A ext/pdo/tests/get_gc_bound_params.phpt
M NEWS
M ext/pdo/pdo_stmt.c
Diff:
diff --git a/NEWS b/NEWS
index 810a69982a79..36ae20791bb0 100644
--- a/NEWS
+++ b/NEWS
@@ -77,6 +77,8 @@ PHP NEWS
"array given" regardless of the value passed. (Ilia Alshanetsky)
. Fixed PDOStatement::bindParam() and bindColumn() leaking the driver
options value. (Ilia Alshanetsky)
+ . Fixed PDOStatement not reporting its bound parameters and columns to the
+ cycle collector. (Ilia Alshanetsky)
. Fixed PDOStatement::execute() leaving the previous result available
after a failed execution. (Ilia Alshanetsky)
. Fixed a use-after-free when bindValue()/execute()/closeCursor() is called
diff --git a/ext/pdo/pdo_stmt.c b/ext/pdo/pdo_stmt.c
index c08c093974ae..0b1dc91923a9 100644
--- a/ext/pdo/pdo_stmt.c
+++ b/ext/pdo/pdo_stmt.c
@@ -2041,6 +2041,22 @@ static HashTable *dbstmt_get_gc(zend_object *object, zval **gc_data, int
*gc_cou
} else if (default_fetch_mode == PDO_FETCH_CLASS && stmt->fetch.cls.ctor_args != NULL)
{
zend_get_gc_buffer_add_ht(gc_buffer, stmt->fetch.cls.ctor_args);
}
+ if (stmt->bound_params) {
+ zval *val;
+ ZEND_HASH_FOREACH_VAL(stmt->bound_params, val) {
+ struct pdo_bound_param_data *param = Z_PTR_P(val);
+ zend_get_gc_buffer_add_zval(gc_buffer, ¶m->parameter);
+ zend_get_gc_buffer_add_zval(gc_buffer, ¶m->driver_params);
+ } ZEND_HASH_FOREACH_END();
+ }
+ if (stmt->bound_columns) {
+ zval *val;
+ ZEND_HASH_FOREACH_VAL(stmt->bound_columns, val) {
+ struct pdo_bound_param_data *param = Z_PTR_P(val);
+ zend_get_gc_buffer_add_zval(gc_buffer, ¶m->parameter);
+ zend_get_gc_buffer_add_zval(gc_buffer, ¶m->driver_params);
+ } ZEND_HASH_FOREACH_END();
+ }
zend_get_gc_buffer_use(gc_buffer, gc_data, gc_count);
/**
diff --git a/ext/pdo/tests/get_gc_bound_params.phpt b/ext/pdo/tests/get_gc_bound_params.phpt
new file mode 100644
index 000000000000..01c824a477c0
--- /dev/null
+++ b/ext/pdo/tests/get_gc_bound_params.phpt
@@ -0,0 +1,66 @@
+--TEST--
+PDO Common: PDOStatement::get_gc() must report bound params and columns for cycle collection
+--EXTENSIONS--
+pdo
+--SKIPIF--
+<?php
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.__DIR__ .
'/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+
+class Tracked {
+ public static array $collected = [];
+
+ public function __construct(public string $name, public PDOStatement $stmt) {}
+
+ public function __destruct() {
+ self::$collected[] = $this->name;
+ }
+}
+
+$db = PDOTest::factory();
+$db->exec('CREATE TABLE get_gc_bound_params (a INT, b INT)');
+
+$insert = $db->prepare('INSERT INTO get_gc_bound_params VALUES (?, ?)');
+$param = new Tracked('param', $insert);
+$insert->bindParam(1, $param, PDO::PARAM_INT);
+$paramOption = null;
+$insert->bindParam(2, $paramOption, PDO::PARAM_INT, 0, new Tracked('param driver
option', $insert));
+
+$select = $db->query('SELECT a, b FROM get_gc_bound_params');
+$column = new Tracked('column', $select);
+$select->bindColumn(1, $column, PDO::PARAM_INT);
+$columnOption = null;
+$select->bindColumn(2, $columnOption, PDO::PARAM_INT, 0, new Tracked('column driver
option', $select));
+
+unset($param, $paramOption, $column, $columnOption, $insert, $select, $db);
+var_dump(Tracked::$collected);
+gc_collect_cycles();
+sort(Tracked::$collected);
+var_dump(Tracked::$collected);
+?>
+--CLEAN--
+<?php
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+PDOTest::dropTableIfExists($db, 'get_gc_bound_params');
+?>
+--EXPECT--
+array(0) {
+}
+array(4) {
+ [0]=>
+ string(6) "column"
+ [1]=>
+ string(20) "column driver option"
+ [2]=>
+ string(5) "param"
+ [3]=>
+ string(19) "param driver option"
+}