diff --git a/ext/pdo/pdo_dbh.c b/ext/pdo/pdo_dbh.c index 9588f23d420c5..faef60e4bef58 100644 --- a/ext/pdo/pdo_dbh.c +++ b/ext/pdo/pdo_dbh.c @@ -857,22 +857,22 @@ static bool pdo_dbh_attribute_set(pdo_dbh_t *dbh, zend_long attr, zval *value, u return true; case PDO_ATTR_DEFAULT_FETCH_MODE: - if (Z_TYPE_P(value) == IS_ARRAY) { - zval *tmp; - if ((tmp = zend_hash_index_find(Z_ARRVAL_P(value), 0)) != NULL && Z_TYPE_P(tmp) == IS_LONG) { - if (Z_LVAL_P(tmp) == PDO_FETCH_INTO || Z_LVAL_P(tmp) == PDO_FETCH_CLASS) { - zend_argument_value_error(value_arg_num, "PDO::FETCH_INTO and PDO::FETCH_CLASS cannot be set as the default fetch mode"); - return false; - } - } - lval = zval_get_long(value); - } else { - if (!pdo_get_long_param(&lval, value)) { - return false; - } + if (!pdo_get_long_param(&lval, value)) { + return false; + } + if (!pdo_verify_fetch_mode(PDO_FETCH_USE_DEFAULT, lval, value_arg_num, false)) { + return false; + } + if (UNEXPECTED( + lval == PDO_FETCH_USE_DEFAULT + || lval == PDO_FETCH_INTO + || lval == PDO_FETCH_FUNC + )) { + zend_argument_value_error(value_arg_num, "cannot set default fetch mode to PDO::FETCH_USE_DEFAULT, PDO::FETCH_INTO, PDO::FETCH_FUNC"); + return false; } - if (lval == PDO_FETCH_USE_DEFAULT) { - zend_argument_value_error(value_arg_num, "Fetch mode must be a bitmask of PDO::FETCH_* constants"); + if (UNEXPECTED((lval & PDO_FETCH_CLASS) && (lval & PDO_FETCH_CLASSTYPE) == 0)) { + zend_argument_value_error(value_arg_num, "cannot set default fetch mode to PDO::FETCH_CLASS without PDO::FETCH_CLASSTYPE"); return false; } dbh->default_fetch_type = lval; diff --git a/ext/pdo/pdo_stmt.c b/ext/pdo/pdo_stmt.c index 697940d94260d..a18cd91e32c6b 100644 --- a/ext/pdo/pdo_stmt.c +++ b/ext/pdo/pdo_stmt.c @@ -764,13 +764,6 @@ static bool do_fetch(pdo_stmt_t *stmt, zval *return_value, enum pdo_fetch_type h } zval_ptr_dtor(&ce_name_from_column); } else { - /* This can happen if the fetch flags are set via PDO::setAttribute() - * $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_CLASS); - * See ext/pdo/tests/bug_38253.phpt */ - if (UNEXPECTED(ce == NULL)) { - pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "No fetch class specified"); - goto in_fetch_error; - } ctor_arguments = stmt->fetch.cls.ctor_args; } ZEND_ASSERT(ce != NULL); @@ -795,14 +788,7 @@ static bool do_fetch(pdo_stmt_t *stmt, zval *return_value, enum pdo_fetch_type h break; case PDO_FETCH_INTO: - /* This can happen if the fetch flags are set via PDO::setAttribute() - * $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_INTO); - * See ext/pdo/tests/bug_38253.phpt */ - if (stmt->fetch.into == NULL) { - pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "No fetch-into object specified."); - goto in_fetch_error; - } - + ZEND_ASSERT(stmt->fetch.into != NULL); ZVAL_OBJ_COPY(return_value, stmt->fetch.into); /* We want the behaviour of fetching into an object to be called from the global scope rather @@ -811,13 +797,7 @@ static bool do_fetch(pdo_stmt_t *stmt, zval *return_value, enum pdo_fetch_type h break; case PDO_FETCH_FUNC: - /* This can happen if the fetch flags are set via PDO::setAttribute() - * $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_FUNC); - * See ext/pdo/tests/bug_38253.phpt */ - if (UNEXPECTED(!ZEND_FCC_INITIALIZED(stmt->fetch.func.fcc))) { - pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "No fetch function specified"); - goto in_fetch_error; - } + ZEND_ASSERT(ZEND_FCC_INITIALIZED(stmt->fetch.func.fcc)); /* There will be at most stmt->column_count parameters. * However, if we fetch a group key we will have over allocated. */ fetch_function_params = safe_emalloc(sizeof(zval), stmt->column_count, 0); @@ -948,8 +928,7 @@ static bool do_fetch(pdo_stmt_t *stmt, zval *return_value, enum pdo_fetch_type h // TODO Error on the following cases: // Combining PDO_FETCH_UNIQUE and PDO_FETCH_GROUP -// Reject $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, value); bypass -static bool pdo_verify_fetch_mode(uint32_t default_mode_and_flags, zend_long mode_and_flags, uint32_t mode_arg_num, bool fetch_all) /* {{{ */ +PDO_API bool pdo_verify_fetch_mode(uint32_t default_mode_and_flags, zend_long mode_and_flags, uint32_t mode_arg_num, bool fetch_all) /* {{{ */ { /* Mode must be a positive */ if (mode_and_flags < 0 || mode_and_flags >= PDO_FIRST_INVALID_FLAG) { @@ -1599,12 +1578,8 @@ void pdo_stmt_free_default_fetch_mode(pdo_stmt_t *stmt) { enum pdo_fetch_type default_fetch_mode = stmt->default_fetch_type & ~PDO_FETCH_FLAGS; if (default_fetch_mode == PDO_FETCH_INTO) { - /* This can happen if the fetch flags are set via PDO::setAttribute() - * $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_INTO); - * See ext/pdo/tests/bug_38253.phpt */ - if (EXPECTED(stmt->fetch.into != NULL)) { - OBJ_RELEASE(stmt->fetch.into); - } + ZEND_ASSERT(stmt->fetch.into != NULL); + OBJ_RELEASE(stmt->fetch.into); } else if (default_fetch_mode == PDO_FETCH_CLASS) { if (stmt->fetch.cls.ctor_args != NULL) { zend_array_release(stmt->fetch.cls.ctor_args); diff --git a/ext/pdo/php_pdo_driver.h b/ext/pdo/php_pdo_driver.h index 9c5986ff8bce8..fb72dc89661bc 100644 --- a/ext/pdo/php_pdo_driver.h +++ b/ext/pdo/php_pdo_driver.h @@ -577,7 +577,10 @@ struct _pdo_stmt_t { * bindParam() for its prepared statements, if false, PDO should * emulate prepare and bind on its behalf */ uint16_t supports_placeholders:2; - uint16_t reserved: 12; + + /* defaults for fetches */ + uint16_t default_fetch_type:9; + uint16_t reserved:3; /* keep track of bound input parameters. Some drivers support * input/output parameters, but you can't rely on that working */ @@ -595,9 +598,6 @@ struct _pdo_stmt_t { * */ int32_t column_count; - /* defaults for fetches */ - enum pdo_fetch_type default_fetch_type; - union { int column; struct { @@ -704,6 +704,8 @@ PDO_API void php_pdo_internal_construct_driver(INTERNAL_FUNCTION_PARAMETERS, zen PDO_API bool pdo_get_long_param(zend_long *lval, const zval *value); PDO_API bool pdo_get_bool_param(bool *bval, const zval *value); +PDO_API bool pdo_verify_fetch_mode(uint32_t default_mode_and_flags, zend_long mode_and_flags, uint32_t mode_arg_num, bool fetch_all); + PDO_API void pdo_throw_exception(unsigned int driver_errcode, char *driver_errmsg, pdo_error_type *pdo_error); /* When a GC cycle is collected, it's possible that the database object is destroyed prior to destroying diff --git a/ext/pdo/tests/bug_38253.phpt b/ext/pdo/tests/bug_38253.phpt index 06a9736ed85bb..a58b2ec8d95c8 100644 --- a/ext/pdo/tests/bug_38253.phpt +++ b/ext/pdo/tests/bug_38253.phpt @@ -18,20 +18,32 @@ $pdo = PDOTest::factory(); $pdo->exec ("create table test38253 (id integer primary key, n varchar(255))"); $pdo->exec ("INSERT INTO test38253 (id, n) VALUES (1, 'hi')"); -$pdo->setAttribute (PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_CLASS); -$stmt = $pdo->prepare ("SELECT * FROM test38253"); -$stmt->execute(); -var_dump($stmt->fetchAll()); +try { + $pdo->setAttribute (PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_CLASS); + $stmt = $pdo->prepare ("SELECT * FROM test38253"); + $stmt->execute(); + var_dump($stmt->fetchAll()); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), \PHP_EOL; +} -$pdo->setAttribute (PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_FUNC); -$stmt = $pdo->prepare ("SELECT * FROM test38253"); -$stmt->execute(); -var_dump($stmt->fetchAll()); +try { + $pdo->setAttribute (PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_FUNC); + $stmt = $pdo->prepare ("SELECT * FROM test38253"); + $stmt->execute(); + var_dump($stmt->fetchAll()); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), \PHP_EOL; +} -$pdo->setAttribute (PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_INTO); -$stmt = $pdo->prepare ("SELECT * FROM test38253"); -$stmt->execute(); -var_dump($stmt->fetch()); +try { + $pdo->setAttribute (PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_INTO); + $stmt = $pdo->prepare ("SELECT * FROM test38253"); + $stmt->execute(); + var_dump($stmt->fetch()); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), \PHP_EOL; +} ?> --CLEAN-- @@ -40,20 +52,7 @@ require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc'; $db = PDOTest::factory(); PDOTest::dropTableIfExists($db, "test38253"); ?> ---EXPECTF-- -Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error: No fetch class specified in %s on line %d - -Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error in %s on line %d -array(0) { -} - -Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error: No fetch function specified in %s on line %d - -Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error in %s on line %d -array(0) { -} - -Warning: PDOStatement::fetch(): SQLSTATE[HY000]: General error: No fetch-into object specified. in %s on line %d - -Warning: PDOStatement::fetch(): SQLSTATE[HY000]: General error in %s on line %d -bool(false) +--EXPECT-- +ValueError: PDO::setAttribute(): Argument #2 ($value) cannot set default fetch mode to PDO::FETCH_CLASS without PDO::FETCH_CLASSTYPE +ValueError: PDO::setAttribute(): Argument #2 ($value) PDO::FETCH_FUNC can only be used with PDOStatement::fetchAll() +ValueError: PDO::setAttribute(): Argument #2 ($value) cannot set default fetch mode to PDO::FETCH_USE_DEFAULT, PDO::FETCH_INTO, PDO::FETCH_FUNC