Skip to content

Commit 060bab7

Browse files
committed
Attempt to not alias on autoloading non qualified namespace call
OpCache doesn't work properly as the local function gets bound to a global one.
1 parent 46657a4 commit 060bab7

12 files changed

+136
-29
lines changed

Zend/Optimizer/compact_literals.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -603,7 +603,6 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
603603
break;
604604
case ZEND_INIT_FCALL:
605605
case ZEND_INIT_FCALL_BY_NAME:
606-
case ZEND_INIT_NS_FCALL_BY_NAME:
607606
// op2 func
608607
if (func_slot[opline->op2.constant] >= 0) {
609608
opline->result.num = func_slot[opline->op2.constant];
@@ -613,6 +612,13 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
613612
func_slot[opline->op2.constant] = opline->result.num;
614613
}
615614
break;
615+
/* Do not compact runtime function cache for non qualified namespace calls */
616+
case ZEND_INIT_NS_FCALL_BY_NAME:
617+
// op2 func
618+
opline->result.num = cache_size;
619+
cache_size += sizeof(void *);
620+
func_slot[opline->op2.constant] = opline->result.num;
621+
break;
616622
case ZEND_INIT_METHOD_CALL:
617623
if (opline->op2_type == IS_CONST) {
618624
// op2 method

Zend/Optimizer/dfa_pass.c

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -408,14 +408,19 @@ int zend_dfa_optimize_calls(zend_op_array *op_array, zend_ssa *ssa)
408408

409409
do {
410410
if (call_info->caller_call_opline
411-
&& call_info->caller_call_opline->opcode == ZEND_DO_ICALL
412-
&& call_info->callee_func
413-
&& zend_string_equals_literal(call_info->callee_func->common.function_name, "in_array")
414-
&& (call_info->caller_init_opline->extended_value == 2
415-
|| (call_info->caller_init_opline->extended_value == 3
416-
&& (call_info->caller_call_opline - 1)->opcode == ZEND_SEND_VAL
417-
&& (call_info->caller_call_opline - 1)->op1_type == IS_CONST))) {
418-
411+
&& call_info->caller_call_opline->opcode == ZEND_DO_ICALL
412+
&& call_info->callee_func
413+
&& call_info->callee_func->common.function_name /* Ignore fake "pass" function */
414+
&& zend_string_equals_literal(call_info->callee_func->common.function_name, "in_array")
415+
&& (
416+
call_info->caller_init_opline->extended_value == 2
417+
|| (
418+
call_info->caller_init_opline->extended_value == 3
419+
&& (call_info->caller_call_opline - 1)->opcode == ZEND_SEND_VAL
420+
&& (call_info->caller_call_opline - 1)->op1_type == IS_CONST
421+
)
422+
)
423+
) {
419424
zend_op *send_array;
420425
zend_op *send_needly;
421426
bool strict = 0;

Zend/Optimizer/zend_optimizer.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -812,7 +812,13 @@ static bool zend_optimizer_ignore_function(zval *fbc_zv, zend_string *filename)
812812
zend_function *fbc = Z_PTR_P(fbc_zv);
813813

814814
if (fbc->type == ZEND_INTERNAL_FUNCTION) {
815-
return false;
815+
/* For ZEND_INIT_NS_FCALL_BY_NAME, the function may be the "fake" pass function
816+
* to indicate that the global function should be used instead */
817+
if (UNEXPECTED(fbc == (zend_function *) &zend_pass_function)) {
818+
return true;
819+
} else {
820+
return false;
821+
}
816822
} else if (fbc->type == ZEND_USER_FUNCTION) {
817823
if (fbc->op_array.fn_flags & ZEND_ACC_PRELOADED) {
818824
Bucket *fbc_bucket = (Bucket*)((uintptr_t)fbc_zv - XtOffsetOf(Bucket, val));
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
--TEST--
2+
Local function which falls back to global is NOT aliased to the global one, dynamic string
3+
--FILE--
4+
<?php
5+
6+
namespace Foo {
7+
var_dump(strlen('hello')); // triggers name pinning
8+
}
9+
namespace Bar {
10+
$v = 'Foo\strlen';
11+
var_dump($v('hello'));
12+
}
13+
14+
?>
15+
--EXPECTF--
16+
int(5)
17+
18+
Fatal error: Uncaught Error: Call to undefined function Foo\strlen() in %s:%d
19+
Stack trace:
20+
#0 {main}
21+
thrown in %s on line %d
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
--TEST--
2+
Local function must be able to be defined after it got pinned to the global one
3+
--FILE--
4+
<?php
5+
6+
namespace Foo;
7+
8+
var_dump(strlen('hello')); // triggers name pinning
9+
if (!function_exists('Foo\strlen') ) {
10+
function strlen(string $s) {
11+
return 42;
12+
}
13+
var_dump(\Foo\strlen('hello'));
14+
var_dump(strlen('hello'));
15+
}
16+
17+
18+
?>
19+
--EXPECT--
20+
int(5)
21+
int(42)
22+
int(42)
Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
11
--TEST--
2-
Local function which falls back to global is aliased to the global one
2+
Local function which falls back to global is NOT aliased to the global one, function_exists
33
--FILE--
44
<?php
55

6-
namespace Bar {
7-
if ( function_exists('Foo\strlen') ) {
8-
var_dump(\Foo\strlen('hello'));
9-
}
10-
}
116
namespace Foo {
127
var_dump(strlen('hello')); // triggers name pinning
138
}
@@ -21,5 +16,3 @@ namespace Bar {
2116
?>
2217
--EXPECT--
2318
int(5)
24-
\Foo\strlen() was bound to global \strlen()
25-
int(5)

Zend/zend_builtin_functions.c

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1165,8 +1165,8 @@ ZEND_FUNCTION(function_exists)
11651165
{
11661166
zend_string *name;
11671167
bool autoload = true;
1168-
bool exists;
11691168
zend_string *lcname;
1169+
zend_function *fbc;
11701170

11711171
ZEND_PARSE_PARAMETERS_START(1, 2)
11721172
Z_PARAM_STR(name)
@@ -1183,14 +1183,18 @@ ZEND_FUNCTION(function_exists)
11831183
lcname = zend_string_tolower(name);
11841184
}
11851185

1186-
exists = zend_hash_exists(EG(function_table), lcname);
1186+
fbc = (zend_function*) zend_hash_find_ptr(EG(function_table), lcname);
11871187
zend_string_release_ex(lcname, 0);
11881188
} else {
1189-
zend_function *fbc = zend_lookup_function(name);
1190-
exists = fbc;
1189+
fbc = zend_lookup_function(name);
11911190
}
11921191

1193-
RETURN_BOOL(exists);
1192+
/* If the function was marked as using the global function, indicate it doesn't exist */
1193+
if (!fbc || fbc == (zend_function *) &zend_pass_function) {
1194+
RETURN_FALSE;
1195+
}
1196+
1197+
RETURN_TRUE;
11941198
}
11951199
/* }}} */
11961200

Zend/zend_compile.c

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1261,6 +1261,20 @@ ZEND_API zend_result do_bind_function(zend_function *func, zval *lcname) /* {{{
12611261
{
12621262
zend_function *added_func = zend_hash_add_ptr(EG(function_table), Z_STR_P(lcname), func);
12631263
if (UNEXPECTED(!added_func)) {
1264+
/* If a function name was marked as using the global name, properly declare the namespaced function */
1265+
zend_function *old_func = zend_hash_find_ptr(EG(function_table), Z_STR_P(lcname));
1266+
if (old_func == (zend_function *) &zend_pass_function) {
1267+
zend_hash_update_ptr(EG(function_table), Z_STR_P(lcname), func);
1268+
if (func->op_array.refcount) {
1269+
++*func->op_array.refcount;
1270+
}
1271+
if (func->common.function_name) {
1272+
zend_string_addref(func->common.function_name);
1273+
}
1274+
zend_observer_function_declared_notify(&func->op_array, Z_STR_P(lcname));
1275+
return SUCCESS;
1276+
}
1277+
12641278
do_bind_function_error(Z_STR_P(lcname), &func->op_array, 0);
12651279
return FAILURE;
12661280
}
@@ -1271,7 +1285,9 @@ ZEND_API zend_result do_bind_function(zend_function *func, zval *lcname) /* {{{
12711285
if (func->common.function_name) {
12721286
zend_string_addref(func->common.function_name);
12731287
}
1274-
zend_observer_function_declared_notify(&func->op_array, Z_STR_P(lcname));
1288+
if (EXPECTED(func != (zend_function *) &zend_pass_function)) {
1289+
zend_observer_function_declared_notify(&func->op_array, Z_STR_P(lcname));
1290+
}
12751291
return SUCCESS;
12761292
}
12771293
/* }}} */

Zend/zend_execute.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4865,7 +4865,10 @@ static zend_never_inline zend_execute_data *zend_init_dynamic_call_string(zend_s
48654865
init_func_run_time_cache(&fbc->op_array);
48664866
}
48674867
} else {
4868-
if (UNEXPECTED((fbc = zend_lookup_function(function)) == NULL)) {
4868+
if (UNEXPECTED(
4869+
((fbc = zend_lookup_function(function)) == NULL)
4870+
|| (fbc == (zend_function *) &zend_pass_function)
4871+
)) {
48694872
zend_throw_error(NULL, "Call to undefined function %s()", ZSTR_VAL(function));
48704873
return NULL;
48714874
}

Zend/zend_opcode.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,9 @@ ZEND_API void zend_function_dtor(zval *zv)
149149
ZEND_ASSERT(function->common.function_name);
150150
destroy_op_array(&function->op_array);
151151
/* op_arrays are allocated on arena, so we don't have to free them */
152+
} else if (UNEXPECTED(function == (zend_function *) &zend_pass_function)) {
153+
/* Ignore fake pass function */
154+
return;
152155
} else {
153156
ZEND_ASSERT(function->type == ZEND_INTERNAL_FUNCTION);
154157
ZEND_ASSERT(function->common.function_name);

Zend/zend_vm_def.h

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3843,6 +3843,8 @@ ZEND_VM_HOT_HANDLER(59, ZEND_INIT_FCALL_BY_NAME, ANY, CONST, NUM|CACHE_SLOT)
38433843
}
38443844
CACHE_PTR(opline->result.num, fbc);
38453845
}
3846+
3847+
ZEND_ASSERT(fbc != (zend_function*)&zend_pass_function);
38463848
call = _zend_vm_stack_push_call_frame(ZEND_CALL_NESTED_FUNCTION,
38473849
fbc, opline->extended_value, NULL);
38483850
call->prev_execute_data = EX(call);
@@ -3958,6 +3960,7 @@ ZEND_VM_HANDLER(118, ZEND_INIT_USER_CALL, CONST, CONST|TMPVAR|CV, NUM)
39583960
HANDLE_EXCEPTION();
39593961
}
39603962

3963+
ZEND_ASSERT(func != (zend_function*)&zend_pass_function);
39613964
call = zend_vm_stack_push_call_frame(call_info,
39623965
func, opline->extended_value, object_or_called_scope);
39633966
call->prev_execute_data = EX(call);
@@ -3990,18 +3993,27 @@ ZEND_VM_HOT_HANDLER(69, ZEND_INIT_NS_FCALL_BY_NAME, ANY, CONST, NUM|CACHE_SLOT)
39903993
}
39913994
ZEND_VM_DISPATCH_TO_HELPER(zend_undefined_function_helper);
39923995
}
3993-
/* We bind the unqualified name to the global function
3996+
/* We bind the unqualified name to the internal "zend_pass_function" for it to indicate that it
3997+
* should use the global function.
39943998
* Use the lowercase name of the function stored in the first cache slot as
39953999
* function names are case insensitive */
39964000
else {
39974001
zval tmp;
39984002
ZVAL_STR(&tmp, Z_STR_P(function_name+1));
3999-
do_bind_function(fbc, &tmp);
4003+
do_bind_function((zend_function *) &zend_pass_function, &tmp);
40004004
}
4005+
} else if (fbc == (zend_function *) &zend_pass_function) {
4006+
/* Unqualified call was marked as using the global function
4007+
* Thus we need to replace the pass function with the actual function.
4008+
* Use the lowercase name of the function without tha namespace which is stored in the second cache slot */
4009+
fbc = (zend_function *) zend_hash_find_ptr(EG(function_table), Z_STR_P(function_name+2));
4010+
ZEND_ASSERT(fbc);
40014011
}
40024012
if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) {
40034013
init_func_run_time_cache(&fbc->op_array);
40044014
}
4015+
4016+
ZEND_ASSERT(fbc != (zend_function*)&zend_pass_function);
40054017
CACHE_PTR(opline->result.num, fbc);
40064018
}
40074019

@@ -4031,6 +4043,7 @@ ZEND_VM_HOT_HANDLER(61, ZEND_INIT_FCALL, NUM, CONST, NUM|CACHE_SLOT)
40314043
CACHE_PTR(opline->result.num, fbc);
40324044
}
40334045

4046+
ZEND_ASSERT(fbc != (zend_function*)&zend_pass_function);
40344047
call = _zend_vm_stack_push_call_frame_ex(
40354048
opline->op1.num, ZEND_CALL_NESTED_FUNCTION,
40364049
fbc, opline->extended_value, NULL);

Zend/zend_vm_execute.h

Lines changed: 17 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)