Skip to content

Commit c2c1710

Browse files
committed
Fix GH-13177: PHP 8.3.2: final private constructor not allowed when used in trait
zend_compile has an exception to this rule for constructors using `zend_is_constructor`, which compares the function name to `__construct`. Sadly, `zend_is_constructor` is not a public API, but we can just do the string compare ourselves. Closes GH-13179.
1 parent 8772810 commit c2c1710

File tree

3 files changed

+80
-4
lines changed

3 files changed

+80
-4
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ PHP NEWS
77
. Fixed bug GH-12349 (linking failure on ARM with mold). (Jan Palus)
88
. Fixed bug GH-13097 (Anonymous class reference in trigger_error / thrown
99
Exception). (nielsdos)
10+
. Fixed bug GH-13177 (PHP 8.3.2: final private constructor not allowed
11+
when used in trait). (nielsdos)
1012

1113
- Curl:
1214
. Fix missing error check in curl_multi_init(). (divinity76)

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
@@ -1949,10 +1949,6 @@ static void zend_add_trait_method(zend_class_entry *ce, zend_string *name, zend_
19491949
zend_function *new_fn;
19501950
bool check_inheritance = false;
19511951

1952-
if ((fn->common.fn_flags & (ZEND_ACC_PRIVATE | ZEND_ACC_FINAL)) == (ZEND_ACC_PRIVATE | ZEND_ACC_FINAL)) {
1953-
zend_error(E_COMPILE_WARNING, "Private methods cannot be final as they are never overridden by other classes");
1954-
}
1955-
19561952
if ((existing_fn = zend_hash_find_ptr(&ce->function_table, key)) != NULL) {
19571953
/* if it is the same function with the same visibility and has not been assigned a class scope yet, regardless
19581954
* of where it is coming from there is no conflict and we do not need to add it again */
@@ -2033,6 +2029,17 @@ static void zend_fixup_trait_method(zend_function *fn, zend_class_entry *ce) /*
20332029
}
20342030
/* }}} */
20352031

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

2068+
zend_traits_check_private_final_inheritance(fn->common.fn_flags, &fn_copy, alias->alias);
2069+
20612070
lcname = zend_string_tolower(alias->alias);
20622071
zend_add_trait_method(ce, alias->alias, lcname, &fn_copy);
20632072
zend_string_release_ex(lcname, 0);
@@ -2095,6 +2104,8 @@ static void zend_traits_copy_functions(zend_string *fnname, zend_function *fn, z
20952104
}
20962105
}
20972106

2107+
zend_traits_check_private_final_inheritance(fn->common.fn_flags, &fn_copy, fnname);
2108+
20982109
zend_add_trait_method(ce, fn->common.function_name, fnname, &fn_copy);
20992110
}
21002111
}

0 commit comments

Comments
 (0)