From 334f9dbf95b5466d0a30baab9cefb4e70bd1c737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Marcin=20Brzuchalski?= Date: Mon, 20 Jul 2026 12:02:16 +0200 Subject: [PATCH] Zend: handle IS_UNDEF in the generic conversion helpers that still lack it IS_UNDEF is a legitimate runtime tag that internal code can obtain from an object property table, and the generic conversion helpers in zend_operators.c are ZEND_API entry points taking an arbitrary zval. Some already account for that and some do not: handled: zendi_try_get_long(), zval_get_long_func(), __zval_get_string_func(), _convert_to_string() missing: _zendi_try_convert_scalar_to_number(), convert_to_long(), convert_to_double(), convert_to_boolean(), zval_get_double_func() The asymmetry is what is left after two point fixes. 2b383848a73 taught zval_try_get_long() about IS_REFERENCE and 9e1b285f65a (GH-22142) taught zendi_try_get_long() about IS_UNDEF, each by adding the tag to the one function that had been reported. The siblings were not revisited, so zval_get_long(undef) returns 0 while zval_get_double(undef) reaches ZEND_UNREACHABLE(): an assertion failure in a debug build, undefined behaviour in a release build. Add the missing cases to arms that already express the intended result, so IS_UNDEF behaves as IS_NULL does for the cast helpers and produces a normal TypeError for the operator helper, matching the bitwise operators which are already safe through zendi_try_get_long(). increment_function() and decrement_function() are deliberately not changed. IS_UNDEF must not reach them: every VM handler normalizes it to IS_NULL before the call (zend_vm_def.h), and the JIT asserts the precondition outright (zend_jit_helpers.c). Accepting it there would weaken a check another subsystem relies on. No behaviour changes for any tag that works today, and no catch-all default is added: IS_CONSTANT_AST, IS_INDIRECT, IS_PTR, IS_ALIAS_PTR and _IS_ERROR indicate a caller bug and must keep failing loudly. --- Zend/zend_operators.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Zend/zend_operators.c b/Zend/zend_operators.c index ab8f2c2b54f8..eb9d8ef90ff1 100644 --- a/Zend/zend_operators.c +++ b/Zend/zend_operators.c @@ -355,6 +355,7 @@ static zend_never_inline zend_result ZEND_FASTCALL _zendi_try_convert_scalar_to_ } ZEND_ASSERT(Z_TYPE_P(holder) == IS_LONG || Z_TYPE_P(holder) == IS_DOUBLE); return SUCCESS; + case IS_UNDEF: case IS_RESOURCE: case IS_ARRAY: return FAILURE; @@ -556,6 +557,7 @@ ZEND_API void ZEND_FASTCALL convert_to_long(zval *op) /* {{{ */ try_again: switch (Z_TYPE_P(op)) { + case IS_UNDEF: case IS_NULL: case IS_FALSE: ZVAL_LONG(op, 0); @@ -617,6 +619,7 @@ ZEND_API void ZEND_FASTCALL convert_to_double(zval *op) /* {{{ */ try_again: switch (Z_TYPE_P(op)) { + case IS_UNDEF: case IS_NULL: case IS_FALSE: ZVAL_DOUBLE(op, 0.0); @@ -689,6 +692,7 @@ ZEND_API void ZEND_FASTCALL convert_to_boolean(zval *op) /* {{{ */ case IS_FALSE: case IS_TRUE: break; + case IS_UNDEF: case IS_NULL: ZVAL_FALSE(op); break; @@ -1024,6 +1028,7 @@ ZEND_API double ZEND_FASTCALL zval_get_double_func(const zval *op) /* {{{ */ { try_again: switch (Z_TYPE_P(op)) { + case IS_UNDEF: case IS_NULL: case IS_FALSE: return 0.0;