From bdd93e33ed1c843dcd416a4dc4d062afeaefcf64 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Thu, 14 Nov 2024 22:01:47 +0100 Subject: [PATCH 1/4] Fix GH-16799: Assertion failure at Zend/zend_vm_execute.h:7469 zend_is_callable_ex() can unfortunately emit a deprecation, and then a user error handler can throw an exception. This causes an assert failure at ZEND_VM_NEXT_OPCODE(). We fix this by checking if there's an exception after zend_is_callable_ex(). --- Zend/tests/gh16799.phpt | 21 +++++++++++++++++++++ Zend/zend_vm_def.h | 7 +++++++ Zend/zend_vm_execute.h | 21 +++++++++++++++++++++ 3 files changed, 49 insertions(+) create mode 100644 Zend/tests/gh16799.phpt diff --git a/Zend/tests/gh16799.phpt b/Zend/tests/gh16799.phpt new file mode 100644 index 0000000000000..db2e31148eee2 --- /dev/null +++ b/Zend/tests/gh16799.phpt @@ -0,0 +1,21 @@ +--TEST-- +GH-16799 (Assertion failure at Zend/zend_vm_execute.h:7469) +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Exception: Use of "static" in callables is deprecated in %s:%d +Stack trace: +#0 %s(%d): {closure}(%d, 'Use of "static"...', %s, %d) +#1 %s(%d): Test::test() +#2 {main} + thrown in %s on line %d diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 2a48b9c3713bb..f44bc24a4b468 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -3806,6 +3806,13 @@ ZEND_VM_HANDLER(118, ZEND_INIT_USER_CALL, CONST, CONST|TMPVAR|CV, NUM) SAVE_OPLINE(); function_name = GET_OP2_ZVAL_PTR(BP_VAR_R); if (zend_is_callable_ex(function_name, NULL, 0, NULL, &fcc, &error)) { + /* Deprecation can be emitted from zend_is_callable_ex(), which can + * invoke a user error handler and throw an exception. */ + if (UNEXPECTED(EG(exception))) { + FREE_OP2(); + HANDLE_EXCEPTION(); + } + ZEND_ASSERT(!error); func = fcc.function_handler; object_or_called_scope = fcc.called_scope; diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 3bc01a597fa6c..5781430351ddf 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -7022,6 +7022,13 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_USER_CALL_SPEC_CONST_CONS SAVE_OPLINE(); function_name = RT_CONSTANT(opline, opline->op2); if (zend_is_callable_ex(function_name, NULL, 0, NULL, &fcc, &error)) { + /* Deprecation can be emitted from zend_is_callable_ex(), which can + * invoke a user error handler and throw an exception. */ + if (UNEXPECTED(EG(exception))) { + + HANDLE_EXCEPTION(); + } + ZEND_ASSERT(!error); func = fcc.function_handler; object_or_called_scope = fcc.called_scope; @@ -9367,6 +9374,13 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_USER_CALL_SPEC_CONST_TMPV SAVE_OPLINE(); function_name = _get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC); if (zend_is_callable_ex(function_name, NULL, 0, NULL, &fcc, &error)) { + /* Deprecation can be emitted from zend_is_callable_ex(), which can + * invoke a user error handler and throw an exception. */ + if (UNEXPECTED(EG(exception))) { + zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); + HANDLE_EXCEPTION(); + } + ZEND_ASSERT(!error); func = fcc.function_handler; object_or_called_scope = fcc.called_scope; @@ -11741,6 +11755,13 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_USER_CALL_SPEC_CONST_CV_H SAVE_OPLINE(); function_name = _get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC); if (zend_is_callable_ex(function_name, NULL, 0, NULL, &fcc, &error)) { + /* Deprecation can be emitted from zend_is_callable_ex(), which can + * invoke a user error handler and throw an exception. */ + if (UNEXPECTED(EG(exception))) { + + HANDLE_EXCEPTION(); + } + ZEND_ASSERT(!error); func = fcc.function_handler; object_or_called_scope = fcc.called_scope; From 2c3531d5baa6667c59875dba86f2cef7a7806d5c Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Thu, 14 Nov 2024 22:07:38 +0100 Subject: [PATCH 2/4] Merge check --- Zend/zend_vm_def.h | 12 ++++-------- Zend/zend_vm_execute.h | 36 ++++++++++++------------------------ 2 files changed, 16 insertions(+), 32 deletions(-) diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index f44bc24a4b468..743df3b86292c 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -3806,13 +3806,6 @@ ZEND_VM_HANDLER(118, ZEND_INIT_USER_CALL, CONST, CONST|TMPVAR|CV, NUM) SAVE_OPLINE(); function_name = GET_OP2_ZVAL_PTR(BP_VAR_R); if (zend_is_callable_ex(function_name, NULL, 0, NULL, &fcc, &error)) { - /* Deprecation can be emitted from zend_is_callable_ex(), which can - * invoke a user error handler and throw an exception. */ - if (UNEXPECTED(EG(exception))) { - FREE_OP2(); - HANDLE_EXCEPTION(); - } - ZEND_ASSERT(!error); func = fcc.function_handler; object_or_called_scope = fcc.called_scope; @@ -3834,7 +3827,10 @@ ZEND_VM_HANDLER(118, ZEND_INIT_USER_CALL, CONST, CONST|TMPVAR|CV, NUM) } FREE_OP2(); - if ((OP2_TYPE & (IS_TMP_VAR|IS_VAR)) && UNEXPECTED(EG(exception))) { + /* Deprecation can be emitted from zend_is_callable_ex(), which can + * invoke a user error handler and throw an exception; so we cannot + * rely on OP2_TYPE. */ + if (UNEXPECTED(EG(exception))) { if (call_info & ZEND_CALL_CLOSURE) { zend_object_release(ZEND_CLOSURE_OBJECT(func)); } else if (call_info & ZEND_CALL_RELEASE_THIS) { diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 5781430351ddf..cab58fa513547 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -7022,13 +7022,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_USER_CALL_SPEC_CONST_CONS SAVE_OPLINE(); function_name = RT_CONSTANT(opline, opline->op2); if (zend_is_callable_ex(function_name, NULL, 0, NULL, &fcc, &error)) { - /* Deprecation can be emitted from zend_is_callable_ex(), which can - * invoke a user error handler and throw an exception. */ - if (UNEXPECTED(EG(exception))) { - - HANDLE_EXCEPTION(); - } - ZEND_ASSERT(!error); func = fcc.function_handler; object_or_called_scope = fcc.called_scope; @@ -7049,7 +7042,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_USER_CALL_SPEC_CONST_CONS call_info |= ZEND_CALL_RELEASE_THIS | ZEND_CALL_HAS_THIS; } - if ((IS_CONST & (IS_TMP_VAR|IS_VAR)) && UNEXPECTED(EG(exception))) { + /* Deprecation can be emitted from zend_is_callable_ex(), which can + * invoke a user error handler and throw an exception; so we cannot + * rely on IS_CONST. */ + if (UNEXPECTED(EG(exception))) { if (call_info & ZEND_CALL_CLOSURE) { zend_object_release(ZEND_CLOSURE_OBJECT(func)); } else if (call_info & ZEND_CALL_RELEASE_THIS) { @@ -9374,13 +9370,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_USER_CALL_SPEC_CONST_TMPV SAVE_OPLINE(); function_name = _get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC); if (zend_is_callable_ex(function_name, NULL, 0, NULL, &fcc, &error)) { - /* Deprecation can be emitted from zend_is_callable_ex(), which can - * invoke a user error handler and throw an exception. */ - if (UNEXPECTED(EG(exception))) { - zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); - HANDLE_EXCEPTION(); - } - ZEND_ASSERT(!error); func = fcc.function_handler; object_or_called_scope = fcc.called_scope; @@ -9402,7 +9391,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_USER_CALL_SPEC_CONST_TMPV } zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); - if (((IS_TMP_VAR|IS_VAR) & (IS_TMP_VAR|IS_VAR)) && UNEXPECTED(EG(exception))) { + /* Deprecation can be emitted from zend_is_callable_ex(), which can + * invoke a user error handler and throw an exception; so we cannot + * rely on (IS_TMP_VAR|IS_VAR). */ + if (UNEXPECTED(EG(exception))) { if (call_info & ZEND_CALL_CLOSURE) { zend_object_release(ZEND_CLOSURE_OBJECT(func)); } else if (call_info & ZEND_CALL_RELEASE_THIS) { @@ -11755,13 +11747,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_USER_CALL_SPEC_CONST_CV_H SAVE_OPLINE(); function_name = _get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC); if (zend_is_callable_ex(function_name, NULL, 0, NULL, &fcc, &error)) { - /* Deprecation can be emitted from zend_is_callable_ex(), which can - * invoke a user error handler and throw an exception. */ - if (UNEXPECTED(EG(exception))) { - - HANDLE_EXCEPTION(); - } - ZEND_ASSERT(!error); func = fcc.function_handler; object_or_called_scope = fcc.called_scope; @@ -11782,7 +11767,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_USER_CALL_SPEC_CONST_CV_H call_info |= ZEND_CALL_RELEASE_THIS | ZEND_CALL_HAS_THIS; } - if ((IS_CV & (IS_TMP_VAR|IS_VAR)) && UNEXPECTED(EG(exception))) { + /* Deprecation can be emitted from zend_is_callable_ex(), which can + * invoke a user error handler and throw an exception; so we cannot + * rely on IS_CV. */ + if (UNEXPECTED(EG(exception))) { if (call_info & ZEND_CALL_CLOSURE) { zend_object_release(ZEND_CLOSURE_OBJECT(func)); } else if (call_info & ZEND_CALL_RELEASE_THIS) { From 887e5a646b86cfa8b50920914291a6acdcfd93a9 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Thu, 14 Nov 2024 23:47:28 +0100 Subject: [PATCH 3/4] Reuse exception block when possible --- Zend/zend_vm_def.h | 14 ++++++++++---- Zend/zend_vm_execute.h | 42 ++++++++++++++++++++++++++++++------------ 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 743df3b86292c..580e2d10f215b 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -3806,6 +3806,15 @@ ZEND_VM_HANDLER(118, ZEND_INIT_USER_CALL, CONST, CONST|TMPVAR|CV, NUM) SAVE_OPLINE(); function_name = GET_OP2_ZVAL_PTR(BP_VAR_R); if (zend_is_callable_ex(function_name, NULL, 0, NULL, &fcc, &error)) { + /* Deprecation can be emitted from zend_is_callable_ex(), which can + * invoke a user error handler and throw an exception. + * For the CONST and CV case we reuse the same exception block below + * to make sure we don't increase VM size too much. */ + if (!(OP2_TYPE & (IS_TMP_VAR|IS_VAR)) && UNEXPECTED(EG(exception))) { + FREE_OP2(); + HANDLE_EXCEPTION(); + } + ZEND_ASSERT(!error); func = fcc.function_handler; object_or_called_scope = fcc.called_scope; @@ -3827,10 +3836,7 @@ ZEND_VM_HANDLER(118, ZEND_INIT_USER_CALL, CONST, CONST|TMPVAR|CV, NUM) } FREE_OP2(); - /* Deprecation can be emitted from zend_is_callable_ex(), which can - * invoke a user error handler and throw an exception; so we cannot - * rely on OP2_TYPE. */ - if (UNEXPECTED(EG(exception))) { + if ((OP2_TYPE & (IS_TMP_VAR|IS_VAR)) && UNEXPECTED(EG(exception))) { if (call_info & ZEND_CALL_CLOSURE) { zend_object_release(ZEND_CLOSURE_OBJECT(func)); } else if (call_info & ZEND_CALL_RELEASE_THIS) { diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index cab58fa513547..ba345f3ab25bc 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -7022,6 +7022,15 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_USER_CALL_SPEC_CONST_CONS SAVE_OPLINE(); function_name = RT_CONSTANT(opline, opline->op2); if (zend_is_callable_ex(function_name, NULL, 0, NULL, &fcc, &error)) { + /* Deprecation can be emitted from zend_is_callable_ex(), which can + * invoke a user error handler and throw an exception. + * For the CONST and CV case we reuse the same exception block below + * to make sure we don't increase VM size too much. */ + if (!(IS_CONST & (IS_TMP_VAR|IS_VAR)) && UNEXPECTED(EG(exception))) { + + HANDLE_EXCEPTION(); + } + ZEND_ASSERT(!error); func = fcc.function_handler; object_or_called_scope = fcc.called_scope; @@ -7042,10 +7051,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_USER_CALL_SPEC_CONST_CONS call_info |= ZEND_CALL_RELEASE_THIS | ZEND_CALL_HAS_THIS; } - /* Deprecation can be emitted from zend_is_callable_ex(), which can - * invoke a user error handler and throw an exception; so we cannot - * rely on IS_CONST. */ - if (UNEXPECTED(EG(exception))) { + if ((IS_CONST & (IS_TMP_VAR|IS_VAR)) && UNEXPECTED(EG(exception))) { if (call_info & ZEND_CALL_CLOSURE) { zend_object_release(ZEND_CLOSURE_OBJECT(func)); } else if (call_info & ZEND_CALL_RELEASE_THIS) { @@ -9370,6 +9376,15 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_USER_CALL_SPEC_CONST_TMPV SAVE_OPLINE(); function_name = _get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC); if (zend_is_callable_ex(function_name, NULL, 0, NULL, &fcc, &error)) { + /* Deprecation can be emitted from zend_is_callable_ex(), which can + * invoke a user error handler and throw an exception. + * For the CONST and CV case we reuse the same exception block below + * to make sure we don't increase VM size too much. */ + if (!((IS_TMP_VAR|IS_VAR) & (IS_TMP_VAR|IS_VAR)) && UNEXPECTED(EG(exception))) { + zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); + HANDLE_EXCEPTION(); + } + ZEND_ASSERT(!error); func = fcc.function_handler; object_or_called_scope = fcc.called_scope; @@ -9391,10 +9406,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_USER_CALL_SPEC_CONST_TMPV } zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); - /* Deprecation can be emitted from zend_is_callable_ex(), which can - * invoke a user error handler and throw an exception; so we cannot - * rely on (IS_TMP_VAR|IS_VAR). */ - if (UNEXPECTED(EG(exception))) { + if (((IS_TMP_VAR|IS_VAR) & (IS_TMP_VAR|IS_VAR)) && UNEXPECTED(EG(exception))) { if (call_info & ZEND_CALL_CLOSURE) { zend_object_release(ZEND_CLOSURE_OBJECT(func)); } else if (call_info & ZEND_CALL_RELEASE_THIS) { @@ -11747,6 +11759,15 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_USER_CALL_SPEC_CONST_CV_H SAVE_OPLINE(); function_name = _get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC); if (zend_is_callable_ex(function_name, NULL, 0, NULL, &fcc, &error)) { + /* Deprecation can be emitted from zend_is_callable_ex(), which can + * invoke a user error handler and throw an exception. + * For the CONST and CV case we reuse the same exception block below + * to make sure we don't increase VM size too much. */ + if (!(IS_CV & (IS_TMP_VAR|IS_VAR)) && UNEXPECTED(EG(exception))) { + + HANDLE_EXCEPTION(); + } + ZEND_ASSERT(!error); func = fcc.function_handler; object_or_called_scope = fcc.called_scope; @@ -11767,10 +11788,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_USER_CALL_SPEC_CONST_CV_H call_info |= ZEND_CALL_RELEASE_THIS | ZEND_CALL_HAS_THIS; } - /* Deprecation can be emitted from zend_is_callable_ex(), which can - * invoke a user error handler and throw an exception; so we cannot - * rely on IS_CV. */ - if (UNEXPECTED(EG(exception))) { + if ((IS_CV & (IS_TMP_VAR|IS_VAR)) && UNEXPECTED(EG(exception))) { if (call_info & ZEND_CALL_CLOSURE) { zend_object_release(ZEND_CLOSURE_OBJECT(func)); } else if (call_info & ZEND_CALL_RELEASE_THIS) { From 26db3b41151f076ff0d59b930f626b8ca3328f8f Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Fri, 15 Nov 2024 18:54:58 +0100 Subject: [PATCH 4/4] review --- Zend/tests/gh16799.phpt | 2 +- Zend/zend_vm_def.h | 3 ++- Zend/zend_vm_execute.h | 9 ++++++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Zend/tests/gh16799.phpt b/Zend/tests/gh16799.phpt index db2e31148eee2..9348c38fc3031 100644 --- a/Zend/tests/gh16799.phpt +++ b/Zend/tests/gh16799.phpt @@ -1,5 +1,5 @@ --TEST-- -GH-16799 (Assertion failure at Zend/zend_vm_execute.h:7469) +GH-16799 (Assertion failure at Zend/zend_vm_execute.h) --FILE-- common.fn_flags & ZEND_ACC_CLOSURE) { diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index ba345f3ab25bc..39bdd2e80b8ac 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -7022,6 +7022,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_USER_CALL_SPEC_CONST_CONS SAVE_OPLINE(); function_name = RT_CONSTANT(opline, opline->op2); if (zend_is_callable_ex(function_name, NULL, 0, NULL, &fcc, &error)) { + ZEND_ASSERT(!error); + /* Deprecation can be emitted from zend_is_callable_ex(), which can * invoke a user error handler and throw an exception. * For the CONST and CV case we reuse the same exception block below @@ -7031,7 +7033,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_USER_CALL_SPEC_CONST_CONS HANDLE_EXCEPTION(); } - ZEND_ASSERT(!error); func = fcc.function_handler; object_or_called_scope = fcc.called_scope; if (func->common.fn_flags & ZEND_ACC_CLOSURE) { @@ -9376,6 +9377,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_USER_CALL_SPEC_CONST_TMPV SAVE_OPLINE(); function_name = _get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC); if (zend_is_callable_ex(function_name, NULL, 0, NULL, &fcc, &error)) { + ZEND_ASSERT(!error); + /* Deprecation can be emitted from zend_is_callable_ex(), which can * invoke a user error handler and throw an exception. * For the CONST and CV case we reuse the same exception block below @@ -9385,7 +9388,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_USER_CALL_SPEC_CONST_TMPV HANDLE_EXCEPTION(); } - ZEND_ASSERT(!error); func = fcc.function_handler; object_or_called_scope = fcc.called_scope; if (func->common.fn_flags & ZEND_ACC_CLOSURE) { @@ -11759,6 +11761,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_USER_CALL_SPEC_CONST_CV_H SAVE_OPLINE(); function_name = _get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC); if (zend_is_callable_ex(function_name, NULL, 0, NULL, &fcc, &error)) { + ZEND_ASSERT(!error); + /* Deprecation can be emitted from zend_is_callable_ex(), which can * invoke a user error handler and throw an exception. * For the CONST and CV case we reuse the same exception block below @@ -11768,7 +11772,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_USER_CALL_SPEC_CONST_CV_H HANDLE_EXCEPTION(); } - ZEND_ASSERT(!error); func = fcc.function_handler; object_or_called_scope = fcc.called_scope; if (func->common.fn_flags & ZEND_ACC_CLOSURE) {