diff options
author | Nikita Popov <nikita.ppv@gmail.com> | 2021-03-18 15:40:48 +0100 |
---|---|---|
committer | Nikita Popov <nikita.ppv@gmail.com> | 2021-03-19 10:49:15 +0100 |
commit | 2d0e2733c8a0010df9f45693d536531a7a725bdf (patch) | |
tree | 715b953768f04d3240c57b14bd8d9c5218af4ada /ext | |
parent | 6689bedd1796380f882fdecc6dcf8da1ff885c2b (diff) | |
download | php-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.c | 3 | ||||
-rw-r--r-- | ext/opcache/jit/zend_jit_x86.dasc | 8 | ||||
-rw-r--r-- | ext/opcache/tests/opt/verify_return_type.phpt | 49 |
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 |