Skip to content

Commit 6592726

Browse files
committed
Merge branch 'PHP-8.6'
* PHP-8.6: ext/pdo: Make PDOStatement::setFetchMode() updates atomic
2 parents 6387fc5 + b113697 commit 6592726

4 files changed

Lines changed: 250 additions & 15 deletions

File tree

‎ext/pdo/pdo_stmt.c‎

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1660,20 +1660,21 @@ bool pdo_stmt_setup_fetch_mode(pdo_stmt_t *stmt, zend_long mode, uint32_t mode_a
16601660
uint32_t arg1_arg_num = mode_arg_num + 1;
16611661
uint32_t constructor_arg_num = mode_arg_num + 2;
16621662
uint32_t total_num_args = mode_arg_num + variadic_num_args;
1663-
1664-
pdo_stmt_free_default_fetch_mode(stmt);
1665-
1666-
stmt->default_fetch_type = stmt->dbh->default_fetch_type;
1663+
zend_long fetch_type = mode & ~PDO_FETCH_FLAGS;
1664+
zend_long fetch_column = 0;
1665+
zend_class_entry *fetch_class = NULL;
1666+
HashTable *fetch_ctor_args = NULL;
1667+
zend_object *fetch_into = NULL;
1668+
zend_object *old_into = NULL;
1669+
HashTable *old_ctor_args = NULL;
16671670

16681671
flags = mode & PDO_FETCH_FLAGS;
16691672

1670-
if (!pdo_verify_fetch_mode(stmt->default_fetch_type, mode, mode_arg_num, false)) {
1673+
if (!pdo_verify_fetch_mode(stmt->dbh->default_fetch_type, mode, mode_arg_num, false)) {
16711674
return false;
16721675
}
16731676

1674-
bool use_default = (mode & ~PDO_FETCH_FLAGS) == PDO_FETCH_USE_DEFAULT;
1675-
1676-
switch (mode & ~PDO_FETCH_FLAGS) {
1677+
switch (fetch_type) {
16771678
case PDO_FETCH_USE_DEFAULT:
16781679
case PDO_FETCH_LAZY:
16791680
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
17081709
zend_argument_value_error(arg1_arg_num, "must be greater than or equal to 0");
17091710
return false;
17101711
}
1711-
stmt->fetch.column = Z_LVAL(args[0]);
1712+
fetch_column = Z_LVAL(args[0]);
17121713
break;
17131714

17141715
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
17591760
zend_argument_value_error(3, "must be empty when class provided in argument #2 ($class) does not have a constructor");
17601761
return false;
17611762
}
1762-
GC_TRY_ADDREF(Z_ARRVAL(args[1]));
1763-
stmt->fetch.cls.ctor_args = Z_ARRVAL(args[1]);
1763+
fetch_ctor_args = Z_ARRVAL(args[1]);
17641764
}
17651765
}
1766-
stmt->fetch.cls.ce = cep;
1766+
fetch_class = cep;
17671767
}
17681768
break;
17691769
}
@@ -1780,18 +1780,52 @@ bool pdo_stmt_setup_fetch_mode(pdo_stmt_t *stmt, zend_long mode, uint32_t mode_a
17801780
return false;
17811781
}
17821782

1783-
GC_ADDREF(Z_OBJ(args[0]));
1784-
stmt->fetch.into = Z_OBJ(args[0]);
1783+
fetch_into = Z_OBJ(args[0]);
17851784
break;
17861785
default:
17871786
zend_argument_value_error(mode_arg_num, "must be one of the PDO::FETCH_* constants");
17881787
return false;
17891788
}
17901789

1791-
if (!use_default) {
1790+
if ((stmt->default_fetch_type & ~PDO_FETCH_FLAGS) == PDO_FETCH_INTO) {
1791+
old_into = stmt->fetch.into;
1792+
} else if ((stmt->default_fetch_type & ~PDO_FETCH_FLAGS) == PDO_FETCH_CLASS) {
1793+
old_ctor_args = stmt->fetch.cls.ctor_args;
1794+
}
1795+
memset(&stmt->fetch, 0, sizeof(stmt->fetch));
1796+
1797+
switch (fetch_type) {
1798+
case PDO_FETCH_COLUMN:
1799+
stmt->fetch.column = fetch_column;
1800+
break;
1801+
case PDO_FETCH_CLASS:
1802+
stmt->fetch.cls.ce = fetch_class;
1803+
if (fetch_ctor_args) {
1804+
GC_TRY_ADDREF(fetch_ctor_args);
1805+
stmt->fetch.cls.ctor_args = fetch_ctor_args;
1806+
}
1807+
break;
1808+
case PDO_FETCH_INTO:
1809+
GC_ADDREF(fetch_into);
1810+
stmt->fetch.into = fetch_into;
1811+
break;
1812+
default:
1813+
break;
1814+
}
1815+
1816+
if (fetch_type == PDO_FETCH_USE_DEFAULT) {
1817+
stmt->default_fetch_type = stmt->dbh->default_fetch_type;
1818+
} else {
17921819
stmt->default_fetch_type = mode;
17931820
}
17941821

1822+
if (old_into) {
1823+
OBJ_RELEASE(old_into);
1824+
}
1825+
if (old_ctor_args) {
1826+
zend_array_release(old_ctor_args);
1827+
}
1828+
17951829
return true;
17961830
}
17971831

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
--TEST--
2+
PDO Common: Failed setFetchMode must preserve the prior fetch mode and payload
3+
--EXTENSIONS--
4+
pdo
5+
--SKIPIF--
6+
<?php
7+
$dir = getenv('REDIR_TEST_DIR');
8+
if ($dir === false) {
9+
die('skip no driver');
10+
}
11+
require_once $dir . 'pdo_test.inc';
12+
PDOTest::skip();
13+
?>
14+
--FILE--
15+
<?php
16+
if (getenv('REDIR_TEST_DIR') === false) {
17+
putenv('REDIR_TEST_DIR=' . __DIR__ . '/../../pdo/tests/');
18+
}
19+
require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
20+
$pdo = PDOTest::factory();
21+
22+
class FetchClass
23+
{
24+
public int $i;
25+
26+
public function __construct(public string $marker)
27+
{
28+
}
29+
}
30+
31+
$pdo->exec('CREATE TABLE pdo_setfetchmode_atomic (i INT)');
32+
$pdo->exec('INSERT INTO pdo_setfetchmode_atomic (i) VALUES (1)');
33+
$stmt = $pdo->query('SELECT i FROM pdo_setfetchmode_atomic');
34+
$row = new stdClass;
35+
$stmt->setFetchMode(PDO::FETCH_INTO, $row);
36+
try {
37+
$stmt->setFetchMode(PDO::FETCH_CLASS);
38+
} catch (Throwable $e) {
39+
echo "into error: ", $e::class, "\n";
40+
}
41+
$r = $stmt->fetch();
42+
43+
echo "into object preserved: ";
44+
var_dump($r === $row);
45+
echo "into value: ", $row->i, "\n";
46+
47+
$stmt = $pdo->query('SELECT i FROM pdo_setfetchmode_atomic');
48+
$stmt->setFetchMode(PDO::FETCH_CLASS, FetchClass::class, ['kept']);
49+
try {
50+
$stmt->setFetchMode(PDO::FETCH_CLASS);
51+
} catch (Throwable $e) {
52+
echo "class error: ", $e::class, "\n";
53+
}
54+
$r = $stmt->fetch();
55+
56+
echo "class preserved: ", $r::class, "\n";
57+
echo "constructor argument: ", $r->marker, "\n";
58+
echo "class value: ", $r->i, "\n";
59+
?>
60+
--CLEAN--
61+
<?php
62+
require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
63+
$pdo = PDOTest::factory();
64+
PDOTest::dropTableIfExists($pdo, 'pdo_setfetchmode_atomic');
65+
?>
66+
--EXPECT--
67+
into error: ArgumentCountError
68+
into object preserved: bool(true)
69+
into value: 1
70+
class error: ArgumentCountError
71+
class preserved: FetchClass
72+
constructor argument: kept
73+
class value: 1
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
--TEST--
2+
PDO Common: setFetchMode autoload re-entry observes the prior complete mode
3+
--EXTENSIONS--
4+
pdo
5+
--SKIPIF--
6+
<?php
7+
$dir = getenv('REDIR_TEST_DIR');
8+
if ($dir === false) {
9+
die('skip no driver');
10+
}
11+
require_once $dir . 'pdo_test.inc';
12+
PDOTest::skip();
13+
?>
14+
--FILE--
15+
<?php
16+
if (getenv('REDIR_TEST_DIR') === false) {
17+
putenv('REDIR_TEST_DIR=' . __DIR__ . '/../../pdo/tests/');
18+
}
19+
require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
20+
$pdo = PDOTest::factory();
21+
22+
class PriorFetchClass
23+
{
24+
public int $i;
25+
26+
public function __construct(public string $marker)
27+
{
28+
}
29+
}
30+
31+
$pdo->exec('CREATE TABLE pdo_setfetchmode_reentrant_fetch (i INT)');
32+
$pdo->exec('INSERT INTO pdo_setfetchmode_reentrant_fetch (i) VALUES (1)');
33+
$pdo->exec('INSERT INTO pdo_setfetchmode_reentrant_fetch (i) VALUES (2)');
34+
35+
$stmt = $pdo->query('SELECT i FROM pdo_setfetchmode_reentrant_fetch ORDER BY i');
36+
$into = new stdClass;
37+
$stmt->setFetchMode(PDO::FETCH_INTO, $into);
38+
$autoload = function (string $class) use ($stmt, $into): void {
39+
$row = $stmt->fetch();
40+
echo "into re-entry preserved: ";
41+
var_dump($row === $into);
42+
eval("class $class { public int \$i; }");
43+
};
44+
spl_autoload_register($autoload);
45+
$stmt->setFetchMode(PDO::FETCH_CLASS, 'ReenteredIntoClass');
46+
spl_autoload_unregister($autoload);
47+
$row = $stmt->fetch();
48+
echo "into re-entry value: ", $into->i, "\n";
49+
echo "outer into class: ", $row::class, " ", $row->i, "\n";
50+
51+
$stmt = $pdo->query('SELECT i FROM pdo_setfetchmode_reentrant_fetch ORDER BY i');
52+
$stmt->setFetchMode(PDO::FETCH_CLASS, PriorFetchClass::class, ['kept']);
53+
$autoload = function (string $class) use ($stmt): void {
54+
$row = $stmt->fetch();
55+
echo "class re-entry: ", $row::class, " ", $row->marker, " ", $row->i, "\n";
56+
eval("class $class { public int \$i; }");
57+
};
58+
spl_autoload_register($autoload);
59+
$stmt->setFetchMode(PDO::FETCH_CLASS, 'ReenteredFetchClass');
60+
spl_autoload_unregister($autoload);
61+
$row = $stmt->fetch();
62+
echo "outer class: ", $row::class, " ", $row->i, "\n";
63+
?>
64+
--CLEAN--
65+
<?php
66+
require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
67+
$pdo = PDOTest::factory();
68+
PDOTest::dropTableIfExists($pdo, 'pdo_setfetchmode_reentrant_fetch');
69+
?>
70+
--EXPECT--
71+
into re-entry preserved: bool(true)
72+
into re-entry value: 1
73+
outer into class: ReenteredIntoClass 2
74+
class re-entry: PriorFetchClass kept 1
75+
outer class: ReenteredFetchClass 2
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
--TEST--
2+
PDO Common: setFetchMode autoload re-entry may change the statement safely
3+
--EXTENSIONS--
4+
pdo
5+
--SKIPIF--
6+
<?php
7+
$dir = getenv('REDIR_TEST_DIR');
8+
if ($dir === false) {
9+
die('skip no driver');
10+
}
11+
require_once $dir . 'pdo_test.inc';
12+
PDOTest::skip();
13+
?>
14+
--FILE--
15+
<?php
16+
if (getenv('REDIR_TEST_DIR') === false) {
17+
putenv('REDIR_TEST_DIR=' . __DIR__ . '/../../pdo/tests/');
18+
}
19+
require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
20+
$pdo = PDOTest::factory();
21+
22+
$pdo->exec('CREATE TABLE pdo_setfetchmode_reentrant_setmode (i INT)');
23+
$pdo->exec('INSERT INTO pdo_setfetchmode_reentrant_setmode (i) VALUES (1)');
24+
$stmt = $pdo->query('SELECT i FROM pdo_setfetchmode_reentrant_setmode');
25+
$first = new stdClass;
26+
$second = new stdClass;
27+
$stmt->setFetchMode(PDO::FETCH_INTO, $first);
28+
29+
$autoload = function (string $class) use ($stmt, $second): void {
30+
$stmt->setFetchMode(PDO::FETCH_INTO, $second);
31+
eval("class $class { public int \$i; }");
32+
};
33+
spl_autoload_register($autoload);
34+
$stmt->setFetchMode(PDO::FETCH_CLASS, 'ReenteredSetModeClass');
35+
spl_autoload_unregister($autoload);
36+
37+
$row = $stmt->fetch();
38+
echo $row::class, " ", $row->i, "\n";
39+
echo "first changed: ";
40+
var_dump(isset($first->i));
41+
echo "second changed: ";
42+
var_dump(isset($second->i));
43+
?>
44+
--CLEAN--
45+
<?php
46+
require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
47+
$pdo = PDOTest::factory();
48+
PDOTest::dropTableIfExists($pdo, 'pdo_setfetchmode_reentrant_setmode');
49+
?>
50+
--EXPECT--
51+
ReenteredSetModeClass 1
52+
first changed: bool(false)
53+
second changed: bool(false)

0 commit comments

Comments
 (0)