From d4a9726a2f58557b464ddd8af7d0ca8952d8727e Mon Sep 17 00:00:00 2001 From: ndossche <7771979+ndossche@users.noreply.github.com> Date: Sun, 7 Jun 2026 22:39:46 +0200 Subject: [PATCH 01/10] exif: read from the correct pointer `vptr` points to the current value being iterated over, not `value`, see other switch cases. Closes GH-22249. --- NEWS | 3 +++ ext/exif/exif.c | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index ad5d3753b22c..3ec0d319b7e4 100644 --- a/NEWS +++ b/NEWS @@ -14,6 +14,9 @@ PHP NEWS . Fix GH-22219 (Dom\XMLDocument::schemaValidate fails to resolve xs:QName with prefix from imported schema). (David Carlier) +- Exif: + . Read correct value for single and double tags. (ndossche) + - GD: . Fixed bug GH-22121 (Double free in gdImageSetStyle() after overflow-triggered early return). (iliaal) diff --git a/ext/exif/exif.c b/ext/exif/exif.c index ab3948819e5b..58e6d2801055 100644 --- a/ext/exif/exif.c +++ b/ext/exif/exif.c @@ -2306,13 +2306,13 @@ static void exif_iif_add_value(image_info_type *image_info, int section_index, c #ifdef EXIF_DEBUG php_error_docref(NULL, E_WARNING, "Found value of type single"); #endif - info_value->f = php_ifd_get_float(value); + info_value->f = php_ifd_get_float(vptr); break; case TAG_FMT_DOUBLE: #ifdef EXIF_DEBUG php_error_docref(NULL, E_WARNING, "Found value of type double"); #endif - info_value->d = php_ifd_get_double(value); + info_value->d = php_ifd_get_double(vptr); break; } } From 3c6b25cca5142b12a2ce440a9e965ac795a24ee6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Sun, 14 Jun 2026 12:48:06 +0200 Subject: [PATCH 02/10] zend_string: Add `zend_string_equals_cstr_ci()` (#22296) This function was missing for consistency with the other functions which all have a `cstr` variant. Adding this function also made it easy to convert some more copy-and-pasted logic. --- UPGRADING.INTERNALS | 1 + Zend/zend_operators.h | 1 - Zend/zend_string.h | 19 +++++++++++++++---- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS index f89123116c75..bb2f62274751 100644 --- a/UPGRADING.INTERNALS +++ b/UPGRADING.INTERNALS @@ -17,6 +17,7 @@ PHP 8.6 INTERNALS UPGRADE NOTES . ZSTR_INIT_LITERAL(), zend_string_starts_with_literal(), and zend_string_starts_with_literal_ci() now support strings containing NUL bytes. Passing non-literal char* is no longer supported. + . Added zend_string_equals_cstr_ci(). . The misnamed ZVAL_IS_NULL() has been removed. Use Z_ISNULL() instead. . New zend_class_entry.ce_flags2 and zend_function.fn_flags2 fields were added, given the primary flags were running out of bits. diff --git a/Zend/zend_operators.h b/Zend/zend_operators.h index b6e1923b3bf4..27aa4fdb0486 100644 --- a/Zend/zend_operators.h +++ b/Zend/zend_operators.h @@ -46,7 +46,6 @@ #include "zend_portability.h" #include "zend_strtod.h" #include "zend_multiply.h" -#include "zend_object_handlers.h" #define LONG_SIGN_MASK ZEND_LONG_MIN diff --git a/Zend/zend_string.h b/Zend/zend_string.h index 3f0c9abd2596..930d733307f4 100644 --- a/Zend/zend_string.h +++ b/Zend/zend_string.h @@ -397,11 +397,22 @@ static zend_always_inline bool zend_string_equals(const zend_string *s1, const z return s1 == s2 || zend_string_equal_content(s1, s2); } -#define zend_string_equals_ci(s1, s2) \ - (ZSTR_LEN(s1) == ZSTR_LEN(s2) && !zend_binary_strcasecmp(ZSTR_VAL(s1), ZSTR_LEN(s1), ZSTR_VAL(s2), ZSTR_LEN(s2))) +BEGIN_EXTERN_C() +ZEND_API int ZEND_FASTCALL zend_binary_strcasecmp(const char *s1, size_t len1, const char *s2, size_t len2); +END_EXTERN_C() + +static zend_always_inline bool zend_string_equals_cstr_ci(const zend_string *s1, const char *s2, size_t s2_length) +{ + return ZSTR_LEN(s1) == s2_length && !zend_binary_strcasecmp(ZSTR_VAL(s1), ZSTR_LEN(s1), s2, s2_length); +} + +#define zend_string_equals_literal_ci(str, literal) \ + zend_string_equals_cstr_ci(str, "" literal, sizeof(literal) - 1) -#define zend_string_equals_literal_ci(str, c) \ - (ZSTR_LEN(str) == sizeof("" c) - 1 && !zend_binary_strcasecmp(ZSTR_VAL(str), ZSTR_LEN(str), (c), sizeof(c) - 1)) +static zend_always_inline bool zend_string_equals_ci(const zend_string *s1, const zend_string *s2) +{ + return zend_string_equals_cstr_ci(s1, ZSTR_VAL(s2), ZSTR_LEN(s2)); +} #define zend_string_equals_literal(str, literal) \ zend_string_equals_cstr(str, "" literal, sizeof(literal) - 1) From 8f44bd924e0c5a646fa905da757c85764a74d778 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Sun, 14 Jun 2026 13:28:37 +0200 Subject: [PATCH 03/10] zend_ast: Quote names of invalid variable names when exporting AST (#22294) Fixes php/php-src#22292. --- NEWS | 2 ++ Zend/tests/assert/expect_015.phpt | 2 +- Zend/zend_ast.c | 11 ++++++--- ext/standard/tests/assert/gh22292.phpt | 31 ++++++++++++++++++++++++++ 4 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 ext/standard/tests/assert/gh22292.phpt diff --git a/NEWS b/NEWS index ac3381900831..eff542c0e46b 100644 --- a/NEWS +++ b/NEWS @@ -24,6 +24,8 @@ PHP NEWS . Fixed bug GH-22046 (The unserialize function can lead to segfault when non-Serializable internal classes are serialized back with the C format). (kocsismate) + . Fixed bug GH-22292 (AST pretty printing does not correctly handle + invalid variable names). (timwolla) - BCMath: . Added NUL-byte validation to BCMath functions. (jorgsowa) diff --git a/Zend/tests/assert/expect_015.phpt b/Zend/tests/assert/expect_015.phpt index 9f8e9b77003b..79ea3703ead6 100644 --- a/Zend/tests/assert/expect_015.phpt +++ b/Zend/tests/assert/expect_015.phpt @@ -304,7 +304,7 @@ assert(0 && ($a = function (): ?static { $x = "{$a}b"; $x = "{$a}b"; $x = " {$foo->bar} {${$foo->bar}} "; - $x = " ${---} "; + $x = " ${'---'} "; foo(); \foo(); namespace\foo(); diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index a7e26711cd17..983299c0a9d8 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1723,9 +1723,14 @@ static ZEND_COLD void zend_ast_export_var(smart_str *str, zend_ast *ast, int ind { if (ast->kind == ZEND_AST_ZVAL) { zval *zv = zend_ast_get_zval(ast); - if (Z_TYPE_P(zv) == IS_STRING && - zend_ast_valid_var_name(Z_STRVAL_P(zv), Z_STRLEN_P(zv))) { - smart_str_append(str, Z_STR_P(zv)); + if (Z_TYPE_P(zv) == IS_STRING) { + if (zend_ast_valid_var_name(Z_STRVAL_P(zv), Z_STRLEN_P(zv))) { + smart_str_append(str, Z_STR_P(zv)); + } else { + smart_str_appends(str, "{'"); + zend_ast_export_str(str, Z_STR_P(zv)); + smart_str_appends(str, "'}"); + } return; } } else if (ast->kind == ZEND_AST_VAR) { diff --git a/ext/standard/tests/assert/gh22292.phpt b/ext/standard/tests/assert/gh22292.phpt new file mode 100644 index 000000000000..e3aa72bf231a --- /dev/null +++ b/ext/standard/tests/assert/gh22292.phpt @@ -0,0 +1,31 @@ +--TEST-- +GH-22292: AST pretty printing does not correctly handle invalid variable names +--FILE-- +getMessage(), PHP_EOL; +} + +try { + $f = new Foo(); + var_dump($f->{'---'}); + assert(!$f->{'---'}); +} catch (Error $e) { + echo $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +string(3) "abc" +assert(!${'---'}) +string(3) "---" +assert(!$f->{'---'}) From f26aae199f43af773708fde6bfbd126327183490 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Sat, 13 Jun 2026 23:38:05 +0200 Subject: [PATCH 04/10] Microoptimize zval_long_or_null_to_lexbor_str Based on Nora's suggestion of not using an unnecesary allocation. --- ext/uri/uri_parser_whatwg.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/uri/uri_parser_whatwg.c b/ext/uri/uri_parser_whatwg.c index 3ff1e1af166f..b00d2201df6e 100644 --- a/ext/uri/uri_parser_whatwg.c +++ b/ext/uri/uri_parser_whatwg.c @@ -43,9 +43,9 @@ static zend_always_inline void zval_string_or_null_to_lexbor_str(const zval *val static zend_always_inline void zval_long_or_null_to_lexbor_str(const zval *value, lexbor_str_t *lexbor_str) { if (Z_TYPE_P(value) == IS_LONG) { - zend_string *tmp = zend_long_to_str(Z_LVAL_P(value)); - lexbor_str_init_append(lexbor_str, lexbor_parser.mraw, (const lxb_char_t *) ZSTR_VAL(tmp), ZSTR_LEN(tmp)); - zend_string_release(tmp); + char buf[MAX_LENGTH_OF_LONG + 1]; + const char *res = zend_print_long_to_buf(buf + sizeof(buf) - 1, Z_LVAL_P(value)); + lexbor_str_init_append(lexbor_str, lexbor_parser.mraw, (const lxb_char_t *) res, strlen(res)); } else { ZEND_ASSERT(Z_ISNULL_P(value)); lexbor_str->data = (lxb_char_t *) ""; From 3642a7acbeff2d80e15f39f0fa939e5a6a156baa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Sun, 14 Jun 2026 14:13:07 +0200 Subject: [PATCH 05/10] Add a few missing modifiers for ext/uri functions - static modifier is added for php_uri_parser_rfc3986_parse() - const modifier is added for component read and write handler parameters --- ext/uri/uri_parser_rfc3986.c | 28 ++++++++++++++-------------- ext/uri/uri_parser_rfc3986.h | 2 +- ext/uri/uri_parser_whatwg.c | 20 ++++++++++---------- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/ext/uri/uri_parser_rfc3986.c b/ext/uri/uri_parser_rfc3986.c index 0b1c8970f473..123f244a51d7 100644 --- a/ext/uri/uri_parser_rfc3986.c +++ b/ext/uri/uri_parser_rfc3986.c @@ -103,7 +103,7 @@ ZEND_ATTRIBUTE_NONNULL static UriUriA *get_normalized_uri(php_uri_parser_rfc3986 return &uriparser_uris->normalized_uri; } -ZEND_ATTRIBUTE_NONNULL static UriUriA *get_uri_for_reading(php_uri_parser_rfc3986_uris *uriparser_uris, php_uri_component_read_mode read_mode) +ZEND_ATTRIBUTE_NONNULL static UriUriA *get_uri_for_reading(php_uri_parser_rfc3986_uris *uriparser_uris, const php_uri_component_read_mode read_mode) { switch (read_mode) { case PHP_URI_COMPONENT_READ_MODE_RAW: @@ -140,7 +140,7 @@ ZEND_ATTRIBUTE_NONNULL void php_uri_parser_rfc3986_uri_type_read(php_uri_parser_ ZVAL_OBJ_COPY(retval, zend_enum_get_case_cstr(php_uri_ce_rfc3986_uri_type, type)); } -ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_scheme_read(void *uri, php_uri_component_read_mode read_mode, zval *retval) +ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_scheme_read(void *uri, const php_uri_component_read_mode read_mode, zval *retval) { const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode); @@ -177,7 +177,7 @@ static zend_result php_uri_parser_rfc3986_scheme_write(void *uri, const zval *va } } -ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_userinfo_read(php_uri_parser_rfc3986_uris *uri, php_uri_component_read_mode read_mode, zval *retval) +ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_userinfo_read(php_uri_parser_rfc3986_uris *uri, const php_uri_component_read_mode read_mode, zval *retval) { const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode); @@ -190,7 +190,7 @@ ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_userinfo_read(php_uri_ return SUCCESS; } -zend_result php_uri_parser_rfc3986_userinfo_write(php_uri_parser_rfc3986_uris *uri, zval *value, zval *errors) +zend_result php_uri_parser_rfc3986_userinfo_write(php_uri_parser_rfc3986_uris *uri, const zval *value, zval *errors) { UriUriA *uriparser_uri = get_uri_for_writing(uri); int result; @@ -217,7 +217,7 @@ zend_result php_uri_parser_rfc3986_userinfo_write(php_uri_parser_rfc3986_uris *u } } -ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_username_read(void *uri, php_uri_component_read_mode read_mode, zval *retval) +ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_username_read(void *uri, const php_uri_component_read_mode read_mode, zval *retval) { const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode); @@ -239,7 +239,7 @@ ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_username_read(v return SUCCESS; } -ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_password_read(void *uri, php_uri_component_read_mode read_mode, zval *retval) +ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_password_read(void *uri, const php_uri_component_read_mode read_mode, zval *retval) { const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode); @@ -258,7 +258,7 @@ ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_password_read(v return SUCCESS; } -ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_host_read(void *uri, php_uri_component_read_mode read_mode, zval *retval) +ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_host_read(void *uri, const php_uri_component_read_mode read_mode, zval *retval) { const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode); @@ -335,7 +335,7 @@ static zend_result php_uri_parser_rfc3986_host_write(void *uri, const zval *valu } } -ZEND_ATTRIBUTE_NONNULL static zend_long port_str_to_zend_long_checked(const char *str, size_t len) +ZEND_ATTRIBUTE_NONNULL static zend_long port_str_to_zend_long_checked(const char *str, const size_t len) { if (len > MAX_LENGTH_OF_LONG) { return -1; @@ -353,7 +353,7 @@ ZEND_ATTRIBUTE_NONNULL static zend_long port_str_to_zend_long_checked(const char return (zend_long)result; } -ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_port_read(void *uri, php_uri_component_read_mode read_mode, zval *retval) +ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_port_read(void *uri, const php_uri_component_read_mode read_mode, zval *retval) { const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode); @@ -395,7 +395,7 @@ static zend_result php_uri_parser_rfc3986_port_write(void *uri, const zval *valu } } -ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_path_read(void *uri, php_uri_component_read_mode read_mode, zval *retval) +ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_path_read(void *uri, const php_uri_component_read_mode read_mode, zval *retval) { const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode); @@ -463,7 +463,7 @@ static zend_result php_uri_parser_rfc3986_path_write(void *uri, const zval *valu } } -ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_query_read(void *uri, php_uri_component_read_mode read_mode, zval *retval) +ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_query_read(void *uri, const php_uri_component_read_mode read_mode, zval *retval) { const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode); @@ -500,7 +500,7 @@ static zend_result php_uri_parser_rfc3986_query_write(void *uri, const zval *val } } -ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_fragment_read(void *uri, php_uri_component_read_mode read_mode, zval *retval) +ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_fragment_read(void *uri, const php_uri_component_read_mode read_mode, zval *retval) { const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode); @@ -619,7 +619,7 @@ php_uri_parser_rfc3986_uris *php_uri_parser_rfc3986_parse_ex(const char *uri_str return NULL; } -void *php_uri_parser_rfc3986_parse(const char *uri_str, size_t uri_str_len, const void *base_url, zval *errors, bool silent) +static void *php_uri_parser_rfc3986_parse(const char *uri_str, const size_t uri_str_len, const void *base_url, zval *errors, bool silent) { return php_uri_parser_rfc3986_parse_ex(uri_str, uri_str_len, base_url, silent); } @@ -637,7 +637,7 @@ ZEND_ATTRIBUTE_NONNULL static void *php_uri_parser_rfc3986_clone(void *uri) return new_uriparser_uris; } -ZEND_ATTRIBUTE_NONNULL static zend_string *php_uri_parser_rfc3986_to_string(void *uri, php_uri_recomposition_mode recomposition_mode, bool exclude_fragment) +ZEND_ATTRIBUTE_NONNULL static zend_string *php_uri_parser_rfc3986_to_string(void *uri, const php_uri_recomposition_mode recomposition_mode, const bool exclude_fragment) { php_uri_parser_rfc3986_uris *uriparser_uris = uri; const UriUriA *uriparser_uri; diff --git a/ext/uri/uri_parser_rfc3986.h b/ext/uri/uri_parser_rfc3986.h index 633dd72062f2..a5fde7074323 100644 --- a/ext/uri/uri_parser_rfc3986.h +++ b/ext/uri/uri_parser_rfc3986.h @@ -25,7 +25,7 @@ ZEND_ATTRIBUTE_NONNULL void php_uri_parser_rfc3986_uri_type_read(php_uri_parser_ ZEND_ATTRIBUTE_NONNULL void php_uri_parser_rfc3986_host_type_read(php_uri_parser_rfc3986_uris *uri, zval *retval); zend_result php_uri_parser_rfc3986_userinfo_read(php_uri_parser_rfc3986_uris *uri, php_uri_component_read_mode read_mode, zval *retval); -zend_result php_uri_parser_rfc3986_userinfo_write(php_uri_parser_rfc3986_uris *uri, zval *value, zval *errors); +zend_result php_uri_parser_rfc3986_userinfo_write(php_uri_parser_rfc3986_uris *uri, const zval *value, zval *errors); php_uri_parser_rfc3986_uris *php_uri_parser_rfc3986_parse_ex(const char *uri_str, size_t uri_str_len, const php_uri_parser_rfc3986_uris *uriparser_base_url, bool silent); diff --git a/ext/uri/uri_parser_whatwg.c b/ext/uri/uri_parser_whatwg.c index b00d2201df6e..6d46feb51aae 100644 --- a/ext/uri/uri_parser_whatwg.c +++ b/ext/uri/uri_parser_whatwg.c @@ -235,7 +235,7 @@ static void throw_invalid_url_exception_during_write(zval *errors, const char *c } } -static lxb_status_t serialize_to_smart_str_callback(const lxb_char_t *data, size_t length, void *ctx) +static lxb_status_t serialize_to_smart_str_callback(const lxb_char_t *data, const size_t length, void *ctx) { smart_str *uri_str = ctx; @@ -246,7 +246,7 @@ static lxb_status_t serialize_to_smart_str_callback(const lxb_char_t *data, size return LXB_STATUS_OK; } -static zend_result php_uri_parser_whatwg_scheme_read(void *uri, php_uri_component_read_mode read_mode, zval *retval) +static zend_result php_uri_parser_whatwg_scheme_read(void *uri, const php_uri_component_read_mode read_mode, zval *retval) { const lxb_url_t *lexbor_uri = uri; @@ -348,7 +348,7 @@ static zend_result php_uri_parser_whatwg_password_write(void *uri, const zval *v return SUCCESS; } -static zend_result php_uri_parser_whatwg_host_read(void *uri, php_uri_component_read_mode read_mode, zval *retval) +static zend_result php_uri_parser_whatwg_host_read(void *uri, const php_uri_component_read_mode read_mode, zval *retval) { const lxb_url_t *lexbor_uri = uri; @@ -435,7 +435,7 @@ static zend_result php_uri_parser_whatwg_host_write(void *uri, const zval *value return SUCCESS; } -static zend_result php_uri_parser_whatwg_port_read(void *uri, php_uri_component_read_mode read_mode, zval *retval) +static zend_result php_uri_parser_whatwg_port_read(void *uri, const php_uri_component_read_mode read_mode, zval *retval) { const lxb_url_t *lexbor_uri = uri; @@ -466,7 +466,7 @@ static zend_result php_uri_parser_whatwg_port_write(void *uri, const zval *value return SUCCESS; } -static zend_result php_uri_parser_whatwg_path_read(void *uri, php_uri_component_read_mode read_mode, zval *retval) +static zend_result php_uri_parser_whatwg_path_read(void *uri, const php_uri_component_read_mode read_mode, zval *retval) { const lxb_url_t *lexbor_uri = uri; @@ -497,7 +497,7 @@ static zend_result php_uri_parser_whatwg_path_write(void *uri, const zval *value return SUCCESS; } -static zend_result php_uri_parser_whatwg_query_read(void *uri, php_uri_component_read_mode read_mode, zval *retval) +static zend_result php_uri_parser_whatwg_query_read(void *uri, const php_uri_component_read_mode read_mode, zval *retval) { const lxb_url_t *lexbor_uri = uri; @@ -528,7 +528,7 @@ static zend_result php_uri_parser_whatwg_query_write(void *uri, const zval *valu return SUCCESS; } -static zend_result php_uri_parser_whatwg_fragment_read(void *uri, php_uri_component_read_mode read_mode, zval *retval) +static zend_result php_uri_parser_whatwg_fragment_read(void *uri, const php_uri_component_read_mode read_mode, zval *retval) { const lxb_url_t *lexbor_uri = uri; @@ -606,7 +606,7 @@ ZEND_MODULE_POST_ZEND_DEACTIVATE_D(uri_parser_whatwg) return SUCCESS; } -lxb_url_t *php_uri_parser_whatwg_parse_ex(const char *uri_str, size_t uri_str_len, const lxb_url_t *lexbor_base_url, zval *errors, bool silent) +lxb_url_t *php_uri_parser_whatwg_parse_ex(const char *uri_str, const size_t uri_str_len, const lxb_url_t *lexbor_base_url, zval *errors, const bool silent) { lxb_url_parser_clean(&lexbor_parser); @@ -630,7 +630,7 @@ lxb_url_t *php_uri_parser_whatwg_parse_ex(const char *uri_str, size_t uri_str_le return url; } -static void *php_uri_parser_whatwg_parse(const char *uri_str, size_t uri_str_len, const void *base_url, zval *errors, bool silent) +static void *php_uri_parser_whatwg_parse(const char *uri_str, const size_t uri_str_len, const void *base_url, zval *errors, const bool silent) { return php_uri_parser_whatwg_parse_ex(uri_str, uri_str_len, base_url, errors, silent); } @@ -642,7 +642,7 @@ static void *php_uri_parser_whatwg_clone(void *uri) return lxb_url_clone(lexbor_parser.mraw, lexbor_uri); } -static zend_string *php_uri_parser_whatwg_to_string(void *uri, php_uri_recomposition_mode recomposition_mode, bool exclude_fragment) +static zend_string *php_uri_parser_whatwg_to_string(void *uri, const php_uri_recomposition_mode recomposition_mode, const bool exclude_fragment) { const lxb_url_t *lexbor_uri = uri; smart_str uri_str = {0}; From 9e9b309b6faa4f9bfe7f6edfaf508dd3c5599743 Mon Sep 17 00:00:00 2001 From: Pratik Bhujel Date: Sun, 14 Jun 2026 18:56:32 +0545 Subject: [PATCH 06/10] Fix GH-22280: Ignore non-finally try blocks (#22286) --- NEWS | 4 ++++ Zend/tests/try/gh22280.phpt | 19 +++++++++++++++++++ Zend/zend_opcode.c | 4 ++++ 3 files changed, 27 insertions(+) create mode 100644 Zend/tests/try/gh22280.phpt diff --git a/NEWS b/NEWS index 3ec0d319b7e4..7b36fb246790 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,10 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.4.23 +- Core: + . Fixed bug GH-22280 (Incorrect compile error for goto to label preceding + try/finally block). (Pratik Bhujel) + - BCMath: . Fixed issues with oversized allocations and signed overflow in bcround() and BcMath\Number::round(). (edorian) diff --git a/Zend/tests/try/gh22280.phpt b/Zend/tests/try/gh22280.phpt new file mode 100644 index 000000000000..30944aff59a6 --- /dev/null +++ b/Zend/tests/try/gh22280.phpt @@ -0,0 +1,19 @@ +--TEST-- +GH-22280: goto to label before try/finally after try/catch +--FILE-- + +--EXPECT-- +try +finally +done diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 7a364dfccbcd..317f68b486bb 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -684,6 +684,10 @@ static void zend_check_finally_breakout(zend_op_array *op_array, uint32_t op_num int i; for (i = 0; i < op_array->last_try_catch; i++) { + if (!op_array->try_catch_array[i].finally_op) { + continue; + } + if ((op_num < op_array->try_catch_array[i].finally_op || op_num >= op_array->try_catch_array[i].finally_end) && (dst_num >= op_array->try_catch_array[i].finally_op && From c76b33d584d6329dcb40a4f8b190e0ecb587135a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Sun, 14 Jun 2026 15:51:47 +0200 Subject: [PATCH 07/10] zend_types: Remove the unused `Z_GC_*()` macros (#22303) --- UPGRADING.INTERNALS | 2 ++ Zend/zend_types.h | 11 ----------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS index bb2f62274751..1f24fb82fcf9 100644 --- a/UPGRADING.INTERNALS +++ b/UPGRADING.INTERNALS @@ -104,6 +104,8 @@ PHP 8.6 INTERNALS UPGRADE NOTES . The deprecated Z_IMMUTABLE(), Z_IMMUTABLE_P(), Z_OPT_IMMUTABLE(), and Z_OPT_IMMUTABLE_P() macros have been removed. Check for IS_ARRAY && !REFCOUNTED directly. + . The unused Z_GC_*() macros have been removed. Use the corresponding + GC_*() macro on the result of Z_COUNTED(). . The zend_binary_zval_strcmp() and zend_binary_zval_strncmp() functions have been removed, because they are unsafe by relying on the zvals having a specific type. Use zend_binary_strcmp() / zend_binary_strncmp(), diff --git a/Zend/zend_types.h b/Zend/zend_types.h index f3039d202519..0edc4df37484 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -864,17 +864,6 @@ static zend_always_inline uint32_t zend_gc_delref_ex(zend_refcounted_h *p, uint3 } \ } while (0) -#define Z_GC_TYPE(zval) GC_TYPE(Z_COUNTED(zval)) -#define Z_GC_TYPE_P(zval_p) Z_GC_TYPE(*(zval_p)) - -#define Z_GC_FLAGS(zval) GC_FLAGS(Z_COUNTED(zval)) -#define Z_GC_FLAGS_P(zval_p) Z_GC_FLAGS(*(zval_p)) - -#define Z_GC_INFO(zval) GC_INFO(Z_COUNTED(zval)) -#define Z_GC_INFO_P(zval_p) Z_GC_INFO(*(zval_p)) -#define Z_GC_TYPE_INFO(zval) GC_TYPE_INFO(Z_COUNTED(zval)) -#define Z_GC_TYPE_INFO_P(zval_p) Z_GC_TYPE_INFO(*(zval_p)) - #define GC_NULL (IS_NULL | (GC_NOT_COLLECTABLE << GC_FLAGS_SHIFT)) #define GC_STRING (IS_STRING | (GC_NOT_COLLECTABLE << GC_FLAGS_SHIFT)) #define GC_ARRAY IS_ARRAY From b8ff8d64ac5af53af0ceca5e991c2fc9dc73f04b Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Sun, 24 May 2026 20:14:51 -0400 Subject: [PATCH 08/10] Fix GH-22112: assertion when error handler throws during NaN coercion zend_parse_arg_bool_weak and zend_parse_arg_str_weak could return success with EG(exception) already set, because zend_is_true and convert_to_string emit the NaN coercion warning without checking whether the user error handler threw. Recv-arg verification for a userland function then took the no-check ZEND_VM_NEXT_OPCODE branch, aborting on ZEND_ASSERT(!EG(exception)). Mirror the existing check in zend_parse_arg_long_weak and propagate failure when the warning leaves an exception pending. Fixes GH-22112 Closes GH-22114 --- NEWS | 2 ++ Zend/tests/type_coercion/gh22112.phpt | 35 +++++++++++++++++++++++++++ Zend/zend_API.c | 6 +++++ 3 files changed, 43 insertions(+) create mode 100644 Zend/tests/type_coercion/gh22112.phpt diff --git a/NEWS b/NEWS index f127593dbe07..5e480287f24f 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,8 @@ PHP NEWS - Core: . Fixed bug GH-22280 (Incorrect compile error for goto to label preceding try/finally block). (Pratik Bhujel) + . Fixed bug GH-22112 (Assertion when error handler throws during NaN to + bool/string coercion). (iliaal) - BCMath: . Fixed issues with oversized allocations and signed overflow in bcround() diff --git a/Zend/tests/type_coercion/gh22112.phpt b/Zend/tests/type_coercion/gh22112.phpt new file mode 100644 index 000000000000..84fdc393a828 --- /dev/null +++ b/Zend/tests/type_coercion/gh22112.phpt @@ -0,0 +1,35 @@ +--TEST-- +GH-22112 (Assertion failure when error handler throws during NaN to bool/string coercion at function entry) +--FILE-- +getMessage(), "\n"; +} + +try { + take_string($nan); +} catch (Exception $e) { + echo "string: ", $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +bool: unexpected NAN value was coerced to bool +string: unexpected NAN value was coerced to string diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 538c02f0395e..e874c4a5bbd7 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -537,6 +537,9 @@ ZEND_API bool ZEND_FASTCALL zend_parse_arg_bool_weak(const zval *arg, bool *dest return 0; } *dest = zend_is_true(arg); + if (UNEXPECTED(EG(exception))) { + return 0; + } } else { return 0; } @@ -762,6 +765,9 @@ ZEND_API bool ZEND_FASTCALL zend_parse_arg_str_weak(zval *arg, zend_string **des return 0; } convert_to_string(arg); + if (UNEXPECTED(EG(exception))) { + return 0; + } *dest = Z_STR_P(arg); } else if (UNEXPECTED(Z_TYPE_P(arg) == IS_OBJECT)) { zend_object *zobj = Z_OBJ_P(arg); From 51c22d320263209632063ab9a8f500bd171151bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Sun, 14 Jun 2026 16:17:16 +0200 Subject: [PATCH 09/10] zend_observer: Convert macros to inline functions (#22301) * zend_observer: Convert `ZEND_OBSERVER_HANDLE()` to inline function * zend_observer: Convert `ZEND_OBSERVER_FCALL_(BEGIN|END)()` to inline functions --- Zend/zend_observer.h | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/Zend/zend_observer.h b/Zend/zend_observer.h index f4ade29285db..bcfba35c0658 100644 --- a/Zend/zend_observer.h +++ b/Zend/zend_observer.h @@ -32,8 +32,9 @@ extern ZEND_API bool zend_observer_errors_observed; extern ZEND_API bool zend_observer_function_declared_observed; extern ZEND_API bool zend_observer_class_linked_observed; -#define ZEND_OBSERVER_HANDLE(function) (ZEND_USER_CODE((function)->type) \ - ? zend_observer_fcall_op_array_extension : zend_observer_fcall_internal_function_extension) +static zend_always_inline int ZEND_OBSERVER_HANDLE(const zend_function *function) { + return ZEND_USER_CODE(function->common.type) ? zend_observer_fcall_op_array_extension : zend_observer_fcall_internal_function_extension; +} #define ZEND_OBSERVER_DATA(function) \ ((zend_observer_fcall_begin_handler *)&ZEND_OP_ARRAY_EXTENSION((&(function)->common), ZEND_OBSERVER_HANDLE(function))) @@ -45,18 +46,6 @@ extern ZEND_API bool zend_observer_class_linked_observed; /* Omit zend_observer_fcall_internal_function_extension check, they are set at the same time. */ #define ZEND_OBSERVER_ENABLED (zend_observer_fcall_op_array_extension != -1) -#define ZEND_OBSERVER_FCALL_BEGIN(execute_data) do { \ - if (ZEND_OBSERVER_ENABLED) { \ - zend_observer_fcall_begin(execute_data); \ - } \ - } while (0) - -#define ZEND_OBSERVER_FCALL_END(execute_data, return_value) do { \ - if (ZEND_OBSERVER_ENABLED) { \ - zend_observer_fcall_end(execute_data, return_value); \ - } \ - } while (0) - typedef void (*zend_observer_fcall_begin_handler)(zend_execute_data *execute_data); typedef void (*zend_observer_fcall_end_handler)(zend_execute_data *execute_data, zval *retval); @@ -87,6 +76,12 @@ ZEND_API void ZEND_FASTCALL zend_observer_fcall_begin(zend_execute_data *execute /* prechecked: the call is actually observed. */ ZEND_API void ZEND_FASTCALL zend_observer_fcall_begin_prechecked(zend_execute_data *execute_data, zend_observer_fcall_begin_handler *observer_data); +static zend_always_inline void ZEND_OBSERVER_FCALL_BEGIN(zend_execute_data *execute_data) { + if (ZEND_OBSERVER_ENABLED) { + zend_observer_fcall_begin(execute_data); + } +} + static zend_always_inline bool zend_observer_handler_is_unobserved(const zend_observer_fcall_begin_handler *handler) { return *handler == ZEND_OBSERVER_NONE_OBSERVED; } @@ -126,6 +121,12 @@ static zend_always_inline void zend_observer_fcall_end(zend_execute_data *execut } } +static zend_always_inline void ZEND_OBSERVER_FCALL_END(zend_execute_data *execute_data, zval *return_value) { + if (ZEND_OBSERVER_ENABLED) { + zend_observer_fcall_end(execute_data, return_value); + } +} + ZEND_API void zend_observer_fcall_end_all(void); typedef void (*zend_observer_function_declared_cb)(zend_op_array *op_array, zend_string *name); From 951680712b460be92c89f56046ae68db61fad490 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Sun, 14 Jun 2026 16:18:01 +0200 Subject: [PATCH 10/10] zend_object_handlers: Replace function-like macros by (inline) functions (#22297) `zend_free_trampoline()` requires access to the globals and thus cannot easily be made an inline function without moving it to a different (less-appropriate) header. Since trampolines should be comparatively rare, having an extra function call here should be okay. --- Zend/zend_object_handlers.c | 12 ++++++++ Zend/zend_object_handlers.h | 56 +++++++++++++++++-------------------- Zend/zend_objects_API.h | 5 ++++ 3 files changed, 42 insertions(+), 31 deletions(-) diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index e7ddd466a51a..9e993627a74a 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -1825,6 +1825,18 @@ ZEND_API ZEND_ATTRIBUTE_NONNULL zend_function *zend_get_call_trampoline_func( } /* }}} */ +ZEND_API void zend_free_trampoline(zend_function *func) +{ + ZEND_ASSERT(func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE); + + if (func == &EG(trampoline)) { + EG(trampoline).common.attributes = NULL; + EG(trampoline).common.function_name = NULL; + } else { + efree(func); + } +} + static ZEND_FUNCTION(zend_parent_hook_get_trampoline) { zend_object *obj = Z_PTR_P(ZEND_THIS); diff --git a/Zend/zend_object_handlers.h b/Zend/zend_object_handlers.h index d0dd804e8a41..8ed4803c3f0e 100644 --- a/Zend/zend_object_handlers.h +++ b/Zend/zend_object_handlers.h @@ -236,11 +236,10 @@ struct _zend_object_handlers { BEGIN_EXTERN_C() extern const ZEND_API zend_object_handlers std_object_handlers; -#define zend_get_std_object_handlers() \ - (&std_object_handlers) - -#define zend_get_function_root_class(fbc) \ - ((fbc)->common.prototype ? (fbc)->common.prototype->common.scope : (fbc)->common.scope) +static zend_always_inline const zend_object_handlers *zend_get_std_object_handlers(void) +{ + return &std_object_handlers; +} #define ZEND_PROPERTY_ISSET 0x0 /* Property exists and is not NULL */ #define ZEND_PROPERTY_NOT_EMPTY ZEND_ISEMPTY /* Property is not empty */ @@ -290,18 +289,20 @@ static zend_always_inline HashTable *zend_std_get_properties_ex(zend_object *obj /* Implements the fast path for array cast */ ZEND_API HashTable *zend_std_build_object_properties_array(zend_object *zobj); -#define ZEND_STD_BUILD_OBJECT_PROPERTIES_ARRAY_COMPATIBLE(object) ( \ - /* We can use zend_std_build_object_properties_array() for objects \ - * without properties ht and with standard handlers */ \ - Z_OBJ_P(object)->properties == NULL \ - && Z_OBJ_HT_P(object)->get_properties_for == NULL \ - && Z_OBJ_HT_P(object)->get_properties == zend_std_get_properties \ - /* For initialized proxies we need to forward to the real instance */ \ - && ( \ - !zend_object_is_lazy_proxy(Z_OBJ_P(object)) \ - || !zend_lazy_object_initialized(Z_OBJ_P(object)) \ - ) \ -) +static zend_always_inline bool ZEND_STD_BUILD_OBJECT_PROPERTIES_ARRAY_COMPATIBLE(const zval *zv) { + /* We can use zend_std_build_object_properties_array() for objects + * without properties ht and with standard handlers */ + const zend_object *obj = Z_OBJ_P(zv); + + return obj->properties == NULL + && obj->handlers->get_properties_for == NULL + && obj->handlers->get_properties == zend_std_get_properties + /* For initialized proxies we need to forward to the real instance */ + && ( + !zend_object_is_lazy_proxy(obj) + || !zend_lazy_object_initialized(obj) + ); +} /* Handler for objects that cannot be meaningfully compared. * Only objects with the same identity will be considered equal. */ @@ -312,6 +313,7 @@ ZEND_API bool zend_check_protected(const zend_class_entry *ce, const zend_class_ ZEND_API zend_result zend_check_property_access(const zend_object *zobj, zend_string *prop_info_name, bool is_dynamic); ZEND_API ZEND_ATTRIBUTE_NONNULL zend_function *zend_get_call_trampoline_func(const zend_function *fbc, zend_string *method_name); +ZEND_API void zend_free_trampoline(zend_function *func); ZEND_API uint32_t *zend_get_property_guard(zend_object *zobj, zend_string *member); @@ -335,20 +337,12 @@ ZEND_API bool ZEND_FASTCALL zend_asymmetric_property_has_set_access(const zend_p void zend_object_handlers_startup(void); -#define zend_release_properties(ht) do { \ - if (ht) { \ - zend_array_release(ht); \ - } \ -} while (0) - -#define zend_free_trampoline(func) do { \ - if ((func) == &EG(trampoline)) { \ - EG(trampoline).common.attributes = NULL; \ - EG(trampoline).common.function_name = NULL; \ - } else { \ - efree(func); \ - } \ - } while (0) +static zend_always_inline void zend_release_properties(HashTable *ht) +{ + if (ht) { + zend_array_release(ht); + } +} /* Fallback to default comparison implementation if the arguments aren't both objects * and have the same compare() handler. You'll likely want to use this unless you diff --git a/Zend/zend_objects_API.h b/Zend/zend_objects_API.h index 694ef398e374..434ac4499e7f 100644 --- a/Zend/zend_objects_API.h +++ b/Zend/zend_objects_API.h @@ -136,6 +136,11 @@ static inline zend_property_info *zend_get_typed_property_info_for_slot(zend_obj return NULL; } +static zend_always_inline zend_class_entry *zend_get_function_root_class(const zend_function *fbc) +{ + return fbc->common.prototype ? fbc->common.prototype->common.scope : fbc->common.scope; +} + static zend_always_inline bool zend_check_method_accessible(const zend_function *fn, const zend_class_entry *scope) { if (!(fn->common.fn_flags & ZEND_ACC_PUBLIC)