From 4f54c15cb10dfd668538ae225fef59c82c66e96e Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 8 Apr 2016 01:41:48 +0200 Subject: [PATCH] Ct bind private/final $this method call args The test covers two edge-cases wrt opcache support. --- Zend/tests/method_argument_binding.phpt | 47 +++++++++++++++++++++ Zend/zend_compile.c | 16 ++++++- ext/opcache/Optimizer/optimize_func_calls.c | 23 +++++++++- 3 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 Zend/tests/method_argument_binding.phpt diff --git a/Zend/tests/method_argument_binding.phpt b/Zend/tests/method_argument_binding.phpt new file mode 100644 index 0000000000..731cc4fb32 --- /dev/null +++ b/Zend/tests/method_argument_binding.phpt @@ -0,0 +1,47 @@ +--TEST-- +Edge cases in compile-time method argument binding +--FILE-- +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 diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index c99cb686bf..b1f5768db9 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -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); } /* }}} */ diff --git a/ext/opcache/Optimizer/optimize_func_calls.c b/ext/opcache/Optimizer/optimize_func_calls.c index 55612d7fa0..c3c519ab4c 100644 --- a/ext/opcache/Optimizer/optimize_func_calls.c +++ b/ext/opcache/Optimizer/optimize_func_calls.c @@ -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); -- 2.40.0