From 74d5f6503aa338f806aefd7114932c0154ddd4ec Mon Sep 17 00:00:00 2001 From: Jorg Sowa Date: Wed, 26 Aug 2026 21:30:38 +0200 Subject: [PATCH] Optimizer: resolve arg/prop/return class hints via op_array scope --- Zend/Optimizer/zend_inference.c | 47 +++++++++++++++++--------------- Zend/Optimizer/zend_inference.h | 3 +- ext/opcache/jit/zend_jit_trace.c | 10 ++++--- 3 files changed, 33 insertions(+), 27 deletions(-) diff --git a/Zend/Optimizer/zend_inference.c b/Zend/Optimizer/zend_inference.c index 2e6cc70ec254..eeb66927f5cb 100644 --- a/Zend/Optimizer/zend_inference.c +++ b/Zend/Optimizer/zend_inference.c @@ -2370,8 +2370,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; } @@ -2388,8 +2389,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); } } @@ -2400,9 +2400,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) { @@ -2491,8 +2492,9 @@ 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; @@ -2500,7 +2502,7 @@ static uint32_t zend_fetch_prop_type(const zend_script *script, const zend_prope 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) @@ -2750,7 +2752,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) { @@ -2761,7 +2763,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) { @@ -2823,7 +2825,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 */ @@ -2842,7 +2844,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 */ @@ -3038,7 +3040,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) { @@ -3309,7 +3311,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; @@ -3782,7 +3784,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) { @@ -3823,7 +3825,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) { @@ -3917,7 +3919,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); } @@ -3965,7 +3967,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; } @@ -4021,7 +4023,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; @@ -4520,7 +4522,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 = (func->common.type != ZEND_INTERNAL_FUNCTION) ? (const zend_op_array *) func : 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 diff --git a/Zend/Optimizer/zend_inference.h b/Zend/Optimizer/zend_inference.h index e6b4207c7494..160c52fd2294 100644 --- a/Zend/Optimizer/zend_inference.h +++ b/Zend/Optimizer/zend_inference.h @@ -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( diff --git a/ext/opcache/jit/zend_jit_trace.c b/ext/opcache/jit/zend_jit_trace.c index 505427890129..3eb052c99137 100644 --- a/ext/opcache/jit/zend_jit_trace.c +++ b/ext/opcache/jit/zend_jit_trace.c @@ -1701,7 +1701,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; @@ -2167,7 +2167,7 @@ static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uin 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, &frame->call->func->op_array, arg_info, &ce); info &= tmp; if (!info) { break; @@ -2506,7 +2506,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; @@ -2673,7 +2673,9 @@ static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uin 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); + const zend_op_array *callee_op_array = + (func->common.type != ZEND_INTERNAL_FUNCTION) ? &func->op_array : NULL; + uint32_t ret_type = zend_fetch_arg_info_type(script, callee_op_array, ret_info, &ce); ssa_var_info[ssa_ops[idx-1].result_def].type &= ret_type; }