Skip to content

Commit 31092c2

Browse files
committed
Merge branch 'PHP-8.3'
* PHP-8.3: Fix GH-13177: PHP 8.3.2: final private constructor not allowed when used in trait Fix GH-12107: When running a stored procedure (that returns a result set) twice, PHP crashes
2 parents ffc250d + c2c1710 commit 31092c2

File tree

4 files changed

+141
-5
lines changed

4 files changed

+141
-5
lines changed

Zend/tests/traits/bugs/gh13177.phpt

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
--TEST--
2+
GH-13177 (PHP 8.3.2: final private constructor not allowed when used in trait)
3+
--FILE--
4+
<?php
5+
6+
trait Bar {
7+
final private function __construct() {}
8+
}
9+
10+
final class Foo1 {
11+
use Bar;
12+
}
13+
14+
final class Foo2 {
15+
use Bar {
16+
__construct as final;
17+
}
18+
}
19+
20+
class Foo3 {
21+
use Bar {
22+
__construct as final;
23+
}
24+
}
25+
26+
trait TraitNonConstructor {
27+
private final function test() {}
28+
}
29+
30+
class Foo4 {
31+
use TraitNonConstructor { test as __construct; }
32+
}
33+
34+
for ($i = 1; $i <= 4; $i++) {
35+
$rc = new ReflectionClass("Foo$i");
36+
echo $rc->getMethod("__construct"), "\n";
37+
}
38+
39+
class Foo5 extends Foo3 {
40+
private function __construct() {}
41+
}
42+
43+
?>
44+
--EXPECTF--
45+
Warning: Private methods cannot be final as they are never overridden by other classes in %s on line %d
46+
Method [ <user, ctor> final private method __construct ] {
47+
@@ %sgh13177.php 4 - 4
48+
}
49+
50+
Method [ <user, ctor> final private method __construct ] {
51+
@@ %sgh13177.php 4 - 4
52+
}
53+
54+
Method [ <user, ctor> final private method __construct ] {
55+
@@ %sgh13177.php 4 - 4
56+
}
57+
58+
Method [ <user, ctor> final private method __construct ] {
59+
@@ %sgh13177.php 24 - 24
60+
}
61+
62+
63+
Fatal error: Cannot override final method Foo3::__construct() in %s on line %d

Zend/zend_inheritance.c

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1952,10 +1952,6 @@ static void zend_add_trait_method(zend_class_entry *ce, zend_string *name, zend_
19521952
zend_function *new_fn;
19531953
bool check_inheritance = false;
19541954

1955-
if ((fn->common.fn_flags & (ZEND_ACC_PRIVATE | ZEND_ACC_FINAL)) == (ZEND_ACC_PRIVATE | ZEND_ACC_FINAL)) {
1956-
zend_error(E_COMPILE_WARNING, "Private methods cannot be final as they are never overridden by other classes");
1957-
}
1958-
19591955
if ((existing_fn = zend_hash_find_ptr(&ce->function_table, key)) != NULL) {
19601956
/* if it is the same function with the same visibility and has not been assigned a class scope yet, regardless
19611957
* of where it is coming from there is no conflict and we do not need to add it again */
@@ -2036,6 +2032,17 @@ static void zend_fixup_trait_method(zend_function *fn, zend_class_entry *ce) /*
20362032
}
20372033
/* }}} */
20382034

2035+
static void zend_traits_check_private_final_inheritance(uint32_t original_fn_flags, zend_function *fn_copy, zend_string *name)
2036+
{
2037+
/* If the function was originally already private+final, then it will have already been warned about.
2038+
* If the function became private+final only after applying modifiers, we need to emit the same warning. */
2039+
if ((original_fn_flags & (ZEND_ACC_PRIVATE | ZEND_ACC_FINAL)) != (ZEND_ACC_PRIVATE | ZEND_ACC_FINAL)
2040+
&& (fn_copy->common.fn_flags & (ZEND_ACC_PRIVATE | ZEND_ACC_FINAL)) == (ZEND_ACC_PRIVATE | ZEND_ACC_FINAL)
2041+
&& !zend_string_equals_literal_ci(name, ZEND_CONSTRUCTOR_FUNC_NAME)) {
2042+
zend_error(E_COMPILE_WARNING, "Private methods cannot be final as they are never overridden by other classes");
2043+
}
2044+
}
2045+
20392046
static void zend_traits_copy_functions(zend_string *fnname, zend_function *fn, zend_class_entry *ce, HashTable *exclude_table, zend_class_entry **aliases) /* {{{ */
20402047
{
20412048
zend_trait_alias *alias, **alias_ptr;
@@ -2061,6 +2068,8 @@ static void zend_traits_copy_functions(zend_string *fnname, zend_function *fn, z
20612068
fn_copy.common.fn_flags = alias->modifiers | fn->common.fn_flags;
20622069
}
20632070

2071+
zend_traits_check_private_final_inheritance(fn->common.fn_flags, &fn_copy, alias->alias);
2072+
20642073
lcname = zend_string_tolower(alias->alias);
20652074
zend_add_trait_method(ce, alias->alias, lcname, &fn_copy);
20662075
zend_string_release_ex(lcname, 0);
@@ -2098,6 +2107,8 @@ static void zend_traits_copy_functions(zend_string *fnname, zend_function *fn, z
20982107
}
20992108
}
21002109

2110+
zend_traits_check_private_final_inheritance(fn->common.fn_flags, &fn_copy, fnname);
2111+
21012112
zend_add_trait_method(ce, fn->common.function_name, fnname, &fn_copy);
21022113
}
21032114
}

ext/mysqli/tests/gh12107.phpt

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
--TEST--
2+
GH-12107 (When running a stored procedure (that returns a result set) twice, PHP crashes)
3+
--EXTENSIONS--
4+
mysqli
5+
--SKIPIF--
6+
<?php
7+
require_once 'skipifconnectfailure.inc';
8+
?>
9+
--FILE--
10+
<?php
11+
require_once 'connect.inc';
12+
13+
$mysqli = new mysqli("$host:$port", $user, $passwd, $db);
14+
15+
$sql = <<<SQL
16+
CREATE PROCEDURE `gh12107`()
17+
BEGIN
18+
SELECT "hello world";
19+
END;
20+
SQL;
21+
$mysqli->query($sql);
22+
23+
echo "Start or run 1\n";
24+
$stmt = $mysqli->prepare("call `gh12107`()");
25+
$stmt->execute();
26+
$stmt->bind_result($output);
27+
var_dump($stmt->fetch());
28+
var_dump($output);
29+
unset($output);
30+
echo "End of run 1\n";
31+
32+
echo "Start or run 2\n";
33+
$stmt->execute();
34+
$stmt->bind_result($output);
35+
var_dump($stmt->fetch());
36+
var_dump($output);
37+
echo "End of run 2\n";
38+
39+
?>
40+
--CLEAN--
41+
<?php
42+
require_once 'connect.inc';
43+
if (!$link = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket))
44+
printf("[c001] [%d] %s\n", mysqli_connect_errno(), mysqli_connect_error());
45+
46+
if (!mysqli_query($link, "DROP PROCEDURE IF EXISTS gh12107"))
47+
printf("[c002] Cannot drop procedure, [%d] %s\n", mysqli_errno($link), mysqli_error($link));
48+
49+
mysqli_close($link);
50+
?>
51+
--EXPECT--
52+
Start or run 1
53+
bool(true)
54+
string(11) "hello world"
55+
End of run 1
56+
Start or run 2
57+
bool(true)
58+
string(11) "hello world"
59+
End of run 2

ext/mysqlnd/mysqlnd_ps.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -652,8 +652,11 @@ MYSQLND_METHOD(mysqlnd_stmt, send_execute)(MYSQLND_STMT * const s, const enum_my
652652
Executed, but the user hasn't started to fetch
653653
This will clean also the metadata, but after the EXECUTE call we will
654654
have it again.
655+
stmt->result may be freed and nullified by free_stmt_result, transitively called from flush.
655656
*/
656-
stmt->result->m.free_result_buffers(stmt->result);
657+
if (stmt->result) {
658+
stmt->result->m.free_result_buffers(stmt->result);
659+
}
657660

658661
stmt->state = MYSQLND_STMT_PREPARED;
659662
} else if (stmt->state < MYSQLND_STMT_PREPARED) {

0 commit comments

Comments
 (0)