From cb7188a33792589fbe7a9c3384bd1e9cfd84e83b Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Tue, 5 Aug 2025 10:14:06 +0200 Subject: [PATCH 01/59] Partial application RFC: https://wiki.php.net/rfc/partial_function_application_v2 Co-authored-by: Joe Watkins --- Zend/Optimizer/compact_literals.c | 5 + Zend/Optimizer/optimize_func_calls.c | 6 +- Zend/Optimizer/zend_call_graph.c | 6 + Zend/Optimizer/zend_inference.c | 1 + .../first_class_callable_non_unary_error.phpt | 10 - ...rst_class_callable_non_variadic_error.phpt | 10 - Zend/tests/partial_application/assert.phpt | 35 + .../partial_application/attributes_001.phpt | 88 ++ .../partial_application/attributes_002.phpt | 19 + .../partial_application/attributes_003.phpt | 16 + Zend/tests/partial_application/clone.phpt | 50 + .../compile_errors_001.phpt | 8 + .../compile_errors_002.phpt | 8 + .../compile_errors_003.phpt | 8 + .../compile_errors_004.phpt | 8 + .../compile_errors_005.phpt | 8 + .../compile_errors_006.phpt | 8 + .../tests/partial_application/errors_001.phpt | 62 + .../tests/partial_application/errors_002.phpt | 15 + .../tests/partial_application/errors_003.phpt | 77 ++ .../tests/partial_application/errors_004.phpt | 16 + .../tests/partial_application/errors_005.phpt | 15 + .../tests/partial_application/errors_006.phpt | 23 + .../tests/partial_application/export_001.phpt | 14 + .../partial_application/extra_named.phpt | 49 + .../partial_application/function_name.phpt | 19 + Zend/tests/partial_application/fuzz_001.phpt | 21 + Zend/tests/partial_application/fuzz_002.phpt | 13 + Zend/tests/partial_application/fuzz_003.phpt | 20 + Zend/tests/partial_application/fuzz_004.phpt | 19 + Zend/tests/partial_application/fuzz_005.phpt | 16 + Zend/tests/partial_application/fuzz_006.phpt | 16 + Zend/tests/partial_application/fuzz_007.phpt | 19 + Zend/tests/partial_application/hook.phpt | 25 + Zend/tests/partial_application/invokable.phpt | 51 + Zend/tests/partial_application/jit_001.phpt | 9 + Zend/tests/partial_application/magic_001.phpt | 82 ++ Zend/tests/partial_application/magic_002.phpt | 65 + Zend/tests/partial_application/magic_003.phpt | 13 + Zend/tests/partial_application/magic_004.phpt | 13 + Zend/tests/partial_application/magic_005.phpt | 30 + Zend/tests/partial_application/magic_006.phpt | 20 + .../named_placeholders.phpt | 117 ++ .../non_dynamic_call_funcs.phpt | 46 + Zend/tests/partial_application/observers.phpt | 34 + .../partial_application/param_reorder.phpt | 202 +++ .../pipe_optimization_001.phpt | 47 + .../pipe_optimization_002.phpt | 51 + .../pipe_optimization_003.phpt | 51 + .../pipe_optimization_004.phpt | 85 ++ .../pipe_optimization_005.phpt | 51 + .../pipe_optimization_006.phpt | 55 + .../pipe_optimization_007.phpt | 85 ++ .../pipe_optimization_008.phpt | 99 ++ .../pipe_optimization_009.phpt | 103 ++ .../pipe_optimization_010.phpt | 58 + .../pipe_optimization_011.phpt | 110 ++ Zend/tests/partial_application/preloading.inc | 13 + .../tests/partial_application/preloading.phpt | 15 + .../partial_application/rebinding_001.phpt | 63 + .../partial_application/rebinding_002.phpt | 45 + .../partial_application/rebinding_003.phpt | 65 + .../recorded_warnings.phpt | 14 + .../partial_application/references_001.phpt | 24 + .../partial_application/references_002.phpt | 24 + .../partial_application/references_003.phpt | 20 + .../partial_application/references_004.phpt | 42 + .../partial_application/references_005.phpt | 26 + .../partial_application/reflection_001.phpt | 48 + .../partial_application/reflection_002.phpt | 57 + .../partial_application/reflection_003.phpt | 43 + .../partial_application/reflection_004.phpt | 16 + .../partial_application/reflection_005.phpt | 16 + .../relative_return_types.phpt | 133 ++ .../partial_application/return_type.phpt | 20 + .../partial_application/rfc_examples.inc | 75 ++ .../rfc_examples_const_expr.phpt | 25 + .../rfc_examples_debug.phpt | 25 + .../rfc_examples_errors.phpt | 34 + .../rfc_examples_eval_order.phpt | 30 + .../rfc_examples_extra_args.phpt | 54 + .../rfc_examples_incompatible_functions.phpt | 14 + .../rfc_examples_magic_methods.phpt | 47 + .../rfc_examples_overview.phpt | 44 + .../rfc_examples_scoping.phpt | 113 ++ .../rfc_examples_semantics.phpt | 30 + .../rfc_examples_semantics_examples.phpt | 217 ++++ .../static_method_001.phpt | 18 + .../partial_application/statics_001.phpt | 22 + .../partial_application/statics_002.phpt | 23 + .../partial_application/statics_003.phpt | 26 + .../superfluous_args_are_forwarded.phpt | 44 + Zend/tests/partial_application/this.phpt | 20 + .../variation_call_001.phpt | 31 + .../variation_closure_001.phpt | 23 + .../variation_closure_002.phpt | 28 + .../variation_closure_003.phpt | 46 + .../variation_debug_001.phpt | 40 + .../variation_debug_002.phpt | 49 + .../partial_application/variation_ex_001.phpt | 14 + .../partial_application/variation_gc_001.phpt | 19 + .../partial_application/variation_gc_002.phpt | 11 + .../partial_application/variation_gc_003.phpt | 15 + .../variation_invoke_001.phpt | 21 + .../variation_nocall_001.phpt | 12 + .../variation_nocall_002.phpt | 30 + .../variation_parent_001.phpt | 75 ++ .../variation_scope_001.phpt | 18 + .../variation_strict_001.phpt | 20 + .../variation_variadics_001.phpt | 31 + .../variation_variadics_002.phpt | 25 + .../variation_variadics_004.phpt | 65 + .../variation_variadics_006.phpt | 17 + .../variation_variadics_007.phpt | 14 + .../variation_variadics_008.phpt | 16 + Zend/zend_ast.c | 10 + Zend/zend_ast.h | 1 + Zend/zend_closures.c | 72 +- Zend/zend_closures.h | 1 + Zend/zend_compile.c | 288 ++++- Zend/zend_compile.h | 4 +- Zend/zend_execute.c | 17 +- Zend/zend_execute.h | 3 + Zend/zend_execute_API.c | 4 + Zend/zend_globals.h | 1 + Zend/zend_language_scanner.l | 83 +- Zend/zend_partial.c | 1146 +++++++++++++++++ Zend/zend_partial.h | 34 + Zend/zend_string.h | 8 + Zend/zend_types.h | 2 + Zend/zend_vm_def.h | 63 + Zend/zend_vm_execute.h | 486 +++++-- Zend/zend_vm_handlers.h | 940 +++++++------- Zend/zend_vm_opcodes.c | 8 +- Zend/zend_vm_opcodes.h | 4 +- configure.ac | 1 + ext/opcache/ZendAccelerator.c | 216 ++++ ext/opcache/ZendAccelerator.h | 10 + ext/opcache/jit/zend_jit.c | 3 + ext/opcache/jit/zend_jit_ir.c | 2 +- ext/opcache/jit/zend_jit_vm_helpers.c | 3 +- win32/build/config.w32 | 2 +- 142 files changed, 7119 insertions(+), 668 deletions(-) delete mode 100644 Zend/tests/first_class_callable/first_class_callable_non_unary_error.phpt delete mode 100644 Zend/tests/first_class_callable/first_class_callable_non_variadic_error.phpt create mode 100644 Zend/tests/partial_application/assert.phpt create mode 100644 Zend/tests/partial_application/attributes_001.phpt create mode 100644 Zend/tests/partial_application/attributes_002.phpt create mode 100644 Zend/tests/partial_application/attributes_003.phpt create mode 100644 Zend/tests/partial_application/clone.phpt create mode 100644 Zend/tests/partial_application/compile_errors_001.phpt create mode 100644 Zend/tests/partial_application/compile_errors_002.phpt create mode 100644 Zend/tests/partial_application/compile_errors_003.phpt create mode 100644 Zend/tests/partial_application/compile_errors_004.phpt create mode 100644 Zend/tests/partial_application/compile_errors_005.phpt create mode 100644 Zend/tests/partial_application/compile_errors_006.phpt create mode 100644 Zend/tests/partial_application/errors_001.phpt create mode 100644 Zend/tests/partial_application/errors_002.phpt create mode 100644 Zend/tests/partial_application/errors_003.phpt create mode 100644 Zend/tests/partial_application/errors_004.phpt create mode 100644 Zend/tests/partial_application/errors_005.phpt create mode 100644 Zend/tests/partial_application/errors_006.phpt create mode 100644 Zend/tests/partial_application/export_001.phpt create mode 100644 Zend/tests/partial_application/extra_named.phpt create mode 100644 Zend/tests/partial_application/function_name.phpt create mode 100644 Zend/tests/partial_application/fuzz_001.phpt create mode 100644 Zend/tests/partial_application/fuzz_002.phpt create mode 100644 Zend/tests/partial_application/fuzz_003.phpt create mode 100644 Zend/tests/partial_application/fuzz_004.phpt create mode 100644 Zend/tests/partial_application/fuzz_005.phpt create mode 100644 Zend/tests/partial_application/fuzz_006.phpt create mode 100644 Zend/tests/partial_application/fuzz_007.phpt create mode 100644 Zend/tests/partial_application/hook.phpt create mode 100644 Zend/tests/partial_application/invokable.phpt create mode 100644 Zend/tests/partial_application/jit_001.phpt create mode 100644 Zend/tests/partial_application/magic_001.phpt create mode 100644 Zend/tests/partial_application/magic_002.phpt create mode 100644 Zend/tests/partial_application/magic_003.phpt create mode 100644 Zend/tests/partial_application/magic_004.phpt create mode 100644 Zend/tests/partial_application/magic_005.phpt create mode 100644 Zend/tests/partial_application/magic_006.phpt create mode 100644 Zend/tests/partial_application/named_placeholders.phpt create mode 100644 Zend/tests/partial_application/non_dynamic_call_funcs.phpt create mode 100644 Zend/tests/partial_application/observers.phpt create mode 100644 Zend/tests/partial_application/param_reorder.phpt create mode 100644 Zend/tests/partial_application/pipe_optimization_001.phpt create mode 100644 Zend/tests/partial_application/pipe_optimization_002.phpt create mode 100644 Zend/tests/partial_application/pipe_optimization_003.phpt create mode 100644 Zend/tests/partial_application/pipe_optimization_004.phpt create mode 100644 Zend/tests/partial_application/pipe_optimization_005.phpt create mode 100644 Zend/tests/partial_application/pipe_optimization_006.phpt create mode 100644 Zend/tests/partial_application/pipe_optimization_007.phpt create mode 100644 Zend/tests/partial_application/pipe_optimization_008.phpt create mode 100644 Zend/tests/partial_application/pipe_optimization_009.phpt create mode 100644 Zend/tests/partial_application/pipe_optimization_010.phpt create mode 100644 Zend/tests/partial_application/pipe_optimization_011.phpt create mode 100644 Zend/tests/partial_application/preloading.inc create mode 100644 Zend/tests/partial_application/preloading.phpt create mode 100644 Zend/tests/partial_application/rebinding_001.phpt create mode 100644 Zend/tests/partial_application/rebinding_002.phpt create mode 100644 Zend/tests/partial_application/rebinding_003.phpt create mode 100644 Zend/tests/partial_application/recorded_warnings.phpt create mode 100644 Zend/tests/partial_application/references_001.phpt create mode 100644 Zend/tests/partial_application/references_002.phpt create mode 100644 Zend/tests/partial_application/references_003.phpt create mode 100644 Zend/tests/partial_application/references_004.phpt create mode 100644 Zend/tests/partial_application/references_005.phpt create mode 100644 Zend/tests/partial_application/reflection_001.phpt create mode 100644 Zend/tests/partial_application/reflection_002.phpt create mode 100644 Zend/tests/partial_application/reflection_003.phpt create mode 100644 Zend/tests/partial_application/reflection_004.phpt create mode 100644 Zend/tests/partial_application/reflection_005.phpt create mode 100644 Zend/tests/partial_application/relative_return_types.phpt create mode 100644 Zend/tests/partial_application/return_type.phpt create mode 100644 Zend/tests/partial_application/rfc_examples.inc create mode 100644 Zend/tests/partial_application/rfc_examples_const_expr.phpt create mode 100644 Zend/tests/partial_application/rfc_examples_debug.phpt create mode 100644 Zend/tests/partial_application/rfc_examples_errors.phpt create mode 100644 Zend/tests/partial_application/rfc_examples_eval_order.phpt create mode 100644 Zend/tests/partial_application/rfc_examples_extra_args.phpt create mode 100644 Zend/tests/partial_application/rfc_examples_incompatible_functions.phpt create mode 100644 Zend/tests/partial_application/rfc_examples_magic_methods.phpt create mode 100644 Zend/tests/partial_application/rfc_examples_overview.phpt create mode 100644 Zend/tests/partial_application/rfc_examples_scoping.phpt create mode 100644 Zend/tests/partial_application/rfc_examples_semantics.phpt create mode 100644 Zend/tests/partial_application/rfc_examples_semantics_examples.phpt create mode 100644 Zend/tests/partial_application/static_method_001.phpt create mode 100644 Zend/tests/partial_application/statics_001.phpt create mode 100644 Zend/tests/partial_application/statics_002.phpt create mode 100644 Zend/tests/partial_application/statics_003.phpt create mode 100644 Zend/tests/partial_application/superfluous_args_are_forwarded.phpt create mode 100644 Zend/tests/partial_application/this.phpt create mode 100644 Zend/tests/partial_application/variation_call_001.phpt create mode 100644 Zend/tests/partial_application/variation_closure_001.phpt create mode 100644 Zend/tests/partial_application/variation_closure_002.phpt create mode 100644 Zend/tests/partial_application/variation_closure_003.phpt create mode 100644 Zend/tests/partial_application/variation_debug_001.phpt create mode 100644 Zend/tests/partial_application/variation_debug_002.phpt create mode 100644 Zend/tests/partial_application/variation_ex_001.phpt create mode 100644 Zend/tests/partial_application/variation_gc_001.phpt create mode 100644 Zend/tests/partial_application/variation_gc_002.phpt create mode 100644 Zend/tests/partial_application/variation_gc_003.phpt create mode 100644 Zend/tests/partial_application/variation_invoke_001.phpt create mode 100644 Zend/tests/partial_application/variation_nocall_001.phpt create mode 100644 Zend/tests/partial_application/variation_nocall_002.phpt create mode 100644 Zend/tests/partial_application/variation_parent_001.phpt create mode 100644 Zend/tests/partial_application/variation_scope_001.phpt create mode 100644 Zend/tests/partial_application/variation_strict_001.phpt create mode 100644 Zend/tests/partial_application/variation_variadics_001.phpt create mode 100644 Zend/tests/partial_application/variation_variadics_002.phpt create mode 100644 Zend/tests/partial_application/variation_variadics_004.phpt create mode 100644 Zend/tests/partial_application/variation_variadics_006.phpt create mode 100644 Zend/tests/partial_application/variation_variadics_007.phpt create mode 100644 Zend/tests/partial_application/variation_variadics_008.phpt create mode 100644 Zend/zend_partial.c create mode 100644 Zend/zend_partial.h diff --git a/Zend/Optimizer/compact_literals.c b/Zend/Optimizer/compact_literals.c index cf74dd8fc1473..a4ecb19c85ef5 100644 --- a/Zend/Optimizer/compact_literals.c +++ b/Zend/Optimizer/compact_literals.c @@ -733,6 +733,7 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx case ZEND_SEND_VAR_NO_REF_EX: case ZEND_SEND_REF: case ZEND_SEND_FUNC_ARG: + case ZEND_SEND_PLACEHOLDER: case ZEND_CHECK_FUNC_ARG: if (opline->op2_type == IS_CONST) { opline->result.num = cache_size; @@ -745,6 +746,10 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx cache_size += sizeof(void *); } break; + case ZEND_CALLABLE_CONVERT_PARTIAL: + opline->op1.num = cache_size; + cache_size += 2 * sizeof(void *); + break; } opline++; } diff --git a/Zend/Optimizer/optimize_func_calls.c b/Zend/Optimizer/optimize_func_calls.c index 69c371207ddc8..05cdce4fc4cf3 100644 --- a/Zend/Optimizer/optimize_func_calls.c +++ b/Zend/Optimizer/optimize_func_calls.c @@ -191,6 +191,7 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx) case ZEND_DO_UCALL: case ZEND_DO_FCALL_BY_NAME: case ZEND_CALLABLE_CONVERT: + case ZEND_CALLABLE_CONVERT_PARTIAL: call--; if (call_stack[call].func && call_stack[call].opline) { zend_op *fcall = call_stack[call].opline; @@ -223,13 +224,14 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx) * At this point we also know whether or not the result of * the DO opcode is used, allowing to optimize calls to * ZEND_ACC_NODISCARD functions. */ - if (opline->opcode != ZEND_CALLABLE_CONVERT) { + if (opline->opcode != ZEND_CALLABLE_CONVERT && opline->opcode != ZEND_CALLABLE_CONVERT_PARTIAL) { opline->opcode = zend_get_call_op(fcall, call_stack[call].func, !RESULT_UNUSED(opline)); } if ((ZEND_OPTIMIZER_PASS_16 & ctx->optimization_level) && call_stack[call].try_inline - && opline->opcode != ZEND_CALLABLE_CONVERT) { + && opline->opcode != ZEND_CALLABLE_CONVERT + && opline->opcode != ZEND_CALLABLE_CONVERT_PARTIAL) { zend_try_inline_call(op_array, fcall, opline, call_stack[call].func); } } diff --git a/Zend/Optimizer/zend_call_graph.c b/Zend/Optimizer/zend_call_graph.c index bb80e21a24658..56f326e100b5f 100644 --- a/Zend/Optimizer/zend_call_graph.c +++ b/Zend/Optimizer/zend_call_graph.c @@ -124,6 +124,7 @@ ZEND_API void zend_analyze_calls(zend_arena **arena, zend_script *script, uint32 case ZEND_DO_UCALL: case ZEND_DO_FCALL_BY_NAME: case ZEND_CALLABLE_CONVERT: + case ZEND_CALLABLE_CONVERT_PARTIAL: func_info->flags |= ZEND_FUNC_HAS_CALLS; if (call_info) { call_info->caller_call_opline = opline; @@ -140,11 +141,16 @@ ZEND_API void zend_analyze_calls(zend_arena **arena, zend_script *script, uint32 case ZEND_SEND_VAR_NO_REF: case ZEND_SEND_VAR_NO_REF_EX: case ZEND_SEND_USER: + case ZEND_SEND_PLACEHOLDER: if (call_info) { if (opline->op2_type == IS_CONST) { call_info->named_args = true; break; } + if (opline->opcode == ZEND_SEND_PLACEHOLDER + && opline->op1.num == ZEND_PLACEHOLDER_VARIADIC) { + break; + } uint32_t num = opline->op2.num; if (num > 0) { diff --git a/Zend/Optimizer/zend_inference.c b/Zend/Optimizer/zend_inference.c index 05d33d3d75fbc..2e6cc70ec254d 100644 --- a/Zend/Optimizer/zend_inference.c +++ b/Zend/Optimizer/zend_inference.c @@ -3903,6 +3903,7 @@ static zend_always_inline zend_result _zend_update_type_info( } break; case ZEND_CALLABLE_CONVERT: + case ZEND_CALLABLE_CONVERT_PARTIAL: UPDATE_SSA_TYPE(MAY_BE_OBJECT | MAY_BE_RC1 | MAY_BE_RCN, ssa_op->result_def); UPDATE_SSA_OBJ_TYPE(zend_ce_closure, /* is_instanceof */ false, ssa_op->result_def); break; diff --git a/Zend/tests/first_class_callable/first_class_callable_non_unary_error.phpt b/Zend/tests/first_class_callable/first_class_callable_non_unary_error.phpt deleted file mode 100644 index 74e36a9ad0df5..0000000000000 --- a/Zend/tests/first_class_callable/first_class_callable_non_unary_error.phpt +++ /dev/null @@ -1,10 +0,0 @@ ---TEST-- -First class callable error: more than one argument ---FILE-- - ---EXPECTF-- -Fatal error: Cannot create a Closure for call expression with more than one argument, or non-variadic placeholders in %s on line %d diff --git a/Zend/tests/first_class_callable/first_class_callable_non_variadic_error.phpt b/Zend/tests/first_class_callable/first_class_callable_non_variadic_error.phpt deleted file mode 100644 index efbd13b7593b6..0000000000000 --- a/Zend/tests/first_class_callable/first_class_callable_non_variadic_error.phpt +++ /dev/null @@ -1,10 +0,0 @@ ---TEST-- -First class callable error: non-variadic placeholder ---FILE-- - ---EXPECTF-- -Fatal error: Cannot create a Closure for call expression with more than one argument, or non-variadic placeholders in %s on line %d diff --git a/Zend/tests/partial_application/assert.phpt b/Zend/tests/partial_application/assert.phpt new file mode 100644 index 0000000000000..fe36e687f8d58 --- /dev/null +++ b/Zend/tests/partial_application/assert.phpt @@ -0,0 +1,35 @@ +--TEST-- +PFA of assert() behaves like a dynamic call to assert() +--FILE-- +getMessage(), "\n"; +} + +try { + echo "# Dynamic call:\n"; + (function ($f) { $f(false); })('assert'); +} catch (Error $e) { + echo $e::class, ": ", $e->getMessage() ?: '(no message)', "\n"; +} + +try { + echo "# PFA call:\n"; + $f = assert(?); + $f(false); +} catch (Error $e) { + echo $e::class, ": ", $e->getMessage() ?: '(no message)', "\n"; +} + +?> +--EXPECT-- +# Static call: +AssertionError: assert(false) +# Dynamic call: +AssertionError: (no message) +# PFA call: +AssertionError: (no message) diff --git a/Zend/tests/partial_application/attributes_001.phpt b/Zend/tests/partial_application/attributes_001.phpt new file mode 100644 index 0000000000000..827ad41321ec6 --- /dev/null +++ b/Zend/tests/partial_application/attributes_001.phpt @@ -0,0 +1,88 @@ +--TEST-- +PFA inherits NoDiscard and SensitiveParameter attributes +--FILE-- +getAttributes()); + + foreach ($r->getParameters() as $i => $p) { + echo "Parameter $i:\n"; + var_dump($p->getAttributes()); + } +} + +echo "# Orig attributes:\n"; + +dump_attributes('f'); + +$f = f(1, ?, ?, ...); + +echo "# PFA attributes:\n"; + +dump_attributes($f); + +?> +--EXPECTF-- +# Orig attributes: +array(2) { + [0]=> + object(ReflectionAttribute)#%d (1) { + ["name"]=> + string(9) "NoDiscard" + } + [1]=> + object(ReflectionAttribute)#%d (1) { + ["name"]=> + string(4) "Test" + } +} +Parameter 0: +array(0) { +} +Parameter 1: +array(1) { + [0]=> + object(ReflectionAttribute)#%d (1) { + ["name"]=> + string(18) "SensitiveParameter" + } +} +Parameter 2: +array(1) { + [0]=> + object(ReflectionAttribute)#%d (1) { + ["name"]=> + string(4) "Test" + } +} +# PFA attributes: +array(1) { + [0]=> + object(ReflectionAttribute)#%d (1) { + ["name"]=> + string(9) "NoDiscard" + } +} +Parameter 0: +array(1) { + [0]=> + object(ReflectionAttribute)#%d (1) { + ["name"]=> + string(18) "SensitiveParameter" + } +} +Parameter 1: +array(0) { +} +Parameter 2: +array(0) { +} diff --git a/Zend/tests/partial_application/attributes_002.phpt b/Zend/tests/partial_application/attributes_002.phpt new file mode 100644 index 0000000000000..be1f1612f9389 --- /dev/null +++ b/Zend/tests/partial_application/attributes_002.phpt @@ -0,0 +1,19 @@ +--TEST-- +PFA preserves #[SensitiveParameter] +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Exception in %s:%d +Stack trace: +#0 %s(%d): f(1, Object(SensitiveParameterValue), 3, Object(SensitiveParameterValue), Object(SensitiveParameterValue)) +#1 %s(%d): {closure:pfa:%s:7}(Object(SensitiveParameterValue), 3, Object(SensitiveParameterValue), Object(SensitiveParameterValue)) +#2 {main} + thrown in %s on line %d diff --git a/Zend/tests/partial_application/attributes_003.phpt b/Zend/tests/partial_application/attributes_003.phpt new file mode 100644 index 0000000000000..d113d02eb8f79 --- /dev/null +++ b/Zend/tests/partial_application/attributes_003.phpt @@ -0,0 +1,16 @@ +--TEST-- +PFA preserves #[NoDiscard] +--FILE-- + +--EXPECTF-- +Warning: The return value of function {closure:%s}() should either be used or intentionally ignored by casting it as (void) in %s on line 7 diff --git a/Zend/tests/partial_application/clone.phpt b/Zend/tests/partial_application/clone.phpt new file mode 100644 index 0000000000000..f778d776b7173 --- /dev/null +++ b/Zend/tests/partial_application/clone.phpt @@ -0,0 +1,50 @@ +--TEST-- +clone() can be partially applied +--FILE-- + 7])); + +$clone = clone(?, ['a' => 8]); +var_dump($clone(new C(9, 10))); + +?> +--EXPECTF-- +object(C)#%d (2) { + ["a"]=> + int(1) + ["b"]=> + int(2) +} +object(C)#%d (2) { + ["a"]=> + int(3) + ["b"]=> + int(4) +} +object(C)#%d (2) { + ["a"]=> + int(7) + ["b"]=> + int(6) +} +object(C)#%d (2) { + ["a"]=> + int(8) + ["b"]=> + int(10) +} diff --git a/Zend/tests/partial_application/compile_errors_001.phpt b/Zend/tests/partial_application/compile_errors_001.phpt new file mode 100644 index 0000000000000..f08495a1f1e56 --- /dev/null +++ b/Zend/tests/partial_application/compile_errors_001.phpt @@ -0,0 +1,8 @@ +--TEST-- +PFA compile errors: multiple variadic placeholders +--FILE-- + +--EXPECTF-- +Fatal error: Variadic placeholder may only appear once in %s on line %d diff --git a/Zend/tests/partial_application/compile_errors_002.phpt b/Zend/tests/partial_application/compile_errors_002.phpt new file mode 100644 index 0000000000000..b6a2073a83638 --- /dev/null +++ b/Zend/tests/partial_application/compile_errors_002.phpt @@ -0,0 +1,8 @@ +--TEST-- +PFA compile errors: variadic placeholder must be last +--FILE-- + +--EXPECTF-- +Fatal error: Variadic placeholder must be last in %s on line %d diff --git a/Zend/tests/partial_application/compile_errors_003.phpt b/Zend/tests/partial_application/compile_errors_003.phpt new file mode 100644 index 0000000000000..26ff8435111b6 --- /dev/null +++ b/Zend/tests/partial_application/compile_errors_003.phpt @@ -0,0 +1,8 @@ +--TEST-- +PFA compile errors: placeholders can not appear after named args +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use positional argument after named argument in %s on line %d diff --git a/Zend/tests/partial_application/compile_errors_004.phpt b/Zend/tests/partial_application/compile_errors_004.phpt new file mode 100644 index 0000000000000..ac7ec163c5dae --- /dev/null +++ b/Zend/tests/partial_application/compile_errors_004.phpt @@ -0,0 +1,8 @@ +--TEST-- +PFA compile errors: variadic placeholder must be last, including after named args +--FILE-- + +--EXPECTF-- +Fatal error: Variadic placeholder must be last in %s on line %d diff --git a/Zend/tests/partial_application/compile_errors_005.phpt b/Zend/tests/partial_application/compile_errors_005.phpt new file mode 100644 index 0000000000000..30e4aa12b488f --- /dev/null +++ b/Zend/tests/partial_application/compile_errors_005.phpt @@ -0,0 +1,8 @@ +--TEST-- +PFA compile errors: variadic placeholder must be last, including after positional args +--FILE-- + +--EXPECTF-- +Fatal error: Variadic placeholder must be last in %s on line %d diff --git a/Zend/tests/partial_application/compile_errors_006.phpt b/Zend/tests/partial_application/compile_errors_006.phpt new file mode 100644 index 0000000000000..90210be6acae7 --- /dev/null +++ b/Zend/tests/partial_application/compile_errors_006.phpt @@ -0,0 +1,8 @@ +--TEST-- +PFA compile errors: can not use unpacking in PFA, including with variadic placeholdres +--FILE-- + "bar"], ...); +?> +--EXPECTF-- +Fatal error: Cannot combine partial application and unpacking in %s on line %d diff --git a/Zend/tests/partial_application/errors_001.phpt b/Zend/tests/partial_application/errors_001.phpt new file mode 100644 index 0000000000000..2d5348cb28af3 --- /dev/null +++ b/Zend/tests/partial_application/errors_001.phpt @@ -0,0 +1,62 @@ +--TEST-- +PFA errors: PFA instantiation follows the usual argument count validation +--FILE-- +getMessage()); +} + +try { + foo(?, ?, ?, ?); +} catch (Error $ex) { + printf("%s: %s\n", $ex::class, $ex->getMessage()); +} + +try { + $c = new C(); + $c->f(?); +} catch (Error $ex) { + printf("%s: %s\n", $ex::class, $ex->getMessage()); +} + +try { + property_exists(?); +} catch (Error $ex) { + printf("%s: %s\n", $ex::class, $ex->getMessage()); +} + +try { + usleep(?, ?); +} catch (Error $ex) { + printf("%s: %s\n", $ex::class, $ex->getMessage()); +} + +try { + foo(?, ?, ?, ?, ...); +} catch (Error $ex) { + printf("%s: %s\n", $ex::class, $ex->getMessage()); +} + +/* It is allowed to specify less than the number of required params, when there + * is a variadic placeholder */ +foo(?, ...); + +?> +--EXPECT-- +ArgumentCountError: Partial application of foo() expects exactly 3 arguments, 1 given +ArgumentCountError: Partial application of foo() expects at most 3 arguments, 4 given +ArgumentCountError: Partial application of C::f() expects exactly 3 arguments, 1 given +ArgumentCountError: Partial application of property_exists() expects exactly 2 arguments, 1 given +ArgumentCountError: Partial application of usleep() expects at most 1 arguments, 2 given +ArgumentCountError: Partial application of foo() expects at most 3 arguments, 4 given diff --git a/Zend/tests/partial_application/errors_002.phpt b/Zend/tests/partial_application/errors_002.phpt new file mode 100644 index 0000000000000..0132d8873e2ab --- /dev/null +++ b/Zend/tests/partial_application/errors_002.phpt @@ -0,0 +1,15 @@ +--TEST-- +PFA errors: named parameter that resolve to the position of a placeholder is an error +--FILE-- +getMessage()); +} +?> +--EXPECT-- +Error: Named parameter $a overwrites previous placeholder diff --git a/Zend/tests/partial_application/errors_003.phpt b/Zend/tests/partial_application/errors_003.phpt new file mode 100644 index 0000000000000..bc1c16e64f9b8 --- /dev/null +++ b/Zend/tests/partial_application/errors_003.phpt @@ -0,0 +1,77 @@ +--TEST-- +PFA errors: PFA call follows the usual argument count validation +--FILE-- +getMessage()); +} + +$foo = foo(?, ?); + +try { + $foo(1); +} catch (Error $ex) { + printf("%s: %s\n", $ex::class, $ex->getMessage()); +} + +$bar = bar(?, ?, ...); + +try { + $bar(1); +} catch (Error $ex) { + printf("%s: %s\n", $ex::class, $ex->getMessage()); +} + +class Foo { + public function bar($a, ...$b) {} +} + +$foo = new Foo; + +$bar = $foo->bar(?); + +try { + $bar(); +} catch (Error $ex) { + printf("%s: %s\n", $ex::class, $ex->getMessage()); +} + +$repeat = str_repeat('a', ...); + +try { + $repeat(); +} catch (Error $ex) { + printf("%s: %s\n", $ex::class, $ex->getMessage()); +} + +$usleep = usleep(?); + +try { + $usleep(); +} catch (Error $ex) { + printf("%s: %s\n", $ex::class, $ex->getMessage()); +} + +try { + $usleep(1, 2); +} catch (Error $ex) { + printf("%s: %s\n", $ex::class, $ex->getMessage()); +} +?> +--EXPECTF-- +ArgumentCountError: Too few arguments to function {closure:%s:%d}(), 0 passed in %s on line %d and exactly 1 expected +ArgumentCountError: Too few arguments to function {closure:%s:%d}(), 1 passed in %s on line %d and exactly 2 expected +ArgumentCountError: Too few arguments to function {closure:%s:%d}(), 1 passed in %s on line %d and exactly 3 expected +ArgumentCountError: Too few arguments to function Foo::{closure:%s:%d}(), 0 passed in %s on line %d and exactly 1 expected +ArgumentCountError: Too few arguments to function {closure:%s:%d}(), 0 passed in %s on line %d and exactly 1 expected +ArgumentCountError: Too few arguments to function {closure:%s:%d}(), 0 passed in %s on line %d and exactly 1 expected diff --git a/Zend/tests/partial_application/errors_004.phpt b/Zend/tests/partial_application/errors_004.phpt new file mode 100644 index 0000000000000..e5e1432753b27 --- /dev/null +++ b/Zend/tests/partial_application/errors_004.phpt @@ -0,0 +1,16 @@ +--TEST-- +PFA errors: not specifying a required param is an error +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught ArgumentCountError: f(): Argument #2 ($b) not passed in %s:6 +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/Zend/tests/partial_application/errors_005.phpt b/Zend/tests/partial_application/errors_005.phpt new file mode 100644 index 0000000000000..2c28f0565e2d8 --- /dev/null +++ b/Zend/tests/partial_application/errors_005.phpt @@ -0,0 +1,15 @@ +--TEST-- +PFA errors: Can not fetch default parameter value for Closure::__invoke() +--FILE-- +__invoke(0, 0, ?); +} catch (Error $e) { + echo $e::class, ": ", $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +ArgumentCountError: Closure::__invoke(): Argument #3 ($c) must be passed explicitly, because the default value is not known diff --git a/Zend/tests/partial_application/errors_006.phpt b/Zend/tests/partial_application/errors_006.phpt new file mode 100644 index 0000000000000..aec2fc5dc0734 --- /dev/null +++ b/Zend/tests/partial_application/errors_006.phpt @@ -0,0 +1,23 @@ +--TEST-- +PFA errors: Some internal function parameters have UNKNOWN default value +--FILE-- + array_keys($array, ???, true) + $f = array_keys(?, strict: true); +} catch (Error $e) { + echo $e::class, ": ", $e->getMessage(), "\n"; +} + +try { + // fn (array $array, mixed $filter_value = ???) => array_keys($array, $filter_value, true) + $f = array_keys(?, strict: true, ...); +} catch (Error $e) { + echo $e::class, ": ", $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +ArgumentCountError: array_keys(): Argument #2 ($filter_value) must be passed explicitly, because the default value is not known +ArgumentCountError: array_keys(): Argument #2 ($filter_value) must be passed explicitly, because the default value is not known diff --git a/Zend/tests/partial_application/export_001.phpt b/Zend/tests/partial_application/export_001.phpt new file mode 100644 index 0000000000000..b48146bcc65a8 --- /dev/null +++ b/Zend/tests/partial_application/export_001.phpt @@ -0,0 +1,14 @@ +--TEST-- +PFA AST export +--INI-- +assert.exception=1 +--FILE-- +getMessage()); +} +?> +--EXPECT-- +AssertionError: assert(0 && foo(?) && foo(new stdClass(), bar: 1, ...)) diff --git a/Zend/tests/partial_application/extra_named.phpt b/Zend/tests/partial_application/extra_named.phpt new file mode 100644 index 0000000000000..4dd80cfa0127f --- /dev/null +++ b/Zend/tests/partial_application/extra_named.phpt @@ -0,0 +1,49 @@ +--TEST-- +PFA extra named parameters are forwarded to the actual function +--FILE-- + +--EXPECT-- +array(3) { + ["foo"]=> + string(3) "foo" + ["bar"]=> + string(3) "bar" + ["baz"]=> + string(3) "baz" +} +array(2) { + ["bar"]=> + string(3) "bar" + ["baz"]=> + string(3) "baz" +} +array(2) { + ["foo"]=> + string(3) "foo" + ["bar"]=> + string(3) "bar" +} diff --git a/Zend/tests/partial_application/function_name.phpt b/Zend/tests/partial_application/function_name.phpt new file mode 100644 index 0000000000000..5ed0c58696408 --- /dev/null +++ b/Zend/tests/partial_application/function_name.phpt @@ -0,0 +1,19 @@ +--TEST-- +Partial application function name +--FILE-- +getName()); +} + +f(); + +var_dump((new ReflectionFunction(g(?)))->getName()); + +?> +--EXPECTF-- +string(%d) "{closure:pfa:f():6}" +string(%d) "{closure:pfa:%sfunction_name.php:11}" diff --git a/Zend/tests/partial_application/fuzz_001.phpt b/Zend/tests/partial_application/fuzz_001.phpt new file mode 100644 index 0000000000000..f6544105a435b --- /dev/null +++ b/Zend/tests/partial_application/fuzz_001.phpt @@ -0,0 +1,21 @@ +--TEST-- +Closure application fuzz 001 +--FILE-- + +--EXPECTF-- +Closure [ static function {closure:%s:%d} ] { + @@ %s 4 - 4 + + - Bound Variables [2] { + Variable #0 [ $fn ] + Variable #1 [ $a ] + } + + - Parameters [1] { + Parameter #0 [ $b ] + } +} diff --git a/Zend/tests/partial_application/fuzz_002.phpt b/Zend/tests/partial_application/fuzz_002.phpt new file mode 100644 index 0000000000000..685cb706e69bb --- /dev/null +++ b/Zend/tests/partial_application/fuzz_002.phpt @@ -0,0 +1,13 @@ +--TEST-- +Closure application fuzz 002 +--FILE-- + +--EXPECTF-- +OK diff --git a/Zend/tests/partial_application/fuzz_003.phpt b/Zend/tests/partial_application/fuzz_003.phpt new file mode 100644 index 0000000000000..6e9d583fda99b --- /dev/null +++ b/Zend/tests/partial_application/fuzz_003.phpt @@ -0,0 +1,20 @@ +--TEST-- +Closure application fuzz 003 +--FILE-- +method(1, ...); +$bar(2); +?> +--EXPECT-- +array(2) { + [0]=> + int(1) + [1]=> + int(2) +} diff --git a/Zend/tests/partial_application/fuzz_004.phpt b/Zend/tests/partial_application/fuzz_004.phpt new file mode 100644 index 0000000000000..ea005304a3afb --- /dev/null +++ b/Zend/tests/partial_application/fuzz_004.phpt @@ -0,0 +1,19 @@ +--TEST-- +Closure application fuzz 004 +--FILE-- +__invoke(UNDEFINED); +} catch (\Throwable $e) { + echo $e::class, ": ", $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Error: Undefined constant "UNDEFINED" diff --git a/Zend/tests/partial_application/fuzz_005.phpt b/Zend/tests/partial_application/fuzz_005.phpt new file mode 100644 index 0000000000000..ea04862d83998 --- /dev/null +++ b/Zend/tests/partial_application/fuzz_005.phpt @@ -0,0 +1,16 @@ +--TEST-- +PFA fuzz 005 +--FILE-- + +==DONE== +--EXPECT-- +==DONE== diff --git a/Zend/tests/partial_application/fuzz_006.phpt b/Zend/tests/partial_application/fuzz_006.phpt new file mode 100644 index 0000000000000..26ec6e3e4dd1f --- /dev/null +++ b/Zend/tests/partial_application/fuzz_006.phpt @@ -0,0 +1,16 @@ +--TEST-- +PFA fuzz 006 +--FILE-- + +--EXPECT-- +int(1) diff --git a/Zend/tests/partial_application/fuzz_007.phpt b/Zend/tests/partial_application/fuzz_007.phpt new file mode 100644 index 0000000000000..123ce29d8b180 --- /dev/null +++ b/Zend/tests/partial_application/fuzz_007.phpt @@ -0,0 +1,19 @@ +--TEST-- +PFA fuzz 007 +--FILE-- +getMessage(), "\n"; +} + +?> +--EXPECT-- +Undefined constant "UNDEFINED" diff --git a/Zend/tests/partial_application/hook.phpt b/Zend/tests/partial_application/hook.phpt new file mode 100644 index 0000000000000..6402c5d01e747 --- /dev/null +++ b/Zend/tests/partial_application/hook.phpt @@ -0,0 +1,25 @@ +--TEST-- +Parent property hook call can not be partially applied +--FILE-- +a = 1; + +?> +--EXPECTF-- +Fatal error: Cannot create Closure for parent property hook call in %s on line %d diff --git a/Zend/tests/partial_application/invokable.phpt b/Zend/tests/partial_application/invokable.phpt new file mode 100644 index 0000000000000..c21030e7733a8 --- /dev/null +++ b/Zend/tests/partial_application/invokable.phpt @@ -0,0 +1,51 @@ +--TEST-- +__invoke() can be partially applied +--FILE-- + +--EXPECTF-- +Closure [ public method {closure:%s:%d} ] { + @@ %s.php 11 - 11 + + - Parameters [2] { + Parameter #0 [ int $a ] + Parameter #1 [ object $b ] + } + - Return [ C ] +} + +Closure [ public method {closure:%s:%d} ] { + @@ %s.php 15 - 15 + + - Bound Variables [1] { + Variable #0 [ $b ] + } + + - Parameters [1] { + Parameter #0 [ int $a ] + } + - Return [ C ] +} + +int(1) +object(stdClass)#%d (0) { +} diff --git a/Zend/tests/partial_application/jit_001.phpt b/Zend/tests/partial_application/jit_001.phpt new file mode 100644 index 0000000000000..84aefa05ab282 --- /dev/null +++ b/Zend/tests/partial_application/jit_001.phpt @@ -0,0 +1,9 @@ +--TEST-- +PFA JIT 001 +--FILE-- + +--EXPECT-- +int(1) +int(2) diff --git a/Zend/tests/partial_application/magic_001.phpt b/Zend/tests/partial_application/magic_001.phpt new file mode 100644 index 0000000000000..60f5a3f20dfb6 --- /dev/null +++ b/Zend/tests/partial_application/magic_001.phpt @@ -0,0 +1,82 @@ +--TEST-- +__call() can be partially applied +--FILE-- +method(?); + +echo (string) new ReflectionFunction($bar); + +try { + $bar(); +} catch (Error $ex) { + printf("%s: %s\n", $ex::class, $ex->getMessage()); +} + +try { + $bar(1, 2); +} catch (Error $ex) { + printf("%s: %s\n", $ex::class, $ex->getMessage()); +} + +$bar(1); + +$bar = $foo->method(?, ...); + +echo (string) new ReflectionFunction($bar); + +$bar(10); + +$bar = $foo->method(new Foo, ...); + +echo (string) new ReflectionFunction($bar); + +$bar(100); +?> +--EXPECTF-- +Closure [ public method {closure:%s:%d} ] { + @@ %s 12 - 12 + + - Parameters [1] { + Parameter #0 [ $args0 ] + } +} +ArgumentCountError: Too few arguments to function Foo::{closure:%s:%d}(), 0 passed in %s on line %d and exactly 1 expected +Foo::method +int(1) +Foo::method +int(1) +Closure [ public method {closure:%s:%d} ] { + @@ %s 30 - 30 + + - Parameters [2] { + Parameter #0 [ $args0 ] + Parameter #1 [ ...$args ] + } +} +Foo::method +int(10) +Closure [ public method {closure:%s:%d} ] { + @@ %s 36 - 36 + + - Bound Variables [1] { + Variable #0 [ $args0 ] + } + + - Parameters [1] { + Parameter #0 [ ...$args ] + } +} +Foo::method +object(Foo)#%d (0) { +} +int(100) diff --git a/Zend/tests/partial_application/magic_002.phpt b/Zend/tests/partial_application/magic_002.phpt new file mode 100644 index 0000000000000..d4baf7afc18ff --- /dev/null +++ b/Zend/tests/partial_application/magic_002.phpt @@ -0,0 +1,65 @@ +--TEST-- +__callStatic() can be partially applied +--FILE-- + +--EXPECTF-- +Closure [ static public method {closure:%s:%d} ] { + @@ %s 10 - 10 + + - Parameters [1] { + Parameter #0 [ $args0 ] + } +} +Foo::method +int(1) +Closure [ static public method {closure:%s:%d} ] { + @@ %s 16 - 16 + + - Parameters [2] { + Parameter #0 [ $args0 ] + Parameter #1 [ ...$args ] + } +} +Foo::method +int(10) +Closure [ static public method {closure:%s:%d} ] { + @@ %s 22 - 22 + + - Bound Variables [1] { + Variable #0 [ $args0 ] + } + + - Parameters [1] { + Parameter #0 [ ...$args ] + } +} +Foo::method +object(Foo)#%d (0) { +} +int(100) diff --git a/Zend/tests/partial_application/magic_003.phpt b/Zend/tests/partial_application/magic_003.phpt new file mode 100644 index 0000000000000..75e26c70b1a34 --- /dev/null +++ b/Zend/tests/partial_application/magic_003.phpt @@ -0,0 +1,13 @@ +--TEST-- +PFA magic trampoline release unused +--FILE-- + +--EXPECT-- +OK diff --git a/Zend/tests/partial_application/magic_004.phpt b/Zend/tests/partial_application/magic_004.phpt new file mode 100644 index 0000000000000..2a340b085f62f --- /dev/null +++ b/Zend/tests/partial_application/magic_004.phpt @@ -0,0 +1,13 @@ +--TEST-- +PFA magic trampoline release used +--FILE-- + +--EXPECT-- +OK diff --git a/Zend/tests/partial_application/magic_005.phpt b/Zend/tests/partial_application/magic_005.phpt new file mode 100644 index 0000000000000..8c4e878196fc7 --- /dev/null +++ b/Zend/tests/partial_application/magic_005.phpt @@ -0,0 +1,30 @@ +--TEST-- +PFA magic null ptr deref in arginfo +--FILE-- +method(?); +var_dump($bar); +?> +--EXPECTF-- +object(Closure)#%d (%d) { + ["name"]=> + string(%d) "{closure:%s}" + ["file"]=> + string(%d) "%smagic_005.php" + ["line"]=> + int(8) + ["this"]=> + object(Foo)#%d (0) { + } + ["parameter"]=> + array(1) { + ["$args0"]=> + string(10) "" + } +} diff --git a/Zend/tests/partial_application/magic_006.phpt b/Zend/tests/partial_application/magic_006.phpt new file mode 100644 index 0000000000000..29564d8b615a7 --- /dev/null +++ b/Zend/tests/partial_application/magic_006.phpt @@ -0,0 +1,20 @@ +--TEST-- +PFA magic varargs +--FILE-- +method(1,...); +$bar(2); +?> +--EXPECT-- +array(2) { + [0]=> + int(1) + [1]=> + int(2) +} diff --git a/Zend/tests/partial_application/named_placeholders.phpt b/Zend/tests/partial_application/named_placeholders.phpt new file mode 100644 index 0000000000000..6517a8946d36b --- /dev/null +++ b/Zend/tests/partial_application/named_placeholders.phpt @@ -0,0 +1,117 @@ +--TEST-- +PFA supports named placeholders +--FILE-- +getMessage(), "\n"; +} + +try { + $bar = bar(c: ?, ...); +} catch (\Throwable $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +Closure [ static function {closure:%s:%d} ] { + @@ %snamed_placeholders.php 11 - 11 + + - Parameters [1] { + Parameter #0 [ $b = 2 ] + } +} +int(1) +object(B)#%d (0) { +} +int(3) +Closure [ static function {closure:%s:%d} ] { + @@ %snamed_placeholders.php 17 - 17 + + - Bound Variables [1] { + Variable #0 [ $fn ] + } + + - Parameters [1] { + Parameter #0 [ $b = 2 ] + } +} +int(1) +object(B)#%d (0) { +} +int(3) +Closure [ static function {closure:%s:%d} ] { + @@ %snamed_placeholders.php 24 - 24 + + - Bound Variables [1] { + Variable #0 [ $fn ] + } + + - Parameters [1] { + Parameter #0 [ $b = 2 ] + } +} +int(1) +object(B)#%d (0) { +} +int(3) +Closure [ static function {closure:%s:%d} ] { + @@ %snamed_placeholders.php 34 - 34 + + - Parameters [3] { + Parameter #0 [ $b = 2 ] + Parameter #1 [ $a = 1 ] + Parameter #2 [ ...$c ] + } +} +object(A)#%d (0) { +} +object(B)#%d (0) { +} +array(1) { + [0]=> + object(C)#%d (0) { + } +} +Named parameter $a overwrites previous placeholder +Cannot use named placeholder for unknown or variadic parameter $c diff --git a/Zend/tests/partial_application/non_dynamic_call_funcs.phpt b/Zend/tests/partial_application/non_dynamic_call_funcs.phpt new file mode 100644 index 0000000000000..0430986be2ac2 --- /dev/null +++ b/Zend/tests/partial_application/non_dynamic_call_funcs.phpt @@ -0,0 +1,46 @@ +--TEST-- +Functions that can not be called dynamically, can not be partially applied +--FILE-- +getMessage(), "\n"; + } +} + +?> +--EXPECT-- +Error: Cannot call func_get_arg() dynamically +Error: Cannot call compact() dynamically +Error: Cannot call extract() dynamically +ArgumentCountError: Partial application of func_get_args() expects at most 0 arguments, 1 given +ArgumentCountError: Partial application of func_num_args() expects at most 0 arguments, 1 given +ArgumentCountError: Partial application of get_defined_vars() expects at most 0 arguments, 1 given diff --git a/Zend/tests/partial_application/observers.phpt b/Zend/tests/partial_application/observers.phpt new file mode 100644 index 0000000000000..8717b38ca326d --- /dev/null +++ b/Zend/tests/partial_application/observers.phpt @@ -0,0 +1,34 @@ +--TEST-- +PFA support observers +--EXTENSIONS-- +zend_test +--INI-- +zend_test.observer.enabled=1 +zend_test.observer.show_output=1 +zend_test.observer.observe_all=1 +--FILE-- + +--EXPECTF-- + + + + <{closure:%s}> + + + + +int(1) +int(2) + + + + diff --git a/Zend/tests/partial_application/param_reorder.phpt b/Zend/tests/partial_application/param_reorder.phpt new file mode 100644 index 0000000000000..3ade1beb0af73 --- /dev/null +++ b/Zend/tests/partial_application/param_reorder.phpt @@ -0,0 +1,202 @@ +--TEST-- +Named parameters define the order of parameters in a PFA +--FILE-- +getMessage(), "\n"; +} + +?> +--EXPECTF-- +# All named +Closure [ static function {closure:%s:%d} ] { + @@ %sparam_reorder.php 13 - 13 + + - Parameters [4] { + Parameter #0 [ $d ] + Parameter #1 [ $c ] + Parameter #2 [ $b ] + Parameter #3 [ $a ] + } +} + +array(4) { + [0]=> + int(4) + [1]=> + int(3) + [2]=> + int(2) + [3]=> + int(1) +} +# Some named: Positional first, then named in specified order +Closure [ static function {closure:%s:%d} ] { + @@ %sparam_reorder.php 19 - 19 + + - Parameters [4] { + Parameter #0 [ $a ] + Parameter #1 [ $b ] + Parameter #2 [ $d ] + Parameter #3 [ $c ] + } +} + +array(4) { + [0]=> + int(1) + [1]=> + int(2) + [2]=> + int(4) + [3]=> + int(3) +} +# Some named, one unspecified +Closure [ static function {closure:%s:%d} ] { + @@ %sparam_reorder.php 25 - 25 + + - Parameters [3] { + Parameter #0 [ $a ] + Parameter #1 [ $c ] + Parameter #2 [ $b ] + } +} + +array(4) { + [0]=> + int(1) + [1]=> + int(3) + [2]=> + int(2) + [3]=> + NULL +} +# Some named, some implicit added by '...' +Closure [ static function {closure:%s:%d} ] { + @@ %sparam_reorder.php 31 - 31 + + - Parameters [4] { + Parameter #0 [ $c ] + Parameter #1 [ $b ] + Parameter #2 [ $a ] + Parameter #3 [ $d = NULL ] + } +} + +array(4) { + [0]=> + int(3) + [1]=> + int(2) + [2]=> + int(1) + [3]=> + int(4) +} +# Some named, some implicit added by '...' on variadic function +Closure [ static function {closure:%s:%d} ] { + @@ %sparam_reorder.php 37 - 37 + + - Parameters [4] { + Parameter #0 [ $c ] + Parameter #1 [ $b ] + Parameter #2 [ $a ] + Parameter #3 [ ...$d ] + } +} + +array(4) { + [0]=> + int(3) + [1]=> + int(2) + [2]=> + int(1) + [3]=> + array(3) { + [0]=> + int(4) + [1]=> + int(5) + [2]=> + int(6) + } +} +# Some prebound, some named +Closure [ static function {closure:%s:%d} ] { + @@ %sparam_reorder.php 43 - 43 + + - Bound Variables [2] { + Variable #0 [ $a ] + Variable #1 [ $d ] + } + + - Parameters [2] { + Parameter #0 [ $c ] + Parameter #1 [ $b ] + } +} + +array(4) { + [0]=> + int(-1) + [1]=> + int(2) + [2]=> + int(1) + [3]=> + int(-2) +} +# Some named, some required missing +ArgumentCountError: f(): Argument #1 ($a) not passed diff --git a/Zend/tests/partial_application/pipe_optimization_001.phpt b/Zend/tests/partial_application/pipe_optimization_001.phpt new file mode 100644 index 0000000000000..71bfee5dba44e --- /dev/null +++ b/Zend/tests/partial_application/pipe_optimization_001.phpt @@ -0,0 +1,47 @@ +--TEST-- +PFA optimization: PFA with single placeholder arg can be optimized +--EXTENSIONS-- +opcache +--INI-- +opcache.opt_debug_level=0x20000 +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_cache= +opcache.file_cache_only=0 +--FILE-- + 0) { + function foo($a) { + var_dump($a); + } +} + +1 |> foo(?); + +?> +--EXPECTF-- +$_main: + ; (lines=9, args=0, vars=0, tmps=2) + ; (after optimizer) + ; %spipe_optimization_001.php:1-12 +0000 INIT_FCALL 0 %d string("time") +0001 V1 = DO_ICALL +0002 T0 = IS_SMALLER int(0) V1 +0003 JMPZ T0 0005 +0004 DECLARE_FUNCTION string("foo") 0 +0005 INIT_FCALL_BY_NAME 1 string("foo") +0006 SEND_VAL_EX int(1) 1 +0007 DO_FCALL_BY_NAME +0008 RETURN int(1) + +foo: + ; (lines=5, args=1, vars=1, tmps=0) + ; (after optimizer) + ; %spipe_optimization_001.php:4-6 +0000 CV0($a) = RECV 1 +0001 INIT_FCALL 1 %d string("var_dump") +0002 SEND_VAR CV0($a) 1 +0003 DO_ICALL +0004 RETURN null +int(1) diff --git a/Zend/tests/partial_application/pipe_optimization_002.phpt b/Zend/tests/partial_application/pipe_optimization_002.phpt new file mode 100644 index 0000000000000..ae5992e405a05 --- /dev/null +++ b/Zend/tests/partial_application/pipe_optimization_002.phpt @@ -0,0 +1,51 @@ +--TEST-- +PFA pipe optimization: PFA with only one placeholder can be optimized +--EXTENSIONS-- +opcache +--INI-- +opcache.opt_debug_level=0x20000 +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_cache= +opcache.file_cache_only=0 +--FILE-- + 0) { + function foo($a, $b) { + var_dump($a, $b); + } +} + +2 |> foo(1, ?); + +?> +--EXPECTF-- +$_main: + ; (lines=10, args=0, vars=0, tmps=2) + ; (after optimizer) + ; %spipe_optimization_002.php:1-12 +0000 INIT_FCALL 0 %d string("time") +0001 V1 = DO_ICALL +0002 T0 = IS_SMALLER int(0) V1 +0003 JMPZ T0 0005 +0004 DECLARE_FUNCTION string("foo") 0 +0005 INIT_FCALL_BY_NAME 2 string("foo") +0006 SEND_VAL_EX int(1) 1 +0007 SEND_VAL_EX int(2) 2 +0008 DO_FCALL_BY_NAME +0009 RETURN int(1) + +foo: + ; (lines=7, args=2, vars=2, tmps=0) + ; (after optimizer) + ; %spipe_optimization_002.php:4-6 +0000 CV0($a) = RECV 1 +0001 CV1($b) = RECV 2 +0002 INIT_FCALL 2 %d string("var_dump") +0003 SEND_VAR CV0($a) 1 +0004 SEND_VAR CV1($b) 2 +0005 DO_ICALL +0006 RETURN null +int(1) +int(2) diff --git a/Zend/tests/partial_application/pipe_optimization_003.phpt b/Zend/tests/partial_application/pipe_optimization_003.phpt new file mode 100644 index 0000000000000..4f45eb5555e29 --- /dev/null +++ b/Zend/tests/partial_application/pipe_optimization_003.phpt @@ -0,0 +1,51 @@ +--TEST-- +PFA pipe optimization: PFA with only one placeholder can be optimized +--EXTENSIONS-- +opcache +--INI-- +opcache.opt_debug_level=0x20000 +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_cache= +opcache.file_cache_only=0 +--FILE-- + 0) { + function foo($a, $b) { + var_dump($a, $b); + } +} + +2 |> foo(?, 1); + +?> +--EXPECTF-- +$_main: + ; (lines=10, args=0, vars=0, tmps=2) + ; (after optimizer) + ; %spipe_optimization_003.php:1-12 +0000 INIT_FCALL 0 %d string("time") +0001 V1 = DO_ICALL +0002 T0 = IS_SMALLER int(0) V1 +0003 JMPZ T0 0005 +0004 DECLARE_FUNCTION string("foo") 0 +0005 INIT_FCALL_BY_NAME 2 string("foo") +0006 SEND_VAL_EX int(2) 1 +0007 SEND_VAL_EX int(1) 2 +0008 DO_FCALL_BY_NAME +0009 RETURN int(1) + +foo: + ; (lines=7, args=2, vars=2, tmps=0) + ; (after optimizer) + ; %spipe_optimization_003.php:4-6 +0000 CV0($a) = RECV 1 +0001 CV1($b) = RECV 2 +0002 INIT_FCALL 2 %d string("var_dump") +0003 SEND_VAR CV0($a) 1 +0004 SEND_VAR CV1($b) 2 +0005 DO_ICALL +0006 RETURN null +int(2) +int(1) diff --git a/Zend/tests/partial_application/pipe_optimization_004.phpt b/Zend/tests/partial_application/pipe_optimization_004.phpt new file mode 100644 index 0000000000000..a2ad5f72ed585 --- /dev/null +++ b/Zend/tests/partial_application/pipe_optimization_004.phpt @@ -0,0 +1,85 @@ +--TEST-- +PFA pipe optimization: PFA with multiple placeholders can not be optimized +--EXTENSIONS-- +opcache +--INI-- +opcache.opt_debug_level=0x20000 +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_cache= +opcache.file_cache_only=0 +--FILE-- + 0) { + function foo($a, $b) { + var_dump($a, $b); + } +} + +try { +2 |> foo(?, ?); +} catch (\Throwable $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +$_main: + ; (lines=19, args=0, vars=1, tmps=2) + ; (after optimizer) + ; %spipe_optimization_004.php:1-16 +0000 INIT_FCALL 0 %d string("time") +0001 V2 = DO_ICALL +0002 T1 = IS_SMALLER int(0) V2 +0003 JMPZ T1 0005 +0004 DECLARE_FUNCTION string("foo") 0 +0005 INIT_FCALL_BY_NAME 2 string("foo") +0006 SEND_PLACEHOLDER +0007 SEND_PLACEHOLDER +0008 T1 = CALLABLE_CONVERT_PARTIAL %d +0009 INIT_DYNAMIC_CALL 1 T1 +0010 SEND_VAL_EX int(2) 1 +0011 DO_FCALL +0012 RETURN int(1) +0013 CV0($e) = CATCH string("Throwable") +0014 INIT_METHOD_CALL 0 CV0($e) string("getMessage") +0015 V1 = DO_FCALL +0016 ECHO V1 +0017 ECHO string("\n") +0018 RETURN int(1) +EXCEPTION TABLE: + 0005, 0013, -, - + +foo: + ; (lines=7, args=2, vars=2, tmps=0) + ; (after optimizer) + ; %spipe_optimization_004.php:4-6 +0000 CV0($a) = RECV 1 +0001 CV1($b) = RECV 2 +0002 INIT_FCALL 2 %d string("var_dump") +0003 SEND_VAR CV0($a) 1 +0004 SEND_VAR CV1($b) 2 +0005 DO_ICALL +0006 RETURN null + +$_main: + ; (lines=3, args=0, vars=0, tmps=1) + ; (after optimizer) + ; %s:1-10 +0000 T0 = DECLARE_LAMBDA_FUNCTION 0 +0001 FREE T0 +0002 RETURN int(1) + +{closure:%s:%d}: + ; (lines=7, args=2, vars=2, tmps=1) + ; (after optimizer) + ; %s:10-10 +0000 CV0($a) = RECV 1 +0001 CV1($b) = RECV 2 +0002 INIT_FCALL 2 %d string("foo") +0003 SEND_VAR CV0($a) 1 +0004 SEND_VAR CV1($b) 2 +0005 V2 = DO_UCALL +0006 RETURN V2 +Too few arguments to function {closure:%s:%d}(), 1 passed in %s on line %d and exactly 2 expected diff --git a/Zend/tests/partial_application/pipe_optimization_005.phpt b/Zend/tests/partial_application/pipe_optimization_005.phpt new file mode 100644 index 0000000000000..3844e09aa8ad2 --- /dev/null +++ b/Zend/tests/partial_application/pipe_optimization_005.phpt @@ -0,0 +1,51 @@ +--TEST-- +PFA pipe optimization: PFA with only one placeholder can be optimized +--EXTENSIONS-- +opcache +--INI-- +opcache.opt_debug_level=0x20000 +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_cache= +opcache.file_cache_only=0 +--FILE-- + 0) { + function foo($a, $b) { + var_dump($a, $b); + } +} + +2 |> foo(1, ...); + +?> +--EXPECTF-- +$_main: + ; (lines=10, args=0, vars=0, tmps=2) + ; (after optimizer) + ; %spipe_optimization_005.php:1-12 +0000 INIT_FCALL 0 %d string("time") +0001 V1 = DO_ICALL +0002 T0 = IS_SMALLER int(0) V1 +0003 JMPZ T0 0005 +0004 DECLARE_FUNCTION string("foo") 0 +0005 INIT_FCALL_BY_NAME 2 string("foo") +0006 SEND_VAL_EX int(1) 1 +0007 SEND_VAL_EX int(2) 2 +0008 DO_FCALL_BY_NAME +0009 RETURN int(1) + +foo: + ; (lines=7, args=2, vars=2, tmps=0) + ; (after optimizer) + ; %spipe_optimization_005.php:4-6 +0000 CV0($a) = RECV 1 +0001 CV1($b) = RECV 2 +0002 INIT_FCALL 2 %d string("var_dump") +0003 SEND_VAR CV0($a) 1 +0004 SEND_VAR CV1($b) 2 +0005 DO_ICALL +0006 RETURN null +int(1) +int(2) diff --git a/Zend/tests/partial_application/pipe_optimization_006.phpt b/Zend/tests/partial_application/pipe_optimization_006.phpt new file mode 100644 index 0000000000000..2a83f2b83fe9a --- /dev/null +++ b/Zend/tests/partial_application/pipe_optimization_006.phpt @@ -0,0 +1,55 @@ +--TEST-- +PFA pipe optimization: PFA with only one placeholder can be optimized (named) +--EXTENSIONS-- +opcache +--INI-- +opcache.opt_debug_level=0x20000 +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_cache= +opcache.file_cache_only=0 +--FILE-- + 0) { + function foo($a, $b = null, $c = null) { + var_dump($a, $b, $c); + } +} + +2 |> foo(1, c: ?); + +?> +--EXPECTF-- +$_main: + ; (lines=11, args=0, vars=0, tmps=2) + ; (after optimizer) + ; %spipe_optimization_006.php:1-12 +0000 INIT_FCALL 0 %d string("time") +0001 V1 = DO_ICALL +0002 T0 = IS_SMALLER int(0) V1 +0003 JMPZ T0 0005 +0004 DECLARE_FUNCTION string("foo") 0 +0005 INIT_FCALL_BY_NAME 1 string("foo") +0006 SEND_VAL_EX int(1) 1 +0007 SEND_VAL_EX int(2) string("c") +0008 CHECK_UNDEF_ARGS +0009 DO_FCALL_BY_NAME +0010 RETURN int(1) + +foo: + ; (lines=9, args=3, vars=3, tmps=0) + ; (after optimizer) + ; %spipe_optimization_006.php:4-6 +0000 CV0($a) = RECV 1 +0001 CV1($b) = RECV_INIT 2 null +0002 CV2($c) = RECV_INIT 3 null +0003 INIT_FCALL 3 %d string("var_dump") +0004 SEND_VAR CV0($a) 1 +0005 SEND_VAR CV1($b) 2 +0006 SEND_VAR CV2($c) 3 +0007 DO_ICALL +0008 RETURN null +int(1) +NULL +int(2) diff --git a/Zend/tests/partial_application/pipe_optimization_007.phpt b/Zend/tests/partial_application/pipe_optimization_007.phpt new file mode 100644 index 0000000000000..39dd48b632e4d --- /dev/null +++ b/Zend/tests/partial_application/pipe_optimization_007.phpt @@ -0,0 +1,85 @@ +--TEST-- +PFA pipe optimization: PFA with multiple placeholders can not be optimized +--EXTENSIONS-- +opcache +--INI-- +opcache.opt_debug_level=0x20000 +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_cache= +opcache.file_cache_only=0 +--FILE-- + 0) { + function foo($a, $b) { + var_dump($a, $b); + } +} + +try { +2 |> foo(a: ?, b: ?); +} catch (\Throwable $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +$_main: + ; (lines=19, args=0, vars=1, tmps=2) + ; (after optimizer) + ; %spipe_optimization_007.php:1-16 +0000 INIT_FCALL 0 %d string("time") +0001 V2 = DO_ICALL +0002 T1 = IS_SMALLER int(0) V2 +0003 JMPZ T1 0005 +0004 DECLARE_FUNCTION string("foo") 0 +0005 INIT_FCALL_BY_NAME 0 string("foo") +0006 SEND_PLACEHOLDER string("a") +0007 SEND_PLACEHOLDER string("b") +0008 T1 = CALLABLE_CONVERT_PARTIAL %d array(...) +0009 INIT_DYNAMIC_CALL 1 T1 +0010 SEND_VAL_EX int(2) 1 +0011 DO_FCALL +0012 RETURN int(1) +0013 CV0($e) = CATCH string("Throwable") +0014 INIT_METHOD_CALL 0 CV0($e) string("getMessage") +0015 V1 = DO_FCALL +0016 ECHO V1 +0017 ECHO string("\n") +0018 RETURN int(1) +EXCEPTION TABLE: + 0005, 0013, -, - + +foo: + ; (lines=7, args=2, vars=2, tmps=0) + ; (after optimizer) + ; %spipe_optimization_007.php:4-6 +0000 CV0($a) = RECV 1 +0001 CV1($b) = RECV 2 +0002 INIT_FCALL 2 %d string("var_dump") +0003 SEND_VAR CV0($a) 1 +0004 SEND_VAR CV1($b) 2 +0005 DO_ICALL +0006 RETURN null + +$_main: + ; (lines=3, args=0, vars=0, tmps=1) + ; (after optimizer) + ; %s:1-10 +0000 T0 = DECLARE_LAMBDA_FUNCTION 0 +0001 FREE T0 +0002 RETURN int(1) + +{closure:%s:%d}: + ; (lines=7, args=2, vars=2, tmps=1) + ; (after optimizer) + ; %s:10-10 +0000 CV0($a) = RECV 1 +0001 CV1($b) = RECV 2 +0002 INIT_FCALL 2 %d string("foo") +0003 SEND_VAR CV0($a) 1 +0004 SEND_VAR CV1($b) 2 +0005 V2 = DO_UCALL +0006 RETURN V2 +Too few arguments to function {closure:%s:%d}(), 1 passed in %s on line %d and exactly 2 expected diff --git a/Zend/tests/partial_application/pipe_optimization_008.phpt b/Zend/tests/partial_application/pipe_optimization_008.phpt new file mode 100644 index 0000000000000..6293f782d4d44 --- /dev/null +++ b/Zend/tests/partial_application/pipe_optimization_008.phpt @@ -0,0 +1,99 @@ +--TEST-- +PFA pipe optimization: PFA with both a variadic placeholder and named arg can not be optimized +--EXTENSIONS-- +opcache +--INI-- +opcache.opt_debug_level=0x20000 +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_cache= +opcache.file_cache_only=0 +--FILE-- + 0) { + function foo($a, $b) { + var_dump($a, $b); + } +} + +try { + 2 |> foo(a: 1, ...); +} catch (\Throwable $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +$_main: + ; (lines=18, args=0, vars=1, tmps=2) + ; (after optimizer) + ; %spipe_optimization_008.php:1-16 +0000 INIT_FCALL 0 %d string("time") +0001 V2 = DO_ICALL +0002 T1 = IS_SMALLER int(0) V2 +0003 JMPZ T1 0005 +0004 DECLARE_FUNCTION string("foo") 0 +0005 INIT_FCALL_BY_NAME 0 string("foo") +0006 SEND_VAL_EX int(1) string("a") +0007 T1 = CALLABLE_CONVERT_PARTIAL %d +0008 INIT_DYNAMIC_CALL 1 T1 +0009 SEND_VAL_EX int(2) 1 +0010 DO_FCALL +0011 RETURN int(1) +0012 CV0($e) = CATCH string("Throwable") +0013 INIT_METHOD_CALL 0 CV0($e) string("getMessage") +0014 V1 = DO_FCALL +0015 ECHO V1 +0016 ECHO string("\n") +0017 RETURN int(1) +EXCEPTION TABLE: + 0005, 0012, -, - + +foo: + ; (lines=7, args=2, vars=2, tmps=0) + ; (after optimizer) + ; %spipe_optimization_008.php:4-6 +0000 CV0($a) = RECV 1 +0001 CV1($b) = RECV 2 +0002 INIT_FCALL 2 %d string("var_dump") +0003 SEND_VAR CV0($a) 1 +0004 SEND_VAR CV1($b) 2 +0005 DO_ICALL +0006 RETURN null + +$_main: + ; (lines=4, args=0, vars=1, tmps=1) + ; (after optimizer) + ; %s:1-10 +0000 T1 = DECLARE_LAMBDA_FUNCTION 0 +0001 BIND_LEXICAL T1 CV0($a) +0002 FREE T1 +0003 RETURN int(1) +LIVE RANGES: + 1: 0001 - 0002 (tmp/var) + +{closure:%s:%d}: + ; (lines=18, args=1, vars=2, tmps=2) + ; (after optimizer) + ; %s:10-10 +0000 CV0($b) = RECV 1 +0001 BIND_STATIC CV1($a) +0002 T3 = FUNC_NUM_ARGS +0003 T2 = IS_SMALLER_OR_EQUAL T3 int(1) +0004 JMPZ T2 0010 +0005 INIT_FCALL 2 %d string("foo") +0006 SEND_VAR CV1($a) 1 +0007 SEND_VAR CV0($b) 2 +0008 V2 = DO_UCALL +0009 RETURN V2 +0010 INIT_FCALL 2 %d string("foo") +0011 SEND_VAR CV1($a) 1 +0012 SEND_VAR CV0($b) 2 +0013 T2 = FUNC_GET_ARGS int(1) +0014 SEND_UNPACK T2 +0015 CHECK_UNDEF_ARGS +0016 V2 = DO_UCALL +0017 RETURN V2 +int(1) +int(2) diff --git a/Zend/tests/partial_application/pipe_optimization_009.phpt b/Zend/tests/partial_application/pipe_optimization_009.phpt new file mode 100644 index 0000000000000..0902786616373 --- /dev/null +++ b/Zend/tests/partial_application/pipe_optimization_009.phpt @@ -0,0 +1,103 @@ +--TEST-- +PFA pipe optimization: Evaluation order +--EXTENSIONS-- +opcache +--INI-- +opcache.opt_debug_level=0x20000 +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_cache= +opcache.file_cache_only=0 +--FILE-- + 0) { + function foo($a, $b, $c) { + var_dump($a, $b, $c); + } + function lhs() { + echo __FUNCTION__, "\n"; + return 0; + } + function arg1() { + echo __FUNCTION__, "\n"; + return 1; + } + function arg2() { + echo __FUNCTION__, "\n"; + return 2; + } +} + +lhs() |> foo(arg1(), ?, arg2()); + +?> +--EXPECTF-- +$_main: + ; (lines=21, args=0, vars=0, tmps=2) + ; (after optimizer) + ; %spipe_optimization_009.php:1-24 +0000 INIT_FCALL 0 %d string("time") +0001 V1 = DO_ICALL +0002 T0 = IS_SMALLER int(0) V1 +0003 JMPZ T0 0008 +0004 DECLARE_FUNCTION string("foo") 0 +0005 DECLARE_FUNCTION string("lhs") 1 +0006 DECLARE_FUNCTION string("arg1") 2 +0007 DECLARE_FUNCTION string("arg2") 3 +0008 INIT_FCALL_BY_NAME 0 string("lhs") +0009 V1 = DO_FCALL_BY_NAME +0010 T0 = QM_ASSIGN V1 +0011 INIT_FCALL_BY_NAME 3 string("foo") +0012 INIT_FCALL_BY_NAME 0 string("arg1") +0013 V1 = DO_FCALL_BY_NAME +0014 SEND_VAR_NO_REF_EX V1 1 +0015 SEND_VAL_EX T0 2 +0016 INIT_FCALL_BY_NAME 0 string("arg2") +0017 V0 = DO_FCALL_BY_NAME +0018 SEND_VAR_NO_REF_EX V0 3 +0019 DO_FCALL_BY_NAME +0020 RETURN int(1) +LIVE RANGES: + 0: 0011 - 0015 (tmp/var) + +foo: + ; (lines=9, args=3, vars=3, tmps=0) + ; (after optimizer) + ; %spipe_optimization_009.php:4-6 +0000 CV0($a) = RECV 1 +0001 CV1($b) = RECV 2 +0002 CV2($c) = RECV 3 +0003 INIT_FCALL 3 %d string("var_dump") +0004 SEND_VAR CV0($a) 1 +0005 SEND_VAR CV1($b) 2 +0006 SEND_VAR CV2($c) 3 +0007 DO_ICALL +0008 RETURN null + +lhs: + ; (lines=2, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %spipe_optimization_009.php:7-10 +0000 ECHO string("lhs\n") +0001 RETURN int(0) + +arg1: + ; (lines=2, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %spipe_optimization_009.php:11-14 +0000 ECHO string("arg1\n") +0001 RETURN int(1) + +arg2: + ; (lines=2, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %spipe_optimization_009.php:15-18 +0000 ECHO string("arg2\n") +0001 RETURN int(2) +lhs +arg1 +arg2 +int(1) +int(0) +int(2) diff --git a/Zend/tests/partial_application/pipe_optimization_010.phpt b/Zend/tests/partial_application/pipe_optimization_010.phpt new file mode 100644 index 0000000000000..60202e72cfd1d --- /dev/null +++ b/Zend/tests/partial_application/pipe_optimization_010.phpt @@ -0,0 +1,58 @@ +--TEST-- +PFA pipe optimization: References +--EXTENSIONS-- +opcache +--INI-- +opcache.opt_debug_level=0x20000 +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_cache= +opcache.file_cache_only=0 +--FILE-- + 0) { + function foo(&$a, $b) { + var_dump($a, $b); + $a = 2; + } +} + +1 |> foo($a, ?); +var_dump($a); + +?> +--EXPECTF-- +$_main: + ; (lines=13, args=0, vars=1, tmps=2) + ; (after optimizer) + ; %spipe_optimization_010.php:1-14 +0000 INIT_FCALL 0 %d string("time") +0001 V2 = DO_ICALL +0002 T1 = IS_SMALLER int(0) V2 +0003 JMPZ T1 0005 +0004 DECLARE_FUNCTION string("foo") 0 +0005 INIT_FCALL_BY_NAME 2 string("foo") +0006 SEND_VAR_EX CV0($a) 1 +0007 SEND_VAL_EX int(1) 2 +0008 DO_FCALL_BY_NAME +0009 INIT_FCALL 1 %d string("var_dump") +0010 SEND_VAR CV0($a) 1 +0011 DO_ICALL +0012 RETURN int(1) + +foo: + ; (lines=8, args=2, vars=2, tmps=0) + ; (after optimizer) + ; %spipe_optimization_010.php:4-7 +0000 CV0($a) = RECV 1 +0001 CV1($b) = RECV 2 +0002 INIT_FCALL 2 %d string("var_dump") +0003 SEND_VAR CV0($a) 1 +0004 SEND_VAR CV1($b) 2 +0005 DO_ICALL +0006 ASSIGN CV0($a) int(2) +0007 RETURN null +NULL +int(1) +int(2) diff --git a/Zend/tests/partial_application/pipe_optimization_011.phpt b/Zend/tests/partial_application/pipe_optimization_011.phpt new file mode 100644 index 0000000000000..1a39e4ff2f0d6 --- /dev/null +++ b/Zend/tests/partial_application/pipe_optimization_011.phpt @@ -0,0 +1,110 @@ +--TEST-- +PFA pipe optimization: Evaluation order +--EXTENSIONS-- +opcache +--INI-- +opcache.opt_debug_level=0x20000 +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_cache= +opcache.file_cache_only=0 +--FILE-- + 0) { + function foo($a, $b, $c) { + var_dump($a, $b, $c); + } + function lhs() { + echo __FUNCTION__, "\n"; + return 0; + } + function arg1() { + global $a; + $a = 2; + echo __FUNCTION__, "\n"; + return 1; + } + function arg2() { + global $a; + $a = 3; + echo __FUNCTION__, "\n"; + return 2; + } +} + +$a = 0; +$a |> foo(arg1(), ?, arg2()); + +?> +--EXPECTF-- +$_main: + ; (lines=20, args=0, vars=1, tmps=2) + ; (after optimizer) + ; %spipe_optimization_011.php:1-29 +0000 INIT_FCALL 0 %d string("time") +0001 V2 = DO_ICALL +0002 T1 = IS_SMALLER int(0) V2 +0003 JMPZ T1 0008 +0004 DECLARE_FUNCTION string("foo") 0 +0005 DECLARE_FUNCTION string("lhs") 1 +0006 DECLARE_FUNCTION string("arg1") 2 +0007 DECLARE_FUNCTION string("arg2") 3 +0008 ASSIGN CV0($a) int(0) +0009 T1 = QM_ASSIGN CV0($a) +0010 INIT_FCALL_BY_NAME 3 string("foo") +0011 INIT_FCALL_BY_NAME 0 string("arg1") +0012 V2 = DO_FCALL_BY_NAME +0013 SEND_VAR_NO_REF_EX V2 1 +0014 SEND_VAL_EX T1 2 +0015 INIT_FCALL_BY_NAME 0 string("arg2") +0016 V1 = DO_FCALL_BY_NAME +0017 SEND_VAR_NO_REF_EX V1 3 +0018 DO_FCALL_BY_NAME +0019 RETURN int(1) +LIVE RANGES: + 1: 0010 - 0014 (tmp/var) + +foo: + ; (lines=9, args=3, vars=3, tmps=0) + ; (after optimizer) + ; %spipe_optimization_011.php:4-6 +0000 CV0($a) = RECV 1 +0001 CV1($b) = RECV 2 +0002 CV2($c) = RECV 3 +0003 INIT_FCALL 3 %d string("var_dump") +0004 SEND_VAR CV0($a) 1 +0005 SEND_VAR CV1($b) 2 +0006 SEND_VAR CV2($c) 3 +0007 DO_ICALL +0008 RETURN null + +lhs: + ; (lines=2, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %spipe_optimization_011.php:7-10 +0000 ECHO string("lhs\n") +0001 RETURN int(0) + +arg1: + ; (lines=4, args=0, vars=1, tmps=0) + ; (after optimizer) + ; %spipe_optimization_011.php:11-16 +0000 BIND_GLOBAL CV0($a) string("a") +0001 ASSIGN CV0($a) int(2) +0002 ECHO string("arg1\n") +0003 RETURN int(1) + +arg2: + ; (lines=4, args=0, vars=1, tmps=0) + ; (after optimizer) + ; %spipe_optimization_011.php:17-22 +0000 BIND_GLOBAL CV0($a) string("a") +0001 ASSIGN CV0($a) int(3) +0002 ECHO string("arg2\n") +0003 RETURN int(2) +arg1 +arg2 +int(1) +int(0) +int(2) diff --git a/Zend/tests/partial_application/preloading.inc b/Zend/tests/partial_application/preloading.inc new file mode 100644 index 0000000000000..885ed0c5b0f18 --- /dev/null +++ b/Zend/tests/partial_application/preloading.inc @@ -0,0 +1,13 @@ + diff --git a/Zend/tests/partial_application/preloading.phpt b/Zend/tests/partial_application/preloading.phpt new file mode 100644 index 0000000000000..5e3069c271a6d --- /dev/null +++ b/Zend/tests/partial_application/preloading.phpt @@ -0,0 +1,15 @@ +--TEST-- +PFA preloading +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.preload={PWD}/preloading.inc +--FILE-- + +--EXPECT-- +int(2) +int(2) diff --git a/Zend/tests/partial_application/rebinding_001.phpt b/Zend/tests/partial_application/rebinding_001.phpt new file mode 100644 index 0000000000000..012de02c55368 --- /dev/null +++ b/Zend/tests/partial_application/rebinding_001.phpt @@ -0,0 +1,63 @@ +--TEST-- +PFA can only be rebound to an instanceof $this +--FILE-- +f(?); +$g = $c->g(?); + +echo "# Can be rebound to \$this of the same class:\n"; +$f->bindTo(new C)(1); + +echo "# Can be rebound to \$this of a sub-class:\n"; +$f->bindTo(new SubClass)(1); + +echo "# Cannot be rebound to an unrelated class:\n"; +try { + $f->bindTo(new Unrelated)(1); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +echo "# Cannot unbind \$this on instance method:\n"; +try { + $f->bindTo(null)(1); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +echo "# Can unbind \$this on static method:\n"; +try { + $g->bindTo(null)(1); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +# Can be rebound to $this of the same class: +object(C)#%d (0) { +} +# Can be rebound to $this of a sub-class: +object(SubClass)#%d (0) { +} +# Cannot be rebound to an unrelated class: + +Warning: Cannot bind method C::{closure:%s:%d}() to object of class Unrelated, this will be an error in PHP 9 in %s on line %d +Value of type null is not callable +# Cannot unbind $this on instance method: + +Warning: Cannot unbind $this of method, this will be an error in PHP 9 in %s on line %d +Value of type null is not callable +# Can unbind $this on static method: +string(1) "C" diff --git a/Zend/tests/partial_application/rebinding_002.phpt b/Zend/tests/partial_application/rebinding_002.phpt new file mode 100644 index 0000000000000..fca6db08500f2 --- /dev/null +++ b/Zend/tests/partial_application/rebinding_002.phpt @@ -0,0 +1,45 @@ +--TEST-- +PFA scope cannot be rebound +--FILE-- +f(?); +$g = g(?); + +echo "# Can be rebound to the same scope:\n"; +$f->bindTo($c, C::class)(1); + +echo "# Method cannot be rebound to a different scope:\n"; +try { + $f->bindTo($c, SubClass::class)(1); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +echo "# Function cannot be refound to a different scope:\n"; +try { + $g->bindTo($c, SubClass::class)(1); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECTF-- +# Can be rebound to the same scope: +string(1) "C" +# Method cannot be rebound to a different scope: + +Warning: Cannot rebind scope of closure created from method, this will be an error in PHP 9 in %s on line %d +Value of type null is not callable +# Function cannot be refound to a different scope: + +Warning: Cannot bind an instance to a static closure, this will be an error in PHP 9 in %s on line %d +Value of type null is not callable diff --git a/Zend/tests/partial_application/rebinding_003.phpt b/Zend/tests/partial_application/rebinding_003.phpt new file mode 100644 index 0000000000000..7b8a29231c32c --- /dev/null +++ b/Zend/tests/partial_application/rebinding_003.phpt @@ -0,0 +1,65 @@ +--TEST-- +Rebinding PFA of Closure rebinds inner Closure +--FILE-- +f(?); +$g = $c->g(?); + +echo "# Can be rebound to \$this of the same class:\n"; +$f->bindTo(new C)($c); + +echo "# Can be rebound to \$this of a sub-class:\n"; +$f->bindTo(new SubClass)($c); + +echo "# Cannot be rebound to an unrelated class:\n"; +try { + $f->bindTo(new Unrelated)($c); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +echo "# Cannot unbind \$this on instance method:\n"; +try { + $f->bindTo(null)($c); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +echo "# Can unbind \$this on static method:\n"; +try { + $g->bindTo(null)($c); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +# Can be rebound to $this of the same class: +object(C)#%d (0) { +} +bool(false) +# Can be rebound to $this of a sub-class: +object(SubClass)#%d (0) { +} +bool(false) +# Cannot be rebound to an unrelated class: + +Warning: Cannot bind method C::{closure:%s:%d}() to object of class Unrelated, this will be an error in PHP 9 in %s on line %d +Value of type null is not callable +# Cannot unbind $this on instance method: + +Warning: Cannot unbind $this of method, this will be an error in PHP 9 in %s on line %d +Value of type null is not callable +# Can unbind $this on static method: +string(1) "C" diff --git a/Zend/tests/partial_application/recorded_warnings.phpt b/Zend/tests/partial_application/recorded_warnings.phpt new file mode 100644 index 0000000000000..f25be826f5250 --- /dev/null +++ b/Zend/tests/partial_application/recorded_warnings.phpt @@ -0,0 +1,14 @@ +--TEST-- +PFA compilation warnings are recorded and replayed +--FILE-- + +--EXPECTF-- +Deprecated: Using "_" as a type name is deprecated since 8.4 in %s on line 3 + +Deprecated: Using "_" as a type name is deprecated since 8.4 in %s on line 5 diff --git a/Zend/tests/partial_application/references_001.phpt b/Zend/tests/partial_application/references_001.phpt new file mode 100644 index 0000000000000..4a6a8663d8708 --- /dev/null +++ b/Zend/tests/partial_application/references_001.phpt @@ -0,0 +1,24 @@ +--TEST-- +PFA returns by val if the actual function does +--FILE-- + +--EXPECT-- +array(1) { + [0]=> + &string(49) "unchanged because foo() doesn't take by reference" +} +string(49) "unchanged because foo() doesn't take by reference" diff --git a/Zend/tests/partial_application/references_002.phpt b/Zend/tests/partial_application/references_002.phpt new file mode 100644 index 0000000000000..c203b4fb907af --- /dev/null +++ b/Zend/tests/partial_application/references_002.phpt @@ -0,0 +1,24 @@ +--TEST-- +PFA receives by ref if the actual function does +--FILE-- + +--EXPECT-- +array(1) { + [0]=> + &int(2) +} +int(2) diff --git a/Zend/tests/partial_application/references_003.phpt b/Zend/tests/partial_application/references_003.phpt new file mode 100644 index 0000000000000..be116b06c79f5 --- /dev/null +++ b/Zend/tests/partial_application/references_003.phpt @@ -0,0 +1,20 @@ +--TEST-- +PFA receives by ref if the actual function does +--FILE-- +getMessage(), "\n"; +} + +?> +--EXPECTF-- +{closure:%s}(): Argument #1 ($b) could not be passed by reference diff --git a/Zend/tests/partial_application/references_004.phpt b/Zend/tests/partial_application/references_004.phpt new file mode 100644 index 0000000000000..d3b85856b37e1 --- /dev/null +++ b/Zend/tests/partial_application/references_004.phpt @@ -0,0 +1,42 @@ +--TEST-- +PFA receives variadic param by ref if the actual function does +--FILE-- + +--EXPECTF-- +Closure [ static function {closure:%s:%d} ] { + @@ %sreferences_004.php 13 - 13 + + - Bound Variables [2] { + Variable #0 [ $a ] + Variable #1 [ $args0 ] + } + + - Parameters [2] { + Parameter #0 [ &$args1 ] + Parameter #1 [ &...$args ] + } +} + +int(-2) +int(-3) +int(-4) diff --git a/Zend/tests/partial_application/references_005.phpt b/Zend/tests/partial_application/references_005.phpt new file mode 100644 index 0000000000000..e8c7c27a07be6 --- /dev/null +++ b/Zend/tests/partial_application/references_005.phpt @@ -0,0 +1,26 @@ +--TEST-- +PFA inherits return by ref +--FILE-- + +--EXPECT-- +array(1) { + [0]=> + int(1) +} +array(1) { + [0]=> + int(1) +} diff --git a/Zend/tests/partial_application/reflection_001.phpt b/Zend/tests/partial_application/reflection_001.phpt new file mode 100644 index 0000000000000..4ee997f562db1 --- /dev/null +++ b/Zend/tests/partial_application/reflection_001.phpt @@ -0,0 +1,48 @@ +--TEST-- +PFA reflection: required parameters +--FILE-- + +--EXPECTF-- +Closure [ static function {closure:%s:%d} ] { + @@ %sreflection_001.php 6 - 6 + + - Parameters [3] { + Parameter #0 [ $a = 1 ] + Parameter #1 [ $b = 5 ] + Parameter #2 [ $c = 10 ] + } +} +Closure [ static function {closure:%s:%d} ] { + @@ %sreflection_001.php 10 - 10 + + - Parameters [3] { + Parameter #0 [ $a = 1 ] + Parameter #1 [ $b = 5 ] + Parameter #2 [ $c = 10 ] + } +} +Closure [ static function {closure:%s:%d} ] { + @@ %sreflection_001.php 14 - 14 + + - Parameters [3] { + Parameter #0 [ $a = 1 ] + Parameter #1 [ $b = 5 ] + Parameter #2 [ $c = 10 ] + } +} diff --git a/Zend/tests/partial_application/reflection_002.phpt b/Zend/tests/partial_application/reflection_002.phpt new file mode 100644 index 0000000000000..da91a7af50cfe --- /dev/null +++ b/Zend/tests/partial_application/reflection_002.phpt @@ -0,0 +1,57 @@ +--TEST-- +PFA reflection: variadics +--FILE-- + +--EXPECTF-- +Closure [ static function {closure:%s:%d} ] { + @@ %s 6 - 6 + + - Parameters [1] { + Parameter #0 [ $a ] + } +} +Closure [ static function {closure:%s:%d} ] { + @@ %s 10 - 10 + + - Parameters [2] { + Parameter #0 [ $a ] + Parameter #1 [ ...$b ] + } +} +Closure [ static function {closure:%s:%d} ] { + @@ %s 14 - 14 + + - Parameters [2] { + Parameter #0 [ $a ] + Parameter #1 [ $b0 ] + } +} +Closure [ static function {closure:%s:%d} ] { + @@ %s 18 - 18 + + - Parameters [3] { + Parameter #0 [ $a ] + Parameter #1 [ $b0 ] + Parameter #2 [ $b1 ] + } +} diff --git a/Zend/tests/partial_application/reflection_003.phpt b/Zend/tests/partial_application/reflection_003.phpt new file mode 100644 index 0000000000000..90506d38a778a --- /dev/null +++ b/Zend/tests/partial_application/reflection_003.phpt @@ -0,0 +1,43 @@ +--TEST-- +PFA reflection: internal with variadics +--FILE-- + +--EXPECTF-- +Closure [ static function {closure:%s:%d} ] { + @@ %sreflection_003.php 2 - 2 + + - Parameters [1] { + Parameter #0 [ string $format ] + } + - Return [ string ] +} +Closure [ static function {closure:%s:%d} ] { + @@ %sreflection_003.php 6 - 6 + + - Parameters [2] { + Parameter #0 [ string $format ] + Parameter #1 [ mixed ...$values ] + } + - Return [ string ] +} +Closure [ static function {closure:%s:%d} ] { + @@ %sreflection_003.php 10 - 10 + + - Parameters [2] { + Parameter #0 [ string $format ] + Parameter #1 [ mixed $values0 ] + } + - Return [ string ] +} diff --git a/Zend/tests/partial_application/reflection_004.phpt b/Zend/tests/partial_application/reflection_004.phpt new file mode 100644 index 0000000000000..7226383f5afa0 --- /dev/null +++ b/Zend/tests/partial_application/reflection_004.phpt @@ -0,0 +1,16 @@ +--TEST-- +PFA reflection: ReflectionFunction::isAnonymous() is true for partials +--FILE-- +isAnonymous()); + +var_dump((new ReflectionFunction(function () {}))->isAnonymous()); + +var_dump((new ReflectionFunction(sprintf(?)))->isAnonymous()); + +?> +--EXPECT-- +bool(false) +bool(true) +bool(true) diff --git a/Zend/tests/partial_application/reflection_005.phpt b/Zend/tests/partial_application/reflection_005.phpt new file mode 100644 index 0000000000000..be86270c004c9 --- /dev/null +++ b/Zend/tests/partial_application/reflection_005.phpt @@ -0,0 +1,16 @@ +--TEST-- +PFA reflection: ReflectionFunction::isClosure() is true for partials +--FILE-- +isClosure()); + +var_dump((new ReflectionFunction(function () {}))->isClosure()); + +var_dump((new ReflectionFunction(sprintf(?)))->isClosure()); + +?> +--EXPECT-- +bool(false) +bool(true) +bool(true) diff --git a/Zend/tests/partial_application/relative_return_types.phpt b/Zend/tests/partial_application/relative_return_types.phpt new file mode 100644 index 0000000000000..f34e1889d394e --- /dev/null +++ b/Zend/tests/partial_application/relative_return_types.phpt @@ -0,0 +1,133 @@ +--TEST-- +PFA supports relative return types +--FILE-- + 0) { + trait T { + public function getSelf(object $o): self { + return $o; + } + public function getStatic(object $o): static { + return $o; + } + } +} + +class C { + use T; +} + +class D extends C { +} + +$c = new C; + +$self = $c->getSelf(?); + +echo (string) new ReflectionFunction($self), "\n"; + +var_dump($self($c)); +var_dump($self(new D)); +try { + $self(new stdClass); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +$static = $c->getStatic(?); + +echo (string) new ReflectionFunction($static), "\n"; + +var_dump($static($c)); +var_dump($static(new D)); +try { + $static(new stdClass); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +$d = new D; + +$self = $d->getSelf(?); + +echo (string) new ReflectionFunction($self), "\n"; + +var_dump($self($d)); +var_dump($self(new D)); +try { + $self(new stdClass); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +$static = $d->getStatic(?); + +echo (string) new ReflectionFunction($static), "\n"; + +var_dump($static($d)); +var_dump($static(new D)); +try { + $static(new stdClass); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +Closure [ public method {closure:%s:%d} ] { + @@ %s.php 23 - 23 + + - Parameters [1] { + Parameter #0 [ object $o ] + } + - Return [ self ] +} + +object(C)#%d (0) { +} +object(D)#%d (0) { +} +C::getSelf(): Return value must be of type C, stdClass returned +Closure [ public method {closure:%s:%d} ] { + @@ %s.php 35 - 35 + + - Parameters [1] { + Parameter #0 [ object $o ] + } + - Return [ static ] +} + +object(C)#%d (0) { +} +object(D)#%d (0) { +} +C::getStatic(): Return value must be of type C, stdClass returned +Closure [ public method {closure:%s:%d} ] { + @@ %s.php 49 - 49 + + - Parameters [1] { + Parameter #0 [ object $o ] + } + - Return [ self ] +} + +object(D)#%d (0) { +} +object(D)#%d (0) { +} +C::getSelf(): Return value must be of type C, stdClass returned +Closure [ public method {closure:%s:%d} ] { + @@ %s.php 61 - 61 + + - Parameters [1] { + Parameter #0 [ object $o ] + } + - Return [ static ] +} + +object(D)#%d (0) { +} +object(D)#%d (0) { +} +C::getStatic(): Return value must be of type D, stdClass returned diff --git a/Zend/tests/partial_application/return_type.phpt b/Zend/tests/partial_application/return_type.phpt new file mode 100644 index 0000000000000..ae3738e2c660c --- /dev/null +++ b/Zend/tests/partial_application/return_type.phpt @@ -0,0 +1,20 @@ +--TEST-- +PFA inherits return type +--FILE-- + +--EXPECTF-- +Closure [ static function {closure:%s:%d} ] { + @@ %s 4 - 4 + + - Bound Variables [1] { + Variable #0 [ $a ] + } + + - Parameters [0] { + } + - Return [ array ] +} diff --git a/Zend/tests/partial_application/rfc_examples.inc b/Zend/tests/partial_application/rfc_examples.inc new file mode 100644 index 0000000000000..f6e7d9f6266dd --- /dev/null +++ b/Zend/tests/partial_application/rfc_examples.inc @@ -0,0 +1,75 @@ + [$pfa, $closure]) { + echo "# ", $test, ": "; + $pfaReflector = new ReflectionFunction($pfa); + $closureReflector = new ReflectionFunction($closure); + + try { + if (count($pfaReflector->getParameters()) !== count($closureReflector->getParameters())) { + throw new Exception(sprintf( + "Arity does not match: expected %d, got %d", + count($closureReflector->getParameters()), + count($pfaReflector->getParameters()), + )); + } + + $it = new MultipleIterator(); + $it->attachIterator(new ArrayIterator($pfaReflector->getParameters())); + $it->attachIterator(new ArrayIterator($closureReflector->getParameters())); + foreach ($it as $i => [$pfaParam, $closureParam]) { + [$i] = $i; + if ($pfaParam->getName() !== $closureParam->getName()) { + throw new Exception(sprintf("Name of param %d does not match: %s vs %s", + $i, + $pfaParam->getName(), + $closureParam->getName(), + )); + } + if ((string)$pfaParam->getType() !== (string)$closureParam->getType()) { + throw new Exception(sprintf("Type of param %d does not match: %s vs %s", + $i, + $pfaParam->getType(), + $closureParam->getType(), + )); + } + if ($pfaParam->isOptional() !== $closureParam->isOptional()) { + throw new Exception(sprintf("Optionalness of param %d does not match: %d vs %d", + $i, + $pfaParam->isOptional(), + $closureParam->isOptional(), + )); + } + } + + $args = []; + foreach ($pfaReflector->getParameters() as $i => $p) { + $args[] = match ((string) $p->getType()) { + 'int' => 100 + $i, + 'float' => 100.5 + $i, + '?float' => 100.5 + $i, + 'string' => (string) (100 + $i), + 'Point' => new Point, + '' => "mixed($i)", + }; + } + + if ($pfaReflector->getClosureThis() !== $closureReflector->getClosureThis()) { + throw new Exception("\$this differs"); + } + + if ($pfa(...$args) !== $closure(...$args)) { + throw new Exception("PFA is not equivalent to closure"); + } + } catch (Exception $e) { + echo $e->getMessage(), "\n"; + echo $pfaReflector; + echo $closureReflector; + return; + } + + echo "Ok\n"; + } +} diff --git a/Zend/tests/partial_application/rfc_examples_const_expr.phpt b/Zend/tests/partial_application/rfc_examples_const_expr.phpt new file mode 100644 index 0000000000000..e2fc574402565 --- /dev/null +++ b/Zend/tests/partial_application/rfc_examples_const_expr.phpt @@ -0,0 +1,25 @@ +--TEST-- +PFA RFC examples: "Constant expressions" section +--XFAIL-- +PFA in constant expressions not implemented yet +--FILE-- + +==DONE== +--EXPECTF-- +==DONE== diff --git a/Zend/tests/partial_application/rfc_examples_debug.phpt b/Zend/tests/partial_application/rfc_examples_debug.phpt new file mode 100644 index 0000000000000..be06e24229f7d --- /dev/null +++ b/Zend/tests/partial_application/rfc_examples_debug.phpt @@ -0,0 +1,25 @@ +--TEST-- +PFA RFC examples: "Debug output" section +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Exception in %s:%d +Stack trace: +#0 %s(%d): g() +#1 %s(%d): f(1, Object(SensitiveParameterValue), 3) +#2 %s(%d): {closure:pfa:%s}(1, Object(SensitiveParameterValue)) +#3 {main} + thrown in %s on line %d diff --git a/Zend/tests/partial_application/rfc_examples_errors.phpt b/Zend/tests/partial_application/rfc_examples_errors.phpt new file mode 100644 index 0000000000000..21818d7446e33 --- /dev/null +++ b/Zend/tests/partial_application/rfc_examples_errors.phpt @@ -0,0 +1,34 @@ +--TEST-- +PFA RFC examples: "Error examples" section +--FILE-- +getMessage(), "\n"; +} + +try { + stuff(?, ?, ?, ?, ?, ?); +} catch (Error $e) { + echo $e::class, ": ", $e->getMessage(), "\n"; +} + +try { + stuff(?, ?, 3.5, $point, i: 5); +} catch (Error $e) { + echo $e::class, ": ", $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +ArgumentCountError: Partial application of stuff() expects at least 4 arguments, 1 given +ArgumentCountError: Partial application of stuff() expects at most 5 arguments, 6 given +Error: Named parameter $i overwrites previous placeholder diff --git a/Zend/tests/partial_application/rfc_examples_eval_order.phpt b/Zend/tests/partial_application/rfc_examples_eval_order.phpt new file mode 100644 index 0000000000000..b54ce0ce15e42 --- /dev/null +++ b/Zend/tests/partial_application/rfc_examples_eval_order.phpt @@ -0,0 +1,30 @@ +--TEST-- +PFA RFC examples: "Evaluation order" section +--FILE-- + speak($who, getArg()); +print "Arnaud\n"; +$arrow('Larry'); + +$partial = speak(?, getArg()); +print "Arnaud\n"; +$partial('Larry'); + +?> +--EXPECT-- +Arnaud +getArg +Larry: hi +getArg +Arnaud +Larry: hi diff --git a/Zend/tests/partial_application/rfc_examples_extra_args.phpt b/Zend/tests/partial_application/rfc_examples_extra_args.phpt new file mode 100644 index 0000000000000..f6a24df8cc713 --- /dev/null +++ b/Zend/tests/partial_application/rfc_examples_extra_args.phpt @@ -0,0 +1,54 @@ +--TEST-- +PFA RFC examples: "Variadics, func_get_args(), and extraneous arguments" section +--FILE-- + [ + foo(1, ?), + static function (int $j) { return foo(1, $j); }, + ], + 'If a PFA call has a ... placeholder, then any extraneous arguments will be passed through to the underlying function' => [ + foo(1, ?, ...), + static function (int $j) { return foo(1, $j, ...array_slice(func_get_args(), 1)); }, + ], + 'If a PFA call has a ... placeholder and the underlying function is variadic, then the trailing arguments will be forwarded directly but will get “collected” by the variadic parameter as normal' => [ + foo2(1, ?, ...), + static function (int $j, ...$extra) { return foo2(1, $j, ...$extra); }, + ], +]; + +check_equivalence($tests); + +echo "# The extra parameter here will be passed to the closure object, which will simply ignore it:\n"; +var_dump(foo(1, ?)(4, 'ignore me')); + +echo "# The extra parameter here will be passed to the closure object, which will forward it directly to the underlying function. It will be accessible only via ''func_get_args()'' et al:\n"; +var_dump(foo(1, ?, ...)(4, 'ignore me')); + +echo "# The extra parameter here will be passed to the closure object, which will forward it directly to the underlying function. It will show up as part of the \$extra array:\n"; +var_dump(foo2(1, ?, ...)(4, 'ignore me')); + +?> +==DONE== +--EXPECT-- +# If a PFA call has no ... placeholder, then any extraneous arguments to the resulting closure will be ignored. That is consistent with how manually writing the equivalent closure would behave, and is the same regardless of whether the underlying function is variadic: Ok +# If a PFA call has a ... placeholder, then any extraneous arguments will be passed through to the underlying function: Ok +# If a PFA call has a ... placeholder and the underlying function is variadic, then the trailing arguments will be forwarded directly but will get “collected” by the variadic parameter as normal: Ok +# The extra parameter here will be passed to the closure object, which will simply ignore it: +int(2) +# The extra parameter here will be passed to the closure object, which will forward it directly to the underlying function. It will be accessible only via ''func_get_args()'' et al: +int(3) +# The extra parameter here will be passed to the closure object, which will forward it directly to the underlying function. It will show up as part of the $extra array: +int(3) +==DONE== diff --git a/Zend/tests/partial_application/rfc_examples_incompatible_functions.phpt b/Zend/tests/partial_application/rfc_examples_incompatible_functions.phpt new file mode 100644 index 0000000000000..2455252b3eba9 --- /dev/null +++ b/Zend/tests/partial_application/rfc_examples_incompatible_functions.phpt @@ -0,0 +1,14 @@ +--TEST-- +PFA RFC examples: "Incompatible functions" section +--FILE-- +getMessage(), "\n"; +} + +?> +--EXPECT-- +Error: Cannot call func_get_args() dynamically diff --git a/Zend/tests/partial_application/rfc_examples_magic_methods.phpt b/Zend/tests/partial_application/rfc_examples_magic_methods.phpt new file mode 100644 index 0000000000000..bf3ce3404e662 --- /dev/null +++ b/Zend/tests/partial_application/rfc_examples_magic_methods.phpt @@ -0,0 +1,47 @@ +--TEST-- +PFA RFC examples: "Magic methods" section +--FILE-- + [ + $f->method(?, ?), + (function ($f) { + return fn($args0, $args1) => $f->method($args0, $args1); + })($f)->bindTo($f), + ], +]); + +try { + $f->method(?, ?)(a: 1, b: 2); +} catch (Error $e) { + echo $e::class, ": ", $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +# Test 1: Foo::method +Array +( + [0] => mixed(0) + [1] => mixed(1) +) +Foo::method +Array +( + [0] => mixed(0) + [1] => mixed(1) +) +Ok +Error: Unknown named parameter $a diff --git a/Zend/tests/partial_application/rfc_examples_overview.phpt b/Zend/tests/partial_application/rfc_examples_overview.phpt new file mode 100644 index 0000000000000..b6fc8c0586c2f --- /dev/null +++ b/Zend/tests/partial_application/rfc_examples_overview.phpt @@ -0,0 +1,44 @@ +--TEST-- +PFA RFC examples: "Overview" section +--FILE-- + [ + foo(1, ?, 3, 4), + static fn(int $b): int => foo(1, $b, 3, 4), + ], + 'Test 2' => [ + foo(1, ?, 3, ?), + static fn(int $b, int $d): int => foo(1, $b, 3, $d), + ], + 'Test 3' => [ + foo(1, ...), + static fn(int $b, int $c, int $d): int => foo(1, $b, $c, $d), + ], + 'Test 4' => [ + foo(1, 2, ...), + static fn(int $c, int $d): int => foo(1, 2, $c, $d), + ], + 'Test 5' => [ + foo(1, ?, 3, ...), + static fn(int $b, int $d): int => foo(1, $b, 3, $d), + ], +]; + +check_equivalence($tests); + +?> +--EXPECT-- +# Test 1: Ok +# Test 2: Ok +# Test 3: Ok +# Test 4: Ok +# Test 5: Ok diff --git a/Zend/tests/partial_application/rfc_examples_scoping.phpt b/Zend/tests/partial_application/rfc_examples_scoping.phpt new file mode 100644 index 0000000000000..071b4a5a5a398 --- /dev/null +++ b/Zend/tests/partial_application/rfc_examples_scoping.phpt @@ -0,0 +1,113 @@ +--TEST-- +PFA RFC examples: "Scoping" section +--FILE-- + [ + foo(?, ?), + static fn(int $i, int $j = 0): string => foo($i, $j), + ], + 'Static closure 2' => [ + Foo::bar(?, ?), + static fn(int $i, int $j): string => Foo::bar($i, $j), + ], + 'Static closure 3' => [ + foo(?, ?)(1, ?), + static fn(int $j = 0): string => foo(1, $j), + ], + 'Static closure 4' => [ + foo(...)(?), + static fn(int $i): string => foo($i, 0), + ], +]; + +check_equivalence($tests); + +$c = new C(); +$f = $c->f(?); + +echo "# Cannot unbind \$this:\n"; +var_dump($f->bindTo(null, C::class)); // Warning: Cannot unbind $this of method, this will be an error in PHP 9 (returns null) + +echo "# Cannot rebind scope:\n"; +var_dump($f->bindTo($c, CSubClass::class)); // Warning: Cannot rebind scope of closure created from method, this will be an error in PHP 9 (returns null) + +echo "# Can rebind \$this with subclass:\n"; +var_dump($f->bindTo(new CSubClass, C::class)); // Allowed + +echo "# Cannot rebind \$this with unrelated class:\n"; +$f = $f->bindTo(new Unrelated, C::class); // Warning: Cannot bind method C::{closure:/path/to/test.php:11}() to object of class Unrelated, this will be an error in PHP 9 (returns null) + +echo "# self resolution:\n"; +$c = new CSubClass(); +var_dump($c->f(?)(1)); // string(1) "C" +var_dump($c->g(?)(1)); // string(9) "CSubClass" +var_dump($c->h(1)(1)); // string(1) "C" + +?> +--EXPECTF-- +# Static closure 1: Ok +# Static closure 2: Ok +# Static closure 3: Ok +# Static closure 4: Ok +# Cannot unbind $this: + +Warning: Cannot unbind $this of method, this will be an error in PHP 9 in %s on line %d +NULL +# Cannot rebind scope: + +Warning: Cannot rebind scope of closure created from method, this will be an error in PHP 9 in %s on line %d +NULL +# Can rebind $this with subclass: +object(Closure)#%d (5) { + ["name"]=> + string(%d) "{closure:pfa:%s}" + ["file"]=> + string(%d) "%s" + ["line"]=> + int(53) + ["this"]=> + object(CSubClass)#%d (0) { + } + ["parameter"]=> + array(1) { + ["$a"]=> + string(10) "" + } +} +# Cannot rebind $this with unrelated class: + +Warning: Cannot bind method C::{closure:pfa:%s}() to object of class Unrelated, this will be an error in PHP 9 in %s on line %d +# self resolution: +string(1) "C" +string(9) "CSubClass" +string(1) "C" diff --git a/Zend/tests/partial_application/rfc_examples_semantics.phpt b/Zend/tests/partial_application/rfc_examples_semantics.phpt new file mode 100644 index 0000000000000..a3ea25c30d6d5 --- /dev/null +++ b/Zend/tests/partial_application/rfc_examples_semantics.phpt @@ -0,0 +1,30 @@ +--TEST-- +PFA RFC examples: "Placeholder Semantics" section +--FILE-- + [ + foo(?, ?, ?, ?), + static fn(int $a, int $b, string $c0, string $c1) => foo($a, $b, $c0, $c1), + ], + 'Test 2' => [ + stuff(1, ?, p: ?, f: 3.14, ...), + static fn(string $s, Point $p, int $m = 0) => stuff(1, $s, 3.14, $p, $m), + ], +]; + +check_equivalence($tests); + +?> +--EXPECT-- +# Test 1: Ok +# Test 2: Ok diff --git a/Zend/tests/partial_application/rfc_examples_semantics_examples.phpt b/Zend/tests/partial_application/rfc_examples_semantics_examples.phpt new file mode 100644 index 0000000000000..9ee52d2786897 --- /dev/null +++ b/Zend/tests/partial_application/rfc_examples_semantics_examples.phpt @@ -0,0 +1,217 @@ +--TEST-- +PFA RFC examples: "Placeholder Semantics: Examples" section +--FILE-- + [ + stuff(?, ?, ...), + static fn(int $i1, string $s2, float $f3, Point $p4, int $m5 = 0): array + => stuff($i1, $s2, $f3, $p4, $m5), + ], + 'The degenerate "first class callables" case. (Supported since 8.1)' => [ + stuff(...), + static fn(int $i1, string $s2, float $f3, Point $p4, int $m5 = 0): array + => stuff($i1, $s2, $f3, $p4, $m5), + ], + 'Provide some values, require the rest to be provided later (1)' => [ + stuff(1, 'hi', ?, ?, ?), + static fn(float $f3, Point $p4, int $m5 = 0): array => stuff(1, 'hi', $f3, $p4, $m5), + ], + 'Provide some values, require the rest to be provided later (2)' => [ + stuff(1, 'hi', ...), + static fn(float $f3, Point $p4, int $m5 = 0): array => stuff(1, 'hi', $f3, $p4, $m5), + ], + 'Provide some values, but not just from the left (1)' => [ + stuff(1, ?, 3.5, ?, ?), + static fn(string $s2, Point $p4, int $m5 = 0): array => stuff(1, $s2, 3.5, $p4, $m5), + ], + 'Provide some values, but not just from the left (2)' => [ + stuff(1, ?, 3.5, ...), + static fn(string $s2, Point $p4, int $m5 = 0): array => stuff(1, $s2, 3.5, $p4, $m5), + ], + 'Provide just the last value' => [ + stuff(?, ?, ?, ?, 5), + static fn(int $i1, string $s2, float $f3, Point $p4): array + => stuff($i1, $s2, $f3, $p4, 5), + ], + 'Not accounting for an optional argument means it will always get its default value' => [ + stuff(?, ?, ?, ?), + static fn(int $i1, string $s2, float $f3, Point $p4): array + => stuff($i1, $s2, $f3, $p4), + ], + 'Named arguments can be pulled "out of order", and still work (1)' => [ + stuff(?, ?, f3: 3.5, p4: $point), + static fn(int $i1, string $s2): array => stuff($i1, $s2, 3.5, $point), + ], + 'Named arguments can be pulled "out of order", and still work (2)' => [ + stuff(?, ?, p4: $point, f3: 3.5), + static fn(int $i1, string $s2): array => stuff($i1, $s2, 3.5, $point), + ], + + 'But named placeholders adopt the order listed' => [ + stuff(s2: ?, i1: ?, p4: ?, f3: 3.5), + static fn(string $s2, int $i1, Point $p4): array => stuff($i1, $s2, 3.5, $p4), + ], + 'The ... "everything else" placeholder respects named arguments' => [ + stuff(?, ?, f3: 3.5, p4: $point, ...), + static fn(int $i1, string $s2, int $m5 = 0): array => stuff($i1, $s2, 3.5, $point, $m5), + ], + 'Prefill all parameters, making a "delayed call" or "thunk"' => [ + stuff(1, 'hi', 3.4, $point, 5, ...), + static fn(): array => stuff(1, 'hi', 3.4, $point, 5), + ], + + // Variadics + + 'FCC equivalent. The signature is unchanged' => [ + things(...), + static fn(int $i1, ?float $f3 = null, Point ...$points): array => things(...[$i1, $f3, ...$points]), + ], + 'Provide some values, but allow the variadic to remain variadic' => [ + things(1, 3.14, ...), + static fn(Point ...$points): array => things(1, 3.14, ...$points), + ], + 'In this version, the partial requires precisely four arguments, the last two of which will get received by things() in the variadic parameter. Note too that $f becomes required in this case' => [ + things(?, ?, ?, ?), + static fn(int $i1, ?float $f3, Point $points0, Point $points1): array => things($i1, $f3, $points0, $points1), + ], + + // Esoteric examples + + 'Esoteric 1' => [ + four(...), + static fn(int $a, int $b, int $c, int $d): string => four($a, $b, $c, $d), + ], + 'Esoteric 2' => [ + four(1, 2, ...), + static fn(int $c, int $d): string => four(1, 2, $c, $d), + ], + 'Esoteric 3' => [ + four(1, 2, 3, ?), + static fn(int $d): string => four(1, 2, 3, $d), + ], + 'Esoteric 4' => [ + four(1, ?, ?, 4), + static fn(int $b, int $c): string => four(1, $b, $c, 4), + ], + 'Esoteric 5' => [ + four(1, 2, 3, 4, ...), + static fn(): string => four(1, 2, 3, 4, ...array_slice(func_get_args(), 4)), + ], + 'Esoteric 6' => [ + four(d: 4, a: 1, ...), + static fn(int $b, int $c): string => four(1, $b, $c, 4, ...array_slice(func_get_args(), 4)), + ], + 'Esoteric 7' => [ + four(c: ?, d: 4, b: ?, a: 1), + static fn(int $c, int $b): string => four(1, $b, $c, 4, ...array_slice(func_get_args(), 4)), + ], + + // Other callable styles + + 'This is allowed. Note the method is static, thus the partial closure is static' => [ + E::make(1, ?), + static fn(int $y): E => E::make(1, $y), + ], + 'Note the method is non-static, so the partial closure is non-static' => (function () { + $eMaker = E::make(1, ?); + $e = $eMaker(2); + return [ + $e->foo(?, ?, 3), + (function ($e) { + return fn(int $a, int $b): array => $e->foo($a, $b, 3); + })($e)->bindTo($e), + ]; + })(), + '$c can then be further refined' => (function () { + $eMaker = E::make(1, ?); + $e = $eMaker(2); + $c = $e->foo(?, ?, 3); + return [ + $c(1, ?), + (function ($e) { + return fn(int $b): array => $e->foo(1, $b, 3); + })($e)->bindTo($e), + ]; + })(), + 'RunMe' => (function () { + $r = new RunMe(); + return [ + $r(?, 3), + (function ($r) { + return fn(int $a): string => $r($a, 3); + })($r)->bindTo($r), + ]; + })(), +]; + +check_equivalence($tests); + +?> +--EXPECT-- +# Manually specify the first two values, and pull the rest "as is": Ok +# The degenerate "first class callables" case. (Supported since 8.1): Ok +# Provide some values, require the rest to be provided later (1): Ok +# Provide some values, require the rest to be provided later (2): Ok +# Provide some values, but not just from the left (1): Ok +# Provide some values, but not just from the left (2): Ok +# Provide just the last value: Ok +# Not accounting for an optional argument means it will always get its default value: Ok +# Named arguments can be pulled "out of order", and still work (1): Ok +# Named arguments can be pulled "out of order", and still work (2): Ok +# But named placeholders adopt the order listed: Ok +# The ... "everything else" placeholder respects named arguments: Ok +# Prefill all parameters, making a "delayed call" or "thunk": Ok +# FCC equivalent. The signature is unchanged: Ok +# Provide some values, but allow the variadic to remain variadic: Ok +# In this version, the partial requires precisely four arguments, the last two of which will get received by things() in the variadic parameter. Note too that $f becomes required in this case: Ok +# Esoteric 1: Ok +# Esoteric 2: Ok +# Esoteric 3: Ok +# Esoteric 4: Ok +# Esoteric 5: Ok +# Esoteric 6: Ok +# Esoteric 7: Ok +# This is allowed. Note the method is static, thus the partial closure is static: Ok +# Note the method is non-static, so the partial closure is non-static: Ok +# $c can then be further refined: Ok +# RunMe: Ok diff --git a/Zend/tests/partial_application/static_method_001.phpt b/Zend/tests/partial_application/static_method_001.phpt new file mode 100644 index 0000000000000..ce4151441c336 --- /dev/null +++ b/Zend/tests/partial_application/static_method_001.phpt @@ -0,0 +1,18 @@ +--TEST-- +PFA supports static methods +--FILE-- + +--EXPECTF-- +Foo::method diff --git a/Zend/tests/partial_application/statics_001.phpt b/Zend/tests/partial_application/statics_001.phpt new file mode 100644 index 0000000000000..906c0dedf121c --- /dev/null +++ b/Zend/tests/partial_application/statics_001.phpt @@ -0,0 +1,22 @@ +--TEST-- +PFA static variables are shared (001) +--FILE-- + +--EXPECTF-- +OK diff --git a/Zend/tests/partial_application/statics_002.phpt b/Zend/tests/partial_application/statics_002.phpt new file mode 100644 index 0000000000000..8e1b6cefe00ae --- /dev/null +++ b/Zend/tests/partial_application/statics_002.phpt @@ -0,0 +1,23 @@ +--TEST-- +PFA static variables are shared (002) +--FILE-- + +--EXPECT-- +OK diff --git a/Zend/tests/partial_application/statics_003.phpt b/Zend/tests/partial_application/statics_003.phpt new file mode 100644 index 0000000000000..9fb2568a67e78 --- /dev/null +++ b/Zend/tests/partial_application/statics_003.phpt @@ -0,0 +1,26 @@ +--TEST-- +PFA static variables are shared (003) +--FILE-- + +--EXPECT-- +OK diff --git a/Zend/tests/partial_application/superfluous_args_are_forwarded.phpt b/Zend/tests/partial_application/superfluous_args_are_forwarded.phpt new file mode 100644 index 0000000000000..506092655217e --- /dev/null +++ b/Zend/tests/partial_application/superfluous_args_are_forwarded.phpt @@ -0,0 +1,44 @@ +--TEST-- +PFAs forwards superfluous args iff a variadic placeholder is specified +--FILE-- + +--EXPECT-- +array(3) { + [0]=> + int(1) + [1]=> + int(2) + [2]=> + int(3) +} +array(1) { + [0]=> + int(1) +} +array(3) { + [0]=> + int(1) + [1]=> + int(2) + [2]=> + int(3) +} diff --git a/Zend/tests/partial_application/this.phpt b/Zend/tests/partial_application/this.phpt new file mode 100644 index 0000000000000..bda6900bae398 --- /dev/null +++ b/Zend/tests/partial_application/this.phpt @@ -0,0 +1,20 @@ +--TEST-- +PFA $this +--FILE-- +method(new stdClass, ...); + +$baz = $bar(new stdClass, ...); + +var_dump($foo === $baz()); +?> +--EXPECT-- +bool(true) diff --git a/Zend/tests/partial_application/variation_call_001.phpt b/Zend/tests/partial_application/variation_call_001.phpt new file mode 100644 index 0000000000000..72bccd1292b36 --- /dev/null +++ b/Zend/tests/partial_application/variation_call_001.phpt @@ -0,0 +1,31 @@ +--TEST-- +PFA variation: call +--FILE-- +method(?, new Param); + +$closure(1); + +$closure->call(new Foo(), 10); +?> +--EXPECT-- +Bar: 1, Param +Foo: 10, Param diff --git a/Zend/tests/partial_application/variation_closure_001.phpt b/Zend/tests/partial_application/variation_closure_001.phpt new file mode 100644 index 0000000000000..c84f8d05f38c6 --- /dev/null +++ b/Zend/tests/partial_application/variation_closure_001.phpt @@ -0,0 +1,23 @@ +--TEST-- +PFA variation: Closure +--FILE-- + +--EXPECTF-- +Closure [ static function {closure:%s:%d} ] { + @@ %s 6 - 6 + + - Bound Variables [2] { + Variable #0 [ $fn ] + Variable #1 [ $a ] + } + + - Parameters [1] { + Parameter #0 [ $b ] + } +} diff --git a/Zend/tests/partial_application/variation_closure_002.phpt b/Zend/tests/partial_application/variation_closure_002.phpt new file mode 100644 index 0000000000000..41abe0aaab0eb --- /dev/null +++ b/Zend/tests/partial_application/variation_closure_002.phpt @@ -0,0 +1,28 @@ +--TEST-- +PFA variation: Closure::__invoke() +--FILE-- +__invoke(1, ?); + +echo (string) new ReflectionFunction($function); + +$function(10); +?> +--EXPECTF-- +Closure [ public method {closure:%s:%d} ] { + @@ %svariation_closure_002.php 6 - 6 + + - Bound Variables [1] { + Variable #0 [ $a ] + } + + - Parameters [1] { + Parameter #0 [ $b ] + } +} +int(1) +int(10) diff --git a/Zend/tests/partial_application/variation_closure_003.phpt b/Zend/tests/partial_application/variation_closure_003.phpt new file mode 100644 index 0000000000000..da567e179853c --- /dev/null +++ b/Zend/tests/partial_application/variation_closure_003.phpt @@ -0,0 +1,46 @@ +--TEST-- +PFA variation: Closure::__invoke() with $this +--FILE-- +bar(); + +$function = $closure->__invoke(1, ?); + +echo (string) new ReflectionFunction($function); + +var_dump($function(10)); +?> +--EXPECTF-- +Closure [ public method {closure:%s:%d} ] { + @@ %svariation_closure_003.php 14 - 14 + + - Bound Variables [1] { + Variable #0 [ $a ] + } + + - Parameters [1] { + Parameter #0 [ $b ] + } +} +array(2) { + [0]=> + object(Foo)#1 (0) { + } + [1]=> + array(2) { + [0]=> + int(1) + [1]=> + int(10) + } +} diff --git a/Zend/tests/partial_application/variation_debug_001.phpt b/Zend/tests/partial_application/variation_debug_001.phpt new file mode 100644 index 0000000000000..d2f6458634e90 --- /dev/null +++ b/Zend/tests/partial_application/variation_debug_001.phpt @@ -0,0 +1,40 @@ +--TEST-- +PFA variation: var_dump(), user function +--FILE-- + +--EXPECTF-- +object(Closure)#%d (5) { + ["name"]=> + string(%d) "{closure:%s}" + ["file"]=> + string(%d) "%svariation_debug_001.php" + ["line"]=> + int(6) + ["static"]=> + array(4) { + ["b"]=> + object(stdClass)#%d (0) { + } + ["c"]=> + int(20) + ["c0"]=> + object(stdClass)#%d (0) { + } + ["extra_named_params"]=> + array(1) { + ["four"]=> + int(4) + } + } + ["parameter"]=> + array(1) { + ["$a"]=> + string(10) "" + } +} diff --git a/Zend/tests/partial_application/variation_debug_002.phpt b/Zend/tests/partial_application/variation_debug_002.phpt new file mode 100644 index 0000000000000..46f80b8b2726e --- /dev/null +++ b/Zend/tests/partial_application/variation_debug_002.phpt @@ -0,0 +1,49 @@ +--TEST-- +PFA variation: var_dump(), internal function +--FILE-- + +--EXPECTF-- +object(Closure)#%d (5) { + ["name"]=> + string(%d) "{closure:%s}" + ["file"]=> + string(%d) "%svariation_debug_002.php" + ["line"]=> + int(2) + ["static"]=> + array(3) { + ["array"]=> + array(3) { + [0]=> + int(1) + [1]=> + int(2) + [2]=> + int(3) + } + ["arrays0"]=> + array(3) { + [0]=> + int(4) + [1]=> + int(5) + [2]=> + int(6) + } + ["extra_named_params"]=> + array(1) { + ["four"]=> + object(stdClass)#%d (0) { + } + } + } + ["parameter"]=> + array(2) { + ["$callback"]=> + string(10) "" + ["$arrays"]=> + string(10) "" + } +} diff --git a/Zend/tests/partial_application/variation_ex_001.phpt b/Zend/tests/partial_application/variation_ex_001.phpt new file mode 100644 index 0000000000000..48db63e70a5ed --- /dev/null +++ b/Zend/tests/partial_application/variation_ex_001.phpt @@ -0,0 +1,14 @@ +--TEST-- +PFA variation: UAF in cleanup unfinished calls +--FILE-- +getMessage(), "\n"; +} +?> +--EXPECTF-- +ArgumentCountError: Partial application of {closure:%s:%d}() expects at most 0 arguments, 1 given diff --git a/Zend/tests/partial_application/variation_gc_001.phpt b/Zend/tests/partial_application/variation_gc_001.phpt new file mode 100644 index 0000000000000..887156afc7e1e --- /dev/null +++ b/Zend/tests/partial_application/variation_gc_001.phpt @@ -0,0 +1,19 @@ +--TEST-- +PFA variation: GC (001) +--FILE-- +method = self::__construct(new stdClass, ...); + } +} + +$foo = new Foo(new stdClass); +$foo->bar = $foo; + +echo "OK"; +?> +--EXPECT-- +OK diff --git a/Zend/tests/partial_application/variation_gc_002.phpt b/Zend/tests/partial_application/variation_gc_002.phpt new file mode 100644 index 0000000000000..31c721e8e6819 --- /dev/null +++ b/Zend/tests/partial_application/variation_gc_002.phpt @@ -0,0 +1,11 @@ +--TEST-- +PFA variation: GC (002) +--FILE-- +prop = var_dump($obj, ?); + +echo "OK"; +?> +--EXPECT-- +OK diff --git a/Zend/tests/partial_application/variation_gc_003.phpt b/Zend/tests/partial_application/variation_gc_003.phpt new file mode 100644 index 0000000000000..23cdaa1c666ff --- /dev/null +++ b/Zend/tests/partial_application/variation_gc_003.phpt @@ -0,0 +1,15 @@ +--TEST-- +PFA variation: GC (003) +--FILE-- +prop = test(?, x: $obj); + +echo "OK"; +?> +--EXPECT-- +OK diff --git a/Zend/tests/partial_application/variation_invoke_001.phpt b/Zend/tests/partial_application/variation_invoke_001.phpt new file mode 100644 index 0000000000000..a6091e955b156 --- /dev/null +++ b/Zend/tests/partial_application/variation_invoke_001.phpt @@ -0,0 +1,21 @@ +--TEST-- +PFA variation: __invoke() +--FILE-- +__invoke(32) == 42); + +try { + $foo->nothing(); +} catch (Error $ex) { + echo $ex::class, ": ", $ex->getMessage(), "\n"; +} +?> +--EXPECT-- +bool(true) +Error: Call to undefined method Closure::nothing() diff --git a/Zend/tests/partial_application/variation_nocall_001.phpt b/Zend/tests/partial_application/variation_nocall_001.phpt new file mode 100644 index 0000000000000..3fbb3ec8d8c32 --- /dev/null +++ b/Zend/tests/partial_application/variation_nocall_001.phpt @@ -0,0 +1,12 @@ +--TEST-- +PFA variation: no call args leak +--FILE-- + +--EXPECT-- +OK diff --git a/Zend/tests/partial_application/variation_nocall_002.phpt b/Zend/tests/partial_application/variation_nocall_002.phpt new file mode 100644 index 0000000000000..cd4823f1bd056 --- /dev/null +++ b/Zend/tests/partial_application/variation_nocall_002.phpt @@ -0,0 +1,30 @@ +--TEST-- +PFA variation: no call, order of destruction +--FILE-- +id, "\n"; + } +} +$foo = new Foo; +$f = $foo->method(?); +$g = $f(?); + +$map = new WeakMap(); +$map[$f] = new Dtor(1); +$map[$g] = new Dtor(2); + +unset($f); +unset($g); + +echo "OK"; +?> +--EXPECT-- +Dtor::__destruct 2 +Dtor::__destruct 1 +OK diff --git a/Zend/tests/partial_application/variation_parent_001.phpt b/Zend/tests/partial_application/variation_parent_001.phpt new file mode 100644 index 0000000000000..6b7545daf2d02 --- /dev/null +++ b/Zend/tests/partial_application/variation_parent_001.phpt @@ -0,0 +1,75 @@ +--TEST-- +PFA variation: parent +--FILE-- +method(10, ...); +$baz = $bar(20, ...); + +var_dump($baz, $baz()); +?> +--EXPECTF-- +object(Closure)#%d (6) { + ["name"]=> + string(%d) "{closure:%s}" + ["file"]=> + string(%d) "%svariation_parent_001.php" + ["line"]=> + int(12) + ["static"]=> + array(2) { + ["fn"]=> + object(Closure)#%d (6) { + ["name"]=> + string(%d) "{closure:%s:%d}" + ["file"]=> + string(%d) "%s" + ["line"]=> + int(11) + ["static"]=> + array(1) { + ["a"]=> + int(10) + } + ["this"]=> + object(Foo)#%d (0) { + } + ["parameter"]=> + array(2) { + ["$b"]=> + string(10) "" + ["$c"]=> + string(10) "" + } + } + ["b"]=> + int(20) + } + ["this"]=> + object(Foo)#%d (0) { + } + ["parameter"]=> + array(1) { + ["$c"]=> + string(10) "" + } +} +object(Closure)#%d (4) { + ["name"]=> + string(25) "{closure:Foo::method():4}" + ["file"]=> + string(%d) "%s" + ["line"]=> + int(4) + ["this"]=> + object(Foo)#%d (0) { + } +} diff --git a/Zend/tests/partial_application/variation_scope_001.phpt b/Zend/tests/partial_application/variation_scope_001.phpt new file mode 100644 index 0000000000000..0dcea0921c436 --- /dev/null +++ b/Zend/tests/partial_application/variation_scope_001.phpt @@ -0,0 +1,18 @@ +--TEST-- +PFA variation: called scope +--FILE-- +method(new stdClass, ...); + +$bar(); +?> +--EXPECT-- +Foo::method diff --git a/Zend/tests/partial_application/variation_strict_001.phpt b/Zend/tests/partial_application/variation_strict_001.phpt new file mode 100644 index 0000000000000..835f6e1380557 --- /dev/null +++ b/Zend/tests/partial_application/variation_strict_001.phpt @@ -0,0 +1,20 @@ +--TEST-- +PFA variation: strict_types declared +--FILE-- +getMessage()); +} +?> +--EXPECTF-- +TypeError: {closure:%s:%d}(): Argument #1 ($int) must be of type int, string given, called in %s on line %d diff --git a/Zend/tests/partial_application/variation_variadics_001.phpt b/Zend/tests/partial_application/variation_variadics_001.phpt new file mode 100644 index 0000000000000..4710ca4d3b993 --- /dev/null +++ b/Zend/tests/partial_application/variation_variadics_001.phpt @@ -0,0 +1,31 @@ +--TEST-- +PFA variation: variadics, user function +--FILE-- + +--EXPECTF-- +Closure [ static function {closure:%s:%d} ] { + @@ %s 6 - 6 + + - Bound Variables [2] { + Variable #0 [ $a ] + Variable #1 [ $b0 ] + } + + - Parameters [1] { + Parameter #0 [ ...$b ] + } +} +int(10) +int(100) +int(1000) +int(10000) diff --git a/Zend/tests/partial_application/variation_variadics_002.phpt b/Zend/tests/partial_application/variation_variadics_002.phpt new file mode 100644 index 0000000000000..4269dd0e66f0b --- /dev/null +++ b/Zend/tests/partial_application/variation_variadics_002.phpt @@ -0,0 +1,25 @@ +--TEST-- +PFA variation: variadics, internal function +--FILE-- + +--EXPECTF-- +Closure [ static function {closure:%s:%d} ] { + @@ %svariation_variadics_002.php 2 - 2 + + - Bound Variables [2] { + Variable #0 [ $format ] + Variable #1 [ $values0 ] + } + + - Parameters [1] { + Parameter #0 [ mixed ...$values ] + } + - Return [ string ] +} +100 1000 10000 diff --git a/Zend/tests/partial_application/variation_variadics_004.phpt b/Zend/tests/partial_application/variation_variadics_004.phpt new file mode 100644 index 0000000000000..55ef6cd344f15 --- /dev/null +++ b/Zend/tests/partial_application/variation_variadics_004.phpt @@ -0,0 +1,65 @@ +--TEST-- +PFA variation: variadics and optional args +--FILE-- + $day, "month" => $month, "year" => $year]; +} + +$foo = foo(year: 2006, ...); + +var_dump($foo(2)); + +$foo = foo(month: 12, ...); + +$bar = $foo(year: 2016, ...); + +var_dump($foo(2)); + +var_dump($bar(2)); + +var_dump($foo()); + +var_dump($bar()); +?> +--EXPECTF-- +array(3) { + ["day"]=> + int(2) + ["month"]=> + int(1) + ["year"]=> + int(2006) +} +array(3) { + ["day"]=> + int(2) + ["month"]=> + int(12) + ["year"]=> + int(2005) +} +array(3) { + ["day"]=> + int(2) + ["month"]=> + int(12) + ["year"]=> + int(2016) +} +array(3) { + ["day"]=> + int(1) + ["month"]=> + int(12) + ["year"]=> + int(2005) +} +array(3) { + ["day"]=> + int(1) + ["month"]=> + int(12) + ["year"]=> + int(2016) +} diff --git a/Zend/tests/partial_application/variation_variadics_006.phpt b/Zend/tests/partial_application/variation_variadics_006.phpt new file mode 100644 index 0000000000000..cda62a2f3bfb6 --- /dev/null +++ b/Zend/tests/partial_application/variation_variadics_006.phpt @@ -0,0 +1,17 @@ +--TEST-- +PFA variation: named may overwrite variadic placeholder +--FILE-- + +--EXPECTF-- +array(1) { + [0]=> + string(1) "a" +} diff --git a/Zend/tests/partial_application/variation_variadics_007.phpt b/Zend/tests/partial_application/variation_variadics_007.phpt new file mode 100644 index 0000000000000..9624e05b449b6 --- /dev/null +++ b/Zend/tests/partial_application/variation_variadics_007.phpt @@ -0,0 +1,14 @@ +--TEST-- +PFA variation: extra through variadic +--FILE-- + $a + $b)); +?> +--EXPECT-- +int(3) diff --git a/Zend/tests/partial_application/variation_variadics_008.phpt b/Zend/tests/partial_application/variation_variadics_008.phpt new file mode 100644 index 0000000000000..4190bb759e68f --- /dev/null +++ b/Zend/tests/partial_application/variation_variadics_008.phpt @@ -0,0 +1,16 @@ +--TEST-- +PFA variation: variadics wrong signature checked +--FILE-- +getMessage() . PHP_EOL; +} +?> +--EXPECTF-- +Partial application of {closure:%s:%d}() expects at most 1 arguments, 2 given diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index a7e26711cd176..9861edafd5407 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1459,6 +1459,16 @@ ZEND_API zend_ast_ref * ZEND_FASTCALL zend_ast_copy(zend_ast *ast) return ref; } +ZEND_API zend_ast * ZEND_FASTCALL zend_ast_dup(zend_ast *ast) +{ + ZEND_ASSERT(ast != NULL); + + void *buf = zend_ast_alloc(zend_ast_tree_size(ast)); + zend_ast_tree_copy(ast, buf); + + return buf; +} + ZEND_API void ZEND_FASTCALL zend_ast_destroy(zend_ast *ast) { tail_call: diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index 32882f5205f74..630f43547883e 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -351,6 +351,7 @@ ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate_ex(zval *result, zend_ast * ZEND_API zend_string *zend_ast_export(const char *prefix, zend_ast *ast, const char *suffix); ZEND_API zend_ast_ref * ZEND_FASTCALL zend_ast_copy(zend_ast *ast); +ZEND_API zend_ast * ZEND_FASTCALL zend_ast_dup(zend_ast *ast); ZEND_API void ZEND_FASTCALL zend_ast_destroy(zend_ast *ast); ZEND_API void ZEND_FASTCALL zend_ast_ref_destroy(zend_ast_ref *ast); diff --git a/Zend/zend_closures.c b/Zend/zend_closures.c index 56090cddcafb8..acf0156a9bc7a 100644 --- a/Zend/zend_closures.c +++ b/Zend/zend_closures.c @@ -27,6 +27,14 @@ #include "zend_globals.h" #include "zend_closures_arginfo.h" +/* Closure is a PFA */ +#define ZEND_PARTIAL OBJ_EXTRA_FLAG_PRIV_1 +/* Closure is a PFA of a Closure. Rebinding the PFA requires rebinding the inner Closure. */ +#define ZEND_PARTIAL_OF_CLOSURE OBJ_EXTRA_FLAG_PRIV_2 + +#define ZEND_CLOSURE_FLAGS(closure) ((closure)->std.extra_flags & (ZEND_PARTIAL|ZEND_PARTIAL_OF_CLOSURE)) +#define ZEND_CLOSURE_IS_FAKE(closure) ((closure)->func.common.fn_flags & ZEND_ACC_FAKE_CLOSURE) + typedef struct _zend_closure { zend_object std; zend_function func; @@ -40,6 +48,7 @@ ZEND_API zend_class_entry *zend_ce_closure; static zend_object_handlers closure_handlers; static zend_result zend_closure_get_closure(zend_object *obj, zend_class_entry **ce_ptr, zend_function **fptr_ptr, zend_object **obj_ptr, bool check_only); +static void zend_create_closure_ex(zval *res, zend_function *func, zend_class_entry *scope, zend_class_entry *called_scope, zval *this_ptr, bool is_fake, uint32_t flags); ZEND_METHOD(Closure, __invoke) /* {{{ */ { @@ -77,7 +86,8 @@ static bool zend_valid_closure_binding( zend_closure *closure, zval *newthis, zend_class_entry *scope) /* {{{ */ { zend_function *func = &closure->func; - bool is_fake_closure = (func->common.fn_flags & ZEND_ACC_FAKE_CLOSURE) != 0; + bool is_fake_closure = (func->common.fn_flags & ZEND_ACC_FAKE_CLOSURE) != 0 + || (closure->std.extra_flags & ZEND_PARTIAL); if (newthis) { if (func->common.fn_flags & ZEND_ACC_STATIC) { zend_error(E_WARNING, "Cannot bind an instance to a static closure, this will be an error in PHP 9"); @@ -160,7 +170,9 @@ ZEND_METHOD(Closure, call) if (closure->func.common.fn_flags & ZEND_ACC_GENERATOR) { zval new_closure; - zend_create_closure(&new_closure, &closure->func, newclass, closure->called_scope, newthis); + zend_create_closure_ex(&new_closure, &closure->func, newclass, + closure->called_scope, newthis, + ZEND_CLOSURE_IS_FAKE(closure), ZEND_CLOSURE_FLAGS(closure)); closure = (zend_closure *) Z_OBJ(new_closure); fci_cache.function_handler = &closure->func; @@ -176,6 +188,7 @@ ZEND_METHOD(Closure, call) memset(&fake_closure->std, 0, sizeof(fake_closure->std)); fake_closure->std.gc.refcount = 1; fake_closure->std.gc.u.type_info = GC_NULL; + fake_closure->std.extra_flags = ZEND_CLOSURE_FLAGS(closure); ZVAL_UNDEF(&fake_closure->this_ptr); fake_closure->called_scope = NULL; my_function = &fake_closure->func; @@ -222,7 +235,7 @@ ZEND_METHOD(Closure, call) } /* }}} */ -static void do_closure_bind(zval *return_value, zval *zclosure, zval *newthis, zend_object *scope_obj, zend_string *scope_str) +static zend_result do_closure_bind(zval *return_value, zval *zclosure, zval *newthis, zend_object *scope_obj, zend_string *scope_str) { zend_class_entry *ce, *called_scope; zend_closure *closure = (zend_closure *) Z_OBJ_P(zclosure); @@ -234,14 +247,15 @@ static void do_closure_bind(zval *return_value, zval *zclosure, zval *newthis, z ce = closure->func.common.scope; } else if ((ce = zend_lookup_class(scope_str)) == NULL) { zend_error(E_WARNING, "Class \"%s\" not found", ZSTR_VAL(scope_str)); - RETURN_NULL(); + RETVAL_NULL(); + return FAILURE; } } else { ce = NULL; } if (!zend_valid_closure_binding(closure, newthis, ce)) { - return; + return FAILURE; } if (newthis) { @@ -250,7 +264,31 @@ static void do_closure_bind(zval *return_value, zval *zclosure, zval *newthis, z called_scope = ce; } - zend_create_closure(return_value, &closure->func, ce, called_scope, newthis); + zend_create_closure_ex(return_value, &closure->func, ce, called_scope, newthis, + ZEND_CLOSURE_IS_FAKE(closure), ZEND_CLOSURE_FLAGS(closure)); + + if (ZEND_CLOSURE_FLAGS(closure) & ZEND_PARTIAL_OF_CLOSURE) { + /* Re-bind the inner closure */ + + HashTable *static_variables = ZEND_MAP_PTR_GET(closure->func.op_array.static_variables_ptr); + ZEND_ASSERT(static_variables->nNumOfElements > 0); + zval *inner = &static_variables->arData[0].val; + ZEND_ASSERT(Z_TYPE_P(inner) == IS_OBJECT && Z_OBJCE_P(inner) == zend_ce_closure); + + zval new_inner; + if (do_closure_bind(&new_inner, inner, newthis, scope_obj, scope_str) != SUCCESS) { + ZEND_UNREACHABLE(); + zval_ptr_dtor(return_value); + ZVAL_NULL(return_value); + return FAILURE; + } + + zend_object *garbage = Z_OBJ_P(inner); + ZVAL_COPY_VALUE(inner, &new_inner); + zend_object_release(garbage); + } + + return SUCCESS; } /* {{{ Create a closure from another one and bind to another object and scope */ @@ -587,8 +625,9 @@ static zend_object *zend_closure_clone(zend_object *zobject) /* {{{ */ zend_closure *closure = (zend_closure *)zobject; zval result; - zend_create_closure(&result, &closure->func, - closure->func.common.scope, closure->called_scope, &closure->this_ptr); + zend_create_closure_ex(&result, &closure->func, + closure->func.common.scope, closure->called_scope, &closure->this_ptr, + ZEND_CLOSURE_IS_FAKE(closure), ZEND_CLOSURE_FLAGS(closure)); return Z_OBJ(result); } /* }}} */ @@ -755,7 +794,7 @@ static ZEND_NAMED_FUNCTION(zend_closure_internal_handler) /* {{{ */ } /* }}} */ -static void zend_create_closure_ex(zval *res, zend_function *func, zend_class_entry *scope, zend_class_entry *called_scope, zval *this_ptr, bool is_fake) /* {{{ */ +static void zend_create_closure_ex(zval *res, zend_function *func, zend_class_entry *scope, zend_class_entry *called_scope, zval *this_ptr, bool is_fake, uint32_t flags) /* {{{ */ { zend_closure *closure; void *ptr; @@ -763,6 +802,7 @@ static void zend_create_closure_ex(zval *res, zend_function *func, zend_class_en object_init_ex(res, zend_ce_closure); closure = (zend_closure *)Z_OBJ_P(res); + closure->std.extra_flags = flags; if ((scope == NULL) && this_ptr && (Z_TYPE_P(this_ptr) != IS_UNDEF)) { /* use dummy scope if we're binding an object without specifying a scope */ @@ -860,14 +900,14 @@ static void zend_create_closure_ex(zval *res, zend_function *func, zend_class_en ZEND_API void zend_create_closure(zval *res, zend_function *func, zend_class_entry *scope, zend_class_entry *called_scope, zval *this_ptr) { zend_create_closure_ex(res, func, scope, called_scope, this_ptr, - /* is_fake */ (func->common.fn_flags & ZEND_ACC_FAKE_CLOSURE) != 0); + /* is_fake */ (func->common.fn_flags & ZEND_ACC_FAKE_CLOSURE) != 0, 0); } ZEND_API void zend_create_fake_closure(zval *res, zend_function *func, zend_class_entry *scope, zend_class_entry *called_scope, zval *this_ptr) /* {{{ */ { zend_closure *closure; - zend_create_closure_ex(res, func, scope, called_scope, this_ptr, /* is_fake */ true); + zend_create_closure_ex(res, func, scope, called_scope, this_ptr, /* is_fake */ true, 0); closure = (zend_closure *)Z_OBJ_P(res); closure->func.common.fn_flags |= ZEND_ACC_FAKE_CLOSURE; @@ -877,6 +917,16 @@ ZEND_API void zend_create_fake_closure(zval *res, zend_function *func, zend_clas } /* }}} */ +ZEND_API void zend_create_partial_closure(zval *res, zend_function *func, zend_class_entry *scope, zend_class_entry *called_scope, zval *this_ptr, bool partial_of_closure) +{ + int flags = ZEND_PARTIAL; + if (partial_of_closure) { + flags |= ZEND_PARTIAL_OF_CLOSURE; + } + zend_create_closure_ex(res, func, scope, called_scope, this_ptr, + /* is_fake */ false, flags); +} + void zend_closure_from_frame(zval *return_value, const zend_execute_data *call) { /* {{{ */ zval instance; zend_internal_function trampoline; diff --git a/Zend/zend_closures.h b/Zend/zend_closures.h index 2ff4934f2a3a9..305d82e5015a1 100644 --- a/Zend/zend_closures.h +++ b/Zend/zend_closures.h @@ -36,6 +36,7 @@ extern ZEND_API zend_class_entry *zend_ce_closure; ZEND_API void zend_create_closure(zval *res, zend_function *op_array, zend_class_entry *scope, zend_class_entry *called_scope, zval *this_ptr); ZEND_API void zend_create_fake_closure(zval *res, zend_function *op_array, zend_class_entry *scope, zend_class_entry *called_scope, zval *this_ptr); +ZEND_API void zend_create_partial_closure(zval *res, zend_function *func, zend_class_entry *scope, zend_class_entry *called_scope, zval *this_ptr, bool partial_of_closure); ZEND_API zend_function *zend_get_closure_invoke_method(zend_object *obj); ZEND_API const zend_function *zend_get_closure_method_def(zend_object *obj); ZEND_API zval* zend_get_closure_this_ptr(zval *obj); diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 78cc114223d0e..47fee5c2e2242 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -28,6 +28,8 @@ #include "zend_exceptions.h" #include "zend_interfaces.h" #include "zend_types.h" +#include "zend_portability.h" +#include "zend_string.h" #include "zend_virtual_cwd.h" #include "zend_multibyte.h" #include "zend_language_scanner.h" @@ -3766,12 +3768,16 @@ static uint32_t zend_get_arg_num(const zend_function *fn, const zend_string *arg return (uint32_t) -1; } -static uint32_t zend_compile_args( - zend_ast *ast, const zend_function *fbc, bool *may_have_extra_named_args) /* {{{ */ +static uint32_t zend_compile_args_ex( + zend_ast *ast, const zend_function *fbc, + bool *may_have_extra_named_args, + bool is_call_partial, bool *uses_variadic_placeholder_p, + zval *named_positions) /* {{{ */ { const zend_ast_list *args = zend_ast_get_list(ast); uint32_t i; bool uses_arg_unpack = false; + bool uses_variadic_placeholder = false; uint32_t arg_count = 0; /* number of arguments not including unpacks */ /* Whether named arguments are used syntactically, to enforce language level limitations. @@ -3797,6 +3803,11 @@ static uint32_t zend_compile_args( "Cannot use argument unpacking after named arguments"); } + if (is_call_partial) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot combine partial application and unpacking"); + } + /* Unpack may contain named arguments. */ may_have_undef = true; if (!fbc || (fbc->common.fn_flags & ZEND_ACC_VARIADIC)) { @@ -3837,18 +3848,75 @@ static uint32_t zend_compile_args( may_have_undef = true; *may_have_extra_named_args = true; } + + if (uses_variadic_placeholder) { + zend_error_noreturn(E_COMPILE_ERROR, + "Variadic placeholder must be last"); + } } else { if (uses_arg_unpack) { zend_error_noreturn(E_COMPILE_ERROR, "Cannot use positional argument after argument unpacking"); } - if (uses_named_args) { + bool is_variadic_placeholder = arg->kind == ZEND_AST_PLACEHOLDER_ARG + && arg->attr == ZEND_PLACEHOLDER_VARIADIC; + + if (uses_named_args && !is_variadic_placeholder) { zend_error_noreturn(E_COMPILE_ERROR, "Cannot use positional argument after named argument"); } - arg_count++; + if (uses_variadic_placeholder) { + if (is_variadic_placeholder) { + zend_error_noreturn(E_COMPILE_ERROR, + "Variadic placeholder may only appear once"); + } else { + zend_error_noreturn(E_COMPILE_ERROR, + "Variadic placeholder must be last"); + } + } + + if (!is_variadic_placeholder) { + arg_count++; + } + } + + if (arg->kind == ZEND_AST_PLACEHOLDER_ARG) { + if (uses_arg_unpack) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot combine partial application and unpacking"); + } + + if (arg->attr == ZEND_PLACEHOLDER_VARIADIC) { + uses_variadic_placeholder = true; + /* Do not emit ZEND_SEND_PLACEHOLDER: We represent the variadic + * placeholder with a flag on the ZEND_CONVERT_CALLABLE_PARTIAL + * op instead. */ + continue; + } + + if (arg_name) { + if (Z_ISUNDEF_P(named_positions)) { + array_init(named_positions); + } + zval tmp; + ZVAL_LONG(&tmp, zend_hash_num_elements(Z_ARRVAL_P(named_positions))); + zend_hash_add(Z_ARRVAL_P(named_positions), arg_name, &tmp); + } + + opline = zend_emit_op(NULL, ZEND_SEND_PLACEHOLDER, NULL, NULL); + if (arg_name) { + opline->op2_type = IS_CONST; + zend_string_addref(arg_name); + opline->op2.constant = zend_add_literal_string(&arg_name); + opline->result.num = zend_alloc_cache_slots(2); + } else if (arg->attr != ZEND_PLACEHOLDER_VARIADIC) { + opline->op2.opline_num = arg_num; + opline->result.var = EX_NUM_TO_VAR(arg_num - 1); + } + + continue; } /* Treat passing of $GLOBALS the same as passing a call. @@ -3963,14 +4031,24 @@ static uint32_t zend_compile_args( } } - if (may_have_undef) { - zend_emit_op(NULL, ZEND_CHECK_UNDEF_ARGS, NULL, NULL); + if (!is_call_partial) { + if (may_have_undef) { + zend_emit_op(NULL, ZEND_CHECK_UNDEF_ARGS, NULL, NULL); + } + } else { + *uses_variadic_placeholder_p = uses_variadic_placeholder; } return arg_count; } /* }}} */ +static uint32_t zend_compile_args(zend_ast *ast, const zend_function *fbc, + bool *may_have_extra_named_args) /* {{{ */ +{ + return zend_compile_args_ex(ast, fbc, may_have_extra_named_args, false, NULL, NULL); +} + ZEND_API uint8_t zend_get_call_op(const zend_op *init_op, const zend_function *fbc, bool result_used) /* {{{ */ { uint32_t no_discard = result_used ? 0 : ZEND_ACC_NODISCARD; @@ -4004,6 +4082,38 @@ ZEND_API uint8_t zend_get_call_op(const zend_op *init_op, const zend_function *f } /* }}} */ +static void zend_compile_call_partial(znode *result, uint32_t arg_count, + bool may_have_extra_named_args, bool uses_variadic_placeholder, + zval *named_positions, uint32_t opnum_init, const zend_function *fbc) { + + zend_op *init_opline = &CG(active_op_array)->opcodes[opnum_init]; + + init_opline->extended_value = arg_count; + + ZEND_ASSERT(init_opline->opcode != ZEND_NEW); + + if (init_opline->opcode == ZEND_INIT_FCALL) { + init_opline->op1.num = zend_vm_calc_used_stack(arg_count, fbc); + } + + zend_op *opline = zend_emit_op_tmp(result, ZEND_CALLABLE_CONVERT_PARTIAL, + NULL, NULL); + + opline->op1.num = zend_alloc_cache_slots(2); + + if (may_have_extra_named_args) { + opline->extended_value = ZEND_FCALL_MAY_HAVE_EXTRA_NAMED_PARAMS; + } + if (uses_variadic_placeholder) { + opline->extended_value |= ZEND_FCALL_USES_VARIADIC_PLACEHOLDER; + } + + if (!Z_ISUNDEF_P(named_positions)) { + opline->op2.constant = zend_add_literal(named_positions); + opline->op2_type = IS_CONST; + } +} + static bool zend_compile_call_common(znode *result, zend_ast *args_ast, const zend_function *fbc, uint32_t lineno, uint32_t type) /* {{{ */ { zend_op *opline; @@ -4019,23 +4129,43 @@ static bool zend_compile_call_common(znode *result, zend_ast *args_ast, const ze zend_error_noreturn(E_COMPILE_ERROR, "Cannot create Closure for new expression"); } - zend_ast_list *args = zend_ast_get_list(((zend_ast_fcc*)args_ast)->args); - if (args->children != 1 || args->child[0]->attr != ZEND_PLACEHOLDER_VARIADIC) { - zend_error_noreturn(E_COMPILE_ERROR, "Cannot create a Closure for call expression with more than one argument, or non-variadic placeholders"); - } + zend_ast_list *fcc_args = zend_ast_get_list(((zend_ast_fcc*)args_ast)->args); - if (opcode == ZEND_INIT_FCALL) { - opline->op1.num = zend_vm_calc_used_stack(0, fbc); - } + /* FCCs are a special case of PFAs with a single variadic placeholder */ + if (fcc_args->children == 1 && fcc_args->child[0]->attr == ZEND_PLACEHOLDER_VARIADIC) { - zend_op *callable_convert_op = zend_emit_op_tmp(result, ZEND_CALLABLE_CONVERT, NULL, NULL); - if (opcode == ZEND_INIT_FCALL - || opcode == ZEND_INIT_FCALL_BY_NAME - || opcode == ZEND_INIT_NS_FCALL_BY_NAME) { - callable_convert_op->extended_value = zend_alloc_cache_slot(); - } else { - callable_convert_op->extended_value = (uint32_t)-1; + if (opline->opcode == ZEND_INIT_FCALL) { + opline->op1.num = zend_vm_calc_used_stack(0, fbc); + } + + zend_op *callable_convert_op = zend_emit_op_tmp(result, ZEND_CALLABLE_CONVERT, NULL, NULL); + if (opcode == ZEND_INIT_FCALL + || opcode == ZEND_INIT_FCALL_BY_NAME + || opcode == ZEND_INIT_NS_FCALL_BY_NAME) { + callable_convert_op->extended_value = zend_alloc_cache_slot(); + } else { + callable_convert_op->extended_value = (uint32_t)-1; + } + + return true; } + + args_ast = ((zend_ast_fcc*)args_ast)->args; + + bool may_have_extra_named_args; + bool uses_variadic_placeholder; + + zval named_positions; + ZVAL_UNDEF(&named_positions); + + uint32_t arg_count = zend_compile_args_ex(args_ast, fbc, + &may_have_extra_named_args, true, &uses_variadic_placeholder, + &named_positions); + + zend_compile_call_partial(result, arg_count, + may_have_extra_named_args, uses_variadic_placeholder, + &named_positions, opnum_init, fbc); + return true; } @@ -5141,7 +5271,7 @@ static zend_result zend_compile_func_array_map(znode *result, zend_ast_list *arg * breaking for the generated call. */ if (callback->kind == ZEND_AST_CALL - && callback->child[0]->kind == ZEND_AST_ZVAL + && callback->child[0]->kind == ZEND_AST_ZVAL && Z_TYPE_P(zend_ast_get_zval(callback->child[0])) == IS_STRING && zend_string_equals_literal_ci(zend_ast_get_str(callback->child[0]), "assert")) { return FAILURE; @@ -6770,6 +6900,76 @@ static bool zend_is_pipe_optimizable_callable_name(zend_ast *ast) return true; } +static zend_ast *zend_partial_apply(zend_ast *callable_ast, zend_ast *pipe_arg) +{ + if (callable_ast->kind != ZEND_AST_CALL + && callable_ast->kind != ZEND_AST_STATIC_CALL + && callable_ast->kind != ZEND_AST_METHOD_CALL) { + return NULL; + } + + zend_ast *args_ast = zend_ast_call_get_args(callable_ast); + if (!args_ast || args_ast->kind != ZEND_AST_CALLABLE_CONVERT) { + return NULL; + } + + if (callable_ast->kind == ZEND_AST_CALL && + !zend_is_pipe_optimizable_callable_name(callable_ast->child[0])) { + return NULL; + } + + zend_ast_list *arg_list = zend_ast_get_list(((zend_ast_fcc*)args_ast)->args); + + zend_ast *first_placeholder = NULL; + bool uses_named_args = false; + + for (uint32_t i = 0; i < arg_list->children; i++) { + zend_ast *arg = arg_list->child[i]; + if (arg->kind == ZEND_AST_NAMED_ARG) { + uses_named_args = true; + arg = arg->child[1]; + } + + if (arg->kind == ZEND_AST_PLACEHOLDER_ARG) { + if (first_placeholder == NULL) { + first_placeholder = arg; + } else { + /* A PFA with multiple placeholders is unexpected in is this + * context, and will usually error due to a missing argument, + * so we don't optimize those. */ + return NULL; + } + if (arg->attr == ZEND_PLACEHOLDER_VARIADIC && uses_named_args) { + /* PFAs with both a variadic placeholder and named args can not + * be optimized because the named arg may resolve to the + * position of the placeholder: f(..., name: $v). + * Arg placeholders ('?') are safe, as named args are not + * allowed to override them. */ + return NULL; + } + } + } + + ZEND_ASSERT(first_placeholder); + + zend_ast *new_arg_list = zend_ast_create_list(0, arg_list->kind); + for (uint32_t i = 0; i < arg_list->children; i++) { + zend_ast *arg = arg_list->child[i]; + if (arg == first_placeholder) { + new_arg_list = zend_ast_list_add(new_arg_list, pipe_arg); + } else if (arg->kind == ZEND_AST_NAMED_ARG + && arg->child[1] == first_placeholder) { + zend_ast *name = arg->child[0]; + new_arg_list = zend_ast_list_add(new_arg_list, + zend_ast_create(ZEND_AST_NAMED_ARG, name, pipe_arg)); + } else { + new_arg_list = zend_ast_list_add(new_arg_list, arg); + } + } + + return new_arg_list; +} + static void zend_compile_pipe(znode *result, zend_ast *ast, uint32_t type) { zend_ast *operand_ast = ast->child[0]; @@ -6794,29 +6994,34 @@ static void zend_compile_pipe(znode *result, zend_ast *ast, uint32_t type) } /* Turn the operand into a function parameter list. */ - zend_ast *arg_list_ast = zend_ast_create_list(1, ZEND_AST_ARG_LIST, zend_ast_create_znode(&wrapped_operand_result)); + zend_ast *arg = zend_ast_create_znode(&wrapped_operand_result); zend_ast *fcall_ast; znode callable_result; + zend_ast *pfa_arg_list_ast = NULL; - /* Turn $foo |> bar(...) into bar($foo). */ - if (callable_ast->kind == ZEND_AST_CALL - && callable_ast->child[1]->kind == ZEND_AST_CALLABLE_CONVERT - && zend_is_pipe_optimizable_callable_name(callable_ast->child[0])) { - fcall_ast = zend_ast_create(ZEND_AST_CALL, - callable_ast->child[0], arg_list_ast); - /* Turn $foo |> bar::baz(...) into bar::baz($foo). */ - } else if (callable_ast->kind == ZEND_AST_STATIC_CALL - && callable_ast->child[2]->kind == ZEND_AST_CALLABLE_CONVERT) { - fcall_ast = zend_ast_create(ZEND_AST_STATIC_CALL, - callable_ast->child[0], callable_ast->child[1], arg_list_ast); - /* Turn $foo |> $bar->baz(...) into $bar->baz($foo). */ - } else if (callable_ast->kind == ZEND_AST_METHOD_CALL - && callable_ast->child[2]->kind == ZEND_AST_CALLABLE_CONVERT) { - fcall_ast = zend_ast_create(ZEND_AST_METHOD_CALL, - callable_ast->child[0], callable_ast->child[1], arg_list_ast); + /* Turn $foo |> PFA into plain function call if possible */ + if ((pfa_arg_list_ast = zend_partial_apply(callable_ast, arg))) { + switch (callable_ast->kind) { + case ZEND_AST_CALL: + fcall_ast = zend_ast_create(ZEND_AST_CALL, + callable_ast->child[0], pfa_arg_list_ast); + break; + case ZEND_AST_STATIC_CALL: + fcall_ast = zend_ast_create(ZEND_AST_STATIC_CALL, + callable_ast->child[0], callable_ast->child[1], + pfa_arg_list_ast); + break; + case ZEND_AST_METHOD_CALL: + fcall_ast = zend_ast_create(ZEND_AST_METHOD_CALL, + callable_ast->child[0], callable_ast->child[1], + pfa_arg_list_ast); + break; + EMPTY_SWITCH_DEFAULT_CASE() + } /* Turn $foo |> $expr into ($expr)($foo) */ } else { + zend_ast *arg_list_ast = zend_ast_create_list(1, ZEND_AST_ARG_LIST, arg); zend_compile_expr(&callable_result, callable_ast); callable_ast = zend_ast_create_znode(&callable_result); fcall_ast = zend_ast_create(ZEND_AST_CALL, @@ -11808,6 +12013,13 @@ static void zend_compile_const_expr_fcc(zend_ast **ast_ptr) if ((*args_ast)->kind != ZEND_AST_CALLABLE_CONVERT) { zend_error_noreturn(E_COMPILE_ERROR, "Constant expression contains invalid operations"); } + + zend_ast_list *args = zend_ast_get_list(((zend_ast_fcc*)*args_ast)->args); + if (args->children != 1 || args->child[0]->attr != ZEND_PLACEHOLDER_VARIADIC) { + // TODO: PFAs + zend_error_noreturn(E_COMPILE_ERROR, "Constant expression contains invalid operations"); + } + ZEND_MAP_PTR_NEW(((zend_ast_fcc *)*args_ast)->fptr); switch ((*ast_ptr)->kind) { diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 3b85d52c1870a..db6dd46d1b21c 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -957,6 +957,7 @@ struct _zend_arena; ZEND_API zend_op_array *compile_file(zend_file_handle *file_handle, int type); ZEND_API zend_op_array *compile_string(zend_string *source_string, const char *filename, zend_compile_position position); ZEND_API zend_op_array *compile_filename(int type, zend_string *filename); +ZEND_API zend_op_array *zend_compile_ast(zend_ast *ast, int type, zend_string *filename); ZEND_API zend_ast *zend_compile_string_to_ast( zend_string *code, struct _zend_arena **ast_arena, zend_string *filename); ZEND_API zend_result zend_execute_scripts(int type, zval *retval, int file_count, ...); @@ -1118,7 +1119,8 @@ ZEND_API zend_string *zend_type_to_string(zend_type type); #define ZEND_THROW_IS_EXPR 1u -#define ZEND_FCALL_MAY_HAVE_EXTRA_NAMED_PARAMS 1 +#define ZEND_FCALL_MAY_HAVE_EXTRA_NAMED_PARAMS (1<<0) +#define ZEND_FCALL_USES_VARIADIC_PLACEHOLDER (1<<1) /* The send mode, the is_variadic, the is_promoted, and the is_tentative flags are stored as part of zend_type */ #define _ZEND_SEND_MODE_SHIFT _ZEND_TYPE_EXTRA_FLAGS_SHIFT diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 85461eaa15693..cc0412b887f70 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -44,6 +44,7 @@ #include "zend_call_stack.h" #include "zend_attributes.h" #include "Optimizer/zend_func_info.h" +#include "zend_partial.h" /* Virtual current working directory support */ #include "zend_virtual_cwd.h" @@ -1229,6 +1230,13 @@ static zend_always_inline bool zend_check_type( return zend_check_type_slow(type, arg, ref, is_return_type, is_internal); } +ZEND_API bool zend_check_type_ex( + const zend_type *type, zval *arg, zend_class_entry *scope, + bool is_return_type, bool is_internal) +{ + return zend_check_type(type, arg, scope, is_return_type, is_internal); +} + ZEND_API bool zend_check_user_type_slow( const zend_type *type, zval *arg, const zend_reference *ref, bool is_return_type) { @@ -4666,6 +4674,7 @@ ZEND_API void zend_unfinished_calls_gc(zend_execute_data *execute_data, zend_exe case ZEND_DO_UCALL: case ZEND_DO_FCALL_BY_NAME: case ZEND_CALLABLE_CONVERT: + case ZEND_CALLABLE_CONVERT_PARTIAL: level++; break; case ZEND_INIT_FCALL: @@ -4722,6 +4731,7 @@ ZEND_API void zend_unfinished_calls_gc(zend_execute_data *execute_data, zend_exe case ZEND_DO_UCALL: case ZEND_DO_FCALL_BY_NAME: case ZEND_CALLABLE_CONVERT: + case ZEND_CALLABLE_CONVERT_PARTIAL: level++; break; case ZEND_INIT_FCALL: @@ -4802,6 +4812,7 @@ static void cleanup_unfinished_calls(zend_execute_data *execute_data, uint32_t o case ZEND_DO_UCALL: case ZEND_DO_FCALL_BY_NAME: case ZEND_CALLABLE_CONVERT: + case ZEND_CALLABLE_CONVERT_PARTIAL: level++; break; case ZEND_INIT_FCALL: @@ -4859,6 +4870,7 @@ static void cleanup_unfinished_calls(zend_execute_data *execute_data, uint32_t o case ZEND_DO_UCALL: case ZEND_DO_FCALL_BY_NAME: case ZEND_CALLABLE_CONVERT: + case ZEND_CALLABLE_CONVERT_PARTIAL: level++; break; case ZEND_INIT_FCALL: @@ -5568,9 +5580,10 @@ zval * ZEND_FASTCALL zend_handle_named_arg( } } else { arg = ZEND_CALL_VAR_NUM(call, arg_offset); + if (UNEXPECTED(!Z_ISUNDEF_P(arg))) { - zend_throw_error(NULL, "Named parameter $%s overwrites previous argument", - ZSTR_VAL(arg_name)); + zend_throw_error(NULL, "Named parameter $%s overwrites previous %s", + ZSTR_VAL(arg_name), Z_TYPE_P(arg) == _IS_PLACEHOLDER ? "placeholder" : "argument"); return NULL; } } diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index ba48b19bcfe1b..f44538d62f6ae 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -109,6 +109,9 @@ ZEND_API zend_never_inline ZEND_COLD void zend_verify_never_error( ZEND_API bool zend_verify_ref_array_assignable(zend_reference *ref); ZEND_API bool zend_check_user_type_slow( const zend_type *type, zval *arg, const zend_reference *ref, bool is_return_type); +ZEND_API bool zend_check_type_ex( + const zend_type *type, zval *arg, zend_class_entry *scope, + bool is_return_type, bool is_internal); #if ZEND_DEBUG ZEND_API bool zend_internal_call_should_throw(const zend_function *fbc, zend_execute_data *call); diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index 5bead7034b9a4..5db3ad67ff0a6 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -38,6 +38,7 @@ #include "zend_observer.h" #include "zend_call_stack.h" #include "zend_frameless_function.h" +#include "zend_partial.h" #ifdef HAVE_SYS_TIME_H #include #endif @@ -201,6 +202,7 @@ void init_executor(void) /* {{{ */ zend_weakrefs_init(); zend_hash_init(&EG(callable_convert_cache), 8, NULL, ZVAL_PTR_DTOR, 0); + zend_hash_init(&EG(partial_function_application_cache), 8, NULL, zend_partial_op_array_dtor, 0); EG(active) = 1; } @@ -418,6 +420,7 @@ ZEND_API void zend_shutdown_executor_values(bool fast_shutdown) zend_stack_clean(&EG(user_exception_handlers), (void (*)(void *))ZVAL_PTR_DTOR, 1); zend_hash_clean(&EG(callable_convert_cache)); + zend_hash_clean(&EG(partial_function_application_cache)); #if ZEND_DEBUG if (!CG(unclean_shutdown)) { @@ -514,6 +517,7 @@ void shutdown_executor(void) /* {{{ */ } zend_hash_destroy(&EG(callable_convert_cache)); + zend_hash_destroy(&EG(partial_function_application_cache)); } #if ZEND_DEBUG diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index 8257df32e8312..e94550247701e 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -325,6 +325,7 @@ struct _zend_executor_globals { zend_strtod_state strtod_state; HashTable callable_convert_cache; + HashTable partial_function_application_cache; void *reserved[ZEND_MAX_RESERVED_RESOURCES]; }; diff --git a/Zend/zend_language_scanner.l b/Zend/zend_language_scanner.l index 07f2d44cb5c6f..7e9dccff58f58 100644 --- a/Zend/zend_language_scanner.l +++ b/Zend/zend_language_scanner.l @@ -590,6 +590,43 @@ ZEND_API zend_result open_file_for_scanning(zend_file_handle *file_handle) return SUCCESS; } +static zend_op_array *zend_compile_ast_internal(int type) +{ + ZEND_ASSERT(CG(in_compilation)); + ZEND_ASSERT(CG(ast)); + + uint32_t last_lineno = CG(zend_lineno); + zend_file_context original_file_context; + zend_oparray_context original_oparray_context; + zend_op_array *original_active_op_array = CG(active_op_array); + + zend_op_array *op_array = emalloc(sizeof(zend_op_array)); + init_op_array(op_array, type, INITIAL_OP_ARRAY_SIZE); + CG(active_op_array) = op_array; + + /* Use heap to not waste arena memory */ + op_array->fn_flags |= ZEND_ACC_HEAP_RT_CACHE; + + if (zend_ast_process) { + zend_ast_process(CG(ast)); + } + + zend_file_context_begin(&original_file_context); + zend_oparray_context_begin(&original_oparray_context, op_array); + zend_compile_top_stmt(CG(ast)); + CG(zend_lineno) = last_lineno; + zend_emit_final_return(type == ZEND_USER_FUNCTION); + op_array->line_start = 1; + op_array->line_end = last_lineno; + pass_two(op_array); + zend_oparray_context_end(&original_oparray_context); + zend_file_context_end(&original_file_context); + + CG(active_op_array) = original_active_op_array; + + return op_array; +} + static zend_op_array *zend_compile(zend_function_type type) { zend_op_array *op_array = NULL; @@ -600,34 +637,7 @@ static zend_op_array *zend_compile(zend_function_type type) CG(ast_arena) = zend_arena_create(1024 * 32); if (!zendparse()) { - uint32_t last_lineno = CG(zend_lineno); - zend_file_context original_file_context; - zend_oparray_context original_oparray_context; - zend_op_array *original_active_op_array = CG(active_op_array); - - op_array = emalloc(sizeof(zend_op_array)); - init_op_array(op_array, type, INITIAL_OP_ARRAY_SIZE); - CG(active_op_array) = op_array; - - /* Use heap to not waste arena memory */ - op_array->fn_flags |= ZEND_ACC_HEAP_RT_CACHE; - - if (zend_ast_process) { - zend_ast_process(CG(ast)); - } - - zend_file_context_begin(&original_file_context); - zend_oparray_context_begin(&original_oparray_context, op_array); - zend_compile_top_stmt(CG(ast)); - CG(zend_lineno) = last_lineno; - zend_emit_final_return(type == ZEND_USER_FUNCTION); - op_array->line_start = 1; - op_array->line_end = last_lineno; - pass_two(op_array); - zend_oparray_context_end(&original_oparray_context); - zend_file_context_end(&original_file_context); - - CG(active_op_array) = original_active_op_array; + op_array = zend_compile_ast_internal(type); } zend_ast_destroy(CG(ast)); @@ -670,6 +680,23 @@ ZEND_API zend_op_array *compile_file(zend_file_handle *file_handle, int type) return op_array; } +ZEND_API zend_op_array *zend_compile_ast( + zend_ast *ast, int type, zend_string *filename) +{ + zend_string *original_compiled_filename = CG(compiled_filename); + bool original_in_compilation = CG(in_compilation); + CG(in_compilation) = 1; + CG(ast) = ast; + + zend_set_compiled_filename(filename); + zend_op_array *op_array = zend_compile_ast_internal(type); + + CG(in_compilation) = original_in_compilation; + zend_restore_compiled_filename(original_compiled_filename); + + return op_array; +} + ZEND_API zend_ast *zend_compile_string_to_ast( zend_string *code, zend_arena **ast_arena, zend_string *filename) { zval code_zv; diff --git a/Zend/zend_partial.c b/Zend/zend_partial.c new file mode 100644 index 0000000000000..2a4195c4049bc --- /dev/null +++ b/Zend/zend_partial.c @@ -0,0 +1,1146 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine | + +----------------------------------------------------------------------+ + | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) | + +----------------------------------------------------------------------+ + | This source file is subject to version 2.00 of the Zend license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.zend.com/license/2_00.txt. | + | If you did not receive a copy of the Zend license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@zend.com so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ +*/ + +/** + * Partial Function Application: + * + * A partial application is compiled to the usual sequence of function call + * opcodes (INIT_FCALL, SEND_VAR, etc), but the sequence ends with a + * CALLABLE_CONVERT_PARTIAL opcode instead of DO_FCALL, similarly to + * first class callables. Placeholders are compiled to SEND_PLACEHOLDER opcodes: + * + * $f = f($a, ?) + * + * 0001 INIT_FCALL f + * 0002 SEND_VAR CV($a) + * 0003 SEND_PLACEHOLDER + * 0004 CV($f) = CALLABLE_CONVERT_PARTIAL + * + * SEND_PLACEHOLDER sets the argument slot type to _IS_PLACEHOLDER. + * + * CALLABLE_CONVERT_PARTIAL uses the information available on the stack to + * create a Closure and return it, consuming the stack frame in the process + * like an internal function call. + * + * We create the Closure by generating the relevant AST and compling it to an + * op_array. The op_array is cached in the Opcache SHM and inline caches. + * + * This file implements the Closure generation logic + * (see zend_partial_create(), zp_compile()). + */ + +#include "zend.h" +#include "zend_API.h" +#include "zend_arena.h" +#include "zend_ast.h" +#include "zend_compile.h" +#include "zend_closures.h" +#include "zend_attributes.h" +#include "zend_exceptions.h" +#include "ext/opcache/ZendAccelerator.h" + +#define Z_IS_PLACEHOLDER_P(p) (Z_TYPE_P(p) == _IS_PLACEHOLDER) + +#define IS_STATIC_CLOSURE(function) \ + (((function)->common.fn_flags & (ZEND_ACC_STATIC|ZEND_ACC_CLOSURE)) == (ZEND_ACC_STATIC|ZEND_ACC_CLOSURE)) + +static zend_never_inline ZEND_COLD void zp_args_underflow( + const zend_function *function, uint32_t args, uint32_t expected) +{ + zend_string *symbol = get_function_or_method_name(function); + const char *limit = function->common.num_args <= function->common.required_num_args ? + "exactly" : "at least"; + + zend_argument_count_error( + "Partial application of %s() expects %s %d arguments, %d given", + ZSTR_VAL(symbol), limit, expected, args); + + zend_string_release(symbol); +} + +static zend_never_inline ZEND_COLD void zp_args_overflow( + const zend_function *function, uint32_t args, uint32_t expected) +{ + zend_string *symbol = get_function_or_method_name(function); + + zend_argument_count_error( + "Partial application of %s() expects at most %d arguments, %d given", + ZSTR_VAL(symbol), expected, args); + + zend_string_release(symbol); +} + +static zend_result zp_args_check(const zend_function *function, + uint32_t argc, const zval *argv, + const zend_array *extra_named_args, + bool uses_variadic_placeholder) { + + if (extra_named_args) { + zval *arg; + zend_string *key; + ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(extra_named_args, key, arg) { + if (UNEXPECTED(Z_IS_PLACEHOLDER_P(arg))) { + zend_throw_error(NULL, + "Cannot use named placeholder for unknown or variadic parameter $%s", + ZSTR_VAL(key)); + return FAILURE; + } + } ZEND_HASH_FOREACH_END(); + } + + if (argc < function->common.required_num_args) { + if (uses_variadic_placeholder) { + /* Missing args will be turned into placeholders */ + return SUCCESS; + } + + zp_args_underflow( + function, argc, function->common.required_num_args); + return FAILURE; + } else if (argc > function->common.num_args && + !(function->common.fn_flags & ZEND_ACC_VARIADIC)) { + zp_args_overflow(function, argc, function->common.num_args); + return FAILURE; + } + + return SUCCESS; +} + +static bool zp_name_exists(zend_string **names, uint32_t num_names, zend_string *name) +{ + for (uint32_t i = 0; i < num_names; i++) { + if (names[i] && zend_string_equals(names[i], name)) { + return true; + } + } + return false; +} + +static zend_string *zp_get_param_name(zend_function *function, uint32_t arg_offset) +{ + return zend_string_copy(function->common.arg_info[arg_offset].name); +} + +/* Assign a name for every variable that will be used in the generated closure, + * including params and used vars. */ +static void zp_assign_names(zend_string **names, uint32_t num_names, + uint32_t argc, zval *argv, + zend_function *function, bool variadic_partial, + zend_array *extra_named_params) +{ + /* Assign names for params. We never rename those. */ + for (uint32_t offset = 0; offset < MIN(argc, function->common.num_args); offset++) { + if (Z_IS_PLACEHOLDER_P(&argv[offset])) { + names[offset] = zp_get_param_name(function, offset); + } + } + + /* Assign name for the variadic param. Never renamed. */ + if (variadic_partial && (function->common.fn_flags & ZEND_ACC_VARIADIC)) { + names[argc] = zp_get_param_name(function, function->common.num_args); + } + + /* Assign names for placeholders that bind to the variadic param: + * + * function f($a, ...$args) {} + * f(?, ?, ...); // The second placeholder binds into the variadic param. + * + * By default these are named $origNameN with N the offset from the + * variadic param. In case of clash we increment N until a free name is + * found. */ + for (uint32_t offset = function->common.num_args; offset < argc; offset++) { + ZEND_ASSERT(function->common.fn_flags & ZEND_ACC_VARIADIC); + if (!Z_IS_PLACEHOLDER_P(&argv[offset])) { + continue; + } + int n = offset - function->common.num_args; + zend_string *orig_name = zp_get_param_name(function, function->common.num_args); + zend_string *new_name; + do { + new_name = zend_strpprintf_unchecked(0, "%S%d", orig_name, n); + if (!zp_name_exists(names, num_names, new_name)) { + break; + } + n++; + zend_string_release(new_name); + } while (true); + names[offset] = new_name; + zend_string_release(orig_name); + } + + /* Assign names for pre-bound params (lexical vars). + * There may be clashes, we ensure to generate unique names. */ + for (uint32_t offset = 0; offset < argc; offset++) { + if (Z_IS_PLACEHOLDER_P(&argv[offset]) || Z_ISUNDEF(argv[offset])) { + continue; + } + int n = -1; + zend_string *orig_name = zp_get_param_name(function, MIN(offset, function->common.num_args)); + zend_string *new_name = zend_string_copy(orig_name); + while (zp_name_exists(names, num_names, new_name)) { + zend_string_release(new_name); + n++; + new_name = zend_strpprintf_unchecked(0, "%S%d", orig_name, n); + } + names[offset] = new_name; + zend_string_release(orig_name); + } + + /* Assign name for $extra_named_params */ + if (extra_named_params) { + int n = 1; + zend_string *new_name = ZSTR_INIT_LITERAL("extra_named_params", 0); + while (zp_name_exists(names, num_names, new_name)) { + zend_string_release(new_name); + n++; + new_name = zend_strpprintf(0, "%s%d", "extra_named_params", n); + } + names[argc + variadic_partial] = new_name; + } + + /* Assign name for $fn */ + if (function->common.fn_flags & ZEND_ACC_CLOSURE) { + int n = 1; + zend_string *new_name = ZSTR_INIT_LITERAL("fn", 0); + while (zp_name_exists(names, num_names, new_name)) { + zend_string_release(new_name); + n++; + new_name = zend_strpprintf(0, "%s%d", "fn", n); + } + names[argc + variadic_partial + (extra_named_params != NULL)] = new_name; + } +} + +static bool zp_is_single_may_be_type(uint32_t type_mask) +{ + return ((type_mask > 0) && (type_mask & (type_mask - 1)) == 0) + || type_mask == MAY_BE_BOOL + || type_mask == MAY_BE_ANY; +} + +static zend_ast *zp_single_may_be_type_to_ast(uint32_t type) +{ + zend_string *name; + + switch (type) { + case MAY_BE_NULL: + name = ZSTR_KNOWN(ZEND_STR_NULL_LOWERCASE); + break; + case MAY_BE_TRUE: + name = ZSTR_KNOWN(ZEND_STR_TRUE); + break; + case MAY_BE_FALSE: + name = ZSTR_KNOWN(ZEND_STR_FALSE); + break; + case MAY_BE_LONG: + name = ZSTR_KNOWN(ZEND_STR_INT); + break; + case MAY_BE_DOUBLE: + name = ZSTR_KNOWN(ZEND_STR_FLOAT); + break; + case MAY_BE_STRING: + name = ZSTR_KNOWN(ZEND_STR_STRING); + break; + case MAY_BE_BOOL: + name = ZSTR_KNOWN(ZEND_STR_BOOL); + break; + case MAY_BE_VOID: + name = ZSTR_KNOWN(ZEND_STR_VOID); + break; + case MAY_BE_NEVER: + name = ZSTR_KNOWN(ZEND_STR_NEVER); + break; + case MAY_BE_OBJECT: + name = ZSTR_KNOWN(ZEND_STR_OBJECT); + break; + case MAY_BE_ANY: + name = ZSTR_KNOWN(ZEND_STR_MIXED); + break; + case MAY_BE_CALLABLE: + return zend_ast_create_ex(ZEND_AST_TYPE, IS_CALLABLE); + case MAY_BE_ARRAY: + return zend_ast_create_ex(ZEND_AST_TYPE, IS_ARRAY); + case MAY_BE_STATIC: + return zend_ast_create_ex(ZEND_AST_TYPE, IS_STATIC); + EMPTY_SWITCH_DEFAULT_CASE() + } + + zend_ast *ast = zend_ast_create_zval_from_str(name); + ast->attr = ZEND_NAME_NOT_FQ; + + return ast; +} + +static zend_ast *zp_type_name_to_ast(zend_string *name) +{ + zend_ast *ast = zend_ast_create_zval_from_str(name); + + if (zend_get_class_fetch_type(name) != ZEND_FETCH_CLASS_DEFAULT) { + ast->attr = ZEND_NAME_NOT_FQ; + } else { + ast->attr = ZEND_NAME_FQ; + } + + return ast; +} + +static zend_ast *zp_type_to_ast(const zend_type type) +{ + if (!ZEND_TYPE_IS_SET(type)) { + return NULL; + } + + if (ZEND_TYPE_IS_UNION(type) + || (ZEND_TYPE_IS_COMPLEX(type) && ZEND_TYPE_PURE_MASK(type)) + || (ZEND_TYPE_PURE_MASK(type) && !zp_is_single_may_be_type(ZEND_TYPE_PURE_MASK(type)))) { + zend_ast *type_ast = zend_ast_create_list(0, ZEND_AST_TYPE_UNION); + if (ZEND_TYPE_HAS_LIST(type)) { + const zend_type *type_ptr; + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), type_ptr) { + type_ast = zend_ast_list_add(type_ast, zp_type_to_ast(*type_ptr)); + } ZEND_TYPE_LIST_FOREACH_END(); + } else if (ZEND_TYPE_HAS_NAME(type)) { + zend_ast *name_ast = zp_type_name_to_ast( + zend_string_copy(ZEND_TYPE_NAME(type))); + type_ast = zend_ast_list_add(type_ast, name_ast); + } else if (ZEND_TYPE_IS_COMPLEX(type)) { + ZEND_UNREACHABLE(); + } + uint32_t type_mask = ZEND_TYPE_PURE_MASK(type); + if ((type_mask & MAY_BE_BOOL) == MAY_BE_BOOL) { + type_ast = zend_ast_list_add(type_ast, zp_single_may_be_type_to_ast(MAY_BE_BOOL)); + type_mask &= ~MAY_BE_BOOL; + } + for (uint32_t may_be_type = 1; may_be_type < _ZEND_TYPE_MAY_BE_MASK; may_be_type <<= 1) { + if (type_mask & may_be_type) { + type_ast = zend_ast_list_add(type_ast, zp_single_may_be_type_to_ast(may_be_type)); + } + } + return type_ast; + } + + if (ZEND_TYPE_IS_INTERSECTION(type)) { + zend_ast *type_ast = zend_ast_create_list(0, ZEND_AST_TYPE_INTERSECTION); + const zend_type *type_ptr; + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), type_ptr) { + type_ast = zend_ast_list_add(type_ast, zp_type_to_ast(*type_ptr)); + } ZEND_TYPE_LIST_FOREACH_END(); + ZEND_ASSERT(!ZEND_TYPE_PURE_MASK(type)); + return type_ast; + } + + if (ZEND_TYPE_HAS_NAME(type)) { + zend_ast *type_ast = zp_type_name_to_ast( + zend_string_copy(ZEND_TYPE_NAME(type))); + return type_ast; + } + + ZEND_ASSERT(!ZEND_TYPE_IS_COMPLEX(type)); + + uint32_t type_mask = ZEND_TYPE_PURE_MASK(type); + ZEND_ASSERT(zp_is_single_may_be_type(type_mask)); + + return zp_single_may_be_type_to_ast(type_mask); +} + +/* Can not use zend_argument_error() as the function is not on the stack */ +static zend_never_inline ZEND_COLD void zp_argument_error(zend_class_entry *error_ce, + zend_function *function, uint32_t arg_num, const char *format, ...) +{ + zend_string *func_name = get_function_or_method_name(function); + const char *arg_name = get_function_arg_name(function, arg_num); + + char *message = NULL; + + va_list va; + va_start(va, format); + zend_vspprintf(&message, 0, format, va); + va_end(va); + + zend_throw_error(error_ce, "%s(): Argument #%d%s%s%s %s", + ZSTR_VAL(func_name), arg_num, + arg_name ? " ($" : "", arg_name ? arg_name : "", arg_name ? ")" : "", message + ); + efree(message); + zend_string_release(func_name); +} + +static zend_result zp_get_param_default_value(zval *result, zend_function *function, uint32_t arg_offset) +{ + ZEND_ASSERT(arg_offset < function->common.num_args); + + if (function->type == ZEND_USER_FUNCTION) { + zend_op *opline = &function->op_array.opcodes[arg_offset]; + if (EXPECTED(opline->opcode == ZEND_RECV_INIT)) { + ZVAL_COPY(result, RT_CONSTANT(opline, opline->op2)); + return SUCCESS; + } else { + ZEND_ASSERT(opline->opcode == ZEND_RECV); + } + } else if (function->type == ZEND_INTERNAL_FUNCTION) { + if (function->common.fn_flags & ZEND_ACC_USER_ARG_INFO) { + goto error; + } + + const zend_arg_info *arg_info = &function->internal_function.arg_info[arg_offset]; + + if (zend_get_default_from_internal_arg_info(result, arg_info) == SUCCESS) { + return SUCCESS; + } + } + +error: + zp_argument_error(zend_ce_argument_count_error, function, arg_offset + 1, + "must be passed explicitly, because the default value is not known"); + + return FAILURE; +} + +static bool zp_arg_must_be_sent_by_ref(zend_function *function, uint32_t arg_num) +{ + if (EXPECTED(arg_num <= MAX_ARG_FLAG_NUM)) { + if (QUICK_ARG_MUST_BE_SENT_BY_REF(function, arg_num)) { + return true; + } + } else if (ARG_MUST_BE_SENT_BY_REF(function, arg_num)) { + return true; + } + return false; +} + +static zend_ast *zp_attribute_to_ast(zend_attribute *attribute) +{ + zend_ast *args_ast; + if (attribute->argc) { + args_ast = zend_ast_create_arg_list(0, ZEND_AST_ARG_LIST); + for (uint32_t i = 0; i < attribute->argc; i++) { + zend_ast *arg_ast = zend_ast_create_zval(&attribute->args[i].value); + if (attribute->args[i].name) { + arg_ast = zend_ast_create(ZEND_AST_NAMED_ARG, + zend_ast_create_zval_from_str( + zend_string_copy(attribute->args[i].name)), + arg_ast); + } + args_ast = zend_ast_list_add(args_ast, arg_ast); + } + } else { + args_ast = NULL; + } + return zend_ast_create(ZEND_AST_ATTRIBUTE, + zend_ast_create_zval_from_str(zend_string_copy(attribute->name)), + args_ast); +} + +static zend_ast *zp_param_attributes_to_ast(zend_function *function, + uint32_t offset) +{ + zend_ast *attributes_ast = NULL; + if (!function->common.attributes) { + return NULL; + } + + /* Inherit the SensitiveParameter attribute */ + zend_attribute *attr = zend_get_parameter_attribute_str( + function->common.attributes, + "sensitiveparameter", strlen("sensitiveparameter"), offset); + if (attr) { + attributes_ast = zend_ast_create_list(1, ZEND_AST_ATTRIBUTE_GROUP, + zp_attribute_to_ast(attr)); + attributes_ast = zend_ast_create_list(1, ZEND_AST_ATTRIBUTE_LIST, + attributes_ast); + } + + return attributes_ast; +} + +static zend_string *zp_pfa_name(const zend_op_array *declaring_op_array, + const zend_op *declaring_opline) +{ + zend_string *filename = declaring_op_array->filename; + uint32_t start_lineno = declaring_opline->lineno; + + zend_string *class = zend_empty_string; + zend_string *separator = zend_empty_string; + zend_string *function = filename; + const char *parens = ""; + + if (declaring_op_array->function_name) { + if (declaring_op_array->fn_flags & ZEND_ACC_CLOSURE) { + /* If the parent function is a closure, don't redundantly + * add the classname and parentheses. + */ + function = declaring_op_array->function_name; + } else { + function = declaring_op_array->function_name; + parens = "()"; + + if (declaring_op_array->scope && declaring_op_array->scope->name) { + class = declaring_op_array->scope->name; + separator = ZSTR_KNOWN(ZEND_STR_PAAMAYIM_NEKUDOTAYIM); + } + } + } + + zend_string *name = zend_strpprintf_unchecked( + 0, + "{closure:pfa:%S%S%S%s:%" PRIu32 "}", + class, + separator, + function, + parens, + start_lineno + ); + + return name; +} + +/* Generate the AST for calling the actual function */ +static zend_ast *zp_compile_forwarding_call( + zval *this_ptr, zend_function *function, + uint32_t argc, zval *argv, zend_array *extra_named_params, + zend_string **param_names, bool variadic_partial, uint32_t num_args, + zend_class_entry *called_scope, zend_type return_type, + bool forward_superfluous_args, + zend_ast *stmts_ast) +{ + bool is_assert = zend_string_equals(function->common.function_name, + ZSTR_KNOWN(ZEND_STR_ASSERT)); + + zend_ast *args_ast = zend_ast_create_list(0, ZEND_AST_ARG_LIST); + zend_ast *call_ast = NULL; + + if (is_assert) { + /* We are going to call assert() dynamically (via call_user_func), + * otherwise assert() would print the generated AST on failure, which is + * irrelevant. */ + args_ast = zend_ast_list_add(args_ast, + zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_ASSERT))); + } + + for (uint32_t offset = 0; offset < argc; offset++) { + if (Z_ISUNDEF(argv[offset])) { + /* Argument was not passed. Pass its default value. */ + if (offset < function->common.required_num_args) { + /* Required param was not passed. This can happen due to named + * args. Using the same exception CE and message as + * zend_handle_undef_args(). */ + zp_argument_error(zend_ce_argument_count_error, function, + offset + 1, "not passed"); + goto error; + } + zval default_value; + if (zp_get_param_default_value(&default_value, function, offset) == FAILURE) { + ZEND_ASSERT(EG(exception)); + goto error; + } + zend_ast *default_value_ast; + if (Z_TYPE(default_value) == IS_CONSTANT_AST) { + default_value_ast = zend_ast_dup(Z_ASTVAL(default_value)); + } else { + default_value_ast = zend_ast_create_zval(&default_value); + } + args_ast = zend_ast_list_add(args_ast, default_value_ast); + } else { + args_ast = zend_ast_list_add(args_ast, zend_ast_create(ZEND_AST_VAR, + zend_ast_create_zval_from_str(zend_string_copy(param_names[offset])))); + } + } + if (extra_named_params) { + args_ast = zend_ast_list_add(args_ast, zend_ast_create(ZEND_AST_UNPACK, + zend_ast_create(ZEND_AST_VAR, + zend_ast_create_zval_from_str(zend_string_copy(param_names[argc + variadic_partial]))))); + } + if (variadic_partial) { + if (function->common.fn_flags & ZEND_ACC_VARIADIC) { + args_ast = zend_ast_list_add(args_ast, zend_ast_create(ZEND_AST_UNPACK, + zend_ast_create(ZEND_AST_VAR, + zend_ast_create_zval_from_str(zend_string_copy(param_names[argc]))))); + } else if (forward_superfluous_args) { + /* When a '...' placeholder is used, and the underlying function is + * not variadic, superfluous arguments are forwarded. + * Add a ...array_slice(func_get_args(), n) argument, which should + * be compiled as ZEND_AST_UNPACK + ZEND_FUNC_GET_ARGS. */ + + zend_ast *func_get_args_name_ast = zend_ast_create_zval_from_str( + zend_string_copy(ZSTR_KNOWN(ZEND_STR_FUNC_GET_ARGS))); + func_get_args_name_ast->attr = ZEND_NAME_FQ; + + zend_ast *array_slice_name_ast = zend_ast_create_zval_from_str( + zend_string_copy(ZSTR_KNOWN(ZEND_STR_ARRAY_SLICE))); + array_slice_name_ast->attr = ZEND_NAME_FQ; + + args_ast = zend_ast_list_add(args_ast, + zend_ast_create(ZEND_AST_UNPACK, + zend_ast_create(ZEND_AST_CALL, + array_slice_name_ast, + zend_ast_create_list(2, ZEND_AST_ARG_LIST, + zend_ast_create(ZEND_AST_CALL, + func_get_args_name_ast, + zend_ast_create_list(0, ZEND_AST_ARG_LIST)), + zend_ast_create_zval_from_long(num_args))))); + } + } + + if (is_assert) { + zend_ast *func_name_ast = zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_CALL_USER_FUNC)); + func_name_ast->attr = ZEND_NAME_FQ; + call_ast = zend_ast_create(ZEND_AST_CALL, func_name_ast, args_ast); + } else if (function->common.fn_flags & ZEND_ACC_CLOSURE) { + zend_ast *fn_ast = zend_ast_create(ZEND_AST_VAR, + zend_ast_create_zval_from_str(zend_string_copy(param_names[argc + variadic_partial + (extra_named_params != NULL)]))); + call_ast = zend_ast_create(ZEND_AST_CALL, fn_ast, args_ast); + } else if (Z_TYPE_P(this_ptr) == IS_OBJECT) { + zend_ast *this_ast = zend_ast_create(ZEND_AST_VAR, + zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_THIS))); + zend_ast *method_name_ast = zend_ast_create_zval_from_str( + zend_string_copy(function->common.function_name)); + call_ast = zend_ast_create(ZEND_AST_METHOD_CALL, this_ast, + method_name_ast, args_ast); + } else if (called_scope) { + zend_ast *class_name_ast = zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_STATIC)); + class_name_ast->attr = ZEND_NAME_NOT_FQ; + zend_ast *method_name_ast = zend_ast_create_zval_from_str( + zend_string_copy(function->common.function_name)); + call_ast = zend_ast_create(ZEND_AST_STATIC_CALL, class_name_ast, + method_name_ast, args_ast); + } else { + zend_ast *func_name_ast = zend_ast_create_zval_from_str(zend_string_copy(function->common.function_name)); + func_name_ast->attr = ZEND_NAME_FQ; + call_ast = zend_ast_create(ZEND_AST_CALL, func_name_ast, args_ast); + } + + /* Void functions can not 'return $expr' */ + if (ZEND_TYPE_FULL_MASK(return_type) & MAY_BE_VOID) { + stmts_ast = zend_ast_list_add(stmts_ast, call_ast); + } else { + zend_ast *return_ast = zend_ast_create(ZEND_AST_RETURN, call_ast); + stmts_ast = zend_ast_list_add(stmts_ast, return_ast); + } + + return stmts_ast; + +error: + zend_ast_destroy(args_ast); + zend_ast_destroy(call_ast); + return NULL; +} + +static uint32_t zp_compute_num_required(zend_function *function, + uint32_t orig_offset, uint32_t new_offset, uint32_t num_required) { + if (orig_offset < function->common.num_args) { + if (orig_offset < function->common.required_num_args) { + num_required = MAX(num_required, new_offset + 1); + } + } else { + ZEND_ASSERT(function->common.fn_flags & ZEND_ACC_VARIADIC); + /* Placeholders that run into the variadic portion become + * required and make all params before them required */ + ZEND_ASSERT(orig_offset >= num_required); + num_required = new_offset + 1; + } + + return num_required; +} + +/* Functions that do not allow to be called dynamically */ +static const zend_known_string_id zp_non_dynamic_call_funcs[] = { + ZEND_STR_FUNC_GET_ARG, + ZEND_STR_COMPACT, + ZEND_STR_EXTRACT, + /* Omit nullary functions such as func_num_args(), as these can't be PFA'd*/ +}; + +static bool zp_is_non_dynamic_call_func(zend_function *function) +{ + for (int i = 0; i < sizeof(zp_non_dynamic_call_funcs) / sizeof(zp_non_dynamic_call_funcs[0]); i++) { + if (zend_string_equals(function->common.function_name, ZSTR_KNOWN(zp_non_dynamic_call_funcs[i]))) { + return true; + } + } + + return false; +} + +/* Compile PFA to an op_array */ +static zend_op_array *zp_compile(zval *this_ptr, zend_function *function, + uint32_t argc, zval *argv, zend_array *extra_named_params, + const zend_array *named_positions, + const zend_op_array *declaring_op_array, + const zend_op *declaring_opline, void **cache_slot, + bool uses_variadic_placeholder) { + + zend_op_array *op_array = NULL; + + if (UNEXPECTED(zp_is_non_dynamic_call_func(function))) { + zend_throw_error(NULL, "Cannot call %.*s() dynamically", + (int) ZSTR_LEN(function->common.function_name), ZSTR_VAL(function->common.function_name)); + return NULL; + } + + if (UNEXPECTED(zp_args_check(function, argc, argv, extra_named_params, uses_variadic_placeholder) != SUCCESS)) { + ZEND_ASSERT(EG(exception)); + return NULL; + } + + zend_class_entry *called_scope; + if (Z_TYPE_P(this_ptr) == IS_OBJECT) { + called_scope = Z_OBJCE_P(this_ptr); + } else { + called_scope = Z_CE_P(this_ptr); + } + + zend_arena *orig_ast_arena = CG(ast_arena); + CG(ast_arena) = zend_arena_create(1024 * 4); + + int orig_lineno = CG(zend_lineno); + CG(zend_lineno) = zend_get_executed_lineno(); + + int new_argc = argc; + + if (uses_variadic_placeholder) { + new_argc = MAX(new_argc, function->common.num_args); + } + + zval *tmp = zend_arena_alloc(&CG(ast_arena), new_argc * sizeof(zval)); + memcpy(tmp, argv, argc * sizeof(zval)); + argv = tmp; + + /* Compute number of required args and param positions, add implicit + * placeholders. + * + * Parameters are placed in the following order: + * - Positional placeholders + * - Then named placeholders in their syntax order + * - Then implicit placeholders added by '...' + */ + uint32_t num_params = 0; + uint32_t num_required = 0; + uint32_t *arg_to_param_offset_map = zend_arena_alloc(&CG(ast_arena), sizeof(uint32_t*) * new_argc); + { + uint32_t num_positional = 0; + + /* First, we handle explicit placeholders */ + for (uint32_t arg_offset = 0; arg_offset < argc; arg_offset++) { + if (!Z_IS_PLACEHOLDER_P(&argv[arg_offset])) { + continue; + } + + num_params++; + + zend_arg_info *arg_info = &function->common.arg_info[MIN(arg_offset, function->common.num_args)]; + zval *named_pos = named_positions ? zend_hash_find(named_positions, arg_info->name) : NULL; + uint32_t param_offset; + if (named_pos) { + /* Placeholder is sent as named arg. 'num_positional' can not + * change at this point. */ + param_offset = num_positional + Z_LVAL_P(named_pos); + } else { + /* Placeholder is sent as positional */ + param_offset = num_positional++; + } + + arg_to_param_offset_map[arg_offset] = param_offset; + + num_required = zp_compute_num_required(function, + arg_offset, param_offset, num_required); + } + + /* Handle implicit placeholders added by '...' */ + if (uses_variadic_placeholder) { + for (uint32_t arg_offset = 0; arg_offset < new_argc; arg_offset++) { + if (arg_offset < argc && !Z_ISUNDEF(argv[arg_offset])) { + continue; + } + + /* Unspecified parameters become placeholders */ + Z_TYPE_INFO(argv[arg_offset]) = _IS_PLACEHOLDER; + + num_params++; + + uint32_t param_offset = num_params - 1; + + arg_to_param_offset_map[arg_offset] = param_offset; + + num_required = zp_compute_num_required(function, + arg_offset, param_offset, num_required); + } + } + } + + argc = new_argc; + + /* Assign variable names */ + + uint32_t num_names = argc + uses_variadic_placeholder + (extra_named_params != NULL) + + ((function->common.fn_flags & ZEND_ACC_CLOSURE) != 0); + zend_string **param_names = zend_arena_calloc(&CG(ast_arena), + num_names, sizeof(zend_string*)); + memset(param_names, 0, sizeof(zend_string*) * num_names); + zp_assign_names(param_names, num_names, argc, argv, function, + uses_variadic_placeholder, extra_named_params); + + /* Generate AST */ + + zend_ast *lexical_vars_ast = zend_ast_create_list(0, ZEND_AST_CLOSURE_USES); + zend_ast *params_ast = zend_ast_create_list(0, ZEND_AST_ARG_LIST); + zend_ast *return_type_ast = NULL; + zend_ast *stmts_ast = zend_ast_create_list(0, ZEND_AST_STMT_LIST); + zend_ast *attributes_ast = NULL; + + /* Generate AST for params and lexical vars */ + { + /* The inner Closure, if any, is assumed to be the first lexical var by + * do_closure_bind(). */ + if (function->common.fn_flags & ZEND_ACC_CLOSURE) { + zend_ast *lexical_var_ast = zend_ast_create_zval_from_str( + zend_string_copy(param_names[argc + uses_variadic_placeholder + (extra_named_params != NULL)])); + lexical_vars_ast = zend_ast_list_add(lexical_vars_ast, lexical_var_ast); + } + + zend_ast **params = zend_arena_calloc(&CG(ast_arena), num_params, sizeof(zend_ast*)); + for (uint32_t offset = 0; offset < argc; offset++) { + if (Z_IS_PLACEHOLDER_P(&argv[offset])) { + zend_arg_info *arg_info = &function->common.arg_info[MIN(offset, function->common.num_args)]; + + int param_flags = 0; + if (zp_arg_must_be_sent_by_ref(function, offset+1)) { + param_flags |= ZEND_PARAM_REF; + } + + uint32_t param_offset = arg_to_param_offset_map[offset]; + zend_ast *param_type_ast = zp_type_to_ast(arg_info->type); + zend_ast *default_value_ast = NULL; + if (param_offset >= num_required) { + zval default_value; + if (zp_get_param_default_value(&default_value, function, offset) == FAILURE) { + for (uint32_t i = 0; i < num_params; i++) { + zend_ast_destroy(params[i]); + } + goto error; + } + default_value_ast = zend_ast_create_zval(&default_value); + } + + ZEND_ASSERT(offset < function->common.num_args || (function->common.fn_flags & ZEND_ACC_VARIADIC)); + + zend_ast *attributes_ast = zp_param_attributes_to_ast(function, MIN(offset, function->common.num_args)); + params[param_offset] = zend_ast_create_ex(ZEND_AST_PARAM, + param_flags, param_type_ast, + zend_ast_create_zval_from_str( + zend_string_copy(param_names[offset])), + default_value_ast, attributes_ast, NULL, NULL); + + } else if (!Z_ISUNDEF(argv[offset])) { + // TODO: If the pre-bound parameter is a literal, it can be a + // literal in the function body instead of a lexical var. + zend_ast *lexical_var_ast = zend_ast_create_zval_from_str( + zend_string_copy(param_names[offset])); + if (zp_arg_must_be_sent_by_ref(function, offset+1)) { + lexical_var_ast->attr = ZEND_BIND_REF; + } + lexical_vars_ast = zend_ast_list_add( + lexical_vars_ast, lexical_var_ast); + } + } + + for (uint32_t i = 0; i < num_params; i++) { + params_ast = zend_ast_list_add(params_ast, params[i]); + } + } + + if (extra_named_params) { + zend_ast *lexical_var_ast = zend_ast_create_zval_from_str( + zend_string_copy(param_names[argc + uses_variadic_placeholder])); + lexical_vars_ast = zend_ast_list_add(lexical_vars_ast, lexical_var_ast); + } + + /* If we have a variadic placeholder and the underlying function is + * variadic, add a variadic param. */ + if (uses_variadic_placeholder + && (function->common.fn_flags & ZEND_ACC_VARIADIC)) { + zend_arg_info *arg_info = &function->common.arg_info[function->common.num_args]; + int param_flags = ZEND_PARAM_VARIADIC; + if (zp_arg_must_be_sent_by_ref(function, function->common.num_args+1)) { + param_flags |= ZEND_PARAM_REF; + } + zend_ast *param_type_ast = zp_type_to_ast(arg_info->type); + zend_ast *attributes_ast = zp_param_attributes_to_ast(function, function->common.num_args); + params_ast = zend_ast_list_add(params_ast, zend_ast_create_ex(ZEND_AST_PARAM, + param_flags, param_type_ast, + zend_ast_create_zval_from_str( + zend_string_copy(param_names[argc])), + NULL, attributes_ast, NULL, NULL)); + } + + zend_type return_type = {0}; + if (function->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { + return_type = (function->common.arg_info-1)->type; + return_type_ast = zp_type_to_ast(return_type); + } + + /** + * Generate function body. + * + * If we may need to forward superflous arguments, do that conditionally, as + * it's faster: + * + * if (func_num_args() <= n) { + * // normal call + * } else { + * // call with superflous arg forwarding + * } + * + * The func_num_args() call should be compiled to a single FUNC_NUM_ARGS op. + */ + + if (uses_variadic_placeholder && !(function->common.fn_flags & ZEND_ACC_VARIADIC)) { + zend_ast *no_forwarding_ast = zend_ast_create_list(0, ZEND_AST_STMT_LIST); + zend_ast *forwarding_ast = zend_ast_create_list(0, ZEND_AST_STMT_LIST); + + no_forwarding_ast = zp_compile_forwarding_call(this_ptr, function, + argc, argv, extra_named_params, + param_names, uses_variadic_placeholder, num_params, + called_scope, return_type, false, no_forwarding_ast); + + if (!no_forwarding_ast) { + ZEND_ASSERT(EG(exception)); + goto error; + } + + forwarding_ast = zp_compile_forwarding_call(this_ptr, function, + argc, argv, extra_named_params, + param_names, uses_variadic_placeholder, num_params, + called_scope, return_type, true, forwarding_ast); + + if (!forwarding_ast) { + ZEND_ASSERT(EG(exception)); + zend_ast_destroy(no_forwarding_ast); + goto error; + } + + zend_ast *func_num_args_name_ast = zend_ast_create_zval_from_str( + zend_string_copy(ZSTR_KNOWN(ZEND_STR_FUNC_NUM_ARGS))); + func_num_args_name_ast->attr = ZEND_NAME_FQ; + + stmts_ast = zend_ast_list_add(stmts_ast, + zend_ast_create_list(2, ZEND_AST_IF, + zend_ast_create(ZEND_AST_IF_ELEM, + zend_ast_create_binary_op(ZEND_IS_SMALLER_OR_EQUAL, + zend_ast_create(ZEND_AST_CALL, func_num_args_name_ast, + zend_ast_create_list(0, ZEND_AST_ARG_LIST)), + zend_ast_create_zval_from_long(num_params)), + no_forwarding_ast), + zend_ast_create(ZEND_AST_IF_ELEM, + NULL, + forwarding_ast))); + } else { + stmts_ast = zp_compile_forwarding_call(this_ptr, function, + argc, argv, extra_named_params, + param_names, uses_variadic_placeholder, num_params, + called_scope, return_type, false, stmts_ast); + + if (!stmts_ast) { + ZEND_ASSERT(EG(exception)); + goto error; + } + } + + /* Inherit the NoDiscard attribute */ + if (function->common.attributes) { + zend_attribute *attr = zend_get_attribute_str( + function->common.attributes, "nodiscard", strlen("nodiscard")); + if (attr) { + attributes_ast = zend_ast_create_list(1, ZEND_AST_ATTRIBUTE_GROUP, + zp_attribute_to_ast(attr)); + attributes_ast = zend_ast_create_list(1, ZEND_AST_ATTRIBUTE_LIST, + attributes_ast); + } + } + + int closure_flags = function->common.fn_flags & ZEND_ACC_RETURN_REFERENCE; + zend_ast *closure_ast = zend_ast_create_decl(ZEND_AST_CLOSURE, + closure_flags, CG(zend_lineno), NULL, + NULL, params_ast, lexical_vars_ast, stmts_ast, + return_type_ast, attributes_ast); + + if (Z_TYPE_P(this_ptr) != IS_OBJECT || IS_STATIC_CLOSURE(function)) { + ((zend_ast_decl*)closure_ast)->flags |= ZEND_ACC_STATIC; + } + +#if ZEND_DEBUG + { + const char *tmp = getenv("DUMP_PFA_AST"); + if (tmp && ZEND_ATOL(tmp)) { + zend_string *str = zend_ast_export("", closure_ast, ""); + fprintf(stderr, "PFA AST: %s\n", ZSTR_VAL(str)); + zend_string_release(str); + } + } +#endif + + zend_string *pfa_name = zp_pfa_name(declaring_op_array, declaring_opline); + + op_array = zend_accel_compile_pfa(closure_ast, declaring_op_array, + declaring_opline, function, pfa_name); + + zend_ast_destroy(closure_ast); + +clean: + for (uint32_t i = 0; i < num_names; i++) { + if (param_names[i]) { + zend_string_release(param_names[i]); + } + } + + zend_arena_destroy(CG(ast_arena)); + CG(ast_arena) = orig_ast_arena; + CG(zend_lineno) = orig_lineno; + + return op_array; + +error: + zend_ast_destroy(lexical_vars_ast); + zend_ast_destroy(params_ast); + zend_ast_destroy(return_type_ast); + zend_ast_destroy(stmts_ast); + zend_ast_destroy(attributes_ast); + goto clean; +} + +static zend_op_array *zp_get_op_array(zval *this_ptr, zend_function *function, + uint32_t argc, zval *argv, zend_array *extra_named_params, + const zend_array *named_positions, + const zend_op_array *declaring_op_array, + const zend_op *declaring_opline, void **cache_slot, + bool uses_variadic_placeholder) { + + if (EXPECTED(function->type == ZEND_INTERNAL_FUNCTION + ? cache_slot[0] == function + : cache_slot[0] == function->op_array.opcodes)) { + return cache_slot[1]; + } + + zend_op_array *op_array = zend_accel_pfa_cache_get(declaring_op_array, + declaring_opline, function); + + if (UNEXPECTED(!op_array)) { + op_array = zp_compile(this_ptr, function, argc, argv, + extra_named_params, named_positions, declaring_op_array, declaring_opline, + cache_slot, uses_variadic_placeholder); + } + + if (EXPECTED(op_array) && !(function->common.fn_flags & ZEND_ACC_NEVER_CACHE)) { + cache_slot[0] = function->type == ZEND_INTERNAL_FUNCTION + ? (void*)function + : (void*)function->op_array.opcodes; + cache_slot[1] = op_array; + } + + return op_array; +} + +/* Bind pre-bound arguments as lexical vars */ +static void zp_bind(zval *result, zend_function *function, uint32_t argc, zval *argv, + zend_array *extra_named_params) { + + zend_arg_info *arg_infos = function->common.arg_info; + uint32_t bind_offset = 0; + + if (function->common.fn_flags & ZEND_ACC_CLOSURE) { + zval var; + ZVAL_OBJ(&var, ZEND_CLOSURE_OBJECT(function)); + Z_ADDREF(var); + zend_closure_bind_var_ex(result, bind_offset, &var); + bind_offset += sizeof(Bucket); + } + + for (uint32_t offset = 0; offset < argc; offset++) { + zval *var = &argv[offset]; + if (Z_IS_PLACEHOLDER_P(var) || Z_ISUNDEF_P(var)) { + continue; + } + zend_arg_info *arg_info; + if (offset < function->common.num_args) { + arg_info = &arg_infos[offset]; + } else if (function->common.fn_flags & ZEND_ACC_VARIADIC) { + arg_info = &arg_infos[function->common.num_args]; + } else { + arg_info = NULL; + } + if (arg_info && ZEND_TYPE_IS_SET(arg_info->type) + && UNEXPECTED(!zend_check_type_ex(&arg_info->type, var, function->common.scope, 0, 0))) { + zend_verify_arg_error(function, arg_info, offset+1, var); + zval_ptr_dtor(result); + ZVAL_NULL(result); + return; + } + ZEND_ASSERT(zp_arg_must_be_sent_by_ref(function, offset+1) ? Z_ISREF_P(var) : !Z_ISREF_P(var)); + zend_closure_bind_var_ex(result, bind_offset, var); + bind_offset += sizeof(Bucket); + } + + if (extra_named_params) { + zval var; + ZVAL_ARR(&var, extra_named_params); + Z_ADDREF(var); + zend_closure_bind_var_ex(result, bind_offset, &var); + } +} + +void zend_partial_create(zval *result, zval *this_ptr, zend_function *function, + uint32_t argc, zval *argv, zend_array *extra_named_params, + const zend_array *named_positions, + const zend_op_array *declaring_op_array, + const zend_op *declaring_opline, void **cache_slot, + bool uses_variadic_placeholder) { + + zend_op_array *op_array = zp_get_op_array(this_ptr, function, argc, argv, + extra_named_params, named_positions, + declaring_op_array, declaring_opline, + cache_slot, uses_variadic_placeholder); + + if (UNEXPECTED(!op_array)) { + ZEND_ASSERT(EG(exception)); + ZVAL_NULL(result); + return; + } + + zend_class_entry *called_scope; + zval object; + + if (Z_TYPE_P(this_ptr) == IS_OBJECT) { + called_scope = Z_OBJCE_P(this_ptr); + } else { + called_scope = Z_CE_P(this_ptr); + } + + if (Z_TYPE_P(this_ptr) == IS_OBJECT && !IS_STATIC_CLOSURE(function)) { + ZVAL_COPY_VALUE(&object, this_ptr); + } else { + ZVAL_UNDEF(&object); + } + + zend_create_partial_closure(result, (zend_function*)op_array, + function->common.scope, called_scope, &object, + (function->common.fn_flags & ZEND_ACC_CLOSURE) != 0); + + zp_bind(result, function, argc, argv, extra_named_params); +} + +void zend_partial_op_array_dtor(zval *pDest) +{ + destroy_op_array(Z_PTR_P(pDest)); +} diff --git a/Zend/zend_partial.h b/Zend/zend_partial.h new file mode 100644 index 0000000000000..7999dc99019c1 --- /dev/null +++ b/Zend/zend_partial.h @@ -0,0 +1,34 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine | + +----------------------------------------------------------------------+ + | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) | + +----------------------------------------------------------------------+ + | This source file is subject to version 2.00 of the Zend license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.zend.com/license/2_00.txt. | + | If you did not receive a copy of the Zend license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@zend.com so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ +*/ +#ifndef ZEND_PARTIAL_H +#define ZEND_PARTIAL_H + +#include "zend_compile.h" + +BEGIN_EXTERN_C() + +void zend_partial_create(zval *result, zval *this_ptr, zend_function *function, + uint32_t argc, zval *argv, zend_array *extra_named_params, + const zend_array *named_positions, + const zend_op_array *declaring_op_array, + const zend_op *declaring_opline, void **cache_slot, + bool uses_variadic_placeholder); + +void zend_partial_op_array_dtor(zval *pDest); + +END_EXTERN_C() + +#endif diff --git a/Zend/zend_string.h b/Zend/zend_string.h index 430f4dec02e22..512b7f4297331 100644 --- a/Zend/zend_string.h +++ b/Zend/zend_string.h @@ -630,6 +630,14 @@ default: ZEND_UNREACHABLE(); _(ZEND_STR_AUTOGLOBAL_ENV, "_ENV") \ _(ZEND_STR_AUTOGLOBAL_REQUEST, "_REQUEST") \ _(ZEND_STR_COUNT, "count") \ + _(ZEND_STR_FUNC_NUM_ARGS, "func_num_args") \ + _(ZEND_STR_FUNC_GET_ARGS, "func_get_args") \ + _(ZEND_STR_FUNC_GET_ARG, "func_get_arg") \ + _(ZEND_STR_COMPACT, "compact") \ + _(ZEND_STR_EXTRACT, "extract") \ + _(ZEND_STR_ASSERT, "assert") \ + _(ZEND_STR_CALL_USER_FUNC, "call_user_func") \ + _(ZEND_STR_ARRAY_SLICE, "array_slice") \ _(ZEND_STR_SENSITIVEPARAMETER, "SensitiveParameter") \ _(ZEND_STR_CONST_EXPR_PLACEHOLDER, "[constant expression]") \ _(ZEND_STR_DEPRECATED_CAPITALIZED, "Deprecated") \ diff --git a/Zend/zend_types.h b/Zend/zend_types.h index dc6ea2c800ee0..06ee60f8627bb 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -857,6 +857,8 @@ static zend_always_inline uint32_t zval_gc_info(uint32_t gc_type_info) { #define IS_OBJ_LAZY_UNINITIALIZED (1U<<31) /* Virtual proxy or uninitialized Ghost */ #define IS_OBJ_LAZY_PROXY (1U<<30) /* Virtual proxy (may be initialized) */ +#define OBJ_EXTRA_FLAG_PRIV_1 (1U<<29) /* Reserved for private use by the object itself */ +#define OBJ_EXTRA_FLAG_PRIV_2 (1U<<28) /* Reserved for private use by the object itself */ #define OBJ_EXTRA_FLAGS(obj) ((obj)->extra_flags) diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 391b82241e477..b22280462a4bd 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -5717,6 +5717,30 @@ ZEND_VM_HELPER(zend_verify_recv_arg_type_helper, ANY, ANY, zval *op_1) ZEND_VM_NEXT_OPCODE(); } +ZEND_VM_HANDLER(213, ZEND_SEND_PLACEHOLDER, UNUSED, CONST|UNUSED) +{ + zval *arg; + + if (OP2_TYPE == IS_CONST) { + /* Named placeholder */ + USE_OPLINE + SAVE_OPLINE(); + zend_string *arg_name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); + uint32_t arg_num; + arg = zend_handle_named_arg(&EX(call), arg_name, &arg_num, CACHE_ADDR(opline->result.num)); + if (UNEXPECTED(!arg)) { + HANDLE_EXCEPTION(); + } + } else { + /* Positional placeholder */ + arg = ZEND_CALL_VAR(EX(call), opline->result.var); + } + + Z_TYPE_INFO_P(arg) = _IS_PLACEHOLDER; + + ZEND_VM_NEXT_OPCODE(); +} + ZEND_VM_HOT_HANDLER(63, ZEND_RECV, NUM, UNUSED) { USE_OPLINE @@ -9830,6 +9854,45 @@ ZEND_VM_HANDLER(202, ZEND_CALLABLE_CONVERT, UNUSED, UNUSED, NUM|CACHE_SLOT) ZEND_VM_NEXT_OPCODE(); } +ZEND_VM_HANDLER(212, ZEND_CALLABLE_CONVERT_PARTIAL, CACHE_SLOT, CONST|UNUSED, NUM) +{ + USE_OPLINE + SAVE_OPLINE(); + + zend_execute_data *call = EX(call); + void **cache_slot = CACHE_ADDR(opline->op1.num); + zval *named_positions = GET_OP2_ZVAL_PTR(); + + zend_partial_create(EX_VAR(opline->result.var), + &call->This, call->func, + ZEND_CALL_NUM_ARGS(call), ZEND_CALL_ARG(call, 1), + ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS ? + call->extra_named_params : NULL, + OP2_TYPE == IS_CONST ? Z_ARRVAL_P(named_positions) : NULL, + &EX(func)->op_array, opline, cache_slot, + opline->extended_value & ZEND_FCALL_USES_VARIADIC_PLACEHOLDER); + + if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + zend_array_release(call->extra_named_params); + } + + if ((call->func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { + zend_free_trampoline(call->func); + } + + if (ZEND_CALL_INFO(call) & ZEND_CALL_RELEASE_THIS) { + OBJ_RELEASE(Z_OBJ(call->This)); + } else if (ZEND_CALL_INFO(call) & ZEND_CALL_CLOSURE) { + OBJ_RELEASE(ZEND_CLOSURE_OBJECT(call->func)); + } + + EX(call) = call->prev_execute_data; + + zend_vm_stack_free_call_frame(call); + + ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); +} + ZEND_VM_HANDLER(208, ZEND_JMP_FRAMELESS, CONST, JMP_ADDR, NUM|CACHE_SLOT) { USE_OPLINE diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index a2e5eac491dc3..60d394124a32a 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -4240,6 +4240,45 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_R ZEND_VM_NEXT_OPCODE(); } +static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + SAVE_OPLINE(); + + zend_execute_data *call = EX(call); + void **cache_slot = CACHE_ADDR(opline->op1.num); + zval *named_positions = RT_CONSTANT(opline, opline->op2); + + zend_partial_create(EX_VAR(opline->result.var), + &call->This, call->func, + ZEND_CALL_NUM_ARGS(call), ZEND_CALL_ARG(call, 1), + ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS ? + call->extra_named_params : NULL, + IS_CONST == IS_CONST ? Z_ARRVAL_P(named_positions) : NULL, + &EX(func)->op_array, opline, cache_slot, + opline->extended_value & ZEND_FCALL_USES_VARIADIC_PLACEHOLDER); + + if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + zend_array_release(call->extra_named_params); + } + + if ((call->func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { + zend_free_trampoline(call->func); + } + + if (ZEND_CALL_INFO(call) & ZEND_CALL_RELEASE_THIS) { + OBJ_RELEASE(Z_OBJ(call->This)); + } else if (ZEND_CALL_INFO(call) & ZEND_CALL_CLOSURE) { + OBJ_RELEASE(ZEND_CLOSURE_OBJECT(call->func)); + } + + EX(call) = call->prev_execute_data; + + zend_vm_stack_free_call_frame(call); + + ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); +} + static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_DYNAMIC_CALL_SPEC_TMP_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -4384,6 +4423,45 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_RECV_VARIADIC ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } +static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + SAVE_OPLINE(); + + zend_execute_data *call = EX(call); + void **cache_slot = CACHE_ADDR(opline->op1.num); + zval *named_positions = NULL; + + zend_partial_create(EX_VAR(opline->result.var), + &call->This, call->func, + ZEND_CALL_NUM_ARGS(call), ZEND_CALL_ARG(call, 1), + ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS ? + call->extra_named_params : NULL, + IS_UNUSED == IS_CONST ? Z_ARRVAL_P(named_positions) : NULL, + &EX(func)->op_array, opline, cache_slot, + opline->extended_value & ZEND_FCALL_USES_VARIADIC_PLACEHOLDER); + + if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + zend_array_release(call->extra_named_params); + } + + if ((call->func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { + zend_free_trampoline(call->func); + } + + if (ZEND_CALL_INFO(call) & ZEND_CALL_RELEASE_THIS) { + OBJ_RELEASE(Z_OBJ(call->This)); + } else if (ZEND_CALL_INFO(call) & ZEND_CALL_CLOSURE) { + OBJ_RELEASE(ZEND_CLOSURE_OBJECT(call->func)); + } + + EX(call) = call->prev_execute_data; + + zend_vm_stack_free_call_frame(call); + + ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); +} + static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_FRAMELESS_ICALL_1_SPEC_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -34430,6 +34508,30 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_CHECK_FUNC_AR ZEND_VM_NEXT_OPCODE(); } +static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_SEND_PLACEHOLDER_SPEC_UNUSED_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + zval *arg; + + if (IS_CONST == IS_CONST) { + /* Named placeholder */ + USE_OPLINE + SAVE_OPLINE(); + zend_string *arg_name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); + uint32_t arg_num; + arg = zend_handle_named_arg(&EX(call), arg_name, &arg_num, CACHE_ADDR(opline->result.num)); + if (UNEXPECTED(!arg)) { + HANDLE_EXCEPTION(); + } + } else { + /* Positional placeholder */ + arg = ZEND_CALL_VAR(EX(call), opline->result.var); + } + + Z_TYPE_INFO_P(arg) = _IS_PLACEHOLDER; + + ZEND_VM_NEXT_OPCODE(); +} + static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_FETCH_CONSTANT_SPEC_UNUSED_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -37054,6 +37156,30 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_C ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } +static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_SEND_PLACEHOLDER_SPEC_UNUSED_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + zval *arg; + + if (IS_UNUSED == IS_CONST) { + /* Named placeholder */ + USE_OPLINE + SAVE_OPLINE(); + zend_string *arg_name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); + uint32_t arg_num; + arg = zend_handle_named_arg(&EX(call), arg_name, &arg_num, CACHE_ADDR(opline->result.num)); + if (UNEXPECTED(!arg)) { + HANDLE_EXCEPTION(); + } + } else { + /* Positional placeholder */ + arg = ZEND_CALL_VAR(EX(call), opline->result.var); + } + + Z_TYPE_INFO_P(arg) = _IS_PLACEHOLDER; + + ZEND_VM_NEXT_OPCODE(); +} + static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_NEW_SPEC_UNUSED_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -56876,6 +57002,45 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_RECV_I ZEND_VM_NEXT_OPCODE(); } +static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_TAILCALL_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + SAVE_OPLINE(); + + zend_execute_data *call = EX(call); + void **cache_slot = CACHE_ADDR(opline->op1.num); + zval *named_positions = RT_CONSTANT(opline, opline->op2); + + zend_partial_create(EX_VAR(opline->result.var), + &call->This, call->func, + ZEND_CALL_NUM_ARGS(call), ZEND_CALL_ARG(call, 1), + ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS ? + call->extra_named_params : NULL, + IS_CONST == IS_CONST ? Z_ARRVAL_P(named_positions) : NULL, + &EX(func)->op_array, opline, cache_slot, + opline->extended_value & ZEND_FCALL_USES_VARIADIC_PLACEHOLDER); + + if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + zend_array_release(call->extra_named_params); + } + + if ((call->func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { + zend_free_trampoline(call->func); + } + + if (ZEND_CALL_INFO(call) & ZEND_CALL_RELEASE_THIS) { + OBJ_RELEASE(Z_OBJ(call->This)); + } else if (ZEND_CALL_INFO(call) & ZEND_CALL_CLOSURE) { + OBJ_RELEASE(ZEND_CLOSURE_OBJECT(call->func)); + } + + EX(call) = call->prev_execute_data; + + zend_vm_stack_free_call_frame(call); + + ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); +} + static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_DYNAMIC_CALL_SPEC_TMP_TAILCALL_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -57020,6 +57185,45 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_RECV_VARIADIC_SPEC ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } +static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_UNUSED_TAILCALL_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + SAVE_OPLINE(); + + zend_execute_data *call = EX(call); + void **cache_slot = CACHE_ADDR(opline->op1.num); + zval *named_positions = NULL; + + zend_partial_create(EX_VAR(opline->result.var), + &call->This, call->func, + ZEND_CALL_NUM_ARGS(call), ZEND_CALL_ARG(call, 1), + ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS ? + call->extra_named_params : NULL, + IS_UNUSED == IS_CONST ? Z_ARRVAL_P(named_positions) : NULL, + &EX(func)->op_array, opline, cache_slot, + opline->extended_value & ZEND_FCALL_USES_VARIADIC_PLACEHOLDER); + + if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + zend_array_release(call->extra_named_params); + } + + if ((call->func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { + zend_free_trampoline(call->func); + } + + if (ZEND_CALL_INFO(call) & ZEND_CALL_RELEASE_THIS) { + OBJ_RELEASE(Z_OBJ(call->This)); + } else if (ZEND_CALL_INFO(call) & ZEND_CALL_CLOSURE) { + OBJ_RELEASE(ZEND_CLOSURE_OBJECT(call->func)); + } + + EX(call) = call->prev_execute_data; + + zend_vm_stack_free_call_frame(call); + + ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); +} + static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_FRAMELESS_ICALL_1_SPEC_UNUSED_TAILCALL_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -86864,6 +87068,30 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_CHECK_FUNC_ARG_SPE ZEND_VM_NEXT_OPCODE(); } +static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_SEND_PLACEHOLDER_SPEC_UNUSED_CONST_TAILCALL_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + zval *arg; + + if (IS_CONST == IS_CONST) { + /* Named placeholder */ + USE_OPLINE + SAVE_OPLINE(); + zend_string *arg_name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); + uint32_t arg_num; + arg = zend_handle_named_arg(&EX(call), arg_name, &arg_num, CACHE_ADDR(opline->result.num)); + if (UNEXPECTED(!arg)) { + HANDLE_EXCEPTION(); + } + } else { + /* Positional placeholder */ + arg = ZEND_CALL_VAR(EX(call), opline->result.var); + } + + Z_TYPE_INFO_P(arg) = _IS_PLACEHOLDER; + + ZEND_VM_NEXT_OPCODE(); +} + static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_FETCH_CONSTANT_SPEC_UNUSED_CONST_TAILCALL_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -89488,6 +89716,30 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_CHECK_ ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } +static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_SEND_PLACEHOLDER_SPEC_UNUSED_UNUSED_TAILCALL_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + zval *arg; + + if (IS_UNUSED == IS_CONST) { + /* Named placeholder */ + USE_OPLINE + SAVE_OPLINE(); + zend_string *arg_name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); + uint32_t arg_num; + arg = zend_handle_named_arg(&EX(call), arg_name, &arg_num, CACHE_ADDR(opline->result.num)); + if (UNEXPECTED(!arg)) { + HANDLE_EXCEPTION(); + } + } else { + /* Positional placeholder */ + arg = ZEND_CALL_VAR(EX(call), opline->result.var); + } + + Z_TYPE_INFO_P(arg) = _IS_PLACEHOLDER; + + ZEND_VM_NEXT_OPCODE(); +} + static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_NEW_SPEC_UNUSED_UNUSED_TAILCALL_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -109169,6 +109421,16 @@ ZEND_API void execute_ex(zend_execute_data *ex) (void*)&&ZEND_INIT_PARENT_PROPERTY_HOOK_CALL_SPEC_CONST_UNUSED_LABEL, (void*)&&ZEND_DECLARE_ATTRIBUTED_CONST_SPEC_CONST_CONST_LABEL, (void*)&&ZEND_TYPE_ASSERT_SPEC_CONST_LABEL, + (void*)&&ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_LABEL, + (void*)&&ZEND_NULL_LABEL, + (void*)&&ZEND_NULL_LABEL, + (void*)&&ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_UNUSED_LABEL, + (void*)&&ZEND_NULL_LABEL, + (void*)&&ZEND_SEND_PLACEHOLDER_SPEC_UNUSED_CONST_LABEL, + (void*)&&ZEND_NULL_LABEL, + (void*)&&ZEND_NULL_LABEL, + (void*)&&ZEND_SEND_PLACEHOLDER_SPEC_UNUSED_UNUSED_LABEL, + (void*)&&ZEND_NULL_LABEL, (void*)&&ZEND_INIT_FCALL_OFFSET_SPEC_CONST_LABEL, (void*)&&ZEND_RECV_NOTYPE_SPEC_LABEL, (void*)&&ZEND_NULL_LABEL, @@ -110573,6 +110835,11 @@ ZEND_API void execute_ex(zend_execute_data *ex) ZEND_RECV_INIT_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); VM_TRACE_OP_END(ZEND_RECV_INIT_SPEC_CONST) HYBRID_BREAK(); + HYBRID_CASE(ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST): + VM_TRACE(ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST) + ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); + VM_TRACE_OP_END(ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST) + HYBRID_BREAK(); HYBRID_CASE(ZEND_INIT_DYNAMIC_CALL_SPEC_TMP): VM_TRACE(ZEND_INIT_DYNAMIC_CALL_SPEC_TMP) ZEND_INIT_DYNAMIC_CALL_SPEC_TMP_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); @@ -110588,6 +110855,11 @@ ZEND_API void execute_ex(zend_execute_data *ex) ZEND_RECV_VARIADIC_SPEC_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); VM_TRACE_OP_END(ZEND_RECV_VARIADIC_SPEC_UNUSED) HYBRID_BREAK(); + HYBRID_CASE(ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_UNUSED): + VM_TRACE(ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_UNUSED) + ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); + VM_TRACE_OP_END(ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_UNUSED) + HYBRID_BREAK(); HYBRID_CASE(ZEND_FRAMELESS_ICALL_1_SPEC_UNUSED): VM_TRACE(ZEND_FRAMELESS_ICALL_1_SPEC_UNUSED) ZEND_FRAMELESS_ICALL_1_SPEC_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); @@ -113811,6 +114083,11 @@ ZEND_API void execute_ex(zend_execute_data *ex) ZEND_CHECK_FUNC_ARG_SPEC_UNUSED_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); VM_TRACE_OP_END(ZEND_CHECK_FUNC_ARG_SPEC_UNUSED_CONST) HYBRID_BREAK(); + HYBRID_CASE(ZEND_SEND_PLACEHOLDER_SPEC_UNUSED_CONST): + VM_TRACE(ZEND_SEND_PLACEHOLDER_SPEC_UNUSED_CONST) + ZEND_SEND_PLACEHOLDER_SPEC_UNUSED_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); + VM_TRACE_OP_END(ZEND_SEND_PLACEHOLDER_SPEC_UNUSED_CONST) + HYBRID_BREAK(); HYBRID_CASE(ZEND_FETCH_CONSTANT_SPEC_UNUSED_CONST): VM_TRACE(ZEND_FETCH_CONSTANT_SPEC_UNUSED_CONST) ZEND_FETCH_CONSTANT_SPEC_UNUSED_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); @@ -113991,6 +114268,11 @@ ZEND_API void execute_ex(zend_execute_data *ex) ZEND_CHECK_UNDEF_ARGS_SPEC_UNUSED_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); VM_TRACE_OP_END(ZEND_CHECK_UNDEF_ARGS_SPEC_UNUSED_UNUSED) HYBRID_BREAK(); + HYBRID_CASE(ZEND_SEND_PLACEHOLDER_SPEC_UNUSED_UNUSED): + VM_TRACE(ZEND_SEND_PLACEHOLDER_SPEC_UNUSED_UNUSED) + ZEND_SEND_PLACEHOLDER_SPEC_UNUSED_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); + VM_TRACE_OP_END(ZEND_SEND_PLACEHOLDER_SPEC_UNUSED_UNUSED) + HYBRID_BREAK(); HYBRID_CASE(ZEND_NEW_SPEC_UNUSED_UNUSED): VM_TRACE(ZEND_NEW_SPEC_UNUSED_UNUSED) ZEND_NEW_SPEC_UNUSED_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); @@ -118107,6 +118389,16 @@ void zend_vm_init(void) ZEND_INIT_PARENT_PROPERTY_HOOK_CALL_SPEC_CONST_UNUSED_HANDLER, ZEND_DECLARE_ATTRIBUTED_CONST_SPEC_CONST_CONST_HANDLER, ZEND_TYPE_ASSERT_SPEC_CONST_HANDLER, + ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_HANDLER, + ZEND_NULL_HANDLER, + ZEND_NULL_HANDLER, + ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_UNUSED_HANDLER, + ZEND_NULL_HANDLER, + ZEND_SEND_PLACEHOLDER_SPEC_UNUSED_CONST_HANDLER, + ZEND_NULL_HANDLER, + ZEND_NULL_HANDLER, + ZEND_SEND_PLACEHOLDER_SPEC_UNUSED_UNUSED_HANDLER, + ZEND_NULL_HANDLER, ZEND_INIT_FCALL_OFFSET_SPEC_CONST_HANDLER, ZEND_RECV_NOTYPE_SPEC_HANDLER, ZEND_NULL_HANDLER, @@ -121585,6 +121877,16 @@ void zend_vm_init(void) ZEND_INIT_PARENT_PROPERTY_HOOK_CALL_SPEC_CONST_UNUSED_TAILCALL_HANDLER, ZEND_DECLARE_ATTRIBUTED_CONST_SPEC_CONST_CONST_TAILCALL_HANDLER, ZEND_TYPE_ASSERT_SPEC_CONST_TAILCALL_HANDLER, + ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_TAILCALL_HANDLER, + ZEND_NULL_TAILCALL_HANDLER, + ZEND_NULL_TAILCALL_HANDLER, + ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_UNUSED_TAILCALL_HANDLER, + ZEND_NULL_TAILCALL_HANDLER, + ZEND_SEND_PLACEHOLDER_SPEC_UNUSED_CONST_TAILCALL_HANDLER, + ZEND_NULL_TAILCALL_HANDLER, + ZEND_NULL_TAILCALL_HANDLER, + ZEND_SEND_PLACEHOLDER_SPEC_UNUSED_UNUSED_TAILCALL_HANDLER, + ZEND_NULL_TAILCALL_HANDLER, ZEND_INIT_FCALL_OFFSET_SPEC_CONST_TAILCALL_HANDLER, ZEND_RECV_NOTYPE_SPEC_TAILCALL_HANDLER, ZEND_NULL_TAILCALL_HANDLER, @@ -122553,7 +122855,7 @@ void zend_vm_init(void) 1255, 1256 | SPEC_RULE_OP1, 1261 | SPEC_RULE_OP1, - 3474, + 3484, 1266 | SPEC_RULE_OP1, 1271 | SPEC_RULE_OP1, 1276 | SPEC_RULE_OP2, @@ -122587,7 +122889,7 @@ void zend_vm_init(void) 1559 | SPEC_RULE_OP1 | SPEC_RULE_OP2, 1584 | SPEC_RULE_OP1, 1589, - 3474, + 3484, 1590 | SPEC_RULE_OP1, 1595 | SPEC_RULE_OP1 | SPEC_RULE_OP2, 1620 | SPEC_RULE_OP1 | SPEC_RULE_OP2, @@ -122720,50 +123022,50 @@ void zend_vm_init(void) 2556, 2557, 2558, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, + 2559 | SPEC_RULE_OP2, + 2564 | SPEC_RULE_OP2, + 3484, + 3484, + 3484, + 3484, + 3484, + 3484, + 3484, + 3484, + 3484, + 3484, + 3484, + 3484, + 3484, + 3484, + 3484, + 3484, + 3484, + 3484, + 3484, + 3484, + 3484, + 3484, + 3484, + 3484, + 3484, + 3484, + 3484, + 3484, + 3484, + 3484, + 3484, + 3484, + 3484, + 3484, + 3484, + 3484, + 3484, + 3484, + 3484, + 3484, + 3484, + 3484, }; #if 0 #elif (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID) @@ -122940,7 +123242,7 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2567 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 2577 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; if (op->op1_type < op->op2_type) { zend_swap_operands(op); } @@ -122948,7 +123250,7 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2592 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 2602 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; if (op->op1_type < op->op2_type) { zend_swap_operands(op); } @@ -122956,7 +123258,7 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2617 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 2627 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; if (op->op1_type < op->op2_type) { zend_swap_operands(op); } @@ -122967,17 +123269,17 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2642 | SPEC_RULE_OP1 | SPEC_RULE_OP2; + spec = 2652 | SPEC_RULE_OP1 | SPEC_RULE_OP2; } else if (op1_info == MAY_BE_LONG && op2_info == MAY_BE_LONG) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2667 | SPEC_RULE_OP1 | SPEC_RULE_OP2; + spec = 2677 | SPEC_RULE_OP1 | SPEC_RULE_OP2; } else if (op1_info == MAY_BE_DOUBLE && op2_info == MAY_BE_DOUBLE) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2692 | SPEC_RULE_OP1 | SPEC_RULE_OP2; + spec = 2702 | SPEC_RULE_OP1 | SPEC_RULE_OP2; } break; case ZEND_MUL: @@ -122988,17 +123290,17 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2717 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 2727 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; } else if (op1_info == MAY_BE_LONG && op2_info == MAY_BE_LONG) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2742 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 2752 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; } else if (op1_info == MAY_BE_DOUBLE && op2_info == MAY_BE_DOUBLE) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2767 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 2777 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; } break; case ZEND_IS_IDENTICAL: @@ -123009,16 +123311,16 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2792 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 2802 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } else if (op1_info == MAY_BE_DOUBLE && op2_info == MAY_BE_DOUBLE) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2867 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 2877 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } else if (op->op2_type == IS_CONST && (Z_TYPE_P(RT_CONSTANT(op, op->op2)) == IS_ARRAY && zend_hash_num_elements(Z_ARR_P(RT_CONSTANT(op, op->op2))) == 0)) { - spec = 3092 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 3102 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } else if (op->op1_type == IS_CV && (op->op2_type & (IS_CONST|IS_CV)) && !(op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) && !(op2_info & (MAY_BE_UNDEF|MAY_BE_REF))) { - spec = 3098 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 3108 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; } break; case ZEND_IS_NOT_IDENTICAL: @@ -123029,16 +123331,16 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2942 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 2952 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } else if (op1_info == MAY_BE_DOUBLE && op2_info == MAY_BE_DOUBLE) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 3017 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 3027 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } else if (op->op2_type == IS_CONST && (Z_TYPE_P(RT_CONSTANT(op, op->op2)) == IS_ARRAY && zend_hash_num_elements(Z_ARR_P(RT_CONSTANT(op, op->op2))) == 0)) { - spec = 3095 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 3105 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } else if (op->op1_type == IS_CV && (op->op2_type & (IS_CONST|IS_CV)) && !(op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) && !(op2_info & (MAY_BE_UNDEF|MAY_BE_REF))) { - spec = 3103 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 3113 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; } break; case ZEND_IS_EQUAL: @@ -123049,12 +123351,12 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2792 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 2802 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } else if (op1_info == MAY_BE_DOUBLE && op2_info == MAY_BE_DOUBLE) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2867 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 2877 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } break; case ZEND_IS_NOT_EQUAL: @@ -123065,12 +123367,12 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2942 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 2952 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } else if (op1_info == MAY_BE_DOUBLE && op2_info == MAY_BE_DOUBLE) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 3017 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 3027 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } break; case ZEND_IS_SMALLER: @@ -123078,12 +123380,12 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 3108 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; + spec = 3118 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; } else if (op1_info == MAY_BE_DOUBLE && op2_info == MAY_BE_DOUBLE) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 3183 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; + spec = 3193 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; } break; case ZEND_IS_SMALLER_OR_EQUAL: @@ -123091,79 +123393,79 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 3258 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; + spec = 3268 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; } else if (op1_info == MAY_BE_DOUBLE && op2_info == MAY_BE_DOUBLE) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 3333 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; + spec = 3343 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; } break; case ZEND_QM_ASSIGN: if (op1_info == MAY_BE_LONG) { - spec = 3420 | SPEC_RULE_OP1; + spec = 3430 | SPEC_RULE_OP1; } else if (op1_info == MAY_BE_DOUBLE) { - spec = 3425 | SPEC_RULE_OP1; + spec = 3435 | SPEC_RULE_OP1; } else if ((op->op1_type == IS_CONST) ? !Z_REFCOUNTED_P(RT_CONSTANT(op, op->op1)) : (!(op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-(MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE))))) { - spec = 3430 | SPEC_RULE_OP1; + spec = 3440 | SPEC_RULE_OP1; } break; case ZEND_PRE_INC: if (res_info == MAY_BE_LONG && op1_info == MAY_BE_LONG) { - spec = 3408 | SPEC_RULE_RETVAL; + spec = 3418 | SPEC_RULE_RETVAL; } else if (op1_info == MAY_BE_LONG) { - spec = 3410 | SPEC_RULE_RETVAL; + spec = 3420 | SPEC_RULE_RETVAL; } break; case ZEND_PRE_DEC: if (res_info == MAY_BE_LONG && op1_info == MAY_BE_LONG) { - spec = 3412 | SPEC_RULE_RETVAL; + spec = 3422 | SPEC_RULE_RETVAL; } else if (op1_info == MAY_BE_LONG) { - spec = 3414 | SPEC_RULE_RETVAL; + spec = 3424 | SPEC_RULE_RETVAL; } break; case ZEND_POST_INC: if (res_info == MAY_BE_LONG && op1_info == MAY_BE_LONG) { - spec = 3416; + spec = 3426; } else if (op1_info == MAY_BE_LONG) { - spec = 3417; + spec = 3427; } break; case ZEND_POST_DEC: if (res_info == MAY_BE_LONG && op1_info == MAY_BE_LONG) { - spec = 3418; + spec = 3428; } else if (op1_info == MAY_BE_LONG) { - spec = 3419; + spec = 3429; } break; case ZEND_JMP: if (OP_JMP_ADDR(op, op->op1) > op) { - spec = 2566; + spec = 2576; } break; case ZEND_INIT_FCALL: if (Z_EXTRA_P(RT_CONSTANT(op, op->op2)) != 0) { - spec = 2559; + spec = 2569; } break; case ZEND_RECV: if (op->op2.num == MAY_BE_ANY) { - spec = 2560; + spec = 2570; } break; case ZEND_SEND_VAL: if (op->op1_type == IS_CONST && op->op2_type == IS_UNUSED && !Z_REFCOUNTED_P(RT_CONSTANT(op, op->op1))) { - spec = 3470; + spec = 3480; } break; case ZEND_SEND_VAR_EX: if (op->op2_type == IS_UNUSED && op->op2.num <= MAX_ARG_FLAG_NUM && (op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) == 0) { - spec = 3465 | SPEC_RULE_OP1; + spec = 3475 | SPEC_RULE_OP1; } break; case ZEND_FE_FETCH_R: if (op->op2_type == IS_CV && (op1_info & (MAY_BE_ANY|MAY_BE_REF)) == MAY_BE_ARRAY) { - spec = 3472 | SPEC_RULE_RETVAL; + spec = 3482 | SPEC_RULE_RETVAL; } break; case ZEND_FETCH_DIM_R: @@ -123171,22 +123473,22 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 3435 | SPEC_RULE_OP1 | SPEC_RULE_OP2; + spec = 3445 | SPEC_RULE_OP1 | SPEC_RULE_OP2; } break; case ZEND_SEND_VAL_EX: if (op->op2_type == IS_UNUSED && op->op2.num <= MAX_ARG_FLAG_NUM && op->op1_type == IS_CONST && !Z_REFCOUNTED_P(RT_CONSTANT(op, op->op1))) { - spec = 3471; + spec = 3481; } break; case ZEND_SEND_VAR: if (op->op2_type == IS_UNUSED && (op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) == 0) { - spec = 3460 | SPEC_RULE_OP1; + spec = 3470 | SPEC_RULE_OP1; } break; case ZEND_COUNT: if ((op1_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF)) == MAY_BE_ARRAY) { - spec = 2561 | SPEC_RULE_OP1; + spec = 2571 | SPEC_RULE_OP1; } break; case ZEND_BW_OR: diff --git a/Zend/zend_vm_handlers.h b/Zend/zend_vm_handlers.h index 6f15951954503..7ffe2c220a028 100644 --- a/Zend/zend_vm_handlers.h +++ b/Zend/zend_vm_handlers.h @@ -1087,507 +1087,511 @@ _(2556, ZEND_INIT_PARENT_PROPERTY_HOOK_CALL_SPEC_CONST_UNUSED) \ _(2557, ZEND_DECLARE_ATTRIBUTED_CONST_SPEC_CONST_CONST) \ _(2558, ZEND_TYPE_ASSERT_SPEC_CONST) \ - _(2559, ZEND_INIT_FCALL_OFFSET_SPEC_CONST) \ - _(2560, ZEND_RECV_NOTYPE_SPEC) \ - _(2562, ZEND_COUNT_ARRAY_SPEC_TMP_UNUSED) \ - _(2565, ZEND_COUNT_ARRAY_SPEC_CV_UNUSED) \ - _(2566, ZEND_JMP_FORWARD_SPEC) \ - _(2572, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ - _(2573, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2574, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2576, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2577, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ - _(2578, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2579, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2581, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2559, ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST) \ + _(2562, ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_UNUSED) \ + _(2564, ZEND_SEND_PLACEHOLDER_SPEC_UNUSED_CONST) \ + _(2567, ZEND_SEND_PLACEHOLDER_SPEC_UNUSED_UNUSED) \ + _(2569, ZEND_INIT_FCALL_OFFSET_SPEC_CONST) \ + _(2570, ZEND_RECV_NOTYPE_SPEC) \ + _(2572, ZEND_COUNT_ARRAY_SPEC_TMP_UNUSED) \ + _(2575, ZEND_COUNT_ARRAY_SPEC_CV_UNUSED) \ + _(2576, ZEND_JMP_FORWARD_SPEC) \ + _(2582, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ + _(2583, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2584, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2586, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ _(2587, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ _(2588, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ _(2589, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ _(2591, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2597, ZEND_ADD_LONG_SPEC_TMPVARCV_CONST) \ - _(2598, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2599, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2601, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2602, ZEND_ADD_LONG_SPEC_TMPVARCV_CONST) \ - _(2603, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2604, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2606, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2597, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ + _(2598, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2599, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2601, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2607, ZEND_ADD_LONG_SPEC_TMPVARCV_CONST) \ + _(2608, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2609, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2611, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ _(2612, ZEND_ADD_LONG_SPEC_TMPVARCV_CONST) \ _(2613, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ _(2614, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ _(2616, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2622, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2623, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2624, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2626, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2627, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2628, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2629, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2631, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2622, ZEND_ADD_LONG_SPEC_TMPVARCV_CONST) \ + _(2623, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2624, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2626, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2632, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(2633, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2634, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2636, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ _(2637, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_CONST) \ _(2638, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ _(2639, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ _(2641, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2643, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_CONST_TMPVARCV) \ - _(2644, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_CONST_TMPVARCV) \ - _(2646, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_CONST_TMPVARCV) \ - _(2647, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ - _(2648, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2649, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2651, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2652, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ - _(2653, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2654, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2656, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2647, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(2648, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2649, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2651, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2653, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_CONST_TMPVARCV) \ + _(2654, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_CONST_TMPVARCV) \ + _(2656, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_CONST_TMPVARCV) \ + _(2657, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ + _(2658, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2659, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2661, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ _(2662, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ _(2663, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ _(2664, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ _(2666, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2668, ZEND_SUB_LONG_SPEC_CONST_TMPVARCV) \ - _(2669, ZEND_SUB_LONG_SPEC_CONST_TMPVARCV) \ - _(2671, ZEND_SUB_LONG_SPEC_CONST_TMPVARCV) \ - _(2672, ZEND_SUB_LONG_SPEC_TMPVARCV_CONST) \ - _(2673, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2674, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2676, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2677, ZEND_SUB_LONG_SPEC_TMPVARCV_CONST) \ - _(2678, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2679, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2681, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2672, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ + _(2673, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2674, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2676, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2678, ZEND_SUB_LONG_SPEC_CONST_TMPVARCV) \ + _(2679, ZEND_SUB_LONG_SPEC_CONST_TMPVARCV) \ + _(2681, ZEND_SUB_LONG_SPEC_CONST_TMPVARCV) \ + _(2682, ZEND_SUB_LONG_SPEC_TMPVARCV_CONST) \ + _(2683, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2684, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2686, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ _(2687, ZEND_SUB_LONG_SPEC_TMPVARCV_CONST) \ _(2688, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ _(2689, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ _(2691, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2693, ZEND_SUB_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(2694, ZEND_SUB_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(2696, ZEND_SUB_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(2697, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2698, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2699, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2701, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2702, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2703, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2704, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2706, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2697, ZEND_SUB_LONG_SPEC_TMPVARCV_CONST) \ + _(2698, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2699, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2701, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2703, ZEND_SUB_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(2704, ZEND_SUB_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(2706, ZEND_SUB_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(2707, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(2708, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2709, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2711, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ _(2712, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_CONST) \ _(2713, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ _(2714, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ _(2716, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2722, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ - _(2723, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2724, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2726, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2727, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ - _(2728, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2729, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2731, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2722, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(2723, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2724, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2726, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2732, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ + _(2733, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2734, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2736, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ _(2737, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ _(2738, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ _(2739, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ _(2741, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2747, ZEND_MUL_LONG_SPEC_TMPVARCV_CONST) \ - _(2748, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2749, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2751, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2752, ZEND_MUL_LONG_SPEC_TMPVARCV_CONST) \ - _(2753, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2754, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2756, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2747, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ + _(2748, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2749, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2751, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2757, ZEND_MUL_LONG_SPEC_TMPVARCV_CONST) \ + _(2758, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2759, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2761, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ _(2762, ZEND_MUL_LONG_SPEC_TMPVARCV_CONST) \ _(2763, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ _(2764, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ _(2766, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2772, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2773, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2774, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2776, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2777, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2778, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2779, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2781, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2772, ZEND_MUL_LONG_SPEC_TMPVARCV_CONST) \ + _(2773, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2774, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2776, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2782, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(2783, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2784, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2786, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ _(2787, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_CONST) \ _(2788, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ _(2789, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ _(2791, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2807, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(2808, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2809, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2810, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2811, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2812, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2813, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2814, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2815, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2819, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2820, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2821, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2822, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(2823, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2824, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2825, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2826, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2827, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2828, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2829, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2830, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2834, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2835, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2836, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2852, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(2853, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2854, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2855, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2856, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2857, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2858, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2859, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2860, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2864, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2865, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2866, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2882, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2883, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2884, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2885, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2886, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2887, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2888, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2889, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2890, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2894, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2895, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2896, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2897, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2898, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2899, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2900, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2901, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2902, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2903, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2904, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2905, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2909, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2910, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2911, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2927, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2928, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2929, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2930, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2931, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2932, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2933, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2934, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2935, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2939, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2940, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2941, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2957, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(2958, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2959, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2960, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2961, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2962, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2963, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2964, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2965, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2969, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2970, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2971, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2972, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(2973, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2974, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2975, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2976, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2977, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2978, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2979, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2980, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2984, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2985, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2986, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3002, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(3003, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3004, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3005, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3006, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3007, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3008, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3009, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3010, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3014, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3015, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3016, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3032, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3033, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3034, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3035, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3036, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3037, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3038, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3039, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3040, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3044, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3045, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3046, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3047, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3048, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3049, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3050, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3051, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3052, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3053, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3054, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3055, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3059, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3060, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3061, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3077, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3078, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3079, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3080, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3081, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3082, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3083, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3084, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3085, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3089, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3090, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3091, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3092, ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST) \ - _(3093, ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3094, ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3095, ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST) \ - _(3096, ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3097, ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3098, ZEND_IS_IDENTICAL_NOTHROW_SPEC_CV_CONST) \ - _(3102, ZEND_IS_IDENTICAL_NOTHROW_SPEC_CV_CV) \ - _(3103, ZEND_IS_NOT_IDENTICAL_NOTHROW_SPEC_CV_CONST) \ - _(3107, ZEND_IS_NOT_IDENTICAL_NOTHROW_SPEC_CV_CV) \ - _(3111, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV) \ - _(3112, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3113, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3114, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV) \ - _(3115, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3116, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3120, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV) \ - _(3121, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3122, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3123, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST) \ - _(3124, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3125, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3126, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3127, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3128, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3129, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3130, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3131, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3135, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3136, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3137, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3138, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST) \ - _(3139, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3140, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3141, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3142, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3143, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3144, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3145, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3146, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3150, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3151, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3152, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3168, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST) \ - _(3169, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3170, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3171, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3172, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3173, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3174, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3175, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3176, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3180, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3181, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3182, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3186, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(3187, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3188, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3189, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(3190, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3191, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3195, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(3196, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3197, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3198, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3199, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3200, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3201, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3202, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3203, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3204, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3205, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3206, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3210, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3211, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3212, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3213, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3214, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3215, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3216, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3217, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3218, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3219, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3220, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3221, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3225, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3226, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3227, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3243, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3244, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3245, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3246, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3247, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3248, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3249, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3250, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3251, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3255, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3256, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3257, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3261, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV) \ - _(3262, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3263, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3264, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV) \ - _(3265, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3266, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3270, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV) \ - _(3271, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3272, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3273, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(3274, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3275, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3276, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3277, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3278, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3279, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3280, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3281, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3285, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3286, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3287, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3288, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(3289, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3290, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3291, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3292, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3293, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3294, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3295, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3296, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3300, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3301, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3302, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3318, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(3319, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3320, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3321, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3322, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3323, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3324, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3325, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3326, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3330, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3331, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3332, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3336, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(3337, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3338, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3339, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(3340, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3341, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3345, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(3346, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3347, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3348, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3349, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3350, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3351, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3352, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3353, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3354, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3355, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3356, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3360, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3361, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3362, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3363, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3364, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3365, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3366, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3367, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3368, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3369, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3370, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3371, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3375, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3376, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3377, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3393, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3394, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3395, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3396, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3397, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3398, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3399, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3400, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3401, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3405, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3406, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3407, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3408, ZEND_PRE_INC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_UNUSED) \ - _(3409, ZEND_PRE_INC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_USED) \ - _(3410, ZEND_PRE_INC_LONG_SPEC_CV_RETVAL_UNUSED) \ - _(3411, ZEND_PRE_INC_LONG_SPEC_CV_RETVAL_USED) \ - _(3412, ZEND_PRE_DEC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_UNUSED) \ - _(3413, ZEND_PRE_DEC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_USED) \ - _(3414, ZEND_PRE_DEC_LONG_SPEC_CV_RETVAL_UNUSED) \ - _(3415, ZEND_PRE_DEC_LONG_SPEC_CV_RETVAL_USED) \ - _(3416, ZEND_POST_INC_LONG_NO_OVERFLOW_SPEC_CV) \ - _(3417, ZEND_POST_INC_LONG_SPEC_CV) \ - _(3418, ZEND_POST_DEC_LONG_NO_OVERFLOW_SPEC_CV) \ - _(3419, ZEND_POST_DEC_LONG_SPEC_CV) \ - _(3420, ZEND_QM_ASSIGN_LONG_SPEC_CONST) \ - _(3421, ZEND_QM_ASSIGN_LONG_SPEC_TMPVARCV) \ - _(3422, ZEND_QM_ASSIGN_LONG_SPEC_TMPVARCV) \ - _(3424, ZEND_QM_ASSIGN_LONG_SPEC_TMPVARCV) \ - _(3425, ZEND_QM_ASSIGN_DOUBLE_SPEC_CONST) \ - _(3426, ZEND_QM_ASSIGN_DOUBLE_SPEC_TMPVARCV) \ - _(3427, ZEND_QM_ASSIGN_DOUBLE_SPEC_TMPVARCV) \ - _(3429, ZEND_QM_ASSIGN_DOUBLE_SPEC_TMPVARCV) \ - _(3430, ZEND_QM_ASSIGN_NOREF_SPEC_CONST) \ - _(3431, ZEND_QM_ASSIGN_NOREF_SPEC_TMPVARCV) \ - _(3432, ZEND_QM_ASSIGN_NOREF_SPEC_TMPVARCV) \ - _(3434, ZEND_QM_ASSIGN_NOREF_SPEC_TMPVARCV) \ - _(3436, ZEND_FETCH_DIM_R_INDEX_SPEC_CONST_TMPVARCV) \ - _(3437, ZEND_FETCH_DIM_R_INDEX_SPEC_CONST_TMPVARCV) \ - _(3439, ZEND_FETCH_DIM_R_INDEX_SPEC_CONST_TMPVARCV) \ - _(3440, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_CONST) \ - _(3441, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ - _(3442, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ - _(3444, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ - _(3445, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_CONST) \ - _(3446, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ - _(3447, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ - _(3449, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ - _(3455, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_CONST) \ - _(3456, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_TMPVARCV) \ - _(3457, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_TMPVARCV) \ - _(3459, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_TMPVARCV) \ - _(3462, ZEND_SEND_VAR_SIMPLE_SPEC_VAR) \ - _(3464, ZEND_SEND_VAR_SIMPLE_SPEC_CV) \ - _(3467, ZEND_SEND_VAR_EX_SIMPLE_SPEC_VAR_UNUSED) \ - _(3469, ZEND_SEND_VAR_EX_SIMPLE_SPEC_CV_UNUSED) \ - _(3470, ZEND_SEND_VAL_SIMPLE_SPEC_CONST) \ - _(3471, ZEND_SEND_VAL_EX_SIMPLE_SPEC_CONST) \ - _(3472, ZEND_FE_FETCH_R_SIMPLE_SPEC_VAR_CV_RETVAL_UNUSED) \ - _(3473, ZEND_FE_FETCH_R_SIMPLE_SPEC_VAR_CV_RETVAL_USED) \ - _(3473+1, ZEND_NULL) + _(2797, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(2798, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2799, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2801, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2817, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(2818, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(2819, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(2820, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2821, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2822, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2823, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2824, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2825, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2829, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2830, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2831, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2832, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(2833, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(2834, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(2835, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2836, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2837, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2838, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2839, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2840, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2844, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2845, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2846, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2862, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(2863, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(2864, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(2865, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2866, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2867, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2868, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2869, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2870, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2874, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2875, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2876, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2892, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(2893, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(2894, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(2895, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2896, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2897, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2898, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2899, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2900, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2904, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2905, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2906, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2907, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(2908, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(2909, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(2910, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2911, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2912, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2913, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2914, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2915, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2919, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2920, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2921, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2937, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(2938, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(2939, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(2940, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2941, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2942, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2943, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2944, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2945, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2949, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2950, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2951, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2967, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(2968, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(2969, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(2970, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2971, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2972, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2973, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2974, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2975, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2979, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2980, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2981, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2982, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(2983, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(2984, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(2985, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2986, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2987, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2988, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2989, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2990, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2994, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2995, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2996, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3012, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(3013, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3014, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3015, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3016, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3017, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3018, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3019, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3020, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3024, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3025, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3026, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3042, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3043, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3044, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3045, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3046, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3047, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3048, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3049, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3050, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3054, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3055, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3056, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3057, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3058, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3059, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3060, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3061, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3062, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3063, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3064, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3065, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3069, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3070, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3071, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3087, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3088, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3089, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3090, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3091, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3092, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3093, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3094, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3095, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3099, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3100, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3101, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3102, ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST) \ + _(3103, ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3104, ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3105, ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST) \ + _(3106, ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3107, ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3108, ZEND_IS_IDENTICAL_NOTHROW_SPEC_CV_CONST) \ + _(3112, ZEND_IS_IDENTICAL_NOTHROW_SPEC_CV_CV) \ + _(3113, ZEND_IS_NOT_IDENTICAL_NOTHROW_SPEC_CV_CONST) \ + _(3117, ZEND_IS_NOT_IDENTICAL_NOTHROW_SPEC_CV_CV) \ + _(3121, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV) \ + _(3122, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3123, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3124, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV) \ + _(3125, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3126, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3130, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV) \ + _(3131, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3132, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3133, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST) \ + _(3134, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3135, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3136, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3137, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3138, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3139, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3140, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3141, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3145, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3146, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3147, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3148, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST) \ + _(3149, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3150, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3151, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3152, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3153, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3154, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3155, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3156, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3160, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3161, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3162, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3178, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST) \ + _(3179, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3180, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3181, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3182, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3183, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3184, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3185, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3186, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3190, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3191, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3192, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3196, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(3197, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3198, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3199, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(3200, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3201, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3205, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(3206, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3207, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3208, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3209, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3210, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3211, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3212, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3213, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3214, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3215, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3216, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3220, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3221, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3222, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3223, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3224, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3225, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3226, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3227, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3228, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3229, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3230, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3231, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3235, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3236, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3237, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3253, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3254, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3255, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3256, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3257, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3258, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3259, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3260, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3261, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3265, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3266, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3267, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3271, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV) \ + _(3272, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3273, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3274, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV) \ + _(3275, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3276, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3280, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV) \ + _(3281, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3282, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3283, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(3284, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3285, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3286, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3287, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3288, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3289, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3290, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3291, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3295, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3296, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3297, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3298, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(3299, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3300, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3301, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3302, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3303, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3304, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3305, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3306, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3310, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3311, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3312, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3328, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(3329, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3330, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3331, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3332, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3333, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3334, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3335, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3336, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3340, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3341, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3342, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3346, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(3347, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3348, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3349, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(3350, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3351, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3355, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(3356, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3357, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3358, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3359, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3360, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3361, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3362, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3363, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3364, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3365, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3366, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3370, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3371, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3372, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3373, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3374, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3375, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3376, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3377, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3378, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3379, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3380, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3381, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3385, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3386, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3387, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3403, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3404, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3405, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3406, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3407, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3408, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3409, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3410, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3411, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3415, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3416, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3417, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3418, ZEND_PRE_INC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_UNUSED) \ + _(3419, ZEND_PRE_INC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_USED) \ + _(3420, ZEND_PRE_INC_LONG_SPEC_CV_RETVAL_UNUSED) \ + _(3421, ZEND_PRE_INC_LONG_SPEC_CV_RETVAL_USED) \ + _(3422, ZEND_PRE_DEC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_UNUSED) \ + _(3423, ZEND_PRE_DEC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_USED) \ + _(3424, ZEND_PRE_DEC_LONG_SPEC_CV_RETVAL_UNUSED) \ + _(3425, ZEND_PRE_DEC_LONG_SPEC_CV_RETVAL_USED) \ + _(3426, ZEND_POST_INC_LONG_NO_OVERFLOW_SPEC_CV) \ + _(3427, ZEND_POST_INC_LONG_SPEC_CV) \ + _(3428, ZEND_POST_DEC_LONG_NO_OVERFLOW_SPEC_CV) \ + _(3429, ZEND_POST_DEC_LONG_SPEC_CV) \ + _(3430, ZEND_QM_ASSIGN_LONG_SPEC_CONST) \ + _(3431, ZEND_QM_ASSIGN_LONG_SPEC_TMPVARCV) \ + _(3432, ZEND_QM_ASSIGN_LONG_SPEC_TMPVARCV) \ + _(3434, ZEND_QM_ASSIGN_LONG_SPEC_TMPVARCV) \ + _(3435, ZEND_QM_ASSIGN_DOUBLE_SPEC_CONST) \ + _(3436, ZEND_QM_ASSIGN_DOUBLE_SPEC_TMPVARCV) \ + _(3437, ZEND_QM_ASSIGN_DOUBLE_SPEC_TMPVARCV) \ + _(3439, ZEND_QM_ASSIGN_DOUBLE_SPEC_TMPVARCV) \ + _(3440, ZEND_QM_ASSIGN_NOREF_SPEC_CONST) \ + _(3441, ZEND_QM_ASSIGN_NOREF_SPEC_TMPVARCV) \ + _(3442, ZEND_QM_ASSIGN_NOREF_SPEC_TMPVARCV) \ + _(3444, ZEND_QM_ASSIGN_NOREF_SPEC_TMPVARCV) \ + _(3446, ZEND_FETCH_DIM_R_INDEX_SPEC_CONST_TMPVARCV) \ + _(3447, ZEND_FETCH_DIM_R_INDEX_SPEC_CONST_TMPVARCV) \ + _(3449, ZEND_FETCH_DIM_R_INDEX_SPEC_CONST_TMPVARCV) \ + _(3450, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_CONST) \ + _(3451, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ + _(3452, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ + _(3454, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ + _(3455, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_CONST) \ + _(3456, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ + _(3457, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ + _(3459, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ + _(3465, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_CONST) \ + _(3466, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_TMPVARCV) \ + _(3467, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_TMPVARCV) \ + _(3469, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_TMPVARCV) \ + _(3472, ZEND_SEND_VAR_SIMPLE_SPEC_VAR) \ + _(3474, ZEND_SEND_VAR_SIMPLE_SPEC_CV) \ + _(3477, ZEND_SEND_VAR_EX_SIMPLE_SPEC_VAR_UNUSED) \ + _(3479, ZEND_SEND_VAR_EX_SIMPLE_SPEC_CV_UNUSED) \ + _(3480, ZEND_SEND_VAL_SIMPLE_SPEC_CONST) \ + _(3481, ZEND_SEND_VAL_EX_SIMPLE_SPEC_CONST) \ + _(3482, ZEND_FE_FETCH_R_SIMPLE_SPEC_VAR_CV_RETVAL_UNUSED) \ + _(3483, ZEND_FE_FETCH_R_SIMPLE_SPEC_VAR_CV_RETVAL_USED) \ + _(3483+1, ZEND_NULL) diff --git a/Zend/zend_vm_opcodes.c b/Zend/zend_vm_opcodes.c index 0ece3e6f0c669..76f3af3698457 100644 --- a/Zend/zend_vm_opcodes.c +++ b/Zend/zend_vm_opcodes.c @@ -21,7 +21,7 @@ #include #include -static const char *zend_vm_opcodes_names[212] = { +static const char *zend_vm_opcodes_names[214] = { "ZEND_NOP", "ZEND_ADD", "ZEND_SUB", @@ -234,9 +234,11 @@ static const char *zend_vm_opcodes_names[212] = { "ZEND_INIT_PARENT_PROPERTY_HOOK_CALL", "ZEND_DECLARE_ATTRIBUTED_CONST", "ZEND_TYPE_ASSERT", + "ZEND_CALLABLE_CONVERT_PARTIAL", + "ZEND_SEND_PLACEHOLDER", }; -static uint32_t zend_vm_opcodes_flags[212] = { +static uint32_t zend_vm_opcodes_flags[214] = { 0x00000000, 0x00000b0b, 0x00000b0b, @@ -449,6 +451,8 @@ static uint32_t zend_vm_opcodes_flags[212] = { 0x01001103, 0x00000303, 0x01000003, + 0x010003a0, + 0x00000301, }; ZEND_API const char* ZEND_FASTCALL zend_get_opcode_name(uint8_t opcode) { diff --git a/Zend/zend_vm_opcodes.h b/Zend/zend_vm_opcodes.h index b63177e2421e1..1d204b9281f54 100644 --- a/Zend/zend_vm_opcodes.h +++ b/Zend/zend_vm_opcodes.h @@ -331,7 +331,9 @@ END_EXTERN_C() #define ZEND_INIT_PARENT_PROPERTY_HOOK_CALL 209 #define ZEND_DECLARE_ATTRIBUTED_CONST 210 #define ZEND_TYPE_ASSERT 211 +#define ZEND_CALLABLE_CONVERT_PARTIAL 212 +#define ZEND_SEND_PLACEHOLDER 213 -#define ZEND_VM_LAST_OPCODE 211 +#define ZEND_VM_LAST_OPCODE 213 #endif diff --git a/configure.ac b/configure.ac index 6c517ecc0a1e3..a83eb5c3dba43 100644 --- a/configure.ac +++ b/configure.ac @@ -1774,6 +1774,7 @@ PHP_ADD_SOURCES([Zend], m4_normalize([ zend_observer.c zend_opcode.c zend_operators.c + zend_partial.c zend_property_hooks.c zend_ptr_stack.c zend_signal.c diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 3d005b3835a7e..c9344f671db7c 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -24,7 +24,9 @@ #include "zend_compile.h" #include "ZendAccelerator.h" #include "zend_modules.h" +#include "zend_operators.h" #include "zend_persist.h" +#include "zend_portability.h" #include "zend_shared_alloc.h" #include "zend_accelerator_module.h" #include "zend_accelerator_blacklist.h" @@ -48,6 +50,7 @@ #include "zend_system_id.h" #include "ext/pcre/php_pcre.h" #include "ext/standard/basic_functions.h" +#include "zend_vm_opcodes.h" #ifdef ZEND_WIN32 # include "ext/hash/php_hash.h" @@ -2006,6 +2009,219 @@ static bool check_persistent_script_access(const zend_persistent_script *persist } } +static const char hexchars[] = "0123456789abcdef"; + +static char *zend_accel_uintptr_hex(char *dest, uintptr_t n) +{ + char *start = dest; + dest += sizeof(uintptr_t)*2; + + while (n > 0) { + *--dest = hexchars[n % strlen(hexchars)]; + n /= strlen(hexchars); + } + while (dest > start) { + *--dest = '0'; + } + + return dest + sizeof(uintptr_t)*2; +} + +/* Prevents collisions with real scripts, as we don't cache paths prefixed with + * a scheme, except file:// and phar://. */ +#define PFA_KEY_PREFIX "pfa://" + +static zend_string *zend_accel_pfa_key(const zend_op *declaring_opline, + const zend_function *called_function) +{ + size_t key_len = strlen(PFA_KEY_PREFIX) + (sizeof(uintptr_t)*2) + strlen(":") + (sizeof(uintptr_t)*2); + zend_string *key = zend_string_alloc(key_len, 0); + char *dest = ZSTR_VAL(key); + + dest = zend_mempcpy(ZSTR_VAL(key), PFA_KEY_PREFIX, strlen(PFA_KEY_PREFIX)); + dest = zend_accel_uintptr_hex(dest, (uintptr_t)declaring_opline); + *dest++ = ':'; + + void *ptr; + if ((called_function->common.fn_flags & ZEND_ACC_CLOSURE) + && called_function->type == ZEND_USER_FUNCTION) { + /* Can not use 'called_function' as part of the key, as it's an inner + * pointer to a Closure, which may be freed. Use its opcodes instead. + * zend_accel_compile_pfa() ensures to extend the lifetime of opcodes + * in this case. */ + ptr = called_function->op_array.opcodes; + } else { + ptr = (void*) called_function; + } + dest = zend_accel_uintptr_hex(dest, (uintptr_t)ptr); + + ZEND_ASSERT(dest == ZSTR_VAL(key) + key_len); + + ZSTR_VAL(key)[key_len] = 0; + ZSTR_LEN(key) = key_len; + + return key; +} + +zend_op_array *zend_accel_pfa_cache_get(const zend_op_array *declaring_op_array, + const zend_op *declaring_opline, const zend_function *called_function) +{ + zend_string *key = zend_accel_pfa_key(declaring_opline, called_function); + zend_op_array *op_array = NULL; + + /* A PFA is SHM-cacheable if the declaring_op_array and called_function are + * cached. */ + if (ZCG(accelerator_enabled) + && !file_cache_only + && !declaring_op_array->refcount + && (called_function->type != ZEND_USER_FUNCTION || !called_function->op_array.refcount)) { + zend_persistent_script *persistent_script = zend_accel_hash_find(&ZCSG(hash), key); + if (persistent_script) { + op_array = persistent_script->script.main_op_array.dynamic_func_defs[0]; + if (persistent_script->num_warnings) { + zend_emit_recorded_errors_ex(persistent_script->num_warnings, + persistent_script->warnings); + } + } + } else { + op_array = zend_hash_find_ptr(&EG(partial_function_application_cache), key); + } + + zend_string_release(key); + + return op_array; +} + +zend_op_array *zend_accel_compile_pfa(zend_ast *ast, + const zend_op_array *declaring_op_array, + const zend_op *declaring_opline, + const zend_function *called_function, + zend_string *pfa_func_name) +{ + zend_begin_record_errors(); + zend_op_array *op_array; + + uint32_t orig_compiler_options = CG(compiler_options); + + zend_try { + CG(compiler_options) |= ZEND_COMPILE_HANDLE_OP_ARRAY; + CG(compiler_options) |= ZEND_COMPILE_DELAYED_BINDING; + CG(compiler_options) |= ZEND_COMPILE_NO_CONSTANT_SUBSTITUTION; + CG(compiler_options) |= ZEND_COMPILE_IGNORE_OTHER_FILES; + CG(compiler_options) |= ZEND_COMPILE_IGNORE_OBSERVER; +#ifdef ZEND_WIN32 + /* On Windows, don't compile with internal classes. Shm may be attached from different + * processes with internal classes living in different addresses. */ + CG(compiler_options) |= ZEND_COMPILE_IGNORE_INTERNAL_CLASSES; +#endif + + op_array = zend_compile_ast(ast, ZEND_USER_FUNCTION, declaring_op_array->filename); + + ZEND_ASSERT(op_array->num_dynamic_func_defs == 1); + + CG(compiler_options) = orig_compiler_options; + } zend_catch { + op_array = NULL; + CG(compiler_options) = orig_compiler_options; + EG(record_errors) = false; + zend_free_recorded_errors(); + zend_bailout(); + } zend_end_try(); + + if (!op_array) { + zend_emit_recorded_errors(); + zend_free_recorded_errors(); + return NULL; + } + + ZEND_ASSERT(op_array->num_dynamic_func_defs == 1); + + zend_string_release(op_array->dynamic_func_defs[0]->function_name); + op_array->dynamic_func_defs[0]->function_name = pfa_func_name; + + zend_string *key = zend_accel_pfa_key(declaring_opline, called_function); + + /* Cache op_array only if the declaring op_array and the called function + * are cached */ + if (!ZCG(accelerator_enabled) + || file_cache_only + || declaring_op_array->refcount + || (called_function->type == ZEND_USER_FUNCTION && called_function->op_array.refcount) + || (ZCSG(restart_in_progress) && accel_restart_is_active()) + || (!ZCG(counted) && accel_activate_add() == FAILURE)) { + zend_op_array *script_op_array = op_array; + zend_op_array *op_array = script_op_array->dynamic_func_defs[0]; + GC_ADDREF(op_array->function_name); + (*op_array->refcount)++; + destroy_op_array(script_op_array); + efree(script_op_array); + + if ((called_function->common.fn_flags & ZEND_ACC_CLOSURE) + && called_function->type == ZEND_USER_FUNCTION + && called_function->op_array.refcount) { + /* Extend the lifetime of the called opcodes if + * the called function is a closure. + * See comment in zend_accel_pfa_key(). */ + zend_op_array *copy = zend_arena_alloc(&CG(arena), sizeof(zend_function)); + memcpy(copy, called_function, sizeof(zend_op_array)); + zend_string_addref(copy->function_name); + (*copy->refcount)++; + /* Reference the copy in op_array->dynamic_func_defs so that it's + * destroyed when op_array is destroy. */ + ZEND_ASSERT(!op_array->dynamic_func_defs && !op_array->num_dynamic_func_defs); + op_array->dynamic_func_defs = emalloc(sizeof(zend_op_array*)); + op_array->dynamic_func_defs[0] = copy; + op_array->num_dynamic_func_defs = 1; + } + + zend_hash_add_new_ptr(&EG(partial_function_application_cache), key, op_array); + zend_string_release(key); + + zend_emit_recorded_errors(); + zend_free_recorded_errors(); + + return op_array; + } + + zend_persistent_script *new_persistent_script = create_persistent_script(); + new_persistent_script->script.main_op_array = *op_array; + efree_size(op_array, sizeof(zend_op_array)); + new_persistent_script->script.filename = key; + + if (ZCG(accel_directives).record_warnings) { + new_persistent_script->num_warnings = EG(num_errors); + new_persistent_script->warnings = EG(errors); + } + + HANDLE_BLOCK_INTERRUPTIONS(); + SHM_UNPROTECT(); + + bool from_shared_memory; + /* See GH-17246: we disable GC so that user code cannot be executed during the optimizer run. */ + bool orig_gc_state = gc_enable(false); + char *orig_file_cache = ZCG(accel_directives).file_cache; + /* Disable file_cache temporarily, as we can't guarantee consistency. */ + ZCG(accel_directives).file_cache = false; + new_persistent_script = cache_script_in_shared_memory(new_persistent_script, NULL, &from_shared_memory); + ZCG(accel_directives).file_cache = orig_file_cache; + gc_enable(orig_gc_state); + + SHM_PROTECT(); + HANDLE_UNBLOCK_INTERRUPTIONS(); + + /* We may have switched to an existing persistent script that was persisted in + * the meantime. Make sure to use its warnings if available. */ + if (ZCG(accel_directives).record_warnings) { + EG(record_errors) = false; + zend_emit_recorded_errors_ex(new_persistent_script->num_warnings, new_persistent_script->warnings); + } else { + zend_emit_recorded_errors(); + } + zend_free_recorded_errors(); + + return new_persistent_script->script.main_op_array.dynamic_func_defs[0]; +} + /* zend_compile() replacement */ zend_op_array *persistent_compile_file(zend_file_handle *file_handle, int type) { diff --git a/ext/opcache/ZendAccelerator.h b/ext/opcache/ZendAccelerator.h index 91642e288d31b..9eec7555c4ea2 100644 --- a/ext/opcache/ZendAccelerator.h +++ b/ext/opcache/ZendAccelerator.h @@ -334,6 +334,16 @@ zend_string* ZEND_FASTCALL accel_new_interned_string(zend_string *str); uint32_t zend_accel_get_class_name_map_ptr(zend_string *type_name); +zend_op_array *zend_accel_pfa_cache_get(const zend_op_array *declaring_op_array, + const zend_op *declaring_opline, + const zend_function *called_function); + +zend_op_array *zend_accel_compile_pfa(zend_ast *ast, + const zend_op_array *declaring_op_array, + const zend_op *declaring_opline, + const zend_function *called_function, + zend_string *pfa_func_name); + END_EXTERN_C() /* memory write protection */ diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index 738f204c50261..21e0213302dc5 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -301,6 +301,7 @@ static int zend_jit_needs_call_chain(zend_call_info *call_info, uint32_t b, cons case ZEND_DO_FCALL_BY_NAME: case ZEND_DO_FCALL: case ZEND_CALLABLE_CONVERT: + case ZEND_CALLABLE_CONVERT_PARTIAL: return 0; case ZEND_SEND_VAL: case ZEND_SEND_VAR: @@ -386,6 +387,7 @@ static int zend_jit_needs_call_chain(zend_call_info *call_info, uint32_t b, cons case ZEND_DO_FCALL_BY_NAME: case ZEND_DO_FCALL: case ZEND_CALLABLE_CONVERT: + case ZEND_CALLABLE_CONVERT_PARTIAL: end = opline; if (end - op_array->opcodes >= ssa->cfg.blocks[b].start + ssa->cfg.blocks[b].len) { /* INIT_FCALL and DO_FCALL in different BasicBlocks */ @@ -865,6 +867,7 @@ static bool zend_jit_dec_call_level(uint8_t opcode) case ZEND_DO_UCALL: case ZEND_DO_FCALL_BY_NAME: case ZEND_CALLABLE_CONVERT: + case ZEND_CALLABLE_CONVERT_PARTIAL: return true; default: return false; diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 2461d024dd6aa..c68b2de5430da 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -10091,7 +10091,7 @@ static int zend_jit_do_fcall(zend_jit_ctx *jit, const zend_op *opline, const zen } bool may_have_extra_named_params = - opline->extended_value == ZEND_FCALL_MAY_HAVE_EXTRA_NAMED_PARAMS && + (opline->extended_value & ZEND_FCALL_MAY_HAVE_EXTRA_NAMED_PARAMS) && (!func || func->common.fn_flags & ZEND_ACC_VARIADIC); if (!jit->reuse_ip) { diff --git a/ext/opcache/jit/zend_jit_vm_helpers.c b/ext/opcache/jit/zend_jit_vm_helpers.c index 1c7fb40735697..8796703ea0156 100644 --- a/ext/opcache/jit/zend_jit_vm_helpers.c +++ b/ext/opcache/jit/zend_jit_vm_helpers.c @@ -1057,7 +1057,8 @@ zend_jit_trace_stop ZEND_FASTCALL zend_jit_trace_execute(zend_execute_data *ex, TRACE_RECORD(ZEND_JIT_TRACE_DO_ICALL, 0, func); } } else if (opline->opcode == ZEND_INCLUDE_OR_EVAL - || opline->opcode == ZEND_CALLABLE_CONVERT) { + || opline->opcode == ZEND_CALLABLE_CONVERT + || opline->opcode == ZEND_CALLABLE_CONVERT_PARTIAL) { /* TODO: Support tracing JIT for ZEND_CALLABLE_CONVERT. */ stop = ZEND_JIT_TRACE_STOP_INTERPRETER; break; diff --git a/win32/build/config.w32 b/win32/build/config.w32 index aefcfb5f82474..b04c636d53c99 100644 --- a/win32/build/config.w32 +++ b/win32/build/config.w32 @@ -241,7 +241,7 @@ ADD_SOURCES("Zend", "zend_language_parser.c zend_language_scanner.c \ zend_float.c zend_string.c zend_generators.c zend_virtual_cwd.c zend_ast.c \ zend_inheritance.c zend_smart_str.c zend_cpuinfo.c zend_observer.c zend_system_id.c \ zend_enum.c zend_fibers.c zend_atomic.c zend_hrtime.c zend_frameless_function.c zend_property_hooks.c \ - zend_lazy_objects.c zend_autoload.c"); + zend_lazy_objects.c zend_autoload.c zend_partial.c"); ADD_SOURCES("Zend\\Optimizer", "zend_optimizer.c pass1.c pass3.c optimize_func_calls.c block_pass.c optimize_temp_vars_5.c nop_removal.c compact_literals.c zend_cfg.c zend_dfg.c dfa_pass.c zend_ssa.c zend_inference.c zend_func_info.c zend_call_graph.c zend_dump.c escape_analysis.c compact_vars.c dce.c sccp.c scdf.c"); var PHP_ASSEMBLER = PATH_PROG({ From 9e565276508fb66d4ba0801fdb2c5a1b385550b6 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Tue, 6 Jan 2026 17:33:40 +0100 Subject: [PATCH 02/59] Remove unnecessary change --- Zend/Optimizer/zend_call_graph.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Zend/Optimizer/zend_call_graph.c b/Zend/Optimizer/zend_call_graph.c index 56f326e100b5f..b67f57cf041d6 100644 --- a/Zend/Optimizer/zend_call_graph.c +++ b/Zend/Optimizer/zend_call_graph.c @@ -147,10 +147,6 @@ ZEND_API void zend_analyze_calls(zend_arena **arena, zend_script *script, uint32 call_info->named_args = true; break; } - if (opline->opcode == ZEND_SEND_PLACEHOLDER - && opline->op1.num == ZEND_PLACEHOLDER_VARIADIC) { - break; - } uint32_t num = opline->op2.num; if (num > 0) { From d0028eac753f9e4824adb88e72edff81e9de3cc6 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Tue, 6 Jan 2026 17:33:54 +0100 Subject: [PATCH 03/59] Skip preload test on Windows --- Zend/tests/partial_application/preloading.phpt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Zend/tests/partial_application/preloading.phpt b/Zend/tests/partial_application/preloading.phpt index 5e3069c271a6d..23ad6edf2c4cb 100644 --- a/Zend/tests/partial_application/preloading.phpt +++ b/Zend/tests/partial_application/preloading.phpt @@ -4,6 +4,10 @@ PFA preloading opcache.enable=1 opcache.enable_cli=1 opcache.preload={PWD}/preloading.inc +--SKIPIF-- + --FILE-- Date: Tue, 6 Jan 2026 17:34:03 +0100 Subject: [PATCH 04/59] Comments --- Zend/zend_closures.c | 1 + Zend/zend_partial.c | 6 ++++-- ext/opcache/ZendAccelerator.c | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Zend/zend_closures.c b/Zend/zend_closures.c index acf0156a9bc7a..1ddce132d50e6 100644 --- a/Zend/zend_closures.c +++ b/Zend/zend_closures.c @@ -86,6 +86,7 @@ static bool zend_valid_closure_binding( zend_closure *closure, zval *newthis, zend_class_entry *scope) /* {{{ */ { zend_function *func = &closure->func; + // TODO: rename variable bool is_fake_closure = (func->common.fn_flags & ZEND_ACC_FAKE_CLOSURE) != 0 || (closure->std.extra_flags & ZEND_PARTIAL); if (newthis) { diff --git a/Zend/zend_partial.c b/Zend/zend_partial.c index 2a4195c4049bc..aa7511153a16c 100644 --- a/Zend/zend_partial.c +++ b/Zend/zend_partial.c @@ -35,7 +35,7 @@ * create a Closure and return it, consuming the stack frame in the process * like an internal function call. * - * We create the Closure by generating the relevant AST and compling it to an + * We create the Closure by generating the relevant AST and compiling it to an * op_array. The op_array is cached in the Opcache SHM and inline caches. * * This file implements the Closure generation logic @@ -548,6 +548,7 @@ static zend_ast *zp_compile_forwarding_call( } zend_ast *default_value_ast; if (Z_TYPE(default_value) == IS_CONSTANT_AST) { + /* Must dup AST because we are doing to destroy it */ default_value_ast = zend_ast_dup(Z_ASTVAL(default_value)); } else { default_value_ast = zend_ast_create_zval(&default_value); @@ -702,6 +703,7 @@ static zend_op_array *zp_compile(zval *this_ptr, zend_function *function, called_scope = Z_CE_P(this_ptr); } + /* CG(ast_arena) is usually NULL, so we can't just make a snapshot */ zend_arena *orig_ast_arena = CG(ast_arena); CG(ast_arena) = zend_arena_create(1024 * 4); @@ -718,7 +720,7 @@ static zend_op_array *zp_compile(zval *this_ptr, zend_function *function, memcpy(tmp, argv, argc * sizeof(zval)); argv = tmp; - /* Compute number of required args and param positions, add implicit + /* Compute param positions and number of required args, add implicit * placeholders. * * Parameters are placed in the following order: diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index c9344f671db7c..2f4ff38311cea 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -2167,7 +2167,7 @@ zend_op_array *zend_accel_compile_pfa(zend_ast *ast, zend_string_addref(copy->function_name); (*copy->refcount)++; /* Reference the copy in op_array->dynamic_func_defs so that it's - * destroyed when op_array is destroy. */ + * destroyed when op_array is destroyed. */ ZEND_ASSERT(!op_array->dynamic_func_defs && !op_array->num_dynamic_func_defs); op_array->dynamic_func_defs = emalloc(sizeof(zend_op_array*)); op_array->dynamic_func_defs[0] = copy; From c76cc8874874e651af92e7f46eb21ada2c6ec8c2 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Tue, 6 Jan 2026 17:55:56 +0100 Subject: [PATCH 05/59] Improve type coherency --- Zend/zend_partial.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Zend/zend_partial.c b/Zend/zend_partial.c index aa7511153a16c..7fbdccb69b905 100644 --- a/Zend/zend_partial.c +++ b/Zend/zend_partial.c @@ -166,11 +166,11 @@ static void zp_assign_names(zend_string **names, uint32_t num_names, if (!Z_IS_PLACEHOLDER_P(&argv[offset])) { continue; } - int n = offset - function->common.num_args; + uint32_t n = offset - function->common.num_args; zend_string *orig_name = zp_get_param_name(function, function->common.num_args); zend_string *new_name; do { - new_name = zend_strpprintf_unchecked(0, "%S%d", orig_name, n); + new_name = zend_strpprintf_unchecked(0, "%S%" PRIu32, orig_name, n); if (!zp_name_exists(names, num_names, new_name)) { break; } @@ -187,13 +187,13 @@ static void zp_assign_names(zend_string **names, uint32_t num_names, if (Z_IS_PLACEHOLDER_P(&argv[offset]) || Z_ISUNDEF(argv[offset])) { continue; } - int n = -1; + uint32_t n = 0; zend_string *orig_name = zp_get_param_name(function, MIN(offset, function->common.num_args)); zend_string *new_name = zend_string_copy(orig_name); while (zp_name_exists(names, num_names, new_name)) { zend_string_release(new_name); + new_name = zend_strpprintf_unchecked(0, "%S%" PRIu32, orig_name, n); n++; - new_name = zend_strpprintf_unchecked(0, "%S%d", orig_name, n); } names[offset] = new_name; zend_string_release(orig_name); @@ -201,24 +201,24 @@ static void zp_assign_names(zend_string **names, uint32_t num_names, /* Assign name for $extra_named_params */ if (extra_named_params) { - int n = 1; + uint32_t n = 1; zend_string *new_name = ZSTR_INIT_LITERAL("extra_named_params", 0); while (zp_name_exists(names, num_names, new_name)) { zend_string_release(new_name); n++; - new_name = zend_strpprintf(0, "%s%d", "extra_named_params", n); + new_name = zend_strpprintf(0, "%s%" PRIu32, "extra_named_params", n); } names[argc + variadic_partial] = new_name; } /* Assign name for $fn */ if (function->common.fn_flags & ZEND_ACC_CLOSURE) { - int n = 1; + uint32_t n = 1; zend_string *new_name = ZSTR_INIT_LITERAL("fn", 0); while (zp_name_exists(names, num_names, new_name)) { zend_string_release(new_name); n++; - new_name = zend_strpprintf(0, "%s%d", "fn", n); + new_name = zend_strpprintf(0, "%s%" PRIu32, "fn", n); } names[argc + variadic_partial + (extra_named_params != NULL)] = new_name; } @@ -666,7 +666,7 @@ static const zend_known_string_id zp_non_dynamic_call_funcs[] = { static bool zp_is_non_dynamic_call_func(zend_function *function) { - for (int i = 0; i < sizeof(zp_non_dynamic_call_funcs) / sizeof(zp_non_dynamic_call_funcs[0]); i++) { + for (size_t i = 0; i < sizeof(zp_non_dynamic_call_funcs) / sizeof(zp_non_dynamic_call_funcs[0]); i++) { if (zend_string_equals(function->common.function_name, ZSTR_KNOWN(zp_non_dynamic_call_funcs[i]))) { return true; } @@ -707,10 +707,10 @@ static zend_op_array *zp_compile(zval *this_ptr, zend_function *function, zend_arena *orig_ast_arena = CG(ast_arena); CG(ast_arena) = zend_arena_create(1024 * 4); - int orig_lineno = CG(zend_lineno); + uint32_t orig_lineno = CG(zend_lineno); CG(zend_lineno) = zend_get_executed_lineno(); - int new_argc = argc; + uint32_t new_argc = argc; if (uses_variadic_placeholder) { new_argc = MAX(new_argc, function->common.num_args); From 4067014f2a1083e403841efc62f908d69f053a04 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Tue, 6 Jan 2026 18:17:48 +0100 Subject: [PATCH 06/59] Clarify --- Zend/zend_partial.c | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/Zend/zend_partial.c b/Zend/zend_partial.c index 7fbdccb69b905..4c07aa6ef5d33 100644 --- a/Zend/zend_partial.c +++ b/Zend/zend_partial.c @@ -224,14 +224,21 @@ static void zp_assign_names(zend_string **names, uint32_t num_names, } } -static bool zp_is_single_may_be_type(uint32_t type_mask) +static bool zp_is_power_of_two(uint32_t x) { - return ((type_mask > 0) && (type_mask & (type_mask - 1)) == 0) + return (x > 0) && !(x & (x - 1)); +} + +static bool zp_is_simple_type(uint32_t type_mask) +{ + ZEND_ASSERT(!(type_mask & ~_ZEND_TYPE_MAY_BE_MASK)); + + return zp_is_power_of_two(type_mask) || type_mask == MAY_BE_BOOL || type_mask == MAY_BE_ANY; } -static zend_ast *zp_single_may_be_type_to_ast(uint32_t type) +static zend_ast *zp_simple_type_to_ast(uint32_t type) { zend_string *name; @@ -305,7 +312,7 @@ static zend_ast *zp_type_to_ast(const zend_type type) if (ZEND_TYPE_IS_UNION(type) || (ZEND_TYPE_IS_COMPLEX(type) && ZEND_TYPE_PURE_MASK(type)) - || (ZEND_TYPE_PURE_MASK(type) && !zp_is_single_may_be_type(ZEND_TYPE_PURE_MASK(type)))) { + || (ZEND_TYPE_PURE_MASK(type) && !zp_is_simple_type(ZEND_TYPE_PURE_MASK(type)))) { zend_ast *type_ast = zend_ast_create_list(0, ZEND_AST_TYPE_UNION); if (ZEND_TYPE_HAS_LIST(type)) { const zend_type *type_ptr; @@ -316,33 +323,34 @@ static zend_ast *zp_type_to_ast(const zend_type type) zend_ast *name_ast = zp_type_name_to_ast( zend_string_copy(ZEND_TYPE_NAME(type))); type_ast = zend_ast_list_add(type_ast, name_ast); - } else if (ZEND_TYPE_IS_COMPLEX(type)) { - ZEND_UNREACHABLE(); + } else { + ZEND_ASSERT(!ZEND_TYPE_IS_COMPLEX(type)); } uint32_t type_mask = ZEND_TYPE_PURE_MASK(type); if ((type_mask & MAY_BE_BOOL) == MAY_BE_BOOL) { - type_ast = zend_ast_list_add(type_ast, zp_single_may_be_type_to_ast(MAY_BE_BOOL)); + type_ast = zend_ast_list_add(type_ast, zp_simple_type_to_ast(MAY_BE_BOOL)); type_mask &= ~MAY_BE_BOOL; } for (uint32_t may_be_type = 1; may_be_type < _ZEND_TYPE_MAY_BE_MASK; may_be_type <<= 1) { if (type_mask & may_be_type) { - type_ast = zend_ast_list_add(type_ast, zp_single_may_be_type_to_ast(may_be_type)); + type_ast = zend_ast_list_add(type_ast, zp_simple_type_to_ast(may_be_type)); } } return type_ast; } if (ZEND_TYPE_IS_INTERSECTION(type)) { + ZEND_ASSERT(!ZEND_TYPE_PURE_MASK(type)); zend_ast *type_ast = zend_ast_create_list(0, ZEND_AST_TYPE_INTERSECTION); const zend_type *type_ptr; ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), type_ptr) { type_ast = zend_ast_list_add(type_ast, zp_type_to_ast(*type_ptr)); } ZEND_TYPE_LIST_FOREACH_END(); - ZEND_ASSERT(!ZEND_TYPE_PURE_MASK(type)); return type_ast; } if (ZEND_TYPE_HAS_NAME(type)) { + ZEND_ASSERT(!ZEND_TYPE_PURE_MASK(type)); zend_ast *type_ast = zp_type_name_to_ast( zend_string_copy(ZEND_TYPE_NAME(type))); return type_ast; @@ -351,9 +359,9 @@ static zend_ast *zp_type_to_ast(const zend_type type) ZEND_ASSERT(!ZEND_TYPE_IS_COMPLEX(type)); uint32_t type_mask = ZEND_TYPE_PURE_MASK(type); - ZEND_ASSERT(zp_is_single_may_be_type(type_mask)); + ZEND_ASSERT(zp_is_simple_type(type_mask)); - return zp_single_may_be_type_to_ast(type_mask); + return zp_simple_type_to_ast(type_mask); } /* Can not use zend_argument_error() as the function is not on the stack */ From e8e044b459d71bd48733596fd4fcf9a40a01a829 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Tue, 6 Jan 2026 18:36:37 +0100 Subject: [PATCH 07/59] Useless memset --- Zend/zend_partial.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Zend/zend_partial.c b/Zend/zend_partial.c index 4c07aa6ef5d33..04a34e8a74228 100644 --- a/Zend/zend_partial.c +++ b/Zend/zend_partial.c @@ -798,7 +798,6 @@ static zend_op_array *zp_compile(zval *this_ptr, zend_function *function, + ((function->common.fn_flags & ZEND_ACC_CLOSURE) != 0); zend_string **param_names = zend_arena_calloc(&CG(ast_arena), num_names, sizeof(zend_string*)); - memset(param_names, 0, sizeof(zend_string*) * num_names); zp_assign_names(param_names, num_names, argc, argv, function, uses_variadic_placeholder, extra_named_params); From a638923461df2e8eb74e25a0ca16621ea657831f Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 7 Jan 2026 14:30:06 +0100 Subject: [PATCH 08/59] Move include --- Zend/zend_execute.c | 1 - Zend/zend_vm_execute.skl | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index cc0412b887f70..25f6319c9db4a 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -44,7 +44,6 @@ #include "zend_call_stack.h" #include "zend_attributes.h" #include "Optimizer/zend_func_info.h" -#include "zend_partial.h" /* Virtual current working directory support */ #include "zend_virtual_cwd.h" diff --git a/Zend/zend_vm_execute.skl b/Zend/zend_vm_execute.skl index 3237a696a6959..f2cfd898964ed 100644 --- a/Zend/zend_vm_execute.skl +++ b/Zend/zend_vm_execute.skl @@ -1,4 +1,5 @@ #include "Zend/zend_vm_opcodes.h" +#include "Zend/zend_partial.h" {%DEFINES%} From 9eaaa0df7a936932d522cbcfea72bee429f52963 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 7 Jan 2026 15:03:55 +0100 Subject: [PATCH 09/59] Rename tests --- Zend/tests/partial_application/magic_003.phpt | 2 +- Zend/tests/partial_application/magic_004.phpt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Zend/tests/partial_application/magic_003.phpt b/Zend/tests/partial_application/magic_003.phpt index 75e26c70b1a34..242403ea38fc8 100644 --- a/Zend/tests/partial_application/magic_003.phpt +++ b/Zend/tests/partial_application/magic_003.phpt @@ -1,5 +1,5 @@ --TEST-- -PFA magic trampoline release unused +PFA magic trampoline release (result unused) --FILE-- From 138a5c1f39cc4b3b4dfa955e01e7757e56b2fc29 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 7 Jan 2026 15:04:02 +0100 Subject: [PATCH 10/59] Fix test --- .../rfc_examples_incompatible_functions.phpt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Zend/tests/partial_application/rfc_examples_incompatible_functions.phpt b/Zend/tests/partial_application/rfc_examples_incompatible_functions.phpt index 2455252b3eba9..dd99cb229ae8a 100644 --- a/Zend/tests/partial_application/rfc_examples_incompatible_functions.phpt +++ b/Zend/tests/partial_application/rfc_examples_incompatible_functions.phpt @@ -4,11 +4,11 @@ PFA RFC examples: "Incompatible functions" section getMessage(), "\n"; } ?> --EXPECT-- -Error: Cannot call func_get_args() dynamically +Error: Cannot call func_get_arg() dynamically From ccb49cccf7d4b7b9c2df4fa790124d83965fb2d5 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 7 Jan 2026 15:04:53 +0100 Subject: [PATCH 11/59] Better type --- Zend/zend_ast.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 9861edafd5407..5b1ff675e1c07 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1463,7 +1463,7 @@ ZEND_API zend_ast * ZEND_FASTCALL zend_ast_dup(zend_ast *ast) { ZEND_ASSERT(ast != NULL); - void *buf = zend_ast_alloc(zend_ast_tree_size(ast)); + zend_ast *buf = zend_ast_alloc(zend_ast_tree_size(ast)); zend_ast_tree_copy(ast, buf); return buf; From 2d74be45566fcfb92ddefa7c3274d4e0a8afba67 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 7 Jan 2026 15:06:29 +0100 Subject: [PATCH 12/59] Generated file --- Zend/zend_vm_execute.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 60d394124a32a..40a68d673ffe3 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -306,6 +306,7 @@ static uint8_t zend_user_opcodes[256] = {0, }; #include "Zend/zend_vm_opcodes.h" +#include "Zend/zend_partial.h" #define SPEC_START_MASK 0x0000ffff #define SPEC_EXTRA_MASK 0xfffc0000 From 61ed1f61b8027df07145b19599d2560d76043c04 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Thu, 8 Jan 2026 10:36:07 +0100 Subject: [PATCH 13/59] Consistency --- Zend/tests/partial_application/non_dynamic_call_funcs.phpt | 6 +++--- Zend/zend_partial.c | 6 ++++-- Zend/zend_string.h | 1 + 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Zend/tests/partial_application/non_dynamic_call_funcs.phpt b/Zend/tests/partial_application/non_dynamic_call_funcs.phpt index 0430986be2ac2..db25ef3dcc93b 100644 --- a/Zend/tests/partial_application/non_dynamic_call_funcs.phpt +++ b/Zend/tests/partial_application/non_dynamic_call_funcs.phpt @@ -41,6 +41,6 @@ foreach (['_func_get_arg', '_compact', '_extract', '_func_get_args', '_func_num_ Error: Cannot call func_get_arg() dynamically Error: Cannot call compact() dynamically Error: Cannot call extract() dynamically -ArgumentCountError: Partial application of func_get_args() expects at most 0 arguments, 1 given -ArgumentCountError: Partial application of func_num_args() expects at most 0 arguments, 1 given -ArgumentCountError: Partial application of get_defined_vars() expects at most 0 arguments, 1 given +Error: Cannot call func_get_args() dynamically +Error: Cannot call func_num_args() dynamically +Error: Cannot call get_defined_vars() dynamically diff --git a/Zend/zend_partial.c b/Zend/zend_partial.c index 04a34e8a74228..dff8d58fec3df 100644 --- a/Zend/zend_partial.c +++ b/Zend/zend_partial.c @@ -666,10 +666,12 @@ static uint32_t zp_compute_num_required(zend_function *function, /* Functions that do not allow to be called dynamically */ static const zend_known_string_id zp_non_dynamic_call_funcs[] = { - ZEND_STR_FUNC_GET_ARG, ZEND_STR_COMPACT, ZEND_STR_EXTRACT, - /* Omit nullary functions such as func_num_args(), as these can't be PFA'd*/ + ZEND_STR_FUNC_GET_ARG, + ZEND_STR_FUNC_GET_ARGS, + ZEND_STR_FUNC_NUM_ARGS, + ZEND_STR_GET_DEFINED_VARS, }; static bool zp_is_non_dynamic_call_func(zend_function *function) diff --git a/Zend/zend_string.h b/Zend/zend_string.h index 512b7f4297331..94ba6e776ecc4 100644 --- a/Zend/zend_string.h +++ b/Zend/zend_string.h @@ -638,6 +638,7 @@ default: ZEND_UNREACHABLE(); _(ZEND_STR_ASSERT, "assert") \ _(ZEND_STR_CALL_USER_FUNC, "call_user_func") \ _(ZEND_STR_ARRAY_SLICE, "array_slice") \ + _(ZEND_STR_GET_DEFINED_VARS, "get_defined_vars") \ _(ZEND_STR_SENSITIVEPARAMETER, "SensitiveParameter") \ _(ZEND_STR_CONST_EXPR_PLACEHOLDER, "[constant expression]") \ _(ZEND_STR_DEPRECATED_CAPITALIZED, "Deprecated") \ From 4b34b92b861f063b78a7560e0e277e3af7d1271b Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Thu, 8 Jan 2026 10:37:49 +0100 Subject: [PATCH 14/59] Fix test name --- Zend/tests/partial_application/references_001.phpt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/tests/partial_application/references_001.phpt b/Zend/tests/partial_application/references_001.phpt index 4a6a8663d8708..080b3085acc07 100644 --- a/Zend/tests/partial_application/references_001.phpt +++ b/Zend/tests/partial_application/references_001.phpt @@ -1,5 +1,5 @@ --TEST-- -PFA returns by val if the actual function does +PFA receives by val if the actual function does --FILE-- Date: Thu, 8 Jan 2026 10:44:05 +0100 Subject: [PATCH 15/59] Improve tests --- Zend/tests/partial_application/references_002.phpt | 2 +- .../tests/partial_application/rfc_examples_debug.phpt | 2 +- Zend/tests/partial_application/statics_001.phpt | 11 +++++------ Zend/tests/partial_application/statics_002.phpt | 9 ++++----- Zend/tests/partial_application/statics_003.phpt | 9 ++++----- Zend/tests/partial_application/variation_ex_001.phpt | 2 +- .../partial_application/variation_invoke_001.phpt | 4 ++-- 7 files changed, 18 insertions(+), 21 deletions(-) diff --git a/Zend/tests/partial_application/references_002.phpt b/Zend/tests/partial_application/references_002.phpt index c203b4fb907af..42a90648c1ae8 100644 --- a/Zend/tests/partial_application/references_002.phpt +++ b/Zend/tests/partial_application/references_002.phpt @@ -7,7 +7,7 @@ function foo($a, &$b) { $b = 2; } -$a = []; +$a = ['this will be changed']; $b = &$a[0]; $foo = foo(1, ?); diff --git a/Zend/tests/partial_application/rfc_examples_debug.phpt b/Zend/tests/partial_application/rfc_examples_debug.phpt index be06e24229f7d..9c5501090d4e0 100644 --- a/Zend/tests/partial_application/rfc_examples_debug.phpt +++ b/Zend/tests/partial_application/rfc_examples_debug.phpt @@ -12,7 +12,7 @@ function g() { } $f = f(?, ?, 3); -$f(1,2); +$f(1, 2); ?> --EXPECTF-- diff --git a/Zend/tests/partial_application/statics_001.phpt b/Zend/tests/partial_application/statics_001.phpt index 906c0dedf121c..610cddf37b547 100644 --- a/Zend/tests/partial_application/statics_001.phpt +++ b/Zend/tests/partial_application/statics_001.phpt @@ -10,13 +10,12 @@ function foo($a) { return $var; } -foo(new stdClass); +var_dump(foo(new stdClass)); $foo = foo(new stdClass, ...); -if ($foo() == 2) { - echo "OK"; -} +var_dump($foo()); ?> ---EXPECTF-- -OK +--EXPECT-- +int(1) +int(2) diff --git a/Zend/tests/partial_application/statics_002.phpt b/Zend/tests/partial_application/statics_002.phpt index 8e1b6cefe00ae..4e6cc82b720db 100644 --- a/Zend/tests/partial_application/statics_002.phpt +++ b/Zend/tests/partial_application/statics_002.phpt @@ -10,14 +10,13 @@ $closure = function ($a) { return $var; }; -$closure(new stdClass); +var_dump($closure(new stdClass)); $foo = $closure(new stdClass, ...); $closure = null; -if ($foo() == 2) { - echo "OK"; -} +var_dump($foo()); ?> --EXPECT-- -OK +int(1) +int(2) diff --git a/Zend/tests/partial_application/statics_003.phpt b/Zend/tests/partial_application/statics_003.phpt index 9fb2568a67e78..04328bf717b8a 100644 --- a/Zend/tests/partial_application/statics_003.phpt +++ b/Zend/tests/partial_application/statics_003.phpt @@ -10,7 +10,7 @@ $closure = function ($a) { return $var; }; -$closure(new stdClass); +var_dump($closure(new stdClass)); $foo = $closure(?); $closure = null; @@ -18,9 +18,8 @@ $closure = null; $bar = $foo(?); $foo = null; -if ($bar(new stdClass) == 2) { - echo "OK"; -} +var_dump($bar(new stdClass)); ?> --EXPECT-- -OK +int(1) +int(2) diff --git a/Zend/tests/partial_application/variation_ex_001.phpt b/Zend/tests/partial_application/variation_ex_001.phpt index 48db63e70a5ed..0f36e90f616d2 100644 --- a/Zend/tests/partial_application/variation_ex_001.phpt +++ b/Zend/tests/partial_application/variation_ex_001.phpt @@ -5,7 +5,7 @@ PFA variation: UAF in cleanup unfinished calls function test($a){} try { - test(1,...)(?); + test(1, ...)(?); } catch (Error $ex) { echo $ex::class, ": ", $ex->getMessage(), "\n"; } diff --git a/Zend/tests/partial_application/variation_invoke_001.phpt b/Zend/tests/partial_application/variation_invoke_001.phpt index a6091e955b156..9285c08da19b9 100644 --- a/Zend/tests/partial_application/variation_invoke_001.phpt +++ b/Zend/tests/partial_application/variation_invoke_001.phpt @@ -8,7 +8,7 @@ function foo($a, $b) { $foo = foo(b: 10, ...); -var_dump($foo->__invoke(32) == 42); +var_dump($foo->__invoke(32)); try { $foo->nothing(); @@ -17,5 +17,5 @@ try { } ?> --EXPECT-- -bool(true) +int(42) Error: Call to undefined method Closure::nothing() From 2af9ec63ced17d4fd2a59dc8c7c6ef8a86e7482e Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Thu, 15 Jan 2026 13:07:22 +0100 Subject: [PATCH 16/59] Improve tests --- Zend/tests/partial_application/rebinding_001.phpt | 14 +++++--------- .../partial_application/variation_call_001.phpt | 2 +- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/Zend/tests/partial_application/rebinding_001.phpt b/Zend/tests/partial_application/rebinding_001.phpt index 012de02c55368..d1913957a8c90 100644 --- a/Zend/tests/partial_application/rebinding_001.phpt +++ b/Zend/tests/partial_application/rebinding_001.phpt @@ -26,22 +26,18 @@ echo "# Cannot be rebound to an unrelated class:\n"; try { $f->bindTo(new Unrelated)(1); } catch (Error $e) { - echo $e->getMessage(), "\n"; + echo $e::class, ": ", $e->getMessage(), "\n"; } echo "# Cannot unbind \$this on instance method:\n"; try { $f->bindTo(null)(1); } catch (Error $e) { - echo $e->getMessage(), "\n"; + echo $e::class, ": ", $e->getMessage(), "\n"; } echo "# Can unbind \$this on static method:\n"; -try { - $g->bindTo(null)(1); -} catch (Error $e) { - echo $e->getMessage(), "\n"; -} +$g->bindTo(null)(1); ?> --EXPECTF-- @@ -54,10 +50,10 @@ object(SubClass)#%d (0) { # Cannot be rebound to an unrelated class: Warning: Cannot bind method C::{closure:%s:%d}() to object of class Unrelated, this will be an error in PHP 9 in %s on line %d -Value of type null is not callable +Error: Value of type null is not callable # Cannot unbind $this on instance method: Warning: Cannot unbind $this of method, this will be an error in PHP 9 in %s on line %d -Value of type null is not callable +Error: Value of type null is not callable # Can unbind $this on static method: string(1) "C" diff --git a/Zend/tests/partial_application/variation_call_001.phpt b/Zend/tests/partial_application/variation_call_001.phpt index 72bccd1292b36..b9c1c19ec9c67 100644 --- a/Zend/tests/partial_application/variation_call_001.phpt +++ b/Zend/tests/partial_application/variation_call_001.phpt @@ -24,7 +24,7 @@ $closure = $bar->method(?, new Param); $closure(1); -$closure->call(new Foo(), 10); +$closure->call(/* newThis: */ new Foo(), 10); ?> --EXPECT-- Bar: 1, Param From 66170c753f375c6264a564a5725d1780b861e1ed Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Thu, 15 Jan 2026 16:53:04 +0100 Subject: [PATCH 17/59] Improve tests --- .../pipe_optimization_003.phpt | 2 +- .../pipe_optimization_004.phpt | 21 +++++++++-------- .../pipe_optimization_005.phpt | 2 +- .../pipe_optimization_007.phpt | 23 +++++++++++-------- 4 files changed, 27 insertions(+), 21 deletions(-) diff --git a/Zend/tests/partial_application/pipe_optimization_003.phpt b/Zend/tests/partial_application/pipe_optimization_003.phpt index 4f45eb5555e29..f6c547d19dae0 100644 --- a/Zend/tests/partial_application/pipe_optimization_003.phpt +++ b/Zend/tests/partial_application/pipe_optimization_003.phpt @@ -1,5 +1,5 @@ --TEST-- -PFA pipe optimization: PFA with only one placeholder can be optimized +PFA pipe optimization: PFA with only one placeholder can be optimized (placeholder first) --EXTENSIONS-- opcache --INI-- diff --git a/Zend/tests/partial_application/pipe_optimization_004.phpt b/Zend/tests/partial_application/pipe_optimization_004.phpt index a2ad5f72ed585..edf0d9f775cd5 100644 --- a/Zend/tests/partial_application/pipe_optimization_004.phpt +++ b/Zend/tests/partial_application/pipe_optimization_004.phpt @@ -18,15 +18,15 @@ if (time() > 0) { } try { -2 |> foo(?, ?); + 2 |> foo(?, ?); } catch (\Throwable $e) { - echo $e->getMessage(), "\n"; + echo $e::class, ": ", $e->getMessage(), "\n"; } ?> --EXPECTF-- $_main: - ; (lines=19, args=0, vars=1, tmps=2) + ; (lines=22, args=0, vars=1, tmps=2) ; (after optimizer) ; %spipe_optimization_004.php:1-16 0000 INIT_FCALL 0 %d string("time") @@ -43,11 +43,14 @@ $_main: 0011 DO_FCALL 0012 RETURN int(1) 0013 CV0($e) = CATCH string("Throwable") -0014 INIT_METHOD_CALL 0 CV0($e) string("getMessage") -0015 V1 = DO_FCALL -0016 ECHO V1 -0017 ECHO string("\n") -0018 RETURN int(1) +0014 T1 = FETCH_CLASS_NAME CV0($e) +0015 ECHO T1 +0016 ECHO string(": ") +0017 INIT_METHOD_CALL 0 CV0($e) string("getMessage") +0018 V1 = DO_FCALL +0019 ECHO V1 +0020 ECHO string("\n") +0021 RETURN int(1) EXCEPTION TABLE: 0005, 0013, -, - @@ -82,4 +85,4 @@ $_main: 0004 SEND_VAR CV1($b) 2 0005 V2 = DO_UCALL 0006 RETURN V2 -Too few arguments to function {closure:%s:%d}(), 1 passed in %s on line %d and exactly 2 expected +ArgumentCountError: Too few arguments to function {closure:pfa:%s:%d}(), 1 passed in %s on line %d and exactly 2 expected diff --git a/Zend/tests/partial_application/pipe_optimization_005.phpt b/Zend/tests/partial_application/pipe_optimization_005.phpt index 3844e09aa8ad2..5a0c6de43bbc2 100644 --- a/Zend/tests/partial_application/pipe_optimization_005.phpt +++ b/Zend/tests/partial_application/pipe_optimization_005.phpt @@ -1,5 +1,5 @@ --TEST-- -PFA pipe optimization: PFA with only one placeholder can be optimized +PFA pipe optimization: PFA with only one placeholder can be optimized (variadic) --EXTENSIONS-- opcache --INI-- diff --git a/Zend/tests/partial_application/pipe_optimization_007.phpt b/Zend/tests/partial_application/pipe_optimization_007.phpt index 39dd48b632e4d..77e0c7cbfa0ee 100644 --- a/Zend/tests/partial_application/pipe_optimization_007.phpt +++ b/Zend/tests/partial_application/pipe_optimization_007.phpt @@ -1,5 +1,5 @@ --TEST-- -PFA pipe optimization: PFA with multiple placeholders can not be optimized +PFA pipe optimization: PFA with multiple placeholders can not be optimized (named) --EXTENSIONS-- opcache --INI-- @@ -18,15 +18,15 @@ if (time() > 0) { } try { -2 |> foo(a: ?, b: ?); + 2 |> foo(a: ?, b: ?); } catch (\Throwable $e) { - echo $e->getMessage(), "\n"; + echo $e::class, ": ", $e->getMessage(), "\n"; } ?> --EXPECTF-- $_main: - ; (lines=19, args=0, vars=1, tmps=2) + ; (lines=22, args=0, vars=1, tmps=2) ; (after optimizer) ; %spipe_optimization_007.php:1-16 0000 INIT_FCALL 0 %d string("time") @@ -43,11 +43,14 @@ $_main: 0011 DO_FCALL 0012 RETURN int(1) 0013 CV0($e) = CATCH string("Throwable") -0014 INIT_METHOD_CALL 0 CV0($e) string("getMessage") -0015 V1 = DO_FCALL -0016 ECHO V1 -0017 ECHO string("\n") -0018 RETURN int(1) +0014 T1 = FETCH_CLASS_NAME CV0($e) +0015 ECHO T1 +0016 ECHO string(": ") +0017 INIT_METHOD_CALL 0 CV0($e) string("getMessage") +0018 V1 = DO_FCALL +0019 ECHO V1 +0020 ECHO string("\n") +0021 RETURN int(1) EXCEPTION TABLE: 0005, 0013, -, - @@ -82,4 +85,4 @@ $_main: 0004 SEND_VAR CV1($b) 2 0005 V2 = DO_UCALL 0006 RETURN V2 -Too few arguments to function {closure:%s:%d}(), 1 passed in %s on line %d and exactly 2 expected +ArgumentCountError: Too few arguments to function {closure:pfa:%s:%d}(), 1 passed in %s on line %d and exactly 2 expected From b618f4a4c6540e23b753fa595f2036e23d53306a Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Thu, 15 Jan 2026 16:54:54 +0100 Subject: [PATCH 18/59] Update comment, add tests --- .../pipe_optimization_012.phpt | 52 +++++++++++ .../pipe_optimization_013.phpt | 87 +++++++++++++++++++ Zend/zend_compile.c | 10 +-- 3 files changed, 144 insertions(+), 5 deletions(-) create mode 100644 Zend/tests/partial_application/pipe_optimization_012.phpt create mode 100644 Zend/tests/partial_application/pipe_optimization_013.phpt diff --git a/Zend/tests/partial_application/pipe_optimization_012.phpt b/Zend/tests/partial_application/pipe_optimization_012.phpt new file mode 100644 index 0000000000000..fef48ba1cd3f9 --- /dev/null +++ b/Zend/tests/partial_application/pipe_optimization_012.phpt @@ -0,0 +1,52 @@ +--TEST-- +PFA optimization: PFA with named args and placeholders can be optimized +--EXTENSIONS-- +opcache +--INI-- +opcache.opt_debug_level=0x20000 +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_cache= +opcache.file_cache_only=0 +--FILE-- + 0) { + function foo($a, $b) { + var_dump($a, $b); + } +} + +1 |> foo(?, b: 2); + +?> +--EXPECTF-- +$_main: + ; (lines=11, args=0, vars=0, tmps=2) + ; (after optimizer) + ; %s:1-12 +0000 INIT_FCALL 0 %d string("time") +0001 V1 = DO_ICALL +0002 T0 = IS_SMALLER int(0) V1 +0003 JMPZ T0 0005 +0004 DECLARE_FUNCTION string("foo") 0 +0005 INIT_FCALL_BY_NAME 1 string("foo") +0006 SEND_VAL_EX int(1) 1 +0007 SEND_VAL_EX int(2) string("b") +0008 CHECK_UNDEF_ARGS +0009 DO_FCALL_BY_NAME +0010 RETURN int(1) + +foo: + ; (lines=7, args=2, vars=2, tmps=0) + ; (after optimizer) + ; %s:4-6 +0000 CV0($a) = RECV 1 +0001 CV1($b) = RECV 2 +0002 INIT_FCALL 2 %d string("var_dump") +0003 SEND_VAR CV0($a) 1 +0004 SEND_VAR CV1($b) 2 +0005 DO_ICALL +0006 RETURN null +int(1) +int(2) diff --git a/Zend/tests/partial_application/pipe_optimization_013.phpt b/Zend/tests/partial_application/pipe_optimization_013.phpt new file mode 100644 index 0000000000000..d150d9cb196bd --- /dev/null +++ b/Zend/tests/partial_application/pipe_optimization_013.phpt @@ -0,0 +1,87 @@ +--TEST-- +PFA optimization: PFA with named args and a variadic placeholder can not be optimized +--EXTENSIONS-- +opcache +--INI-- +opcache.opt_debug_level=0x20000 +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_cache= +opcache.file_cache_only=0 +--FILE-- + 0) { + function foo($a, $b) { + var_dump($a, $b); + } +} + +1 |> foo(b: 2, ...); + +?> +--EXPECTF-- +$_main: + ; (lines=12, args=0, vars=0, tmps=2) + ; (after optimizer) + ; %s:1-12 +0000 INIT_FCALL 0 %d string("time") +0001 V1 = DO_ICALL +0002 T0 = IS_SMALLER int(0) V1 +0003 JMPZ T0 0005 +0004 DECLARE_FUNCTION string("foo") 0 +0005 INIT_FCALL_BY_NAME 0 string("foo") +0006 SEND_VAL_EX int(2) string("b") +0007 T0 = CALLABLE_CONVERT_PARTIAL 3 +0008 INIT_DYNAMIC_CALL 1 T0 +0009 SEND_VAL_EX int(1) 1 +0010 DO_FCALL +0011 RETURN int(1) + +foo: + ; (lines=7, args=2, vars=2, tmps=0) + ; (after optimizer) + ; %s:4-6 +0000 CV0($a) = RECV 1 +0001 CV1($b) = RECV 2 +0002 INIT_FCALL 2 %d string("var_dump") +0003 SEND_VAR CV0($a) 1 +0004 SEND_VAR CV1($b) 2 +0005 DO_ICALL +0006 RETURN null + +$_main: + ; (lines=4, args=0, vars=1, tmps=1) + ; (after optimizer) + ; %s:1-9 +0000 T1 = DECLARE_LAMBDA_FUNCTION 0 +0001 BIND_LEXICAL T1 CV0($b) +0002 FREE T1 +0003 RETURN int(1) +LIVE RANGES: + 1: 0001 - 0002 (tmp/var) + +{closure:pfa:%s:9}: + ; (lines=18, args=1, vars=2, tmps=2) + ; (after optimizer) + ; %s:9-9 +0000 CV0($a) = RECV 1 +0001 BIND_STATIC CV1($b) +0002 T3 = FUNC_NUM_ARGS +0003 T2 = IS_SMALLER_OR_EQUAL T3 int(1) +0004 JMPZ T2 0010 +0005 INIT_FCALL 2 112 string("foo") +0006 SEND_VAR CV0($a) 1 +0007 SEND_VAR CV1($b) 2 +0008 V2 = DO_UCALL +0009 RETURN V2 +0010 INIT_FCALL 2 112 string("foo") +0011 SEND_VAR CV0($a) 1 +0012 SEND_VAR CV1($b) 2 +0013 T2 = FUNC_GET_ARGS int(1) +0014 SEND_UNPACK T2 +0015 CHECK_UNDEF_ARGS +0016 V2 = DO_UCALL +0017 RETURN V2 +int(1) +int(2) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 47fee5c2e2242..b71394990fff9 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -6940,11 +6940,11 @@ static zend_ast *zend_partial_apply(zend_ast *callable_ast, zend_ast *pipe_arg) return NULL; } if (arg->attr == ZEND_PLACEHOLDER_VARIADIC && uses_named_args) { - /* PFAs with both a variadic placeholder and named args can not - * be optimized because the named arg may resolve to the - * position of the placeholder: f(..., name: $v). - * Arg placeholders ('?') are safe, as named args are not - * allowed to override them. */ + /* A PFA with both a variadic placeholder and named args can not + * be optimized because this would result in a positional arg + * after a named arg: f(name: $v, ...) -> f(name: $v, pipe_arg). + * Arg placeholders ('?') are safe since they are not allowed + * after named args. */ return NULL; } } From 73e57c278adebe7abf513a8bd4e21395192b035b Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Fri, 16 Jan 2026 12:04:00 +0100 Subject: [PATCH 19/59] Improve test --- .../variation_closure_001.phpt | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/Zend/tests/partial_application/variation_closure_001.phpt b/Zend/tests/partial_application/variation_closure_001.phpt index c84f8d05f38c6..d8e463b14c640 100644 --- a/Zend/tests/partial_application/variation_closure_001.phpt +++ b/Zend/tests/partial_application/variation_closure_001.phpt @@ -3,14 +3,19 @@ PFA variation: Closure --FILE-- --EXPECTF-- Closure [ static function {closure:%s:%d} ] { - @@ %s 6 - 6 + @@ %s 5 - 5 - Bound Variables [2] { Variable #0 [ $fn ] @@ -21,3 +26,15 @@ Closure [ static function {closure:%s:%d} ] { Parameter #0 [ $b ] } } +Closure [ static function {closure:pfa:%s:%d} ] { + @@ %s 10 - 10 + + - Bound Variables [2] { + Variable #0 [ $fn2 ] + Variable #1 [ $a ] + } + + - Parameters [1] { + Parameter #0 [ $fn ] + } +} From 0a566519406be5ea99355b8609967f3e2e792b02 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Fri, 16 Jan 2026 12:04:08 +0100 Subject: [PATCH 20/59] Avoid void cast --- ext/opcache/ZendAccelerator.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 2f4ff38311cea..b35aa2b6ca0da 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -2042,7 +2042,7 @@ static zend_string *zend_accel_pfa_key(const zend_op *declaring_opline, dest = zend_accel_uintptr_hex(dest, (uintptr_t)declaring_opline); *dest++ = ':'; - void *ptr; + const void *ptr; if ((called_function->common.fn_flags & ZEND_ACC_CLOSURE) && called_function->type == ZEND_USER_FUNCTION) { /* Can not use 'called_function' as part of the key, as it's an inner @@ -2051,7 +2051,7 @@ static zend_string *zend_accel_pfa_key(const zend_op *declaring_opline, * in this case. */ ptr = called_function->op_array.opcodes; } else { - ptr = (void*) called_function; + ptr = called_function; } dest = zend_accel_uintptr_hex(dest, (uintptr_t)ptr); From 36f606d45bcdaa7617287cfc2e81c6d812be812a Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Fri, 16 Jan 2026 12:44:02 +0100 Subject: [PATCH 21/59] Fix tests --- Zend/tests/partial_application/magic_001.phpt | 14 +++++++------- Zend/tests/partial_application/magic_002.phpt | 14 +++++++------- Zend/tests/partial_application/magic_005.phpt | 2 +- Zend/tests/partial_application/rfc_examples.inc | 1 + .../rfc_examples_magic_methods.phpt | 2 +- 5 files changed, 17 insertions(+), 16 deletions(-) diff --git a/Zend/tests/partial_application/magic_001.phpt b/Zend/tests/partial_application/magic_001.phpt index 60f5a3f20dfb6..e1d5248fdee41 100644 --- a/Zend/tests/partial_application/magic_001.phpt +++ b/Zend/tests/partial_application/magic_001.phpt @@ -3,10 +3,10 @@ __call() can be partially applied --FILE-- public method {closure:%s:%d} ] { @@ %s 12 - 12 - Parameters [1] { - Parameter #0 [ $args0 ] + Parameter #0 [ mixed $arguments0 ] } } ArgumentCountError: Too few arguments to function Foo::{closure:%s:%d}(), 0 passed in %s on line %d and exactly 1 expected @@ -59,8 +59,8 @@ Closure [ public method {closure:%s:%d} ] { @@ %s 30 - 30 - Parameters [2] { - Parameter #0 [ $args0 ] - Parameter #1 [ ...$args ] + Parameter #0 [ mixed $arguments0 ] + Parameter #1 [ mixed ...$arguments ] } } Foo::method @@ -69,11 +69,11 @@ Closure [ public method {closure:%s:%d} ] { @@ %s 36 - 36 - Bound Variables [1] { - Variable #0 [ $args0 ] + Variable #0 [ $arguments0 ] } - Parameters [1] { - Parameter #0 [ ...$args ] + Parameter #0 [ mixed ...$arguments ] } } Foo::method diff --git a/Zend/tests/partial_application/magic_002.phpt b/Zend/tests/partial_application/magic_002.phpt index d4baf7afc18ff..ff4e26623a9ed 100644 --- a/Zend/tests/partial_application/magic_002.phpt +++ b/Zend/tests/partial_application/magic_002.phpt @@ -3,10 +3,10 @@ __callStatic() can be partially applied --FILE-- static public method {closure:%s:%d} ] { @@ %s 10 - 10 - Parameters [1] { - Parameter #0 [ $args0 ] + Parameter #0 [ mixed $arguments0 ] } } Foo::method @@ -42,8 +42,8 @@ Closure [ static public method {closure:%s:%d} ] { @@ %s 16 - 16 - Parameters [2] { - Parameter #0 [ $args0 ] - Parameter #1 [ ...$args ] + Parameter #0 [ mixed $arguments0 ] + Parameter #1 [ mixed ...$arguments ] } } Foo::method @@ -52,11 +52,11 @@ Closure [ static public method {closure:%s:%d} ] { @@ %s 22 - 22 - Bound Variables [1] { - Variable #0 [ $args0 ] + Variable #0 [ $arguments0 ] } - Parameters [1] { - Parameter #0 [ ...$args ] + Parameter #0 [ mixed ...$arguments ] } } Foo::method diff --git a/Zend/tests/partial_application/magic_005.phpt b/Zend/tests/partial_application/magic_005.phpt index 8c4e878196fc7..5cdee061cc77f 100644 --- a/Zend/tests/partial_application/magic_005.phpt +++ b/Zend/tests/partial_application/magic_005.phpt @@ -24,7 +24,7 @@ object(Closure)#%d (%d) { } ["parameter"]=> array(1) { - ["$args0"]=> + ["$arguments0"]=> string(10) "" } } diff --git a/Zend/tests/partial_application/rfc_examples.inc b/Zend/tests/partial_application/rfc_examples.inc index f6e7d9f6266dd..53babc07888b6 100644 --- a/Zend/tests/partial_application/rfc_examples.inc +++ b/Zend/tests/partial_application/rfc_examples.inc @@ -52,6 +52,7 @@ function check_equivalence($tests) '?float' => 100.5 + $i, 'string' => (string) (100 + $i), 'Point' => new Point, + 'mixed' => "mixed($i)", '' => "mixed($i)", }; } diff --git a/Zend/tests/partial_application/rfc_examples_magic_methods.phpt b/Zend/tests/partial_application/rfc_examples_magic_methods.phpt index bf3ce3404e662..7162a790c57cc 100644 --- a/Zend/tests/partial_application/rfc_examples_magic_methods.phpt +++ b/Zend/tests/partial_application/rfc_examples_magic_methods.phpt @@ -18,7 +18,7 @@ check_equivalence([ 'Test 1' => [ $f->method(?, ?), (function ($f) { - return fn($args0, $args1) => $f->method($args0, $args1); + return fn(mixed $arguments0, mixed $arguments1) => $f->method($arguments0, $arguments1); })($f)->bindTo($f), ], ]); From b6c3c71c7361a26e7c9dcc5ca065f367919179e9 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Fri, 16 Jan 2026 12:54:24 +0100 Subject: [PATCH 22/59] constify --- Zend/zend_partial.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Zend/zend_partial.c b/Zend/zend_partial.c index dff8d58fec3df..a6071b5cb1c4d 100644 --- a/Zend/zend_partial.c +++ b/Zend/zend_partial.c @@ -54,8 +54,9 @@ #define Z_IS_PLACEHOLDER_P(p) (Z_TYPE_P(p) == _IS_PLACEHOLDER) -#define IS_STATIC_CLOSURE(function) \ - (((function)->common.fn_flags & (ZEND_ACC_STATIC|ZEND_ACC_CLOSURE)) == (ZEND_ACC_STATIC|ZEND_ACC_CLOSURE)) +static zend_always_inline bool zp_is_static_closure(const zend_function *function) { + return ((function->common.fn_flags & (ZEND_ACC_STATIC|ZEND_ACC_CLOSURE)) == (ZEND_ACC_STATIC|ZEND_ACC_CLOSURE)); +} static zend_never_inline ZEND_COLD void zp_args_underflow( const zend_function *function, uint32_t args, uint32_t expected) @@ -119,7 +120,7 @@ static zend_result zp_args_check(const zend_function *function, return SUCCESS; } -static bool zp_name_exists(zend_string **names, uint32_t num_names, zend_string *name) +static bool zp_name_exists(zend_string **names, uint32_t num_names, const zend_string *name) { for (uint32_t i = 0; i < num_names; i++) { if (names[i] && zend_string_equals(names[i], name)) { @@ -987,7 +988,7 @@ static zend_op_array *zp_compile(zval *this_ptr, zend_function *function, NULL, params_ast, lexical_vars_ast, stmts_ast, return_type_ast, attributes_ast); - if (Z_TYPE_P(this_ptr) != IS_OBJECT || IS_STATIC_CLOSURE(function)) { + if (Z_TYPE_P(this_ptr) != IS_OBJECT || zp_is_static_closure(function)) { ((zend_ast_decl*)closure_ast)->flags |= ZEND_ACC_STATIC; } @@ -1138,7 +1139,7 @@ void zend_partial_create(zval *result, zval *this_ptr, zend_function *function, called_scope = Z_CE_P(this_ptr); } - if (Z_TYPE_P(this_ptr) == IS_OBJECT && !IS_STATIC_CLOSURE(function)) { + if (Z_TYPE_P(this_ptr) == IS_OBJECT && !zp_is_static_closure(function)) { ZVAL_COPY_VALUE(&object, this_ptr); } else { ZVAL_UNDEF(&object); From d1b7094298b99e082a5b0123d8f9dc3d702de526 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Fri, 16 Jan 2026 13:00:46 +0100 Subject: [PATCH 23/59] inline --- Zend/zend_partial.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_partial.c b/Zend/zend_partial.c index a6071b5cb1c4d..956de6b5af0c2 100644 --- a/Zend/zend_partial.c +++ b/Zend/zend_partial.c @@ -225,7 +225,7 @@ static void zp_assign_names(zend_string **names, uint32_t num_names, } } -static bool zp_is_power_of_two(uint32_t x) +static inline bool zp_is_power_of_two(uint32_t x) { return (x > 0) && !(x & (x - 1)); } From 6be58237591c41ec40768e7ecc61c6c616b4a19f Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Fri, 16 Jan 2026 13:00:54 +0100 Subject: [PATCH 24/59] Useless condition --- Zend/zend_partial.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_partial.c b/Zend/zend_partial.c index 956de6b5af0c2..bdf825076aad8 100644 --- a/Zend/zend_partial.c +++ b/Zend/zend_partial.c @@ -399,7 +399,7 @@ static zend_result zp_get_param_default_value(zval *result, zend_function *funct } else { ZEND_ASSERT(opline->opcode == ZEND_RECV); } - } else if (function->type == ZEND_INTERNAL_FUNCTION) { + } else { if (function->common.fn_flags & ZEND_ACC_USER_ARG_INFO) { goto error; } From 21f0054eac5a5df70cd90c03a72fc9609f40a173 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Fri, 16 Jan 2026 13:03:54 +0100 Subject: [PATCH 25/59] const --- Zend/zend_partial.c | 8 ++++---- ext/opcache/ZendAccelerator.c | 2 +- ext/opcache/ZendAccelerator.h | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Zend/zend_partial.c b/Zend/zend_partial.c index bdf825076aad8..1577c6be4168c 100644 --- a/Zend/zend_partial.c +++ b/Zend/zend_partial.c @@ -1032,7 +1032,7 @@ static zend_op_array *zp_compile(zval *this_ptr, zend_function *function, goto clean; } -static zend_op_array *zp_get_op_array(zval *this_ptr, zend_function *function, +static const zend_op_array *zp_get_op_array(zval *this_ptr, zend_function *function, uint32_t argc, zval *argv, zend_array *extra_named_params, const zend_array *named_positions, const zend_op_array *declaring_op_array, @@ -1045,7 +1045,7 @@ static zend_op_array *zp_get_op_array(zval *this_ptr, zend_function *function, return cache_slot[1]; } - zend_op_array *op_array = zend_accel_pfa_cache_get(declaring_op_array, + const zend_op_array *op_array = zend_accel_pfa_cache_get(declaring_op_array, declaring_opline, function); if (UNEXPECTED(!op_array)) { @@ -1058,7 +1058,7 @@ static zend_op_array *zp_get_op_array(zval *this_ptr, zend_function *function, cache_slot[0] = function->type == ZEND_INTERNAL_FUNCTION ? (void*)function : (void*)function->op_array.opcodes; - cache_slot[1] = op_array; + cache_slot[1] = (zend_op_array*)op_array; } return op_array; @@ -1119,7 +1119,7 @@ void zend_partial_create(zval *result, zval *this_ptr, zend_function *function, const zend_op *declaring_opline, void **cache_slot, bool uses_variadic_placeholder) { - zend_op_array *op_array = zp_get_op_array(this_ptr, function, argc, argv, + const zend_op_array *op_array = zp_get_op_array(this_ptr, function, argc, argv, extra_named_params, named_positions, declaring_op_array, declaring_opline, cache_slot, uses_variadic_placeholder); diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index b35aa2b6ca0da..82eea9a25d154 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -2063,7 +2063,7 @@ static zend_string *zend_accel_pfa_key(const zend_op *declaring_opline, return key; } -zend_op_array *zend_accel_pfa_cache_get(const zend_op_array *declaring_op_array, +const zend_op_array *zend_accel_pfa_cache_get(const zend_op_array *declaring_op_array, const zend_op *declaring_opline, const zend_function *called_function) { zend_string *key = zend_accel_pfa_key(declaring_opline, called_function); diff --git a/ext/opcache/ZendAccelerator.h b/ext/opcache/ZendAccelerator.h index 9eec7555c4ea2..ef7eea433c095 100644 --- a/ext/opcache/ZendAccelerator.h +++ b/ext/opcache/ZendAccelerator.h @@ -334,7 +334,7 @@ zend_string* ZEND_FASTCALL accel_new_interned_string(zend_string *str); uint32_t zend_accel_get_class_name_map_ptr(zend_string *type_name); -zend_op_array *zend_accel_pfa_cache_get(const zend_op_array *declaring_op_array, +const zend_op_array *zend_accel_pfa_cache_get(const zend_op_array *declaring_op_array, const zend_op *declaring_opline, const zend_function *called_function); From aa696d2c36ffcdd7d22b793a450cd7e2fdbce669 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Fri, 16 Jan 2026 13:24:39 +0100 Subject: [PATCH 26/59] Simplify --- Zend/zend_partial.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Zend/zend_partial.c b/Zend/zend_partial.c index 1577c6be4168c..9b903cbe4a2c8 100644 --- a/Zend/zend_partial.c +++ b/Zend/zend_partial.c @@ -167,17 +167,15 @@ static void zp_assign_names(zend_string **names, uint32_t num_names, if (!Z_IS_PLACEHOLDER_P(&argv[offset])) { continue; } - uint32_t n = offset - function->common.num_args; zend_string *orig_name = zp_get_param_name(function, function->common.num_args); zend_string *new_name; - do { + for (uint32_t n = offset - function->common.num_args;; n++) { new_name = zend_strpprintf_unchecked(0, "%S%" PRIu32, orig_name, n); if (!zp_name_exists(names, num_names, new_name)) { break; } - n++; zend_string_release(new_name); - } while (true); + } names[offset] = new_name; zend_string_release(orig_name); } From d553c3d3b6f71ed19f2f4573d0be67e8ea51516c Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Fri, 16 Jan 2026 13:29:31 +0100 Subject: [PATCH 27/59] fix build --- Zend/zend_execute.c | 5 ++--- Zend/zend_execute.h | 3 +-- Zend/zend_partial.c | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 25f6319c9db4a..1cc0d490f031a 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1230,10 +1230,9 @@ static zend_always_inline bool zend_check_type( } ZEND_API bool zend_check_type_ex( - const zend_type *type, zval *arg, zend_class_entry *scope, - bool is_return_type, bool is_internal) + const zend_type *type, zval *arg, bool is_return_type, bool is_internal) { - return zend_check_type(type, arg, scope, is_return_type, is_internal); + return zend_check_type(type, arg, is_return_type, is_internal); } ZEND_API bool zend_check_user_type_slow( diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index f44538d62f6ae..48f7e7a725304 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -110,8 +110,7 @@ ZEND_API bool zend_verify_ref_array_assignable(zend_reference *ref); ZEND_API bool zend_check_user_type_slow( const zend_type *type, zval *arg, const zend_reference *ref, bool is_return_type); ZEND_API bool zend_check_type_ex( - const zend_type *type, zval *arg, zend_class_entry *scope, - bool is_return_type, bool is_internal); + const zend_type *type, zval *arg, bool is_return_type, bool is_internal); #if ZEND_DEBUG ZEND_API bool zend_internal_call_should_throw(const zend_function *fbc, zend_execute_data *call); diff --git a/Zend/zend_partial.c b/Zend/zend_partial.c index 9b903cbe4a2c8..ed4cfcb9d6154 100644 --- a/Zend/zend_partial.c +++ b/Zend/zend_partial.c @@ -1091,7 +1091,7 @@ static void zp_bind(zval *result, zend_function *function, uint32_t argc, zval * arg_info = NULL; } if (arg_info && ZEND_TYPE_IS_SET(arg_info->type) - && UNEXPECTED(!zend_check_type_ex(&arg_info->type, var, function->common.scope, 0, 0))) { + && UNEXPECTED(!zend_check_type_ex(&arg_info->type, var, 0, 0))) { zend_verify_arg_error(function, arg_info, offset+1, var); zval_ptr_dtor(result); ZVAL_NULL(result); From bd1814329daebed18256398c0730ccc1895c9c67 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Fri, 16 Jan 2026 15:40:29 +0100 Subject: [PATCH 28/59] Fix test on x32 --- Zend/tests/partial_application/pipe_optimization_013.phpt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Zend/tests/partial_application/pipe_optimization_013.phpt b/Zend/tests/partial_application/pipe_optimization_013.phpt index d150d9cb196bd..164562fdd0b40 100644 --- a/Zend/tests/partial_application/pipe_optimization_013.phpt +++ b/Zend/tests/partial_application/pipe_optimization_013.phpt @@ -70,12 +70,12 @@ LIVE RANGES: 0002 T3 = FUNC_NUM_ARGS 0003 T2 = IS_SMALLER_OR_EQUAL T3 int(1) 0004 JMPZ T2 0010 -0005 INIT_FCALL 2 112 string("foo") +0005 INIT_FCALL 2 %d string("foo") 0006 SEND_VAR CV0($a) 1 0007 SEND_VAR CV1($b) 2 0008 V2 = DO_UCALL 0009 RETURN V2 -0010 INIT_FCALL 2 112 string("foo") +0010 INIT_FCALL 2 %d string("foo") 0011 SEND_VAR CV0($a) 1 0012 SEND_VAR CV1($b) 2 0013 T2 = FUNC_GET_ARGS int(1) From f7bf55499e690f32b4f70161d5722ac7b864bd97 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Mon, 19 Jan 2026 12:56:25 +0100 Subject: [PATCH 29/59] Support PFAs in array_map() optimization --- Zend/zend_compile.c | 41 ++----- .../array_map_foreach_optimization_006.phpt | 84 ++++++++++++++ .../array_map_foreach_optimization_007.phpt | 109 ++++++++++++++++++ 3 files changed, 204 insertions(+), 30 deletions(-) create mode 100644 ext/standard/tests/array/array_map_foreach_optimization_006.phpt create mode 100644 ext/standard/tests/array/array_map_foreach_optimization_007.phpt diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index b71394990fff9..d3b556752a775 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -104,6 +104,8 @@ static void zend_compile_expr(znode *result, zend_ast *ast); static void zend_compile_stmt(zend_ast *ast); static void zend_compile_assign(znode *result, zend_ast *ast, bool stmt, uint32_t type); +static zend_ast *zend_partial_apply(zend_ast *callable_ast, zend_ast *pipe_arg); + #ifdef ZEND_CHECK_STACK_LIMIT zend_never_inline static void zend_stack_limit_error(void) { @@ -5251,42 +5253,21 @@ static zend_result zend_compile_func_array_map(znode *result, zend_ast_list *arg } zend_ast *callback = args->child[0]; - - /* Bail out if the callback is not a FCC/PFA. */ - zend_ast *args_ast; - switch (callback->kind) { - case ZEND_AST_CALL: - case ZEND_AST_STATIC_CALL: - args_ast = zend_ast_call_get_args(callback); - if (args_ast->kind != ZEND_AST_CALLABLE_CONVERT) { - return FAILURE; - } - - break; - default: - return FAILURE; - } - - /* Bail out if the callback is assert() due to the AST stringification logic - * breaking for the generated call. - */ - if (callback->kind == ZEND_AST_CALL - && callback->child[0]->kind == ZEND_AST_ZVAL - && Z_TYPE_P(zend_ast_get_zval(callback->child[0])) == IS_STRING - && zend_string_equals_literal_ci(zend_ast_get_str(callback->child[0]), "assert")) { - return FAILURE; - } - - zend_ast_list *callback_args = zend_ast_get_list(((zend_ast_fcc*)args_ast)->args); - if (callback_args->children != 1 || callback_args->child[0]->attr != ZEND_PLACEHOLDER_VARIADIC) { - /* Full PFA is not yet implemented, will fail in zend_compile_call_common(). */ + if (callback->kind != ZEND_AST_CALL && callback->kind != ZEND_AST_STATIC_CALL) { return FAILURE; } znode value; value.op_type = IS_TMP_VAR; value.u.op.var = get_temporary_variable(); - zend_ast *call_args = zend_ast_create_list(1, ZEND_AST_ARG_LIST, zend_ast_create_znode(&value)); + + zend_ast *call_args = zend_partial_apply(callback, + zend_ast_create_znode(&value)); + if (!call_args) { + CG(active_op_array)->T--; + /* The callback is not a FCC/PFA, or is not optimizable */ + return FAILURE; + } zend_op *opline; diff --git a/ext/standard/tests/array/array_map_foreach_optimization_006.phpt b/ext/standard/tests/array/array_map_foreach_optimization_006.phpt new file mode 100644 index 0000000000000..d354f1d5ec70c --- /dev/null +++ b/ext/standard/tests/array/array_map_foreach_optimization_006.phpt @@ -0,0 +1,84 @@ +--TEST-- +array_map(): foreach optimization - PFA +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.opt_debug_level=0x20000 +--FILE-- + +--EXPECTF-- +$_main: + ; (lines=%d, args=0, vars=%d, tmps=%d) + ; (after optimizer) + ; %s +0000 INIT_FCALL 2 %d string("range") +0001 SEND_VAL int(1) 1 +0002 SEND_VAL int(10) 2 +0003 V2 = DO_ICALL +0004 ASSIGN CV0($array) V2 +0005 TYPE_ASSERT 131079 string("array_map") CV0($array) +0006 T2 = INIT_ARRAY 0 (packed) NEXT +0007 V3 = FE_RESET_R CV0($array) 0015 +0008 T5 = FE_FETCH_R V3 T4 0015 +0009 INIT_FCALL 2 %d string("plusn") +0010 SEND_VAL T4 1 +0011 SEND_VAL int(2) 2 +0012 V4 = DO_UCALL +0013 T2 = ADD_ARRAY_ELEMENT V4 T5 +0014 JMP 0008 +0015 FE_FREE V3 +0016 ASSIGN CV1($foo) T2 +0017 INIT_FCALL 1 %d string("var_dump") +0018 SEND_VAR CV1($foo) 1 +0019 DO_ICALL +0020 RETURN int(1) +LIVE RANGES: + 2: 0007 - 0016 (tmp/var) + 3: 0008 - 0015 (loop) + 4: 0009 - 0010 (tmp/var) + 5: 0009 - 0013 (tmp/var) + +plusn: + ; (lines=4, args=2, vars=2, tmps=1) + ; (after optimizer) + ; %s +0000 CV0($x) = RECV 1 +0001 CV1($n) = RECV 2 +0002 T2 = ADD CV0($x) CV1($n) +0003 RETURN T2 +array(10) { + [0]=> + int(3) + [1]=> + int(4) + [2]=> + int(5) + [3]=> + int(6) + [4]=> + int(7) + [5]=> + int(8) + [6]=> + int(9) + [7]=> + int(10) + [8]=> + int(11) + [9]=> + int(12) +} diff --git a/ext/standard/tests/array/array_map_foreach_optimization_007.phpt b/ext/standard/tests/array/array_map_foreach_optimization_007.phpt new file mode 100644 index 0000000000000..d2dbf7f722529 --- /dev/null +++ b/ext/standard/tests/array/array_map_foreach_optimization_007.phpt @@ -0,0 +1,109 @@ +--TEST-- +array_map(): foreach optimization - unoptimizable PFA +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.opt_debug_level=0x20000 +--FILE-- + +--EXPECTF-- +$_main: + ; (lines=%d, args=0, vars=%d, tmps=%d) + ; (after optimizer) + ; %s +0000 INIT_FCALL 2 %d string("range") +0001 SEND_VAL int(1) 1 +0002 SEND_VAL int(10) 2 +0003 V2 = DO_ICALL +0004 ASSIGN CV0($array) V2 +0005 INIT_FCALL 2 %d string("array_map") +0006 INIT_FCALL 0 %d string("plusn") +0007 SEND_VAL int(2) string("n") +0008 T2 = CALLABLE_CONVERT_PARTIAL 2 +0009 SEND_VAL T2 1 +0010 SEND_VAR CV0($array) 2 +0011 V2 = DO_ICALL +0012 ASSIGN CV1($foo) V2 +0013 INIT_FCALL 1 %d string("var_dump") +0014 SEND_VAR CV1($foo) 1 +0015 DO_ICALL +0016 RETURN int(1) + +plusn: + ; (lines=4, args=2, vars=2, tmps=1) + ; (after optimizer) + ; %s +0000 CV0($x) = RECV 1 +0001 CV1($n) = RECV 2 +0002 T2 = ADD CV0($x) CV1($n) +0003 RETURN T2 + +$_main: + ; (lines=4, args=0, vars=1, tmps=1) + ; (after optimizer) + ; %s:1-9 +0000 T1 = DECLARE_LAMBDA_FUNCTION 0 +0001 BIND_LEXICAL T1 CV0($n) +0002 FREE T1 +0003 RETURN int(1) +LIVE RANGES: + 1: 0001 - 0002 (tmp/var) + +{closure:pfa:%s:9}: + ; (lines=18, args=1, vars=2, tmps=2) + ; (after optimizer) + ; %s:9-9 +0000 CV0($x) = RECV 1 +0001 BIND_STATIC CV1($n) +0002 T3 = FUNC_NUM_ARGS +0003 T2 = IS_SMALLER_OR_EQUAL T3 int(1) +0004 JMPZ T2 0010 +0005 INIT_FCALL 2 %d string("plusn") +0006 SEND_VAR CV0($x) 1 +0007 SEND_VAR CV1($n) 2 +0008 V2 = DO_UCALL +0009 RETURN V2 +0010 INIT_FCALL 2 %d string("plusn") +0011 SEND_VAR CV0($x) 1 +0012 SEND_VAR CV1($n) 2 +0013 T2 = FUNC_GET_ARGS int(1) +0014 SEND_UNPACK T2 +0015 CHECK_UNDEF_ARGS +0016 V2 = DO_UCALL +0017 RETURN V2 +array(10) { + [0]=> + int(3) + [1]=> + int(4) + [2]=> + int(5) + [3]=> + int(6) + [4]=> + int(7) + [5]=> + int(8) + [6]=> + int(9) + [7]=> + int(10) + [8]=> + int(11) + [9]=> + int(12) +} From 415741e4ddc0cadfa9dc8e8a08570b1761927c76 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Mon, 19 Jan 2026 13:10:41 +0100 Subject: [PATCH 30/59] Reuse zp_argument_error() --- Zend/zend_API.c | 39 ++++++++++++++++++++++++++++++++++++--- Zend/zend_API.h | 9 +++++++++ Zend/zend_partial.c | 29 ++++------------------------- Zend/zend_vm_def.h | 5 +++-- Zend/zend_vm_execute.h | 10 ++++++---- 5 files changed, 58 insertions(+), 34 deletions(-) diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 2541486c492a2..e6d544a3cbadf 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -374,7 +374,9 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_unexpected_extra_named_error(void) ); } -ZEND_API ZEND_COLD void ZEND_FASTCALL zend_argument_error_variadic(zend_class_entry *error_ce, uint32_t arg_num, const char *format, va_list va) /* {{{ */ +ZEND_API ZEND_COLD void ZEND_FASTCALL zend_argument_error_variadic_ex( + const zend_function *function, uint32_t arg_num, + zend_class_entry *error_ce, const char *format, va_list va) { zend_string *func_name; const char *arg_name; @@ -383,8 +385,8 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_argument_error_variadic(zend_class_en return; } - func_name = get_active_function_or_method_name(); - arg_name = get_active_function_arg_name(arg_num); + func_name = get_function_or_method_name(function); + arg_name = get_function_arg_name(function, arg_num); zend_vspprintf(&message, 0, format, va); zend_throw_error(error_ce, "%s(): Argument #%d%s%s%s %s", @@ -394,8 +396,27 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_argument_error_variadic(zend_class_en efree(message); zend_string_release(func_name); } + +ZEND_API ZEND_COLD void ZEND_FASTCALL zend_argument_error_variadic(zend_class_entry *error_ce, uint32_t arg_num, const char *format, va_list va) /* {{{ */ +{ + ZEND_ASSERT(zend_is_executing()); + + const zend_function *function = zend_active_function(); + + zend_argument_error_variadic_ex(function, arg_num, error_ce, format, va); +} /* }}} */ +ZEND_API ZEND_COLD void zend_argument_error_ex(const zend_function *function, + uint32_t arg_num, zend_class_entry *error_ce, const char *format, ...) +{ + va_list va; + + va_start(va, format); + zend_argument_error_variadic_ex(function, arg_num, error_ce, format, va); + va_end(va); +} + ZEND_API ZEND_COLD void zend_argument_error(zend_class_entry *error_ce, uint32_t arg_num, const char *format, ...) /* {{{ */ { va_list va; @@ -406,6 +427,18 @@ ZEND_API ZEND_COLD void zend_argument_error(zend_class_entry *error_ce, uint32_t } /* }}} */ +ZEND_API ZEND_COLD void zend_argument_type_error_ex( + const zend_function *function, uint32_t arg_num, + const char *format, ...) +{ + va_list va; + + va_start(va, format); + zend_argument_error_variadic_ex(function, arg_num, + zend_ce_type_error, format, va); + va_end(va); +} + ZEND_API ZEND_COLD void zend_argument_type_error(uint32_t arg_num, const char *format, ...) /* {{{ */ { va_list va; diff --git a/Zend/zend_API.h b/Zend/zend_API.h index 17f7ce3263f84..5990874e01e3f 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -1559,13 +1559,22 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_wrong_callback_error(uint32_t num, ch ZEND_API ZEND_COLD void ZEND_FASTCALL zend_wrong_callback_or_null_error(uint32_t num, char *error); ZEND_API ZEND_COLD void ZEND_FASTCALL zend_unexpected_extra_named_error(void); ZEND_API ZEND_COLD void ZEND_FASTCALL zend_argument_error_variadic(zend_class_entry *error_ce, uint32_t arg_num, const char *format, va_list va); +ZEND_API ZEND_COLD void ZEND_FASTCALL zend_argument_error_variadic_ex( + const zend_function *function, uint32_t arg_num, + zend_class_entry *error_ce, const char *format, va_list va); ZEND_API ZEND_COLD void zend_argument_error(zend_class_entry *error_ce, uint32_t arg_num, const char *format, ...); +ZEND_API ZEND_COLD void zend_argument_error_ex(const zend_function *function, + uint32_t arg_num, zend_class_entry *error_ce, const char *format, ...); ZEND_API ZEND_COLD void zend_argument_type_error(uint32_t arg_num, const char *format, ...); +ZEND_API ZEND_COLD void zend_argument_type_error_ex( + const zend_function *function, uint32_t arg_num, + const char *format, ...); ZEND_API ZEND_COLD void zend_argument_value_error(uint32_t arg_num, const char *format, ...); ZEND_API ZEND_COLD void zend_argument_must_not_be_empty_error(uint32_t arg_num); ZEND_API ZEND_COLD void zend_class_redeclaration_error(int type, const zend_class_entry *old_ce); ZEND_API ZEND_COLD void zend_class_redeclaration_error_ex(int type, zend_string *new_name, const zend_class_entry *old_ce); + #define ZPP_ERROR_OK 0 #define ZPP_ERROR_FAILURE 1 #define ZPP_ERROR_WRONG_CALLBACK 2 diff --git a/Zend/zend_partial.c b/Zend/zend_partial.c index ed4cfcb9d6154..555c0763361d2 100644 --- a/Zend/zend_partial.c +++ b/Zend/zend_partial.c @@ -363,28 +363,6 @@ static zend_ast *zp_type_to_ast(const zend_type type) return zp_simple_type_to_ast(type_mask); } -/* Can not use zend_argument_error() as the function is not on the stack */ -static zend_never_inline ZEND_COLD void zp_argument_error(zend_class_entry *error_ce, - zend_function *function, uint32_t arg_num, const char *format, ...) -{ - zend_string *func_name = get_function_or_method_name(function); - const char *arg_name = get_function_arg_name(function, arg_num); - - char *message = NULL; - - va_list va; - va_start(va, format); - zend_vspprintf(&message, 0, format, va); - va_end(va); - - zend_throw_error(error_ce, "%s(): Argument #%d%s%s%s %s", - ZSTR_VAL(func_name), arg_num, - arg_name ? " ($" : "", arg_name ? arg_name : "", arg_name ? ")" : "", message - ); - efree(message); - zend_string_release(func_name); -} - static zend_result zp_get_param_default_value(zval *result, zend_function *function, uint32_t arg_offset) { ZEND_ASSERT(arg_offset < function->common.num_args); @@ -410,7 +388,8 @@ static zend_result zp_get_param_default_value(zval *result, zend_function *funct } error: - zp_argument_error(zend_ce_argument_count_error, function, arg_offset + 1, + zend_argument_error_ex(function, arg_offset + 1, + zend_ce_argument_count_error, "must be passed explicitly, because the default value is not known"); return FAILURE; @@ -544,8 +523,8 @@ static zend_ast *zp_compile_forwarding_call( /* Required param was not passed. This can happen due to named * args. Using the same exception CE and message as * zend_handle_undef_args(). */ - zp_argument_error(zend_ce_argument_count_error, function, - offset + 1, "not passed"); + zend_argument_error_ex(function, offset + 1, + zend_ce_argument_count_error, "not passed"); goto error; } zval default_value; diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index b22280462a4bd..abd42f56a4e16 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -8936,9 +8936,10 @@ ZEND_VM_HOT_HANDLER(211, ZEND_TYPE_ASSERT, CONST, ANY, NUM) zend_arg_info *arginfo = &fbc->common.arg_info[argno - 1]; if (!zend_check_type(&arginfo->type, value, /* is_return_type */ false, /* is_internal */ true)) { - const char *param_name = get_function_arg_name(fbc, argno); zend_string *expected = zend_type_to_string(arginfo->type); - zend_type_error("%s(): Argument #%d%s%s%s must be of type %s, %s given", ZSTR_VAL(fbc->common.function_name), argno, param_name ? " ($" : "", param_name ? param_name : "", param_name ? ")" : "", ZSTR_VAL(expected), zend_zval_value_name(value)); + zend_argument_type_error_ex(fbc, argno, + "must be of type %s, %s given", + ZSTR_VAL(expected), zend_zval_value_name(value)); zend_string_release(expected); } } diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 40a68d673ffe3..5566c39e71467 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -6217,9 +6217,10 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_T zend_arg_info *arginfo = &fbc->common.arg_info[argno - 1]; if (!zend_check_type(&arginfo->type, value, /* is_return_type */ false, /* is_internal */ true)) { - const char *param_name = get_function_arg_name(fbc, argno); zend_string *expected = zend_type_to_string(arginfo->type); - zend_type_error("%s(): Argument #%d%s%s%s must be of type %s, %s given", ZSTR_VAL(fbc->common.function_name), argno, param_name ? " ($" : "", param_name ? param_name : "", param_name ? ")" : "", ZSTR_VAL(expected), zend_zval_value_name(value)); + zend_argument_type_error_ex(fbc, argno, + "must be of type %s, %s given", + ZSTR_VAL(expected), zend_zval_value_name(value)); zend_string_release(expected); } } @@ -58979,9 +58980,10 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_TYPE_A zend_arg_info *arginfo = &fbc->common.arg_info[argno - 1]; if (!zend_check_type(&arginfo->type, value, /* is_return_type */ false, /* is_internal */ true)) { - const char *param_name = get_function_arg_name(fbc, argno); zend_string *expected = zend_type_to_string(arginfo->type); - zend_type_error("%s(): Argument #%d%s%s%s must be of type %s, %s given", ZSTR_VAL(fbc->common.function_name), argno, param_name ? " ($" : "", param_name ? param_name : "", param_name ? ")" : "", ZSTR_VAL(expected), zend_zval_value_name(value)); + zend_argument_type_error_ex(fbc, argno, + "must be of type %s, %s given", + ZSTR_VAL(expected), zend_zval_value_name(value)); zend_string_release(expected); } } From effcc349495fd5f299faf6e515f1b0a2dd21c0ad Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Mon, 19 Jan 2026 13:20:02 +0100 Subject: [PATCH 31/59] WS --- Zend/zend_API.h | 1 - 1 file changed, 1 deletion(-) diff --git a/Zend/zend_API.h b/Zend/zend_API.h index 5990874e01e3f..535c65dc4d57e 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -1574,7 +1574,6 @@ ZEND_API ZEND_COLD void zend_argument_must_not_be_empty_error(uint32_t arg_num); ZEND_API ZEND_COLD void zend_class_redeclaration_error(int type, const zend_class_entry *old_ce); ZEND_API ZEND_COLD void zend_class_redeclaration_error_ex(int type, zend_string *new_name, const zend_class_entry *old_ce); - #define ZPP_ERROR_OK 0 #define ZPP_ERROR_FAILURE 1 #define ZPP_ERROR_WRONG_CALLBACK 2 From 7cb38bd7d6d1c9aea8be1603a336f0d6213c8bd7 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Tue, 20 Jan 2026 15:55:08 +0100 Subject: [PATCH 32/59] Improve tests --- Zend/tests/partial_application/magic_005.phpt | 1 - .../variation_debug_002.phpt | 2 +- .../variation_variadics_004.phpt | 74 +++++++------------ 3 files changed, 28 insertions(+), 49 deletions(-) diff --git a/Zend/tests/partial_application/magic_005.phpt b/Zend/tests/partial_application/magic_005.phpt index 5cdee061cc77f..f7b2ce7dd8e22 100644 --- a/Zend/tests/partial_application/magic_005.phpt +++ b/Zend/tests/partial_application/magic_005.phpt @@ -4,7 +4,6 @@ PFA magic null ptr deref in arginfo --EXPECTF-- object(Closure)#%d (5) { diff --git a/Zend/tests/partial_application/variation_variadics_004.phpt b/Zend/tests/partial_application/variation_variadics_004.phpt index 55ef6cd344f15..228404dc82723 100644 --- a/Zend/tests/partial_application/variation_variadics_004.phpt +++ b/Zend/tests/partial_application/variation_variadics_004.phpt @@ -3,63 +3,43 @@ PFA variation: variadics and optional args --FILE-- $day, "month" => $month, "year" => $year]; + printf("%04d-%02d-%02d\n", $year, $month, $day); } $foo = foo(year: 2006, ...); -var_dump($foo(2)); +echo "# Bound year, pass day:\n"; + +$foo(2); $foo = foo(month: 12, ...); $bar = $foo(year: 2016, ...); -var_dump($foo(2)); +echo "# Bound month, pass day:\n"; + +$foo(2); + +echo "# Bound month, bound year, pass day:\n"; + +$bar(2); + +echo "# Bound month, no args:\n"; -var_dump($bar(2)); +$foo(); -var_dump($foo()); +echo "# Bound month, bound year, no args:\n"; -var_dump($bar()); +$bar(); ?> ---EXPECTF-- -array(3) { - ["day"]=> - int(2) - ["month"]=> - int(1) - ["year"]=> - int(2006) -} -array(3) { - ["day"]=> - int(2) - ["month"]=> - int(12) - ["year"]=> - int(2005) -} -array(3) { - ["day"]=> - int(2) - ["month"]=> - int(12) - ["year"]=> - int(2016) -} -array(3) { - ["day"]=> - int(1) - ["month"]=> - int(12) - ["year"]=> - int(2005) -} -array(3) { - ["day"]=> - int(1) - ["month"]=> - int(12) - ["year"]=> - int(2016) -} +--EXPECT-- +# Bound year, pass day: +2006-01-02 +# Bound month, pass day: +2005-12-02 +# Bound month, bound year, pass day: +2016-12-02 +# Bound month, no args: +2005-12-01 +# Bound month, bound year, no args: +2016-12-01 From c0f6e9504dfb81963c2c06402f4bebaf7d213242 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Tue, 20 Jan 2026 15:55:16 +0100 Subject: [PATCH 33/59] Add tests --- .../pipe_optimization_014.phpt | 61 +++++++++++++++++++ .../pipe_optimization_015.phpt | 55 +++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 Zend/tests/partial_application/pipe_optimization_014.phpt create mode 100644 Zend/tests/partial_application/pipe_optimization_015.phpt diff --git a/Zend/tests/partial_application/pipe_optimization_014.phpt b/Zend/tests/partial_application/pipe_optimization_014.phpt new file mode 100644 index 0000000000000..82191154bb85d --- /dev/null +++ b/Zend/tests/partial_application/pipe_optimization_014.phpt @@ -0,0 +1,61 @@ +--TEST-- +PFA pipe optimization: PFA with unknown named parameter can be optimized +--EXTENSIONS-- +opcache +--INI-- +opcache.opt_debug_level=0x20000 +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_cache= +opcache.file_cache_only=0 +--FILE-- + 0) { + function foo($a, $b = null, $c = null) { + var_dump($a, $b, $c); + } +} + +try { + 2 |> foo(1, unknown: ?); +} catch (Error $e) { + echo $e::class, ": ", $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +$_main: + ; (lines=11, args=0, vars=0, tmps=2) + ; (after optimizer) + ; %s:1-12 +0000 INIT_FCALL 0 %d string("time") +0001 V1 = DO_ICALL +0002 T0 = IS_SMALLER int(0) V1 +0003 JMPZ T0 0005 +0004 DECLARE_FUNCTION string("foo") 0 +0005 INIT_FCALL_BY_NAME 1 string("foo") +0006 SEND_VAL_EX int(1) 1 +0007 SEND_VAL_EX int(2) string("unknown") +0008 CHECK_UNDEF_ARGS +0009 DO_FCALL_BY_NAME +0010 RETURN int(1) + +foo: + ; (lines=9, args=3, vars=3, tmps=0) + ; (after optimizer) + ; %s:4-6 +0000 CV0($a) = RECV 1 +0001 CV1($b) = RECV_INIT 2 null +0002 CV2($c) = RECV_INIT 3 null +0003 INIT_FCALL 3 %d string("var_dump") +0004 SEND_VAR CV0($a) 1 +0005 SEND_VAR CV1($b) 2 +0006 SEND_VAR CV2($c) 3 +0007 DO_ICALL +0008 RETURN null + +Fatal error: Uncaught Error: Unknown named parameter $unknown in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/Zend/tests/partial_application/pipe_optimization_015.phpt b/Zend/tests/partial_application/pipe_optimization_015.phpt new file mode 100644 index 0000000000000..66507bfa955a7 --- /dev/null +++ b/Zend/tests/partial_application/pipe_optimization_015.phpt @@ -0,0 +1,55 @@ +--TEST-- +PFA pipe optimization: PFA with skipped optional parameter can be optimized +--EXTENSIONS-- +opcache +--INI-- +opcache.opt_debug_level=0x20000 +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_cache= +opcache.file_cache_only=0 +--FILE-- + 0) { + function foo($a, $b = null, $c = null) { + var_dump($a, $b, $c); + } +} + +3 |> foo(1, c: ?); + +?> +--EXPECTF-- +$_main: + ; (lines=11, args=0, vars=0, tmps=2) + ; (after optimizer) + ; %s:1-12 +0000 INIT_FCALL 0 %d string("time") +0001 V1 = DO_ICALL +0002 T0 = IS_SMALLER int(0) V1 +0003 JMPZ T0 0005 +0004 DECLARE_FUNCTION string("foo") 0 +0005 INIT_FCALL_BY_NAME 1 string("foo") +0006 SEND_VAL_EX int(1) 1 +0007 SEND_VAL_EX int(3) string("c") +0008 CHECK_UNDEF_ARGS +0009 DO_FCALL_BY_NAME +0010 RETURN int(1) + +foo: + ; (lines=9, args=3, vars=3, tmps=0) + ; (after optimizer) + ; %s:4-6 +0000 CV0($a) = RECV 1 +0001 CV1($b) = RECV_INIT 2 null +0002 CV2($c) = RECV_INIT 3 null +0003 INIT_FCALL 3 %d string("var_dump") +0004 SEND_VAR CV0($a) 1 +0005 SEND_VAR CV1($b) 2 +0006 SEND_VAR CV2($c) 3 +0007 DO_ICALL +0008 RETURN null +int(1) +NULL +int(3) From 730970491c01513ff0f814ed7279566beff610f5 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 21 Jan 2026 11:40:38 +0100 Subject: [PATCH 34/59] Fix tests --- Zend/tests/partial_application/magic_005.phpt | 2 +- .../pipe_optimization_014.phpt | 27 ++++++++++++------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/Zend/tests/partial_application/magic_005.phpt b/Zend/tests/partial_application/magic_005.phpt index f7b2ce7dd8e22..e70ed90b17672 100644 --- a/Zend/tests/partial_application/magic_005.phpt +++ b/Zend/tests/partial_application/magic_005.phpt @@ -17,7 +17,7 @@ object(Closure)#%d (%d) { ["file"]=> string(%d) "%smagic_005.php" ["line"]=> - int(8) + int(7) ["this"]=> object(Foo)#%d (0) { } diff --git a/Zend/tests/partial_application/pipe_optimization_014.phpt b/Zend/tests/partial_application/pipe_optimization_014.phpt index 82191154bb85d..a1390bc551032 100644 --- a/Zend/tests/partial_application/pipe_optimization_014.phpt +++ b/Zend/tests/partial_application/pipe_optimization_014.phpt @@ -26,13 +26,13 @@ try { ?> --EXPECTF-- $_main: - ; (lines=11, args=0, vars=0, tmps=2) + ; (lines=20, args=0, vars=1, tmps=2) ; (after optimizer) - ; %s:1-12 + ; %s:1-16 0000 INIT_FCALL 0 %d string("time") -0001 V1 = DO_ICALL -0002 T0 = IS_SMALLER int(0) V1 -0003 JMPZ T0 0005 +0001 V2 = DO_ICALL +0002 T1 = IS_SMALLER int(0) V2 +0003 JMPZ T1 0005 0004 DECLARE_FUNCTION string("foo") 0 0005 INIT_FCALL_BY_NAME 1 string("foo") 0006 SEND_VAL_EX int(1) 1 @@ -40,6 +40,17 @@ $_main: 0008 CHECK_UNDEF_ARGS 0009 DO_FCALL_BY_NAME 0010 RETURN int(1) +0011 CV0($e) = CATCH string("Error") +0012 T1 = FETCH_CLASS_NAME CV0($e) +0013 ECHO T1 +0014 ECHO string(": ") +0015 INIT_METHOD_CALL 0 CV0($e) string("getMessage") +0016 V1 = DO_FCALL +0017 ECHO V1 +0018 ECHO string("\n") +0019 RETURN int(1) +EXCEPTION TABLE: + 0005, 0011, -, - foo: ; (lines=9, args=3, vars=3, tmps=0) @@ -54,8 +65,4 @@ foo: 0006 SEND_VAR CV2($c) 3 0007 DO_ICALL 0008 RETURN null - -Fatal error: Uncaught Error: Unknown named parameter $unknown in %s:%d -Stack trace: -#0 {main} - thrown in %s on line %d +Error: Unknown named parameter $unknown From 9dc177cb31d4cffa2e79aa9d5ccb249f7d2152c1 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 21 Jan 2026 11:40:49 +0100 Subject: [PATCH 35/59] Fix arg name --- Zend/tests/partial_application/references_004.phpt | 4 ++-- Zend/zend_partial.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Zend/tests/partial_application/references_004.phpt b/Zend/tests/partial_application/references_004.phpt index d3b85856b37e1..8ef3ff002ce7d 100644 --- a/Zend/tests/partial_application/references_004.phpt +++ b/Zend/tests/partial_application/references_004.phpt @@ -28,11 +28,11 @@ Closure [ static function {closure:%s:%d} ] { - Bound Variables [2] { Variable #0 [ $a ] - Variable #1 [ $args0 ] + Variable #1 [ $args1 ] } - Parameters [2] { - Parameter #0 [ &$args1 ] + Parameter #0 [ &$args0 ] Parameter #1 [ &...$args ] } } diff --git a/Zend/zend_partial.c b/Zend/zend_partial.c index 555c0763361d2..60ae63b46bfc7 100644 --- a/Zend/zend_partial.c +++ b/Zend/zend_partial.c @@ -169,7 +169,7 @@ static void zp_assign_names(zend_string **names, uint32_t num_names, } zend_string *orig_name = zp_get_param_name(function, function->common.num_args); zend_string *new_name; - for (uint32_t n = offset - function->common.num_args;; n++) { + for (uint32_t n = 0;; n++) { new_name = zend_strpprintf_unchecked(0, "%S%" PRIu32, orig_name, n); if (!zp_name_exists(names, num_names, new_name)) { break; From 78a8b17019beb16e85ad308a5b5ce4e5b2382bbb Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Sat, 14 Feb 2026 12:57:29 +0100 Subject: [PATCH 36/59] Add tests --- .../instance_polymorphism.phpt | 43 +++++++++++++++++++ .../static_polymorphism.phpt | 43 +++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 Zend/tests/partial_application/instance_polymorphism.phpt create mode 100644 Zend/tests/partial_application/static_polymorphism.phpt diff --git a/Zend/tests/partial_application/instance_polymorphism.phpt b/Zend/tests/partial_application/instance_polymorphism.phpt new file mode 100644 index 0000000000000..59fb5795d60fd --- /dev/null +++ b/Zend/tests/partial_application/instance_polymorphism.phpt @@ -0,0 +1,43 @@ +--TEST-- +PFA: instance polymorphism +--FILE-- +m(?); + } +} + +class C extends P { + public function m(string|array $b): void { + echo __METHOD__, PHP_EOL; + var_dump($b); + } +} + +for ($i = 0; $i < 2; $i++) { + (new P())->get()(a: 'a'); + (new C())->get()(b: []); +} + +?> +--EXPECT-- +P::m +string(1) "a" +C::m +array(0) { +} +P::m +string(1) "a" +C::m +array(0) { +} diff --git a/Zend/tests/partial_application/static_polymorphism.phpt b/Zend/tests/partial_application/static_polymorphism.phpt new file mode 100644 index 0000000000000..5f121c65c0e8d --- /dev/null +++ b/Zend/tests/partial_application/static_polymorphism.phpt @@ -0,0 +1,43 @@ +--TEST-- +PFA: static polymorphism +--FILE-- + +--EXPECT-- +P::m +string(1) "a" +C::m +array(0) { +} +P::m +string(1) "a" +C::m +array(0) { +} From 1b3cc5ab7792f86e3793125ddd5d859529f8cf44 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Sat, 14 Feb 2026 13:14:38 +0100 Subject: [PATCH 37/59] Fix tests after rebasing --- .../pipe_optimization_001.phpt | 4 +-- .../pipe_optimization_002.phpt | 4 +-- .../pipe_optimization_003.phpt | 4 +-- .../pipe_optimization_004.phpt | 12 +++---- .../pipe_optimization_005.phpt | 4 +-- .../pipe_optimization_006.phpt | 4 +-- .../pipe_optimization_007.phpt | 12 +++---- .../pipe_optimization_008.phpt | 16 +++++----- .../pipe_optimization_009.phpt | 31 +++++++++---------- .../pipe_optimization_010.phpt | 4 +-- .../pipe_optimization_011.phpt | 4 +-- .../pipe_optimization_012.phpt | 4 +-- .../pipe_optimization_013.phpt | 12 +++---- .../pipe_optimization_014.phpt | 8 ++--- .../pipe_optimization_015.phpt | 4 +-- 15 files changed, 63 insertions(+), 64 deletions(-) diff --git a/Zend/tests/partial_application/pipe_optimization_001.phpt b/Zend/tests/partial_application/pipe_optimization_001.phpt index 71bfee5dba44e..8cd5d71f5ef8d 100644 --- a/Zend/tests/partial_application/pipe_optimization_001.phpt +++ b/Zend/tests/partial_application/pipe_optimization_001.phpt @@ -26,8 +26,8 @@ $_main: ; (after optimizer) ; %spipe_optimization_001.php:1-12 0000 INIT_FCALL 0 %d string("time") -0001 V1 = DO_ICALL -0002 T0 = IS_SMALLER int(0) V1 +0001 T1 = DO_ICALL +0002 T0 = IS_SMALLER int(0) T1 0003 JMPZ T0 0005 0004 DECLARE_FUNCTION string("foo") 0 0005 INIT_FCALL_BY_NAME 1 string("foo") diff --git a/Zend/tests/partial_application/pipe_optimization_002.phpt b/Zend/tests/partial_application/pipe_optimization_002.phpt index ae5992e405a05..06026c074c0a0 100644 --- a/Zend/tests/partial_application/pipe_optimization_002.phpt +++ b/Zend/tests/partial_application/pipe_optimization_002.phpt @@ -26,8 +26,8 @@ $_main: ; (after optimizer) ; %spipe_optimization_002.php:1-12 0000 INIT_FCALL 0 %d string("time") -0001 V1 = DO_ICALL -0002 T0 = IS_SMALLER int(0) V1 +0001 T1 = DO_ICALL +0002 T0 = IS_SMALLER int(0) T1 0003 JMPZ T0 0005 0004 DECLARE_FUNCTION string("foo") 0 0005 INIT_FCALL_BY_NAME 2 string("foo") diff --git a/Zend/tests/partial_application/pipe_optimization_003.phpt b/Zend/tests/partial_application/pipe_optimization_003.phpt index f6c547d19dae0..10e44c0184027 100644 --- a/Zend/tests/partial_application/pipe_optimization_003.phpt +++ b/Zend/tests/partial_application/pipe_optimization_003.phpt @@ -26,8 +26,8 @@ $_main: ; (after optimizer) ; %spipe_optimization_003.php:1-12 0000 INIT_FCALL 0 %d string("time") -0001 V1 = DO_ICALL -0002 T0 = IS_SMALLER int(0) V1 +0001 T1 = DO_ICALL +0002 T0 = IS_SMALLER int(0) T1 0003 JMPZ T0 0005 0004 DECLARE_FUNCTION string("foo") 0 0005 INIT_FCALL_BY_NAME 2 string("foo") diff --git a/Zend/tests/partial_application/pipe_optimization_004.phpt b/Zend/tests/partial_application/pipe_optimization_004.phpt index edf0d9f775cd5..0bd4ed800c4a9 100644 --- a/Zend/tests/partial_application/pipe_optimization_004.phpt +++ b/Zend/tests/partial_application/pipe_optimization_004.phpt @@ -30,8 +30,8 @@ $_main: ; (after optimizer) ; %spipe_optimization_004.php:1-16 0000 INIT_FCALL 0 %d string("time") -0001 V2 = DO_ICALL -0002 T1 = IS_SMALLER int(0) V2 +0001 T2 = DO_ICALL +0002 T1 = IS_SMALLER int(0) T2 0003 JMPZ T1 0005 0004 DECLARE_FUNCTION string("foo") 0 0005 INIT_FCALL_BY_NAME 2 string("foo") @@ -47,8 +47,8 @@ $_main: 0015 ECHO T1 0016 ECHO string(": ") 0017 INIT_METHOD_CALL 0 CV0($e) string("getMessage") -0018 V1 = DO_FCALL -0019 ECHO V1 +0018 T1 = DO_FCALL +0019 ECHO T1 0020 ECHO string("\n") 0021 RETURN int(1) EXCEPTION TABLE: @@ -83,6 +83,6 @@ $_main: 0002 INIT_FCALL 2 %d string("foo") 0003 SEND_VAR CV0($a) 1 0004 SEND_VAR CV1($b) 2 -0005 V2 = DO_UCALL -0006 RETURN V2 +0005 T2 = DO_UCALL +0006 RETURN T2 ArgumentCountError: Too few arguments to function {closure:pfa:%s:%d}(), 1 passed in %s on line %d and exactly 2 expected diff --git a/Zend/tests/partial_application/pipe_optimization_005.phpt b/Zend/tests/partial_application/pipe_optimization_005.phpt index 5a0c6de43bbc2..dbc8deed47130 100644 --- a/Zend/tests/partial_application/pipe_optimization_005.phpt +++ b/Zend/tests/partial_application/pipe_optimization_005.phpt @@ -26,8 +26,8 @@ $_main: ; (after optimizer) ; %spipe_optimization_005.php:1-12 0000 INIT_FCALL 0 %d string("time") -0001 V1 = DO_ICALL -0002 T0 = IS_SMALLER int(0) V1 +0001 T1 = DO_ICALL +0002 T0 = IS_SMALLER int(0) T1 0003 JMPZ T0 0005 0004 DECLARE_FUNCTION string("foo") 0 0005 INIT_FCALL_BY_NAME 2 string("foo") diff --git a/Zend/tests/partial_application/pipe_optimization_006.phpt b/Zend/tests/partial_application/pipe_optimization_006.phpt index 2a83f2b83fe9a..3a33ae7ae0c8c 100644 --- a/Zend/tests/partial_application/pipe_optimization_006.phpt +++ b/Zend/tests/partial_application/pipe_optimization_006.phpt @@ -26,8 +26,8 @@ $_main: ; (after optimizer) ; %spipe_optimization_006.php:1-12 0000 INIT_FCALL 0 %d string("time") -0001 V1 = DO_ICALL -0002 T0 = IS_SMALLER int(0) V1 +0001 T1 = DO_ICALL +0002 T0 = IS_SMALLER int(0) T1 0003 JMPZ T0 0005 0004 DECLARE_FUNCTION string("foo") 0 0005 INIT_FCALL_BY_NAME 1 string("foo") diff --git a/Zend/tests/partial_application/pipe_optimization_007.phpt b/Zend/tests/partial_application/pipe_optimization_007.phpt index 77e0c7cbfa0ee..043ee75c777e6 100644 --- a/Zend/tests/partial_application/pipe_optimization_007.phpt +++ b/Zend/tests/partial_application/pipe_optimization_007.phpt @@ -30,8 +30,8 @@ $_main: ; (after optimizer) ; %spipe_optimization_007.php:1-16 0000 INIT_FCALL 0 %d string("time") -0001 V2 = DO_ICALL -0002 T1 = IS_SMALLER int(0) V2 +0001 T2 = DO_ICALL +0002 T1 = IS_SMALLER int(0) T2 0003 JMPZ T1 0005 0004 DECLARE_FUNCTION string("foo") 0 0005 INIT_FCALL_BY_NAME 0 string("foo") @@ -47,8 +47,8 @@ $_main: 0015 ECHO T1 0016 ECHO string(": ") 0017 INIT_METHOD_CALL 0 CV0($e) string("getMessage") -0018 V1 = DO_FCALL -0019 ECHO V1 +0018 T1 = DO_FCALL +0019 ECHO T1 0020 ECHO string("\n") 0021 RETURN int(1) EXCEPTION TABLE: @@ -83,6 +83,6 @@ $_main: 0002 INIT_FCALL 2 %d string("foo") 0003 SEND_VAR CV0($a) 1 0004 SEND_VAR CV1($b) 2 -0005 V2 = DO_UCALL -0006 RETURN V2 +0005 T2 = DO_UCALL +0006 RETURN T2 ArgumentCountError: Too few arguments to function {closure:pfa:%s:%d}(), 1 passed in %s on line %d and exactly 2 expected diff --git a/Zend/tests/partial_application/pipe_optimization_008.phpt b/Zend/tests/partial_application/pipe_optimization_008.phpt index 6293f782d4d44..55ed9c4898e4a 100644 --- a/Zend/tests/partial_application/pipe_optimization_008.phpt +++ b/Zend/tests/partial_application/pipe_optimization_008.phpt @@ -30,8 +30,8 @@ $_main: ; (after optimizer) ; %spipe_optimization_008.php:1-16 0000 INIT_FCALL 0 %d string("time") -0001 V2 = DO_ICALL -0002 T1 = IS_SMALLER int(0) V2 +0001 T2 = DO_ICALL +0002 T1 = IS_SMALLER int(0) T2 0003 JMPZ T1 0005 0004 DECLARE_FUNCTION string("foo") 0 0005 INIT_FCALL_BY_NAME 0 string("foo") @@ -43,8 +43,8 @@ $_main: 0011 RETURN int(1) 0012 CV0($e) = CATCH string("Throwable") 0013 INIT_METHOD_CALL 0 CV0($e) string("getMessage") -0014 V1 = DO_FCALL -0015 ECHO V1 +0014 T1 = DO_FCALL +0015 ECHO T1 0016 ECHO string("\n") 0017 RETURN int(1) EXCEPTION TABLE: @@ -85,15 +85,15 @@ LIVE RANGES: 0005 INIT_FCALL 2 %d string("foo") 0006 SEND_VAR CV1($a) 1 0007 SEND_VAR CV0($b) 2 -0008 V2 = DO_UCALL -0009 RETURN V2 +0008 T2 = DO_UCALL +0009 RETURN T2 0010 INIT_FCALL 2 %d string("foo") 0011 SEND_VAR CV1($a) 1 0012 SEND_VAR CV0($b) 2 0013 T2 = FUNC_GET_ARGS int(1) 0014 SEND_UNPACK T2 0015 CHECK_UNDEF_ARGS -0016 V2 = DO_UCALL -0017 RETURN V2 +0016 T2 = DO_UCALL +0017 RETURN T2 int(1) int(2) diff --git a/Zend/tests/partial_application/pipe_optimization_009.phpt b/Zend/tests/partial_application/pipe_optimization_009.phpt index 0902786616373..a01da7b757daf 100644 --- a/Zend/tests/partial_application/pipe_optimization_009.phpt +++ b/Zend/tests/partial_application/pipe_optimization_009.phpt @@ -34,32 +34,31 @@ lhs() |> foo(arg1(), ?, arg2()); ?> --EXPECTF-- $_main: - ; (lines=21, args=0, vars=0, tmps=2) + ; (lines=20, args=0, vars=0, tmps=2) ; (after optimizer) ; %spipe_optimization_009.php:1-24 0000 INIT_FCALL 0 %d string("time") -0001 V1 = DO_ICALL -0002 T0 = IS_SMALLER int(0) V1 +0001 T1 = DO_ICALL +0002 T0 = IS_SMALLER int(0) T1 0003 JMPZ T0 0008 0004 DECLARE_FUNCTION string("foo") 0 0005 DECLARE_FUNCTION string("lhs") 1 0006 DECLARE_FUNCTION string("arg1") 2 0007 DECLARE_FUNCTION string("arg2") 3 0008 INIT_FCALL_BY_NAME 0 string("lhs") -0009 V1 = DO_FCALL_BY_NAME -0010 T0 = QM_ASSIGN V1 -0011 INIT_FCALL_BY_NAME 3 string("foo") -0012 INIT_FCALL_BY_NAME 0 string("arg1") -0013 V1 = DO_FCALL_BY_NAME -0014 SEND_VAR_NO_REF_EX V1 1 -0015 SEND_VAL_EX T0 2 -0016 INIT_FCALL_BY_NAME 0 string("arg2") -0017 V0 = DO_FCALL_BY_NAME -0018 SEND_VAR_NO_REF_EX V0 3 -0019 DO_FCALL_BY_NAME -0020 RETURN int(1) +0009 T0 = DO_FCALL_BY_NAME +0010 INIT_FCALL_BY_NAME 3 string("foo") +0011 INIT_FCALL_BY_NAME 0 string("arg1") +0012 V1 = DO_FCALL_BY_NAME +0013 SEND_VAR_NO_REF_EX V1 1 +0014 SEND_VAL_EX T0 2 +0015 INIT_FCALL_BY_NAME 0 string("arg2") +0016 V0 = DO_FCALL_BY_NAME +0017 SEND_VAR_NO_REF_EX V0 3 +0018 DO_FCALL_BY_NAME +0019 RETURN int(1) LIVE RANGES: - 0: 0011 - 0015 (tmp/var) + 0: 0010 - 0014 (tmp/var) foo: ; (lines=9, args=3, vars=3, tmps=0) diff --git a/Zend/tests/partial_application/pipe_optimization_010.phpt b/Zend/tests/partial_application/pipe_optimization_010.phpt index 60202e72cfd1d..8b734d5209ba4 100644 --- a/Zend/tests/partial_application/pipe_optimization_010.phpt +++ b/Zend/tests/partial_application/pipe_optimization_010.phpt @@ -28,8 +28,8 @@ $_main: ; (after optimizer) ; %spipe_optimization_010.php:1-14 0000 INIT_FCALL 0 %d string("time") -0001 V2 = DO_ICALL -0002 T1 = IS_SMALLER int(0) V2 +0001 T2 = DO_ICALL +0002 T1 = IS_SMALLER int(0) T2 0003 JMPZ T1 0005 0004 DECLARE_FUNCTION string("foo") 0 0005 INIT_FCALL_BY_NAME 2 string("foo") diff --git a/Zend/tests/partial_application/pipe_optimization_011.phpt b/Zend/tests/partial_application/pipe_optimization_011.phpt index 1a39e4ff2f0d6..da67c519dcc41 100644 --- a/Zend/tests/partial_application/pipe_optimization_011.phpt +++ b/Zend/tests/partial_application/pipe_optimization_011.phpt @@ -43,8 +43,8 @@ $_main: ; (after optimizer) ; %spipe_optimization_011.php:1-29 0000 INIT_FCALL 0 %d string("time") -0001 V2 = DO_ICALL -0002 T1 = IS_SMALLER int(0) V2 +0001 T2 = DO_ICALL +0002 T1 = IS_SMALLER int(0) T2 0003 JMPZ T1 0008 0004 DECLARE_FUNCTION string("foo") 0 0005 DECLARE_FUNCTION string("lhs") 1 diff --git a/Zend/tests/partial_application/pipe_optimization_012.phpt b/Zend/tests/partial_application/pipe_optimization_012.phpt index fef48ba1cd3f9..67e2ad49025b2 100644 --- a/Zend/tests/partial_application/pipe_optimization_012.phpt +++ b/Zend/tests/partial_application/pipe_optimization_012.phpt @@ -26,8 +26,8 @@ $_main: ; (after optimizer) ; %s:1-12 0000 INIT_FCALL 0 %d string("time") -0001 V1 = DO_ICALL -0002 T0 = IS_SMALLER int(0) V1 +0001 T1 = DO_ICALL +0002 T0 = IS_SMALLER int(0) T1 0003 JMPZ T0 0005 0004 DECLARE_FUNCTION string("foo") 0 0005 INIT_FCALL_BY_NAME 1 string("foo") diff --git a/Zend/tests/partial_application/pipe_optimization_013.phpt b/Zend/tests/partial_application/pipe_optimization_013.phpt index 164562fdd0b40..e02145138def7 100644 --- a/Zend/tests/partial_application/pipe_optimization_013.phpt +++ b/Zend/tests/partial_application/pipe_optimization_013.phpt @@ -26,8 +26,8 @@ $_main: ; (after optimizer) ; %s:1-12 0000 INIT_FCALL 0 %d string("time") -0001 V1 = DO_ICALL -0002 T0 = IS_SMALLER int(0) V1 +0001 T1 = DO_ICALL +0002 T0 = IS_SMALLER int(0) T1 0003 JMPZ T0 0005 0004 DECLARE_FUNCTION string("foo") 0 0005 INIT_FCALL_BY_NAME 0 string("foo") @@ -73,15 +73,15 @@ LIVE RANGES: 0005 INIT_FCALL 2 %d string("foo") 0006 SEND_VAR CV0($a) 1 0007 SEND_VAR CV1($b) 2 -0008 V2 = DO_UCALL -0009 RETURN V2 +0008 T2 = DO_UCALL +0009 RETURN T2 0010 INIT_FCALL 2 %d string("foo") 0011 SEND_VAR CV0($a) 1 0012 SEND_VAR CV1($b) 2 0013 T2 = FUNC_GET_ARGS int(1) 0014 SEND_UNPACK T2 0015 CHECK_UNDEF_ARGS -0016 V2 = DO_UCALL -0017 RETURN V2 +0016 T2 = DO_UCALL +0017 RETURN T2 int(1) int(2) diff --git a/Zend/tests/partial_application/pipe_optimization_014.phpt b/Zend/tests/partial_application/pipe_optimization_014.phpt index a1390bc551032..dedda9e9ba6de 100644 --- a/Zend/tests/partial_application/pipe_optimization_014.phpt +++ b/Zend/tests/partial_application/pipe_optimization_014.phpt @@ -30,8 +30,8 @@ $_main: ; (after optimizer) ; %s:1-16 0000 INIT_FCALL 0 %d string("time") -0001 V2 = DO_ICALL -0002 T1 = IS_SMALLER int(0) V2 +0001 T2 = DO_ICALL +0002 T1 = IS_SMALLER int(0) T2 0003 JMPZ T1 0005 0004 DECLARE_FUNCTION string("foo") 0 0005 INIT_FCALL_BY_NAME 1 string("foo") @@ -45,8 +45,8 @@ $_main: 0013 ECHO T1 0014 ECHO string(": ") 0015 INIT_METHOD_CALL 0 CV0($e) string("getMessage") -0016 V1 = DO_FCALL -0017 ECHO V1 +0016 T1 = DO_FCALL +0017 ECHO T1 0018 ECHO string("\n") 0019 RETURN int(1) EXCEPTION TABLE: diff --git a/Zend/tests/partial_application/pipe_optimization_015.phpt b/Zend/tests/partial_application/pipe_optimization_015.phpt index 66507bfa955a7..2789beba7f52a 100644 --- a/Zend/tests/partial_application/pipe_optimization_015.phpt +++ b/Zend/tests/partial_application/pipe_optimization_015.phpt @@ -26,8 +26,8 @@ $_main: ; (after optimizer) ; %s:1-12 0000 INIT_FCALL 0 %d string("time") -0001 V1 = DO_ICALL -0002 T0 = IS_SMALLER int(0) V1 +0001 T1 = DO_ICALL +0002 T0 = IS_SMALLER int(0) T1 0003 JMPZ T0 0005 0004 DECLARE_FUNCTION string("foo") 0 0005 INIT_FCALL_BY_NAME 1 string("foo") From 4417b0acb752d721d5cda79e574da4398603760b Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Mon, 16 Feb 2026 17:59:13 +0100 Subject: [PATCH 38/59] Fix tests --- .../array_map_foreach_optimization_006.phpt | 8 ++++---- .../array_map_foreach_optimization_007.phpt | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ext/standard/tests/array/array_map_foreach_optimization_006.phpt b/ext/standard/tests/array/array_map_foreach_optimization_006.phpt index d354f1d5ec70c..e21e3e0a9a8b8 100644 --- a/ext/standard/tests/array/array_map_foreach_optimization_006.phpt +++ b/ext/standard/tests/array/array_map_foreach_optimization_006.phpt @@ -28,8 +28,8 @@ $_main: 0000 INIT_FCALL 2 %d string("range") 0001 SEND_VAL int(1) 1 0002 SEND_VAL int(10) 2 -0003 V2 = DO_ICALL -0004 ASSIGN CV0($array) V2 +0003 T2 = DO_ICALL +0004 ASSIGN CV0($array) T2 0005 TYPE_ASSERT 131079 string("array_map") CV0($array) 0006 T2 = INIT_ARRAY 0 (packed) NEXT 0007 V3 = FE_RESET_R CV0($array) 0015 @@ -37,8 +37,8 @@ $_main: 0009 INIT_FCALL 2 %d string("plusn") 0010 SEND_VAL T4 1 0011 SEND_VAL int(2) 2 -0012 V4 = DO_UCALL -0013 T2 = ADD_ARRAY_ELEMENT V4 T5 +0012 T4 = DO_UCALL +0013 T2 = ADD_ARRAY_ELEMENT T4 T5 0014 JMP 0008 0015 FE_FREE V3 0016 ASSIGN CV1($foo) T2 diff --git a/ext/standard/tests/array/array_map_foreach_optimization_007.phpt b/ext/standard/tests/array/array_map_foreach_optimization_007.phpt index d2dbf7f722529..67654b9e76403 100644 --- a/ext/standard/tests/array/array_map_foreach_optimization_007.phpt +++ b/ext/standard/tests/array/array_map_foreach_optimization_007.phpt @@ -28,16 +28,16 @@ $_main: 0000 INIT_FCALL 2 %d string("range") 0001 SEND_VAL int(1) 1 0002 SEND_VAL int(10) 2 -0003 V2 = DO_ICALL -0004 ASSIGN CV0($array) V2 +0003 T2 = DO_ICALL +0004 ASSIGN CV0($array) T2 0005 INIT_FCALL 2 %d string("array_map") 0006 INIT_FCALL 0 %d string("plusn") 0007 SEND_VAL int(2) string("n") 0008 T2 = CALLABLE_CONVERT_PARTIAL 2 0009 SEND_VAL T2 1 0010 SEND_VAR CV0($array) 2 -0011 V2 = DO_ICALL -0012 ASSIGN CV1($foo) V2 +0011 T2 = DO_ICALL +0012 ASSIGN CV1($foo) T2 0013 INIT_FCALL 1 %d string("var_dump") 0014 SEND_VAR CV1($foo) 1 0015 DO_ICALL @@ -75,16 +75,16 @@ LIVE RANGES: 0005 INIT_FCALL 2 %d string("plusn") 0006 SEND_VAR CV0($x) 1 0007 SEND_VAR CV1($n) 2 -0008 V2 = DO_UCALL -0009 RETURN V2 +0008 T2 = DO_UCALL +0009 RETURN T2 0010 INIT_FCALL 2 %d string("plusn") 0011 SEND_VAR CV0($x) 1 0012 SEND_VAR CV1($n) 2 0013 T2 = FUNC_GET_ARGS int(1) 0014 SEND_UNPACK T2 0015 CHECK_UNDEF_ARGS -0016 V2 = DO_UCALL -0017 RETURN V2 +0016 T2 = DO_UCALL +0017 RETURN T2 array(10) { [0]=> int(3) From b143f3b75776bbedc333b3f2d2f6718571bfef22 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Mon, 16 Feb 2026 17:57:08 +0100 Subject: [PATCH 39/59] Improve tests --- Zend/tests/partial_application/magic_001.phpt | 15 +++--- Zend/tests/partial_application/magic_002.phpt | 11 +++- .../named_placeholders.phpt | 30 +++++------ .../pipe_optimization_011.phpt | 52 +++++++------------ .../variation_parent_001.phpt | 3 +- 5 files changed, 53 insertions(+), 58 deletions(-) diff --git a/Zend/tests/partial_application/magic_001.phpt b/Zend/tests/partial_application/magic_001.phpt index e1d5248fdee41..6cd6044c62def 100644 --- a/Zend/tests/partial_application/magic_001.phpt +++ b/Zend/tests/partial_application/magic_001.phpt @@ -22,12 +22,7 @@ try { printf("%s: %s\n", $ex::class, $ex->getMessage()); } -try { - $bar(1, 2); -} catch (Error $ex) { - printf("%s: %s\n", $ex::class, $ex->getMessage()); -} - +$bar(1, 2); $bar(1); $bar = $foo->method(?, ...); @@ -35,6 +30,7 @@ $bar = $foo->method(?, ...); echo (string) new ReflectionFunction($bar); $bar(10); +$bar(10, 20); $bar = $foo->method(new Foo, ...); @@ -56,7 +52,7 @@ int(1) Foo::method int(1) Closure [ public method {closure:%s:%d} ] { - @@ %s 30 - 30 + @@ %s 25 - 25 - Parameters [2] { Parameter #0 [ mixed $arguments0 ] @@ -65,8 +61,11 @@ Closure [ public method {closure:%s:%d} ] { } Foo::method int(10) +Foo::method +int(10) +int(20) Closure [ public method {closure:%s:%d} ] { - @@ %s 36 - 36 + @@ %s 32 - 32 - Bound Variables [1] { Variable #0 [ $arguments0 ] diff --git a/Zend/tests/partial_application/magic_002.phpt b/Zend/tests/partial_application/magic_002.phpt index ff4e26623a9ed..d91ef7a7afe25 100644 --- a/Zend/tests/partial_application/magic_002.phpt +++ b/Zend/tests/partial_application/magic_002.phpt @@ -15,12 +15,14 @@ $bar = Foo::method(?); echo (string) new ReflectionFunction($bar); $bar(1); +$bar(1, 2); $bar = Foo::method(?, ...); echo (string) new ReflectionFunction($bar); $bar(10); +$bar(10, 20); $bar = Foo::method(new Foo,...); @@ -38,8 +40,10 @@ Closure [ static public method {closure:%s:%d} ] { } Foo::method int(1) +Foo::method +int(1) Closure [ static public method {closure:%s:%d} ] { - @@ %s 16 - 16 + @@ %s 17 - 17 - Parameters [2] { Parameter #0 [ mixed $arguments0 ] @@ -48,8 +52,11 @@ Closure [ static public method {closure:%s:%d} ] { } Foo::method int(10) +Foo::method +int(10) +int(20) Closure [ static public method {closure:%s:%d} ] { - @@ %s 22 - 22 + @@ %s 24 - 24 - Bound Variables [1] { Variable #0 [ $arguments0 ] diff --git a/Zend/tests/partial_application/named_placeholders.phpt b/Zend/tests/partial_application/named_placeholders.phpt index 6517a8946d36b..7d4a11f936247 100644 --- a/Zend/tests/partial_application/named_placeholders.phpt +++ b/Zend/tests/partial_application/named_placeholders.phpt @@ -11,43 +11,43 @@ function foo($a = 1, $b = 2, $c = 3) { var_dump($a, $b, $c); } -$foo = foo(b: ?); +$c = foo(b: ?); -echo (string) new ReflectionFunction($foo); +echo (string) new ReflectionFunction($c); -$foo(new B); +$c(new B); -$foo = $foo(?); +$c = $c(?); -echo (string) new ReflectionFunction($foo); +echo (string) new ReflectionFunction($c); -$foo(new B); +$c(new B); -$foo = foo(?, ?); -$foo = $foo(b: ?); +$c = foo(?, ?); +$c = $c(b: ?); -echo (string) new ReflectionFunction($foo); +echo (string) new ReflectionFunction($c); -$foo(new B); +$c(new B); function bar($a = 1, $b = 2, ...$c) { var_dump($a, $b, $c); } -$bar = bar(b: ?, ...); +$d = bar(b: ?, ...); -echo (string) new ReflectionFunction($bar); +echo (string) new ReflectionFunction($d); -$bar(new B, new A, new C); +$d(new B, new A, new C); try { - $bar = bar(?, a: ?); + $d = bar(?, a: ?); } catch (\Throwable $e) { echo $e->getMessage(), "\n"; } try { - $bar = bar(c: ?, ...); + $d = bar(c: ?, ...); } catch (\Throwable $e) { echo $e->getMessage(), "\n"; } diff --git a/Zend/tests/partial_application/pipe_optimization_011.phpt b/Zend/tests/partial_application/pipe_optimization_011.phpt index da67c519dcc41..6190703ef3784 100644 --- a/Zend/tests/partial_application/pipe_optimization_011.phpt +++ b/Zend/tests/partial_application/pipe_optimization_011.phpt @@ -15,10 +15,6 @@ if (time() > 0) { function foo($a, $b, $c) { var_dump($a, $b, $c); } - function lhs() { - echo __FUNCTION__, "\n"; - return 0; - } function arg1() { global $a; $a = 2; @@ -39,31 +35,30 @@ $a |> foo(arg1(), ?, arg2()); ?> --EXPECTF-- $_main: - ; (lines=20, args=0, vars=1, tmps=2) + ; (lines=19, args=0, vars=1, tmps=2) ; (after optimizer) - ; %spipe_optimization_011.php:1-29 + ; %spipe_optimization_011.php:1-25 0000 INIT_FCALL 0 %d string("time") 0001 T2 = DO_ICALL 0002 T1 = IS_SMALLER int(0) T2 -0003 JMPZ T1 0008 +0003 JMPZ T1 0007 0004 DECLARE_FUNCTION string("foo") 0 -0005 DECLARE_FUNCTION string("lhs") 1 -0006 DECLARE_FUNCTION string("arg1") 2 -0007 DECLARE_FUNCTION string("arg2") 3 -0008 ASSIGN CV0($a) int(0) -0009 T1 = QM_ASSIGN CV0($a) -0010 INIT_FCALL_BY_NAME 3 string("foo") -0011 INIT_FCALL_BY_NAME 0 string("arg1") -0012 V2 = DO_FCALL_BY_NAME -0013 SEND_VAR_NO_REF_EX V2 1 -0014 SEND_VAL_EX T1 2 -0015 INIT_FCALL_BY_NAME 0 string("arg2") -0016 V1 = DO_FCALL_BY_NAME -0017 SEND_VAR_NO_REF_EX V1 3 -0018 DO_FCALL_BY_NAME -0019 RETURN int(1) +0005 DECLARE_FUNCTION string("arg1") %d +0006 DECLARE_FUNCTION string("arg2") %d +0007 ASSIGN CV0($a) int(0) +0008 T1 = QM_ASSIGN CV0($a) +0009 INIT_FCALL_BY_NAME 3 string("foo") +0010 INIT_FCALL_BY_NAME 0 string("arg1") +0011 V2 = DO_FCALL_BY_NAME +0012 SEND_VAR_NO_REF_EX V2 1 +0013 SEND_VAL_EX T1 2 +0014 INIT_FCALL_BY_NAME 0 string("arg2") +0015 V1 = DO_FCALL_BY_NAME +0016 SEND_VAR_NO_REF_EX V1 3 +0017 DO_FCALL_BY_NAME +0018 RETURN int(1) LIVE RANGES: - 1: 0010 - 0014 (tmp/var) + 1: 0009 - 0013 (tmp/var) foo: ; (lines=9, args=3, vars=3, tmps=0) @@ -79,17 +74,10 @@ foo: 0007 DO_ICALL 0008 RETURN null -lhs: - ; (lines=2, args=0, vars=0, tmps=0) - ; (after optimizer) - ; %spipe_optimization_011.php:7-10 -0000 ECHO string("lhs\n") -0001 RETURN int(0) - arg1: ; (lines=4, args=0, vars=1, tmps=0) ; (after optimizer) - ; %spipe_optimization_011.php:11-16 + ; %spipe_optimization_011.php:7-12 0000 BIND_GLOBAL CV0($a) string("a") 0001 ASSIGN CV0($a) int(2) 0002 ECHO string("arg1\n") @@ -98,7 +86,7 @@ arg1: arg2: ; (lines=4, args=0, vars=1, tmps=0) ; (after optimizer) - ; %spipe_optimization_011.php:17-22 + ; %spipe_optimization_011.php:13-18 0000 BIND_GLOBAL CV0($a) string("a") 0001 ASSIGN CV0($a) int(3) 0002 ECHO string("arg2\n") diff --git a/Zend/tests/partial_application/variation_parent_001.phpt b/Zend/tests/partial_application/variation_parent_001.phpt index 6b7545daf2d02..98095f45009a2 100644 --- a/Zend/tests/partial_application/variation_parent_001.phpt +++ b/Zend/tests/partial_application/variation_parent_001.phpt @@ -14,7 +14,7 @@ $foo = new Foo(); $bar = $foo->method(10, ...); $baz = $bar(20, ...); -var_dump($baz, $baz()); +var_dump($baz, $c = $baz(), $c() === $foo); ?> --EXPECTF-- object(Closure)#%d (6) { @@ -73,3 +73,4 @@ object(Closure)#%d (4) { object(Foo)#%d (0) { } } +bool(true) From feff78cba961fcd5d10af3f6413c304b687a8735 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Mon, 16 Feb 2026 17:57:16 +0100 Subject: [PATCH 40/59] CS --- Zend/zend_language_scanner.l | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_language_scanner.l b/Zend/zend_language_scanner.l index 7e9dccff58f58..fd7d36d837922 100644 --- a/Zend/zend_language_scanner.l +++ b/Zend/zend_language_scanner.l @@ -600,7 +600,7 @@ static zend_op_array *zend_compile_ast_internal(int type) zend_oparray_context original_oparray_context; zend_op_array *original_active_op_array = CG(active_op_array); - zend_op_array *op_array = emalloc(sizeof(zend_op_array)); + zend_op_array *op_array = emalloc(sizeof(*op_array)); init_op_array(op_array, type, INITIAL_OP_ARRAY_SIZE); CG(active_op_array) = op_array; From 9d5f80e22a931474089ef71b1536bec9b1a9f83c Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 11 Mar 2026 12:30:58 +0100 Subject: [PATCH 41/59] Placeholder tmps= --- .../partial_application/pipe_optimization_001.phpt | 4 ++-- .../partial_application/pipe_optimization_002.phpt | 4 ++-- .../partial_application/pipe_optimization_003.phpt | 4 ++-- .../partial_application/pipe_optimization_004.phpt | 8 ++++---- .../partial_application/pipe_optimization_005.phpt | 4 ++-- .../partial_application/pipe_optimization_006.phpt | 4 ++-- .../partial_application/pipe_optimization_007.phpt | 8 ++++---- .../partial_application/pipe_optimization_008.phpt | 8 ++++---- .../partial_application/pipe_optimization_009.phpt | 10 +++++----- .../partial_application/pipe_optimization_010.phpt | 4 ++-- .../partial_application/pipe_optimization_011.phpt | 8 ++++---- .../partial_application/pipe_optimization_012.phpt | 4 ++-- .../partial_application/pipe_optimization_013.phpt | 8 ++++---- .../partial_application/pipe_optimization_014.phpt | 4 ++-- .../partial_application/pipe_optimization_015.phpt | 4 ++-- 15 files changed, 43 insertions(+), 43 deletions(-) diff --git a/Zend/tests/partial_application/pipe_optimization_001.phpt b/Zend/tests/partial_application/pipe_optimization_001.phpt index 8cd5d71f5ef8d..9610f37b76ac6 100644 --- a/Zend/tests/partial_application/pipe_optimization_001.phpt +++ b/Zend/tests/partial_application/pipe_optimization_001.phpt @@ -22,7 +22,7 @@ if (time() > 0) { ?> --EXPECTF-- $_main: - ; (lines=9, args=0, vars=0, tmps=2) + ; (lines=9, args=0, vars=0, tmps=%d) ; (after optimizer) ; %spipe_optimization_001.php:1-12 0000 INIT_FCALL 0 %d string("time") @@ -36,7 +36,7 @@ $_main: 0008 RETURN int(1) foo: - ; (lines=5, args=1, vars=1, tmps=0) + ; (lines=5, args=1, vars=1, tmps=%d) ; (after optimizer) ; %spipe_optimization_001.php:4-6 0000 CV0($a) = RECV 1 diff --git a/Zend/tests/partial_application/pipe_optimization_002.phpt b/Zend/tests/partial_application/pipe_optimization_002.phpt index 06026c074c0a0..729e70d30e6b7 100644 --- a/Zend/tests/partial_application/pipe_optimization_002.phpt +++ b/Zend/tests/partial_application/pipe_optimization_002.phpt @@ -22,7 +22,7 @@ if (time() > 0) { ?> --EXPECTF-- $_main: - ; (lines=10, args=0, vars=0, tmps=2) + ; (lines=10, args=0, vars=0, tmps=%d) ; (after optimizer) ; %spipe_optimization_002.php:1-12 0000 INIT_FCALL 0 %d string("time") @@ -37,7 +37,7 @@ $_main: 0009 RETURN int(1) foo: - ; (lines=7, args=2, vars=2, tmps=0) + ; (lines=7, args=2, vars=2, tmps=%d) ; (after optimizer) ; %spipe_optimization_002.php:4-6 0000 CV0($a) = RECV 1 diff --git a/Zend/tests/partial_application/pipe_optimization_003.phpt b/Zend/tests/partial_application/pipe_optimization_003.phpt index 10e44c0184027..da112f8f3cea3 100644 --- a/Zend/tests/partial_application/pipe_optimization_003.phpt +++ b/Zend/tests/partial_application/pipe_optimization_003.phpt @@ -22,7 +22,7 @@ if (time() > 0) { ?> --EXPECTF-- $_main: - ; (lines=10, args=0, vars=0, tmps=2) + ; (lines=10, args=0, vars=0, tmps=%d) ; (after optimizer) ; %spipe_optimization_003.php:1-12 0000 INIT_FCALL 0 %d string("time") @@ -37,7 +37,7 @@ $_main: 0009 RETURN int(1) foo: - ; (lines=7, args=2, vars=2, tmps=0) + ; (lines=7, args=2, vars=2, tmps=%d) ; (after optimizer) ; %spipe_optimization_003.php:4-6 0000 CV0($a) = RECV 1 diff --git a/Zend/tests/partial_application/pipe_optimization_004.phpt b/Zend/tests/partial_application/pipe_optimization_004.phpt index 0bd4ed800c4a9..36cb2733ed927 100644 --- a/Zend/tests/partial_application/pipe_optimization_004.phpt +++ b/Zend/tests/partial_application/pipe_optimization_004.phpt @@ -26,7 +26,7 @@ try { ?> --EXPECTF-- $_main: - ; (lines=22, args=0, vars=1, tmps=2) + ; (lines=22, args=0, vars=1, tmps=%d) ; (after optimizer) ; %spipe_optimization_004.php:1-16 0000 INIT_FCALL 0 %d string("time") @@ -55,7 +55,7 @@ EXCEPTION TABLE: 0005, 0013, -, - foo: - ; (lines=7, args=2, vars=2, tmps=0) + ; (lines=7, args=2, vars=2, tmps=%d) ; (after optimizer) ; %spipe_optimization_004.php:4-6 0000 CV0($a) = RECV 1 @@ -67,7 +67,7 @@ foo: 0006 RETURN null $_main: - ; (lines=3, args=0, vars=0, tmps=1) + ; (lines=3, args=0, vars=0, tmps=%d) ; (after optimizer) ; %s:1-10 0000 T0 = DECLARE_LAMBDA_FUNCTION 0 @@ -75,7 +75,7 @@ $_main: 0002 RETURN int(1) {closure:%s:%d}: - ; (lines=7, args=2, vars=2, tmps=1) + ; (lines=7, args=2, vars=2, tmps=%d) ; (after optimizer) ; %s:10-10 0000 CV0($a) = RECV 1 diff --git a/Zend/tests/partial_application/pipe_optimization_005.phpt b/Zend/tests/partial_application/pipe_optimization_005.phpt index dbc8deed47130..3ccfec8366093 100644 --- a/Zend/tests/partial_application/pipe_optimization_005.phpt +++ b/Zend/tests/partial_application/pipe_optimization_005.phpt @@ -22,7 +22,7 @@ if (time() > 0) { ?> --EXPECTF-- $_main: - ; (lines=10, args=0, vars=0, tmps=2) + ; (lines=10, args=0, vars=0, tmps=%d) ; (after optimizer) ; %spipe_optimization_005.php:1-12 0000 INIT_FCALL 0 %d string("time") @@ -37,7 +37,7 @@ $_main: 0009 RETURN int(1) foo: - ; (lines=7, args=2, vars=2, tmps=0) + ; (lines=7, args=2, vars=2, tmps=%d) ; (after optimizer) ; %spipe_optimization_005.php:4-6 0000 CV0($a) = RECV 1 diff --git a/Zend/tests/partial_application/pipe_optimization_006.phpt b/Zend/tests/partial_application/pipe_optimization_006.phpt index 3a33ae7ae0c8c..6e06477427a36 100644 --- a/Zend/tests/partial_application/pipe_optimization_006.phpt +++ b/Zend/tests/partial_application/pipe_optimization_006.phpt @@ -22,7 +22,7 @@ if (time() > 0) { ?> --EXPECTF-- $_main: - ; (lines=11, args=0, vars=0, tmps=2) + ; (lines=11, args=0, vars=0, tmps=%d) ; (after optimizer) ; %spipe_optimization_006.php:1-12 0000 INIT_FCALL 0 %d string("time") @@ -38,7 +38,7 @@ $_main: 0010 RETURN int(1) foo: - ; (lines=9, args=3, vars=3, tmps=0) + ; (lines=9, args=3, vars=3, tmps=%d) ; (after optimizer) ; %spipe_optimization_006.php:4-6 0000 CV0($a) = RECV 1 diff --git a/Zend/tests/partial_application/pipe_optimization_007.phpt b/Zend/tests/partial_application/pipe_optimization_007.phpt index 043ee75c777e6..09c3c765d237b 100644 --- a/Zend/tests/partial_application/pipe_optimization_007.phpt +++ b/Zend/tests/partial_application/pipe_optimization_007.phpt @@ -26,7 +26,7 @@ try { ?> --EXPECTF-- $_main: - ; (lines=22, args=0, vars=1, tmps=2) + ; (lines=22, args=0, vars=1, tmps=%d) ; (after optimizer) ; %spipe_optimization_007.php:1-16 0000 INIT_FCALL 0 %d string("time") @@ -55,7 +55,7 @@ EXCEPTION TABLE: 0005, 0013, -, - foo: - ; (lines=7, args=2, vars=2, tmps=0) + ; (lines=7, args=2, vars=2, tmps=%d) ; (after optimizer) ; %spipe_optimization_007.php:4-6 0000 CV0($a) = RECV 1 @@ -67,7 +67,7 @@ foo: 0006 RETURN null $_main: - ; (lines=3, args=0, vars=0, tmps=1) + ; (lines=3, args=0, vars=0, tmps=%d) ; (after optimizer) ; %s:1-10 0000 T0 = DECLARE_LAMBDA_FUNCTION 0 @@ -75,7 +75,7 @@ $_main: 0002 RETURN int(1) {closure:%s:%d}: - ; (lines=7, args=2, vars=2, tmps=1) + ; (lines=7, args=2, vars=2, tmps=%d) ; (after optimizer) ; %s:10-10 0000 CV0($a) = RECV 1 diff --git a/Zend/tests/partial_application/pipe_optimization_008.phpt b/Zend/tests/partial_application/pipe_optimization_008.phpt index 55ed9c4898e4a..070074632c771 100644 --- a/Zend/tests/partial_application/pipe_optimization_008.phpt +++ b/Zend/tests/partial_application/pipe_optimization_008.phpt @@ -26,7 +26,7 @@ try { ?> --EXPECTF-- $_main: - ; (lines=18, args=0, vars=1, tmps=2) + ; (lines=18, args=0, vars=1, tmps=%d) ; (after optimizer) ; %spipe_optimization_008.php:1-16 0000 INIT_FCALL 0 %d string("time") @@ -51,7 +51,7 @@ EXCEPTION TABLE: 0005, 0012, -, - foo: - ; (lines=7, args=2, vars=2, tmps=0) + ; (lines=7, args=2, vars=2, tmps=%d) ; (after optimizer) ; %spipe_optimization_008.php:4-6 0000 CV0($a) = RECV 1 @@ -63,7 +63,7 @@ foo: 0006 RETURN null $_main: - ; (lines=4, args=0, vars=1, tmps=1) + ; (lines=4, args=0, vars=1, tmps=%d) ; (after optimizer) ; %s:1-10 0000 T1 = DECLARE_LAMBDA_FUNCTION 0 @@ -74,7 +74,7 @@ LIVE RANGES: 1: 0001 - 0002 (tmp/var) {closure:%s:%d}: - ; (lines=18, args=1, vars=2, tmps=2) + ; (lines=18, args=1, vars=2, tmps=%d) ; (after optimizer) ; %s:10-10 0000 CV0($b) = RECV 1 diff --git a/Zend/tests/partial_application/pipe_optimization_009.phpt b/Zend/tests/partial_application/pipe_optimization_009.phpt index a01da7b757daf..118482a3860b3 100644 --- a/Zend/tests/partial_application/pipe_optimization_009.phpt +++ b/Zend/tests/partial_application/pipe_optimization_009.phpt @@ -34,7 +34,7 @@ lhs() |> foo(arg1(), ?, arg2()); ?> --EXPECTF-- $_main: - ; (lines=20, args=0, vars=0, tmps=2) + ; (lines=20, args=0, vars=0, tmps=%d) ; (after optimizer) ; %spipe_optimization_009.php:1-24 0000 INIT_FCALL 0 %d string("time") @@ -61,7 +61,7 @@ LIVE RANGES: 0: 0010 - 0014 (tmp/var) foo: - ; (lines=9, args=3, vars=3, tmps=0) + ; (lines=9, args=3, vars=3, tmps=%d) ; (after optimizer) ; %spipe_optimization_009.php:4-6 0000 CV0($a) = RECV 1 @@ -75,21 +75,21 @@ foo: 0008 RETURN null lhs: - ; (lines=2, args=0, vars=0, tmps=0) + ; (lines=2, args=0, vars=0, tmps=%d) ; (after optimizer) ; %spipe_optimization_009.php:7-10 0000 ECHO string("lhs\n") 0001 RETURN int(0) arg1: - ; (lines=2, args=0, vars=0, tmps=0) + ; (lines=2, args=0, vars=0, tmps=%d) ; (after optimizer) ; %spipe_optimization_009.php:11-14 0000 ECHO string("arg1\n") 0001 RETURN int(1) arg2: - ; (lines=2, args=0, vars=0, tmps=0) + ; (lines=2, args=0, vars=0, tmps=%d) ; (after optimizer) ; %spipe_optimization_009.php:15-18 0000 ECHO string("arg2\n") diff --git a/Zend/tests/partial_application/pipe_optimization_010.phpt b/Zend/tests/partial_application/pipe_optimization_010.phpt index 8b734d5209ba4..f77c2a97732e7 100644 --- a/Zend/tests/partial_application/pipe_optimization_010.phpt +++ b/Zend/tests/partial_application/pipe_optimization_010.phpt @@ -24,7 +24,7 @@ var_dump($a); ?> --EXPECTF-- $_main: - ; (lines=13, args=0, vars=1, tmps=2) + ; (lines=13, args=0, vars=1, tmps=%d) ; (after optimizer) ; %spipe_optimization_010.php:1-14 0000 INIT_FCALL 0 %d string("time") @@ -42,7 +42,7 @@ $_main: 0012 RETURN int(1) foo: - ; (lines=8, args=2, vars=2, tmps=0) + ; (lines=8, args=2, vars=2, tmps=%d) ; (after optimizer) ; %spipe_optimization_010.php:4-7 0000 CV0($a) = RECV 1 diff --git a/Zend/tests/partial_application/pipe_optimization_011.phpt b/Zend/tests/partial_application/pipe_optimization_011.phpt index 6190703ef3784..cd1c986d99e7b 100644 --- a/Zend/tests/partial_application/pipe_optimization_011.phpt +++ b/Zend/tests/partial_application/pipe_optimization_011.phpt @@ -35,7 +35,7 @@ $a |> foo(arg1(), ?, arg2()); ?> --EXPECTF-- $_main: - ; (lines=19, args=0, vars=1, tmps=2) + ; (lines=19, args=0, vars=1, tmps=%d) ; (after optimizer) ; %spipe_optimization_011.php:1-25 0000 INIT_FCALL 0 %d string("time") @@ -61,7 +61,7 @@ LIVE RANGES: 1: 0009 - 0013 (tmp/var) foo: - ; (lines=9, args=3, vars=3, tmps=0) + ; (lines=9, args=3, vars=3, tmps=%d) ; (after optimizer) ; %spipe_optimization_011.php:4-6 0000 CV0($a) = RECV 1 @@ -75,7 +75,7 @@ foo: 0008 RETURN null arg1: - ; (lines=4, args=0, vars=1, tmps=0) + ; (lines=4, args=0, vars=1, tmps=%d) ; (after optimizer) ; %spipe_optimization_011.php:7-12 0000 BIND_GLOBAL CV0($a) string("a") @@ -84,7 +84,7 @@ arg1: 0003 RETURN int(1) arg2: - ; (lines=4, args=0, vars=1, tmps=0) + ; (lines=4, args=0, vars=1, tmps=%d) ; (after optimizer) ; %spipe_optimization_011.php:13-18 0000 BIND_GLOBAL CV0($a) string("a") diff --git a/Zend/tests/partial_application/pipe_optimization_012.phpt b/Zend/tests/partial_application/pipe_optimization_012.phpt index 67e2ad49025b2..da172874adceb 100644 --- a/Zend/tests/partial_application/pipe_optimization_012.phpt +++ b/Zend/tests/partial_application/pipe_optimization_012.phpt @@ -22,7 +22,7 @@ if (time() > 0) { ?> --EXPECTF-- $_main: - ; (lines=11, args=0, vars=0, tmps=2) + ; (lines=11, args=0, vars=0, tmps=%d) ; (after optimizer) ; %s:1-12 0000 INIT_FCALL 0 %d string("time") @@ -38,7 +38,7 @@ $_main: 0010 RETURN int(1) foo: - ; (lines=7, args=2, vars=2, tmps=0) + ; (lines=7, args=2, vars=2, tmps=%d) ; (after optimizer) ; %s:4-6 0000 CV0($a) = RECV 1 diff --git a/Zend/tests/partial_application/pipe_optimization_013.phpt b/Zend/tests/partial_application/pipe_optimization_013.phpt index e02145138def7..7d1a48b2f2edb 100644 --- a/Zend/tests/partial_application/pipe_optimization_013.phpt +++ b/Zend/tests/partial_application/pipe_optimization_013.phpt @@ -22,7 +22,7 @@ if (time() > 0) { ?> --EXPECTF-- $_main: - ; (lines=12, args=0, vars=0, tmps=2) + ; (lines=12, args=0, vars=0, tmps=%d) ; (after optimizer) ; %s:1-12 0000 INIT_FCALL 0 %d string("time") @@ -39,7 +39,7 @@ $_main: 0011 RETURN int(1) foo: - ; (lines=7, args=2, vars=2, tmps=0) + ; (lines=7, args=2, vars=2, tmps=%d) ; (after optimizer) ; %s:4-6 0000 CV0($a) = RECV 1 @@ -51,7 +51,7 @@ foo: 0006 RETURN null $_main: - ; (lines=4, args=0, vars=1, tmps=1) + ; (lines=4, args=0, vars=1, tmps=%d) ; (after optimizer) ; %s:1-9 0000 T1 = DECLARE_LAMBDA_FUNCTION 0 @@ -62,7 +62,7 @@ LIVE RANGES: 1: 0001 - 0002 (tmp/var) {closure:pfa:%s:9}: - ; (lines=18, args=1, vars=2, tmps=2) + ; (lines=18, args=1, vars=2, tmps=%d) ; (after optimizer) ; %s:9-9 0000 CV0($a) = RECV 1 diff --git a/Zend/tests/partial_application/pipe_optimization_014.phpt b/Zend/tests/partial_application/pipe_optimization_014.phpt index dedda9e9ba6de..6e66d7e0e99da 100644 --- a/Zend/tests/partial_application/pipe_optimization_014.phpt +++ b/Zend/tests/partial_application/pipe_optimization_014.phpt @@ -26,7 +26,7 @@ try { ?> --EXPECTF-- $_main: - ; (lines=20, args=0, vars=1, tmps=2) + ; (lines=20, args=0, vars=1, tmps=%d) ; (after optimizer) ; %s:1-16 0000 INIT_FCALL 0 %d string("time") @@ -53,7 +53,7 @@ EXCEPTION TABLE: 0005, 0011, -, - foo: - ; (lines=9, args=3, vars=3, tmps=0) + ; (lines=9, args=3, vars=3, tmps=%d) ; (after optimizer) ; %s:4-6 0000 CV0($a) = RECV 1 diff --git a/Zend/tests/partial_application/pipe_optimization_015.phpt b/Zend/tests/partial_application/pipe_optimization_015.phpt index 2789beba7f52a..ce293c7a300c5 100644 --- a/Zend/tests/partial_application/pipe_optimization_015.phpt +++ b/Zend/tests/partial_application/pipe_optimization_015.phpt @@ -22,7 +22,7 @@ if (time() > 0) { ?> --EXPECTF-- $_main: - ; (lines=11, args=0, vars=0, tmps=2) + ; (lines=11, args=0, vars=0, tmps=%d) ; (after optimizer) ; %s:1-12 0000 INIT_FCALL 0 %d string("time") @@ -38,7 +38,7 @@ $_main: 0010 RETURN int(1) foo: - ; (lines=9, args=3, vars=3, tmps=0) + ; (lines=9, args=3, vars=3, tmps=%d) ; (after optimizer) ; %s:4-6 0000 CV0($a) = RECV 1 From 2025267b9bbb8032581b598a4718f3960b2c4971 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 11 Mar 2026 12:50:59 +0100 Subject: [PATCH 42/59] Improve tests --- .../tests/partial_application/errors_003.phpt | 7 +--- .../tests/partial_application/errors_006.phpt | 22 +++++++++++ .../partial_application/rebinding_002.phpt | 4 +- .../partial_application/rebinding_003.phpt | 6 +-- .../partial_application/reflection_001.phpt | 2 +- .../relative_return_types.phpt | 37 +++++++++++++------ 6 files changed, 54 insertions(+), 24 deletions(-) diff --git a/Zend/tests/partial_application/errors_003.phpt b/Zend/tests/partial_application/errors_003.phpt index bc1c16e64f9b8..85ecf398fa1bd 100644 --- a/Zend/tests/partial_application/errors_003.phpt +++ b/Zend/tests/partial_application/errors_003.phpt @@ -62,11 +62,8 @@ try { printf("%s: %s\n", $ex::class, $ex->getMessage()); } -try { - $usleep(1, 2); -} catch (Error $ex) { - printf("%s: %s\n", $ex::class, $ex->getMessage()); -} +$usleep(1, 2); + ?> --EXPECTF-- ArgumentCountError: Too few arguments to function {closure:%s:%d}(), 0 passed in %s on line %d and exactly 1 expected diff --git a/Zend/tests/partial_application/errors_006.phpt b/Zend/tests/partial_application/errors_006.phpt index aec2fc5dc0734..9534bb40ee54b 100644 --- a/Zend/tests/partial_application/errors_006.phpt +++ b/Zend/tests/partial_application/errors_006.phpt @@ -3,6 +3,16 @@ PFA errors: Some internal function parameters have UNKNOWN default value --FILE-- array_keys($array, ???, true) $f = array_keys(?, strict: true); @@ -19,5 +29,17 @@ try { ?> --EXPECT-- +array(1) { + [0]=> + int(1) +} +array(0) { +} +array(1) { + [0]=> + int(1) +} +array(0) { +} ArgumentCountError: array_keys(): Argument #2 ($filter_value) must be passed explicitly, because the default value is not known ArgumentCountError: array_keys(): Argument #2 ($filter_value) must be passed explicitly, because the default value is not known diff --git a/Zend/tests/partial_application/rebinding_002.phpt b/Zend/tests/partial_application/rebinding_002.phpt index fca6db08500f2..25e9381206762 100644 --- a/Zend/tests/partial_application/rebinding_002.phpt +++ b/Zend/tests/partial_application/rebinding_002.phpt @@ -25,7 +25,7 @@ try { echo $e->getMessage(), "\n"; } -echo "# Function cannot be refound to a different scope:\n"; +echo "# Function cannot be rebound to a different scope:\n"; try { $g->bindTo($c, SubClass::class)(1); } catch (Error $e) { @@ -39,7 +39,7 @@ string(1) "C" Warning: Cannot rebind scope of closure created from method, this will be an error in PHP 9 in %s on line %d Value of type null is not callable -# Function cannot be refound to a different scope: +# Function cannot be rebound to a different scope: Warning: Cannot bind an instance to a static closure, this will be an error in PHP 9 in %s on line %d Value of type null is not callable diff --git a/Zend/tests/partial_application/rebinding_003.phpt b/Zend/tests/partial_application/rebinding_003.phpt index 7b8a29231c32c..e0b6eaab4ac41 100644 --- a/Zend/tests/partial_application/rebinding_003.phpt +++ b/Zend/tests/partial_application/rebinding_003.phpt @@ -37,11 +37,7 @@ try { } echo "# Can unbind \$this on static method:\n"; -try { - $g->bindTo(null)($c); -} catch (Error $e) { - echo $e->getMessage(), "\n"; -} +$g->bindTo(null)($c); ?> --EXPECTF-- diff --git a/Zend/tests/partial_application/reflection_001.phpt b/Zend/tests/partial_application/reflection_001.phpt index 4ee997f562db1..f101bc3348e0d 100644 --- a/Zend/tests/partial_application/reflection_001.phpt +++ b/Zend/tests/partial_application/reflection_001.phpt @@ -1,5 +1,5 @@ --TEST-- -PFA reflection: required parameters +PFA reflection: optional parameters --FILE-- getSelf(?); echo (string) new ReflectionFunction($self), "\n"; @@ -35,6 +37,8 @@ try { echo $e->getMessage(), "\n"; } +echo "# C::getStatic():\n"; + $static = $c->getStatic(?); echo (string) new ReflectionFunction($static), "\n"; @@ -49,24 +53,32 @@ try { $d = new D; +echo "# D::getSelf():\n"; + $self = $d->getSelf(?); echo (string) new ReflectionFunction($self), "\n"; var_dump($self($d)); -var_dump($self(new D)); +var_dump($self(new C)); try { $self(new stdClass); } catch (Error $e) { - echo $e->getMessage(), "\n"; + echo $e::class, ": ", $e->getMessage(), "\n"; } +echo "# D::getStatic():\n"; + $static = $d->getStatic(?); echo (string) new ReflectionFunction($static), "\n"; var_dump($static($d)); -var_dump($static(new D)); +try { + var_dump($static(new C)); +} catch (Error $e) { + echo $e::class, ": ", $e->getMessage(), "\n"; +} try { $static(new stdClass); } catch (Error $e) { @@ -75,8 +87,9 @@ try { ?> --EXPECTF-- +# C::getSelf(): Closure [ public method {closure:%s:%d} ] { - @@ %s.php 23 - 23 + @@ %s 25 - 25 - Parameters [1] { Parameter #0 [ object $o ] @@ -89,8 +102,9 @@ object(C)#%d (0) { object(D)#%d (0) { } C::getSelf(): Return value must be of type C, stdClass returned +# C::getStatic(): Closure [ public method {closure:%s:%d} ] { - @@ %s.php 35 - 35 + @@ %s 39 - 39 - Parameters [1] { Parameter #0 [ object $o ] @@ -103,8 +117,9 @@ object(C)#%d (0) { object(D)#%d (0) { } C::getStatic(): Return value must be of type C, stdClass returned +# D::getSelf(): Closure [ public method {closure:%s:%d} ] { - @@ %s.php 49 - 49 + @@ %s 55 - 55 - Parameters [1] { Parameter #0 [ object $o ] @@ -114,11 +129,12 @@ Closure [ public method {closure:%s:%d} ] { object(D)#%d (0) { } -object(D)#%d (0) { +object(C)#%d (0) { } -C::getSelf(): Return value must be of type C, stdClass returned +TypeError: C::getSelf(): Return value must be of type C, stdClass returned +# D::getStatic(): Closure [ public method {closure:%s:%d} ] { - @@ %s.php 61 - 61 + @@ %s 69 - 69 - Parameters [1] { Parameter #0 [ object $o ] @@ -128,6 +144,5 @@ Closure [ public method {closure:%s:%d} ] { object(D)#%d (0) { } -object(D)#%d (0) { -} +TypeError: C::getStatic(): Return value must be of type D, C returned C::getStatic(): Return value must be of type D, stdClass returned From f791a4a7cdcd53e6c74f418dcd1381029c9cbf49 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 11 Mar 2026 12:52:20 +0100 Subject: [PATCH 43/59] ZEND_SEND_PLACEHOLDER metadata --- Zend/tests/partial_application/pipe_optimization_004.phpt | 4 ++-- Zend/zend_vm_def.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Zend/tests/partial_application/pipe_optimization_004.phpt b/Zend/tests/partial_application/pipe_optimization_004.phpt index 36cb2733ed927..addee498d8102 100644 --- a/Zend/tests/partial_application/pipe_optimization_004.phpt +++ b/Zend/tests/partial_application/pipe_optimization_004.phpt @@ -35,8 +35,8 @@ $_main: 0003 JMPZ T1 0005 0004 DECLARE_FUNCTION string("foo") 0 0005 INIT_FCALL_BY_NAME 2 string("foo") -0006 SEND_PLACEHOLDER -0007 SEND_PLACEHOLDER +0006 SEND_PLACEHOLDER 1 +0007 SEND_PLACEHOLDER 2 0008 T1 = CALLABLE_CONVERT_PARTIAL %d 0009 INIT_DYNAMIC_CALL 1 T1 0010 SEND_VAL_EX int(2) 1 diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index abd42f56a4e16..c4bfd9da4b7a1 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -5717,7 +5717,7 @@ ZEND_VM_HELPER(zend_verify_recv_arg_type_helper, ANY, ANY, zval *op_1) ZEND_VM_NEXT_OPCODE(); } -ZEND_VM_HANDLER(213, ZEND_SEND_PLACEHOLDER, UNUSED, CONST|UNUSED) +ZEND_VM_HANDLER(213, ZEND_SEND_PLACEHOLDER, UNUSED, CONST|UNUSED|NUM) { zval *arg; From 80f4c603474a16a291473199f94a5c4007e0507b Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 11 Mar 2026 12:53:08 +0100 Subject: [PATCH 44/59] CS --- Zend/zend_vm_def.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index c4bfd9da4b7a1..af2a3f07f1559 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -9867,7 +9867,7 @@ ZEND_VM_HANDLER(212, ZEND_CALLABLE_CONVERT_PARTIAL, CACHE_SLOT, CONST|UNUSED, NU zend_partial_create(EX_VAR(opline->result.var), &call->This, call->func, ZEND_CALL_NUM_ARGS(call), ZEND_CALL_ARG(call, 1), - ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS ? + (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) ? call->extra_named_params : NULL, OP2_TYPE == IS_CONST ? Z_ARRVAL_P(named_positions) : NULL, &EX(func)->op_array, opline, cache_slot, From d57e1880542f84eb8212f6605e90ea47ce30882d Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Thu, 12 Mar 2026 16:43:25 +0100 Subject: [PATCH 45/59] Fix re-binding of inner closure, fix test --- .../partial_application/rebinding_003.phpt | 40 +++++++++++-------- Zend/zend_closures.c | 1 + 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/Zend/tests/partial_application/rebinding_003.phpt b/Zend/tests/partial_application/rebinding_003.phpt index e0b6eaab4ac41..9417bbc2d320b 100644 --- a/Zend/tests/partial_application/rebinding_003.phpt +++ b/Zend/tests/partial_application/rebinding_003.phpt @@ -4,8 +4,12 @@ Rebinding PFA of Closure rebinds inner Closure f(?); -$g = $c->g(?); +$f = $c->getF()(?); +$g = $c->getG()(?); echo "# Can be rebound to \$this of the same class:\n"; -$f->bindTo(new C)($c); +$d = new C(); +$f->bindTo($d)($d); echo "# Can be rebound to \$this of a sub-class:\n"; -$f->bindTo(new SubClass)($c); +$d = new SubClass(); +$f->bindTo($d)($d); echo "# Cannot be rebound to an unrelated class:\n"; try { $f->bindTo(new Unrelated)($c); } catch (Error $e) { - echo $e->getMessage(), "\n"; + echo $e::class, ": ", $e->getMessage(), "\n"; } -echo "# Cannot unbind \$this on instance method:\n"; +echo "# Cannot unbind \$this on non-static closure:\n"; try { $f->bindTo(null)($c); } catch (Error $e) { - echo $e->getMessage(), "\n"; + echo $e::class, ": ", $e->getMessage(), "\n"; } -echo "# Can unbind \$this on static method:\n"; +echo "# Can unbind \$this on static closure:\n"; $g->bindTo(null)($c); ?> @@ -44,18 +50,20 @@ $g->bindTo(null)($c); # Can be rebound to $this of the same class: object(C)#%d (0) { } -bool(false) +bool(true) # Can be rebound to $this of a sub-class: object(SubClass)#%d (0) { } -bool(false) +bool(true) # Cannot be rebound to an unrelated class: Warning: Cannot bind method C::{closure:%s:%d}() to object of class Unrelated, this will be an error in PHP 9 in %s on line %d -Value of type null is not callable -# Cannot unbind $this on instance method: +Error: Value of type null is not callable +# Cannot unbind $this on non-static closure: Warning: Cannot unbind $this of method, this will be an error in PHP 9 in %s on line %d -Value of type null is not callable -# Can unbind $this on static method: +Error: Value of type null is not callable +# Can unbind $this on static closure: string(1) "C" +object(C)#%d (0) { +} diff --git a/Zend/zend_closures.c b/Zend/zend_closures.c index 1ddce132d50e6..b40f12f3622c8 100644 --- a/Zend/zend_closures.c +++ b/Zend/zend_closures.c @@ -271,6 +271,7 @@ static zend_result do_closure_bind(zval *return_value, zval *zclosure, zval *new if (ZEND_CLOSURE_FLAGS(closure) & ZEND_PARTIAL_OF_CLOSURE) { /* Re-bind the inner closure */ + closure = (zend_closure*)Z_OBJ_P(return_value); HashTable *static_variables = ZEND_MAP_PTR_GET(closure->func.op_array.static_variables_ptr); ZEND_ASSERT(static_variables->nNumOfElements > 0); zval *inner = &static_variables->arData[0].val; From 6201971cc79ba74f42828661933b224b095d38f6 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Thu, 12 Mar 2026 16:44:19 +0100 Subject: [PATCH 46/59] Fix staticness inference --- Zend/tests/partial_application/fuzz_001.phpt | 2 +- .../partial_application/static_pfa_001.phpt | 45 +++++++++++++++++++ .../partial_application/static_pfa_002.phpt | 35 +++++++++++++++ .../partial_application/static_pfa_003.phpt | 30 +++++++++++++ .../partial_application/static_pfa_004.phpt | 39 ++++++++++++++++ .../partial_application/static_pfa_005.phpt | 34 ++++++++++++++ ...{statics_001.phpt => static_vars_001.phpt} | 0 ...{statics_002.phpt => static_vars_002.phpt} | 0 ...{statics_003.phpt => static_vars_003.phpt} | 0 .../variation_closure_001.phpt | 4 +- Zend/zend_partial.c | 6 ++- 11 files changed, 191 insertions(+), 4 deletions(-) create mode 100644 Zend/tests/partial_application/static_pfa_001.phpt create mode 100644 Zend/tests/partial_application/static_pfa_002.phpt create mode 100644 Zend/tests/partial_application/static_pfa_003.phpt create mode 100644 Zend/tests/partial_application/static_pfa_004.phpt create mode 100644 Zend/tests/partial_application/static_pfa_005.phpt rename Zend/tests/partial_application/{statics_001.phpt => static_vars_001.phpt} (100%) rename Zend/tests/partial_application/{statics_002.phpt => static_vars_002.phpt} (100%) rename Zend/tests/partial_application/{statics_003.phpt => static_vars_003.phpt} (100%) diff --git a/Zend/tests/partial_application/fuzz_001.phpt b/Zend/tests/partial_application/fuzz_001.phpt index f6544105a435b..790162ba6a36b 100644 --- a/Zend/tests/partial_application/fuzz_001.phpt +++ b/Zend/tests/partial_application/fuzz_001.phpt @@ -7,7 +7,7 @@ $closure = function($a, $b) {}; echo (string) new ReflectionFunction($closure(1, ?)); ?> --EXPECTF-- -Closure [ static function {closure:%s:%d} ] { +Closure [ function {closure:%s:%d} ] { @@ %s 4 - 4 - Bound Variables [2] { diff --git a/Zend/tests/partial_application/static_pfa_001.phpt b/Zend/tests/partial_application/static_pfa_001.phpt new file mode 100644 index 0000000000000..bc0f65b1ffd8f --- /dev/null +++ b/Zend/tests/partial_application/static_pfa_001.phpt @@ -0,0 +1,45 @@ +--TEST-- +PFA of static method is static +--FILE-- +f(?); +echo new ReflectionFunction($f), "\n"; +$f(1); + +// Warns +var_dump($f->bindTo(new C)); + +?> +--EXPECTF-- +Closure [ static public method {closure:pfa:%s:9} ] { + @@ %s 9 - 9 + + - Parameters [1] { + Parameter #0 [ $a ] + } +} + +int(1) +Closure [ static public method {closure:pfa:%s:13} ] { + @@ %s 13 - 13 + + - Parameters [1] { + Parameter #0 [ $a ] + } +} + +int(1) + +Warning: Cannot bind an instance to a static closure, this will be an error in PHP 9 in %s on line %d +NULL diff --git a/Zend/tests/partial_application/static_pfa_002.phpt b/Zend/tests/partial_application/static_pfa_002.phpt new file mode 100644 index 0000000000000..5222109958b84 --- /dev/null +++ b/Zend/tests/partial_application/static_pfa_002.phpt @@ -0,0 +1,35 @@ +--TEST-- +PFA of instance method is not static +--FILE-- +f(?); +echo new ReflectionFunction($f), "\n"; +$f($c); + +$c2 = new C(); +$f->bindTo($c2)($c2); + +?> +--EXPECTF-- +Closure [ public method {closure:pfa:%s:10} ] { + @@ %s 10 - 10 + + - Parameters [1] { + Parameter #0 [ $a ] + } +} + +object(C)#%d (0) { +} +bool(true) +object(C)#%d (0) { +} +bool(true) diff --git a/Zend/tests/partial_application/static_pfa_003.phpt b/Zend/tests/partial_application/static_pfa_003.phpt new file mode 100644 index 0000000000000..e7b3c187e1878 --- /dev/null +++ b/Zend/tests/partial_application/static_pfa_003.phpt @@ -0,0 +1,30 @@ +--TEST-- +PFA of standalone function is static +--FILE-- +bindTo(new class {})); + +?> +--EXPECTF-- +Closure [ static function {closure:pfa:%s:7} ] { + @@ %s 7 - 7 + + - Parameters [1] { + Parameter #0 [ $a ] + } +} + +int(1) + +Warning: Cannot bind an instance to a static closure, this will be an error in PHP 9 in %s on line %d +NULL diff --git a/Zend/tests/partial_application/static_pfa_004.phpt b/Zend/tests/partial_application/static_pfa_004.phpt new file mode 100644 index 0000000000000..9b842c91c4cd0 --- /dev/null +++ b/Zend/tests/partial_application/static_pfa_004.phpt @@ -0,0 +1,39 @@ +--TEST-- +PFA of non-static closure is not static +--FILE-- +bindTo($c2)($c2); + +?> +--EXPECTF-- +Closure [ function {closure:pfa:%s:11} ] { + @@ %s 11 - 11 + + - Bound Variables [1] { + Variable #0 [ $fn ] + } + + - Parameters [1] { + Parameter #0 [ $a ] + } +} + +NULL +bool(false) +object(class@anonymous)#3 (0) { +} +bool(true) diff --git a/Zend/tests/partial_application/static_pfa_005.phpt b/Zend/tests/partial_application/static_pfa_005.phpt new file mode 100644 index 0000000000000..2783485b2f49c --- /dev/null +++ b/Zend/tests/partial_application/static_pfa_005.phpt @@ -0,0 +1,34 @@ +--TEST-- +PFA of static closure is static +--FILE-- +bindTo(new class {})); + +?> +--EXPECTF-- +Closure [ static function {closure:pfa:%s:7} ] { + @@ %s 7 - 7 + + - Bound Variables [1] { + Variable #0 [ $fn ] + } + + - Parameters [1] { + Parameter #0 [ $a ] + } +} + +int(1) + +Warning: Cannot bind an instance to a static closure, this will be an error in PHP 9 in %s on line %d +NULL diff --git a/Zend/tests/partial_application/statics_001.phpt b/Zend/tests/partial_application/static_vars_001.phpt similarity index 100% rename from Zend/tests/partial_application/statics_001.phpt rename to Zend/tests/partial_application/static_vars_001.phpt diff --git a/Zend/tests/partial_application/statics_002.phpt b/Zend/tests/partial_application/static_vars_002.phpt similarity index 100% rename from Zend/tests/partial_application/statics_002.phpt rename to Zend/tests/partial_application/static_vars_002.phpt diff --git a/Zend/tests/partial_application/statics_003.phpt b/Zend/tests/partial_application/static_vars_003.phpt similarity index 100% rename from Zend/tests/partial_application/statics_003.phpt rename to Zend/tests/partial_application/static_vars_003.phpt diff --git a/Zend/tests/partial_application/variation_closure_001.phpt b/Zend/tests/partial_application/variation_closure_001.phpt index d8e463b14c640..1d31f22c7f8ab 100644 --- a/Zend/tests/partial_application/variation_closure_001.phpt +++ b/Zend/tests/partial_application/variation_closure_001.phpt @@ -14,7 +14,7 @@ echo (string) new ReflectionFunction($closure2(1, ?)); ?> --EXPECTF-- -Closure [ static function {closure:%s:%d} ] { +Closure [ function {closure:%s:%d} ] { @@ %s 5 - 5 - Bound Variables [2] { @@ -26,7 +26,7 @@ Closure [ static function {closure:%s:%d} ] { Parameter #0 [ $b ] } } -Closure [ static function {closure:pfa:%s:%d} ] { +Closure [ function {closure:pfa:%s:%d} ] { @@ %s 10 - 10 - Bound Variables [2] { diff --git a/Zend/zend_partial.c b/Zend/zend_partial.c index 60ae63b46bfc7..592eaf3678987 100644 --- a/Zend/zend_partial.c +++ b/Zend/zend_partial.c @@ -58,6 +58,10 @@ static zend_always_inline bool zp_is_static_closure(const zend_function *functio return ((function->common.fn_flags & (ZEND_ACC_STATIC|ZEND_ACC_CLOSURE)) == (ZEND_ACC_STATIC|ZEND_ACC_CLOSURE)); } +static zend_always_inline bool zp_is_non_static_closure(const zend_function *function) { + return ((function->common.fn_flags & (ZEND_ACC_STATIC|ZEND_ACC_CLOSURE)) == ZEND_ACC_CLOSURE); +} + static zend_never_inline ZEND_COLD void zp_args_underflow( const zend_function *function, uint32_t args, uint32_t expected) { @@ -965,7 +969,7 @@ static zend_op_array *zp_compile(zval *this_ptr, zend_function *function, NULL, params_ast, lexical_vars_ast, stmts_ast, return_type_ast, attributes_ast); - if (Z_TYPE_P(this_ptr) != IS_OBJECT || zp_is_static_closure(function)) { + if (Z_TYPE_P(this_ptr) != IS_OBJECT && !zp_is_non_static_closure(function)) { ((zend_ast_decl*)closure_ast)->flags |= ZEND_ACC_STATIC; } From 66595fdec9443b8ee8d5a5a8f59c5be46c9f7dcd Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Thu, 12 Mar 2026 18:30:05 +0100 Subject: [PATCH 47/59] Generated files --- Zend/zend_vm_execute.h | 8 ++++---- Zend/zend_vm_opcodes.c | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 5566c39e71467..191ee6f282c93 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -4253,7 +4253,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_CALLABLE_CONV zend_partial_create(EX_VAR(opline->result.var), &call->This, call->func, ZEND_CALL_NUM_ARGS(call), ZEND_CALL_ARG(call, 1), - ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS ? + (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) ? call->extra_named_params : NULL, IS_CONST == IS_CONST ? Z_ARRVAL_P(named_positions) : NULL, &EX(func)->op_array, opline, cache_slot, @@ -4436,7 +4436,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_CALLABLE_CONV zend_partial_create(EX_VAR(opline->result.var), &call->This, call->func, ZEND_CALL_NUM_ARGS(call), ZEND_CALL_ARG(call, 1), - ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS ? + (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) ? call->extra_named_params : NULL, IS_UNUSED == IS_CONST ? Z_ARRVAL_P(named_positions) : NULL, &EX(func)->op_array, opline, cache_slot, @@ -57016,7 +57016,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_CALLABLE_CONVERT_P zend_partial_create(EX_VAR(opline->result.var), &call->This, call->func, ZEND_CALL_NUM_ARGS(call), ZEND_CALL_ARG(call, 1), - ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS ? + (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) ? call->extra_named_params : NULL, IS_CONST == IS_CONST ? Z_ARRVAL_P(named_positions) : NULL, &EX(func)->op_array, opline, cache_slot, @@ -57199,7 +57199,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_CALLABLE_CONVERT_P zend_partial_create(EX_VAR(opline->result.var), &call->This, call->func, ZEND_CALL_NUM_ARGS(call), ZEND_CALL_ARG(call, 1), - ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS ? + (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) ? call->extra_named_params : NULL, IS_UNUSED == IS_CONST ? Z_ARRVAL_P(named_positions) : NULL, &EX(func)->op_array, opline, cache_slot, diff --git a/Zend/zend_vm_opcodes.c b/Zend/zend_vm_opcodes.c index 76f3af3698457..9846e36037b9d 100644 --- a/Zend/zend_vm_opcodes.c +++ b/Zend/zend_vm_opcodes.c @@ -452,7 +452,7 @@ static uint32_t zend_vm_opcodes_flags[214] = { 0x00000303, 0x01000003, 0x010003a0, - 0x00000301, + 0x00001301, }; ZEND_API const char* ZEND_FASTCALL zend_get_opcode_name(uint8_t opcode) { From e0ceefff11590ac3a7d0806b4669fed5667ed449 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Mon, 20 Apr 2026 13:32:34 +0200 Subject: [PATCH 48/59] Update license --- Zend/zend_partial.c | 17 +++++++++-------- Zend/zend_partial.h | 17 +++++++++-------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/Zend/zend_partial.c b/Zend/zend_partial.c index 592eaf3678987..59735ac927580 100644 --- a/Zend/zend_partial.c +++ b/Zend/zend_partial.c @@ -2,15 +2,16 @@ +----------------------------------------------------------------------+ | Zend Engine | +----------------------------------------------------------------------+ - | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) | + | Copyright © Zend Technologies Ltd., a subsidiary company of | + | Perforce Software, Inc., and Contributors. | +----------------------------------------------------------------------+ - | This source file is subject to version 2.00 of the Zend license, | - | that is bundled with this package in the file LICENSE, and is | - | available through the world-wide-web at the following url: | - | http://www.zend.com/license/2_00.txt. | - | If you did not receive a copy of the Zend license and are unable to | - | obtain it through the world-wide-web, please send a note to | - | license@zend.com so we can mail you a copy immediately. | + | This source file is subject to the Modified BSD License that is | + | bundled with this package in the file LICENSE, and is available | + | through the World Wide Web at . | + | | + | SPDX-License-Identifier: BSD-3-Clause | + +----------------------------------------------------------------------+ + | Authors: Arnaud Le Blanc | +----------------------------------------------------------------------+ */ diff --git a/Zend/zend_partial.h b/Zend/zend_partial.h index 7999dc99019c1..3c47305ff543f 100644 --- a/Zend/zend_partial.h +++ b/Zend/zend_partial.h @@ -2,15 +2,16 @@ +----------------------------------------------------------------------+ | Zend Engine | +----------------------------------------------------------------------+ - | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) | + | Copyright © Zend Technologies Ltd., a subsidiary company of | + | Perforce Software, Inc., and Contributors. | +----------------------------------------------------------------------+ - | This source file is subject to version 2.00 of the Zend license, | - | that is bundled with this package in the file LICENSE, and is | - | available through the world-wide-web at the following url: | - | http://www.zend.com/license/2_00.txt. | - | If you did not receive a copy of the Zend license and are unable to | - | obtain it through the world-wide-web, please send a note to | - | license@zend.com so we can mail you a copy immediately. | + | This source file is subject to the Modified BSD License that is | + | bundled with this package in the file LICENSE, and is available | + | through the World Wide Web at . | + | | + | SPDX-License-Identifier: BSD-3-Clause | + +----------------------------------------------------------------------+ + | Authors: Arnaud Le Blanc | +----------------------------------------------------------------------+ */ #ifndef ZEND_PARTIAL_H From 96c60c078eec35f21845032e292d4cf1c295aebd Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Mon, 20 Apr 2026 13:39:10 +0200 Subject: [PATCH 49/59] Typo --- Zend/tests/partial_application/compile_errors_006.phpt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/tests/partial_application/compile_errors_006.phpt b/Zend/tests/partial_application/compile_errors_006.phpt index 90210be6acae7..8549c671e9069 100644 --- a/Zend/tests/partial_application/compile_errors_006.phpt +++ b/Zend/tests/partial_application/compile_errors_006.phpt @@ -1,5 +1,5 @@ --TEST-- -PFA compile errors: can not use unpacking in PFA, including with variadic placeholdres +PFA compile errors: can not use unpacking in PFA, including with variadic placeholders --FILE-- "bar"], ...); From 91ddb337de16d6b6aa809dafc74e80d37e952a8d Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Mon, 20 Apr 2026 13:39:20 +0200 Subject: [PATCH 50/59] Expand test --- .../partial_application/function_name.phpt | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/Zend/tests/partial_application/function_name.phpt b/Zend/tests/partial_application/function_name.phpt index 5ed0c58696408..e27527f2ce5e1 100644 --- a/Zend/tests/partial_application/function_name.phpt +++ b/Zend/tests/partial_application/function_name.phpt @@ -5,15 +5,36 @@ Partial application function name function g($a) {} +class C { + static function m() { + echo "# From a method:\n"; + var_dump((new ReflectionFunction(g(?)))->getName()); + } +} + function f() { + echo "# From a function:\n"; var_dump((new ReflectionFunction(g(?)))->getName()); + + echo "# Declared on same line:\n"; + [$a, $b] = [g(?), g(?)]; + var_dump((new ReflectionFunction($a))->getName(), (new ReflectionFunction($b))->getName()); } f(); +C::m(); +echo "# From global scope:\n"; var_dump((new ReflectionFunction(g(?)))->getName()); ?> ---EXPECTF-- -string(%d) "{closure:pfa:f():6}" -string(%d) "{closure:pfa:%sfunction_name.php:11}" +--EXPECT-- +# From a function: +string(20) "{closure:pfa:f():14}" +# Declared on same line: +string(20) "{closure:pfa:f():17}" +string(20) "{closure:pfa:f():17}" +# From a method: +string(22) "{closure:pfa:C::m():8}" +# From global scope: +string(94) "{closure:pfa:%sfunction_name.php:25}" From bc86d2c218b0d857515fb0bda7c73dc3e6922493 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Mon, 20 Apr 2026 13:39:30 +0200 Subject: [PATCH 51/59] Indentation --- Zend/tests/partial_application/rfc_examples_eval_order.phpt | 6 +++--- Zend/tests/partial_application/variation_gc_001.phpt | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Zend/tests/partial_application/rfc_examples_eval_order.phpt b/Zend/tests/partial_application/rfc_examples_eval_order.phpt index b54ce0ce15e42..2fd6c9422e923 100644 --- a/Zend/tests/partial_application/rfc_examples_eval_order.phpt +++ b/Zend/tests/partial_application/rfc_examples_eval_order.phpt @@ -4,12 +4,12 @@ PFA RFC examples: "Evaluation order" section speak($who, getArg()); diff --git a/Zend/tests/partial_application/variation_gc_001.phpt b/Zend/tests/partial_application/variation_gc_001.phpt index 887156afc7e1e..2db49cc74363c 100644 --- a/Zend/tests/partial_application/variation_gc_001.phpt +++ b/Zend/tests/partial_application/variation_gc_001.phpt @@ -5,9 +5,9 @@ PFA variation: GC (001) #[AllowDynamicProperties] class Foo { - public function __construct($a) { - $this->method = self::__construct(new stdClass, ...); - } + public function __construct($a) { + $this->method = self::__construct(new stdClass, ...); + } } $foo = new Foo(new stdClass); From 7edc384cffdc335ce2cdfce7a21c333e84fbcd6c Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Mon, 20 Apr 2026 13:39:36 +0200 Subject: [PATCH 52/59] Typo --- Zend/zend_compile.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index d3b556752a775..515f44f8b2a79 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -6915,7 +6915,7 @@ static zend_ast *zend_partial_apply(zend_ast *callable_ast, zend_ast *pipe_arg) if (first_placeholder == NULL) { first_placeholder = arg; } else { - /* A PFA with multiple placeholders is unexpected in is this + /* A PFA with multiple placeholders is unexpected in this * context, and will usually error due to a missing argument, * so we don't optimize those. */ return NULL; From ab1c84878f00a031cc21e87d390a31af25c936e4 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Mon, 20 Apr 2026 13:39:48 +0200 Subject: [PATCH 53/59] EMPTY_SWITCH_DEFAULT_CASE --- Zend/zend_compile.c | 3 ++- Zend/zend_partial.c | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 515f44f8b2a79..c52b32da020e6 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -6998,7 +6998,8 @@ static void zend_compile_pipe(znode *result, zend_ast *ast, uint32_t type) callable_ast->child[0], callable_ast->child[1], pfa_arg_list_ast); break; - EMPTY_SWITCH_DEFAULT_CASE() + default: + ZEND_UNREACHABLE(); } /* Turn $foo |> $expr into ($expr)($foo) */ } else { diff --git a/Zend/zend_partial.c b/Zend/zend_partial.c index 59735ac927580..d53520e69fb02 100644 --- a/Zend/zend_partial.c +++ b/Zend/zend_partial.c @@ -286,7 +286,8 @@ static zend_ast *zp_simple_type_to_ast(uint32_t type) return zend_ast_create_ex(ZEND_AST_TYPE, IS_ARRAY); case MAY_BE_STATIC: return zend_ast_create_ex(ZEND_AST_TYPE, IS_STATIC); - EMPTY_SWITCH_DEFAULT_CASE() + default: + ZEND_UNREACHABLE(); } zend_ast *ast = zend_ast_create_zval_from_str(name); From 0420bc6e36b113fd27884cc5b06406c29ee96def Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Mon, 20 Apr 2026 13:40:00 +0200 Subject: [PATCH 54/59] Code style --- Zend/zend_language_scanner.l | 2 +- Zend/zend_partial.c | 3 +-- ext/opcache/ZendAccelerator.c | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Zend/zend_language_scanner.l b/Zend/zend_language_scanner.l index fd7d36d837922..f4fba22407841 100644 --- a/Zend/zend_language_scanner.l +++ b/Zend/zend_language_scanner.l @@ -685,7 +685,7 @@ ZEND_API zend_op_array *zend_compile_ast( { zend_string *original_compiled_filename = CG(compiled_filename); bool original_in_compilation = CG(in_compilation); - CG(in_compilation) = 1; + CG(in_compilation) = true; CG(ast) = ast; zend_set_compiled_filename(filename); diff --git a/Zend/zend_partial.c b/Zend/zend_partial.c index d53520e69fb02..6986e6c2631d0 100644 --- a/Zend/zend_partial.c +++ b/Zend/zend_partial.c @@ -378,9 +378,8 @@ static zend_result zp_get_param_default_value(zval *result, zend_function *funct if (EXPECTED(opline->opcode == ZEND_RECV_INIT)) { ZVAL_COPY(result, RT_CONSTANT(opline, opline->op2)); return SUCCESS; - } else { - ZEND_ASSERT(opline->opcode == ZEND_RECV); } + ZEND_ASSERT(opline->opcode == ZEND_RECV); } else { if (function->common.fn_flags & ZEND_ACC_USER_ARG_INFO) { goto error; diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 82eea9a25d154..d36172d1e7ddd 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -2034,7 +2034,7 @@ static char *zend_accel_uintptr_hex(char *dest, uintptr_t n) static zend_string *zend_accel_pfa_key(const zend_op *declaring_opline, const zend_function *called_function) { - size_t key_len = strlen(PFA_KEY_PREFIX) + (sizeof(uintptr_t)*2) + strlen(":") + (sizeof(uintptr_t)*2); + const size_t key_len = strlen(PFA_KEY_PREFIX) + (sizeof(uintptr_t)*2) + strlen(":") + (sizeof(uintptr_t)*2); zend_string *key = zend_string_alloc(key_len, 0); char *dest = ZSTR_VAL(key); From 79a86f644230cf75a5b253a333116307975a806a Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Mon, 20 Apr 2026 13:47:02 +0200 Subject: [PATCH 55/59] Comment about zend_check_type --- Zend/zend_execute.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 1cc0d490f031a..1fb8f208cff62 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1229,6 +1229,7 @@ static zend_always_inline bool zend_check_type( return zend_check_type_slow(type, arg, ref, is_return_type, is_internal); } +/* We can not expose zend_check_type() directly because it's inline and uses static functions */ ZEND_API bool zend_check_type_ex( const zend_type *type, zval *arg, bool is_return_type, bool is_internal) { From 32fe5212c404a093ea256891b050468c146250e2 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Mon, 20 Apr 2026 14:21:39 +0200 Subject: [PATCH 56/59] Fix build after rebase --- ext/opcache/ZendAccelerator.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index d36172d1e7ddd..1bce00f0edde0 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -2189,8 +2189,8 @@ zend_op_array *zend_accel_compile_pfa(zend_ast *ast, new_persistent_script->script.filename = key; if (ZCG(accel_directives).record_warnings) { - new_persistent_script->num_warnings = EG(num_errors); - new_persistent_script->warnings = EG(errors); + new_persistent_script->num_warnings = EG(errors).size; + new_persistent_script->warnings = EG(errors).errors; } HANDLE_BLOCK_INTERRUPTIONS(); From a0e136b44d72d98b895754baf73bcba0d5525fd6 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Mon, 20 Apr 2026 14:23:59 +0200 Subject: [PATCH 57/59] Fix test --- Zend/tests/partial_application/function_name.phpt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/tests/partial_application/function_name.phpt b/Zend/tests/partial_application/function_name.phpt index e27527f2ce5e1..b925d0b56d31b 100644 --- a/Zend/tests/partial_application/function_name.phpt +++ b/Zend/tests/partial_application/function_name.phpt @@ -28,7 +28,7 @@ echo "# From global scope:\n"; var_dump((new ReflectionFunction(g(?)))->getName()); ?> ---EXPECT-- +--EXPECTF-- # From a function: string(20) "{closure:pfa:f():14}" # Declared on same line: From ed2c4082fdded4f58427f1fcf9e82d531fb916aa Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Mon, 20 Apr 2026 14:24:34 +0200 Subject: [PATCH 58/59] Organize names in a struct --- Zend/zend_partial.c | 114 ++++++++++++++++++++++++++++---------------- 1 file changed, 72 insertions(+), 42 deletions(-) diff --git a/Zend/zend_partial.c b/Zend/zend_partial.c index 6986e6c2631d0..88bdad9ace081 100644 --- a/Zend/zend_partial.c +++ b/Zend/zend_partial.c @@ -125,38 +125,75 @@ static zend_result zp_args_check(const zend_function *function, return SUCCESS; } -static bool zp_name_exists(zend_string **names, uint32_t num_names, const zend_string *name) +typedef struct zp_names { + zend_string *variadic_param; + zend_string *extra_named_params; + zend_string *inner_closure; + zend_string *params[1]; +} zp_names; + +static bool zp_name_exists(zp_names *names, uint32_t argc, const zend_string *name) { - for (uint32_t i = 0; i < num_names; i++) { - if (names[i] && zend_string_equals(names[i], name)) { + for (uint32_t i = 0; i < argc; i++) { + if (names->params[i] && zend_string_equals(names->params[i], name)) { return true; } } + if (names->variadic_param && zend_string_equals(names->variadic_param, name)) { + return true; + } + if (names->extra_named_params && zend_string_equals(names->extra_named_params, name)) { + return true; + } + if (names->inner_closure && zend_string_equals(names->inner_closure, name)) { + return true; + } + return false; } -static zend_string *zp_get_param_name(zend_function *function, uint32_t arg_offset) +static void zp_names_dtor(zp_names *names, uint32_t argc) +{ + for (uint32_t i = 0; i < argc; i++) { + if (names->params[i]) { + zend_string_release(names->params[i]); + } + } + if (names->variadic_param) { + zend_string_release(names->variadic_param); + } + if (names->extra_named_params) { + zend_string_release(names->extra_named_params); + } + if (names->inner_closure) { + zend_string_release(names->inner_closure); + } +} + +static zend_string *zp_get_func_param_name(zend_function *function, uint32_t arg_offset) { return zend_string_copy(function->common.arg_info[arg_offset].name); } /* Assign a name for every variable that will be used in the generated closure, * including params and used vars. */ -static void zp_assign_names(zend_string **names, uint32_t num_names, - uint32_t argc, zval *argv, +static zp_names *zp_assign_names(uint32_t argc, zval *argv, zend_function *function, bool variadic_partial, zend_array *extra_named_params) { + zp_names *names = zend_arena_calloc(&CG(ast_arena), + 1, XtOffsetOf(zp_names, params) + sizeof(zend_string*) * argc); + /* Assign names for params. We never rename those. */ for (uint32_t offset = 0; offset < MIN(argc, function->common.num_args); offset++) { if (Z_IS_PLACEHOLDER_P(&argv[offset])) { - names[offset] = zp_get_param_name(function, offset); + names->params[offset] = zp_get_func_param_name(function, offset); } } /* Assign name for the variadic param. Never renamed. */ if (variadic_partial && (function->common.fn_flags & ZEND_ACC_VARIADIC)) { - names[argc] = zp_get_param_name(function, function->common.num_args); + names->variadic_param = zp_get_func_param_name(function, function->common.num_args); } /* Assign names for placeholders that bind to the variadic param: @@ -172,16 +209,16 @@ static void zp_assign_names(zend_string **names, uint32_t num_names, if (!Z_IS_PLACEHOLDER_P(&argv[offset])) { continue; } - zend_string *orig_name = zp_get_param_name(function, function->common.num_args); + zend_string *orig_name = zp_get_func_param_name(function, function->common.num_args); zend_string *new_name; for (uint32_t n = 0;; n++) { new_name = zend_strpprintf_unchecked(0, "%S%" PRIu32, orig_name, n); - if (!zp_name_exists(names, num_names, new_name)) { + if (!zp_name_exists(names, argc, new_name)) { break; } zend_string_release(new_name); } - names[offset] = new_name; + names->params[offset] = new_name; zend_string_release(orig_name); } @@ -192,14 +229,14 @@ static void zp_assign_names(zend_string **names, uint32_t num_names, continue; } uint32_t n = 0; - zend_string *orig_name = zp_get_param_name(function, MIN(offset, function->common.num_args)); + zend_string *orig_name = zp_get_func_param_name(function, MIN(offset, function->common.num_args)); zend_string *new_name = zend_string_copy(orig_name); - while (zp_name_exists(names, num_names, new_name)) { + while (zp_name_exists(names, argc, new_name)) { zend_string_release(new_name); new_name = zend_strpprintf_unchecked(0, "%S%" PRIu32, orig_name, n); n++; } - names[offset] = new_name; + names->params[offset] = new_name; zend_string_release(orig_name); } @@ -207,25 +244,27 @@ static void zp_assign_names(zend_string **names, uint32_t num_names, if (extra_named_params) { uint32_t n = 1; zend_string *new_name = ZSTR_INIT_LITERAL("extra_named_params", 0); - while (zp_name_exists(names, num_names, new_name)) { + while (zp_name_exists(names, argc, new_name)) { zend_string_release(new_name); n++; new_name = zend_strpprintf(0, "%s%" PRIu32, "extra_named_params", n); } - names[argc + variadic_partial] = new_name; + names->extra_named_params = new_name; } /* Assign name for $fn */ if (function->common.fn_flags & ZEND_ACC_CLOSURE) { uint32_t n = 1; zend_string *new_name = ZSTR_INIT_LITERAL("fn", 0); - while (zp_name_exists(names, num_names, new_name)) { + while (zp_name_exists(names, argc, new_name)) { zend_string_release(new_name); n++; new_name = zend_strpprintf(0, "%s%" PRIu32, "fn", n); } - names[argc + variadic_partial + (extra_named_params != NULL)] = new_name; + names->inner_closure = new_name; } + + return names; } static inline bool zp_is_power_of_two(uint32_t x) @@ -502,7 +541,7 @@ static zend_string *zp_pfa_name(const zend_op_array *declaring_op_array, static zend_ast *zp_compile_forwarding_call( zval *this_ptr, zend_function *function, uint32_t argc, zval *argv, zend_array *extra_named_params, - zend_string **param_names, bool variadic_partial, uint32_t num_args, + zp_names *var_names, bool variadic_partial, uint32_t num_args, zend_class_entry *called_scope, zend_type return_type, bool forward_superfluous_args, zend_ast *stmts_ast) @@ -547,19 +586,19 @@ static zend_ast *zp_compile_forwarding_call( args_ast = zend_ast_list_add(args_ast, default_value_ast); } else { args_ast = zend_ast_list_add(args_ast, zend_ast_create(ZEND_AST_VAR, - zend_ast_create_zval_from_str(zend_string_copy(param_names[offset])))); + zend_ast_create_zval_from_str(zend_string_copy(var_names->params[offset])))); } } if (extra_named_params) { args_ast = zend_ast_list_add(args_ast, zend_ast_create(ZEND_AST_UNPACK, zend_ast_create(ZEND_AST_VAR, - zend_ast_create_zval_from_str(zend_string_copy(param_names[argc + variadic_partial]))))); + zend_ast_create_zval_from_str(zend_string_copy(var_names->extra_named_params))))); } if (variadic_partial) { if (function->common.fn_flags & ZEND_ACC_VARIADIC) { args_ast = zend_ast_list_add(args_ast, zend_ast_create(ZEND_AST_UNPACK, zend_ast_create(ZEND_AST_VAR, - zend_ast_create_zval_from_str(zend_string_copy(param_names[argc]))))); + zend_ast_create_zval_from_str(zend_string_copy(var_names->variadic_param))))); } else if (forward_superfluous_args) { /* When a '...' placeholder is used, and the underlying function is * not variadic, superfluous arguments are forwarded. @@ -592,7 +631,7 @@ static zend_ast *zp_compile_forwarding_call( call_ast = zend_ast_create(ZEND_AST_CALL, func_name_ast, args_ast); } else if (function->common.fn_flags & ZEND_ACC_CLOSURE) { zend_ast *fn_ast = zend_ast_create(ZEND_AST_VAR, - zend_ast_create_zval_from_str(zend_string_copy(param_names[argc + variadic_partial + (extra_named_params != NULL)]))); + zend_ast_create_zval_from_str(zend_string_copy(var_names->inner_closure))); call_ast = zend_ast_create(ZEND_AST_CALL, fn_ast, args_ast); } else if (Z_TYPE_P(this_ptr) == IS_OBJECT) { zend_ast *this_ast = zend_ast_create(ZEND_AST_VAR, @@ -779,11 +818,7 @@ static zend_op_array *zp_compile(zval *this_ptr, zend_function *function, /* Assign variable names */ - uint32_t num_names = argc + uses_variadic_placeholder + (extra_named_params != NULL) - + ((function->common.fn_flags & ZEND_ACC_CLOSURE) != 0); - zend_string **param_names = zend_arena_calloc(&CG(ast_arena), - num_names, sizeof(zend_string*)); - zp_assign_names(param_names, num_names, argc, argv, function, + zp_names *var_names = zp_assign_names(argc, argv, function, uses_variadic_placeholder, extra_named_params); /* Generate AST */ @@ -800,7 +835,7 @@ static zend_op_array *zp_compile(zval *this_ptr, zend_function *function, * do_closure_bind(). */ if (function->common.fn_flags & ZEND_ACC_CLOSURE) { zend_ast *lexical_var_ast = zend_ast_create_zval_from_str( - zend_string_copy(param_names[argc + uses_variadic_placeholder + (extra_named_params != NULL)])); + zend_string_copy(var_names->inner_closure)); lexical_vars_ast = zend_ast_list_add(lexical_vars_ast, lexical_var_ast); } @@ -834,14 +869,14 @@ static zend_op_array *zp_compile(zval *this_ptr, zend_function *function, params[param_offset] = zend_ast_create_ex(ZEND_AST_PARAM, param_flags, param_type_ast, zend_ast_create_zval_from_str( - zend_string_copy(param_names[offset])), + zend_string_copy(var_names->params[offset])), default_value_ast, attributes_ast, NULL, NULL); } else if (!Z_ISUNDEF(argv[offset])) { // TODO: If the pre-bound parameter is a literal, it can be a // literal in the function body instead of a lexical var. zend_ast *lexical_var_ast = zend_ast_create_zval_from_str( - zend_string_copy(param_names[offset])); + zend_string_copy(var_names->params[offset])); if (zp_arg_must_be_sent_by_ref(function, offset+1)) { lexical_var_ast->attr = ZEND_BIND_REF; } @@ -857,7 +892,7 @@ static zend_op_array *zp_compile(zval *this_ptr, zend_function *function, if (extra_named_params) { zend_ast *lexical_var_ast = zend_ast_create_zval_from_str( - zend_string_copy(param_names[argc + uses_variadic_placeholder])); + zend_string_copy(var_names->extra_named_params)); lexical_vars_ast = zend_ast_list_add(lexical_vars_ast, lexical_var_ast); } @@ -875,7 +910,7 @@ static zend_op_array *zp_compile(zval *this_ptr, zend_function *function, params_ast = zend_ast_list_add(params_ast, zend_ast_create_ex(ZEND_AST_PARAM, param_flags, param_type_ast, zend_ast_create_zval_from_str( - zend_string_copy(param_names[argc])), + zend_string_copy(var_names->variadic_param)), NULL, attributes_ast, NULL, NULL)); } @@ -906,7 +941,7 @@ static zend_op_array *zp_compile(zval *this_ptr, zend_function *function, no_forwarding_ast = zp_compile_forwarding_call(this_ptr, function, argc, argv, extra_named_params, - param_names, uses_variadic_placeholder, num_params, + var_names, uses_variadic_placeholder, num_params, called_scope, return_type, false, no_forwarding_ast); if (!no_forwarding_ast) { @@ -916,7 +951,7 @@ static zend_op_array *zp_compile(zval *this_ptr, zend_function *function, forwarding_ast = zp_compile_forwarding_call(this_ptr, function, argc, argv, extra_named_params, - param_names, uses_variadic_placeholder, num_params, + var_names, uses_variadic_placeholder, num_params, called_scope, return_type, true, forwarding_ast); if (!forwarding_ast) { @@ -943,7 +978,7 @@ static zend_op_array *zp_compile(zval *this_ptr, zend_function *function, } else { stmts_ast = zp_compile_forwarding_call(this_ptr, function, argc, argv, extra_named_params, - param_names, uses_variadic_placeholder, num_params, + var_names, uses_variadic_placeholder, num_params, called_scope, return_type, false, stmts_ast); if (!stmts_ast) { @@ -993,12 +1028,7 @@ static zend_op_array *zp_compile(zval *this_ptr, zend_function *function, zend_ast_destroy(closure_ast); clean: - for (uint32_t i = 0; i < num_names; i++) { - if (param_names[i]) { - zend_string_release(param_names[i]); - } - } - + zp_names_dtor(var_names, argc); zend_arena_destroy(CG(ast_arena)); CG(ast_arena) = orig_ast_arena; CG(zend_lineno) = orig_lineno; From 7dc3a1210eb9ce35bce41ef0b399d8de9a799238 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Mon, 20 Apr 2026 14:48:09 +0200 Subject: [PATCH 59/59] Test ZEND_ACC_NEVER_CACHE behavior --- .../partial_application/never_cache_001.phpt | 30 +++++++++++++++++++ Zend/zend_partial.c | 1 + 2 files changed, 31 insertions(+) create mode 100644 Zend/tests/partial_application/never_cache_001.phpt diff --git a/Zend/tests/partial_application/never_cache_001.phpt b/Zend/tests/partial_application/never_cache_001.phpt new file mode 100644 index 0000000000000..3c9e8a173e51a --- /dev/null +++ b/Zend/tests/partial_application/never_cache_001.phpt @@ -0,0 +1,30 @@ +--TEST-- +Inline cache can not be used for ZEND_ACC_NEVER_CACHE functions +--FILE-- +__invoke(?)); +} + +?> +--EXPECT-- +string(1) "A" +string(1) "B" +string(1) "A" +string(1) "B" diff --git a/Zend/zend_partial.c b/Zend/zend_partial.c index 88bdad9ace081..e44c67f165d30 100644 --- a/Zend/zend_partial.c +++ b/Zend/zend_partial.c @@ -1054,6 +1054,7 @@ static const zend_op_array *zp_get_op_array(zval *this_ptr, zend_function *funct if (EXPECTED(function->type == ZEND_INTERNAL_FUNCTION ? cache_slot[0] == function : cache_slot[0] == function->op_array.opcodes)) { + ZEND_ASSERT(!(function->common.fn_flags & ZEND_ACC_NEVER_CACHE)); return cache_slot[1]; }