From b51f575ba1a15f426c1356b104a9a8b1084029d6 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Sun, 12 Jul 2026 19:21:54 -0400 Subject: [PATCH 1/8] ext/gd/tests/gh19{666,739}.phpt: skip for external libgd These two are regression tests for the bundled libgd, we have to skip them if an external libgd is used. close GH-22711 --- ext/gd/tests/gh19666.phpt | 4 ++++ ext/gd/tests/gh19739.phpt | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/ext/gd/tests/gh19666.phpt b/ext/gd/tests/gh19666.phpt index 2d82b9b44760..b1409946aa0a 100644 --- a/ext/gd/tests/gh19666.phpt +++ b/ext/gd/tests/gh19666.phpt @@ -2,6 +2,10 @@ GH-19666 (Unexpected nan value in imageconvolution) --EXTENSIONS-- gd +--SKIPIF-- + --FILE-- --FILE-- Date: Thu, 2 Jul 2026 17:46:05 +0530 Subject: [PATCH 2/8] ext/ldap: fix ldap_explode_dn result ordering Closes GH-22550 --- ext/ldap/ldap.c | 12 +++++++++--- ext/ldap/tests/ldap_explode_dn.phpt | 16 ++++++++-------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/ext/ldap/ldap.c b/ext/ldap/ldap.c index 2c9fdb8e3761..8c0a04729deb 100644 --- a/ext/ldap/ldap.c +++ b/ext/ldap/ldap.c @@ -2209,6 +2209,8 @@ PHP_FUNCTION(ldap_explode_dn) zend_long with_attrib; char *dn, **ldap_value; size_t dn_len; + int i, count; + if (zend_parse_parameters(ZEND_NUM_ARGS(), "pl", &dn, &dn_len, &with_attrib) != SUCCESS) { RETURN_THROWS(); @@ -2220,11 +2222,15 @@ PHP_FUNCTION(ldap_explode_dn) } array_init(return_value); - int i; - for (i = 0; ldap_value[i] != NULL; i++) { + i = 0; + while (ldap_value[i] != NULL) i++; + count = i; + + add_assoc_long(return_value, "count", count); + + for (i = 0; i < count; i++) { add_index_string(return_value, i, ldap_value[i]); } - add_assoc_long(return_value, "count", i); ldap_memvfree((void **)ldap_value); } diff --git a/ext/ldap/tests/ldap_explode_dn.phpt b/ext/ldap/tests/ldap_explode_dn.phpt index 047078c7beb7..2012506c3ef0 100644 --- a/ext/ldap/tests/ldap_explode_dn.phpt +++ b/ext/ldap/tests/ldap_explode_dn.phpt @@ -34,16 +34,18 @@ echo "Done\n"; ?> --EXPECT-- array(4) { + ["count"]=> + int(3) [0]=> string(6) "cn=bob" [1]=> string(10) "dc=example" [2]=> string(6) "dc=com" - ["count"]=> - int(3) } array(5) { + ["count"]=> + int(4) [0]=> string(6) "cn=bob" [1]=> @@ -52,20 +54,20 @@ array(5) { string(10) "dc=example" [3]=> string(6) "dc=com" - ["count"]=> - int(4) } array(4) { + ["count"]=> + int(3) [0]=> string(3) "bob" [1]=> string(7) "example" [2]=> string(3) "com" - ["count"]=> - int(3) } array(5) { + ["count"]=> + int(4) [0]=> string(3) "bob" [1]=> @@ -74,8 +76,6 @@ array(5) { string(7) "example" [3]=> string(3) "com" - ["count"]=> - int(4) } bool(false) bool(false) From e2c5b3c684fddb7c22a47f6e518435176ec715fb Mon Sep 17 00:00:00 2001 From: NickSdot <32384907+NickSdot@users.noreply.github.com> Date: Mon, 13 Jul 2026 13:52:27 +0700 Subject: [PATCH 3/8] zend_objects: Readonly properties must be re-locked after clone-with (#22654) --- NEWS | 2 ++ Zend/tests/clone/clone_with_014.phpt | 41 ++++++++++++++++++++++++++++ Zend/zend_objects.c | 8 ++++++ 3 files changed, 51 insertions(+) create mode 100644 Zend/tests/clone/clone_with_014.phpt diff --git a/NEWS b/NEWS index 46f23c3aa6d1..ed10d3bf2595 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,8 @@ PHP NEWS containing NUL). (iliaal) . Fixed bug GH-22206 (missing return in global register detection). (P3p111n0) + . Lock unmodified readonly properties for modification after clone-with. + (NickSdot) - Calendar: . Fixed bug GH-22602 (gregoriantojd() and juliantojd() integer overflow with diff --git a/Zend/tests/clone/clone_with_014.phpt b/Zend/tests/clone/clone_with_014.phpt new file mode 100644 index 000000000000..bfbee40e163c --- /dev/null +++ b/Zend/tests/clone/clone_with_014.phpt @@ -0,0 +1,41 @@ +--TEST-- +Properties are still readonly after clone-with +--FILE-- +a = 1; + $this->b = 2; + } +} + +$test = clone(new Test(), ['a' => 3]); +var_dump($test); + +try { + $test->b = 4; +} catch (Error $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +var_dump($test); + +?> +--EXPECT-- +object(Test)#2 (2) { + ["a"]=> + int(3) + ["b"]=> + int(2) +} +Error: Cannot modify readonly property Test::$b +object(Test)#2 (2) { + ["a"]=> + int(3) + ["b"]=> + int(2) +} diff --git a/Zend/zend_objects.c b/Zend/zend_objects.c index 6f6a82638944..d92d54d3f4a4 100644 --- a/Zend/zend_objects.c +++ b/Zend/zend_objects.c @@ -322,6 +322,14 @@ ZEND_API zend_object *zend_objects_clone_obj_with(zend_object *old_object, const } ZEND_HASH_FOREACH_END(); EG(fake_scope) = old_scope; + + /* Lock readonly properties once more. */ + if (ZEND_CLASS_HAS_READONLY_PROPS(new_object->ce)) { + for (uint32_t i = 0; i < new_object->ce->default_properties_count; i++) { + zval* prop = OBJ_PROP_NUM(new_object, i); + Z_PROP_FLAG_P(prop) &= ~IS_PROP_REINITABLE; + } + } } return new_object; From 841b5bfcf20daa97cdd682949df1b980d9359442 Mon Sep 17 00:00:00 2001 From: Jorg Adam Sowa Date: Mon, 13 Jul 2026 09:10:23 +0200 Subject: [PATCH 4/8] CI: Revert timeout fix for Symfony community build (#22682) This reverts the FileInputHelperTest skip added in 1182b14b7f1. --- .github/workflows/test-suite.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml index e269ed336408..b7609608f8be 100644 --- a/.github/workflows/test-suite.yml +++ b/.github/workflows/test-suite.yml @@ -573,10 +573,6 @@ jobs: php -r '$c = file_get_contents("src/Symfony/Component/HtmlSanitizer/Tests/HtmlSanitizerCustomTest.php"); $c = str_replace("public function testSanitizeDeepNestedString()", "#[\\PHPUnit\\Framework\\Attributes\\Group('"'"'skip'"'"')]\n public function testSanitizeDeepNestedString()", $c); file_put_contents("src/Symfony/Component/HtmlSanitizer/Tests/HtmlSanitizerCustomTest.php", $c);' # Buggy FFI test in Symfony, see https://github.com/symfony/symfony/issues/47668 php -r '$c = file_get_contents("src/Symfony/Component/VarDumper/Tests/Caster/FFICasterTest.php"); $c = str_replace("public function testCastNonTrailingCharPointer()", "#[\\PHPUnit\\Framework\\Attributes\\Group('"'"'skip'"'"')]\n public function testCastNonTrailingCharPointer()", $c); file_put_contents("src/Symfony/Component/VarDumper/Tests/Caster/FFICasterTest.php", $c);' - # Causes massive amounts of system calls with USE_ZEND_ALLOC=0, exceeding the timeout - if [ -e 'src/Symfony/Component/Console/Tests/Helper/FileInputHelperTest.php' ]; then - php -r '$c = file_get_contents("src/Symfony/Component/Console/Tests/Helper/FileInputHelperTest.php"); $c = str_replace("public function testReadWithPasteDetectionAbortsBeyondMaxBytes()", "#[\\PHPUnit\\Framework\\Attributes\\Group('"'"'skip'"'"')]\n public function testReadWithPasteDetectionAbortsBeyondMaxBytes()", $c); file_put_contents("src/Symfony/Component/Console/Tests/Helper/FileInputHelperTest.php", $c);' - fi export SYMFONY_DEPRECATIONS_HELPER=max[total]=999 X=0 for component in $(find src/Symfony -mindepth 2 -type f -name phpunit.xml.dist -printf '%h\n'); do From cf3c803b506709c2af84c2be84e7746a97868a3e Mon Sep 17 00:00:00 2001 From: Florian Engelhardt Date: Wed, 8 Jul 2026 12:21:58 +0200 Subject: [PATCH 5/8] `abort()` in OOM case Closes GH-22637 --- NEWS | 1 + UPGRADING | 4 ++++ Zend/zend_alloc.c | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index b2544cb5abdf..2fa31ab34fde 100644 --- a/NEWS +++ b/NEWS @@ -11,6 +11,7 @@ PHP NEWS iterator). (iliaal) . Lock unmodified readonly properties for modification after clone-with. (NickSdot) + . abort() instead of exit() on hard OOM. (realFlowControl) - Calendar: . Fixed bug GH-22602 (gregoriantojd() and juliantojd() integer overflow with diff --git a/UPGRADING b/UPGRADING index a809687d1961..729dfed0363e 100644 --- a/UPGRADING +++ b/UPGRADING @@ -533,6 +533,10 @@ PHP 8.6 UPGRADE NOTES 13. Other Changes ======================================== +- Core: + . In case of a hard OOM PHP now calls abort() instead of exit(1), changing + the exit code to 134 and possibly creating a core dump. + ======================================== 14. Performance Improvements ======================================== diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c index 0b040743abf1..5ff2860d8223 100644 --- a/Zend/zend_alloc.c +++ b/Zend/zend_alloc.c @@ -2974,7 +2974,7 @@ ZEND_API void refresh_memory_manager(void) static ZEND_COLD ZEND_NORETURN void zend_out_of_memory(void) { fprintf(stderr, "Out of memory\n"); - exit(1); + abort(); } #if ZEND_MM_CUSTOM From 13169cd2fec73f9ea0ecac3c555618c3c32fb8aa Mon Sep 17 00:00:00 2001 From: Marc Date: Mon, 13 Jul 2026 17:59:42 +0700 Subject: [PATCH 6/8] json: Stop tracking columns in the success path (#22487) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of tracking columns during lexing, which costs performance for something that will only be required in the unexpected failure path, we now only track the line numbers and calculate the column on demand when an error occurs. As part of this we also fix GH-22487 by adjusting the logic to report the column based on the (UTF-8) column in the source, instead of treating `\uXXXX` escapes as a single column, which users will need to count manually instead of being able to rely on their editor or `mb_substr()` or similar. [tim: (re)written the commit message] Co-authored-by: Tim Düsterhus --- NEWS | 4 + ext/json/json.c | 2 +- ext/json/json_parser.y | 35 ++++++--- ext/json/json_scanner.re | 77 +++---------------- ext/json/php_json.h | 4 +- ext/json/php_json_parser.h | 8 -- ext/json/php_json_scanner.h | 16 +--- ext/json/tests/bug68546.phpt | 2 +- ext/json/tests/gh22514.phpt | 21 +++++ ...son_last_error_msg_error_location_005.phpt | 14 ++-- 10 files changed, 73 insertions(+), 110 deletions(-) create mode 100644 ext/json/tests/gh22514.phpt diff --git a/NEWS b/NEWS index 2fa31ab34fde..35eb80d675ca 100644 --- a/NEWS +++ b/NEWS @@ -65,6 +65,10 @@ PHP NEWS - JSON: . Report unterminated JSON strings as syntax errors. (timwolla) + . Improve performance error position tracking during JSON decoding. + (henderkes) + . Fixed bug GH-22514 (Incorrect error column in PHP 8.6 JSON parser). + (henderkes, timwolla) - Opcache: . Fixed bug GH-21770 (Infinite recursion in property hook getter in opcache diff --git a/ext/json/json.c b/ext/json/json.c index ac033c057ac4..04a62f52152f 100644 --- a/ext/json/json.c +++ b/ext/json/json.c @@ -184,7 +184,7 @@ static zend_string *php_json_get_error_msg_with_location(const php_json_error_de const char *base_msg = php_json_get_error_msg(details->code); if (details->line > 0 && details->column > 0) { - return zend_strpprintf(0, "%s near location %zu:%zu", base_msg, details->line, details->column); + return zend_strpprintf(0, "%s near location %" PRIu64 ":%" PRIu64, base_msg, details->line, details->column); } return zend_string_init(base_msg, strlen(base_msg), 0); diff --git a/ext/json/json_parser.y b/ext/json/json_parser.y index 0d3b90b29e1e..0fd3e2c4e364 100644 --- a/ext/json/json_parser.y +++ b/ext/json/json_parser.y @@ -39,7 +39,6 @@ int json_yydebug = 1; } -%locations %define api.prefix {php_json_yy} %define api.pure full %param { php_json_parser *parser } @@ -64,8 +63,8 @@ int json_yydebug = 1; %destructor { zval_ptr_dtor_nogc(&$$); } %code { -static int php_json_yylex(union YYSTYPE *value, YYLTYPE *location, php_json_parser *parser); -static void php_json_yyerror(YYLTYPE *location, php_json_parser *parser, char const *msg); +static int php_json_yylex(union YYSTYPE *value, php_json_parser *parser); +static void php_json_yyerror(php_json_parser *parser, char const *msg); static int php_json_parser_array_create(php_json_parser *parser, zval *array); static int php_json_parser_object_create(php_json_parser *parser, zval *array); @@ -275,7 +274,7 @@ static int php_json_parser_object_update_validate(php_json_parser *parser, zval return SUCCESS; } -static int php_json_yylex(union YYSTYPE *value, YYLTYPE *location, php_json_parser *parser) +static int php_json_yylex(union YYSTYPE *value, php_json_parser *parser) { int token = php_json_scan(&parser->scanner); @@ -291,15 +290,10 @@ static int php_json_yylex(union YYSTYPE *value, YYLTYPE *location, php_json_pars value->value = parser->scanner.value; } - location->first_column = PHP_JSON_SCANNER_LOCATION(parser->scanner, first_column); - location->first_line = PHP_JSON_SCANNER_LOCATION(parser->scanner, first_line); - location->last_column = PHP_JSON_SCANNER_LOCATION(parser->scanner, last_column); - location->last_line = PHP_JSON_SCANNER_LOCATION(parser->scanner, last_line); - return token; } -static void php_json_yyerror(YYLTYPE *location, php_json_parser *parser, char const *msg) +static void php_json_yyerror(php_json_parser *parser, char const *msg) { if (!parser->scanner.errcode) { parser->scanner.errcode = PHP_JSON_ERROR_SYNTAX; @@ -311,11 +305,28 @@ PHP_JSON_API php_json_error_code php_json_parser_error_code(const php_json_parse return parser->scanner.errcode; } +static uint64_t php_json_compute_error_column(const php_json_scanner *s) +{ + const php_json_ctype *p = s->line_start; + const php_json_ctype *end = s->token; + /* Count characters from the start of the line to the failing token, + * folding UTF-8 continuation bytes into their leading byte. */ + uint64_t column = 1; + + while (p < end) { + if ((*p & 0b11000000) != 0b10000000) { + column++; + } + p++; + } + return column; +} + PHP_JSON_API void php_json_parser_error_details(const php_json_parser *parser, php_json_error_details *out) { out->code = parser->scanner.errcode; - out->line = parser->scanner.errloc.first_line; - out->column = parser->scanner.errloc.first_column; + out->line = parser->scanner.line; + out->column = php_json_compute_error_column(&parser->scanner); } static const php_json_parser_methods default_parser_methods = diff --git a/ext/json/json_scanner.re b/ext/json/json_scanner.re index 0c64a6423baf..be62875a00e0 100644 --- a/ext/json/json_scanner.re +++ b/ext/json/json_scanner.re @@ -51,7 +51,6 @@ #define PHP_JSON_INT_MAX_LENGTH (MAX_LENGTH_OF_LONG - 1) #define PHP_JSON_TOKEN_LENGTH() ((size_t) (s->cursor - s->token)) -#define PHP_JSON_TOKEN_LOCATION(location) (s)->errloc.location static void php_json_scanner_copy_string(php_json_scanner *s, size_t esc_size) { @@ -96,10 +95,8 @@ void php_json_scanner_init(php_json_scanner *s, const char *str, size_t str_len, s->cursor = (php_json_ctype *) str; s->limit = (php_json_ctype *) str + str_len; s->options = options; - PHP_JSON_TOKEN_LOCATION(first_column) = 1; - PHP_JSON_TOKEN_LOCATION(first_line) = 1; - PHP_JSON_TOKEN_LOCATION(last_column) = 1; - PHP_JSON_TOKEN_LOCATION(last_line) = 1; + s->line = 1; + s->line_start = (php_json_ctype *) str; PHP_JSON_CONDITION_SET(JS); } @@ -108,8 +105,6 @@ int php_json_scan(php_json_scanner *s) ZVAL_NULL(&s->value); std: - PHP_JSON_TOKEN_LOCATION(first_column) = s->errloc.last_column; - PHP_JSON_TOKEN_LOCATION(first_line) = s->errloc.last_line; s->token = s->cursor; /*!re2c @@ -155,49 +150,27 @@ std: UTF16_3 = UTFPREF ( ( ( HEXC | [efEF] ) HEX ) | ( [dD] HEX7 ) ) HEX{2} ; UTF16_4 = UTFPREF [dD] [89abAB] HEX{2} UTFPREF [dD] [c-fC-F] HEX{2} ; - "{" { - PHP_JSON_TOKEN_LOCATION(last_column)++; - return '{'; - } - "}" { - PHP_JSON_TOKEN_LOCATION(last_column)++; - return '}'; - } - "[" { - PHP_JSON_TOKEN_LOCATION(last_column)++; - return '['; - } - "]" { - PHP_JSON_TOKEN_LOCATION(last_column)++; - return ']'; - } - ":" { - PHP_JSON_TOKEN_LOCATION(last_column)++; - return ':'; - } - "," { - PHP_JSON_TOKEN_LOCATION(last_column)++; - return ','; - } + "{" { return '{'; } + "}" { return '}'; } + "[" { return '['; } + "]" { return ']'; } + ":" { return ':'; } + "," { return ','; } "null" { - PHP_JSON_TOKEN_LOCATION(last_column) += 4; ZVAL_NULL(&s->value); return PHP_JSON_T_NUL; } "true" { - PHP_JSON_TOKEN_LOCATION(last_column) += 4; ZVAL_TRUE(&s->value); return PHP_JSON_T_TRUE; } "false" { - PHP_JSON_TOKEN_LOCATION(last_column) += 5; ZVAL_FALSE(&s->value); return PHP_JSON_T_FALSE; } INT { bool bigint = 0, negative = s->token[0] == '-'; size_t digits = PHP_JSON_TOKEN_LENGTH(); - PHP_JSON_TOKEN_LOCATION(last_column) += digits; digits -= negative; if (digits >= PHP_JSON_INT_MAX_LENGTH) { if (digits == PHP_JSON_INT_MAX_LENGTH) { @@ -221,19 +194,15 @@ std: } } FLOAT|EXP { - PHP_JSON_TOKEN_LOCATION(last_column) += PHP_JSON_TOKEN_LENGTH(); ZVAL_DOUBLE(&s->value, zend_strtod((char *) s->token, NULL)); return PHP_JSON_T_DOUBLE; } NL { - PHP_JSON_TOKEN_LOCATION(last_line)++; - PHP_JSON_TOKEN_LOCATION(last_column) = 1; - goto std; - } - WS { - PHP_JSON_TOKEN_LOCATION(last_column) += PHP_JSON_TOKEN_LENGTH(); + s->line++; + s->line_start = s->cursor; goto std; } + WS { goto std; } EOI { if (s->limit < s->cursor) { return PHP_JSON_T_EOI; @@ -243,7 +212,6 @@ std: } } ["] { - PHP_JSON_TOKEN_LOCATION(last_column)++; s->str_start = s->cursor; s->str_esc = 0; s->utf8_invalid = 0; @@ -275,22 +243,18 @@ std: return PHP_JSON_T_ERROR; } UTF16_1 { - PHP_JSON_TOKEN_LOCATION(last_column) += 1; s->str_esc += 5; PHP_JSON_CONDITION_GOTO(STR_P1); } UTF16_2 { - PHP_JSON_TOKEN_LOCATION(last_column) += 1; s->str_esc += 4; PHP_JSON_CONDITION_GOTO(STR_P1); } UTF16_3 { - PHP_JSON_TOKEN_LOCATION(last_column) += 1; s->str_esc += 3; PHP_JSON_CONDITION_GOTO(STR_P1); } UTF16_4 { - PHP_JSON_TOKEN_LOCATION(last_column) += 1; s->str_esc += 8; PHP_JSON_CONDITION_GOTO(STR_P1); } @@ -299,7 +263,6 @@ std: return PHP_JSON_T_ERROR; } ESC { - PHP_JSON_TOKEN_LOCATION(last_column) += 2; s->str_esc++; PHP_JSON_CONDITION_GOTO(STR_P1); } @@ -308,7 +271,6 @@ std: return PHP_JSON_T_ERROR; } ["] { - PHP_JSON_TOKEN_LOCATION(last_column)++; zend_string *str; size_t len = (size_t)(s->cursor - s->str_start - s->str_esc - 1 + s->utf8_invalid_count); if (len == 0) { @@ -329,22 +291,7 @@ std: return PHP_JSON_T_STRING; } } - UTF8_1 { - PHP_JSON_TOKEN_LOCATION(last_column)++; - PHP_JSON_CONDITION_GOTO(STR_P1); - } - UTF8_2 { - PHP_JSON_TOKEN_LOCATION(last_column) += 1; - PHP_JSON_CONDITION_GOTO(STR_P1); - } - UTF8_3 { - PHP_JSON_TOKEN_LOCATION(last_column) += 1; - PHP_JSON_CONDITION_GOTO(STR_P1); - } - UTF8_4 { - PHP_JSON_TOKEN_LOCATION(last_column) += 1; - PHP_JSON_CONDITION_GOTO(STR_P1); - } + UTF8 { PHP_JSON_CONDITION_GOTO(STR_P1); } ANY { if (s->options & (PHP_JSON_INVALID_UTF8_IGNORE | PHP_JSON_INVALID_UTF8_SUBSTITUTE)) { if (s->options & PHP_JSON_INVALID_UTF8_SUBSTITUTE) { diff --git a/ext/json/php_json.h b/ext/json/php_json.h index f34684e149d8..f20b20964a71 100644 --- a/ext/json/php_json.h +++ b/ext/json/php_json.h @@ -54,8 +54,8 @@ typedef enum { typedef struct php_json_error_details { php_json_error_code code; - size_t line; - size_t column; + uint64_t line; + uint64_t column; } php_json_error_details; static inline void php_json_error_details_clear(php_json_error_details *out) { diff --git a/ext/json/php_json_parser.h b/ext/json/php_json_parser.h index 888a0d317fe0..ae927d880aeb 100644 --- a/ext/json/php_json_parser.h +++ b/ext/json/php_json_parser.h @@ -48,20 +48,12 @@ typedef struct _php_json_parser_methods { php_json_parser_func_object_end_t object_end; } php_json_parser_methods; - typedef struct _php_json_parser_location { - size_t first_line; - size_t first_column; - size_t last_line; - size_t last_column; -} php_json_parser_location; - struct _php_json_parser { php_json_scanner scanner; zval *return_value; int depth; int max_depth; php_json_parser_methods methods; - php_json_parser_location *location; }; PHP_JSON_API void php_json_parser_init_ex( diff --git a/ext/json/php_json_scanner.h b/ext/json/php_json_scanner.h index 90460cf9952a..f432f66b6d78 100644 --- a/ext/json/php_json_scanner.h +++ b/ext/json/php_json_scanner.h @@ -20,17 +20,6 @@ typedef unsigned char php_json_ctype; -typedef struct _php_json_error_location { - /** first column of the error */ - size_t first_column; - /** first line of the error */ - size_t first_line; - /** last column of the error */ - size_t last_column; - /** last line of the error */ - size_t last_line; -} php_json_error_location; - typedef struct _php_json_scanner { php_json_ctype *cursor; /* cursor position */ php_json_ctype *token; /* token position */ @@ -39,18 +28,17 @@ typedef struct _php_json_scanner { php_json_ctype *ctxmarker; /* marker position for context backtracking */ php_json_ctype *str_start; /* start position of the string */ php_json_ctype *pstr; /* string pointer for escapes conversion */ + php_json_ctype *line_start; /* start position of the current line */ + uint64_t line; /* current line number (1-based) */ zval value; /* value */ int str_esc; /* number of extra characters for escaping */ int state; /* condition state */ int options; /* options */ php_json_error_code errcode; /* error type if there is an error */ - php_json_error_location errloc; /* error location */ int utf8_invalid; /* whether utf8 is invalid */ int utf8_invalid_count; /* number of extra character for invalid utf8 */ } php_json_scanner; -#define PHP_JSON_SCANNER_LOCATION(scanner, slocation) (scanner).errloc.slocation - void php_json_scanner_init(php_json_scanner *scanner, const char *str, size_t str_len, int options); int php_json_scan(php_json_scanner *s); diff --git a/ext/json/tests/bug68546.phpt b/ext/json/tests/bug68546.phpt index 1847eabf3a8e..40ea085ba23b 100644 --- a/ext/json/tests/bug68546.phpt +++ b/ext/json/tests/bug68546.phpt @@ -16,5 +16,5 @@ NULL bool(true) NULL bool(true) -string(55) "The decoded property name is invalid near location 1:27" +string(55) "The decoded property name is invalid near location 1:37" Done diff --git a/ext/json/tests/gh22514.phpt b/ext/json/tests/gh22514.phpt new file mode 100644 index 000000000000..9e7d2adfff8a --- /dev/null +++ b/ext/json/tests/gh22514.phpt @@ -0,0 +1,21 @@ +--TEST-- +GH-22514: Incorrect error column in JSON parser after escape sequences +--FILE-- + +--EXPECT-- +NULL +string(31) "Syntax error near location 2:10" +NULL +string(30) "Syntax error near location 2:5" diff --git a/ext/json/tests/json_last_error_msg_error_location_005.phpt b/ext/json/tests/json_last_error_msg_error_location_005.phpt index ee1800eb8273..ca798fcb01fe 100644 --- a/ext/json/tests/json_last_error_msg_error_location_005.phpt +++ b/ext/json/tests/json_last_error_msg_error_location_005.phpt @@ -19,8 +19,8 @@ json_validate_trycatchdump('{"test": "\uD83D\uDE00\uD83C\uDF89}'); echo "\nError with mixed UTF-8 and UTF-16:\n"; json_validate_trycatchdump('{"mixed": "Hello \u4E16\u754C world}'); -// UTF-16 in key and value -echo "\nError with UTF-16 in key:\n"; +// Missing closing quote after a UTF-16 key +echo "\nError on unterminated value after UTF-16 key:\n"; json_validate_trycatchdump('{"\u30D7\u30EC\u30B9": "value}'); // Multiple keys with UTF-16 @@ -66,15 +66,15 @@ bool(false) int(4) string(31) "Syntax error near location 1:11" -Error with UTF-16 in key: +Error on unterminated value after UTF-16 key: bool(false) int(4) -string(30) "Syntax error near location 1:9" +string(31) "Syntax error near location 1:24" Error with multiple UTF-16 keys: bool(false) int(4) -string(31) "Syntax error near location 1:22" +string(31) "Syntax error near location 1:42" Error with BMP characters: bool(false) @@ -89,12 +89,12 @@ string(31) "Syntax error near location 1:11" Error in array with UTF-16: bool(false) int(4) -string(31) "Syntax error near location 1:12" +string(31) "Syntax error near location 1:22" Error in nested structure with UTF-16: bool(false) int(4) -string(31) "Syntax error near location 1:18" +string(31) "Syntax error near location 1:28" Error with UTF-16 and control chars: bool(false) From 0b0284eb16dba22bcb6a56d8da9604b8ec4228cc Mon Sep 17 00:00:00 2001 From: henderkes Date: Sat, 4 Jul 2026 21:25:52 +0700 Subject: [PATCH 7/8] perf: move AG and SCNG into native __thread storage Replay of GH-22231 Closes GH-22595 --- NEWS | 1 + TSRM/TSRM.c | 66 ++++++++++++++++++++++++++++++++---- TSRM/TSRM.h | 4 +++ UPGRADING.INTERNALS | 8 +++++ Zend/zend.c | 6 ++-- Zend/zend_alloc.c | 16 +++------ Zend/zend_alloc.h | 4 --- Zend/zend_globals.h | 4 +-- Zend/zend_globals_macros.h | 14 ++++++-- Zend/zend_language_scanner.l | 6 +++- main/main.c | 6 +--- 11 files changed, 99 insertions(+), 36 deletions(-) diff --git a/NEWS b/NEWS index 35eb80d675ca..8c29466dcc64 100644 --- a/NEWS +++ b/NEWS @@ -12,6 +12,7 @@ PHP NEWS . Lock unmodified readonly properties for modification after clone-with. (NickSdot) . abort() instead of exit() on hard OOM. (realFlowControl) + . perf: ZTS: move AG and SCNG into native __thread storage. (henderkes) - Calendar: . Fixed bug GH-22602 (gregoriantojd() and juliantojd() integer overflow with diff --git a/TSRM/TSRM.c b/TSRM/TSRM.c index 4222e88755d6..a5032e456aae 100644 --- a/TSRM/TSRM.c +++ b/TSRM/TSRM.c @@ -41,6 +41,8 @@ typedef struct { ts_allocate_ctor ctor; ts_allocate_dtor dtor; ptrdiff_t fast_offset; + /* When set, storage comes from __thread memory instead of being allocated by TSRM. */ + void *(*tls_addr)(void); int done; } tsrm_resource_type; @@ -164,14 +166,20 @@ TSRM_API bool tsrm_startup(int expected_threads, int expected_resources, int deb static void ts_free_resources(tsrm_tls_entry *thread_resources) { + bool own_thread = thread_resources->thread_id == tsrm_thread_id(); + /* Need to destroy in reverse order to respect dependencies. */ for (int i = thread_resources->count - 1; i >= 0; i--) { if (!resource_types_table[i].done) { + /* A __thread block of a foreign thread is inaccessible. */ + if (resource_types_table[i].tls_addr && !own_thread) { + continue; + } if (resource_types_table[i].dtor) { resource_types_table[i].dtor(thread_resources->storage[i]); } - if (!resource_types_table[i].fast_offset) { + if (!resource_types_table[i].fast_offset && !resource_types_table[i].tls_addr) { free(thread_resources->storage[i]); } } @@ -180,7 +188,9 @@ static void ts_free_resources(tsrm_tls_entry *thread_resources) free(thread_resources->storage); } -/* Shutdown TSRM (call once for the entire process) */ +/* Shutdown TSRM (call once for the entire process). Tears down every thread left + * in the table. For resources allocated with ts_allocate_tls_id(), only the dtor + * of the calling thread is invoked. */ TSRM_API void tsrm_shutdown(void) {/*{{{*/ if (is_thread_shutdown) { @@ -258,7 +268,10 @@ static void tsrm_update_active_threads(void) p->storage = (void *) realloc(p->storage, sizeof(void *)*id_count); for (j=p->count; jthread_id == tsrm_thread_id()); + p->storage[j] = resource_types_table[j].tls_addr(); + } else if (resource_types_table[j].fast_offset) { p->storage[j] = (void *) (((char*)p) + resource_types_table[j].fast_offset); } else { p->storage[j] = (void *) malloc(resource_types_table[j].size); @@ -303,6 +316,7 @@ TSRM_API ts_rsrc_id ts_allocate_id(ts_rsrc_id *rsrc_id, size_t size, ts_allocate resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].ctor = ctor; resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].dtor = dtor; resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].fast_offset = 0; + resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].tls_addr = NULL; resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].done = 0; tsrm_update_active_threads(); @@ -381,6 +395,7 @@ TSRM_API ts_rsrc_id ts_allocate_fast_id_at(ts_rsrc_id *rsrc_id, size_t *offset, resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].ctor = ctor; resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].dtor = dtor; resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].fast_offset = *offset; + resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].tls_addr = NULL; resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].done = 0; tsrm_update_active_threads(); @@ -390,6 +405,41 @@ TSRM_API ts_rsrc_id ts_allocate_fast_id_at(ts_rsrc_id *rsrc_id, size_t *offset, return *rsrc_id; } +/* allocates a resource id whose per-thread storage is a native __thread block */ +TSRM_API ts_rsrc_id ts_allocate_tls_id(ts_rsrc_id *rsrc_id, void *(*tls_addr)(void), size_t size, ts_allocate_ctor ctor, ts_allocate_dtor dtor) +{ + TSRM_ERROR((TSRM_ERROR_LEVEL_CORE, "Obtaining a new TLS resource id, %d bytes", size)); + + tsrm_mutex_lock(tsmm_mutex); + + *rsrc_id = TSRM_SHUFFLE_RSRC_ID(id_count++); + + if (resource_types_table_size < id_count) { + tsrm_resource_type *_tmp; + _tmp = (tsrm_resource_type *) realloc(resource_types_table, sizeof(tsrm_resource_type)*id_count); + if (!_tmp) { + TSRM_ERROR((TSRM_ERROR_LEVEL_ERROR, "Unable to allocate storage for resource")); + *rsrc_id = 0; + tsrm_mutex_unlock(tsmm_mutex); + return 0; + } + resource_types_table = _tmp; + resource_types_table_size = id_count; + } + resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].size = size; + resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].ctor = ctor; + resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].dtor = dtor; + resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].fast_offset = 0; + resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].tls_addr = tls_addr; + resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].done = 0; + + tsrm_update_active_threads(); + tsrm_mutex_unlock(tsmm_mutex); + + TSRM_ERROR((TSRM_ERROR_LEVEL_CORE, "Successfully allocated new TLS resource id %d", *rsrc_id)); + return *rsrc_id; +} + static void set_thread_local_storage_resource_to(tsrm_tls_entry *thread_resource) { tsrm_tls_set(thread_resource); @@ -422,7 +472,9 @@ static void allocate_new_resource(tsrm_tls_entry **thread_resources_ptr, THREAD_ if (resource_types_table[i].done) { (*thread_resources_ptr)->storage[i] = NULL; } else { - if (resource_types_table[i].fast_offset) { + if (resource_types_table[i].tls_addr) { + (*thread_resources_ptr)->storage[i] = resource_types_table[i].tls_addr(); + } else if (resource_types_table[i].fast_offset) { (*thread_resources_ptr)->storage[i] = (void *) (((char*)(*thread_resources_ptr)) + resource_types_table[i].fast_offset); } else { (*thread_resources_ptr)->storage[i] = (void *) malloc(resource_types_table[i].size); @@ -510,7 +562,9 @@ TSRM_API void *ts_resource_ex(ts_rsrc_id id, THREAD_T *th_id) /* In case that extensions don't use the pointer passed from the dtor, but incorrectly * use the global pointer, we need to setup the global pointer temporarily here. */ set_thread_local_storage_resource_to(thread_resources); - /* Free up the old resource from the old thread instance */ + /* Dead thread with a recycled id: its __thread blocks are gone, and this + * thread's blocks were never constructed, so keep tls dtors from running. */ + thread_resources->thread_id = 0; ts_free_resources(thread_resources); free((char *) thread_resources - tsrm_reserved_front); /* Allocate a new resource at the same point in the linked list, and relink the next pointer */ @@ -584,7 +638,7 @@ void ts_free_id(ts_rsrc_id id) if (resource_types_table[rsrc_id].dtor) { resource_types_table[rsrc_id].dtor(p->storage[rsrc_id]); } - if (!resource_types_table[rsrc_id].fast_offset) { + if (!resource_types_table[rsrc_id].fast_offset && !resource_types_table[rsrc_id].tls_addr) { free(p->storage[rsrc_id]); } } diff --git a/TSRM/TSRM.h b/TSRM/TSRM.h index 2e8cbddfcda7..752ea1803f9c 100644 --- a/TSRM/TSRM.h +++ b/TSRM/TSRM.h @@ -100,6 +100,10 @@ TSRM_API ts_rsrc_id ts_allocate_fast_id(ts_rsrc_id *rsrc_id, size_t *offset, siz TSRM_API void tsrm_reserve_fast_front(size_t size); TSRM_API ts_rsrc_id ts_allocate_fast_id_at(ts_rsrc_id *rsrc_id, size_t *offset, ptrdiff_t fixed_offset, size_t size, ts_allocate_ctor ctor, ts_allocate_dtor dtor); +/* Resource whose per-thread storage is a native __thread block. + * Must be called at startup before any other thread exists. */ +TSRM_API ts_rsrc_id ts_allocate_tls_id(ts_rsrc_id *rsrc_id, void *(*tls_addr)(void), size_t size, ts_allocate_ctor ctor, ts_allocate_dtor dtor); + /* fetches the requested resource for the current thread */ TSRM_API void *ts_resource_ex(ts_rsrc_id id, THREAD_T *th_id); #define ts_resource(id) ts_resource_ex(id, NULL) diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS index 7775c9316040..462e567f3f36 100644 --- a/UPGRADING.INTERNALS +++ b/UPGRADING.INTERNALS @@ -235,3 +235,11 @@ PHP 8.6 INTERNALS UPGRADE NOTES ======================== 5. SAPI changes ======================== + +- SAPIs should explicitly release a thread's resources by calling + ts_free_thread() before terminating it. tsrm_shutdown() can only release the + resources of the calling thread, for resources allocated with + ts_allocate_tls_id(). + +- AG and SCNG are now allocated with ts_allocate_tls_id() and live in native + __thread storage on ZTS builds. diff --git a/Zend/zend.c b/Zend/zend.c index 9411b92a2018..07692db85196 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -54,6 +54,9 @@ ZEND_API int compiler_globals_id; ZEND_API int executor_globals_id; ZEND_API size_t compiler_globals_offset; ZEND_API size_t executor_globals_offset; +/* ts_allocate_tls_id takes a callback so each thread resolves its own block. + * A plain &language_scanner_globals would capture only the registering thread's address. */ +static void *language_scanner_globals_tls_addr(void) { return &language_scanner_globals; } static HashTable *global_function_table = NULL; static HashTable *global_class_table = NULL; static HashTable *global_constants_table = NULL; @@ -1021,10 +1024,9 @@ void zend_startup(zend_utility_functions *utility_functions) /* {{{ */ #ifdef ZTS ts_allocate_fast_id_at(&compiler_globals_id, &compiler_globals_offset, ZEND_CG_OFFSET, sizeof(zend_compiler_globals), (ts_allocate_ctor) compiler_globals_ctor, (ts_allocate_dtor) compiler_globals_dtor); ts_allocate_fast_id_at(&executor_globals_id, &executor_globals_offset, ZEND_EG_OFFSET, sizeof(zend_executor_globals), (ts_allocate_ctor) executor_globals_ctor, (ts_allocate_dtor) executor_globals_dtor); - ts_allocate_fast_id_at(&language_scanner_globals_id, &language_scanner_globals_offset, ZEND_SCNG_OFFSET, sizeof(zend_php_scanner_globals), (ts_allocate_ctor) php_scanner_globals_ctor, NULL); + ts_allocate_tls_id(&language_scanner_globals_id, language_scanner_globals_tls_addr, sizeof(zend_php_scanner_globals), (ts_allocate_ctor) php_scanner_globals_ctor, NULL); ZEND_ASSERT(compiler_globals_offset == ZEND_CG_OFFSET); ZEND_ASSERT(executor_globals_offset == ZEND_EG_OFFSET); - ZEND_ASSERT(language_scanner_globals_offset == ZEND_SCNG_OFFSET); ts_allocate_fast_id(&ini_scanner_globals_id, &ini_scanner_globals_offset, sizeof(zend_ini_scanner_globals), (ts_allocate_ctor) ini_scanner_globals_ctor, NULL); compiler_globals = ts_resource(compiler_globals_id); executor_globals = ts_resource(executor_globals_id); diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c index 5ff2860d8223..fc7bc1f4d9d4 100644 --- a/Zend/zend_alloc.c +++ b/Zend/zend_alloc.c @@ -2613,13 +2613,12 @@ typedef struct _zend_alloc_globals { #ifdef ZTS static int alloc_globals_id; -static size_t alloc_globals_offset; -# define ZEND_AG_OFFSET (ZEND_SCNG_OFFSET - (ptrdiff_t) TSRM_ALIGNED_SIZE(sizeof(zend_alloc_globals))) -# define AG(v) ZEND_TSRMG_FAST(ZEND_AG_OFFSET, zend_alloc_globals *, v) +static TSRM_TLS TSRM_TLS_MODEL_ATTR zend_alloc_globals alloc_globals; +static void *alloc_globals_tls_addr(void) { return &alloc_globals; } #else -# define AG(v) (alloc_globals.v) static zend_alloc_globals alloc_globals; #endif +#define AG(v) (alloc_globals.v) ZEND_API bool is_zend_mm(void) { @@ -3336,8 +3335,7 @@ ZEND_API void start_memory_manager(void) # endif #endif #ifdef ZTS - ts_allocate_fast_id_at(&alloc_globals_id, &alloc_globals_offset, ZEND_AG_OFFSET, sizeof(zend_alloc_globals), (ts_allocate_ctor) alloc_globals_ctor, (ts_allocate_dtor) alloc_globals_dtor); - ZEND_ASSERT(alloc_globals_offset == ZEND_AG_OFFSET); + ts_allocate_tls_id(&alloc_globals_id, alloc_globals_tls_addr, sizeof(zend_alloc_globals), (ts_allocate_ctor) alloc_globals_ctor, (ts_allocate_dtor) alloc_globals_dtor); #else alloc_globals_ctor(&alloc_globals); #endif @@ -3583,9 +3581,3 @@ ZEND_API char * __zend_strdup(const char *s) zend_out_of_memory(); } -#ifdef ZTS -size_t zend_mm_globals_size(void) -{ - return sizeof(zend_alloc_globals); -} -#endif diff --git a/Zend/zend_alloc.h b/Zend/zend_alloc.h index ff51c4fe8652..7e3ea031dc72 100644 --- a/Zend/zend_alloc.h +++ b/Zend/zend_alloc.h @@ -403,10 +403,6 @@ static void apc_init_heap(void) */ -#ifdef ZTS -size_t zend_mm_globals_size(void); -#endif - END_EXTERN_C() #endif diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index 61499c0cc23d..f78567cfaa74 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -330,11 +330,9 @@ struct _zend_executor_globals { }; #ifdef ZTS -/* Compile-time offsets of the hot globals, in a reserved region just before the - * cache pointer. ZEND_AG_OFFSET is furthest, in zend_alloc.c. */ +/* Compile-time offsets of the hot globals, in a reserved region just before *_tsrm_ls_cache. */ # define ZEND_CG_OFFSET (-(ptrdiff_t) TSRM_ALIGNED_SIZE(sizeof(zend_compiler_globals))) # define ZEND_EG_OFFSET (ZEND_CG_OFFSET - (ptrdiff_t) TSRM_ALIGNED_SIZE(sizeof(zend_executor_globals))) -# define ZEND_SCNG_OFFSET (ZEND_EG_OFFSET - (ptrdiff_t) TSRM_ALIGNED_SIZE(sizeof(zend_php_scanner_globals))) #endif #define EG_FLAGS_INITIAL (0) diff --git a/Zend/zend_globals_macros.h b/Zend/zend_globals_macros.h index 2d2948e50a86..adb3913ece71 100644 --- a/Zend/zend_globals_macros.h +++ b/Zend/zend_globals_macros.h @@ -48,12 +48,20 @@ extern ZEND_API zend_executor_globals executor_globals; /* Language Scanner */ #ifdef ZTS -# define LANG_SCNG(v) ZEND_TSRMG_FAST(ZEND_SCNG_OFFSET, zend_php_scanner_globals *, v) extern ZEND_API ts_rsrc_id language_scanner_globals_id; -extern ZEND_API size_t language_scanner_globals_offset; +# if defined(ZEND_WIN32) && !defined(LIBZEND_EXPORTS) +# define LANG_SCNG(v) TSRMG(language_scanner_globals_id, zend_php_scanner_globals *, v) +# else +# ifdef ZEND_WIN32 +extern TSRM_TLS zend_php_scanner_globals language_scanner_globals; +# else +extern ZEND_API TSRM_TLS TSRM_TLS_MODEL_ATTR zend_php_scanner_globals language_scanner_globals; +# endif +# define LANG_SCNG(v) (language_scanner_globals.v) +# endif #else -# define LANG_SCNG(v) (language_scanner_globals.v) extern ZEND_API zend_php_scanner_globals language_scanner_globals; +# define LANG_SCNG(v) (language_scanner_globals.v) #endif diff --git a/Zend/zend_language_scanner.l b/Zend/zend_language_scanner.l index 07f2d44cb5c6..1d7e41a2a7d3 100644 --- a/Zend/zend_language_scanner.l +++ b/Zend/zend_language_scanner.l @@ -84,7 +84,11 @@ #define SCNG LANG_SCNG #ifdef ZTS ZEND_API ts_rsrc_id language_scanner_globals_id; -ZEND_API size_t language_scanner_globals_offset; +# ifdef ZEND_WIN32 +TSRM_TLS zend_php_scanner_globals language_scanner_globals; +# else +ZEND_API TSRM_TLS TSRM_TLS_MODEL_ATTR zend_php_scanner_globals language_scanner_globals; +# endif #else ZEND_API zend_php_scanner_globals language_scanner_globals; #endif diff --git a/main/main.c b/main/main.c index 48e4a757513b..9e77c99a45df 100644 --- a/main/main.c +++ b/main/main.c @@ -2819,13 +2819,11 @@ PHPAPI void php_reserve_tsrm_memory(void) tsrm_reserve( TSRM_ALIGNED_SIZE(sizeof(zend_compiler_globals)) + TSRM_ALIGNED_SIZE(sizeof(zend_executor_globals)) + - TSRM_ALIGNED_SIZE(sizeof(zend_php_scanner_globals)) + TSRM_ALIGNED_SIZE(sizeof(zend_ini_scanner_globals)) + TSRM_ALIGNED_SIZE(sizeof(virtual_cwd_globals)) + #ifdef ZEND_SIGNALS TSRM_ALIGNED_SIZE(sizeof(zend_signal_globals_t)) + #endif - TSRM_ALIGNED_SIZE(zend_mm_globals_size()) + TSRM_ALIGNED_SIZE(zend_gc_globals_size()) + TSRM_ALIGNED_SIZE(sizeof(php_core_globals)) + TSRM_ALIGNED_SIZE(sizeof(sapi_globals_struct)) + @@ -2845,9 +2843,7 @@ PHPAPI bool php_tsrm_startup_ex(int expected_threads) /* Must cover the total size of every ZEND_*_OFFSET global, or the furthest underflows the block. */ tsrm_reserve_fast_front( TSRM_ALIGNED_SIZE(sizeof(zend_compiler_globals)) + - TSRM_ALIGNED_SIZE(sizeof(zend_executor_globals)) + - TSRM_ALIGNED_SIZE(sizeof(zend_php_scanner_globals)) + - TSRM_ALIGNED_SIZE(zend_mm_globals_size())); // AG size, exposed through function call + TSRM_ALIGNED_SIZE(sizeof(zend_executor_globals))); (void)ts_resource(0); return ret; } From a095c57f03293109d8391c08aeaac24ab04abe6c Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Wed, 8 Jul 2026 13:50:15 -0400 Subject: [PATCH 8/8] Alpine CI: don't install gnu-libiconv-dev If we do not install GNU libiconv, the musl libc iconv() implementation should take over, allowing it to be tested on the CI. Closes GH-22645 --- .github/actions/apk/action.yml | 1 - .github/actions/configure-alpine/action.yml | 2 +- ext/iconv/tests/bug48147.phpt | 32 ++++++++++++++++++--- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/.github/actions/apk/action.yml b/.github/actions/apk/action.yml index 0cda4963a6a9..8d0fee161187 100644 --- a/.github/actions/apk/action.yml +++ b/.github/actions/apk/action.yml @@ -28,7 +28,6 @@ runs: curl-dev \ freetype-dev \ gettext-dev \ - gnu-libiconv-dev \ gmp-dev \ icu-dev \ icu-data-full \ diff --git a/.github/actions/configure-alpine/action.yml b/.github/actions/configure-alpine/action.yml index fe02dacfcdaf..d1b4cfea0412 100644 --- a/.github/actions/configure-alpine/action.yml +++ b/.github/actions/configure-alpine/action.yml @@ -43,7 +43,7 @@ runs: --enable-pcntl \ --with-readline \ --enable-mbstring \ - --with-iconv=/usr \ + --with-iconv \ --with-curl \ --with-gettext \ --enable-sockets \ diff --git a/ext/iconv/tests/bug48147.phpt b/ext/iconv/tests/bug48147.phpt index ce304eecfb3c..434f71e517bb 100644 --- a/ext/iconv/tests/bug48147.phpt +++ b/ext/iconv/tests/bug48147.phpt @@ -2,17 +2,41 @@ Bug #48147 (iconv with //IGNORE cuts the string) --EXTENSIONS-- iconv +--SKIPIF-- + --FILE-- --EXPECTF-- Notice: iconv(): Detected an illegal character in input string in %s on line %d @@ -20,8 +44,8 @@ bool(false) string(10) "aa%C3%B8aa" Notice: iconv(): Detected an incomplete multibyte character in input string in %s on line %d -string(0) "" +bool(false) string(8) "%C3%B8aa" Notice: iconv(): Detected an incomplete multibyte character in input string in %s on line %d -string(0) "" +bool(false)