[php-src] master: Merge branch 'PHP-8.6'

From: Date: Wed, 30 Sep 2026 18:20:15 +0000
Subject: [php-src] master: Merge branch 'PHP-8.6'
Groups: php.cvs 
Request: Send a blank email to php-cvs+get-141818@lists.php.net to get a copy of this message
Author: Ilia Alshanetsky (iliaal) Date: 2026-09-30T13:56:15-04:00 Commit: https://github.com/php/php-src/commit/659272641aba394e61d8886fadfe8e424af9107b Raw diff: https://github.com/php/php-src/commit/659272641aba394e61d8886fadfe8e424af9107b.diff Merge branch 'PHP-8.6' * PHP-8.6: ext/pdo: Make PDOStatement::setFetchMode() updates atomic Changed paths: A ext/pdo/tests/pdo_setfetchmode_atomic.phpt A ext/pdo/tests/pdo_setfetchmode_reentrant_fetch.phpt A ext/pdo/tests/pdo_setfetchmode_reentrant_setmode.phpt M ext/pdo/pdo_stmt.c Diff: diff --git a/ext/pdo/pdo_stmt.c b/ext/pdo/pdo_stmt.c index 7d25427a0d42..c828cc0bccaf 100644 --- a/ext/pdo/pdo_stmt.c +++ b/ext/pdo/pdo_stmt.c @@ -1660,20 +1660,21 @@ bool pdo_stmt_setup_fetch_mode(pdo_stmt_t *stmt, zend_long mode, uint32_t mode_a uint32_t arg1_arg_num = mode_arg_num + 1; uint32_t constructor_arg_num = mode_arg_num + 2; uint32_t total_num_args = mode_arg_num + variadic_num_args; - - pdo_stmt_free_default_fetch_mode(stmt); - - stmt->default_fetch_type = stmt->dbh->default_fetch_type; + zend_long fetch_type = mode & ~PDO_FETCH_FLAGS; + zend_long fetch_column = 0; + zend_class_entry *fetch_class = NULL; + HashTable *fetch_ctor_args = NULL; + zend_object *fetch_into = NULL; + zend_object *old_into = NULL; + HashTable *old_ctor_args = NULL; flags = mode & PDO_FETCH_FLAGS; - if (!pdo_verify_fetch_mode(stmt->default_fetch_type, mode, mode_arg_num, false)) { + if (!pdo_verify_fetch_mode(stmt->dbh->default_fetch_type, mode, mode_arg_num, false)) { return false; } - bool use_default = (mode & ~PDO_FETCH_FLAGS) == PDO_FETCH_USE_DEFAULT; - - switch (mode & ~PDO_FETCH_FLAGS) { + switch (fetch_type) { case PDO_FETCH_USE_DEFAULT: case PDO_FETCH_LAZY: case PDO_FETCH_ASSOC: @@ -1708,7 +1709,7 @@ bool pdo_stmt_setup_fetch_mode(pdo_stmt_t *stmt, zend_long mode, uint32_t mode_a zend_argument_value_error(arg1_arg_num, "must be greater than or equal to 0"); return false; } - stmt->fetch.column = Z_LVAL(args[0]); + fetch_column = Z_LVAL(args[0]); break; case PDO_FETCH_CLASS: { @@ -1759,11 +1760,10 @@ bool pdo_stmt_setup_fetch_mode(pdo_stmt_t *stmt, zend_long mode, uint32_t mode_a zend_argument_value_error(3, "must be empty when class provided in argument #2 ($class) does not have a constructor"); return false; } - GC_TRY_ADDREF(Z_ARRVAL(args[1])); - stmt->fetch.cls.ctor_args = Z_ARRVAL(args[1]); + fetch_ctor_args = Z_ARRVAL(args[1]); } } - stmt->fetch.cls.ce = cep; + fetch_class = cep; } break; } @@ -1780,18 +1780,52 @@ bool pdo_stmt_setup_fetch_mode(pdo_stmt_t *stmt, zend_long mode, uint32_t mode_a return false; } - GC_ADDREF(Z_OBJ(args[0])); - stmt->fetch.into = Z_OBJ(args[0]); + fetch_into = Z_OBJ(args[0]); break; default: zend_argument_value_error(mode_arg_num, "must be one of the PDO::FETCH_* constants"); return false; } - if (!use_default) { + if ((stmt->default_fetch_type & ~PDO_FETCH_FLAGS) == PDO_FETCH_INTO) { + old_into = stmt->fetch.into; + } else if ((stmt->default_fetch_type & ~PDO_FETCH_FLAGS) == PDO_FETCH_CLASS) { + old_ctor_args = stmt->fetch.cls.ctor_args; + } + memset(&stmt->fetch, 0, sizeof(stmt->fetch)); + + switch (fetch_type) { + case PDO_FETCH_COLUMN: + stmt->fetch.column = fetch_column; + break; + case PDO_FETCH_CLASS: + stmt->fetch.cls.ce = fetch_class; + if (fetch_ctor_args) { + GC_TRY_ADDREF(fetch_ctor_args); + stmt->fetch.cls.ctor_args = fetch_ctor_args; + } + break; + case PDO_FETCH_INTO: + GC_ADDREF(fetch_into); + stmt->fetch.into = fetch_into; + break; + default: + break; + } + + if (fetch_type == PDO_FETCH_USE_DEFAULT) { + stmt->default_fetch_type = stmt->dbh->default_fetch_type; + } else { stmt->default_fetch_type = mode; } + if (old_into) { + OBJ_RELEASE(old_into); + } + if (old_ctor_args) { + zend_array_release(old_ctor_args); + } + return true; } diff --git a/ext/pdo/tests/pdo_setfetchmode_atomic.phpt b/ext/pdo/tests/pdo_setfetchmode_atomic.phpt new file mode 100644 index 000000000000..8751c27673a5 --- /dev/null +++ b/ext/pdo/tests/pdo_setfetchmode_atomic.phpt @@ -0,0 +1,73 @@ +--TEST-- +PDO Common: Failed setFetchMode must preserve the prior fetch mode and payload +--EXTENSIONS-- +pdo +--SKIPIF-- +<?php +$dir = getenv('REDIR_TEST_DIR'); +if ($dir === false) { + 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'; +$pdo = PDOTest::factory(); + +class FetchClass +{ + public int $i; + + public function __construct(public string $marker) + { + } +} + +$pdo->exec('CREATE TABLE pdo_setfetchmode_atomic (i INT)'); +$pdo->exec('INSERT INTO pdo_setfetchmode_atomic (i) VALUES (1)'); +$stmt = $pdo->query('SELECT i FROM pdo_setfetchmode_atomic'); +$row = new stdClass; +$stmt->setFetchMode(PDO::FETCH_INTO, $row); +try { + $stmt->setFetchMode(PDO::FETCH_CLASS); +} catch (Throwable $e) { + echo "into error: ", $e::class, "\n"; +} +$r = $stmt->fetch(); + +echo "into object preserved: "; +var_dump($r === $row); +echo "into value: ", $row->i, "\n"; + +$stmt = $pdo->query('SELECT i FROM pdo_setfetchmode_atomic'); +$stmt->setFetchMode(PDO::FETCH_CLASS, FetchClass::class, ['kept']); +try { + $stmt->setFetchMode(PDO::FETCH_CLASS); +} catch (Throwable $e) { + echo "class error: ", $e::class, "\n"; +} +$r = $stmt->fetch(); + +echo "class preserved: ", $r::class, "\n"; +echo "constructor argument: ", $r->marker, "\n"; +echo "class value: ", $r->i, "\n"; +?> +--CLEAN-- +<?php +require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc'; +$pdo = PDOTest::factory(); +PDOTest::dropTableIfExists($pdo, 'pdo_setfetchmode_atomic'); +?> +--EXPECT-- +into error: ArgumentCountError +into object preserved: bool(true) +into value: 1 +class error: ArgumentCountError +class preserved: FetchClass +constructor argument: kept +class value: 1 diff --git a/ext/pdo/tests/pdo_setfetchmode_reentrant_fetch.phpt b/ext/pdo/tests/pdo_setfetchmode_reentrant_fetch.phpt new file mode 100644 index 000000000000..bc163366fa8d --- /dev/null +++ b/ext/pdo/tests/pdo_setfetchmode_reentrant_fetch.phpt @@ -0,0 +1,75 @@ +--TEST-- +PDO Common: setFetchMode autoload re-entry observes the prior complete mode +--EXTENSIONS-- +pdo +--SKIPIF-- +<?php +$dir = getenv('REDIR_TEST_DIR'); +if ($dir === false) { + 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'; +$pdo = PDOTest::factory(); + +class PriorFetchClass +{ + public int $i; + + public function __construct(public string $marker) + { + } +} + +$pdo->exec('CREATE TABLE pdo_setfetchmode_reentrant_fetch (i INT)'); +$pdo->exec('INSERT INTO pdo_setfetchmode_reentrant_fetch (i) VALUES (1)'); +$pdo->exec('INSERT INTO pdo_setfetchmode_reentrant_fetch (i) VALUES (2)'); + +$stmt = $pdo->query('SELECT i FROM pdo_setfetchmode_reentrant_fetch ORDER BY i'); +$into = new stdClass; +$stmt->setFetchMode(PDO::FETCH_INTO, $into); +$autoload = function (string $class) use ($stmt, $into): void { + $row = $stmt->fetch(); + echo "into re-entry preserved: "; + var_dump($row === $into); + eval("class $class { public int \$i; }"); +}; +spl_autoload_register($autoload); +$stmt->setFetchMode(PDO::FETCH_CLASS, 'ReenteredIntoClass'); +spl_autoload_unregister($autoload); +$row = $stmt->fetch(); +echo "into re-entry value: ", $into->i, "\n"; +echo "outer into class: ", $row::class, " ", $row->i, "\n"; + +$stmt = $pdo->query('SELECT i FROM pdo_setfetchmode_reentrant_fetch ORDER BY i'); +$stmt->setFetchMode(PDO::FETCH_CLASS, PriorFetchClass::class, ['kept']); +$autoload = function (string $class) use ($stmt): void { + $row = $stmt->fetch(); + echo "class re-entry: ", $row::class, " ", $row->marker, " ", $row->i, "\n"; + eval("class $class { public int \$i; }"); +}; +spl_autoload_register($autoload); +$stmt->setFetchMode(PDO::FETCH_CLASS, 'ReenteredFetchClass'); +spl_autoload_unregister($autoload); +$row = $stmt->fetch(); +echo "outer class: ", $row::class, " ", $row->i, "\n"; +?> +--CLEAN-- +<?php +require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc'; +$pdo = PDOTest::factory(); +PDOTest::dropTableIfExists($pdo, 'pdo_setfetchmode_reentrant_fetch'); +?> +--EXPECT-- +into re-entry preserved: bool(true) +into re-entry value: 1 +outer into class: ReenteredIntoClass 2 +class re-entry: PriorFetchClass kept 1 +outer class: ReenteredFetchClass 2 diff --git a/ext/pdo/tests/pdo_setfetchmode_reentrant_setmode.phpt b/ext/pdo/tests/pdo_setfetchmode_reentrant_setmode.phpt new file mode 100644 index 000000000000..7eb68e10136d --- /dev/null +++ b/ext/pdo/tests/pdo_setfetchmode_reentrant_setmode.phpt @@ -0,0 +1,53 @@ +--TEST-- +PDO Common: setFetchMode autoload re-entry may change the statement safely +--EXTENSIONS-- +pdo +--SKIPIF-- +<?php +$dir = getenv('REDIR_TEST_DIR'); +if ($dir === false) { + 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'; +$pdo = PDOTest::factory(); + +$pdo->exec('CREATE TABLE pdo_setfetchmode_reentrant_setmode (i INT)'); +$pdo->exec('INSERT INTO pdo_setfetchmode_reentrant_setmode (i) VALUES (1)'); +$stmt = $pdo->query('SELECT i FROM pdo_setfetchmode_reentrant_setmode'); +$first = new stdClass; +$second = new stdClass; +$stmt->setFetchMode(PDO::FETCH_INTO, $first); + +$autoload = function (string $class) use ($stmt, $second): void { + $stmt->setFetchMode(PDO::FETCH_INTO, $second); + eval("class $class { public int \$i; }"); +}; +spl_autoload_register($autoload); +$stmt->setFetchMode(PDO::FETCH_CLASS, 'ReenteredSetModeClass'); +spl_autoload_unregister($autoload); + +$row = $stmt->fetch(); +echo $row::class, " ", $row->i, "\n"; +echo "first changed: "; +var_dump(isset($first->i)); +echo "second changed: "; +var_dump(isset($second->i)); +?> +--CLEAN-- +<?php +require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc'; +$pdo = PDOTest::factory(); +PDOTest::dropTableIfExists($pdo, 'pdo_setfetchmode_reentrant_setmode'); +?> +--EXPECT-- +ReenteredSetModeClass 1 +first changed: bool(false) +second changed: bool(false)

« previous php.cvs (#141818) next »