1
0
mirror of https://github.com/php/php-src.git synced 2026-04-03 22:22:18 +02:00

Add support for $callable() sytnax with 'Class::method'

Using the $callable() syntax when used with a string of
the form 'Class::method' would error as an undefined
function, even if the string passed is_callable() or the
callable type-hint. The fix adds support for the $callable()
syntax for any string accepted by is_callable() or the
callable type-hint.

Fix bug 68475 test with deprecated notice

Reduced scope of unit test.

Added tests with arguments.
This commit is contained in:
Aaron Piotrowski
2015-05-08 00:28:54 -05:00
committed by Julien Pauli
parent f4d6596283
commit 071111ecfc
3 changed files with 359 additions and 58 deletions

57
Zend/tests/bug68475.phpt Normal file
View File

@@ -0,0 +1,57 @@
--TEST--
Bug #68475 Calling function using $callable() syntax with strings of form 'Class::method'
--FILE--
<?php
class TestClass
{
public static function staticMethod()
{
echo "Static method called!\n";
}
public static function staticMethodWithArgs($arg1, $arg2, $arg3)
{
printf("Static method called with args: %s, %s, %s\n", $arg1, $arg2, $arg3);
}
}
// Test basic call using Class::method syntax.
$callback = 'TestClass::staticMethod';
$callback();
// Case should not matter.
$callback = 'testclass::staticmethod';
$callback();
$args = ['arg1', 'arg2', 'arg3'];
$callback = 'TestClass::staticMethodWithArgs';
// Test call with args.
$callback($args[0], $args[1], $args[2]);
// Test call with splat operator.
$callback(...$args);
// Reference undefined method.
$callback = 'TestClass::undefinedMethod';
try {
$callback();
} catch (EngineException $e) {
echo $e->getMessage() . "\n";
}
// Reference undefined class.
$callback = 'UndefinedClass::testMethod';
try {
$callback();
} catch (EngineException $e) {
echo $e->getMessage() . "\n";
}
?>
--EXPECT--
Static method called!
Static method called!
Static method called with args: arg1, arg2, arg3
Static method called with args: arg1, arg2, arg3
Call to undefined method TestClass::undefinedMethod()
Class 'UndefinedClass' not found

View File

@@ -3167,24 +3167,85 @@ ZEND_VM_HANDLER(128, ZEND_INIT_DYNAMIC_CALL, ANY, CONST|TMPVAR|CV)
ZEND_VM_C_LABEL(try_function_name):
if (OP2_TYPE != IS_CONST && EXPECTED(Z_TYPE_P(function_name) == IS_STRING)) {
if (Z_STRVAL_P(function_name)[0] == '\\') {
lcname = zend_string_alloc(Z_STRLEN_P(function_name) - 1, 0);
zend_str_tolower_copy(lcname->val, Z_STRVAL_P(function_name) + 1, Z_STRLEN_P(function_name) - 1);
} else {
lcname = zend_string_tolower(Z_STR_P(function_name));
}
if (UNEXPECTED((func = zend_hash_find(EG(function_table), lcname)) == NULL)) {
zend_error(E_EXCEPTION | E_ERROR, "Call to undefined function %s()", Z_STRVAL_P(function_name));
const char *colon;
if ((colon = zend_memrchr(Z_STRVAL_P(function_name), ':', Z_STRLEN_P(function_name))) != NULL &&
colon > Z_STRVAL_P(function_name) &&
*(colon-1) == ':'
) {
zend_string *mname;
size_t cname_length = colon - Z_STRVAL_P(function_name) - 1;
size_t mname_length = Z_STRLEN_P(function_name) - cname_length - (sizeof("::") - 1);
if (!mname_length) {
zend_error(E_EXCEPTION | E_ERROR, "Call to undefined function %s()", Z_STRVAL_P(function_name));
FREE_OP2();
HANDLE_EXCEPTION();
}
lcname = zend_string_init(Z_STRVAL_P(function_name), cname_length, 0);
called_scope = zend_fetch_class_by_name(lcname, NULL, ZEND_FETCH_CLASS_DEFAULT | ZEND_FETCH_CLASS_EXCEPTION);
if (UNEXPECTED(called_scope == NULL)) {
zend_string_release(lcname);
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}
mname = zend_string_init(Z_STRVAL_P(function_name) + (cname_length + sizeof("::") - 1), mname_length, 0);
if (called_scope->get_static_method) {
fbc = called_scope->get_static_method(called_scope, mname);
} else {
fbc = zend_std_get_static_method(called_scope, mname, NULL);
}
if (UNEXPECTED(fbc == NULL)) {
if (EXPECTED(!EG(exception))) {
zend_error(E_EXCEPTION | E_ERROR, "Call to undefined method %s::%s()", called_scope->name->val, mname->val);
}
zend_string_release(lcname);
zend_string_release(mname);
FREE_OP2();
HANDLE_EXCEPTION();
}
zend_string_release(lcname);
zend_string_release(mname);
if (!(fbc->common.fn_flags & ZEND_ACC_STATIC)) {
if (fbc->common.fn_flags & ZEND_ACC_ALLOW_STATIC) {
zend_error(E_DEPRECATED,
"Non-static method %s::%s() should not be called statically",
fbc->common.scope->name->val, fbc->common.function_name->val);
} else {
zend_error(
E_EXCEPTION | E_ERROR,
"Non-static method %s::%s() cannot be called statically",
fbc->common.scope->name->val, fbc->common.function_name->val);
FREE_OP2();
HANDLE_EXCEPTION();
}
}
} else {
if (Z_STRVAL_P(function_name)[0] == '\\') {
lcname = zend_string_alloc(Z_STRLEN_P(function_name) - 1, 0);
zend_str_tolower_copy(lcname->val, Z_STRVAL_P(function_name) + 1, Z_STRLEN_P(function_name) - 1);
} else {
lcname = zend_string_tolower(Z_STR_P(function_name));
}
if (UNEXPECTED((func = zend_hash_find(EG(function_table), lcname)) == NULL)) {
zend_error(E_EXCEPTION | E_ERROR, "Call to undefined function %s()", Z_STRVAL_P(function_name));
zend_string_release(lcname);
FREE_OP2();
HANDLE_EXCEPTION();
}
zend_string_release(lcname);
FREE_OP2();
HANDLE_EXCEPTION();
}
zend_string_release(lcname);
FREE_OP2();
fbc = Z_FUNC_P(func);
called_scope = NULL;
object = NULL;
fbc = Z_FUNC_P(func);
called_scope = NULL;
object = NULL;
}
FREE_OP2();
} else if (OP2_TYPE != IS_CONST &&
EXPECTED(Z_TYPE_P(function_name) == IS_OBJECT) &&
Z_OBJ_HANDLER_P(function_name, get_closure) &&

View File

@@ -2057,23 +2057,84 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_DYNAMIC_CALL_SPEC_CONST_H
try_function_name:
if (IS_CONST != IS_CONST && EXPECTED(Z_TYPE_P(function_name) == IS_STRING)) {
if (Z_STRVAL_P(function_name)[0] == '\\') {
lcname = zend_string_alloc(Z_STRLEN_P(function_name) - 1, 0);
zend_str_tolower_copy(lcname->val, Z_STRVAL_P(function_name) + 1, Z_STRLEN_P(function_name) - 1);
const char *colon;
if ((colon = zend_memrchr(Z_STRVAL_P(function_name), ':', Z_STRLEN_P(function_name))) != NULL &&
colon > Z_STRVAL_P(function_name) &&
*(colon-1) == ':'
) {
zend_string *mname;
size_t cname_length = colon - Z_STRVAL_P(function_name) - 1;
size_t mname_length = Z_STRLEN_P(function_name) - cname_length - (sizeof("::") - 1);
if (!mname_length) {
zend_error(E_EXCEPTION | E_ERROR, "Call to undefined function %s()", Z_STRVAL_P(function_name));
HANDLE_EXCEPTION();
}
lcname = zend_string_init(Z_STRVAL_P(function_name), cname_length, 0);
called_scope = zend_fetch_class_by_name(lcname, NULL, ZEND_FETCH_CLASS_DEFAULT | ZEND_FETCH_CLASS_EXCEPTION);
if (UNEXPECTED(called_scope == NULL)) {
zend_string_release(lcname);
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}
mname = zend_string_init(Z_STRVAL_P(function_name) + (cname_length + sizeof("::") - 1), mname_length, 0);
if (called_scope->get_static_method) {
fbc = called_scope->get_static_method(called_scope, mname);
} else {
fbc = zend_std_get_static_method(called_scope, mname, NULL);
}
if (UNEXPECTED(fbc == NULL)) {
if (EXPECTED(!EG(exception))) {
zend_error(E_EXCEPTION | E_ERROR, "Call to undefined method %s::%s()", called_scope->name->val, mname->val);
}
zend_string_release(lcname);
zend_string_release(mname);
HANDLE_EXCEPTION();
}
zend_string_release(lcname);
zend_string_release(mname);
if (!(fbc->common.fn_flags & ZEND_ACC_STATIC)) {
if (fbc->common.fn_flags & ZEND_ACC_ALLOW_STATIC) {
zend_error(E_DEPRECATED,
"Non-static method %s::%s() should not be called statically",
fbc->common.scope->name->val, fbc->common.function_name->val);
} else {
zend_error(
E_EXCEPTION | E_ERROR,
"Non-static method %s::%s() cannot be called statically",
fbc->common.scope->name->val, fbc->common.function_name->val);
HANDLE_EXCEPTION();
}
}
} else {
lcname = zend_string_tolower(Z_STR_P(function_name));
}
if (UNEXPECTED((func = zend_hash_find(EG(function_table), lcname)) == NULL)) {
zend_error(E_EXCEPTION | E_ERROR, "Call to undefined function %s()", Z_STRVAL_P(function_name));
if (Z_STRVAL_P(function_name)[0] == '\\') {
lcname = zend_string_alloc(Z_STRLEN_P(function_name) - 1, 0);
zend_str_tolower_copy(lcname->val, Z_STRVAL_P(function_name) + 1, Z_STRLEN_P(function_name) - 1);
} else {
lcname = zend_string_tolower(Z_STR_P(function_name));
}
if (UNEXPECTED((func = zend_hash_find(EG(function_table), lcname)) == NULL)) {
zend_error(E_EXCEPTION | E_ERROR, "Call to undefined function %s()", Z_STRVAL_P(function_name));
zend_string_release(lcname);
HANDLE_EXCEPTION();
}
zend_string_release(lcname);
HANDLE_EXCEPTION();
fbc = Z_FUNC_P(func);
called_scope = NULL;
object = NULL;
}
zend_string_release(lcname);
fbc = Z_FUNC_P(func);
called_scope = NULL;
object = NULL;
} else if (IS_CONST != IS_CONST &&
EXPECTED(Z_TYPE_P(function_name) == IS_OBJECT) &&
Z_OBJ_HANDLER_P(function_name, get_closure) &&
@@ -2467,23 +2528,84 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_DYNAMIC_CALL_SPEC_CV_HAND
try_function_name:
if (IS_CV != IS_CONST && EXPECTED(Z_TYPE_P(function_name) == IS_STRING)) {
if (Z_STRVAL_P(function_name)[0] == '\\') {
lcname = zend_string_alloc(Z_STRLEN_P(function_name) - 1, 0);
zend_str_tolower_copy(lcname->val, Z_STRVAL_P(function_name) + 1, Z_STRLEN_P(function_name) - 1);
const char *colon;
if ((colon = zend_memrchr(Z_STRVAL_P(function_name), ':', Z_STRLEN_P(function_name))) != NULL &&
colon > Z_STRVAL_P(function_name) &&
*(colon-1) == ':'
) {
zend_string *mname;
size_t cname_length = colon - Z_STRVAL_P(function_name) - 1;
size_t mname_length = Z_STRLEN_P(function_name) - cname_length - (sizeof("::") - 1);
if (!mname_length) {
zend_error(E_EXCEPTION | E_ERROR, "Call to undefined function %s()", Z_STRVAL_P(function_name));
HANDLE_EXCEPTION();
}
lcname = zend_string_init(Z_STRVAL_P(function_name), cname_length, 0);
called_scope = zend_fetch_class_by_name(lcname, NULL, ZEND_FETCH_CLASS_DEFAULT | ZEND_FETCH_CLASS_EXCEPTION);
if (UNEXPECTED(called_scope == NULL)) {
zend_string_release(lcname);
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}
mname = zend_string_init(Z_STRVAL_P(function_name) + (cname_length + sizeof("::") - 1), mname_length, 0);
if (called_scope->get_static_method) {
fbc = called_scope->get_static_method(called_scope, mname);
} else {
fbc = zend_std_get_static_method(called_scope, mname, NULL);
}
if (UNEXPECTED(fbc == NULL)) {
if (EXPECTED(!EG(exception))) {
zend_error(E_EXCEPTION | E_ERROR, "Call to undefined method %s::%s()", called_scope->name->val, mname->val);
}
zend_string_release(lcname);
zend_string_release(mname);
HANDLE_EXCEPTION();
}
zend_string_release(lcname);
zend_string_release(mname);
if (!(fbc->common.fn_flags & ZEND_ACC_STATIC)) {
if (fbc->common.fn_flags & ZEND_ACC_ALLOW_STATIC) {
zend_error(E_DEPRECATED,
"Non-static method %s::%s() should not be called statically",
fbc->common.scope->name->val, fbc->common.function_name->val);
} else {
zend_error(
E_EXCEPTION | E_ERROR,
"Non-static method %s::%s() cannot be called statically",
fbc->common.scope->name->val, fbc->common.function_name->val);
HANDLE_EXCEPTION();
}
}
} else {
lcname = zend_string_tolower(Z_STR_P(function_name));
}
if (UNEXPECTED((func = zend_hash_find(EG(function_table), lcname)) == NULL)) {
zend_error(E_EXCEPTION | E_ERROR, "Call to undefined function %s()", Z_STRVAL_P(function_name));
if (Z_STRVAL_P(function_name)[0] == '\\') {
lcname = zend_string_alloc(Z_STRLEN_P(function_name) - 1, 0);
zend_str_tolower_copy(lcname->val, Z_STRVAL_P(function_name) + 1, Z_STRLEN_P(function_name) - 1);
} else {
lcname = zend_string_tolower(Z_STR_P(function_name));
}
if (UNEXPECTED((func = zend_hash_find(EG(function_table), lcname)) == NULL)) {
zend_error(E_EXCEPTION | E_ERROR, "Call to undefined function %s()", Z_STRVAL_P(function_name));
zend_string_release(lcname);
HANDLE_EXCEPTION();
}
zend_string_release(lcname);
HANDLE_EXCEPTION();
fbc = Z_FUNC_P(func);
called_scope = NULL;
object = NULL;
}
zend_string_release(lcname);
fbc = Z_FUNC_P(func);
called_scope = NULL;
object = NULL;
} else if (IS_CV != IS_CONST &&
EXPECTED(Z_TYPE_P(function_name) == IS_OBJECT) &&
Z_OBJ_HANDLER_P(function_name, get_closure) &&
@@ -2658,24 +2780,85 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_DYNAMIC_CALL_SPEC_TMPVAR_
try_function_name:
if ((IS_TMP_VAR|IS_VAR) != IS_CONST && EXPECTED(Z_TYPE_P(function_name) == IS_STRING)) {
if (Z_STRVAL_P(function_name)[0] == '\\') {
lcname = zend_string_alloc(Z_STRLEN_P(function_name) - 1, 0);
zend_str_tolower_copy(lcname->val, Z_STRVAL_P(function_name) + 1, Z_STRLEN_P(function_name) - 1);
} else {
lcname = zend_string_tolower(Z_STR_P(function_name));
}
if (UNEXPECTED((func = zend_hash_find(EG(function_table), lcname)) == NULL)) {
zend_error(E_EXCEPTION | E_ERROR, "Call to undefined function %s()", Z_STRVAL_P(function_name));
const char *colon;
if ((colon = zend_memrchr(Z_STRVAL_P(function_name), ':', Z_STRLEN_P(function_name))) != NULL &&
colon > Z_STRVAL_P(function_name) &&
*(colon-1) == ':'
) {
zend_string *mname;
size_t cname_length = colon - Z_STRVAL_P(function_name) - 1;
size_t mname_length = Z_STRLEN_P(function_name) - cname_length - (sizeof("::") - 1);
if (!mname_length) {
zend_error(E_EXCEPTION | E_ERROR, "Call to undefined function %s()", Z_STRVAL_P(function_name));
zval_ptr_dtor_nogc(free_op2);
HANDLE_EXCEPTION();
}
lcname = zend_string_init(Z_STRVAL_P(function_name), cname_length, 0);
called_scope = zend_fetch_class_by_name(lcname, NULL, ZEND_FETCH_CLASS_DEFAULT | ZEND_FETCH_CLASS_EXCEPTION);
if (UNEXPECTED(called_scope == NULL)) {
zend_string_release(lcname);
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}
mname = zend_string_init(Z_STRVAL_P(function_name) + (cname_length + sizeof("::") - 1), mname_length, 0);
if (called_scope->get_static_method) {
fbc = called_scope->get_static_method(called_scope, mname);
} else {
fbc = zend_std_get_static_method(called_scope, mname, NULL);
}
if (UNEXPECTED(fbc == NULL)) {
if (EXPECTED(!EG(exception))) {
zend_error(E_EXCEPTION | E_ERROR, "Call to undefined method %s::%s()", called_scope->name->val, mname->val);
}
zend_string_release(lcname);
zend_string_release(mname);
zval_ptr_dtor_nogc(free_op2);
HANDLE_EXCEPTION();
}
zend_string_release(lcname);
zend_string_release(mname);
if (!(fbc->common.fn_flags & ZEND_ACC_STATIC)) {
if (fbc->common.fn_flags & ZEND_ACC_ALLOW_STATIC) {
zend_error(E_DEPRECATED,
"Non-static method %s::%s() should not be called statically",
fbc->common.scope->name->val, fbc->common.function_name->val);
} else {
zend_error(
E_EXCEPTION | E_ERROR,
"Non-static method %s::%s() cannot be called statically",
fbc->common.scope->name->val, fbc->common.function_name->val);
zval_ptr_dtor_nogc(free_op2);
HANDLE_EXCEPTION();
}
}
} else {
if (Z_STRVAL_P(function_name)[0] == '\\') {
lcname = zend_string_alloc(Z_STRLEN_P(function_name) - 1, 0);
zend_str_tolower_copy(lcname->val, Z_STRVAL_P(function_name) + 1, Z_STRLEN_P(function_name) - 1);
} else {
lcname = zend_string_tolower(Z_STR_P(function_name));
}
if (UNEXPECTED((func = zend_hash_find(EG(function_table), lcname)) == NULL)) {
zend_error(E_EXCEPTION | E_ERROR, "Call to undefined function %s()", Z_STRVAL_P(function_name));
zend_string_release(lcname);
zval_ptr_dtor_nogc(free_op2);
HANDLE_EXCEPTION();
}
zend_string_release(lcname);
zval_ptr_dtor_nogc(free_op2);
HANDLE_EXCEPTION();
}
zend_string_release(lcname);
zval_ptr_dtor_nogc(free_op2);
fbc = Z_FUNC_P(func);
called_scope = NULL;
object = NULL;
fbc = Z_FUNC_P(func);
called_scope = NULL;
object = NULL;
}
zval_ptr_dtor_nogc(free_op2);
} else if ((IS_TMP_VAR|IS_VAR) != IS_CONST &&
EXPECTED(Z_TYPE_P(function_name) == IS_OBJECT) &&
Z_OBJ_HANDLER_P(function_name, get_closure) &&