Skip to content

Commit

Permalink
Run autoloader only once for unqualified functions in a namespace
Browse files Browse the repository at this point in the history
We bind the namespaced function to the global one if such a function exists.
  • Loading branch information
Danack authored and Girgias committed Dec 15, 2022
1 parent 201640b commit 41f9378
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ namespace bar {
try {
foo();
} catch (\Throwable $e) {
/* No autoloading for unqualified function names */
do {
echo $e::class, ': ', $e->getMessage(), "\n";
} while ($e = $e->getPrevious());
Expand All @@ -52,7 +51,8 @@ Try-catch around function_exists()
autoload_first
first
Try-catch around unqualified function call
Error: Call to undefined function bar\foo()
autoload_first
Exception: first
Try-catch around qualified function call
autoload_first
Exception: first
Expand Down
3 changes: 2 additions & 1 deletion Zend/tests/autoloading/function/global_fallback002.phpt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
--TEST--
Fallback to global function should not trigger autoloading.
Fallback to global function triggers autoloading once.
--FILE--
<?php

Expand All @@ -21,4 +21,5 @@ namespace bar {

?>
--EXPECT--
function loader called with bar\foo
I am foo in global namespace.
27 changes: 27 additions & 0 deletions Zend/tests/autoloading/function/global_fallback003.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
--TEST--
Fallback to non-existent function triggers autoloading once in namespace, once in global.
--FILE--
<?php

namespace {
function loader($name) {
echo "function loader called with $name\n";
}

autoload_register_function('loader');
}

namespace bar {
try {
non_existent_function();
}
catch (\Error $e) {
echo "Error correctly caught: " . $e->getMessage() . "\n";
}
}

?>
--EXPECT--
function loader called with bar\non_existent_function
function loader called with non_existent_function
Error correctly caught: Call to undefined function bar\non_existent_function()
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
--TEST--
Fallback to global function should trigger autoloading only once per namespace.
--FILE--
<?php

namespace {
function loader($name) {
echo "function loader called with $name\n";
}

autoload_register_function('loader');

function foo() {
echo "I am foo in global namespace.\n";
}
}

namespace bar {
foo();

for ($i = 0; $i < 3; $i += 1) {
foo();
}
}
namespace bar {
foo();
}

namespace Quux {
foo();
}

?>
--EXPECT--
function loader called with bar\foo
I am foo in global namespace.
I am foo in global namespace.
I am foo in global namespace.
I am foo in global namespace.
I am foo in global namespace.
function loader called with Quux\foo
I am foo in global namespace.
14 changes: 11 additions & 3 deletions Zend/zend_vm_def.h
Original file line number Diff line number Diff line change
Expand Up @@ -3859,19 +3859,27 @@ ZEND_VM_HOT_HANDLER(69, ZEND_INIT_NS_FCALL_BY_NAME, ANY, CONST, NUM|CACHE_SLOT)
if (UNEXPECTED(fbc == NULL)) {
zval *function_name = (zval *)RT_CONSTANT(opline, opline->op2);
/* Fetch lowercase name stored in the next literal slot */
fbc = zend_lookup_function_ex(Z_STR_P(function_name), Z_STR_P(function_name+1), /* use_autoload */ false);
fbc = zend_lookup_function_ex(Z_STR_P(function_name), Z_STR_P(function_name+1), /* use_autoload */ true);
if (UNEXPECTED(fbc == NULL)) {
if (UNEXPECTED(EG(exception))) {
HANDLE_EXCEPTION();
}
/* Fallback onto global namespace, by fetching the unqualified lowercase name stored in the second literal slot */
fbc = zend_lookup_function_ex(Z_STR_P(function_name+2), Z_STR_P(function_name+2), /* use_autoload */ false);
if (fbc == NULL) {
fbc = zend_lookup_function_ex(Z_STR_P(function_name+2), Z_STR_P(function_name+2), /* use_autoload */ true);
if (UNEXPECTED(fbc == NULL)) {
if (UNEXPECTED(EG(exception))) {
HANDLE_EXCEPTION();
}
ZEND_VM_DISPATCH_TO_HELPER(zend_undefined_function_helper);
}
/* We bind the unqualified name to the global function
* Use the lowercase name of the function stored in the first cache slot as
* function names are case insensitive */
else {
zval tmp;
ZVAL_STR(&tmp, Z_STR_P(function_name+1));
do_bind_function(fbc, &tmp);
}
}
if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) {
init_func_run_time_cache(&fbc->op_array);
Expand Down
14 changes: 11 additions & 3 deletions Zend/zend_vm_execute.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 41f9378

Please sign in to comment.