Skip to content

Commit 41f9378

Browse files
DanackGirgias
authored andcommitted
Run autoloader only once for unqualified functions in a namespace
We bind the namespaced function to the global one if such a function exists.
1 parent 201640b commit 41f9378

6 files changed

+95
-9
lines changed

Zend/tests/autoloading/function/exceptions_during_autoloading004.phpt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ namespace bar {
2929
try {
3030
foo();
3131
} catch (\Throwable $e) {
32-
/* No autoloading for unqualified function names */
3332
do {
3433
echo $e::class, ': ', $e->getMessage(), "\n";
3534
} while ($e = $e->getPrevious());
@@ -52,7 +51,8 @@ Try-catch around function_exists()
5251
autoload_first
5352
first
5453
Try-catch around unqualified function call
55-
Error: Call to undefined function bar\foo()
54+
autoload_first
55+
Exception: first
5656
Try-catch around qualified function call
5757
autoload_first
5858
Exception: first

Zend/tests/autoloading/function/global_fallback002.phpt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
--TEST--
2-
Fallback to global function should not trigger autoloading.
2+
Fallback to global function triggers autoloading once.
33
--FILE--
44
<?php
55

@@ -21,4 +21,5 @@ namespace bar {
2121

2222
?>
2323
--EXPECT--
24+
function loader called with bar\foo
2425
I am foo in global namespace.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
--TEST--
2+
Fallback to non-existent function triggers autoloading once in namespace, once in global.
3+
--FILE--
4+
<?php
5+
6+
namespace {
7+
function loader($name) {
8+
echo "function loader called with $name\n";
9+
}
10+
11+
autoload_register_function('loader');
12+
}
13+
14+
namespace bar {
15+
try {
16+
non_existent_function();
17+
}
18+
catch (\Error $e) {
19+
echo "Error correctly caught: " . $e->getMessage() . "\n";
20+
}
21+
}
22+
23+
?>
24+
--EXPECT--
25+
function loader called with bar\non_existent_function
26+
function loader called with non_existent_function
27+
Error correctly caught: Call to undefined function bar\non_existent_function()
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
--TEST--
2+
Fallback to global function should trigger autoloading only once per namespace.
3+
--FILE--
4+
<?php
5+
6+
namespace {
7+
function loader($name) {
8+
echo "function loader called with $name\n";
9+
}
10+
11+
autoload_register_function('loader');
12+
13+
function foo() {
14+
echo "I am foo in global namespace.\n";
15+
}
16+
}
17+
18+
namespace bar {
19+
foo();
20+
21+
for ($i = 0; $i < 3; $i += 1) {
22+
foo();
23+
}
24+
}
25+
namespace bar {
26+
foo();
27+
}
28+
29+
namespace Quux {
30+
foo();
31+
}
32+
33+
?>
34+
--EXPECT--
35+
function loader called with bar\foo
36+
I am foo in global namespace.
37+
I am foo in global namespace.
38+
I am foo in global namespace.
39+
I am foo in global namespace.
40+
I am foo in global namespace.
41+
function loader called with Quux\foo
42+
I am foo in global namespace.

Zend/zend_vm_def.h

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3859,19 +3859,27 @@ ZEND_VM_HOT_HANDLER(69, ZEND_INIT_NS_FCALL_BY_NAME, ANY, CONST, NUM|CACHE_SLOT)
38593859
if (UNEXPECTED(fbc == NULL)) {
38603860
zval *function_name = (zval *)RT_CONSTANT(opline, opline->op2);
38613861
/* Fetch lowercase name stored in the next literal slot */
3862-
fbc = zend_lookup_function_ex(Z_STR_P(function_name), Z_STR_P(function_name+1), /* use_autoload */ false);
3862+
fbc = zend_lookup_function_ex(Z_STR_P(function_name), Z_STR_P(function_name+1), /* use_autoload */ true);
38633863
if (UNEXPECTED(fbc == NULL)) {
38643864
if (UNEXPECTED(EG(exception))) {
38653865
HANDLE_EXCEPTION();
38663866
}
38673867
/* Fallback onto global namespace, by fetching the unqualified lowercase name stored in the second literal slot */
3868-
fbc = zend_lookup_function_ex(Z_STR_P(function_name+2), Z_STR_P(function_name+2), /* use_autoload */ false);
3869-
if (fbc == NULL) {
3868+
fbc = zend_lookup_function_ex(Z_STR_P(function_name+2), Z_STR_P(function_name+2), /* use_autoload */ true);
3869+
if (UNEXPECTED(fbc == NULL)) {
38703870
if (UNEXPECTED(EG(exception))) {
38713871
HANDLE_EXCEPTION();
38723872
}
38733873
ZEND_VM_DISPATCH_TO_HELPER(zend_undefined_function_helper);
38743874
}
3875+
/* We bind the unqualified name to the global function
3876+
* Use the lowercase name of the function stored in the first cache slot as
3877+
* function names are case insensitive */
3878+
else {
3879+
zval tmp;
3880+
ZVAL_STR(&tmp, Z_STR_P(function_name+1));
3881+
do_bind_function(fbc, &tmp);
3882+
}
38753883
}
38763884
if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) {
38773885
init_func_run_time_cache(&fbc->op_array);

Zend/zend_vm_execute.h

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

0 commit comments

Comments
 (0)