Skip to content

Commit 53efa1b

Browse files
committed
Store aliased name of trait method
Currently, trait methods are aliased will continue to use the original function name. In a few places in the codebase, we will try to look up the actual method name instead. However, this does not work if an aliased method is used indirectly (https://bugs.php.net/bug.php?id=69180). I think it would be better to instead actually change the method name to the alias. This is in principle easy: We have to allow function_name to be changed even if op array is otherwise shared (similar to static_variables). This means we need to addref/release the function_name separately, but I don't think there is a performance concern here (especially as everything is usually interned). There is a bit of complication in opcache, where we need to make sure that the function name is released the correct number of times (interning may overwrite the name in the original op_array, but we need to release it as many times as the op_array is shared). Fixes bug #69180. Fixes bug #74939. Closes GH-5226.
1 parent a7a2e98 commit 53efa1b

File tree

13 files changed

+104
-118
lines changed

13 files changed

+104
-118
lines changed

NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ PHP NEWS
8989
scope). (Nikita)
9090
. Fixed bug #77325 (ReflectionClassConstant::$class returns wrong class when
9191
extending). (Nikita)
92+
. Fixed bug #69180 (Reflection does not honor trait conflict resolution /
93+
method aliasing). (Nikita)
94+
. Fixed bug #74939 (Nested traits' aliased methods are lowercased). (Nikita)
9295

9396
- Session:
9497
. Fixed bug #78624 (session_gc return value for user defined session

Zend/tests/bug65579.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,5 @@ array(2) {
2525
[0]=>
2626
string(10) "testMethod"
2727
[1]=>
28-
string(25) "testmethodfromparenttrait"
28+
string(25) "testMethodFromParentTrait"
2929
}

Zend/zend_API.c

Lines changed: 0 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -4228,55 +4228,6 @@ ZEND_API void zend_restore_error_handling(zend_error_handling *saved) /* {{{ */
42284228
}
42294229
/* }}} */
42304230

4231-
ZEND_API zend_string* zend_find_alias_name(zend_class_entry *ce, zend_string *name) /* {{{ */
4232-
{
4233-
zend_trait_alias *alias, **alias_ptr;
4234-
4235-
if ((alias_ptr = ce->trait_aliases)) {
4236-
alias = *alias_ptr;
4237-
while (alias) {
4238-
if (alias->alias && zend_string_equals_ci(alias->alias, name)) {
4239-
return alias->alias;
4240-
}
4241-
alias_ptr++;
4242-
alias = *alias_ptr;
4243-
}
4244-
}
4245-
4246-
return name;
4247-
}
4248-
/* }}} */
4249-
4250-
ZEND_API zend_string *zend_resolve_method_name(zend_class_entry *ce, zend_function *f) /* {{{ */
4251-
{
4252-
zend_function *func;
4253-
HashTable *function_table;
4254-
zend_string *name;
4255-
4256-
if (f->common.type != ZEND_USER_FUNCTION ||
4257-
(f->op_array.refcount && *(f->op_array.refcount) < 2) ||
4258-
!f->common.scope ||
4259-
!f->common.scope->trait_aliases) {
4260-
return f->common.function_name;
4261-
}
4262-
4263-
function_table = &ce->function_table;
4264-
ZEND_HASH_FOREACH_STR_KEY_PTR(function_table, name, func) {
4265-
if (func == f) {
4266-
if (!name) {
4267-
return f->common.function_name;
4268-
}
4269-
if (ZSTR_LEN(name) == ZSTR_LEN(f->common.function_name) &&
4270-
!strncasecmp(ZSTR_VAL(name), ZSTR_VAL(f->common.function_name), ZSTR_LEN(f->common.function_name))) {
4271-
return f->common.function_name;
4272-
}
4273-
return zend_find_alias_name(f->common.scope, name);
4274-
}
4275-
} ZEND_HASH_FOREACH_END();
4276-
return f->common.function_name;
4277-
}
4278-
/* }}} */
4279-
42804231
ZEND_API ZEND_COLD const char *zend_get_object_type(const zend_class_entry *ce) /* {{{ */
42814232
{
42824233
if(ce->ce_flags & ZEND_ACC_TRAIT) {

Zend/zend_API.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -575,9 +575,6 @@ static zend_always_inline int zend_forbid_dynamic_call(const char *func_name)
575575
return SUCCESS;
576576
}
577577

578-
ZEND_API zend_string *zend_find_alias_name(zend_class_entry *ce, zend_string *name);
579-
ZEND_API zend_string *zend_resolve_method_name(zend_class_entry *ce, zend_function *f);
580-
581578
ZEND_API ZEND_COLD const char *zend_get_object_type(const zend_class_entry *ce);
582579

583580
ZEND_API zend_bool zend_is_iterable(zval *iterable);

Zend/zend_builtin_functions.c

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1058,7 +1058,6 @@ ZEND_FUNCTION(get_class_methods)
10581058
zend_class_entry *ce = NULL;
10591059
zend_class_entry *scope;
10601060
zend_function *mptr;
1061-
zend_string *key;
10621061

10631062
if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &klass) == FAILURE) {
10641063
RETURN_THROWS();
@@ -1077,24 +1076,16 @@ ZEND_FUNCTION(get_class_methods)
10771076
array_init(return_value);
10781077
scope = zend_get_executed_scope();
10791078

1080-
ZEND_HASH_FOREACH_STR_KEY_PTR(&ce->function_table, key, mptr) {
1081-
1079+
ZEND_HASH_FOREACH_PTR(&ce->function_table, mptr) {
10821080
if ((mptr->common.fn_flags & ZEND_ACC_PUBLIC)
10831081
|| (scope &&
10841082
(((mptr->common.fn_flags & ZEND_ACC_PROTECTED) &&
10851083
zend_check_protected(mptr->common.scope, scope))
10861084
|| ((mptr->common.fn_flags & ZEND_ACC_PRIVATE) &&
10871085
scope == mptr->common.scope)))
10881086
) {
1089-
if (mptr->type == ZEND_USER_FUNCTION &&
1090-
(!mptr->op_array.refcount || *mptr->op_array.refcount > 1) &&
1091-
key && !same_name(key, mptr->common.function_name)) {
1092-
ZVAL_STR_COPY(&method_name, zend_find_alias_name(mptr->common.scope, key));
1093-
zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &method_name);
1094-
} else {
1095-
ZVAL_STR_COPY(&method_name, mptr->common.function_name);
1096-
zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &method_name);
1097-
}
1087+
ZVAL_STR_COPY(&method_name, mptr->common.function_name);
1088+
zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &method_name);
10981089
}
10991090
} ZEND_HASH_FOREACH_END();
11001091
}
@@ -1955,16 +1946,9 @@ ZEND_FUNCTION(debug_print_backtrace)
19551946
object = (Z_TYPE(call->This) == IS_OBJECT) ? Z_OBJ(call->This) : NULL;
19561947

19571948
if (call->func) {
1958-
zend_string *zend_function_name;
1959-
19601949
func = call->func;
1961-
if (func->common.scope && func->common.scope->trait_aliases) {
1962-
zend_function_name = zend_resolve_method_name(object ? object->ce : func->common.scope, func);
1963-
} else {
1964-
zend_function_name = func->common.function_name;
1965-
}
1966-
if (zend_function_name != NULL) {
1967-
function_name = ZSTR_VAL(zend_function_name);
1950+
if (func->common.function_name) {
1951+
function_name = ZSTR_VAL(func->common.function_name);
19681952
} else {
19691953
function_name = NULL;
19701954
}
@@ -2184,11 +2168,7 @@ ZEND_API void zend_fetch_debug_backtrace(zval *return_value, int skip_last, int
21842168

21852169
if (call && call->func) {
21862170
func = call->func;
2187-
function_name = (func->common.scope &&
2188-
func->common.scope->trait_aliases) ?
2189-
zend_resolve_method_name(
2190-
(object ? object->ce : func->common.scope), func) :
2191-
func->common.function_name;
2171+
function_name = func->common.function_name;
21922172
} else {
21932173
func = NULL;
21942174
function_name = NULL;

Zend/zend_closures.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -702,6 +702,7 @@ ZEND_API void zend_create_closure(zval *res, zend_function *func, zend_class_ent
702702
}
703703
memset(ptr, 0, func->op_array.cache_size);
704704
}
705+
zend_string_addref(closure->func.op_array.function_name);
705706
if (closure->func.op_array.refcount) {
706707
(*closure->func.op_array.refcount)++;
707708
}

Zend/zend_compile.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1063,10 +1063,10 @@ ZEND_API void function_add_ref(zend_function *function) /* {{{ */
10631063
ZEND_MAP_PTR_INIT(op_array->run_time_cache, zend_arena_alloc(&CG(arena), sizeof(void*)));
10641064
ZEND_MAP_PTR_SET(op_array->run_time_cache, NULL);
10651065
}
1066-
} else if (function->type == ZEND_INTERNAL_FUNCTION) {
1067-
if (function->common.function_name) {
1068-
zend_string_addref(function->common.function_name);
1069-
}
1066+
}
1067+
1068+
if (function->common.function_name) {
1069+
zend_string_addref(function->common.function_name);
10701070
}
10711071
}
10721072
/* }}} */

Zend/zend_inheritance.c

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ static zend_always_inline zend_function *zend_duplicate_function(zend_function *
115115
if (func->op_array.refcount) {
116116
(*func->op_array.refcount)++;
117117
}
118+
if (EXPECTED(func->op_array.function_name)) {
119+
zend_string_addref(func->op_array.function_name);
120+
}
118121
if (is_interface
119122
|| EXPECTED(!func->op_array.static_variables)) {
120123
/* reuse the same op_array structure */
@@ -1577,7 +1580,7 @@ static void zend_add_magic_methods(zend_class_entry* ce, zend_string* mname, zen
15771580
}
15781581
/* }}} */
15791582

1580-
static void zend_add_trait_method(zend_class_entry *ce, const char *name, zend_string *key, zend_function *fn, HashTable **overridden) /* {{{ */
1583+
static void zend_add_trait_method(zend_class_entry *ce, zend_string *name, zend_string *key, zend_function *fn, HashTable **overridden) /* {{{ */
15811584
{
15821585
zend_function *existing_fn = NULL;
15831586
zend_function *new_fn;
@@ -1622,11 +1625,11 @@ static void zend_add_trait_method(zend_class_entry *ce, const char *name, zend_s
16221625
/* two traits can't define the same non-abstract method */
16231626
#if 1
16241627
zend_error_noreturn(E_COMPILE_ERROR, "Trait method %s has not been applied, because there are collisions with other trait methods on %s",
1625-
name, ZSTR_VAL(ce->name));
1628+
ZSTR_VAL(name), ZSTR_VAL(ce->name));
16261629
#else /* TODO: better error message */
16271630
zend_error_noreturn(E_COMPILE_ERROR, "Trait method %s::%s has not been applied as %s::%s, because of collision with %s::%s",
16281631
ZSTR_VAL(fn->common.scope->name), ZSTR_VAL(fn->common.function_name),
1629-
ZSTR_VAL(ce->name), name,
1632+
ZSTR_VAL(ce->name), ZSTR_VAL(name),
16301633
ZSTR_VAL(existing_fn->common.scope->name), ZSTR_VAL(existing_fn->common.function_name));
16311634
#endif
16321635
} else {
@@ -1647,6 +1650,9 @@ static void zend_add_trait_method(zend_class_entry *ce, const char *name, zend_s
16471650
new_fn->op_array.fn_flags |= ZEND_ACC_TRAIT_CLONE;
16481651
new_fn->op_array.fn_flags &= ~ZEND_ACC_IMMUTABLE;
16491652
}
1653+
1654+
/* Reassign method name, in case it is an alias. */
1655+
new_fn->common.function_name = name;
16501656
function_add_ref(new_fn);
16511657
fn = zend_hash_update_ptr(&ce->function_table, key, new_fn);
16521658
zend_add_magic_methods(ce, key, fn);
@@ -1695,7 +1701,7 @@ static void zend_traits_copy_functions(zend_string *fnname, zend_function *fn, z
16951701
}
16961702

16971703
lcname = zend_string_tolower(alias->alias);
1698-
zend_add_trait_method(ce, ZSTR_VAL(alias->alias), lcname, &fn_copy, overridden);
1704+
zend_add_trait_method(ce, alias->alias, lcname, &fn_copy, overridden);
16991705
zend_string_release_ex(lcname, 0);
17001706

17011707
/* Record the trait from which this alias was resolved. */
@@ -1747,7 +1753,7 @@ static void zend_traits_copy_functions(zend_string *fnname, zend_function *fn, z
17471753
}
17481754
}
17491755

1750-
zend_add_trait_method(ce, ZSTR_VAL(fn->common.function_name), fnname, &fn_copy, overridden);
1756+
zend_add_trait_method(ce, fn->common.function_name, fnname, &fn_copy, overridden);
17511757
}
17521758
}
17531759
/* }}} */

Zend/zend_opcode.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,10 @@ ZEND_API void destroy_op_array(zend_op_array *op_array)
447447
efree(ZEND_MAP_PTR(op_array->run_time_cache));
448448
}
449449

450+
if (op_array->function_name) {
451+
zend_string_release_ex(op_array->function_name, 0);
452+
}
453+
450454
if (!op_array->refcount || --(*op_array->refcount) > 0) {
451455
return;
452456
}
@@ -476,9 +480,6 @@ ZEND_API void destroy_op_array(zend_op_array *op_array)
476480
}
477481
efree(op_array->opcodes);
478482

479-
if (op_array->function_name) {
480-
zend_string_release_ex(op_array->function_name, 0);
481-
}
482483
if (op_array->doc_comment) {
483484
zend_string_release_ex(op_array->doc_comment, 0);
484485
}

ext/opcache/zend_persist.c

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,16 @@ static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_sc
311311
EG(current_execute_data) = orig_execute_data;
312312
}
313313

314+
if (op_array->function_name) {
315+
zend_string *old_name = op_array->function_name;
316+
zend_accel_store_interned_string(op_array->function_name);
317+
/* Remember old function name, so it can be released multiple times if shared. */
318+
if (op_array->function_name != old_name
319+
&& !zend_shared_alloc_get_xlat_entry(&op_array->function_name)) {
320+
zend_shared_alloc_register_xlat_entry(&op_array->function_name, old_name);
321+
}
322+
}
323+
314324
if (op_array->scope) {
315325
zend_class_entry *scope = zend_shared_alloc_get_xlat_entry(op_array->scope);
316326

@@ -337,10 +347,6 @@ static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_sc
337347
op_array->literals = zend_shared_alloc_get_xlat_entry(op_array->literals);
338348
ZEND_ASSERT(op_array->literals != NULL);
339349
}
340-
if (op_array->function_name && !IS_ACCEL_INTERNED(op_array->function_name)) {
341-
op_array->function_name = zend_shared_alloc_get_xlat_entry(op_array->function_name);
342-
ZEND_ASSERT(op_array->function_name != NULL);
343-
}
344350
if (op_array->filename) {
345351
op_array->filename = zend_shared_alloc_get_xlat_entry(op_array->filename);
346352
ZEND_ASSERT(op_array->filename != NULL);
@@ -502,10 +508,6 @@ static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_sc
502508
ZEND_MAP_PTR_INIT(op_array->run_time_cache, NULL);
503509
}
504510

505-
if (op_array->function_name && !IS_ACCEL_INTERNED(op_array->function_name)) {
506-
zend_accel_store_interned_string(op_array->function_name);
507-
}
508-
509511
if (op_array->filename) {
510512
/* do not free! PHP has centralized filename storage, compiler will free it */
511513
zend_accel_memdup_string(op_array->filename);
@@ -632,6 +634,14 @@ static void zend_persist_class_method(zval *zv)
632634
if (op_array->refcount && --(*op_array->refcount) == 0) {
633635
efree(op_array->refcount);
634636
}
637+
638+
/* If op_array is shared, the function name refcount is still incremented for each use,
639+
* so we need to release it here. We remembered the original function name in xlat. */
640+
zend_string *old_function_name =
641+
zend_shared_alloc_get_xlat_entry(&old_op_array->function_name);
642+
if (old_function_name) {
643+
zend_string_release_ex(old_function_name, 0);
644+
}
635645
return;
636646
}
637647
if (ZCG(is_immutable_class)) {

ext/opcache/zend_persist_calc.c

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -171,14 +171,18 @@ static void zend_persist_type_calc(zend_type *type)
171171

172172
static void zend_persist_op_array_calc_ex(zend_op_array *op_array)
173173
{
174+
if (op_array->function_name) {
175+
zend_string *old_name = op_array->function_name;
176+
ADD_INTERNED_STRING(op_array->function_name);
177+
/* Remember old function name, so it can be released multiple times if shared. */
178+
if (op_array->function_name != old_name
179+
&& !zend_shared_alloc_get_xlat_entry(&op_array->function_name)) {
180+
zend_shared_alloc_register_xlat_entry(&op_array->function_name, old_name);
181+
}
182+
}
183+
174184
if (op_array->scope && zend_shared_alloc_get_xlat_entry(op_array->opcodes)) {
175185
/* already stored */
176-
if (op_array->function_name) {
177-
zend_string *new_name = zend_shared_alloc_get_xlat_entry(op_array->function_name);
178-
if (new_name) {
179-
op_array->function_name = new_name;
180-
}
181-
}
182186
ADD_SIZE(ZEND_ALIGNED_SIZE(zend_extensions_op_array_persist_calc(op_array)));
183187
return;
184188
}
@@ -211,16 +215,6 @@ static void zend_persist_op_array_calc_ex(zend_op_array *op_array)
211215
zend_shared_alloc_register_xlat_entry(op_array->opcodes, op_array->opcodes);
212216
ADD_SIZE(sizeof(zend_op) * op_array->last);
213217

214-
if (op_array->function_name) {
215-
zend_string *old_name = op_array->function_name;
216-
if (!zend_shared_alloc_get_xlat_entry(old_name)) {
217-
ADD_INTERNED_STRING(op_array->function_name);
218-
if (!zend_shared_alloc_get_xlat_entry(op_array->function_name)) {
219-
zend_shared_alloc_register_xlat_entry(old_name, op_array->function_name);
220-
}
221-
}
222-
}
223-
224218
if (op_array->filename) {
225219
ADD_STRING(op_array->filename);
226220
}
@@ -308,6 +302,14 @@ static void zend_persist_class_method_calc(zval *zv)
308302
if (!ZCG(is_immutable_class)) {
309303
ADD_ARENA_SIZE(sizeof(void*));
310304
}
305+
} else {
306+
/* If op_array is shared, the function name refcount is still incremented for each use,
307+
* so we need to release it here. We remembered the original function name in xlat. */
308+
zend_string *old_function_name =
309+
zend_shared_alloc_get_xlat_entry(&old_op_array->function_name);
310+
if (old_function_name) {
311+
zend_string_release_ex(old_function_name, 0);
312+
}
311313
}
312314
}
313315

ext/reflection/php_reflection.c

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1210,9 +1210,7 @@ static void reflection_method_factory(zend_class_entry *ce, zend_function *metho
12101210
ZVAL_OBJ(&intern->obj, Z_OBJ_P(closure_object));
12111211
}
12121212

1213-
ZVAL_STR_COPY(reflection_prop_name(object),
1214-
(method->common.scope && method->common.scope->trait_aliases)
1215-
? zend_resolve_method_name(ce, method) : method->common.function_name);
1213+
ZVAL_STR_COPY(reflection_prop_name(object), method->common.function_name);
12161214
ZVAL_STR_COPY(reflection_prop_class(object), method->common.scope->name);
12171215
}
12181216
/* }}} */

0 commit comments

Comments
 (0)