From c33115519fdc5eb893ce63522a80817c8d2c7890 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Tue, 5 Aug 2025 10:14:06 +0200 Subject: [PATCH 01/35] 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 | 286 +++- 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 | 942 +++++++------- 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 447a034530e1b..c534a5ecd8a45 100644 --- a/Zend/Optimizer/compact_literals.c +++ b/Zend/Optimizer/compact_literals.c @@ -735,6 +735,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; @@ -747,6 +748,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 62b50464e87ba..522a8927acc7f 100644 --- a/Zend/Optimizer/optimize_func_calls.c +++ b/Zend/Optimizer/optimize_func_calls.c @@ -193,6 +193,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; @@ -225,13 +226,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 645edd2f99914..4e414a95172fe 100644 --- a/Zend/Optimizer/zend_call_graph.c +++ b/Zend/Optimizer/zend_call_graph.c @@ -128,6 +128,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; @@ -144,11 +145,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 9b5561e7fbe23..c476961166c96 100644 --- a/Zend/Optimizer/zend_inference.c +++ b/Zend/Optimizer/zend_inference.c @@ -3906,6 +3906,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 cd8e6792d031f..8c44547e81c37 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1452,6 +1452,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 c212cd8367a87..33a43f536821a 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -353,6 +353,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 43eefbeff9dac..d1441e571fc2f 100644 --- a/Zend/zend_closures.c +++ b/Zend/zend_closures.c @@ -28,6 +28,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; @@ -41,6 +49,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) /* {{{ */ { @@ -78,7 +87,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"); @@ -161,7 +171,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; @@ -177,6 +189,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; @@ -223,7 +236,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); @@ -235,14 +248,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) { @@ -251,7 +265,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 */ @@ -588,8 +626,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); } /* }}} */ @@ -756,7 +795,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; @@ -764,6 +803,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 */ @@ -861,14 +901,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; @@ -878,6 +918,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 8bea4ffb051e8..3c8d2f83c32ec 100644 --- a/Zend/zend_closures.h +++ b/Zend/zend_closures.h @@ -37,6 +37,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 916d8eebd8960..146906f463bb4 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -29,6 +29,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" @@ -3697,12 +3699,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. @@ -3728,6 +3734,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)) { @@ -3768,18 +3779,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. @@ -3892,14 +3960,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; @@ -3933,6 +4011,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) /* {{{ */ { zend_op *opline; @@ -3948,23 +4058,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; } @@ -6681,6 +6811,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) { zend_ast *operand_ast = ast->child[0]; @@ -6705,29 +6905,34 @@ static void zend_compile_pipe(znode *result, zend_ast *ast) } /* 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, @@ -11716,6 +11921,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 6075ae3d1e5d2..b95cdf3eab6fa 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -951,6 +951,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, ...); @@ -1125,7 +1126,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 e95931276ef51..7c24f728a905e 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -45,6 +45,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" @@ -1230,6 +1231,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) { @@ -4652,6 +4660,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: @@ -4708,6 +4717,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: @@ -4788,6 +4798,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: @@ -4845,6 +4856,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: @@ -5556,9 +5568,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 8858f9fce96ae..ebec845a3d8c4 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -110,6 +110,9 @@ ZEND_API 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 69337e27fd530..393adfc8839fb 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -39,6 +39,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 @@ -203,6 +204,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; } @@ -420,6 +422,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)) { @@ -519,6 +522,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 f09b81acb315b..0d15e35a9e3eb 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -320,6 +320,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 5a8a78cc3bd7b..86ae6dc06a53b 100644 --- a/Zend/zend_language_scanner.l +++ b/Zend/zend_language_scanner.l @@ -591,6 +591,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(int type) { zend_op_array *op_array = NULL; @@ -601,34 +638,7 @@ static zend_op_array *zend_compile(int 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)); @@ -671,6 +681,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 97386ea6bad64..fcf8451cb7fcb 100644 --- a/Zend/zend_string.h +++ b/Zend/zend_string.h @@ -631,6 +631,14 @@ EMPTY_SWITCH_DEFAULT_CASE() _(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 22dbfa9be879b..6a17ed9a69e1d 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -858,6 +858,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 2d059159a23c8..2c0fc3b495a69 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -5681,6 +5681,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 @@ -9782,6 +9806,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 38b869c4a4c07..b406ec05e4720 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -4246,6 +4246,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_TMPVAR_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -4390,6 +4429,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 @@ -36109,6 +36187,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 @@ -38874,6 +38976,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 @@ -59930,6 +60056,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_TMPVAR_TAILCALL_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -60074,6 +60239,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 @@ -91591,6 +91795,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 @@ -94356,6 +94584,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 @@ -115290,6 +115542,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, @@ -116699,6 +116961,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_TMPVAR): VM_TRACE(ZEND_INIT_DYNAMIC_CALL_SPEC_TMPVAR) ZEND_INIT_DYNAMIC_CALL_SPEC_TMPVAR_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); @@ -116714,6 +116981,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); @@ -120166,6 +120438,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); @@ -120351,6 +120628,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); @@ -124547,6 +124829,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, @@ -128045,6 +128337,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, @@ -129013,7 +129315,7 @@ void zend_vm_init(void) 1255, 1256 | SPEC_RULE_OP1, 1261 | SPEC_RULE_OP1, - 3494, + 3504, 1266 | SPEC_RULE_OP1, 1271 | SPEC_RULE_OP1, 1276 | SPEC_RULE_OP2, @@ -129047,7 +129349,7 @@ void zend_vm_init(void) 1559 | SPEC_RULE_OP1 | SPEC_RULE_OP2, 1584 | SPEC_RULE_OP1, 1589, - 3494, + 3504, 1590 | SPEC_RULE_OP1, 1595 | SPEC_RULE_OP1 | SPEC_RULE_OP2, 1620 | SPEC_RULE_OP1 | SPEC_RULE_OP2, @@ -129180,50 +129482,50 @@ void zend_vm_init(void) 2576, 2577, 2578, - 3494, - 3494, - 3494, - 3494, - 3494, - 3494, - 3494, - 3494, - 3494, - 3494, - 3494, - 3494, - 3494, - 3494, - 3494, - 3494, - 3494, - 3494, - 3494, - 3494, - 3494, - 3494, - 3494, - 3494, - 3494, - 3494, - 3494, - 3494, - 3494, - 3494, - 3494, - 3494, - 3494, - 3494, - 3494, - 3494, - 3494, - 3494, - 3494, - 3494, - 3494, - 3494, - 3494, - 3494, + 2579 | SPEC_RULE_OP2, + 2584 | SPEC_RULE_OP2, + 3504, + 3504, + 3504, + 3504, + 3504, + 3504, + 3504, + 3504, + 3504, + 3504, + 3504, + 3504, + 3504, + 3504, + 3504, + 3504, + 3504, + 3504, + 3504, + 3504, + 3504, + 3504, + 3504, + 3504, + 3504, + 3504, + 3504, + 3504, + 3504, + 3504, + 3504, + 3504, + 3504, + 3504, + 3504, + 3504, + 3504, + 3504, + 3504, + 3504, + 3504, + 3504, }; #if 0 #elif (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID) @@ -129400,7 +129702,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 = 2587 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 2597 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; if (op->op1_type < op->op2_type) { zend_swap_operands(op); } @@ -129408,7 +129710,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 = 2612 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 2622 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; if (op->op1_type < op->op2_type) { zend_swap_operands(op); } @@ -129416,7 +129718,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 = 2637 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 2647 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; if (op->op1_type < op->op2_type) { zend_swap_operands(op); } @@ -129427,17 +129729,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 = 2662 | SPEC_RULE_OP1 | SPEC_RULE_OP2; + spec = 2672 | 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 = 2687 | SPEC_RULE_OP1 | SPEC_RULE_OP2; + spec = 2697 | 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 = 2712 | SPEC_RULE_OP1 | SPEC_RULE_OP2; + spec = 2722 | SPEC_RULE_OP1 | SPEC_RULE_OP2; } break; case ZEND_MUL: @@ -129448,17 +129750,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 = 2737 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 2747 | 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 = 2762 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 2772 | 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 = 2787 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 2797 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; } break; case ZEND_IS_IDENTICAL: @@ -129469,16 +129771,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 = 2812 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 2822 | 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 = 2887 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 2897 | 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 = 3112 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 3122 | 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 = 3118 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 3128 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; } break; case ZEND_IS_NOT_IDENTICAL: @@ -129489,16 +129791,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 = 2962 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 2972 | 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 = 3037 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 3047 | 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 = 3115 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 3125 | 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 = 3123 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 3133 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; } break; case ZEND_IS_EQUAL: @@ -129509,12 +129811,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 = 2812 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 2822 | 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 = 2887 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 2897 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } break; case ZEND_IS_NOT_EQUAL: @@ -129525,12 +129827,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 = 2962 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 2972 | 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 = 3037 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 3047 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } break; case ZEND_IS_SMALLER: @@ -129538,12 +129840,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 = 3128 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; + spec = 3138 | 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 = 3203 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; + spec = 3213 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; } break; case ZEND_IS_SMALLER_OR_EQUAL: @@ -129551,79 +129853,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 = 3278 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; + spec = 3288 | 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 = 3353 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; + spec = 3363 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; } break; case ZEND_QM_ASSIGN: if (op1_info == MAY_BE_LONG) { - spec = 3440 | SPEC_RULE_OP1; + spec = 3450 | SPEC_RULE_OP1; } else if (op1_info == MAY_BE_DOUBLE) { - spec = 3445 | SPEC_RULE_OP1; + spec = 3455 | 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 = 3450 | SPEC_RULE_OP1; + spec = 3460 | SPEC_RULE_OP1; } break; case ZEND_PRE_INC: if (res_info == MAY_BE_LONG && op1_info == MAY_BE_LONG) { - spec = 3428 | SPEC_RULE_RETVAL; + spec = 3438 | SPEC_RULE_RETVAL; } else if (op1_info == MAY_BE_LONG) { - spec = 3430 | SPEC_RULE_RETVAL; + spec = 3440 | SPEC_RULE_RETVAL; } break; case ZEND_PRE_DEC: if (res_info == MAY_BE_LONG && op1_info == MAY_BE_LONG) { - spec = 3432 | SPEC_RULE_RETVAL; + spec = 3442 | SPEC_RULE_RETVAL; } else if (op1_info == MAY_BE_LONG) { - spec = 3434 | SPEC_RULE_RETVAL; + spec = 3444 | SPEC_RULE_RETVAL; } break; case ZEND_POST_INC: if (res_info == MAY_BE_LONG && op1_info == MAY_BE_LONG) { - spec = 3436; + spec = 3446; } else if (op1_info == MAY_BE_LONG) { - spec = 3437; + spec = 3447; } break; case ZEND_POST_DEC: if (res_info == MAY_BE_LONG && op1_info == MAY_BE_LONG) { - spec = 3438; + spec = 3448; } else if (op1_info == MAY_BE_LONG) { - spec = 3439; + spec = 3449; } break; case ZEND_JMP: if (OP_JMP_ADDR(op, op->op1) > op) { - spec = 2586; + spec = 2596; } break; case ZEND_INIT_FCALL: if (Z_EXTRA_P(RT_CONSTANT(op, op->op2)) != 0) { - spec = 2579; + spec = 2589; } break; case ZEND_RECV: if (op->op2.num == MAY_BE_ANY) { - spec = 2580; + spec = 2590; } 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 = 3490; + spec = 3500; } 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 = 3485 | SPEC_RULE_OP1; + spec = 3495 | 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 = 3492 | SPEC_RULE_RETVAL; + spec = 3502 | SPEC_RULE_RETVAL; } break; case ZEND_FETCH_DIM_R: @@ -129631,22 +129933,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 = 3455 | SPEC_RULE_OP1 | SPEC_RULE_OP2; + spec = 3465 | 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 = 3491; + spec = 3501; } break; case ZEND_SEND_VAR: if (op->op2_type == IS_UNUSED && (op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) == 0) { - spec = 3480 | SPEC_RULE_OP1; + spec = 3490 | SPEC_RULE_OP1; } break; case ZEND_COUNT: if ((op1_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF)) == MAY_BE_ARRAY) { - spec = 2581 | SPEC_RULE_OP1; + spec = 2591 | SPEC_RULE_OP1; } break; case ZEND_BW_OR: diff --git a/Zend/zend_vm_handlers.h b/Zend/zend_vm_handlers.h index d52de1226df13..30ab98fcb7ee2 100644 --- a/Zend/zend_vm_handlers.h +++ b/Zend/zend_vm_handlers.h @@ -1374,508 +1374,512 @@ _(2576, ZEND_INIT_PARENT_PROPERTY_HOOK_CALL_SPEC_CONST_UNUSED) \ _(2577, ZEND_DECLARE_ATTRIBUTED_CONST_SPEC_CONST_CONST) \ _(2578, ZEND_TYPE_ASSERT_SPEC_CONST) \ - _(2579, ZEND_INIT_FCALL_OFFSET_SPEC_CONST) \ - _(2580, ZEND_RECV_NOTYPE_SPEC) \ - _(2582, ZEND_COUNT_ARRAY_SPEC_TMPVAR_UNUSED) \ - _(2583, ZEND_COUNT_ARRAY_SPEC_TMPVAR_UNUSED) \ - _(2585, ZEND_COUNT_ARRAY_SPEC_CV_UNUSED) \ - _(2586, ZEND_JMP_FORWARD_SPEC) \ - _(2592, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ - _(2593, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2594, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2596, ZEND_ADD_LONG_NO_OVERFLOW_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) \ + _(2579, ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST) \ + _(2582, ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_UNUSED) \ + _(2584, ZEND_SEND_PLACEHOLDER_SPEC_UNUSED_CONST) \ + _(2587, ZEND_SEND_PLACEHOLDER_SPEC_UNUSED_UNUSED) \ + _(2589, ZEND_INIT_FCALL_OFFSET_SPEC_CONST) \ + _(2590, ZEND_RECV_NOTYPE_SPEC) \ + _(2592, ZEND_COUNT_ARRAY_SPEC_TMPVAR_UNUSED) \ + _(2593, ZEND_COUNT_ARRAY_SPEC_TMPVAR_UNUSED) \ + _(2595, ZEND_COUNT_ARRAY_SPEC_CV_UNUSED) \ + _(2596, ZEND_JMP_FORWARD_SPEC) \ + _(2602, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ + _(2603, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2604, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2606, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ _(2607, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ _(2608, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ _(2609, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ _(2611, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2617, ZEND_ADD_LONG_SPEC_TMPVARCV_CONST) \ - _(2618, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2619, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2621, ZEND_ADD_LONG_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) \ + _(2617, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ + _(2618, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2619, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2621, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2627, ZEND_ADD_LONG_SPEC_TMPVARCV_CONST) \ + _(2628, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2629, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2631, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ _(2632, ZEND_ADD_LONG_SPEC_TMPVARCV_CONST) \ _(2633, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ _(2634, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ _(2636, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2642, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2643, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2644, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2646, ZEND_ADD_DOUBLE_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) \ + _(2642, ZEND_ADD_LONG_SPEC_TMPVARCV_CONST) \ + _(2643, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2644, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2646, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2652, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(2653, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2654, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2656, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ _(2657, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_CONST) \ _(2658, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ _(2659, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ _(2661, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2663, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_CONST_TMPVARCV) \ - _(2664, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_CONST_TMPVARCV) \ - _(2666, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_CONST_TMPVARCV) \ - _(2667, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ - _(2668, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2669, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2671, ZEND_SUB_LONG_NO_OVERFLOW_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) \ + _(2667, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(2668, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2669, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2671, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2673, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_CONST_TMPVARCV) \ + _(2674, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_CONST_TMPVARCV) \ + _(2676, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_CONST_TMPVARCV) \ + _(2677, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ + _(2678, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2679, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2681, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ _(2682, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ _(2683, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ _(2684, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ _(2686, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2688, ZEND_SUB_LONG_SPEC_CONST_TMPVARCV) \ - _(2689, ZEND_SUB_LONG_SPEC_CONST_TMPVARCV) \ - _(2691, ZEND_SUB_LONG_SPEC_CONST_TMPVARCV) \ - _(2692, ZEND_SUB_LONG_SPEC_TMPVARCV_CONST) \ - _(2693, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2694, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2696, ZEND_SUB_LONG_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) \ + _(2692, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ + _(2693, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2694, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2696, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2698, ZEND_SUB_LONG_SPEC_CONST_TMPVARCV) \ + _(2699, ZEND_SUB_LONG_SPEC_CONST_TMPVARCV) \ + _(2701, ZEND_SUB_LONG_SPEC_CONST_TMPVARCV) \ + _(2702, ZEND_SUB_LONG_SPEC_TMPVARCV_CONST) \ + _(2703, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2704, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2706, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ _(2707, ZEND_SUB_LONG_SPEC_TMPVARCV_CONST) \ _(2708, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ _(2709, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ _(2711, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2713, ZEND_SUB_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(2714, ZEND_SUB_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(2716, ZEND_SUB_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(2717, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2718, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2719, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2721, ZEND_SUB_DOUBLE_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) \ + _(2717, ZEND_SUB_LONG_SPEC_TMPVARCV_CONST) \ + _(2718, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2719, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2721, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2723, ZEND_SUB_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(2724, ZEND_SUB_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(2726, ZEND_SUB_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(2727, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(2728, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2729, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2731, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ _(2732, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_CONST) \ _(2733, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ _(2734, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ _(2736, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2742, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ - _(2743, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2744, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2746, ZEND_MUL_LONG_NO_OVERFLOW_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) \ + _(2742, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(2743, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2744, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2746, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2752, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ + _(2753, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2754, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2756, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ _(2757, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ _(2758, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ _(2759, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ _(2761, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2767, ZEND_MUL_LONG_SPEC_TMPVARCV_CONST) \ - _(2768, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2769, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2771, ZEND_MUL_LONG_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) \ + _(2767, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ + _(2768, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2769, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2771, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2777, ZEND_MUL_LONG_SPEC_TMPVARCV_CONST) \ + _(2778, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2779, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2781, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ _(2782, ZEND_MUL_LONG_SPEC_TMPVARCV_CONST) \ _(2783, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ _(2784, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ _(2786, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2792, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2793, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2794, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2796, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(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) \ + _(2792, ZEND_MUL_LONG_SPEC_TMPVARCV_CONST) \ + _(2793, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2794, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2796, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2802, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(2803, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2804, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2806, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ _(2807, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_CONST) \ _(2808, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ _(2809, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ _(2811, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2827, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(2828, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2829, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2830, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2831, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2832, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2833, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2834, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2835, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2839, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2840, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2841, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2842, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(2843, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2844, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2845, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2846, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2847, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2848, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2849, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2850, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2854, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2855, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2856, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2872, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(2873, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2874, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2875, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2876, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2877, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2878, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2879, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2880, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2884, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2885, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2886, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2902, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2903, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2904, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2905, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2906, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2907, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2908, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2909, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2910, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2914, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2915, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2916, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2917, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2918, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2919, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2920, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2921, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2922, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2923, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2924, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2925, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2929, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2930, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2931, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2947, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2948, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2949, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2950, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2951, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2952, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2953, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2954, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2955, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2959, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2960, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2961, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2977, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(2978, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2979, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2980, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2981, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2982, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2983, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2984, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2985, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2989, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2990, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2991, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2992, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(2993, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2994, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2995, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2996, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2997, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2998, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2999, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3000, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3004, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3005, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3006, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3022, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(3023, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3024, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3025, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3026, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3027, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3028, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3029, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3030, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3034, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3035, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3036, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3052, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3053, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3054, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3055, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3056, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3057, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3058, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3059, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3060, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3064, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3065, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3066, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3067, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3068, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3069, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3070, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3071, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3072, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3073, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3074, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3075, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3079, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3080, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3081, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3097, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3098, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3099, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3100, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3101, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3102, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3103, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3104, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3105, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3109, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3110, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3111, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3112, ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST) \ - _(3113, ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3114, ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3115, ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST) \ - _(3116, ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3117, ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3118, ZEND_IS_IDENTICAL_NOTHROW_SPEC_CV_CONST) \ - _(3122, ZEND_IS_IDENTICAL_NOTHROW_SPEC_CV_CV) \ - _(3123, ZEND_IS_NOT_IDENTICAL_NOTHROW_SPEC_CV_CONST) \ - _(3127, ZEND_IS_NOT_IDENTICAL_NOTHROW_SPEC_CV_CV) \ - _(3131, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV) \ - _(3132, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3133, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3134, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV) \ - _(3135, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3136, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3140, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV) \ - _(3141, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3142, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3143, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST) \ - _(3144, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3145, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3146, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3147, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3148, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3149, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3150, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3151, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3155, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3156, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3157, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3158, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST) \ - _(3159, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3160, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3161, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3162, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3163, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3164, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3165, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3166, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3170, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3171, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3172, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3188, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST) \ - _(3189, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3190, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3191, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3192, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3193, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3194, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3195, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3196, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3200, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3201, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3202, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3206, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(3207, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3208, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3209, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(3210, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3211, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3215, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(3216, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3217, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3218, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3219, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3220, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3221, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3222, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3223, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3224, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3225, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3226, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3230, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3231, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3232, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3233, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3234, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3235, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3236, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3237, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3238, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3239, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3240, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3241, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3245, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3246, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3247, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3263, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3264, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3265, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3266, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3267, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3268, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3269, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3270, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3271, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3275, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3276, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3277, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3281, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV) \ - _(3282, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3283, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3284, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV) \ - _(3285, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3286, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3290, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV) \ - _(3291, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3292, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3293, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(3294, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3295, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3296, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3297, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3298, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3299, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3300, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3301, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3305, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3306, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3307, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3308, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(3309, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3310, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3311, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3312, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3313, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3314, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3315, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3316, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3320, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3321, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3322, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3338, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(3339, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3340, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3341, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3342, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3343, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3344, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3345, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3346, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3350, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3351, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3352, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3356, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(3357, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3358, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3359, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(3360, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3361, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3365, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(3366, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3367, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3368, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3369, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3370, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3371, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3372, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3373, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3374, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3375, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3376, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3380, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3381, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3382, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3383, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3384, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3385, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3386, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3387, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3388, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3389, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3390, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3391, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3395, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3396, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3397, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3413, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3414, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3415, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3416, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3417, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3418, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3419, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3420, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3421, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3425, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3426, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3427, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3428, ZEND_PRE_INC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_UNUSED) \ - _(3429, ZEND_PRE_INC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_USED) \ - _(3430, ZEND_PRE_INC_LONG_SPEC_CV_RETVAL_UNUSED) \ - _(3431, ZEND_PRE_INC_LONG_SPEC_CV_RETVAL_USED) \ - _(3432, ZEND_PRE_DEC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_UNUSED) \ - _(3433, ZEND_PRE_DEC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_USED) \ - _(3434, ZEND_PRE_DEC_LONG_SPEC_CV_RETVAL_UNUSED) \ - _(3435, ZEND_PRE_DEC_LONG_SPEC_CV_RETVAL_USED) \ - _(3436, ZEND_POST_INC_LONG_NO_OVERFLOW_SPEC_CV) \ - _(3437, ZEND_POST_INC_LONG_SPEC_CV) \ - _(3438, ZEND_POST_DEC_LONG_NO_OVERFLOW_SPEC_CV) \ - _(3439, ZEND_POST_DEC_LONG_SPEC_CV) \ - _(3440, ZEND_QM_ASSIGN_LONG_SPEC_CONST) \ - _(3441, ZEND_QM_ASSIGN_LONG_SPEC_TMPVARCV) \ - _(3442, ZEND_QM_ASSIGN_LONG_SPEC_TMPVARCV) \ - _(3444, ZEND_QM_ASSIGN_LONG_SPEC_TMPVARCV) \ - _(3445, ZEND_QM_ASSIGN_DOUBLE_SPEC_CONST) \ - _(3446, ZEND_QM_ASSIGN_DOUBLE_SPEC_TMPVARCV) \ - _(3447, ZEND_QM_ASSIGN_DOUBLE_SPEC_TMPVARCV) \ - _(3449, ZEND_QM_ASSIGN_DOUBLE_SPEC_TMPVARCV) \ - _(3450, ZEND_QM_ASSIGN_NOREF_SPEC_CONST) \ - _(3451, ZEND_QM_ASSIGN_NOREF_SPEC_TMPVARCV) \ - _(3452, ZEND_QM_ASSIGN_NOREF_SPEC_TMPVARCV) \ - _(3454, ZEND_QM_ASSIGN_NOREF_SPEC_TMPVARCV) \ - _(3456, ZEND_FETCH_DIM_R_INDEX_SPEC_CONST_TMPVARCV) \ - _(3457, ZEND_FETCH_DIM_R_INDEX_SPEC_CONST_TMPVARCV) \ - _(3459, ZEND_FETCH_DIM_R_INDEX_SPEC_CONST_TMPVARCV) \ - _(3460, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_CONST) \ - _(3461, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ - _(3462, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ - _(3464, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ - _(3465, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_CONST) \ - _(3466, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ - _(3467, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ - _(3469, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ - _(3475, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_CONST) \ - _(3476, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_TMPVARCV) \ - _(3477, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_TMPVARCV) \ - _(3479, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_TMPVARCV) \ - _(3482, ZEND_SEND_VAR_SIMPLE_SPEC_VAR) \ - _(3484, ZEND_SEND_VAR_SIMPLE_SPEC_CV) \ - _(3487, ZEND_SEND_VAR_EX_SIMPLE_SPEC_VAR_UNUSED) \ - _(3489, ZEND_SEND_VAR_EX_SIMPLE_SPEC_CV_UNUSED) \ - _(3490, ZEND_SEND_VAL_SIMPLE_SPEC_CONST) \ - _(3491, ZEND_SEND_VAL_EX_SIMPLE_SPEC_CONST) \ - _(3492, ZEND_FE_FETCH_R_SIMPLE_SPEC_VAR_CV_RETVAL_UNUSED) \ - _(3493, ZEND_FE_FETCH_R_SIMPLE_SPEC_VAR_CV_RETVAL_USED) \ - _(3493+1, ZEND_NULL) + _(2817, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(2818, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2819, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2821, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2837, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(2838, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(2839, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(2840, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2841, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2842, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2843, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2844, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2845, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2849, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2850, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2851, 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_LONG_SPEC_TMPVARCV_CONST) \ + _(2883, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(2884, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(2885, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2886, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2887, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2888, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2889, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2890, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2894, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2895, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2896, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2912, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(2913, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(2914, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(2915, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2916, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2917, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2918, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2919, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2920, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2924, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2925, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2926, 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_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(2958, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(2959, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(2960, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2961, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2962, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2963, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2964, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2965, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2969, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2970, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2971, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2987, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(2988, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(2989, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(2990, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2991, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2992, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2993, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2994, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2995, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2999, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3000, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3001, 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_LONG_SPEC_TMPVARCV_CONST) \ + _(3033, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3034, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3035, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3036, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3037, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3038, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3039, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3040, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3044, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3045, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3046, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3062, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3063, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3064, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3065, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3066, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3067, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3068, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3069, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3070, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3074, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3075, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3076, 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) \ + _(3107, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3108, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3109, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3110, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3111, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3112, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3113, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3114, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3115, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3119, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3120, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3121, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3122, ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST) \ + _(3123, ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3124, ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3125, ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST) \ + _(3126, ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3127, ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3128, ZEND_IS_IDENTICAL_NOTHROW_SPEC_CV_CONST) \ + _(3132, ZEND_IS_IDENTICAL_NOTHROW_SPEC_CV_CV) \ + _(3133, ZEND_IS_NOT_IDENTICAL_NOTHROW_SPEC_CV_CONST) \ + _(3137, ZEND_IS_NOT_IDENTICAL_NOTHROW_SPEC_CV_CV) \ + _(3141, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV) \ + _(3142, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3143, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3144, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV) \ + _(3145, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3146, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3150, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV) \ + _(3151, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3152, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3153, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST) \ + _(3154, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3155, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3156, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3157, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3158, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3159, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3160, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3161, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3165, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3166, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3167, 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) \ + _(3198, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST) \ + _(3199, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3200, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3201, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3202, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3203, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3204, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3205, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3206, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3210, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3211, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3212, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3216, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(3217, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3218, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3219, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(3220, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3221, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3225, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(3226, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3227, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3228, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3229, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3230, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3231, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3232, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3233, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3234, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3235, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3236, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3240, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3241, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3242, 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) \ + _(3273, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3274, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3275, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3276, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3277, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3278, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3279, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3280, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3281, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3285, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3286, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3287, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3291, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV) \ + _(3292, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3293, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3294, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV) \ + _(3295, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3296, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3300, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV) \ + _(3301, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3302, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3303, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(3304, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3305, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3306, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3307, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3308, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3309, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3310, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3311, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3315, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3316, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3317, 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) \ + _(3348, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(3349, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3350, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3351, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3352, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3353, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3354, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3355, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3356, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3360, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3361, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3362, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3366, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(3367, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3368, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3369, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(3370, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3371, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3375, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(3376, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3377, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3378, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3379, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3380, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3381, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3382, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3383, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3384, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3385, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3386, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3390, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3391, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3392, 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) \ + _(3423, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3424, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3425, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3426, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3427, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3428, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3429, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3430, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3431, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3435, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3436, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3437, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3438, ZEND_PRE_INC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_UNUSED) \ + _(3439, ZEND_PRE_INC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_USED) \ + _(3440, ZEND_PRE_INC_LONG_SPEC_CV_RETVAL_UNUSED) \ + _(3441, ZEND_PRE_INC_LONG_SPEC_CV_RETVAL_USED) \ + _(3442, ZEND_PRE_DEC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_UNUSED) \ + _(3443, ZEND_PRE_DEC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_USED) \ + _(3444, ZEND_PRE_DEC_LONG_SPEC_CV_RETVAL_UNUSED) \ + _(3445, ZEND_PRE_DEC_LONG_SPEC_CV_RETVAL_USED) \ + _(3446, ZEND_POST_INC_LONG_NO_OVERFLOW_SPEC_CV) \ + _(3447, ZEND_POST_INC_LONG_SPEC_CV) \ + _(3448, ZEND_POST_DEC_LONG_NO_OVERFLOW_SPEC_CV) \ + _(3449, ZEND_POST_DEC_LONG_SPEC_CV) \ + _(3450, ZEND_QM_ASSIGN_LONG_SPEC_CONST) \ + _(3451, ZEND_QM_ASSIGN_LONG_SPEC_TMPVARCV) \ + _(3452, ZEND_QM_ASSIGN_LONG_SPEC_TMPVARCV) \ + _(3454, ZEND_QM_ASSIGN_LONG_SPEC_TMPVARCV) \ + _(3455, ZEND_QM_ASSIGN_DOUBLE_SPEC_CONST) \ + _(3456, ZEND_QM_ASSIGN_DOUBLE_SPEC_TMPVARCV) \ + _(3457, ZEND_QM_ASSIGN_DOUBLE_SPEC_TMPVARCV) \ + _(3459, ZEND_QM_ASSIGN_DOUBLE_SPEC_TMPVARCV) \ + _(3460, ZEND_QM_ASSIGN_NOREF_SPEC_CONST) \ + _(3461, ZEND_QM_ASSIGN_NOREF_SPEC_TMPVARCV) \ + _(3462, ZEND_QM_ASSIGN_NOREF_SPEC_TMPVARCV) \ + _(3464, ZEND_QM_ASSIGN_NOREF_SPEC_TMPVARCV) \ + _(3466, ZEND_FETCH_DIM_R_INDEX_SPEC_CONST_TMPVARCV) \ + _(3467, ZEND_FETCH_DIM_R_INDEX_SPEC_CONST_TMPVARCV) \ + _(3469, ZEND_FETCH_DIM_R_INDEX_SPEC_CONST_TMPVARCV) \ + _(3470, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_CONST) \ + _(3471, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ + _(3472, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ + _(3474, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ + _(3475, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_CONST) \ + _(3476, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ + _(3477, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ + _(3479, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ + _(3485, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_CONST) \ + _(3486, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_TMPVARCV) \ + _(3487, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_TMPVARCV) \ + _(3489, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_TMPVARCV) \ + _(3492, ZEND_SEND_VAR_SIMPLE_SPEC_VAR) \ + _(3494, ZEND_SEND_VAR_SIMPLE_SPEC_CV) \ + _(3497, ZEND_SEND_VAR_EX_SIMPLE_SPEC_VAR_UNUSED) \ + _(3499, ZEND_SEND_VAR_EX_SIMPLE_SPEC_CV_UNUSED) \ + _(3500, ZEND_SEND_VAL_SIMPLE_SPEC_CONST) \ + _(3501, ZEND_SEND_VAL_EX_SIMPLE_SPEC_CONST) \ + _(3502, ZEND_FE_FETCH_R_SIMPLE_SPEC_VAR_CV_RETVAL_UNUSED) \ + _(3503, ZEND_FE_FETCH_R_SIMPLE_SPEC_VAR_CV_RETVAL_USED) \ + _(3503+1, ZEND_NULL) diff --git a/Zend/zend_vm_opcodes.c b/Zend/zend_vm_opcodes.c index cb70e8ccd86fa..b695b707d9a71 100644 --- a/Zend/zend_vm_opcodes.c +++ b/Zend/zend_vm_opcodes.c @@ -22,7 +22,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", @@ -235,9 +235,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, @@ -450,6 +452,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 7aae4d0e55f19..7a86c396bd864 100644 --- a/Zend/zend_vm_opcodes.h +++ b/Zend/zend_vm_opcodes.h @@ -332,7 +332,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 77fc8c89cdf40..40bd29ae99a78 100644 --- a/configure.ac +++ b/configure.ac @@ -1779,6 +1779,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 f4134212bc4eb..7a8d0442485fa 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -26,7 +26,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" @@ -50,6 +52,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" @@ -2014,6 +2017,219 @@ static int check_persistent_script_access(zend_persistent_script *persistent_scr } } +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 524a6f5e12139..7f14da4493d97 100644 --- a/ext/opcache/ZendAccelerator.h +++ b/ext/opcache/ZendAccelerator.h @@ -336,6 +336,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 3ffb669e84742..42236f0af90fe 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -303,6 +303,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: @@ -388,6 +389,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 */ @@ -867,6 +869,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 1c5cab899e783..0bcf972447a93 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -10078,7 +10078,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 bed5ab59992ed..da1ac594e5e95 100644 --- a/ext/opcache/jit/zend_jit_vm_helpers.c +++ b/ext/opcache/jit/zend_jit_vm_helpers.c @@ -1059,7 +1059,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 403f0aa6efbfe..1efdc57bca4da 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_lazy_objects.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 de087f1f1057ad0655cc8ce28cbe4a54849b9ff5 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Tue, 6 Jan 2026 17:33:40 +0100 Subject: [PATCH 02/35] 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 4e414a95172fe..845b3d5813ee0 100644 --- a/Zend/Optimizer/zend_call_graph.c +++ b/Zend/Optimizer/zend_call_graph.c @@ -151,10 +151,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 9875c734cb6df24d961f9d6c23af826936d60aae Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Tue, 6 Jan 2026 17:33:54 +0100 Subject: [PATCH 03/35] 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/35] 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 d1441e571fc2f..1fdebb06db183 100644 --- a/Zend/zend_closures.c +++ b/Zend/zend_closures.c @@ -87,6 +87,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 7a8d0442485fa..dabde4c707e9e 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -2175,7 +2175,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 3248ef6b19c9a1ea423f9900be687adf2e03b56b Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Tue, 6 Jan 2026 17:55:56 +0100 Subject: [PATCH 05/35] 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 feb0a5d7714714586ac37004c879a1c138887fe0 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Tue, 6 Jan 2026 18:17:48 +0100 Subject: [PATCH 06/35] 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 3543378a00c98bbe7b9321145442ec7ff37cc62b Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Tue, 6 Jan 2026 18:36:37 +0100 Subject: [PATCH 07/35] 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 831c32440949d5b6844d0e842ab9ba31c6e1da80 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 7 Jan 2026 14:30:06 +0100 Subject: [PATCH 08/35] 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 7c24f728a905e..fc7aa5f41f849 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -45,7 +45,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 53b1ac6baf0a3..d664d17be6074 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 0f2836dd3eff08ebd881ffced98cb1827c9f2e2c Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 7 Jan 2026 15:03:55 +0100 Subject: [PATCH 09/35] 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 b2704edc189c52033c3a398ea052e1a43df714b8 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 7 Jan 2026 15:04:02 +0100 Subject: [PATCH 10/35] 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 5ae98cf64b469951ff9dc984167f5f07a42697bf Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 7 Jan 2026 15:04:53 +0100 Subject: [PATCH 11/35] 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 8c44547e81c37..eb1e1effe0f99 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1456,7 +1456,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 ceaa53b4212523fa8a724c80f8b5d318f2d8ace5 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 7 Jan 2026 15:06:29 +0100 Subject: [PATCH 12/35] 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 b406ec05e4720..c0c39d2c11141 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -307,6 +307,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 0469a31c9e6eeeccb912fae776cd07ace3fbe639 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Thu, 8 Jan 2026 10:36:07 +0100 Subject: [PATCH 13/35] 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 fcf8451cb7fcb..b88481fa6bddc 100644 --- a/Zend/zend_string.h +++ b/Zend/zend_string.h @@ -639,6 +639,7 @@ EMPTY_SWITCH_DEFAULT_CASE() _(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 3c10e460ba1dc3ccadd2ce7f79e5a1f5ea81c05e Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Thu, 8 Jan 2026 10:37:49 +0100 Subject: [PATCH 14/35] 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/35] 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 2e3c0df513bb650914505df3e0dd8a3b978e98a2 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Thu, 15 Jan 2026 13:07:22 +0100 Subject: [PATCH 16/35] 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 2f62cfac0173ad9c360b5598933d206fe35bfae2 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Thu, 15 Jan 2026 16:53:04 +0100 Subject: [PATCH 17/35] 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 e6d33c0b81dc3ad14fc91f5ea6b9e5093e897ba2 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Thu, 15 Jan 2026 16:54:54 +0100 Subject: [PATCH 18/35] 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 146906f463bb4..93ded14bbc44b 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -6851,11 +6851,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 1441fb89f9db00d7e57ae915abd3a967d6b03e45 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Fri, 16 Jan 2026 12:04:00 +0100 Subject: [PATCH 19/35] 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 9ee6da268a45ed04b5273ecb990735437dbd2ec5 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Fri, 16 Jan 2026 12:04:08 +0100 Subject: [PATCH 20/35] 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 dabde4c707e9e..2a72e970b9abc 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -2050,7 +2050,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 @@ -2059,7 +2059,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 2827a81e4844cb8b11a6b46849746463fe203b27 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Fri, 16 Jan 2026 12:44:02 +0100 Subject: [PATCH 21/35] 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 786bdefab594c6d8d2056a7379b4a3c0a57c9ed8 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Fri, 16 Jan 2026 12:54:24 +0100 Subject: [PATCH 22/35] 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 acb81f486007353428a783a262089f3d03b0c1ad Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Fri, 16 Jan 2026 13:00:46 +0100 Subject: [PATCH 23/35] 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 1fc5632ad10b518a60296dd33b25186953123bd4 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Fri, 16 Jan 2026 13:00:54 +0100 Subject: [PATCH 24/35] 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 e34424a935dbef07eec2f2dbd0f892393556b880 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Fri, 16 Jan 2026 13:03:54 +0100 Subject: [PATCH 25/35] 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 2a72e970b9abc..ebfdce43841bb 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -2071,7 +2071,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 7f14da4493d97..97cdadfad5f02 100644 --- a/ext/opcache/ZendAccelerator.h +++ b/ext/opcache/ZendAccelerator.h @@ -336,7 +336,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 e49dc62b5180257caeed95b6deb6ab6cf8c4dbfa Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Fri, 16 Jan 2026 13:24:39 +0100 Subject: [PATCH 26/35] 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 fd28bbf0e540d9b8060716871f3f7aa708d34ca5 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Fri, 16 Jan 2026 13:29:31 +0100 Subject: [PATCH 27/35] 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 fc7aa5f41f849..765095fca36f5 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1231,10 +1231,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 ebec845a3d8c4..104b9c49abe20 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -111,8 +111,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 2ee213a9caaffbc7c0594dfa7a7565eb3ba70af2 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Fri, 16 Jan 2026 15:40:29 +0100 Subject: [PATCH 28/35] 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 65fd2c67da3f04b608fe7c3630cfffb483a66aef Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Mon, 19 Jan 2026 12:56:25 +0100 Subject: [PATCH 29/35] Support PFAs in array_map() optimization --- Zend/zend_compile.c | 39 ++----- .../array_map_foreach_optimization_006.phpt | 84 ++++++++++++++ .../array_map_foreach_optimization_007.phpt | 109 ++++++++++++++++++ 3 files changed, 202 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 93ded14bbc44b..dd3ba8bbeaba1 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -105,6 +105,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); +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) { @@ -5165,26 +5167,7 @@ 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 && zend_string_equals_literal_ci(zend_ast_get_str(callback->child[0]), "assert")) { + if (callback->kind != ZEND_AST_CALL && callback->kind != ZEND_AST_STATIC_CALL) { return FAILURE; } @@ -5192,16 +5175,12 @@ static zend_result zend_compile_func_array_map(znode *result, zend_ast_list *arg value.op_type = IS_TMP_VAR; value.u.op.var = get_temporary_variable(); - zend_ast_list *callback_args = zend_ast_get_list(((zend_ast_fcc*)args_ast)->args); - zend_ast *call_args = zend_ast_create_list(0, ZEND_AST_ARG_LIST); - for (uint32_t i = 0; i < callback_args->children; i++) { - zend_ast *child = callback_args->child[i]; - if (child->kind == ZEND_AST_PLACEHOLDER_ARG) { - call_args = zend_ast_list_add(call_args, zend_ast_create_znode(&value)); - } else { - ZEND_ASSERT(0 && "not implemented"); - call_args = zend_ast_list_add(call_args, child); - } + 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 8514af422d0d7caae5e67d469a492f1e98cbe5c7 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Mon, 19 Jan 2026 13:10:41 +0100 Subject: [PATCH 30/35] 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 0881a169dfa7d..c4d73bf481e88 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) class_name, space, get_active_function_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_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 c1ccbf13666a5..6b87d5b90c01f 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -1560,13 +1560,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 2c0fc3b495a69..d23645c819346 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -8892,9 +8892,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 c0c39d2c11141..64e8a807e61c8 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -6218,9 +6218,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); } } @@ -62028,9 +62029,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 c8f29948da81c3777c30088706bbe2640fee5c4a Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Mon, 19 Jan 2026 13:20:02 +0100 Subject: [PATCH 31/35] WS --- Zend/zend_API.h | 1 - 1 file changed, 1 deletion(-) diff --git a/Zend/zend_API.h b/Zend/zend_API.h index 6b87d5b90c01f..233e4be6a04a1 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -1575,7 +1575,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 dd1eec24d72269c78c94ac99b54bc162bdaf60b3 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Tue, 20 Jan 2026 15:55:08 +0100 Subject: [PATCH 32/35] 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 0a88cefffde02946f5ea4ccb68c62cb26e0c5262 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Tue, 20 Jan 2026 15:55:16 +0100 Subject: [PATCH 33/35] 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 dce2e7865236007d848e5a2ead2e7e17fa944750 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 21 Jan 2026 11:40:38 +0100 Subject: [PATCH 34/35] 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 1f7054a3be3477507fda51cbb1866b55db1a6a99 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 21 Jan 2026 11:40:49 +0100 Subject: [PATCH 35/35] 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;