Skip to content

Commit cc22fb5

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 7c8007f commit cc22fb5

14 files changed

+143
-28
lines changed

Zend/Optimizer/dfa_pass.c

+13-8
Original file line numberDiff line numberDiff line change
@@ -395,14 +395,19 @@ int zend_dfa_optimize_calls(zend_op_array *op_array, zend_ssa *ssa)
395395

396396
do {
397397
if (call_info->caller_call_opline
398-
&& call_info->caller_call_opline->opcode == ZEND_DO_ICALL
399-
&& call_info->callee_func
400-
&& zend_string_equals_literal(call_info->callee_func->common.function_name, "in_array")
401-
&& (call_info->caller_init_opline->extended_value == 2
402-
|| (call_info->caller_init_opline->extended_value == 3
403-
&& (call_info->caller_call_opline - 1)->opcode == ZEND_SEND_VAL
404-
&& (call_info->caller_call_opline - 1)->op1_type == IS_CONST))) {
405-
398+
&& call_info->caller_call_opline->opcode == ZEND_DO_ICALL
399+
&& call_info->callee_func
400+
&& call_info->callee_func->common.function_name /* Ignore fake "pass" function */
401+
&& zend_string_equals_literal(call_info->callee_func->common.function_name, "in_array")
402+
&& (
403+
call_info->caller_init_opline->extended_value == 2
404+
|| (
405+
call_info->caller_init_opline->extended_value == 3
406+
&& (call_info->caller_call_opline - 1)->opcode == ZEND_SEND_VAL
407+
&& (call_info->caller_call_opline - 1)->op1_type == IS_CONST
408+
)
409+
)
410+
) {
406411
zend_op *send_array;
407412
zend_op *send_needly;
408413
bool strict = 0;

Zend/Optimizer/zend_optimizer.c

+7-1
Original file line numberDiff line numberDiff line change
@@ -854,7 +854,13 @@ zend_function *zend_optimizer_get_called_func(
854854
return func;
855855
} else if ((func = zend_hash_find_ptr(EG(function_table), Z_STR_P(function_name))) != NULL) {
856856
if (func->type == ZEND_INTERNAL_FUNCTION) {
857-
return func;
857+
/* For ZEND_INIT_NS_FCALL_BY_NAME, the function may be the "fake" pass function
858+
* to indicate that the global function should be used instead */
859+
if (UNEXPECTED(func == (zend_function *) &zend_pass_function)) {
860+
return NULL;
861+
} else {
862+
return func;
863+
}
858864
} else if (func->type == ZEND_USER_FUNCTION &&
859865
func->op_array.filename &&
860866
func->op_array.filename == op_array->filename) {
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
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
<?php
22
namespace bar;
33

4+
echo "First file START\n";
5+
46
foo();
57

68
for ($i = 0; $i < 3; $i += 1) {
79
foo();
810
}
11+
12+
echo "First file END\n";

Zend/tests/autoloading/function/global_fallback_doesnt_repeat_autoloading_file2.inc

+4
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,8 @@
22

33
namespace bar;
44

5+
echo "Second file START\n";
6+
57
foo();
8+
9+
echo "Second file END\n";

Zend/tests/autoloading/function/global_fallback_doesnt_repeat_autoloading_seperate_files_inclusion.phpt

+4
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,13 @@ include __DIR__ . DIRECTORY_SEPARATOR . 'global_fallback_doesnt_repeat_autoloadi
1818

1919
?>
2020
--EXPECT--
21+
First file START
2122
function loader called with bar\foo
2223
I am foo in global namespace.
2324
I am foo in global namespace.
2425
I am foo in global namespace.
2526
I am foo in global namespace.
27+
First file END
28+
Second file START
2629
I am foo in global namespace.
30+
Second file END
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
--TEST--
2+
Local function must be able to be defined after it got pinned to the global one
3+
--XFAIL--
4+
Currently fails under opcache as strlen() gets bound to the global function
5+
--FILE--
6+
<?php
7+
8+
namespace Foo;
9+
10+
var_dump(strlen('hello')); // triggers name pinning
11+
if (!function_exists('Foo\strlen') ) {
12+
function strlen(string $s) {
13+
return 42;
14+
}
15+
var_dump(\Foo\strlen('hello'));
16+
var_dump(strlen('hello'));
17+
}
18+
19+
20+
?>
21+
--EXPECT--
22+
int(5)
23+
int(42)
24+
int(42)
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

+9-5
Original file line numberDiff line numberDiff line change
@@ -1040,8 +1040,8 @@ ZEND_FUNCTION(function_exists)
10401040
{
10411041
zend_string *name;
10421042
bool autoload = true;
1043-
bool exists;
10441043
zend_string *lcname;
1044+
zend_function *fbc;
10451045

10461046
ZEND_PARSE_PARAMETERS_START(1, 2)
10471047
Z_PARAM_STR(name)
@@ -1058,14 +1058,18 @@ ZEND_FUNCTION(function_exists)
10581058
lcname = zend_string_tolower(name);
10591059
}
10601060

1061-
exists = zend_hash_exists(EG(function_table), lcname);
1061+
fbc = (zend_function*) zend_hash_find_ptr(EG(function_table), lcname);
10621062
zend_string_release_ex(lcname, 0);
10631063
} else {
1064-
zend_function *fbc = zend_lookup_function(name);
1065-
exists = fbc;
1064+
fbc = zend_lookup_function(name);
10661065
}
10671066

1068-
RETURN_BOOL(exists);
1067+
/* If the function was marked as using the global function, indicate it doesn't exist */
1068+
if (!fbc || fbc == (zend_function *) &zend_pass_function) {
1069+
RETURN_FALSE;
1070+
}
1071+
1072+
RETURN_TRUE;
10691073
}
10701074
/* }}} */
10711075

Zend/zend_compile.c

+17-1
Original file line numberDiff line numberDiff line change
@@ -1193,6 +1193,20 @@ ZEND_API zend_result do_bind_function(zend_function *func, zval *lcname) /* {{{
11931193
{
11941194
zend_function *added_func = zend_hash_add_ptr(EG(function_table), Z_STR_P(lcname), func);
11951195
if (UNEXPECTED(!added_func)) {
1196+
/* If a function name was marked as using the global name, properly declare the namespaced function */
1197+
zend_function *old_func = zend_hash_find_ptr(EG(function_table), Z_STR_P(lcname));
1198+
if (old_func == (zend_function *) &zend_pass_function) {
1199+
zend_hash_update_ptr(EG(function_table), Z_STR_P(lcname), func);
1200+
if (func->op_array.refcount) {
1201+
++*func->op_array.refcount;
1202+
}
1203+
if (func->common.function_name) {
1204+
zend_string_addref(func->common.function_name);
1205+
}
1206+
zend_observer_function_declared_notify(&func->op_array, Z_STR_P(lcname));
1207+
return SUCCESS;
1208+
}
1209+
11961210
do_bind_function_error(Z_STR_P(lcname), &func->op_array, 0);
11971211
return FAILURE;
11981212
}
@@ -1203,7 +1217,9 @@ ZEND_API zend_result do_bind_function(zend_function *func, zval *lcname) /* {{{
12031217
if (func->common.function_name) {
12041218
zend_string_addref(func->common.function_name);
12051219
}
1206-
zend_observer_function_declared_notify(&func->op_array, Z_STR_P(lcname));
1220+
if (EXPECTED(func != (zend_function *) &zend_pass_function)) {
1221+
zend_observer_function_declared_notify(&func->op_array, Z_STR_P(lcname));
1222+
}
12071223
return SUCCESS;
12081224
}
12091225
/* }}} */

Zend/zend_execute.c

+4-1
Original file line numberDiff line numberDiff line change
@@ -4694,7 +4694,10 @@ static zend_never_inline zend_execute_data *zend_init_dynamic_call_string(zend_s
46944694
init_func_run_time_cache(&fbc->op_array);
46954695
}
46964696
} else {
4697-
if (UNEXPECTED((fbc = zend_lookup_function(function)) == NULL)) {
4697+
if (UNEXPECTED(
4698+
((fbc = zend_lookup_function(function)) == NULL)
4699+
|| (fbc == (zend_function *) &zend_pass_function)
4700+
)) {
46984701
zend_throw_error(NULL, "Call to undefined function %s()", ZSTR_VAL(function));
46994702
return NULL;
47004703
}

Zend/zend_opcode.c

+3
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,9 @@ ZEND_API void zend_function_dtor(zval *zv)
156156
ZEND_ASSERT(function->common.function_name);
157157
destroy_op_array(&function->op_array);
158158
/* op_arrays are allocated on arena, so we don't have to free them */
159+
} else if (UNEXPECTED(function == (zend_function *) &zend_pass_function)) {
160+
/* Ignore fake pass function */
161+
return;
159162
} else {
160163
ZEND_ASSERT(function->type == ZEND_INTERNAL_FUNCTION);
161164
ZEND_ASSERT(function->common.function_name);

Zend/zend_vm_def.h

+15-2
Original file line numberDiff line numberDiff line change
@@ -3770,6 +3770,8 @@ ZEND_VM_HOT_HANDLER(59, ZEND_INIT_FCALL_BY_NAME, ANY, CONST, NUM|CACHE_SLOT)
37703770
}
37713771
CACHE_PTR(opline->result.num, fbc);
37723772
}
3773+
3774+
ZEND_ASSERT(fbc != (zend_function*)&zend_pass_function);
37733775
call = _zend_vm_stack_push_call_frame(ZEND_CALL_NESTED_FUNCTION,
37743776
fbc, opline->extended_value, NULL);
37753777
call->prev_execute_data = EX(call);
@@ -3885,6 +3887,7 @@ ZEND_VM_HANDLER(118, ZEND_INIT_USER_CALL, CONST, CONST|TMPVAR|CV, NUM)
38853887
HANDLE_EXCEPTION();
38863888
}
38873889

3890+
ZEND_ASSERT(func != (zend_function*)&zend_pass_function);
38883891
call = zend_vm_stack_push_call_frame(call_info,
38893892
func, opline->extended_value, object_or_called_scope);
38903893
call->prev_execute_data = EX(call);
@@ -3917,18 +3920,27 @@ ZEND_VM_HOT_HANDLER(69, ZEND_INIT_NS_FCALL_BY_NAME, ANY, CONST, NUM|CACHE_SLOT)
39173920
}
39183921
ZEND_VM_DISPATCH_TO_HELPER(zend_undefined_function_helper);
39193922
}
3920-
/* We bind the unqualified name to the global function
3923+
/* We bind the unqualified name to the internal "zend_pass_function" for it to indicate that it
3924+
* should use the global function.
39213925
* Use the lowercase name of the function stored in the first cache slot as
39223926
* function names are case insensitive */
39233927
else {
39243928
zval tmp;
39253929
ZVAL_STR(&tmp, Z_STR_P(function_name+1));
3926-
do_bind_function(fbc, &tmp);
3930+
do_bind_function((zend_function *) &zend_pass_function, &tmp);
39273931
}
3932+
} else if (fbc == (zend_function *) &zend_pass_function) {
3933+
/* Unqualified call was marked as using the global function
3934+
* Thus we need to replace the pass function with the actual function.
3935+
* Use the lowercase name of the function without tha namespace which is stored in the second cache slot */
3936+
fbc = (zend_function *) zend_hash_find_ptr(EG(function_table), Z_STR_P(function_name+2));
3937+
ZEND_ASSERT(fbc);
39283938
}
39293939
if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) {
39303940
init_func_run_time_cache(&fbc->op_array);
39313941
}
3942+
3943+
ZEND_ASSERT(fbc != (zend_function*)&zend_pass_function);
39323944
CACHE_PTR(opline->result.num, fbc);
39333945
}
39343946

@@ -3958,6 +3970,7 @@ ZEND_VM_HOT_HANDLER(61, ZEND_INIT_FCALL, NUM, CONST, NUM|CACHE_SLOT)
39583970
CACHE_PTR(opline->result.num, fbc);
39593971
}
39603972

3973+
ZEND_ASSERT(fbc != (zend_function*)&zend_pass_function);
39613974
call = _zend_vm_stack_push_call_frame_ex(
39623975
opline->op1.num, ZEND_CALL_NESTED_FUNCTION,
39633976
fbc, opline->extended_value, NULL);

Zend/zend_vm_execute.h

+17-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)