]> granicus.if.org Git - php/commitdiff
Add support for $callable() sytnax with 'Class::method'
authorAaron Piotrowski <aaron@trowski.com>
Fri, 8 May 2015 05:28:54 +0000 (00:28 -0500)
committerJulien Pauli <jpauli@php.net>
Tue, 12 May 2015 11:33:28 +0000 (13:33 +0200)
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.

Zend/tests/bug68475.phpt [new file with mode: 0644]
Zend/zend_vm_def.h
Zend/zend_vm_execute.h

diff --git a/Zend/tests/bug68475.phpt b/Zend/tests/bug68475.phpt
new file mode 100644 (file)
index 0000000..351edb2
--- /dev/null
@@ -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
index 7d324fe08f733cc167060833354591d1426ae0ca..1c8afad0584d53fd5adba114a826f8e5fc497ab8 100644 (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);
+               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 {
-                       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);
+                               FREE_OP2();
+                               HANDLE_EXCEPTION();
+                       }
                        zend_string_release(lcname);
-                       FREE_OP2();
-                       HANDLE_EXCEPTION();
+
+                       fbc = Z_FUNC_P(func);
+                       called_scope = NULL;
+                       object = NULL;
                }
-               zend_string_release(lcname);
                FREE_OP2();
-
-               fbc = Z_FUNC_P(func);
-               called_scope = NULL;
-               object = NULL;
        } else if (OP2_TYPE != IS_CONST &&
            EXPECTED(Z_TYPE_P(function_name) == IS_OBJECT) &&
                Z_OBJ_HANDLER_P(function_name, get_closure) &&
index 7abf0f9854ba82521359957e8264d80c6f4ba140..a0e18253766d18b97758b009ab6d20d419d5f20a 100644 (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);
+               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 {
-                       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);
+                               zval_ptr_dtor_nogc(free_op2);
+                               HANDLE_EXCEPTION();
+                       }
                        zend_string_release(lcname);
-                       zval_ptr_dtor_nogc(free_op2);
-                       HANDLE_EXCEPTION();
+
+                       fbc = Z_FUNC_P(func);
+                       called_scope = NULL;
+                       object = NULL;
                }
-               zend_string_release(lcname);
                zval_ptr_dtor_nogc(free_op2);
-
-               fbc = Z_FUNC_P(func);
-               called_scope = NULL;
-               object = NULL;
        } 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) &&