Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions UPGRADING.INTERNALS
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ PHP 8.7 INTERNALS UPGRADE NOTES
- The INIT_CLASS_ENTRY_INIT_METHODS() macro, which was previously used as
part of handling disabled classes, was removed following the removal of
the `disable_classes` INI setting in PHP 8.5.
- zend_fetch_arg_info_type() now takes an additional const zend_op_array*
parameter after the script. It is used to resolve class names against the
op_array's scope and filename. NULL may be passed if no op_array is
available.

========================
2. Build system changes
Expand Down
47 changes: 25 additions & 22 deletions Zend/Optimizer/zend_inference.c
Original file line number Diff line number Diff line change
Expand Up @@ -2375,8 +2375,9 @@ static uint32_t zend_convert_type_declaration_mask(uint32_t type_mask) {
return result_mask;
}

static uint32_t zend_convert_type(const zend_script *script, zend_type type, zend_class_entry **pce)
{
static uint32_t zend_convert_type(
const zend_script *script, const zend_op_array *op_array,
zend_type type, zend_class_entry **pce) {
if (pce) {
*pce = NULL;
}
Expand All @@ -2393,8 +2394,7 @@ static uint32_t zend_convert_type(const zend_script *script, zend_type type, zen
* we use a plain object type for class unions. */
if (ZEND_TYPE_HAS_NAME(type)) {
zend_string *lcname = zend_string_tolower(ZEND_TYPE_NAME(type));
// TODO: Pass through op_array.
*pce = zend_optimizer_get_class_entry(script, NULL, lcname);
*pce = zend_optimizer_get_class_entry(script, op_array, lcname);
zend_string_release_ex(lcname, 0);
}
}
Expand All @@ -2405,9 +2405,10 @@ static uint32_t zend_convert_type(const zend_script *script, zend_type type, zen
return tmp;
}

ZEND_API uint32_t zend_fetch_arg_info_type(const zend_script *script, const zend_arg_info *arg_info, zend_class_entry **pce)
{
return zend_convert_type(script, arg_info->type, pce);
ZEND_API uint32_t zend_fetch_arg_info_type(
const zend_script *script, const zend_op_array *op_array,
const zend_arg_info *arg_info, zend_class_entry **pce) {
return zend_convert_type(script, op_array, arg_info->type, pce);
}

static const zend_property_info *lookup_prop_info(const zend_class_entry *ce, zend_string *name, zend_class_entry *scope) {
Expand Down Expand Up @@ -2496,16 +2497,17 @@ static const zend_property_info *zend_fetch_static_prop_info(const zend_script *
return prop_info;
}

static uint32_t zend_fetch_prop_type(const zend_script *script, const zend_property_info *prop_info, zend_class_entry **pce)
{
static uint32_t zend_fetch_prop_type(
const zend_script *script, const zend_op_array *op_array,
const zend_property_info *prop_info, zend_class_entry **pce) {
if (!prop_info) {
if (pce) {
*pce = NULL;
}
return MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_RC1 | MAY_BE_RCN;
}

return zend_convert_type(script, prop_info->type, pce);
return zend_convert_type(script, op_array, prop_info->type, pce);
}

static bool result_may_be_separated(const zend_ssa *ssa, const zend_ssa_op *ssa_op)
Expand Down Expand Up @@ -2755,7 +2757,7 @@ static zend_always_inline zend_result _zend_update_type_info(
if (opline->opcode == ZEND_ASSIGN_OBJ_OP) {
prop_info = zend_fetch_prop_info(op_array, ssa, opline, ssa_op);
orig = t1;
t1 = zend_fetch_prop_type(script, prop_info, NULL);
t1 = zend_fetch_prop_type(script, op_array, prop_info, NULL);
t2 = OP1_DATA_INFO();
} else if (opline->opcode == ZEND_ASSIGN_DIM_OP) {
if (t1 & MAY_BE_ARRAY_OF_REF) {
Expand All @@ -2766,7 +2768,7 @@ static zend_always_inline zend_result _zend_update_type_info(
t2 = OP1_DATA_INFO();
} else if (opline->opcode == ZEND_ASSIGN_STATIC_PROP_OP) {
prop_info = zend_fetch_static_prop_info(script, op_array, ssa, opline);
t1 = zend_fetch_prop_type(script, prop_info, NULL);
t1 = zend_fetch_prop_type(script, op_array, prop_info, NULL);
t2 = OP1_DATA_INFO();
} else {
if (t1 & MAY_BE_REF) {
Expand Down Expand Up @@ -2828,7 +2830,7 @@ static zend_always_inline zend_result _zend_update_type_info(
} else if (opline->opcode == ZEND_ASSIGN_OBJ_OP) {
/* The return value must also satisfy the property type */
if (prop_info) {
t1 = zend_fetch_prop_type(script, prop_info, &ce);
t1 = zend_fetch_prop_type(script, op_array, prop_info, &ce);
if ((t1 & (MAY_BE_LONG|MAY_BE_DOUBLE)) == MAY_BE_LONG
&& (tmp & (MAY_BE_LONG|MAY_BE_DOUBLE)) == MAY_BE_DOUBLE) {
/* DOUBLE may be auto-converted to LONG */
Expand All @@ -2847,7 +2849,7 @@ static zend_always_inline zend_result _zend_update_type_info(
} else if (opline->opcode == ZEND_ASSIGN_STATIC_PROP_OP) {
/* The return value must also satisfy the property type */
if (prop_info) {
t1 = zend_fetch_prop_type(script, prop_info, &ce);
t1 = zend_fetch_prop_type(script, op_array, prop_info, &ce);
if ((t1 & (MAY_BE_LONG|MAY_BE_DOUBLE)) == MAY_BE_LONG
&& (tmp & (MAY_BE_LONG|MAY_BE_DOUBLE)) == MAY_BE_DOUBLE) {
/* DOUBLE may be auto-converted to LONG */
Expand Down Expand Up @@ -3043,7 +3045,7 @@ static zend_always_inline zend_result _zend_update_type_info(
}
if (ssa_op->result_def >= 0) {
// TODO: If there is no __set we might do better
tmp = zend_fetch_prop_type(script,
tmp = zend_fetch_prop_type(script, op_array,
zend_fetch_prop_info(op_array, ssa, opline, ssa_op), &ce);
UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
if (ce) {
Expand Down Expand Up @@ -3314,7 +3316,7 @@ static zend_always_inline zend_result _zend_update_type_info(
zend_arg_info *arg_info = &op_array->arg_info[opline->op1.num-1];

ce = NULL;
tmp = zend_fetch_arg_info_type(script, arg_info, &ce);
tmp = zend_fetch_arg_info_type(script, op_array, arg_info, &ce);
if (ZEND_ARG_SEND_MODE(arg_info)) {
tmp |= MAY_BE_REF;
ce = NULL;
Expand Down Expand Up @@ -3791,7 +3793,7 @@ static zend_always_inline zend_result _zend_update_type_info(
}
if (opline->op1_type == IS_UNUSED || (t1 & MAY_BE_OBJECT)) {
const zend_property_info *prop_info = zend_fetch_prop_info(op_array, ssa, opline, ssa_op);
tmp |= zend_fetch_prop_type(script, prop_info, &ce);
tmp |= zend_fetch_prop_type(script, op_array, prop_info, &ce);
if (opline->opcode != ZEND_FETCH_OBJ_R && opline->opcode != ZEND_FETCH_OBJ_IS) {
tmp |= MAY_BE_REF | MAY_BE_INDIRECT;
if ((opline->extended_value & ZEND_FETCH_OBJ_FLAGS) == ZEND_FETCH_DIM_WRITE) {
Expand Down Expand Up @@ -3832,7 +3834,7 @@ static zend_always_inline zend_result _zend_update_type_info(
case ZEND_FETCH_STATIC_PROP_W:
case ZEND_FETCH_STATIC_PROP_UNSET:
case ZEND_FETCH_STATIC_PROP_FUNC_ARG:
tmp = zend_fetch_prop_type(script,
tmp = zend_fetch_prop_type(script, op_array,
zend_fetch_static_prop_info(script, op_array, ssa, opline), &ce);
if (opline->opcode != ZEND_FETCH_STATIC_PROP_R
&& opline->opcode != ZEND_FETCH_STATIC_PROP_IS) {
Expand Down Expand Up @@ -3926,7 +3928,7 @@ static zend_always_inline zend_result _zend_update_type_info(
UPDATE_SSA_TYPE(MAY_BE_RC1|MAY_BE_RCN|MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY, ssa_op->result_def);
break;
}
UPDATE_SSA_TYPE(zend_convert_type(script, cc->type, &ce), ssa_op->result_def);
UPDATE_SSA_TYPE(zend_convert_type(script, op_array, cc->type, &ce), ssa_op->result_def);
if (ce) {
UPDATE_SSA_OBJ_TYPE(ce, /* is_instanceof */ true, ssa_op->result_def);
}
Expand Down Expand Up @@ -3974,7 +3976,7 @@ static zend_always_inline zend_result _zend_update_type_info(
ce = NULL;
} else {
zend_arg_info *ret_info = op_array->arg_info - 1;
tmp = zend_fetch_arg_info_type(script, ret_info, &ce);
tmp = zend_fetch_arg_info_type(script, op_array, ret_info, &ce);
if ((tmp & MAY_BE_NULL) && opline->op1_type == IS_CV) {
tmp |= MAY_BE_UNDEF;
}
Expand Down Expand Up @@ -4030,7 +4032,7 @@ static zend_always_inline zend_result _zend_update_type_info(
if (ssa_op->result_def >= 0) {
const zend_property_info *prop_info = zend_fetch_static_prop_info(script, op_array, ssa, opline);
zend_class_entry *prop_ce;
tmp = zend_fetch_prop_type(script, prop_info, &prop_ce);
tmp = zend_fetch_prop_type(script, op_array, prop_info, &prop_ce);
/* Internal objects may result in essentially anything. */
if (tmp & MAY_BE_OBJECT) {
goto unknown_opcode;
Expand Down Expand Up @@ -4529,7 +4531,8 @@ uint32_t zend_get_return_info_from_signature_only(
(use_tentative_return_info || !ZEND_ARG_TYPE_IS_TENTATIVE(func->common.arg_info - 1))
) {
const zend_arg_info *ret_info = func->common.arg_info - 1;
type = zend_fetch_arg_info_type(script, ret_info, ce);
const zend_op_array *op_array = ZEND_USER_CODE(func->type) ? &func->op_array : NULL;
type = zend_fetch_arg_info_type(script, op_array, ret_info, ce);
*ce_is_instanceof = ce != NULL;
} else {
type = MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF
Expand Down
3 changes: 2 additions & 1 deletion Zend/Optimizer/zend_inference.h
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,8 @@ ZEND_API uint32_t zend_array_element_type(uint32_t t1, uint8_t op_type, bool wri
ZEND_API bool zend_inference_propagate_range(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline, const zend_ssa_op* ssa_op, int var, zend_ssa_range *tmp);

ZEND_API uint32_t zend_fetch_arg_info_type(
const zend_script *script, const zend_arg_info *arg_info, zend_class_entry **pce);
const zend_script *script, const zend_op_array *op_array,
const zend_arg_info *arg_info, zend_class_entry **pce);
ZEND_API void zend_init_func_return_info(
const zend_op_array *op_array, const zend_script *script, zend_ssa_var_info *ret);
uint32_t zend_get_return_info_from_signature_only(
Expand Down
10 changes: 4 additions & 6 deletions ext/opcache/jit/zend_jit_trace.c
Original file line number Diff line number Diff line change
Expand Up @@ -1720,7 +1720,7 @@ static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uin
if (op_array->arg_info && i < trace_buffer[1].opline - op_array->opcodes) {
zend_arg_info *arg_info = &op_array->arg_info[i];
zend_class_entry *ce;
uint32_t tmp = zend_fetch_arg_info_type(script, arg_info, &ce);
uint32_t tmp = zend_fetch_arg_info_type(script, op_array, arg_info, &ce);

if (ZEND_ARG_SEND_MODE(arg_info)) {
tmp |= MAY_BE_REF;
Expand Down Expand Up @@ -2181,8 +2181,7 @@ static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uin
ZEND_ASSERT(frame->call->func->op_array.arg_info);
arg_info = &frame->call->func->op_array.arg_info[opline->op2.num - 1];
if (ZEND_TYPE_IS_SET(arg_info->type)) {
zend_class_entry *ce;
uint32_t tmp = zend_fetch_arg_info_type(script, arg_info, &ce);
uint32_t tmp = zend_fetch_arg_info_type(script, NULL, arg_info, NULL);
info &= tmp;
if (!info) {
break;
Expand Down Expand Up @@ -2521,7 +2520,7 @@ static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uin
if (op_array->arg_info) {
zend_arg_info *arg_info = &op_array->arg_info[i];
zend_class_entry *ce;
uint32_t tmp = zend_fetch_arg_info_type(script, arg_info, &ce);
uint32_t tmp = zend_fetch_arg_info_type(script, op_array, arg_info, &ce);

if (ZEND_ARG_SEND_MODE(arg_info)) {
tmp |= MAY_BE_REF;
Expand Down Expand Up @@ -2685,10 +2684,9 @@ static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uin
opline->opcode == ZEND_DO_FCALL_BY_NAME);

if (opline->result_type != IS_UNDEF) {
zend_class_entry *ce;
const zend_function *func = p->func;
zend_arg_info *ret_info = func->common.arg_info - 1;
uint32_t ret_type = zend_fetch_arg_info_type(NULL, ret_info, &ce);
uint32_t ret_type = zend_fetch_arg_info_type(NULL, NULL, ret_info, NULL);

ssa_var_info[ssa_ops[idx-1].result_def].type &= ret_type;
}
Expand Down
68 changes: 68 additions & 0 deletions ext/opcache/tests/opt/type_inference_unlinked_scope.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
--TEST--
Type inference resolves class hints naming the unlinked declaring class
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.optimization_level=-1
opcache.opt_debug_level=0x400000
opcache.preload=
zend_test.observer.enabled=0
--EXTENSIONS--
opcache
--FILE--
<?php

if (true) {
final class A {
public int $x = 1;
public function f(A $a) { return $a->x; }
}
}

class B extends C {
public int $y = 1;
public function f(B $b) { return $b->y; }
}

class C {}

?>
--EXPECTF--

$_main:
; (lines=3, args=0, vars=0, tmps=0, ssa_vars=0, no_loops)
; (after dfa pass)
; %s:1-18
; return [long] RANGE[1..1]
BB0:
; start exit lines=[0-2]
; level=0
0000 DECLARE_CLASS string("a")
0001 DECLARE_CLASS_DELAYED string("b") string("c")
0002 RETURN int(1)

A::f:
; (lines=3, args=1, vars=1, tmps=1, ssa_vars=3, no_loops)
; (after dfa pass)
; %s:6-6
; return [long]
; #0.CV0($a) NOVAL [undef]
BB0:
; start exit lines=[0-2]
; level=0
0000 #1.CV0($a) [object (A)] = RECV 1
0001 #2.T1 [long] = FETCH_OBJ_R #1.CV0($a) [object (A)] string("x")
0002 RETURN #2.T1 [long]

B::f:
; (lines=3, args=1, vars=1, tmps=1, ssa_vars=3, no_loops)
; (after dfa pass)
; %s:12-12
; return [long]
; #0.CV0($b) NOVAL [undef]
BB0:
; start exit lines=[0-2]
; level=0
0000 #1.CV0($b) [object (instanceof B)] = RECV 1
0001 #2.T1 [long] = FETCH_OBJ_R #1.CV0($b) [object (instanceof B)] string("y")
0002 RETURN #2.T1 [long]
Loading