]> granicus.if.org Git - php/commitdiff
Ct bind private/final $this method call args
authorNikita Popov <nikic@php.net>
Thu, 7 Apr 2016 23:41:48 +0000 (01:41 +0200)
committerNikita Popov <nikic@php.net>
Wed, 27 Apr 2016 15:10:44 +0000 (17:10 +0200)
The test covers two edge-cases wrt opcache support.

Zend/tests/method_argument_binding.phpt [new file with mode: 0644]
Zend/zend_compile.c
ext/opcache/Optimizer/optimize_func_calls.c

diff --git a/Zend/tests/method_argument_binding.phpt b/Zend/tests/method_argument_binding.phpt
new file mode 100644 (file)
index 0000000..731cc4f
--- /dev/null
@@ -0,0 +1,47 @@
+--TEST--
+Edge cases in compile-time method argument binding
+--FILE--
+<?php
+
+class A {
+    private function method($x) {}
+}
+
+class B extends A {
+    public function test() {
+        $x = 1;
+        $this->method($x);
+        var_dump($x);
+    }
+}
+
+class C extends B {
+    public function method(&$x) {
+        ++$x;
+    }
+}
+
+(new C)->test();
+
+class D {
+    private final function method(&$x) {
+        ++$x;
+    }
+}
+
+class E extends D {
+    public function __call($name, $args) { }
+
+    public function test() {
+        $this->method($x);
+    }
+}
+
+(new E)->test();
+
+?>
+--EXPECTF--
+Warning: Declaration of C::method(&$x) should be compatible with A::method($x) in %s on line %d
+int(2)
+
+Notice: Undefined variable: x in %s on line %d
index c99cb686bfad1e5b2c1ffa12782a61ce9fef08e6..b1f5768db926e3226a3f63dcbef06e77b2b3733e 100644 (file)
@@ -3674,6 +3674,7 @@ void zend_compile_method_call(znode *result, zend_ast *ast, uint32_t type) /* {{
 
        znode obj_node, method_node;
        zend_op *opline;
+       zend_function *fbc = NULL;
 
        if (is_this_fetch(obj_ast)) {
                obj_node.op_type = IS_UNUSED;
@@ -3697,7 +3698,20 @@ void zend_compile_method_call(znode *result, zend_ast *ast, uint32_t type) /* {{
                SET_NODE(opline->op2, &method_node);
        }
 
-       zend_compile_call_common(result, args_ast, NULL);
+       /* Check if this calls a known method on $this */
+       if (opline->op1_type == IS_UNUSED && opline->op2_type == IS_CONST &&
+                       CG(active_class_entry) && zend_is_scope_known()) {
+               zend_string *lcname = Z_STR_P(CT_CONSTANT(opline->op2) + 1);
+               fbc = zend_hash_find_ptr(&CG(active_class_entry)->function_table, lcname);
+
+               /* We only know the exact method that is being called if it is either private or final.
+                * Otherwise an overriding method in a child class may be called. */
+               if (fbc && !(fbc->common.fn_flags & (ZEND_ACC_PRIVATE|ZEND_ACC_FINAL))) {
+                       fbc = NULL;
+               }
+       }
+
+       zend_compile_call_common(result, args_ast, fbc);
 }
 /* }}} */
 
index 55612d7fa005486080df6ca7ab1e2d42fcae0fc1..c3c519ab4c53dc255c985467a35e30b56c58597f 100644 (file)
@@ -86,10 +86,28 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx)
                                                call_stack[call].func = zend_hash_find_ptr(&ce->function_table, func_name);
                                        }
                                }
+                               call_stack[call].opline = opline;
+                               call++;
+                               break;
+                       case ZEND_INIT_METHOD_CALL:
+                               if (opline->op1_type == IS_UNUSED && ZEND_OP2_IS_CONST_STRING(opline)
+                                               && op_array->scope && !(op_array->scope->ce_flags & ZEND_ACC_TRAIT)) {
+                                       zend_string *method_name = Z_STR_P(&ZEND_OP2_LITERAL(opline) + 1);
+                                       zend_function *fbc = zend_hash_find_ptr(
+                                               &op_array->scope->function_table, method_name);
+                                       if (fbc) {
+                                               zend_bool is_private = (fbc->common.fn_flags & ZEND_ACC_PRIVATE) != 0;
+                                               zend_bool is_final = (fbc->common.fn_flags & ZEND_ACC_FINAL) != 0;
+                                               zend_bool same_scope = fbc->common.scope == op_array->scope;
+                                               if ((is_private && same_scope)
+                                                               || (is_final && (!is_private || same_scope))) {
+                                                       call_stack[call].func = fbc;
+                                               }
+                                       }
+                               }
                                /* break missing intentionally */
                        case ZEND_NEW:
                        case ZEND_INIT_DYNAMIC_CALL:
-                       case ZEND_INIT_METHOD_CALL:
                        case ZEND_INIT_FCALL:
                        case ZEND_INIT_USER_CALL:
                                call_stack[call].opline = opline;
@@ -118,7 +136,8 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx)
                                                literal_dtor(&op_array->literals[fcall->op2.constant + 2]);
                                                fcall->op2.constant = fcall->op2.constant + 1;
                                                opline->opcode = zend_get_call_op(fcall, call_stack[call].func);
-                                       } else if (fcall->opcode == ZEND_INIT_STATIC_METHOD_CALL) {
+                                       } else if (fcall->opcode == ZEND_INIT_STATIC_METHOD_CALL
+                                                       || fcall->opcode == ZEND_INIT_METHOD_CALL) {
                                                /* We don't have specialized opcodes for this, do nothing */
                                        } else {
                                                ZEND_ASSERT(0);