--- /dev/null
+--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
znode obj_node, method_node;
zend_op *opline;
+ zend_function *fbc = NULL;
if (is_this_fetch(obj_ast)) {
obj_node.op_type = IS_UNUSED;
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);
}
/* }}} */
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;
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);