Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion ext/pdo/pdo_stmt.c
Original file line number Diff line number Diff line change
Expand Up @@ -1199,6 +1199,7 @@ PHP_METHOD(PDOStatement, fetchAll)
zend_class_entry *old_ce;
zval old_ctor_args, *ctor_args = NULL;
uint32_t old_arg_count;
HashTable *current_ctor = NULL;

ZEND_PARSE_PARAMETERS_START(0, 3)
Z_PARAM_OPTIONAL
Expand All @@ -1217,6 +1218,10 @@ PHP_METHOD(PDOStatement, fetchAll)

old_ce = stmt->fetch.cls.ce;
ZVAL_COPY_VALUE(&old_ctor_args, &stmt->fetch.cls.ctor_args);
if (Z_TYPE(old_ctor_args) == IS_ARRAY) {
/* Protect against destruction by marking this as immutable: we consider this non-owned temporarily */
Z_TYPE_INFO(stmt->fetch.cls.ctor_args) = IS_ARRAY;
}
old_arg_count = stmt->fetch.cls.fci.param_count;

do_fetch_opt_finish(stmt, 0);
Expand All @@ -1241,7 +1246,13 @@ PHP_METHOD(PDOStatement, fetchAll)
}

if (ctor_args && zend_hash_num_elements(Z_ARRVAL_P(ctor_args)) > 0) {
ZVAL_COPY_VALUE(&stmt->fetch.cls.ctor_args, ctor_args); /* we're not going to free these */
/* We increase the refcount and store it in case usercode has been messing around with the ctor args.
* We need to store current_ctor separately as usercode may change the ctor_args which will cause a leak. */
current_ctor = Z_ARRVAL_P(ctor_args);
ZVAL_COPY(&stmt->fetch.cls.ctor_args, ctor_args);
/* Protect against destruction by marking this as immutable: we consider this non-owned
* as destruction is handled via current_ctor. */
Z_TYPE_INFO(stmt->fetch.cls.ctor_args) = IS_ARRAY;
} else {
ZVAL_UNDEF(&stmt->fetch.cls.ctor_args);
}
Expand Down Expand Up @@ -1343,9 +1354,15 @@ PHP_METHOD(PDOStatement, fetchAll)
}

do_fetch_opt_finish(stmt, 0);
if (current_ctor) {
zend_array_release(current_ctor);
}

/* Restore defaults which were changed by PDO_FETCH_CLASS mode */
stmt->fetch.cls.ce = old_ce;
/* ctor_args may have been changed to an owned object in the meantime, so destroy it.
* If it was not, then the type flags update will have protected us against destruction. */
zval_ptr_dtor(&stmt->fetch.cls.ctor_args);
ZVAL_COPY_VALUE(&stmt->fetch.cls.ctor_args, &old_ctor_args);
stmt->fetch.cls.fci.param_count = old_arg_count;

Expand Down
59 changes: 59 additions & 0 deletions ext/pdo/tests/pdo_fetch_class_change_ctor_args_during_fetch1.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
--TEST--
PDO Common: PDO::FETCH_CLASS with a constructor that changes the ctor args within PDO::fetch()
--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';
$db = PDOTest::factory();

class Test {
public string $val1;
public string $val2;

public function __construct(mixed $v) {
var_dump($v);
if ($v instanceof PDOStatement) {
$v->setFetchMode(PDO::FETCH_CLASS, 'Test', [$this->val2]);
}
}
}

$db->exec('CREATE TABLE pdo_fetch_class_change_ctor_one(id int NOT NULL PRIMARY KEY, val1 VARCHAR(10), val2 VARCHAR(10))');
$db->exec("INSERT INTO pdo_fetch_class_change_ctor_one VALUES(1, 'A', 'alpha')");
$db->exec("INSERT INTO pdo_fetch_class_change_ctor_one VALUES(2, 'B', 'beta')");
$db->exec("INSERT INTO pdo_fetch_class_change_ctor_one VALUES(3, 'C', 'gamma')");
$db->exec("INSERT INTO pdo_fetch_class_change_ctor_one VALUES(4, 'D', 'delta')");

$stmt = $db->prepare('SELECT val1, val2 FROM pdo_fetch_class_change_ctor_one');
$stmt->setFetchMode(PDO::FETCH_CLASS, 'Test', [$stmt]);

$stmt->execute();
var_dump($stmt->fetch());

?>
--CLEAN--
<?php
require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
$db = PDOTest::factory();
PDOTest::dropTableIfExists($db, "pdo_fetch_class_change_ctor_one");
?>
--EXPECTF--
object(PDOStatement)#%d (1) {
["queryString"]=>
string(54) "SELECT val1, val2 FROM pdo_fetch_class_change_ctor_one"
}
object(Test)#%d (2) {
["val1"]=>
string(1) "A"
["val2"]=>
string(5) "alpha"
}
59 changes: 59 additions & 0 deletions ext/pdo/tests/pdo_fetch_class_change_ctor_args_during_fetch2.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
--TEST--
PDO Common: PDO::FETCH_CLASS with a constructor that changes the ctor args within PDO::fetchObject()
--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';
$db = PDOTest::factory();

class Test {
public string $val1;
public string $val2;

public function __construct(mixed $v) {
var_dump($v);
if ($v instanceof PDOStatement) {
$v->setFetchMode(PDO::FETCH_CLASS, 'Test', [$this->val2]);
}
}
}

// TODO Rename pdo_fetch_class_change_ctor_two table to pdo_fetch_class_change_ctor_two in PHP-8.4
$db->exec('CREATE TABLE pdo_fetch_class_change_ctor_two(id int NOT NULL PRIMARY KEY, val1 VARCHAR(10), val2 VARCHAR(10))');
$db->exec("INSERT INTO pdo_fetch_class_change_ctor_two VALUES(1, 'A', 'alpha')");
$db->exec("INSERT INTO pdo_fetch_class_change_ctor_two VALUES(2, 'B', 'beta')");
$db->exec("INSERT INTO pdo_fetch_class_change_ctor_two VALUES(3, 'C', 'gamma')");
$db->exec("INSERT INTO pdo_fetch_class_change_ctor_two VALUES(4, 'D', 'delta')");

$stmt = $db->prepare('SELECT val1, val2 FROM pdo_fetch_class_change_ctor_two');

$stmt->execute();
var_dump($stmt->fetchObject('Test', [$stmt]));

?>
--CLEAN--
<?php
require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
$db = PDOTest::factory();
PDOTest::dropTableIfExists($db, "pdo_fetch_class_change_ctor_two");
?>
--EXPECTF--
object(PDOStatement)#%s (1) {
["queryString"]=>
string(54) "SELECT val1, val2 FROM pdo_fetch_class_change_ctor_two"
}
object(Test)#%s (2) {
["val1"]=>
string(1) "A"
["val2"]=>
string(5) "alpha"
}
86 changes: 86 additions & 0 deletions ext/pdo/tests/pdo_fetch_class_change_ctor_args_during_fetch3.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
--TEST--
PDO Common: PDO::FETCH_CLASS with a constructor that changes the ctor args within PDO::fetchAll() (no args variation)
--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';
$db = PDOTest::factory();

class Test {
public string $val1;
public string $val2;

public function __construct(mixed $v) {
var_dump($v);
if ($v instanceof PDOStatement) {
$v->setFetchMode(PDO::FETCH_CLASS, 'Test', [$this->val2]);
}
}
}

$db->exec('CREATE TABLE pdo_fetch_class_change_ctor_three(id int NOT NULL PRIMARY KEY, val1 VARCHAR(10), val2 VARCHAR(10))');
$db->exec("INSERT INTO pdo_fetch_class_change_ctor_three VALUES(1, 'A', 'alpha')");
$db->exec("INSERT INTO pdo_fetch_class_change_ctor_three VALUES(2, 'B', 'beta')");
$db->exec("INSERT INTO pdo_fetch_class_change_ctor_three VALUES(3, 'C', 'gamma')");
$db->exec("INSERT INTO pdo_fetch_class_change_ctor_three VALUES(4, 'D', 'delta')");

$stmt = $db->prepare('SELECT val1, val2 FROM pdo_fetch_class_change_ctor_three');
$stmt->setFetchMode(PDO::FETCH_CLASS, 'Test', [$stmt]);

$stmt->execute();
var_dump($stmt->fetchAll());

?>
--CLEAN--
<?php
require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
$db = PDOTest::factory();
PDOTest::dropTableIfExists($db, "pdo_fetch_class_change_ctor_three");
?>
--EXPECTF--
object(PDOStatement)#%d (1) {
["queryString"]=>
string(56) "SELECT val1, val2 FROM pdo_fetch_class_change_ctor_three"
}
string(5) "alpha"
string(5) "alpha"
string(5) "alpha"
array(4) {
[0]=>
object(Test)#%d (2) {
["val1"]=>
string(1) "A"
["val2"]=>
string(5) "alpha"
}
[1]=>
object(Test)#%d (2) {
["val1"]=>
string(1) "B"
["val2"]=>
string(4) "beta"
}
[2]=>
object(Test)#%d (2) {
["val1"]=>
string(1) "C"
["val2"]=>
string(5) "gamma"
}
[3]=>
object(Test)#%d (2) {
["val1"]=>
string(1) "D"
["val2"]=>
string(5) "delta"
}
}
85 changes: 85 additions & 0 deletions ext/pdo/tests/pdo_fetch_class_change_ctor_args_during_fetch4.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
--TEST--
PDO Common: PDO::FETCH_CLASS with a constructor that changes the ctor args within PDO::fetchAll() (args in fetchAll)
--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';
$db = PDOTest::factory();

class Test {
public string $val1;
public string $val2;

public function __construct(mixed $v) {
var_dump($v);
if ($v instanceof PDOStatement) {
$v->setFetchMode(PDO::FETCH_CLASS, 'Test', [$this->val2]);
}
}
}

$db->exec('CREATE TABLE pdo_fetch_class_change_ctor_four(id int NOT NULL PRIMARY KEY, val1 VARCHAR(10), val2 VARCHAR(10))');
$db->exec("INSERT INTO pdo_fetch_class_change_ctor_four VALUES(1, 'A', 'alpha')");
$db->exec("INSERT INTO pdo_fetch_class_change_ctor_four VALUES(2, 'B', 'beta')");
$db->exec("INSERT INTO pdo_fetch_class_change_ctor_four VALUES(3, 'C', 'gamma')");
$db->exec("INSERT INTO pdo_fetch_class_change_ctor_four VALUES(4, 'D', 'delta')");

$stmt = $db->prepare('SELECT val1, val2 FROM pdo_fetch_class_change_ctor_four');

$stmt->execute();
var_dump($stmt->fetchAll(PDO::FETCH_CLASS, 'Test', [$stmt]));

?>
--CLEAN--
<?php
require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
$db = PDOTest::factory();
PDOTest::dropTableIfExists($db, "pdo_fetch_class_change_ctor_four");
?>
--EXPECTF--
object(PDOStatement)#%d (1) {
["queryString"]=>
string(55) "SELECT val1, val2 FROM pdo_fetch_class_change_ctor_four"
}
string(5) "alpha"
string(5) "alpha"
string(5) "alpha"
array(4) {
[0]=>
object(Test)#%d (2) {
["val1"]=>
string(1) "A"
["val2"]=>
string(5) "alpha"
}
[1]=>
object(Test)#%d (2) {
["val1"]=>
string(1) "B"
["val2"]=>
string(4) "beta"
}
[2]=>
object(Test)#%d (2) {
["val1"]=>
string(1) "C"
["val2"]=>
string(5) "gamma"
}
[3]=>
object(Test)#%d (2) {
["val1"]=>
string(1) "D"
["val2"]=>
string(5) "delta"
}
}
55 changes: 55 additions & 0 deletions ext/pdo/tests/pdo_fetch_class_change_ctor_args_during_fetch5.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
--TEST--
PDO Common: PDO::FETCH_CLASS with a constructor that changes the ctor args within PDO::fetchAll() (via warning and error handler)
--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';
$db = PDOTest::factory();

// Warning to hook into error handler
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);

class B {
public function __construct() {}
}

$db->exec('CREATE TABLE pdo_fetch_class_change_ctor_five(id int NOT NULL PRIMARY KEY, val1 VARCHAR(10), val2 VARCHAR(10))');
$db->exec("INSERT INTO pdo_fetch_class_change_ctor_five VALUES(1, 'A', 'alpha')");
$db->exec("INSERT INTO pdo_fetch_class_change_ctor_five VALUES(2, 'B', 'beta')");
$db->exec("INSERT INTO pdo_fetch_class_change_ctor_five VALUES(3, 'C', 'gamma')");
$db->exec("INSERT INTO pdo_fetch_class_change_ctor_five VALUES(4, 'D', 'delta')");

$stmt = $db->prepare('SELECT val1, val2 FROM pdo_fetch_class_change_ctor_five');
$stmt->execute();

function stuffingErrorHandler(int $errno, string $errstr, string $errfile, int $errline) {
global $stmt;
$stmt->setFetchMode(PDO::FETCH_CLASS, 'B', [$errstr]);
echo $errstr, PHP_EOL;
}
set_error_handler(stuffingErrorHandler(...));

var_dump($stmt->fetchAll(PDO::FETCH_CLASS|PDO::FETCH_SERIALIZE, 'B', [$stmt]));

?>
--CLEAN--
<?php
require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
$db = PDOTest::factory();
PDOTest::dropTableIfExists($db, "pdo_fetch_class_change_ctor_five");
?>
--EXPECTF--
PDOStatement::fetchAll(): The PDO::FETCH_SERIALIZE mode is deprecated
PDOStatement::fetchAll(): SQLSTATE[HY000]: General error: cannot unserialize class
PDOStatement::fetchAll(): SQLSTATE[HY000]: General error%S
array(0) {
}
Loading
Loading