Skip to content

Use ZPP callable check in spl_autoload_register #5301

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Zend/tests/bug78868.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ function main_autoload($class_name) {
eval("class B {const foo = 1;}");
}

spl_autoload_register('main_autoload', false);
spl_autoload_register('main_autoload');

$classA = new ReflectionClass("A");
$props = $classA->getProperties();
Expand Down
88 changes: 24 additions & 64 deletions ext/spl/php_spl.c
Original file line number Diff line number Diff line change
Expand Up @@ -508,86 +508,46 @@ PHP_FUNCTION(spl_autoload_call)
PHP_FUNCTION(spl_autoload_register)
{
zend_string *func_name;
char *error = NULL;
zend_string *lc_name;
zval *zcallable = NULL;
zend_bool do_throw = 1;
zend_bool prepend = 0;
zend_function *spl_func_ptr;
autoload_func_info alfi;
zend_object *obj_ptr;
zend_fcall_info fci = {0};
zend_fcall_info_cache fcc;

if (zend_parse_parameters(ZEND_NUM_ARGS(), "|z!bb", &zcallable, &do_throw, &prepend) == FAILURE) {
RETURN_THROWS();
ZEND_PARSE_PARAMETERS_START(0, 3)
Z_PARAM_OPTIONAL
Z_PARAM_FUNC_OR_NULL(fci, fcc)
Z_PARAM_BOOL(do_throw)
Z_PARAM_BOOL(prepend)
ZEND_PARSE_PARAMETERS_END();

if (!do_throw) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know why this argument exists in the first place (e.g. from commit history)? It's very weird, but I feel like there must be some reason it was added...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll look into it, because I'm quite baffled by it too.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like it was added 12 years ago without much explanation: 0cfdd9a

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reason for change introduction: https://bugs.php.net/bug.php?id=42823
Thanks to @IMSoP for finding the bug report.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

composer defaults to true: composer/composer@c80cb76

Docs: https://getcomposer.org/doc/06-config.md#prepend-autoloader

Seems it's pretty critical, except if we make the prepend behaviour the default and deprecate the last 2 arguments.
Or maybe a better idea is to introduce a new function autoload_register(callable $callable, bool $prepend) but that means changing all SPL related autoload functions which doesn't seem really feasible.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, isn't this about do_throw? That has been introduced with a5c37f3 ("Add ability to fail silently").

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Err, sorry, I think was referring to the do_throw argument here, that gets ignored by this patch. I can see the usefulness of prepend, but not so much of do_throw.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah my mistake, I would say the consistent TypeError RFC would back this change, but it does make the signature awkward

php_error_docref(NULL, E_NOTICE, "Argument #2 ($do_throw) has been ignored, "
"spl_autoload_register() will always throw");
}

if (zcallable) {
if (!zend_is_callable_ex(zcallable, NULL, 0, &func_name, &fcc, &error)) {
alfi.ce = fcc.calling_scope;
alfi.func_ptr = fcc.function_handler;
obj_ptr = fcc.object;
if (Z_TYPE_P(zcallable) == IS_ARRAY) {
if (!obj_ptr && alfi.func_ptr && !(alfi.func_ptr->common.fn_flags & ZEND_ACC_STATIC)) {
if (do_throw) {
zend_throw_exception_ex(spl_ce_LogicException, 0, "Passed array specifies a non static method but no object (%s)", error);
}
if (error) {
efree(error);
}
zend_string_release_ex(func_name, 0);
RETURN_FALSE;
} else if (do_throw) {
zend_throw_exception_ex(spl_ce_LogicException, 0, "Passed array does not specify %s %smethod (%s)", alfi.func_ptr ? "a callable" : "an existing", !obj_ptr ? "static " : "", error);
}
if (error) {
efree(error);
}
zend_string_release_ex(func_name, 0);
RETURN_FALSE;
} else if (Z_TYPE_P(zcallable) == IS_STRING) {
if (do_throw) {
zend_throw_exception_ex(spl_ce_LogicException, 0, "Function '%s' not %s (%s)", ZSTR_VAL(func_name), alfi.func_ptr ? "callable" : "found", error);
}
if (error) {
efree(error);
}
zend_string_release_ex(func_name, 0);
RETURN_FALSE;
} else {
if (do_throw) {
zend_throw_exception_ex(spl_ce_LogicException, 0, "Illegal value passed (%s)", error);
}
if (error) {
efree(error);
}
zend_string_release_ex(func_name, 0);
RETURN_FALSE;
}
} else if (fcc.function_handler->type == ZEND_INTERNAL_FUNCTION &&
fcc.function_handler->internal_function.handler == zif_spl_autoload_call) {
if (do_throw) {
zend_throw_exception_ex(spl_ce_LogicException, 0, "Function spl_autoload_call() cannot be registered");
}
if (error) {
efree(error);
}
zend_string_release_ex(func_name, 0);
RETURN_FALSE;
}
/* If first arg is not null */
if (ZEND_FCI_INITIALIZED(fci)) {
alfi.ce = fcc.calling_scope;
alfi.func_ptr = fcc.function_handler;
obj_ptr = fcc.object;
if (error) {
efree(error);
}

if (Z_TYPE_P(zcallable) == IS_OBJECT) {
ZVAL_COPY(&alfi.closure, zcallable);
if (fcc.function_handler->type == ZEND_INTERNAL_FUNCTION &&
fcc.function_handler->internal_function.handler == zif_spl_autoload_call) {
zend_argument_value_error(1, "must not be the spl_autoload_call() function");
RETURN_THROWS();
}

/* fci.function_name contains the callable zval */
func_name = zend_get_callable_name(&fci.function_name);
if (Z_TYPE(fci.function_name) == IS_OBJECT) {
ZVAL_COPY(&alfi.closure, &fci.function_name);
lc_name = zend_string_alloc(ZSTR_LEN(func_name) + sizeof(uint32_t), 0);
zend_str_tolower_copy(ZSTR_VAL(lc_name), ZSTR_VAL(func_name), ZSTR_LEN(func_name));
memcpy(ZSTR_VAL(lc_name) + ZSTR_LEN(func_name), &Z_OBJ_HANDLE_P(zcallable), sizeof(uint32_t));
memcpy(ZSTR_VAL(lc_name) + ZSTR_LEN(func_name), &Z_OBJ_HANDLE(fci.function_name), sizeof(uint32_t));
ZSTR_VAL(lc_name)[ZSTR_LEN(lc_name)] = '\0';
} else {
ZVAL_UNDEF(&alfi.closure);
Expand All @@ -599,7 +559,7 @@ PHP_FUNCTION(spl_autoload_register)
lc_name = zend_string_tolower(func_name);
}
}
zend_string_release_ex(func_name, 0);
zend_string_release(func_name);

if (SPL_G(autoload_functions) && zend_hash_exists(SPL_G(autoload_functions), lc_name)) {
if (!Z_ISUNDEF(alfi.closure)) {
Expand Down
2 changes: 1 addition & 1 deletion ext/spl/php_spl.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ function spl_autoload_extensions(?string $file_extensions = null): string {}

function spl_autoload_functions(): array|false {}

function spl_autoload_register($autoload_function = null, bool $throw = true, bool $prepend = false): bool {}
function spl_autoload_register(?callable $autoload_function = null, bool $throw = true, bool $prepend = false): bool {}

function spl_autoload_unregister($autoload_function): bool {}

Expand Down
2 changes: 1 addition & 1 deletion ext/spl/php_spl_arginfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_spl_autoload_functions, 0, 0, MA
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_spl_autoload_register, 0, 0, _IS_BOOL, 0)
ZEND_ARG_INFO_WITH_DEFAULT_VALUE(0, autoload_function, "null")
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, autoload_function, IS_CALLABLE, 1, "null")
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, throw, _IS_BOOL, 0, "true")
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, prepend, _IS_BOOL, 0, "false")
ZEND_END_ARG_INFO()
Expand Down
11 changes: 4 additions & 7 deletions ext/spl/tests/spl_autoload_001.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,10 @@ var_dump(class_exists("TestClass", true));

echo "===NOFUNCTION===\n";

try
{
try {
spl_autoload_register("unavailable_autoload_function");
}
catch(Exception $e)
{
echo 'Exception: ' . $e->getMessage() . "\n";
} catch(\TypeError $e) {
echo $e->getMessage() . \PHP_EOL;
}

?>
Expand Down Expand Up @@ -103,4 +100,4 @@ TestFunc2(TestClass)
%stestclass.class.inc
bool(true)
===NOFUNCTION===
Exception: Function 'unavailable_autoload_function' not found (function 'unavailable_autoload_function' not found or invalid function name)
spl_autoload_register(): Argument #1 ($autoload_function) must be a valid callback, function 'unavailable_autoload_function' not found or invalid function name
11 changes: 4 additions & 7 deletions ext/spl/tests/spl_autoload_005.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,10 @@ class MyAutoLoader {
}
}

try
{
try {
spl_autoload_register(array('MyAutoLoader', 'autoLoad'), true);
}
catch(Exception $e)
{
echo 'Exception: ' . $e->getMessage() . "\n";
} catch(\TypeError $e) {
echo $e->getMessage() . \PHP_EOL;
}

// and
Expand All @@ -46,7 +43,7 @@ catch(Exception $e)

?>
--EXPECT--
Exception: Passed array specifies a non static method but no object (non-static method MyAutoLoader::autoLoad() cannot be called statically)
spl_autoload_register(): Argument #1 ($autoload_function) must be a valid callback, non-static method MyAutoLoader::autoLoad() cannot be called statically
MyAutoLoader::autoLoad(TestClass)
MyAutoLoader::autoThrow(TestClass)
Exception: Unavailable
25 changes: 11 additions & 14 deletions ext/spl/tests/spl_autoload_007.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -40,47 +40,44 @@ $funcs = array(
foreach($funcs as $idx => $func)
{
if ($idx) echo "\n";
try
{
try {
var_dump($func);
spl_autoload_register($func);
echo "ok\n";
}
catch (Exception $e)
{
echo $e->getMessage() . "\n";
} catch(\TypeError $e) {
echo $e->getMessage() . \PHP_EOL;
}
}

?>
--EXPECTF--
string(22) "MyAutoLoader::notExist"
Function 'MyAutoLoader::notExist' not found (class 'MyAutoLoader' does not have a method 'notExist')
spl_autoload_register(): Argument #1 ($autoload_function) must be a valid callback, class 'MyAutoLoader' does not have a method 'notExist'

string(22) "MyAutoLoader::noAccess"
Function 'MyAutoLoader::noAccess' not callable (cannot access protected method MyAutoLoader::noAccess())
spl_autoload_register(): Argument #1 ($autoload_function) must be a valid callback, cannot access protected method MyAutoLoader::noAccess()

string(22) "MyAutoLoader::autoLoad"
ok

string(22) "MyAutoLoader::dynaLoad"
Function 'MyAutoLoader::dynaLoad' not callable (non-static method MyAutoLoader::dynaLoad() cannot be called statically)
spl_autoload_register(): Argument #1 ($autoload_function) must be a valid callback, non-static method MyAutoLoader::dynaLoad() cannot be called statically

array(2) {
[0]=>
string(12) "MyAutoLoader"
[1]=>
string(8) "notExist"
}
Passed array does not specify an existing static method (class 'MyAutoLoader' does not have a method 'notExist')
spl_autoload_register(): Argument #1 ($autoload_function) must be a valid callback, class 'MyAutoLoader' does not have a method 'notExist'

array(2) {
[0]=>
string(12) "MyAutoLoader"
[1]=>
string(8) "noAccess"
}
Passed array does not specify a callable static method (cannot access protected method MyAutoLoader::noAccess())
spl_autoload_register(): Argument #1 ($autoload_function) must be a valid callback, cannot access protected method MyAutoLoader::noAccess()

array(2) {
[0]=>
Expand All @@ -96,7 +93,7 @@ array(2) {
[1]=>
string(8) "dynaLoad"
}
Passed array specifies a non static method but no object (non-static method MyAutoLoader::dynaLoad() cannot be called statically)
spl_autoload_register(): Argument #1 ($autoload_function) must be a valid callback, non-static method MyAutoLoader::dynaLoad() cannot be called statically

array(2) {
[0]=>
Expand All @@ -105,7 +102,7 @@ array(2) {
[1]=>
string(8) "notExist"
}
Passed array does not specify an existing method (class 'MyAutoLoader' does not have a method 'notExist')
spl_autoload_register(): Argument #1 ($autoload_function) must be a valid callback, class 'MyAutoLoader' does not have a method 'notExist'

array(2) {
[0]=>
Expand All @@ -114,7 +111,7 @@ array(2) {
[1]=>
string(8) "noAccess"
}
Passed array does not specify a callable static method (cannot access protected method MyAutoLoader::noAccess())
spl_autoload_register(): Argument #1 ($autoload_function) must be a valid callback, cannot access protected method MyAutoLoader::noAccess()

array(2) {
[0]=>
Expand Down
10 changes: 4 additions & 6 deletions ext/spl/tests/spl_autoload_008.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,8 @@ foreach($funcs as $idx => $func)

var_dump(class_exists("NoExistingTestClass", true));
}
}
catch (Exception $e)
{
echo get_class($e) . ": " . $e->getMessage() . "\n";
} catch(\TypeError|\Exception $e) {
echo get_class($e) . ': ' . $e->getMessage() . \PHP_EOL;
}

spl_autoload_unregister($func);
Expand All @@ -78,7 +76,7 @@ Exception: Bla
int(0)
====2====
string(22) "MyAutoLoader::dynaLoad"
LogicException: Function 'MyAutoLoader::dynaLoad' not callable (non-static method MyAutoLoader::dynaLoad() cannot be called statically)
TypeError: spl_autoload_register(): Argument #1 ($autoload_function) must be a valid callback, non-static method MyAutoLoader::dynaLoad() cannot be called statically
int(0)
====3====
array(2) {
Expand All @@ -98,7 +96,7 @@ array(2) {
[1]=>
string(8) "dynaLoad"
}
LogicException: Passed array specifies a non static method but no object (non-static method MyAutoLoader::dynaLoad() cannot be called statically)
TypeError: spl_autoload_register(): Argument #1 ($autoload_function) must be a valid callback, non-static method MyAutoLoader::dynaLoad() cannot be called statically
int(0)
====5====
array(2) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
spl_autoload_register() function - warn when using spl_autoload_call() as the autoloading function
--FILE--
<?php

try {
spl_autoload_register('spl_autoload_call');
} catch (\ValueError $e) {
echo $e->getMessage() . \PHP_EOL;
}

?>
--EXPECT--
spl_autoload_register(): Argument #1 ($autoload_function) must not be the spl_autoload_call() function
16 changes: 16 additions & 0 deletions ext/spl/tests/spl_autoload_warn_on_false_do_throw.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--TEST--
spl_autoload_register() function - warn when using false as second argument for spl_autoload_register()
--FILE--
<?php
function customAutolader($class) {
require_once __DIR__ . '/testclass.class.inc';
}
spl_autoload_register('customAutolader', false);

spl_autoload_call('TestClass');
var_dump(class_exists('TestClass', false));
?>
--EXPECTF--
Notice: spl_autoload_register(): Argument #2 ($do_throw) has been ignored, spl_autoload_register() will always throw in %s on line %d
%stestclass.class.inc
bool(true)