diff --git a/ext/pdo/pdo_dbh.c b/ext/pdo/pdo_dbh.c index 9588f23d420c5..be24b23a5adb6 100644 --- a/ext/pdo/pdo_dbh.c +++ b/ext/pdo/pdo_dbh.c @@ -542,15 +542,12 @@ PHP_METHOD(PDO, connect) } /* }}} */ -static zval *pdo_stmt_instantiate(pdo_dbh_t *dbh, zval *object, zend_class_entry *dbstmt_ce, zval *ctor_args) /* {{{ */ +static zval *pdo_stmt_instantiate(pdo_dbh_t *dbh, zval *object, zend_class_entry *dbstmt_ce, const HashTable *ctor_args) /* {{{ */ { - if (!Z_ISUNDEF_P(ctor_args)) { + if (ctor_args && dbstmt_ce->constructor == NULL) { /* This implies an error within PDO if this does not hold */ - ZEND_ASSERT(Z_TYPE_P(ctor_args) == IS_ARRAY); - if (!dbstmt_ce->constructor) { - zend_throw_error(NULL, "User-supplied statement does not accept constructor arguments"); - return NULL; - } + zend_throw_error(NULL, "User-supplied statement does not accept constructor arguments"); + return NULL; } if (UNEXPECTED(object_init_ex(object, dbstmt_ce) != SUCCESS)) { @@ -583,10 +580,11 @@ PHP_METHOD(PDO, prepare) { pdo_stmt_t *stmt; zend_string *statement; - zval *options = NULL, *value, *item, ctor_args; + zval *options = NULL, *value, *item; zend_class_entry *dbstmt_ce, *pce; pdo_dbh_object_t *dbh_obj = Z_PDO_OBJECT_P(ZEND_THIS); pdo_dbh_t *dbh = dbh_obj->inner; + HashTable *ctor_args = NULL; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_STR(statement) @@ -633,16 +631,14 @@ PHP_METHOD(PDO, prepare) zend_zval_value_name(value)); RETURN_THROWS(); } - ZVAL_COPY_VALUE(&ctor_args, item); - } else { - ZVAL_UNDEF(&ctor_args); + ctor_args = Z_ARRVAL_P(item); } } else { dbstmt_ce = dbh->def_stmt_ce; - ZVAL_COPY_VALUE(&ctor_args, &dbh->def_stmt_ctor_args); + ctor_args = dbh->def_stmt_ctor_args; } - if (!pdo_stmt_instantiate(dbh, return_value, dbstmt_ce, &ctor_args)) { + if (!pdo_stmt_instantiate(dbh, return_value, dbstmt_ce, ctor_args)) { RETURN_THROWS(); } stmt = Z_PDO_STMT_P(return_value); @@ -656,11 +652,7 @@ PHP_METHOD(PDO, prepare) stmt->database_object_handle = &dbh_obj->std; if (dbh->methods->preparer(dbh, statement, stmt, options)) { - if (Z_TYPE(ctor_args) == IS_ARRAY) { - pdo_stmt_construct(stmt, return_value, dbstmt_ce, Z_ARRVAL(ctor_args)); - } else { - pdo_stmt_construct(stmt, return_value, dbstmt_ce, /* ctor_args */ NULL); - } + pdo_stmt_construct(stmt, return_value, dbstmt_ce, ctor_args); return; } @@ -924,9 +916,9 @@ static bool pdo_dbh_attribute_set(pdo_dbh_t *dbh, zend_long attr, zval *value, u return false; } dbh->def_stmt_ce = pce; - if (!Z_ISUNDEF(dbh->def_stmt_ctor_args)) { - zval_ptr_dtor(&dbh->def_stmt_ctor_args); - ZVAL_UNDEF(&dbh->def_stmt_ctor_args); + if (dbh->def_stmt_ctor_args != NULL) { + zend_array_release(dbh->def_stmt_ctor_args); + dbh->def_stmt_ctor_args = NULL; } if ((item = zend_hash_index_find(Z_ARRVAL_P(value), 1)) != NULL) { if (Z_TYPE_P(item) != IS_ARRAY) { @@ -934,7 +926,9 @@ static bool pdo_dbh_attribute_set(pdo_dbh_t *dbh, zend_long attr, zval *value, u zend_zval_value_name(value)); return false; } - ZVAL_COPY(&dbh->def_stmt_ctor_args, item); + dbh->def_stmt_ctor_args = Z_ARRVAL_P(item); + /* Increase refcount */ + GC_TRY_ADDREF(dbh->def_stmt_ctor_args); } return true; } @@ -1013,9 +1007,10 @@ PHP_METHOD(PDO, getAttribute) case PDO_ATTR_STATEMENT_CLASS: array_init(return_value); add_next_index_str(return_value, zend_string_copy(dbh->def_stmt_ce->name)); - if (!Z_ISUNDEF(dbh->def_stmt_ctor_args)) { - Z_TRY_ADDREF(dbh->def_stmt_ctor_args); - add_next_index_zval(return_value, &dbh->def_stmt_ctor_args); + if (dbh->def_stmt_ctor_args != NULL) { + /* Increase refcount */ + GC_TRY_ADDREF(dbh->def_stmt_ctor_args); + add_next_index_array(return_value, dbh->def_stmt_ctor_args); } return; @@ -1205,7 +1200,7 @@ PHP_METHOD(PDO, query) PDO_DBH_CLEAR_ERR(); - if (!pdo_stmt_instantiate(dbh, return_value, dbh->def_stmt_ce, &dbh->def_stmt_ctor_args)) { + if (!pdo_stmt_instantiate(dbh, return_value, dbh->def_stmt_ce, dbh->def_stmt_ctor_args)) { RETURN_THROWS(); } stmt = Z_PDO_STMT_P(return_value); @@ -1233,11 +1228,7 @@ PHP_METHOD(PDO, query) stmt->executed = 1; } if (ret) { - if (Z_TYPE(dbh->def_stmt_ctor_args) == IS_ARRAY) { - pdo_stmt_construct(stmt, return_value, dbh->def_stmt_ce, Z_ARRVAL(dbh->def_stmt_ctor_args)); - } else { - pdo_stmt_construct(stmt, return_value, dbh->def_stmt_ce, /* ctor_args */ NULL); - } + pdo_stmt_construct(stmt, return_value, dbh->def_stmt_ce, dbh->def_stmt_ctor_args); return; } } @@ -1432,7 +1423,10 @@ static HashTable *dbh_get_gc(zend_object *object, zval **gc_data, int *gc_count) { pdo_dbh_t *dbh = php_pdo_dbh_fetch_inner(object); zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create(); - zend_get_gc_buffer_add_zval(gc_buffer, &dbh->def_stmt_ctor_args); + if (dbh->def_stmt_ctor_args != NULL) { + zend_get_gc_buffer_add_ht(gc_buffer, dbh->def_stmt_ctor_args); + dbh->def_stmt_ctor_args = NULL; + } if (dbh->methods && dbh->methods->get_gc) { dbh->methods->get_gc(dbh, gc_buffer); } @@ -1496,8 +1490,9 @@ static void dbh_free(pdo_dbh_t *dbh, bool free_persistent) pefree((char *)dbh->persistent_id, dbh->is_persistent); } - if (!Z_ISUNDEF(dbh->def_stmt_ctor_args)) { - zval_ptr_dtor(&dbh->def_stmt_ctor_args); + if (dbh->def_stmt_ctor_args != NULL) { + zend_array_release(dbh->def_stmt_ctor_args); + dbh->def_stmt_ctor_args = NULL; } for (i = 0; i < PDO_DBH_DRIVER_METHOD_KIND__MAX; i++) { @@ -1542,6 +1537,7 @@ zend_object *pdo_dbh_new(zend_class_entry *ce) zend_std_get_properties_ex(&dbh->std); dbh->inner = ecalloc(1, sizeof(pdo_dbh_t)); dbh->inner->def_stmt_ce = pdo_dbstmt_ce; + dbh->inner->def_stmt_ctor_args = NULL; return &dbh->std; } diff --git a/ext/pdo/php_pdo_driver.h b/ext/pdo/php_pdo_driver.h index 1bbdb6f0eceac..bd08cc0ee79f4 100644 --- a/ext/pdo/php_pdo_driver.h +++ b/ext/pdo/php_pdo_driver.h @@ -495,7 +495,7 @@ struct _pdo_dbh_t { zend_class_entry *def_stmt_ce; - zval def_stmt_ctor_args; + HashTable *def_stmt_ctor_args; /* when calling PDO::query(), we need to keep the error * context from the statement around until we next clear it. diff --git a/ext/pdo/tests/attr_statement_class/pdo_ATTR_STATEMENT_CLASS_basic.phpt b/ext/pdo/tests/attr_statement_class/pdo_ATTR_STATEMENT_CLASS_basic.phpt new file mode 100644 index 0000000000000..dfef3bb4839f2 --- /dev/null +++ b/ext/pdo/tests/attr_statement_class/pdo_ATTR_STATEMENT_CLASS_basic.phpt @@ -0,0 +1,132 @@ +--TEST-- +PDO Common: Set PDOStatement class with PDO::ATTR_STATEMENT_CLASS overall test +--EXTENSIONS-- +pdo +--SKIPIF-- + +--FILE-- +setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true); + +$table = 'pdo_attr_statement_class_basic'; +$db->exec("CREATE TABLE {$table} (id INT, label CHAR(1), PRIMARY KEY(id))"); +$db->exec("INSERT INTO {$table} (id, label) VALUES (1, 'a')"); +$db->exec("INSERT INTO {$table} (id, label) VALUES (2, 'b')"); + +$default = $db->getAttribute(PDO::ATTR_STATEMENT_CLASS); +var_dump($default); + +// Having a public destructor is allowed +class StatementWithPublicDestructor extends PDOStatement { + public function __destruct() { + echo __METHOD__, PHP_EOL; + } +} + +try { + var_dump($db->setAttribute(PDO::ATTR_STATEMENT_CLASS, ['StatementWithPublicDestructor', []])); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), \PHP_EOL; +} +var_dump($db->setAttribute(PDO::ATTR_STATEMENT_CLASS, ['StatementWithPublicDestructor'])); +$stmt = $db->query("SELECT id, label FROM {$table} ORDER BY id ASC"); +unset($stmt); + +echo "Class derived from PDOStatement, with private constructor:\n"; +class StatementWithPrivateConstructor extends PDOStatement { + private function __construct($msg) { + echo __METHOD__, PHP_EOL; + var_dump($this); + var_dump($msg); + } +} +var_dump($db->setAttribute(PDO::ATTR_STATEMENT_CLASS, ['StatementWithPrivateConstructor', ['param1']])); +$stmt = $db->query("SELECT id, label FROM {$table} ORDER BY id ASC"); +unset($stmt); + +echo "Class derived from a child of PDOStatement:\n"; +class StatementDerivedFromChild extends StatementWithPrivateConstructor { + public function fetchAll($fetch_style = 1, ...$fetch_args): array { + return []; + } +} + +var_dump($db->setAttribute(PDO::ATTR_STATEMENT_CLASS, ['StatementDerivedFromChild', ['param1']])); +$stmt = $db->query("SELECT id, label FROM {$table} ORDER BY id ASC"); +var_dump($stmt->fetchAll()); +unset($stmt); + +echo "Reset to default PDOStatement class:\n"; +var_dump($db->setAttribute(PDO::ATTR_STATEMENT_CLASS, ['PDOStatement'])); +$stmt = $db->query("SELECT id, label FROM {$table} ORDER BY id ASC"); +var_dump($stmt->fetchAll()); +unset($stmt); + +?> +--CLEAN-- + +--EXPECT-- +array(1) { + [0]=> + string(12) "PDOStatement" +} +bool(true) +bool(true) +StatementWithPublicDestructor::__destruct +Class derived from PDOStatement, with private constructor: +bool(true) +StatementWithPrivateConstructor::__construct +object(StatementWithPrivateConstructor)#2 (1) { + ["queryString"]=> + string(68) "SELECT id, label FROM pdo_attr_statement_class_basic ORDER BY id ASC" +} +string(6) "param1" +Class derived from a child of PDOStatement: +bool(true) +StatementWithPrivateConstructor::__construct +object(StatementDerivedFromChild)#2 (1) { + ["queryString"]=> + string(68) "SELECT id, label FROM pdo_attr_statement_class_basic ORDER BY id ASC" +} +string(6) "param1" +array(0) { +} +Reset to default PDOStatement class: +bool(true) +array(2) { + [0]=> + array(4) { + ["id"]=> + string(1) "1" + [0]=> + string(1) "1" + ["label"]=> + string(1) "a" + [1]=> + string(1) "a" + } + [1]=> + array(4) { + ["id"]=> + string(1) "2" + [0]=> + string(1) "2" + ["label"]=> + string(1) "b" + [1]=> + string(1) "b" + } +} diff --git a/ext/pdo/tests/attr_statement_class/pdo_ATTR_STATEMENT_CLASS_ctor_arg_gc.phpt b/ext/pdo/tests/attr_statement_class/pdo_ATTR_STATEMENT_CLASS_ctor_arg_gc.phpt new file mode 100644 index 0000000000000..301a8835eee2e --- /dev/null +++ b/ext/pdo/tests/attr_statement_class/pdo_ATTR_STATEMENT_CLASS_ctor_arg_gc.phpt @@ -0,0 +1,58 @@ +--TEST-- +PDO Common: Set PDOStatement class with ctor_args that are freed with GC intervention +--EXTENSIONS-- +pdo +--SKIPIF-- + +--FILE-- +setAttribute(PDO::ATTR_STATEMENT_CLASS, [$this->statementClass, [$this]]); + } +} + +$db = PDOTest::factory(Bar::class); + +$table = 'pdo_attr_statement_class_ctor_arg_gc'; +$db->exec("CREATE TABLE {$table} (id INT, label CHAR(1), PRIMARY KEY(id))"); +$db->exec("INSERT INTO {$table} (id, label) VALUES (1, 'a')"); +$db->exec("INSERT INTO {$table} (id, label) VALUES (2, 'b')"); +$query = "SELECT id, label FROM {$table} ORDER BY id ASC"; +$stmt = $db->query($query); + +var_dump($stmt instanceof Foo); +var_dump($stmt->queryString === $query); + +?> +--CLEAN-- + +--EXPECT-- +object(Bar)#1 (1) { + ["statementClass"]=> + string(3) "Foo" +} +bool(true) +bool(true) diff --git a/ext/pdo/tests/attr_statement_class/pdo_ATTR_STATEMENT_CLASS_ctor_arg_no-gc_var_modified.phpt b/ext/pdo/tests/attr_statement_class/pdo_ATTR_STATEMENT_CLASS_ctor_arg_no-gc_var_modified.phpt new file mode 100644 index 0000000000000..bcf07b1ea8d73 --- /dev/null +++ b/ext/pdo/tests/attr_statement_class/pdo_ATTR_STATEMENT_CLASS_ctor_arg_no-gc_var_modified.phpt @@ -0,0 +1,49 @@ +--TEST-- +PDO Common: Set PDOStatement class with ctor_args that are freed without GC intervention as a variable that is modified +--EXTENSIONS-- +pdo +--SKIPIF-- + +--FILE-- +setAttribute(PDO::ATTR_STATEMENT_CLASS, $a); +$a[0] = 'Bar'; + +$table = 'pdo_attr_statement_class_ctor_arg_no_gc_var_modified'; +$db->exec("CREATE TABLE {$table} (id INT, label CHAR(1), PRIMARY KEY(id))"); +$db->exec("INSERT INTO {$table} (id, label) VALUES (1, 'a')"); +$db->exec("INSERT INTO {$table} (id, label) VALUES (2, 'b')"); +$query = "SELECT id, label FROM {$table} ORDER BY id ASC"; +$stmt = $db->query($query); + +var_dump($stmt instanceof Foo); +var_dump($stmt->queryString === $query); + +?> +--CLEAN-- + +--EXPECT-- +string(6) "param1" +bool(true) +bool(true) diff --git a/ext/pdo/tests/attr_statement_class/pdo_ATTR_STATEMENT_CLASS_ctor_arg_no-gc_var_unset.phpt b/ext/pdo/tests/attr_statement_class/pdo_ATTR_STATEMENT_CLASS_ctor_arg_no-gc_var_unset.phpt new file mode 100644 index 0000000000000..e5d214fea5759 --- /dev/null +++ b/ext/pdo/tests/attr_statement_class/pdo_ATTR_STATEMENT_CLASS_ctor_arg_no-gc_var_unset.phpt @@ -0,0 +1,49 @@ +--TEST-- +PDO Common: Set PDOStatement class with ctor_args that are freed without GC intervention as a variable that is unset +--EXTENSIONS-- +pdo +--SKIPIF-- + +--FILE-- +setAttribute(PDO::ATTR_STATEMENT_CLASS, $a); +unset($a); + +$table = 'pdo_attr_statement_class_ctor_arg_no_gc_var_unset'; +$db->exec("CREATE TABLE {$table} (id INT, label CHAR(1), PRIMARY KEY(id))"); +$db->exec("INSERT INTO {$table} (id, label) VALUES (1, 'a')"); +$db->exec("INSERT INTO {$table} (id, label) VALUES (2, 'b')"); +$query = "SELECT id, label FROM {$table} ORDER BY id ASC"; +$stmt = $db->query($query); + +var_dump($stmt instanceof Foo); +var_dump($stmt->queryString === $query); + +?> +--CLEAN-- + +--EXPECT-- +string(6) "param1" +bool(true) +bool(true) diff --git a/ext/pdo/tests/attr_statement_class/pdo_ATTR_STATEMENT_CLASS_ctor_arg_no_gc.phpt b/ext/pdo/tests/attr_statement_class/pdo_ATTR_STATEMENT_CLASS_ctor_arg_no_gc.phpt new file mode 100644 index 0000000000000..2a111a9ac1947 --- /dev/null +++ b/ext/pdo/tests/attr_statement_class/pdo_ATTR_STATEMENT_CLASS_ctor_arg_no_gc.phpt @@ -0,0 +1,47 @@ +--TEST-- +PDO Common: Set PDOStatement class with ctor_args that are freed without GC intervention +--EXTENSIONS-- +pdo +--SKIPIF-- + +--FILE-- +setAttribute(PDO::ATTR_STATEMENT_CLASS, array('Foo', ['param1'])); + +$table = 'pdo_attr_statement_class_ctor_arg_no_gc'; +$db->exec("CREATE TABLE {$table} (id INT, label CHAR(1), PRIMARY KEY(id))"); +$db->exec("INSERT INTO {$table} (id, label) VALUES (1, 'a')"); +$db->exec("INSERT INTO {$table} (id, label) VALUES (2, 'b')"); +$query = "SELECT id, label FROM {$table} ORDER BY id ASC"; +$stmt = $db->query($query); + +var_dump($stmt instanceof Foo); +var_dump($stmt->queryString === $query); + +?> +--CLEAN-- + +--EXPECT-- +string(6) "param1" +bool(true) +bool(true) diff --git a/ext/pdo/tests/attr_statement_class/pdo_ATTR_STATEMENT_CLASS_errors.phpt b/ext/pdo/tests/attr_statement_class/pdo_ATTR_STATEMENT_CLASS_errors.phpt new file mode 100644 index 0000000000000..c65116d3e5088 --- /dev/null +++ b/ext/pdo/tests/attr_statement_class/pdo_ATTR_STATEMENT_CLASS_errors.phpt @@ -0,0 +1,79 @@ +--TEST-- +PDO Common: Set PDOStatement class with PDO::ATTR_STATEMENT_CLASS various error conditions +--EXTENSIONS-- +pdo +--SKIPIF-- + +--FILE-- +setAttribute(PDO::ATTR_STATEMENT_CLASS, 'not an array'); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), \PHP_EOL; +} + +echo "Unknown class variation:\n"; +try { + $db->setAttribute(PDO::ATTR_STATEMENT_CLASS, ['classname']); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), \PHP_EOL; +} + +try { + $db->setAttribute(PDO::ATTR_STATEMENT_CLASS, ['classname', []]); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), \PHP_EOL; +} + +echo "Class not derived from PDOStatement:\n"; +class NotDerived { +} + +try { + $db->setAttribute(PDO::ATTR_STATEMENT_CLASS, ['NotDerived']); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), \PHP_EOL; +} +try { + $db->setAttribute(PDO::ATTR_STATEMENT_CLASS, ['NotDerived', []]); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), \PHP_EOL; +} + +echo "Class derived from PDOStatement, but with public constructor:\n"; +class DerivedWithPublicConstructor extends PDOStatement { + public function __construct() { } +} + +try { + $db->setAttribute(PDO::ATTR_STATEMENT_CLASS, ['DerivedWithPublicConstructor']); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), \PHP_EOL; +} +try { + $db->setAttribute(PDO::ATTR_STATEMENT_CLASS, ['DerivedWithPublicConstructor', []]); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), \PHP_EOL; +} +?> +--EXPECT-- +TypeError: PDO::setAttribute(): Argument #2 ($value) PDO::ATTR_STATEMENT_CLASS value must be of type array, string given +Unknown class variation: +TypeError: PDO::setAttribute(): Argument #2 ($value) PDO::ATTR_STATEMENT_CLASS class must be a valid class +TypeError: PDO::setAttribute(): Argument #2 ($value) PDO::ATTR_STATEMENT_CLASS class must be a valid class +Class not derived from PDOStatement: +TypeError: PDO::setAttribute(): Argument #2 ($value) PDO::ATTR_STATEMENT_CLASS class must be derived from PDOStatement +TypeError: PDO::setAttribute(): Argument #2 ($value) PDO::ATTR_STATEMENT_CLASS class must be derived from PDOStatement +Class derived from PDOStatement, but with public constructor: +TypeError: PDO::setAttribute(): Argument #2 ($value) User-supplied statement class cannot have a public constructor +TypeError: PDO::setAttribute(): Argument #2 ($value) User-supplied statement class cannot have a public constructor diff --git a/ext/pdo/tests/attr_statement_class/pdo_ATTR_STATEMENT_CLASS_with_abstract_class.phpt b/ext/pdo/tests/attr_statement_class/pdo_ATTR_STATEMENT_CLASS_with_abstract_class.phpt new file mode 100644 index 0000000000000..339f4ec0be58d --- /dev/null +++ b/ext/pdo/tests/attr_statement_class/pdo_ATTR_STATEMENT_CLASS_with_abstract_class.phpt @@ -0,0 +1,42 @@ +--TEST-- +PDO Common: Set PDOStatement class with PDO::ATTR_STATEMENT_CLASS that is abstract +--EXTENSIONS-- +pdo +--SKIPIF-- + +--FILE-- +exec("CREATE TABLE {$table} (id INT, label CHAR(1), PRIMARY KEY(id))"); +$db->exec("INSERT INTO {$table} (id, label) VALUES (1, 'a')"); +$db->exec("INSERT INTO {$table} (id, label) VALUES (2, 'b')"); + +abstract class DerivedButAbstract extends PDOStatement { + private function __construct() {} +} + +try { + var_dump($db->setAttribute(PDO::ATTR_STATEMENT_CLASS, ['DerivedButAbstract', ['DerivedButAbstract']])); + $stmt = $db->query("SELECT id, label FROM {$table} ORDER BY id ASC"); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), \PHP_EOL; +} +?> +--CLEAN-- + +--EXPECT-- +bool(true) +Error: Cannot instantiate abstract class DerivedButAbstract diff --git a/ext/pdo/tests/attr_statement_class/pdo_prepare_ATTR_STATEMENT_CLASS_ctor_arg_gc.phpt b/ext/pdo/tests/attr_statement_class/pdo_prepare_ATTR_STATEMENT_CLASS_ctor_arg_gc.phpt new file mode 100644 index 0000000000000..86d2cfcefd5fd --- /dev/null +++ b/ext/pdo/tests/attr_statement_class/pdo_prepare_ATTR_STATEMENT_CLASS_ctor_arg_gc.phpt @@ -0,0 +1,61 @@ +--TEST-- +PDO Common: Set PDOStatement class with ctor_args that are freed with GC intervention in PDO::prepare() +--EXTENSIONS-- +pdo +--SKIPIF-- + +--FILE-- +setAttribute(PDO::ATTR_STATEMENT_CLASS, [$this->statementClass, [$this]]); + } +} + +$db = PDOTest::factory(Bar::class); + +$table = 'pdo_prepare_attr_statement_class_ctor_arg_gc'; +$db->exec("CREATE TABLE {$table} (id INT, label CHAR(1), PRIMARY KEY(id))"); +$db->exec("INSERT INTO {$table} (id, label) VALUES (1, 'a')"); +$db->exec("INSERT INTO {$table} (id, label) VALUES (2, 'b')"); + +$query = "SELECT id, label FROM {$table} ORDER BY id ASC"; + +$stmt = $db->prepare($query); +var_dump($stmt instanceof Foo); +var_dump($stmt->queryString === $query); +$r = $stmt->execute(); +var_dump($r); +?> +--CLEAN-- + +--EXPECT-- +object(Bar)#1 (1) { + ["statementClass"]=> + string(3) "Foo" +} +bool(true) +bool(true) +bool(true) diff --git a/ext/pdo/tests/attr_statement_class/pdo_prepare_ATTR_STATEMENT_CLASS_ctor_arg_no-gc.phpt b/ext/pdo/tests/attr_statement_class/pdo_prepare_ATTR_STATEMENT_CLASS_ctor_arg_no-gc.phpt new file mode 100644 index 0000000000000..bf406b8952750 --- /dev/null +++ b/ext/pdo/tests/attr_statement_class/pdo_prepare_ATTR_STATEMENT_CLASS_ctor_arg_no-gc.phpt @@ -0,0 +1,51 @@ +--TEST-- +PDO Common: Set PDOStatement class with ctor_args that are freed without GC intervention in PDO::prepare() +--EXTENSIONS-- +pdo +--SKIPIF-- + +--FILE-- +setAttribute(PDO::ATTR_STATEMENT_CLASS, ['Foo', ['param1']]); + +$table = 'pdo_prepare_attr_statement_class_ctor_arg_no_gc'; +$db->exec("CREATE TABLE {$table} (id INT, label CHAR(1), PRIMARY KEY(id))"); +$db->exec("INSERT INTO {$table} (id, label) VALUES (1, 'a')"); +$db->exec("INSERT INTO {$table} (id, label) VALUES (2, 'b')"); + +$query = "SELECT id, label FROM {$table} ORDER BY id ASC"; + +//$stmt = $db->prepare($dummy_query, [PDO::ATTR_STATEMENT_CLASS => ['Foo', ['param1']] ]); +$stmt = $db->prepare($query); + +var_dump($stmt instanceof Foo); +var_dump($stmt->queryString === $query); +$r = $stmt->execute(); +var_dump($r); +?> +--CLEAN-- + +--EXPECT-- +string(6) "param1" +bool(true) +bool(true) +bool(true) diff --git a/ext/pdo/tests/pdo_test.inc b/ext/pdo/tests/pdo_test.inc index 8cd60f68aa519..b44d0b88e77b7 100644 --- a/ext/pdo/tests/pdo_test.inc +++ b/ext/pdo/tests/pdo_test.inc @@ -96,5 +96,20 @@ class PDOTest { }; } } +/** See https://stackoverflow.com/a/3732466 */ +function get_dummy_sql_request(): string +{ + $dsn = getenv('PDOTEST_DSN'); -?> + // Firebird: https://www.firebirdfaq.org/faq30/ + if (str_starts_with($dsn, 'firebird')) { + return "SELECT FIRST 0 FROM (VALUES ('Hello world')"; + } + + // Oracle + if (str_starts_with($dsn, 'oci')) { + return 'SELECT 1 FROM DUAL'; + } + + return 'SELECT 1'; +}