From b4eedd128ba9f61be08a50c94afd72837d7cf70b Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 17 Mar 2016 12:39:31 +0100 Subject: [PATCH] Fixed bug #71820 pg_fetch_object bind parameters before call constructor If we want to fetch into an object of a custom class that implemens __set handler, the corstructor has to be called first. The data passed to the constructor can be possibly required in __set handler. --- ext/mysqli/mysqli.c | 25 ++++++-- ext/mysqli/tests/bug71820.phpt | 104 +++++++++++++++++++++++++++++++++ ext/pgsql/pgsql.c | 24 +++++++- ext/pgsql/tests/bug71820.phpt | 93 +++++++++++++++++++++++++++++ 4 files changed, 239 insertions(+), 7 deletions(-) create mode 100644 ext/mysqli/tests/bug71820.phpt create mode 100644 ext/pgsql/tests/bug71820.phpt diff --git a/ext/mysqli/mysqli.c b/ext/mysqli/mysqli.c index 594dd0da35..a7d81757a7 100644 --- a/ext/mysqli/mysqli.c +++ b/ext/mysqli/mysqli.c @@ -1278,15 +1278,13 @@ void php_mysqli_fetch_into_hash(INTERNAL_FUNCTION_PARAMETERS, int override_flags zval dataset, retval; zend_fcall_info fci; zend_fcall_info_cache fcc; + zend_bool props_handled = 0; ZVAL_COPY_VALUE(&dataset, return_value); - object_and_properties_init(return_value, ce, NULL); if (!ce->default_properties_count && !ce->__set) { Z_OBJ_P(return_value)->properties = Z_ARR(dataset); - } else { - zend_merge_properties(return_value, Z_ARRVAL(dataset)); - zval_ptr_dtor(&dataset); + props_handled = 1; } if (ce->constructor) { @@ -1308,6 +1306,9 @@ void php_mysqli_fetch_into_hash(INTERNAL_FUNCTION_PARAMETERS, int override_flags * single value is an array. Also we'd have to make that one * argument passed by reference. */ + if (!props_handled) { + zval_ptr_dtor(&dataset); + } zend_throw_exception(zend_ce_exception, "Parameter ctor_params must be an array", 0); return; } @@ -1320,7 +1321,14 @@ void php_mysqli_fetch_into_hash(INTERNAL_FUNCTION_PARAMETERS, int override_flags fcc.object = Z_OBJ_P(return_value); if (zend_call_function(&fci, &fcc) == FAILURE) { + if (fci.params) { + efree(fci.params); + } + if (!props_handled) { + zval_ptr_dtor(&dataset); + } zend_throw_exception_ex(zend_ce_exception, 0, "Could not execute %s::%s()", ZSTR_VAL(ce->name), ZSTR_VAL(ce->constructor->common.function_name)); + return; } else { zval_ptr_dtor(&retval); } @@ -1328,7 +1336,16 @@ void php_mysqli_fetch_into_hash(INTERNAL_FUNCTION_PARAMETERS, int override_flags efree(fci.params); } } else if (ctor_params) { + if (!props_handled) { + zval_ptr_dtor(&dataset); + } zend_throw_exception_ex(zend_ce_exception, 0, "Class %s does not have a constructor hence you cannot use ctor_params", ZSTR_VAL(ce->name)); + return; + } + + if (!props_handled) { + zend_merge_properties(return_value, Z_ARRVAL(dataset)); + zval_ptr_dtor(&dataset); } } } diff --git a/ext/mysqli/tests/bug71820.phpt b/ext/mysqli/tests/bug71820.phpt new file mode 100644 index 0000000000..02a25939e2 --- /dev/null +++ b/ext/mysqli/tests/bug71820.phpt @@ -0,0 +1,104 @@ +--TEST-- +Bug #71820 __set has to be called after constructor, mysqli part +--SKIPIF-- + +--FILE-- +set_from_constructor = $set_from_constructor; + } + + public function __set($name, $value) + { + if (!isset($this->data[$name])) { + /* $this->set_from_constructor has an expected value */ + $this->data[$name] = 42 == $this->set_from_constructor ? $value : -1; + return; + } + throw new \Exception('Duplicity column name.'); + } + +} + + +if (!($connection = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket))) { + printf("[001] Cannot connect to the server"); +} + +$rc = mysqli_query($connection, "DROP TABLE IF EXISTS $tableName"); +if (!$rc) + printf("[002] [%d] %s\n", mysqli_errno($connection), mysqli_error($connection)); + +$table = << +==DONE== +--EXPECTF-- +object(TestRow)#%d (3) { + ["set_from_constructor":"TestRow":private]=> + int(42) + ["data":"TestRow":private]=> + array(2) { + ["id"]=> + string(1) "1" + ["name"]=> + string(3) "Doe" + } + ["hello":"TestRow":private]=> + string(5) "world" +} +object(TestRow)#%d (3) { + ["set_from_constructor":"TestRow":private]=> + int(42) + ["data":"TestRow":private]=> + array(2) { + ["id"]=> + string(1) "2" + ["name"]=> + string(3) "Joe" + } + ["hello":"TestRow":private]=> + string(5) "world" +} +==DONE== diff --git a/ext/pgsql/pgsql.c b/ext/pgsql/pgsql.c index 86421e9531..cb6f4cac49 100644 --- a/ext/pgsql/pgsql.c +++ b/ext/pgsql/pgsql.c @@ -2777,14 +2777,13 @@ static void php_pgsql_fetch_hash(INTERNAL_FUNCTION_PARAMETERS, zend_long result_ zend_fcall_info fci; zend_fcall_info_cache fcc; zval retval; + zend_bool props_handled = 0; ZVAL_COPY_VALUE(&dataset, return_value); object_and_properties_init(return_value, ce, NULL); if (!ce->default_properties_count && !ce->__set) { Z_OBJ_P(return_value)->properties = Z_ARR(dataset); - } else { - zend_merge_properties(return_value, Z_ARRVAL(dataset)); - zval_ptr_dtor(&dataset); + props_handled = 1; } if (ce->constructor) { @@ -2807,6 +2806,9 @@ static void php_pgsql_fetch_hash(INTERNAL_FUNCTION_PARAMETERS, zend_long result_ * argument passed by reference. */ zend_throw_exception(zend_ce_exception, "Parameter ctor_params must be an array", 0); + if (!props_handled) { + zval_ptr_dtor(&dataset); + } return; } } @@ -2819,6 +2821,13 @@ static void php_pgsql_fetch_hash(INTERNAL_FUNCTION_PARAMETERS, zend_long result_ if (zend_call_function(&fci, &fcc) == FAILURE) { zend_throw_exception_ex(zend_ce_exception, 0, "Could not execute %s::%s()", ce->name, ce->constructor->common.function_name); + if (fci.params) { + efree(fci.params); + } + if (!props_handled) { + zval_ptr_dtor(&dataset); + } + return; } else { zval_ptr_dtor(&retval); } @@ -2827,6 +2836,15 @@ static void php_pgsql_fetch_hash(INTERNAL_FUNCTION_PARAMETERS, zend_long result_ } } else if (ctor_params) { zend_throw_exception_ex(zend_ce_exception, 0, "Class %s does not have a constructor hence you cannot use ctor_params", ce->name); + if (!props_handled) { + zval_ptr_dtor(&dataset); + } + return; + } + + if (!props_handled) { + zend_merge_properties(return_value, Z_ARRVAL(dataset)); + zval_ptr_dtor(&dataset); } } } diff --git a/ext/pgsql/tests/bug71820.phpt b/ext/pgsql/tests/bug71820.phpt new file mode 100644 index 0000000000..4d99e3468e --- /dev/null +++ b/ext/pgsql/tests/bug71820.phpt @@ -0,0 +1,93 @@ +--TEST-- +Bug #71820 pg_fetch_object bind parameters before call constructor +--SKIPIF-- + +--FILE-- +set_from_constructor = $set_from_constructor; + } + + public function __set($name, $value) + { + if (!isset($this->data[$name])) { + /* $this->set_from_constructor has an expected value */ + $this->data[$name] = 42 == $this->set_from_constructor ? $value : -1; + return; + } + throw new \Exception('Duplicity column name.'); + } + +} + +$connection = pg_connect($conn_str); + +if (!$connection) { + die('Connection faild.'); +} + +$table = << 'Doe', '$2' => 'Joe']); + +$result = pg_query('SELECT * FROM ' . $tableName . ' LIMIT 10;'); + +while ($row = pg_fetch_object($result, NULL, 'TestRow', [42])) { + var_dump($row); +} + +pg_query($connection, "DROP TABLE $tableName"); + +pg_close($connection); + +?> +==DONE== +--EXPECTF-- +object(TestRow)#%d (3) { + ["set_from_constructor":"TestRow":private]=> + int(42) + ["data":"TestRow":private]=> + array(2) { + ["id"]=> + string(1) "1" + ["name"]=> + string(3) "Doe" + } + ["hello":"TestRow":private]=> + int(42) +} +object(TestRow)#%d (3) { + ["set_from_constructor":"TestRow":private]=> + int(42) + ["data":"TestRow":private]=> + array(2) { + ["id"]=> + string(1) "2" + ["name"]=> + string(3) "Joe" + } + ["hello":"TestRow":private]=> + int(42) +} +==DONE== -- 2.40.0