summaryrefslogtreecommitdiff
path: root/ext
diff options
context:
space:
mode:
authorNikita Popov <nikita.ppv@gmail.com>2021-03-18 15:40:48 +0100
committerNikita Popov <nikita.ppv@gmail.com>2021-03-19 10:49:15 +0100
commit2d0e2733c8a0010df9f45693d536531a7a725bdf (patch)
tree715b953768f04d3240c57b14bd8d9c5218af4ada /ext
parent6689bedd1796380f882fdecc6dcf8da1ff885c2b (diff)
downloadphp-git-2d0e2733c8a0010df9f45693d536531a7a725bdf.tar.gz
Support prototypes in call graph
Even if we don't know the exact method being called, include it in the call graph with the is_prototype flag. In particular, we can still make use of return types from prototype methods, as PHP 8 makes LSP violations a hard error. Most other places are adjusted to skip calls with !is_prototype. Maybe some of them would be fine, but ignoring them is conservative.
Diffstat (limited to 'ext')
-rw-r--r--ext/opcache/jit/zend_jit_trace.c3
-rw-r--r--ext/opcache/jit/zend_jit_x86.dasc8
-rw-r--r--ext/opcache/tests/opt/verify_return_type.phpt49
3 files changed, 56 insertions, 4 deletions
diff --git a/ext/opcache/jit/zend_jit_trace.c b/ext/opcache/jit/zend_jit_trace.c
index bc7038731a..80ce45605e 100644
--- a/ext/opcache/jit/zend_jit_trace.c
+++ b/ext/opcache/jit/zend_jit_trace.c
@@ -6207,7 +6207,8 @@ done:
zend_call_info *call_info = jit_extension->func_info.callee_info;
while (call_info) {
- if (call_info->caller_init_opline == init_opline) {
+ if (call_info->caller_init_opline == init_opline
+ && !call_info->is_prototype) {
if (op_array->fn_flags & ZEND_ACC_TRAIT_CLONE) {
if (init_opline->opcode == ZEND_INIT_STATIC_METHOD_CALL
&& init_opline->op1_type != IS_CONST) {
diff --git a/ext/opcache/jit/zend_jit_x86.dasc b/ext/opcache/jit/zend_jit_x86.dasc
index ccf3a173de..6aac9306e4 100644
--- a/ext/opcache/jit/zend_jit_x86.dasc
+++ b/ext/opcache/jit/zend_jit_x86.dasc
@@ -9186,7 +9186,7 @@ static int zend_jit_init_fcall(dasm_State **Dst, const zend_op *opline, uint32_t
while (call_info && call_info->caller_init_opline != opline) {
call_info = call_info->next_callee;
}
- if (call_info && call_info->callee_func) {
+ if (call_info && call_info->callee_func && !call_info->is_prototype) {
func = call_info->callee_func;
}
}
@@ -9353,7 +9353,7 @@ static int zend_jit_init_method_call(dasm_State **Dst,
while (call_info && call_info->caller_init_opline != opline) {
call_info = call_info->next_callee;
}
- if (call_info && call_info->callee_func) {
+ if (call_info && call_info->callee_func && !call_info->is_prototype) {
func = call_info->callee_func;
}
}
@@ -9678,6 +9678,8 @@ static uint32_t skip_valid_arguments(const zend_op_array *op_array, zend_ssa *ss
uint32_t num_args = 0;
zend_function *func = call_info->callee_func;
+ /* It's okay to handle prototypes here, because they can only increase the accepted arguments.
+ * Anything legal for the parent method is also legal for the parent method. */
while (num_args < call_info->num_args) {
zend_arg_info *arg_info = func->op_array.arg_info + num_args;
@@ -9731,7 +9733,7 @@ static int zend_jit_do_fcall(dasm_State **Dst, const zend_op *opline, const zend
while (call_info && call_info->caller_call_opline != opline) {
call_info = call_info->next_callee;
}
- if (call_info && call_info->callee_func) {
+ if (call_info && call_info->callee_func && !call_info->is_prototype) {
func = call_info->callee_func;
}
}
diff --git a/ext/opcache/tests/opt/verify_return_type.phpt b/ext/opcache/tests/opt/verify_return_type.phpt
index c1384c7af8..3838cd8b0c 100644
--- a/ext/opcache/tests/opt/verify_return_type.phpt
+++ b/ext/opcache/tests/opt/verify_return_type.phpt
@@ -20,6 +20,23 @@ class Test1 {
}
}
+class Test2 {
+ public function getInt(): int {
+ return 42;
+ }
+ public function getInt2(): int {
+ return $this->getInt();
+ }
+ public function getIntOrFloat(int $i): int|float {
+ return $i;
+ }
+ public function getInt3(int $i): int {
+ // Should not elide return type check. Test2::getIntOrFloat() returns only int,
+ // but a child method may return int|float.
+ return $this->getIntOrFloat($i);
+ }
+}
+
?>
--EXPECTF--
$_main:
@@ -42,3 +59,35 @@ Test1::getInt:
0000 INIT_METHOD_CALL 0 THIS string("getIntOrFloat")
0001 V0 = DO_UCALL
0002 RETURN V0
+
+Test2::getInt:
+ ; (lines=1, args=0, vars=0, tmps=0)
+ ; (after optimizer)
+ ; %s
+0000 RETURN int(42)
+
+Test2::getInt2:
+ ; (lines=3, args=0, vars=0, tmps=1)
+ ; (after optimizer)
+ ; %s
+0000 INIT_METHOD_CALL 0 THIS string("getInt")
+0001 V0 = DO_FCALL
+0002 RETURN V0
+
+Test2::getIntOrFloat:
+ ; (lines=2, args=1, vars=1, tmps=0)
+ ; (after optimizer)
+ ; %s
+0000 CV0($i) = RECV 1
+0001 RETURN CV0($i)
+
+Test2::getInt3:
+ ; (lines=6, args=1, vars=1, tmps=1)
+ ; (after optimizer)
+ ; %s
+0000 CV0($i) = RECV 1
+0001 INIT_METHOD_CALL 1 THIS string("getIntOrFloat")
+0002 SEND_VAR CV0($i) 1
+0003 V1 = DO_FCALL
+0004 VERIFY_RETURN_TYPE V1
+0005 RETURN V1