From 3d468a181a73a83535114b6cf82343ca0b0f45e8 Mon Sep 17 00:00:00 2001 From: Calvin Buckley Date: Tue, 15 Jul 2025 13:46:17 -0300 Subject: [PATCH 01/66] PHP-8.4 is now for PHP 8.4.12-dev --- NEWS | 6 +++++- Zend/zend.h | 2 +- configure.ac | 2 +- main/php_version.h | 6 +++--- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/NEWS b/NEWS index 5190b9b563a8e..e0b23bddcc5d9 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,10 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| -?? ??? ????, PHP 8.4.11 +?? ??? ????, PHP 8.4.12 + + + +31 Jul 2025, PHP 8.4.11 - Calendar: . Fixed jewishtojd overflow on year argument. (David Carlier) diff --git a/Zend/zend.h b/Zend/zend.h index 682dc813b5a6f..0e34aeef9f9e4 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -20,7 +20,7 @@ #ifndef ZEND_H #define ZEND_H -#define ZEND_VERSION "4.4.11-dev" +#define ZEND_VERSION "4.4.12-dev" #define ZEND_ENGINE_3 diff --git a/configure.ac b/configure.ac index 53481b10c056a..dd13a9278dec0 100644 --- a/configure.ac +++ b/configure.ac @@ -17,7 +17,7 @@ dnl Basic autoconf initialization, generation of config.nice. dnl ---------------------------------------------------------------------------- AC_PREREQ([2.68]) -AC_INIT([PHP],[8.4.11-dev],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) +AC_INIT([PHP],[8.4.12-dev],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) AC_CONFIG_SRCDIR([main/php_version.h]) AC_CONFIG_AUX_DIR([build]) AC_PRESERVE_HELP_ORDER diff --git a/main/php_version.h b/main/php_version.h index e518de26b2f04..0758d83aed3d3 100644 --- a/main/php_version.h +++ b/main/php_version.h @@ -2,7 +2,7 @@ /* edit configure.ac to change version number */ #define PHP_MAJOR_VERSION 8 #define PHP_MINOR_VERSION 4 -#define PHP_RELEASE_VERSION 11 +#define PHP_RELEASE_VERSION 12 #define PHP_EXTRA_VERSION "-dev" -#define PHP_VERSION "8.4.11-dev" -#define PHP_VERSION_ID 80411 +#define PHP_VERSION "8.4.12-dev" +#define PHP_VERSION_ID 80412 From a8086be81c088db439dda157d330dfd69e38bcc7 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Wed, 2 Jul 2025 22:04:09 +0200 Subject: [PATCH 02/66] Fix GH-18986: OpenSSL backend: incorrect RAND_{load,write}_file() return value check As noted by the LibreSSL maintainer, these functions return -1 on error. This is further confirmed by my static analyzer that inferred the same thing for OpenSSL. Closes GH-19013. --- NEWS | 2 ++ ext/openssl/openssl.c | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index c395d0dcd3b59..42de159acbaab 100644 --- a/NEWS +++ b/NEWS @@ -37,6 +37,8 @@ PHP NEWS - OpenSSL: . Fixed bug #80770 (It is not possible to get client peer certificate with stream_socket_server). (Jakub Zelenka) + . Fixed bug GH-18986 (OpenSSL backend: incorrect RAND_{load,write}_file() + return value check). (nielsdos, botovq) - PCNTL: . Fixed bug GH-18958 (Fatal error during shutdown after pcntl_rfork() or diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index 718f946ad176d..e44bc90bbd7f8 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -1095,7 +1095,7 @@ static int php_openssl_load_rand_file(const char * file, int *egdsocket, int *se return SUCCESS; #endif } - if (file == NULL || !RAND_load_file(file, -1)) { + if (file == NULL || RAND_load_file(file, -1) < 0) { if (RAND_status() == 0) { php_openssl_store_errors(); php_error_docref(NULL, E_WARNING, "Unable to load random state; not enough random data!"); @@ -1122,7 +1122,7 @@ static int php_openssl_write_rand_file(const char * file, int egdsocket, int see file = RAND_file_name(buffer, sizeof(buffer)); } PHP_OPENSSL_RAND_ADD_TIME(); - if (file == NULL || !RAND_write_file(file)) { + if (file == NULL || RAND_write_file(file) < 0) { php_openssl_store_errors(); php_error_docref(NULL, E_WARNING, "Unable to write random state"); return FAILURE; From b495ce03716bbfa469c5eafb244205153abfb883 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Tue, 15 Jul 2025 19:09:08 +0200 Subject: [PATCH 03/66] Fix memleak on failure in collator_get_sort_key() Closes GH-19138. --- NEWS | 3 +++ ext/intl/collator/collator_sort.c | 1 + 2 files changed, 4 insertions(+) diff --git a/NEWS b/NEWS index 42de159acbaab..0b66c5447a1fa 100644 --- a/NEWS +++ b/NEWS @@ -17,6 +17,9 @@ PHP NEWS (nielsdos) . Remove incorrect string release. (nielsdos) +- Intl: + . Fix memleak on failure in collator_get_sort_key(). (nielsdos) + - LDAP: . Fixed GH-18902 ldap_exop/ldap_exop_sync assert triggered on empty request OID. (David Carlier) diff --git a/ext/intl/collator/collator_sort.c b/ext/intl/collator/collator_sort.c index 0634e68fc7a36..b610a8aa6c789 100644 --- a/ext/intl/collator/collator_sort.c +++ b/ext/intl/collator/collator_sort.c @@ -556,6 +556,7 @@ PHP_FUNCTION( collator_get_sort_key ) key_len = ucol_getSortKey(co->ucoll, ustr, ustr_len, (uint8_t*)ZSTR_VAL(key_str), key_len); efree( ustr ); if(!key_len) { + zend_string_efree(key_str); RETURN_FALSE; } ZSTR_LEN(key_str) = key_len - 1; From cd8722304caebe91f86964643c27fc763bda92da Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Tue, 1 Jul 2025 00:07:22 +0200 Subject: [PATCH 04/66] Fix error return check of EVP_CIPHER_CTX_ctrl() OpenSSL can return -1 on error [1, 2], and OpenBSD's docs confirm this [3]. Change all checks to <= 0. [1] https://github.com/openssl/openssl/blob/b3161bd9a9329be3d6bf6b29a06835e2721898bb/crypto/evp/evp_enc.c#L1530-L1531 [2] https://github.com/openssl/openssl/blob/b3161bd9a9329be3d6bf6b29a06835e2721898bb/crypto/evp/evp_enc.c#L1611 [3] https://man.openbsd.org/EVP_CIPHER_CTX_ctrl.3 Closes GH-18987. --- NEWS | 1 + ext/openssl/openssl.c | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index 0b66c5447a1fa..81e23e6c6ff63 100644 --- a/NEWS +++ b/NEWS @@ -42,6 +42,7 @@ PHP NEWS stream_socket_server). (Jakub Zelenka) . Fixed bug GH-18986 (OpenSSL backend: incorrect RAND_{load,write}_file() return value check). (nielsdos, botovq) + . Fix error return check of EVP_CIPHER_CTX_ctrl(). (nielsdos) - PCNTL: . Fixed bug GH-18958 (Fatal error during shutdown after pcntl_rfork() or diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index e44bc90bbd7f8..a0418e74ba7d6 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -7475,7 +7475,7 @@ static int php_openssl_validate_iv(const char **piv, size_t *piv_len, size_t iv_ char *iv_new; if (mode->is_aead) { - if (EVP_CIPHER_CTX_ctrl(cipher_ctx, mode->aead_ivlen_flag, *piv_len, NULL) != 1) { + if (EVP_CIPHER_CTX_ctrl(cipher_ctx, mode->aead_ivlen_flag, *piv_len, NULL) <= 0) { php_error_docref(NULL, E_WARNING, "Setting of IV length for AEAD mode failed"); return FAILURE; } @@ -7547,7 +7547,7 @@ static int php_openssl_cipher_init(const EVP_CIPHER *cipher_type, return FAILURE; } if (mode->set_tag_length_always || (enc && mode->set_tag_length_when_encrypting)) { - if (!EVP_CIPHER_CTX_ctrl(cipher_ctx, mode->aead_set_tag_flag, tag_len, NULL)) { + if (EVP_CIPHER_CTX_ctrl(cipher_ctx, mode->aead_set_tag_flag, tag_len, NULL) <= 0) { php_error_docref(NULL, E_WARNING, "Setting tag length for AEAD cipher failed"); return FAILURE; } @@ -7555,7 +7555,7 @@ static int php_openssl_cipher_init(const EVP_CIPHER *cipher_type, if (!enc && tag && tag_len > 0) { if (!mode->is_aead) { php_error_docref(NULL, E_WARNING, "The tag cannot be used because the cipher algorithm does not support AEAD"); - } else if (!EVP_CIPHER_CTX_ctrl(cipher_ctx, mode->aead_set_tag_flag, tag_len, (unsigned char *) tag)) { + } else if (EVP_CIPHER_CTX_ctrl(cipher_ctx, mode->aead_set_tag_flag, tag_len, (unsigned char *) tag) <= 0) { php_error_docref(NULL, E_WARNING, "Setting tag for AEAD cipher decryption failed"); return FAILURE; } @@ -7693,7 +7693,7 @@ PHP_OPENSSL_API zend_string* php_openssl_encrypt( if (mode.is_aead && tag) { zend_string *tag_str = zend_string_alloc(tag_len, 0); - if (EVP_CIPHER_CTX_ctrl(cipher_ctx, mode.aead_get_tag_flag, tag_len, ZSTR_VAL(tag_str)) == 1) { + if (EVP_CIPHER_CTX_ctrl(cipher_ctx, mode.aead_get_tag_flag, tag_len, ZSTR_VAL(tag_str)) > 0) { ZSTR_VAL(tag_str)[tag_len] = '\0'; ZSTR_LEN(tag_str) = tag_len; ZEND_TRY_ASSIGN_REF_NEW_STR(tag, tag_str); From faf833bffc9ec448c69b48f6cafab4ae759281b9 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Wed, 16 Jul 2025 14:09:24 +0200 Subject: [PATCH 05/66] PHP 8.3 is now for PHP-8.3.25-dev --- NEWS | 13 +++++++++---- Zend/zend.h | 2 +- configure.ac | 2 +- main/php_version.h | 6 +++--- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/NEWS b/NEWS index 81e23e6c6ff63..4fa5dda65c3db 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,14 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| -?? ??? ????, PHP 8.3.24 +?? ??? ????, PHP 8.3.25 + +OpenSSL: + . Fixed bug GH-18986 (OpenSSL backend: incorrect RAND_{load,write}_file() + return value check). (nielsdos, botovq) + . Fix error return check of EVP_CIPHER_CTX_ctrl(). (nielsdos) + + +31 Jul 2025, PHP 8.3.24 - Calendar: . Fixed jewishtojd overflow on year argument. (David Carlier) @@ -40,9 +48,6 @@ PHP NEWS - OpenSSL: . Fixed bug #80770 (It is not possible to get client peer certificate with stream_socket_server). (Jakub Zelenka) - . Fixed bug GH-18986 (OpenSSL backend: incorrect RAND_{load,write}_file() - return value check). (nielsdos, botovq) - . Fix error return check of EVP_CIPHER_CTX_ctrl(). (nielsdos) - PCNTL: . Fixed bug GH-18958 (Fatal error during shutdown after pcntl_rfork() or diff --git a/Zend/zend.h b/Zend/zend.h index 0d2c52e2505e2..df90c452654a9 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -20,7 +20,7 @@ #ifndef ZEND_H #define ZEND_H -#define ZEND_VERSION "4.3.24-dev" +#define ZEND_VERSION "4.3.25-dev" #define ZEND_ENGINE_3 diff --git a/configure.ac b/configure.ac index e96c12486fa2c..822184e809960 100644 --- a/configure.ac +++ b/configure.ac @@ -17,7 +17,7 @@ dnl Basic autoconf initialization, generation of config.nice. dnl ---------------------------------------------------------------------------- AC_PREREQ([2.68]) -AC_INIT([PHP],[8.3.24-dev],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) +AC_INIT([PHP],[8.3.25-dev],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) AC_CONFIG_SRCDIR([main/php_version.h]) AC_CONFIG_AUX_DIR([build]) AC_PRESERVE_HELP_ORDER diff --git a/main/php_version.h b/main/php_version.h index cf74940556ddc..80c0dc560653b 100644 --- a/main/php_version.h +++ b/main/php_version.h @@ -2,7 +2,7 @@ /* edit configure.ac to change version number */ #define PHP_MAJOR_VERSION 8 #define PHP_MINOR_VERSION 3 -#define PHP_RELEASE_VERSION 24 +#define PHP_RELEASE_VERSION 25 #define PHP_EXTRA_VERSION "-dev" -#define PHP_VERSION "8.3.24-dev" -#define PHP_VERSION_ID 80324 +#define PHP_VERSION "8.3.25-dev" +#define PHP_VERSION_ID 80325 From eade5c17ead650b0735295214bbec84872465e87 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Tue, 27 May 2025 19:03:56 +0200 Subject: [PATCH 06/66] Fix GH-18529: additional inheriting of TLS int options This is for LDAP_OPT_X_TLS_PROTOCOL_MIN and LDAP_OPT_X_TLS_PROTOCOL_MAX It also adds a test that uses LDAPCONF with TLS max version lower than the minimum TLS server version so it should always fail. However it does not fial for the second case without this change which confirms that the change works as expected. Closes GH-18676 --- .github/scripts/setup-slapd.sh | 3 ++ NEWS | 6 ++- ext/ldap/ldap.c | 32 ++++++++++++--- .../tests/ldap_start_tls_rc_max_version.conf | 1 + .../tests/ldap_start_tls_rc_max_version.phpt | 41 +++++++++++++++++++ ext/ldap/tests/skipifbindfailure.inc | 33 +++++++++++++++ 6 files changed, 110 insertions(+), 6 deletions(-) create mode 100644 ext/ldap/tests/ldap_start_tls_rc_max_version.conf create mode 100644 ext/ldap/tests/ldap_start_tls_rc_max_version.phpt diff --git a/.github/scripts/setup-slapd.sh b/.github/scripts/setup-slapd.sh index fcaa67d0a5f7d..f6b976783c77e 100755 --- a/.github/scripts/setup-slapd.sh +++ b/.github/scripts/setup-slapd.sh @@ -72,6 +72,9 @@ olcTLSCertificateKeyFile: /etc/ldap/ssl/server.key add: olcTLSVerifyClient olcTLSVerifyClient: never - +add: olcTLSProtocolMin +olcTLSProtocolMin: 3.3 +- add: olcAuthzRegexp olcAuthzRegexp: uid=usera,cn=digest-md5,cn=auth cn=usera,dc=my-domain,dc=com - diff --git a/NEWS b/NEWS index 4fa5dda65c3db..06427de7f9652 100644 --- a/NEWS +++ b/NEWS @@ -2,7 +2,11 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.3.25 -OpenSSL: +- LDAP: + . Fixed bug GH-18529 (additional inheriting of TLS int options). + (Jakub Zelenka) + +- OpenSSL: . Fixed bug GH-18986 (OpenSSL backend: incorrect RAND_{load,write}_file() return value check). (nielsdos, botovq) . Fix error return check of EVP_CIPHER_CTX_ctrl(). (nielsdos) diff --git a/ext/ldap/ldap.c b/ext/ldap/ldap.c index 769e6caa277b4..0b0a0c21df4ad 100644 --- a/ext/ldap/ldap.c +++ b/ext/ldap/ldap.c @@ -3732,7 +3732,8 @@ PHP_FUNCTION(ldap_rename_ext) */ static int _php_ldap_tls_newctx(LDAP *ld) { - int val = 0, i, opts[] = { + int val = 0, i; + int str_opts[] = { #if (LDAP_API_VERSION > 2000) LDAP_OPT_X_TLS_CACERTDIR, LDAP_OPT_X_TLS_CACERTFILE, @@ -3752,21 +3753,42 @@ static int _php_ldap_tls_newctx(LDAP *ld) #endif 0}; - for (i=0 ; opts[i] ; i++) { + for (i=0 ; str_opts[i] ; i++) { char *path = NULL; - ldap_get_option(ld, opts[i], &path); + ldap_get_option(ld, str_opts[i], &path); if (path) { /* already set locally */ ldap_memfree(path); } else { - ldap_get_option(NULL, opts[i], &path); + ldap_get_option(NULL, str_opts[i], &path); if (path) { /* set globally, inherit */ - ldap_set_option(ld, opts[i], path); + ldap_set_option(ld, str_opts[i], path); ldap_memfree(path); } } } +#ifdef LDAP_OPT_X_TLS_PROTOCOL_MIN + int int_opts[] = { + LDAP_OPT_X_TLS_PROTOCOL_MIN, +#ifdef LDAP_OPT_X_TLS_PROTOCOL_MAX + LDAP_OPT_X_TLS_PROTOCOL_MAX, +#endif + 0 + }; + for (i=0 ; int_opts[i] ; i++) { + int value = 0; + + ldap_get_option(ld, int_opts[i], &value); + if (value <= 0) { /* if value is not set already */ + ldap_get_option(NULL, int_opts[i], &value); + if (value > 0) { /* set globally, inherit */ + ldap_set_option(ld, int_opts[i], &value); + } + } + } +#endif + return ldap_set_option(ld, LDAP_OPT_X_TLS_NEWCTX, &val); } diff --git a/ext/ldap/tests/ldap_start_tls_rc_max_version.conf b/ext/ldap/tests/ldap_start_tls_rc_max_version.conf new file mode 100644 index 0000000000000..0cd03f8b8e191 --- /dev/null +++ b/ext/ldap/tests/ldap_start_tls_rc_max_version.conf @@ -0,0 +1 @@ +TLS_PROTOCOL_MAX 3.2 \ No newline at end of file diff --git a/ext/ldap/tests/ldap_start_tls_rc_max_version.phpt b/ext/ldap/tests/ldap_start_tls_rc_max_version.phpt new file mode 100644 index 0000000000000..359785f8b5a34 --- /dev/null +++ b/ext/ldap/tests/ldap_start_tls_rc_max_version.phpt @@ -0,0 +1,41 @@ +--TEST-- +ldap_start_tls() - Basic ldap_start_tls test +--EXTENSIONS-- +ldap +--ENV-- +LDAPCONF={PWD}/ldap_start_tls_rc_max_version.conf +--SKIPIF-- + "OpenLDAP", + "min_version" => 20600, +]; +require_once __DIR__ .'/skipifbindfailure.inc'; +?> +--FILE-- + +--EXPECT-- +bool(false) +bool(false) +bool(false) diff --git a/ext/ldap/tests/skipifbindfailure.inc b/ext/ldap/tests/skipifbindfailure.inc index 1a0d0c6d1998b..81c7998cfbb59 100644 --- a/ext/ldap/tests/skipifbindfailure.inc +++ b/ext/ldap/tests/skipifbindfailure.inc @@ -10,4 +10,37 @@ if ($skip_on_bind_failure) { ldap_unbind($link); } + +if (isset($require_vendor)) { + ob_start(); + phpinfo(INFO_MODULES); + $phpinfo = ob_get_clean(); + + // Extract the LDAP section specifically + if (preg_match('/^ldap\s*$(.*?)^[a-z_]+\s*$/ims', $phpinfo, $ldap_section_match)) { + $ldap_section = $ldap_section_match[1]; + + // Extract vendor info from the LDAP section only + if (preg_match('/Vendor Name\s*=>\s*(.+)/i', $ldap_section, $name_match) && + preg_match('/Vendor Version\s*=>\s*(\d+)/i', $ldap_section, $version_match)) { + + $vendor_name = trim($name_match[1]); + $vendor_version = (int)$version_match[1]; + + // Check vendor name if specified + if (isset($require_vendor['name']) && $vendor_name !== $require_vendor['name']) { + die("skip Requires {$require_vendor['name']} (detected: $vendor_name)"); + } + + // Check minimum version if specified + if (isset($require_vendor['min_version']) && $vendor_version < $require_vendor['min_version']) { + die("skip Requires minimum version {$require_vendor['min_version']} (detected: $vendor_version)"); + } + } else { + die("skip Cannot determine LDAP vendor information"); + } + } else { + die("skip LDAP extension information not found"); + } +} ?> From a5df26691d1b1f71b964b869510473a5d5413999 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Tue, 15 Jul 2025 13:36:56 +0200 Subject: [PATCH 07/66] ext/xml: Suppress libxml deprecation for `_xmlParserCtxt.inState` (#19131) The FreeBSD build fails due to the deprecation and -Werror: 2025-07-15T00:37:20.8390774Z /home/runner/work/php-src/php-src/ext/xml/compat.c:358:38: error: 'instate' is deprecated [-Werror,-Wdeprecated-declarations] 2025-07-15T00:37:20.8392577Z 358 | if (ret == NULL || parser->parser->instate == XML_PARSER_CONTENT) { 2025-07-15T00:37:20.8393184Z | ^ 2025-07-15T00:37:20.8394006Z /usr/local/include/libxml2/libxml/parser.h:309:33: note: 'instate' has been explicitly marked deprecated here 2025-07-15T00:37:20.8394903Z 309 | xmlParserInputState instate XML_DEPRECATED_MEMBER; 2025-07-15T00:37:20.8395413Z | ^ 2025-07-15T00:37:20.8396166Z /usr/local/include/libxml2/libxml/xmlexports.h:74:50: note: expanded from macro 'XML_DEPRECATED_MEMBER' 2025-07-15T00:37:20.8397058Z 74 | #define XML_DEPRECATED_MEMBER __attribute__((deprecated)) 2025-07-15T00:37:20.8397581Z | ^ 2025-07-15T00:37:20.8425542Z 1 error generated. --- ext/xml/compat.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/xml/compat.c b/ext/xml/compat.c index 7b463ebb5112e..6bd69bec19616 100644 --- a/ext/xml/compat.c +++ b/ext/xml/compat.c @@ -375,7 +375,9 @@ _get_entity(void *user, const xmlChar *name) if (ret == NULL) ret = xmlGetDocEntity(parser->parser->myDoc, name); + ZEND_DIAGNOSTIC_IGNORED_START("-Wdeprecated-declarations") if (ret == NULL || (parser->parser->instate != XML_PARSER_ENTITY_VALUE && parser->parser->instate != XML_PARSER_ATTRIBUTE_VALUE)) { + ZEND_DIAGNOSTIC_IGNORED_END if (ret == NULL || ret->etype == XML_INTERNAL_GENERAL_ENTITY || ret->etype == XML_INTERNAL_PARAMETER_ENTITY || ret->etype == XML_INTERNAL_PREDEFINED_ENTITY) { /* Predefined entities will expand unless no cdata handler is present */ if (parser->h_default && ! (ret && ret->etype == XML_INTERNAL_PREDEFINED_ENTITY && parser->h_cdata)) { From 8516ae86d7de1987e19e0ff34a9eae855d518b0e Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Thu, 17 Jul 2025 13:44:21 +0200 Subject: [PATCH 08/66] Skip LDAP TLS max version test for now --- ext/ldap/tests/ldap_start_tls_rc_max_version.phpt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ext/ldap/tests/ldap_start_tls_rc_max_version.phpt b/ext/ldap/tests/ldap_start_tls_rc_max_version.phpt index 359785f8b5a34..e983b97c4b4ea 100644 --- a/ext/ldap/tests/ldap_start_tls_rc_max_version.phpt +++ b/ext/ldap/tests/ldap_start_tls_rc_max_version.phpt @@ -6,6 +6,10 @@ ldap LDAPCONF={PWD}/ldap_start_tls_rc_max_version.conf --SKIPIF-- "OpenLDAP", "min_version" => 20600, From bdca73cc142ae682cdfea3b21b89354fad346ffe Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Thu, 17 Jul 2025 20:22:10 +0200 Subject: [PATCH 09/66] ext/hash: Remove incorrect zval_ptr_dtor --- ext/hash/hash.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/ext/hash/hash.c b/ext/hash/hash.c index 19d72ed7699b1..58b68fc63b9b7 100644 --- a/ext/hash/hash.c +++ b/ext/hash/hash.c @@ -850,8 +850,6 @@ PHP_FUNCTION(hash_copy) RETVAL_OBJ(Z_OBJ_HANDLER_P(zhash, clone_obj)(Z_OBJ_P(zhash))); if (php_hashcontext_from_object(Z_OBJ_P(return_value))->context == NULL) { - zval_ptr_dtor(return_value); - zend_throw_error(NULL, "Cannot copy hash"); RETURN_THROWS(); } From d11099ae325b0ade036c039f3e6962120c0a1709 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Thu, 17 Jul 2025 20:22:16 +0200 Subject: [PATCH 10/66] ext/socket: Remove incorrect zval_ptr_dtor --- ext/sockets/sockets.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index c252dc6e07a41..a0d5e52458f32 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -2511,7 +2511,6 @@ PHP_FUNCTION(socket_addrinfo_bind) } default: close(php_sock->bsd_socket); - zval_ptr_dtor(return_value); zend_argument_value_error(1, "must be one of AF_UNIX, AF_INET, or AF_INET6"); RETURN_THROWS(); } @@ -2575,7 +2574,6 @@ PHP_FUNCTION(socket_addrinfo_connect) default: zend_argument_value_error(1, "socket type must be one of AF_UNIX, AF_INET, or AF_INET6"); close(php_sock->bsd_socket); - zval_ptr_dtor(return_value); RETURN_THROWS(); } From 86aaded7e57dd2b2f5a4dd4e6a6d9511e6aec4e7 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Thu, 17 Jul 2025 21:51:46 +0200 Subject: [PATCH 11/66] NEWS for GH-19162 Closes GH-19162. --- NEWS | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/NEWS b/NEWS index 06427de7f9652..cbd68f4ce7484 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,9 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.3.25 +- Hash: + . Fix crash on clone failure. (nielsdos) + - LDAP: . Fixed bug GH-18529 (additional inheriting of TLS int options). (Jakub Zelenka) @@ -11,6 +14,8 @@ PHP NEWS return value check). (nielsdos, botovq) . Fix error return check of EVP_CIPHER_CTX_ctrl(). (nielsdos) +- Sockets: + . Fix some potential crashes on incorrect argument value. (nielsdos) 31 Jul 2025, PHP 8.3.24 From be09985c870a72d93e53295897679f49c0f11a58 Mon Sep 17 00:00:00 2001 From: Petr Sumbera Date: Fri, 18 Jul 2025 17:29:02 +0200 Subject: [PATCH 12/66] Fix GH-19169: ZEND_STATIC_ASSERT for -std=c++17 needs to define ZEND_STATIC_ASSERT to appropriate C++ static_assert instead of the C version. --- NEWS | 4 ++++ Zend/zend_portability.h | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index cbd68f4ce7484..cee68cfdd6266 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,10 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.3.25 +- Core: + . Fixed GH-19169 build issue with C++17 and ZEND_STATIC_ASSERT macro. + (psumbera) + - Hash: . Fix crash on clone failure. (nielsdos) diff --git a/Zend/zend_portability.h b/Zend/zend_portability.h index 670687973148f..2308d6cdb3057 100644 --- a/Zend/zend_portability.h +++ b/Zend/zend_portability.h @@ -761,7 +761,9 @@ extern "C++" { /** @deprecated */ #define ZEND_CGG_DIAGNOSTIC_IGNORED_END ZEND_DIAGNOSTIC_IGNORED_END -#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) /* C11 */ +#if defined(__cplusplus) +# define ZEND_STATIC_ASSERT(c, m) static_assert((c), m) +#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) /* C11 */ # define ZEND_STATIC_ASSERT(c, m) _Static_assert((c), m) #else # define ZEND_STATIC_ASSERT(c, m) From 312869381aaa993ae0429afd65535e3372b8dd99 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sat, 19 Jul 2025 23:25:29 +0200 Subject: [PATCH 13/66] Fix GH-19098: libxml<2.13 segmentation fault caused by php_libxml_node_free This implements a workaround for reconciliation not being performed for document-less nodes in libxml<2.13. Closes GH-19186. --- NEWS | 4 +++ ext/libxml/libxml.c | 27 +++++++++++++++----- ext/xmlreader/tests/gh19098.phpt | 44 ++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 7 deletions(-) create mode 100644 ext/xmlreader/tests/gh19098.phpt diff --git a/NEWS b/NEWS index cee68cfdd6266..6fe91bee54332 100644 --- a/NEWS +++ b/NEWS @@ -13,6 +13,10 @@ PHP NEWS . Fixed bug GH-18529 (additional inheriting of TLS int options). (Jakub Zelenka) +- LibXML: + . Fixed bug GH-19098 (libxml<2.13 segmentation fault caused by + php_libxml_node_free). (nielsdos) + - OpenSSL: . Fixed bug GH-18986 (OpenSSL backend: incorrect RAND_{load,write}_file() return value check). (nielsdos, botovq) diff --git a/ext/libxml/libxml.c b/ext/libxml/libxml.c index 5c903d2c9a228..eecbca4ed8908 100644 --- a/ext/libxml/libxml.c +++ b/ext/libxml/libxml.c @@ -331,13 +331,26 @@ PHP_LIBXML_API void php_libxml_node_free_list(xmlNodePtr node) if (curnode->type == XML_ELEMENT_NODE) { /* This ensures that namespace references in this subtree are defined within this subtree, * otherwise a use-after-free would be possible when the original namespace holder gets freed. */ -#if 0 - xmlDOMWrapCtxt dummy_ctxt = {0}; - xmlDOMWrapReconcileNamespaces(&dummy_ctxt, curnode, /* options */ 0); -#else - /* See php_dom.c */ - xmlReconciliateNs(curnode->doc, curnode); -#endif + if (LIBXML_VERSION < 21300 && UNEXPECTED(curnode->doc == NULL)) { + /* xmlReconciliateNs() in these versions just uses the document for xmlNewReconciledNs(), + * which can create an oldNs xml namespace declaration via xmlSearchNs() -> xmlTreeEnsureXMLDecl(). */ + xmlDoc dummy; + memset(&dummy, 0, sizeof(dummy)); + dummy.type = XML_DOCUMENT_NODE; + curnode->doc = &dummy; + xmlReconciliateNs(curnode->doc, curnode); + curnode->doc = NULL; + + /* Append oldNs to current node's nsDef, which can be at most one node. */ + if (dummy.oldNs) { + ZEND_ASSERT(dummy.oldNs->next == NULL); + xmlNsPtr old = curnode->nsDef; + curnode->nsDef = dummy.oldNs; + dummy.oldNs->next = old; + } + } else { + xmlReconciliateNs(curnode->doc, curnode); + } } /* Skip freeing */ curnode = next; diff --git a/ext/xmlreader/tests/gh19098.phpt b/ext/xmlreader/tests/gh19098.phpt new file mode 100644 index 0000000000000..13a6eda328f25 --- /dev/null +++ b/ext/xmlreader/tests/gh19098.phpt @@ -0,0 +1,44 @@ +--TEST-- +GH-19098 (libxml<2.13 segmentation fault caused by php_libxml_node_free) +--EXTENSIONS-- +xmlreader +dom +--FILE-- + + + + +'); + +$success = $xml_reader->next("sparql"); + +$success = $xml_reader->read(); +$success = $xml_reader->next("results"); + +while ($xml_reader->read()) { + if ($xml_reader->next("result")) { + $result_as_dom_node = $xml_reader->expand(); + $child = $result_as_dom_node->firstChild; + unset($result_as_dom_node); + var_dump($child->namespaceURI); + foreach ($child->attributes as $attr) { + var_dump($attr->namespaceURI); + } + $doc = new DOMDocument; + $doc->adoptNode($child); + echo $doc->saveXML($child), "\n"; + unset($child); + break; + } +} + +?> +--EXPECT-- +string(38) "http://www.w3.org/2005/sparql-results#" +string(36) "http://www.w3.org/XML/1998/namespace" +string(10) "urn:custom" +NULL + From 6cc4ae1f1dcf5128b8aa610fbfea8bcba270ceb1 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sat, 19 Jul 2025 12:25:21 +0200 Subject: [PATCH 14/66] Fix GH-18640: heap-use-after-free ext/soap/php_encoding.c:299:32 in soap_check_zval_ref For attributes, relying on the ref_map doesn't make sense the first place as you can't really refer to attributes from attributes. The code therefore assumes that the node is unique, which is broken. Closes GH-19181. --- NEWS | 4 +++ ext/soap/php_encoding.c | 14 +++++++++++ ext/soap/tests/bugs/gh18640.phpt | 42 ++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+) create mode 100644 ext/soap/tests/bugs/gh18640.phpt diff --git a/NEWS b/NEWS index 6fe91bee54332..6d020762eeeed 100644 --- a/NEWS +++ b/NEWS @@ -22,6 +22,10 @@ PHP NEWS return value check). (nielsdos, botovq) . Fix error return check of EVP_CIPHER_CTX_ctrl(). (nielsdos) +- SOAP: + . Fixed bug GH-18640 (heap-use-after-free ext/soap/php_encoding.c:299:32 + in soap_check_zval_ref). (nielsdos) + - Sockets: . Fix some potential crashes on incorrect argument value. (nielsdos) diff --git a/ext/soap/php_encoding.c b/ext/soap/php_encoding.c index 27b2c4e652b96..0b4b553d87540 100644 --- a/ext/soap/php_encoding.c +++ b/ext/soap/php_encoding.c @@ -1936,6 +1936,11 @@ static xmlNodePtr to_xml_object(encodeTypePtr type, zval *data, int style, xmlNo sdlAttributePtr attr; zval *zattr, rv; + /* Attributes can't refer to other attributes as there's nothing to attach the href to. */ + HashTable **ref_map = &SOAP_GLOBAL(ref_map); + HashTable *old_ref_map = *ref_map; + *ref_map = NULL; + ZEND_HASH_FOREACH_PTR(sdlType->attributes, attr) { if (attr->name) { zattr = get_zval_property(data, attr->name, &rv); @@ -1965,6 +1970,8 @@ static xmlNodePtr to_xml_object(encodeTypePtr type, zval *data, int style, xmlNo } } } ZEND_HASH_FOREACH_END(); + + *ref_map = old_ref_map; } } if (style == SOAP_ENCODED) { @@ -3034,6 +3041,12 @@ static xmlNodePtr to_xml_list(encodeTypePtr enc, zval *data, int style, xmlNodeP ret = xmlNewNode(NULL, BAD_CAST("BOGUS")); xmlAddChild(parent, ret); FIND_ZVAL_NULL(data, ret, style); + + /* Literals are unique and can't refer to other references via attributes. */ + HashTable **ref_map = &SOAP_GLOBAL(ref_map); + HashTable *old_ref_map = *ref_map; + *ref_map = NULL; + if (Z_TYPE_P(data) == IS_ARRAY) { zval *tmp; smart_str list = {0}; @@ -3108,6 +3121,7 @@ static xmlNodePtr to_xml_list(encodeTypePtr enc, zval *data, int style, xmlNodeP zval_ptr_dtor_str(&tmp); } } + *ref_map = old_ref_map; return ret; } diff --git a/ext/soap/tests/bugs/gh18640.phpt b/ext/soap/tests/bugs/gh18640.phpt new file mode 100644 index 0000000000000..493659eca30c7 --- /dev/null +++ b/ext/soap/tests/bugs/gh18640.phpt @@ -0,0 +1,42 @@ +--TEST--- +GH-18640 (heap-use-after-free ext/soap/php_encoding.c:299:32 in soap_check_zval_ref) +--EXTENSIONS-- +soap +--CREDITS-- +YuanchengJiang +--FILE-- + 1, 'classmap' => ['logOnEvent' => 'LogOnEvent', 'events' => 'IVREvents']]); +$timestamp = new LogOnEvent(); // Bogus! +$logOffEvents[] = new LogOffEvent($timestamp); +$logOffEvents[] = new LogOffEvent($timestamp); +$ivrEvents = new IVREvents($logOffEvents); +$result = $soapClient->PostEvents($ivrEvents); + +class LogOffEvent { + function __construct(public $timestamp) { + $this->timestamp = $timestamp; + } +} + +class LogOnEvent { +} + +class IVREvents { + function __construct(public $logOffEvent) { + } +} +?> +--EXPECT-- +string(359) " + +" From c8cc23336dd7efb3ab0aac66326b1df9375556b6 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Tue, 15 Jul 2025 20:36:38 +0200 Subject: [PATCH 15/66] Fix properties_info_table for abstract properties Fixes GH-19053 Closes GH-19140 Co-authored-by: Bob Weinand --- NEWS | 2 ++ Zend/tests/gh19053.phpt | 26 ++++++++++++++++++++++++++ Zend/zend_inheritance.c | 19 +++++++++++++++++-- 3 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 Zend/tests/gh19053.phpt diff --git a/NEWS b/NEWS index 0a4ceead58e8b..45bb77482fd84 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,8 @@ PHP NEWS - Core: . Fixed GH-19169 build issue with C++17 and ZEND_STATIC_ASSERT macro. (psumbera) + . Fixed bug GH-19053 (Duplicate property slot with hooks and interface + property). (ilutov) - Hash: . Fix crash on clone failure. (nielsdos) diff --git a/Zend/tests/gh19053.phpt b/Zend/tests/gh19053.phpt new file mode 100644 index 0000000000000..7c82f1bc5f412 --- /dev/null +++ b/Zend/tests/gh19053.phpt @@ -0,0 +1,26 @@ +--TEST-- +GH-19053: Incorrect properties_info_table for abstract properties +--FILE-- + 2; } +} + +$c = new C; +var_dump($c); + +?> +--EXPECTF-- +object(C)#%d (0) { + ["foo"]=> + uninitialized(mixed) +} diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 6f399fbfec5ab..ced75ad82d9db 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -1687,10 +1687,25 @@ void zend_build_properties_info_table(zend_class_entry *ce) } } - ZEND_HASH_MAP_FOREACH_PTR(&ce->properties_info, prop) { + ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&ce->properties_info, zend_string *key, prop) { if (prop->ce == ce && (prop->flags & ZEND_ACC_STATIC) == 0 && !(prop->flags & ZEND_ACC_VIRTUAL)) { - uint32_t prop_table_offset = OBJ_PROP_TO_NUM(!(prop->prototype->flags & ZEND_ACC_VIRTUAL) ? prop->prototype->offset : prop->offset); + const zend_property_info *root_prop = prop->prototype; + if (UNEXPECTED(root_prop->flags & ZEND_ACC_VIRTUAL)) { + /* Prototype is virtual, we need to manually hunt down the first backed property. */ + root_prop = prop; + zend_class_entry *parent_ce; + while ((parent_ce = root_prop->ce->parent)) { + zend_property_info *parent_prop = zend_hash_find_ptr(&parent_ce->properties_info, key); + if (!parent_prop + || parent_prop->prototype != prop->prototype + || (parent_prop->flags & ZEND_ACC_VIRTUAL)) { + break; + } + root_prop = parent_prop; + } + } + uint32_t prop_table_offset = OBJ_PROP_TO_NUM(root_prop->offset); table[prop_table_offset] = prop; } } ZEND_HASH_FOREACH_END(); From 605ee054915beff79811c2b2868888426ce16ff2 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Mon, 21 Jul 2025 23:19:48 +0200 Subject: [PATCH 16/66] Fix test conflict between chmod_variation2 and file_variation5 Both used "somelink". See https://github.com/php/php-src/actions/runs/16427526464/job/46421461376 Closes GH-19208. --- ext/standard/tests/file/chmod_variation2.phpt | 2 +- ext/standard/tests/file/file_variation5.phpt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/standard/tests/file/chmod_variation2.phpt b/ext/standard/tests/file/chmod_variation2.phpt index e96af25ec469b..c2b6c2e810a3c 100644 --- a/ext/standard/tests/file/chmod_variation2.phpt +++ b/ext/standard/tests/file/chmod_variation2.phpt @@ -34,7 +34,7 @@ clearstatcache(); printf("%o\n", fileperms($filepath) & PERMISSIONS_MASK); echo "\nchmod() on a linked file\n"; -$linkname = "somelink"; +$linkname = "somelink2"; var_dump(symlink($filepath, $linkname)); var_dump(chmod($filepath, 0777)); var_dump(chmod($linkname, 0755)); diff --git a/ext/standard/tests/file/file_variation5.phpt b/ext/standard/tests/file/file_variation5.phpt index 7f012b7d431ec..65e68ab0927d2 100644 --- a/ext/standard/tests/file/file_variation5.phpt +++ b/ext/standard/tests/file/file_variation5.phpt @@ -27,7 +27,7 @@ echo "\nfile() on a path containing .. with invalid directories\n"; var_dump(file("./$test_dirname/bad_dir/../../$filename")); echo "\nfile() on a linked file\n"; -$linkname = "somelink"; +$linkname = "somelink5"; var_dump(symlink($filepath, $linkname)); var_dump(file($linkname)); var_dump(unlink($linkname)); From 13c781f04df4c3ee4e57ff96df67e5868ffdadb8 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Mon, 21 Jul 2025 22:15:23 +0200 Subject: [PATCH 17/66] Add missing cc clobber Closes GH-19205. --- Zend/zend_multiply.h | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Zend/zend_multiply.h b/Zend/zend_multiply.h index a99e858bd7798..b3f0086009f47 100644 --- a/Zend/zend_multiply.h +++ b/Zend/zend_multiply.h @@ -176,13 +176,15 @@ static zend_always_inline size_t zend_safe_address(size_t nmemb, size_t size, si __asm__ ("mull %3\n\tadcl $0,%1" : "=&a"(res), "=&d" (m_overflow) : "%0"(res), - "rm"(size)); + "rm"(size) + : "cc"); } else { __asm__ ("mull %3\n\taddl %4,%0\n\tadcl $0,%1" : "=&a"(res), "=&d" (m_overflow) : "%0"(res), "rm"(size), - "rm"(offset)); + "rm"(offset) + : "cc"); } if (UNEXPECTED(m_overflow)) { @@ -211,7 +213,8 @@ static zend_always_inline size_t zend_safe_address(size_t nmemb, size_t size, si "adc $0,%1" : "=&a"(res), "=&d" (m_overflow) : "%0"(res), - "rm"(size)); + "rm"(size) + : "cc"); } else { __asm__ ("mul" LP_SUFF " %3\n\t" "add %4,%0\n\t" @@ -219,7 +222,8 @@ static zend_always_inline size_t zend_safe_address(size_t nmemb, size_t size, si : "=&a"(res), "=&d" (m_overflow) : "%0"(res), "rm"(size), - "rm"(offset)); + "rm"(offset) + : "cc"); } #undef LP_SUFF if (UNEXPECTED(m_overflow)) { From 5d1636e40bb6c9e5641024e532aa97851a7d9837 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Tue, 22 Jul 2025 12:38:06 +0200 Subject: [PATCH 18/66] Leak in failed unserialize() with opcache With opcache, zend_string_init_interned() will allocate non-interned strings at runtime because shm is locked. Hence, we need to make sure to actually free this string. Fixes OSS-Fuzz #433303828 Closes GH-19211 --- NEWS | 4 ++++ .../tests/serialize/oss_fuzz_433303828.phpt | 13 +++++++++++++ ext/standard/var_unserializer.re | 2 ++ 3 files changed, 19 insertions(+) create mode 100644 ext/standard/tests/serialize/oss_fuzz_433303828.phpt diff --git a/NEWS b/NEWS index 6d020762eeeed..5fdee5f121037 100644 --- a/NEWS +++ b/NEWS @@ -29,6 +29,10 @@ PHP NEWS - Sockets: . Fix some potential crashes on incorrect argument value. (nielsdos) +- Standard: + . Fixed OSS Fuzz #417078295 (Leak in failed unserialize() with opcache). + (ilutov) + 31 Jul 2025, PHP 8.3.24 - Calendar: diff --git a/ext/standard/tests/serialize/oss_fuzz_433303828.phpt b/ext/standard/tests/serialize/oss_fuzz_433303828.phpt new file mode 100644 index 0000000000000..fb90b51d4dadf --- /dev/null +++ b/ext/standard/tests/serialize/oss_fuzz_433303828.phpt @@ -0,0 +1,13 @@ +--TEST-- +OSS-Fuzz #433303828 +--FILE-- + +--EXPECTF-- +Warning: unserialize(): Error at offset 9 of 10 bytes in %s on line %d + +Warning: unserialize(): Error at offset 10 of 11 bytes in %s on line %d diff --git a/ext/standard/var_unserializer.re b/ext/standard/var_unserializer.re index a050fb5f74a70..1d23269ef03d4 100644 --- a/ext/standard/var_unserializer.re +++ b/ext/standard/var_unserializer.re @@ -1310,10 +1310,12 @@ object ":" uiv ":" ["] { YYCURSOR = *p; if (*(YYCURSOR) != ':') { + zend_string_release_ex(class_name, 0); return 0; } if (*(YYCURSOR+1) != '{') { *p = YYCURSOR+1; + zend_string_release_ex(class_name, 0); return 0; } From a48d620c262bb96b75990a4a9aca5ad3698a1ec8 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Tue, 22 Jul 2025 15:45:48 +0200 Subject: [PATCH 19/66] [skip ci] Fix wrong oss-fuzz ID Oops, darn copy & paste. --- NEWS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 5fdee5f121037..c7d7ef1c2b58c 100644 --- a/NEWS +++ b/NEWS @@ -30,7 +30,7 @@ PHP NEWS . Fix some potential crashes on incorrect argument value. (nielsdos) - Standard: - . Fixed OSS Fuzz #417078295 (Leak in failed unserialize() with opcache). + . Fixed OSS Fuzz #433303828 (Leak in failed unserialize() with opcache). (ilutov) 31 Jul 2025, PHP 8.3.24 From be8819259417d63b389eb9ea6c00abd6aeb8e1c6 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Tue, 22 Jul 2025 14:30:40 +0200 Subject: [PATCH 20/66] Run FreebSD CI under 13.5 13.3 gives a 404 now. Also pulls in a 8.4 fix to include xxhash from the bundled location. Closes GH-19213. --- .github/actions/freebsd/action.yml | 2 +- ext/hash/php_hash_xxhash.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/freebsd/action.yml b/.github/actions/freebsd/action.yml index fd37e92bbe5f9..17446311bb864 100644 --- a/.github/actions/freebsd/action.yml +++ b/.github/actions/freebsd/action.yml @@ -9,7 +9,7 @@ runs: - name: FreeBSD uses: vmactions/freebsd-vm@v1 with: - release: '13.3' + release: '13.5' usesh: true copyback: false # Temporarily disable sqlite, as FreeBSD ships it with disabled double quotes. We'll need to fix our tests. diff --git a/ext/hash/php_hash_xxhash.h b/ext/hash/php_hash_xxhash.h index a1e8840ce272a..ace70deedb008 100644 --- a/ext/hash/php_hash_xxhash.h +++ b/ext/hash/php_hash_xxhash.h @@ -18,7 +18,7 @@ #define PHP_HASH_XXHASH_H #define XXH_INLINE_ALL 1 -#include "xxhash.h" +#include "xxhash/xxhash.h" typedef struct { XXH32_state_t s; From b13347be38e9482b48d3722c031b6dbea16afa1f Mon Sep 17 00:00:00 2001 From: Bob Weinand Date: Tue, 22 Jul 2025 17:46:14 +0200 Subject: [PATCH 21/66] Fix GH-19044: Protected properties are not scoped according to their prototype (#19046) * Fix GH-19044: Protected properties are not scoped according to their prototype * Adjust after review * Simplify to using prototype even for asymmetric visibility --- NEWS | 5 ++-- Zend/tests/asymmetric_visibility/gh19044.phpt | 24 ++++++++++++++++ Zend/tests/gh19044.phpt | 26 +++++++++++++++++ Zend/tests/property_hooks/gh19044-1.phpt | 26 +++++++++++++++++ Zend/tests/property_hooks/gh19044-2.phpt | 26 +++++++++++++++++ Zend/tests/property_hooks/gh19044-3.phpt | 24 ++++++++++++++++ Zend/tests/property_hooks/gh19044-4.phpt | 28 +++++++++++++++++++ Zend/tests/property_hooks/gh19044-5.phpt | 28 +++++++++++++++++++ Zend/tests/property_hooks/gh19044-6.phpt | 28 +++++++++++++++++++ Zend/tests/property_hooks/gh19044-8.phpt | 26 +++++++++++++++++ Zend/zend_object_handlers.c | 10 +++---- 11 files changed, 243 insertions(+), 8 deletions(-) create mode 100644 Zend/tests/asymmetric_visibility/gh19044.phpt create mode 100644 Zend/tests/gh19044.phpt create mode 100644 Zend/tests/property_hooks/gh19044-1.phpt create mode 100644 Zend/tests/property_hooks/gh19044-2.phpt create mode 100644 Zend/tests/property_hooks/gh19044-3.phpt create mode 100644 Zend/tests/property_hooks/gh19044-4.phpt create mode 100644 Zend/tests/property_hooks/gh19044-5.phpt create mode 100644 Zend/tests/property_hooks/gh19044-6.phpt create mode 100644 Zend/tests/property_hooks/gh19044-8.phpt diff --git a/NEWS b/NEWS index d3faca76e507c..19dbea1300bf8 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,8 @@ PHP NEWS (psumbera) . Fixed bug GH-19053 (Duplicate property slot with hooks and interface property). (ilutov) + . Fixed bug GH-19044 (Protected properties are not scoped according to their + prototype). (Bob) - Hash: . Fix crash on clone failure. (nielsdos) @@ -49,9 +51,6 @@ PHP NEWS . Fixed bug GH-18907 (Leak when creating cycle in hook). (ilutov) . Fix OSS-Fuzz #427814456. (nielsdos) . Fix OSS-Fuzz #428983568 and #428760800. (nielsdos) - . Fixed bug GH-17204 (-Wuseless-escape warnings emitted by re2c). (Peter Kokot) - . Fixed bug GH-19064 (Undefined symbol 'execute_ex' on Windows ARM64). - (Demon) - Curl: . Fix memory leaks when returning refcounted value from curl callback. diff --git a/Zend/tests/asymmetric_visibility/gh19044.phpt b/Zend/tests/asymmetric_visibility/gh19044.phpt new file mode 100644 index 0000000000000..253e315d5b945 --- /dev/null +++ b/Zend/tests/asymmetric_visibility/gh19044.phpt @@ -0,0 +1,24 @@ +--TEST-- +GH-19044: Protected properties must be scoped according to their prototype (protected(set) on non-hooked property) +--FILE-- + 42; } +} + +class C1 extends P { + public protected(set) mixed $foo = 1; +} + +class C2 extends P { + public protected(set) mixed $foo; + + static function foo($c) { return $c->foo += 1; } +} + +var_dump(C2::foo(new C1)); + +?> +--EXPECT-- +int(43) diff --git a/Zend/tests/gh19044.phpt b/Zend/tests/gh19044.phpt new file mode 100644 index 0000000000000..3477dd3c8b08a --- /dev/null +++ b/Zend/tests/gh19044.phpt @@ -0,0 +1,26 @@ +--TEST-- +GH-19044: Protected properties must be scoped according to their prototype +--FILE-- +foo; } +} + +var_dump(C2::foo(new C2)); +var_dump(C2::foo(new C1)); + +?> +--EXPECT-- +int(2) +int(1) diff --git a/Zend/tests/property_hooks/gh19044-1.phpt b/Zend/tests/property_hooks/gh19044-1.phpt new file mode 100644 index 0000000000000..133727c73def8 --- /dev/null +++ b/Zend/tests/property_hooks/gh19044-1.phpt @@ -0,0 +1,26 @@ +--TEST-- +GH-19044: Protected properties must be scoped according to their prototype (common ancestor has a protected setter) +--FILE-- + 1; set {} } +} + +class GrandC1 extends C1 { + public protected(set) mixed $foo { get => 2; set {} } +} + +class C2 extends C1 { + static function foo($c) { return $c->foo += 1; } +} + +var_dump(C2::foo(new GrandC1)); + +?> +--EXPECT-- +int(3) diff --git a/Zend/tests/property_hooks/gh19044-2.phpt b/Zend/tests/property_hooks/gh19044-2.phpt new file mode 100644 index 0000000000000..ed73c60dc5a3a --- /dev/null +++ b/Zend/tests/property_hooks/gh19044-2.phpt @@ -0,0 +1,26 @@ +--TEST-- +GH-19044: Protected properties must be scoped according to their prototype (common ancestor does not have a setter) +--FILE-- + 1; } +} + +class GrandC1 extends C1 { + public protected(set) mixed $foo { get => 2; set {} } +} + +class C2 extends C1 { + static function foo($c) { return $c->foo += 1; } +} + +var_dump(C2::foo(new GrandC1)); + +?> +--EXPECT-- +int(3) diff --git a/Zend/tests/property_hooks/gh19044-3.phpt b/Zend/tests/property_hooks/gh19044-3.phpt new file mode 100644 index 0000000000000..3d4f6789a5a37 --- /dev/null +++ b/Zend/tests/property_hooks/gh19044-3.phpt @@ -0,0 +1,24 @@ +--TEST-- +GH-19044: Protected properties must be scoped according to their prototype (abstract parent defining visibility only takes precedence) +--FILE-- + 2; set {} } +} + +class C2 extends P { + public mixed $foo = 1; + + static function foo($c) { return $c->foo += 1; } +} + +var_dump(C2::foo(new C1)); + +?> +--EXPECT-- +int(3) diff --git a/Zend/tests/property_hooks/gh19044-4.phpt b/Zend/tests/property_hooks/gh19044-4.phpt new file mode 100644 index 0000000000000..e1abf47390831 --- /dev/null +++ b/Zend/tests/property_hooks/gh19044-4.phpt @@ -0,0 +1,28 @@ +--TEST-- +GH-19044: Protected properties must be scoped according to their prototype (abstract parent sets protected(set) with not having grandparent a setter - both inherit from parent) +--FILE-- + 2; set {} } +} + +class C2 extends P { + public mixed $foo = 1; + + static function foo($c) { return $c->foo += 1; } +} + +var_dump(C2::foo(new C1)); + +?> +--EXPECT-- +int(3) diff --git a/Zend/tests/property_hooks/gh19044-5.phpt b/Zend/tests/property_hooks/gh19044-5.phpt new file mode 100644 index 0000000000000..773682961ab41 --- /dev/null +++ b/Zend/tests/property_hooks/gh19044-5.phpt @@ -0,0 +1,28 @@ +--TEST-- +GH-19044: Protected properties must be scoped according to their prototype (abstract parent sets protected(set) with not having grandparent a setter - one inherits from grandparent) +--FILE-- + 2; set {} } +} + +class C2 extends GP { + public mixed $foo = 1; + + static function foo($c) { return $c->foo += 1; } +} + +var_dump(C2::foo(new C1)); + +?> +--EXPECT-- +int(3) diff --git a/Zend/tests/property_hooks/gh19044-6.phpt b/Zend/tests/property_hooks/gh19044-6.phpt new file mode 100644 index 0000000000000..43332698fe9f7 --- /dev/null +++ b/Zend/tests/property_hooks/gh19044-6.phpt @@ -0,0 +1,28 @@ +--TEST-- +GH-19044: Protected properties must be scoped according to their prototype (abstract parent has implicit set hook) +--FILE-- + $this->foo; } +} + +class C1 extends P { + public protected(set) mixed $foo = 1; +} + +class C2 extends P { + public protected(set) mixed $foo; + + static function foo($c) { return $c->foo += 1; } +} + +var_dump(C2::foo(new C1)); + +?> +--EXPECT-- +int(2) diff --git a/Zend/tests/property_hooks/gh19044-8.phpt b/Zend/tests/property_hooks/gh19044-8.phpt new file mode 100644 index 0000000000000..2fa62e4619f56 --- /dev/null +++ b/Zend/tests/property_hooks/gh19044-8.phpt @@ -0,0 +1,26 @@ +--TEST-- +GH-19044: Protected properties must be scoped according to their prototype (hooks variation) +--FILE-- +foo; } +} + +var_dump(C2::foo(new C2)); +var_dump(C2::foo(new C1)); + +?> +--EXPECT-- +int(2) +int(1) diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 2ddaeae96e999..13a74e1725c28 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -282,7 +282,7 @@ static zend_always_inline bool is_derived_class(const zend_class_entry *child_cl static zend_never_inline int is_protected_compatible_scope(const zend_class_entry *ce, const zend_class_entry *scope) /* {{{ */ { return scope && - (is_derived_class(ce, scope) || is_derived_class(scope, ce)); + (ce == scope || is_derived_class(ce, scope) || is_derived_class(scope, ce)); } /* }}} */ @@ -419,7 +419,7 @@ static zend_always_inline uintptr_t zend_get_property_offset(zend_class_entry *c } } else { ZEND_ASSERT(flags & ZEND_ACC_PROTECTED); - if (UNEXPECTED(!is_protected_compatible_scope(property_info->ce, scope))) { + if (UNEXPECTED(!is_protected_compatible_scope(property_info->prototype->ce, scope))) { goto wrong; } } @@ -514,7 +514,7 @@ ZEND_API zend_property_info *zend_get_property_info(const zend_class_entry *ce, } } else { ZEND_ASSERT(flags & ZEND_ACC_PROTECTED); - if (UNEXPECTED(!is_protected_compatible_scope(property_info->ce, scope))) { + if (UNEXPECTED(!is_protected_compatible_scope(property_info->prototype->ce, scope))) { goto wrong; } } @@ -585,7 +585,7 @@ ZEND_API bool ZEND_FASTCALL zend_asymmetric_property_has_set_access(const zend_p return true; } return EXPECTED((prop_info->flags & ZEND_ACC_PROTECTED_SET) - && is_protected_compatible_scope(prop_info->ce, scope)); + && is_protected_compatible_scope(prop_info->prototype->ce, scope)); } static void zend_property_guard_dtor(zval *el) /* {{{ */ { @@ -2030,7 +2030,7 @@ ZEND_API zval *zend_std_get_static_property_with_info(zend_class_entry *ce, zend zend_class_entry *scope = get_fake_or_executed_scope(); if (property_info->ce != scope) { if (UNEXPECTED(property_info->flags & ZEND_ACC_PRIVATE) - || UNEXPECTED(!is_protected_compatible_scope(property_info->ce, scope))) { + || UNEXPECTED(!is_protected_compatible_scope(property_info->prototype->ce, scope))) { if (type != BP_VAR_IS) { zend_bad_property_access(property_info, ce, property_name); } From 23ec35bf4a89b6879f1a90c097516cffbeb7d16d Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Wed, 16 Jul 2025 23:07:40 +0200 Subject: [PATCH 22/66] Coerce numeric string keys from iterators when argument unpacking Fixes GH-18581 Closes GH-19151 --- NEWS | 2 ++ Zend/tests/gh18581.phpt | 32 ++++++++++++++++++++++++++++++++ Zend/zend_vm_def.h | 5 +++++ Zend/zend_vm_execute.h | 5 +++++ 4 files changed, 44 insertions(+) create mode 100644 Zend/tests/gh18581.phpt diff --git a/NEWS b/NEWS index c7d7ef1c2b58c..89f24c4bfc450 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,8 @@ PHP NEWS - Core: . Fixed GH-19169 build issue with C++17 and ZEND_STATIC_ASSERT macro. (psumbera) + . Fixed bug GH-18581 (Coerce numeric string keys from iterators when argument + unpacking). (ilutov) - Hash: . Fix crash on clone failure. (nielsdos) diff --git a/Zend/tests/gh18581.phpt b/Zend/tests/gh18581.phpt new file mode 100644 index 0000000000000..cc5c0fff02e92 --- /dev/null +++ b/Zend/tests/gh18581.phpt @@ -0,0 +1,32 @@ +--TEST-- +GH-18581: Coerce numeric string keys from iterators when argument unpacking +--FILE-- + 'first'; + yield '101' => 'second'; + yield '102' => 'third'; + yield 'named' => 'fourth'; +} + +function test($x = null, $y = null, ...$z) { + var_dump($x, $y, $z); + var_dump($z[0]); + var_dump($z['named']); +} + +test(...g()); + +?> +--EXPECT-- +string(5) "first" +string(6) "second" +array(2) { + [0]=> + string(5) "third" + ["named"]=> + string(6) "fourth" +} +string(5) "third" +string(6) "fourth" diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 0eacdfe145d49..7c9f151134fe2 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -5236,6 +5236,11 @@ ZEND_VM_C_LABEL(send_again): } name = Z_STR_P(&key); + + zend_ulong tmp; + if (ZEND_HANDLE_NUMERIC(name, tmp)) { + name = NULL; + } } } diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 4ea6b302c8cfc..9daa00d97c418 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -2355,6 +2355,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_SEND_UNPACK_SPEC_HANDLER(ZEND_ } name = Z_STR_P(&key); + + zend_ulong tmp; + if (ZEND_HANDLE_NUMERIC(name, tmp)) { + name = NULL; + } } } From ad2143f3b06f629c8e6a5d3a4aa409935182de59 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Wed, 23 Jul 2025 11:28:14 +0200 Subject: [PATCH 23/66] Fix arginfo/zpp violation if zend_hrtime is not available Part of GH-19210. Closes GH-19218. --- ext/standard/hrtime.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/standard/hrtime.c b/ext/standard/hrtime.c index 6af8bfc965069..831e903e1cc38 100644 --- a/ext/standard/hrtime.c +++ b/ext/standard/hrtime.c @@ -46,7 +46,6 @@ delivered timestamp is monotonic and cannot be adjusted. */ PHP_FUNCTION(hrtime) { -#if ZEND_HRTIME_AVAILABLE bool get_as_num = 0; zend_hrtime_t t = zend_hrtime(); @@ -55,6 +54,7 @@ PHP_FUNCTION(hrtime) Z_PARAM_BOOL(get_as_num) ZEND_PARSE_PARAMETERS_END(); +#if ZEND_HRTIME_AVAILABLE if (UNEXPECTED(get_as_num)) { PHP_RETURN_HRTIME(t); } else { From beeeee29783604b80be465d738dc4eee635161c1 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Wed, 23 Jul 2025 11:32:16 +0200 Subject: [PATCH 24/66] Handle broken hrtime in ftp Part of GH-19210. Closes GH-19219. --- ext/ftp/ftp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ext/ftp/ftp.c b/ext/ftp/ftp.c index acdc1522e58fc..3f9a78a81f0f4 100644 --- a/ext/ftp/ftp.c +++ b/ext/ftp/ftp.c @@ -1471,7 +1471,8 @@ static int my_poll(php_socket_t fd, int events, int timeout) { if (n == -1 && php_socket_errno() == EINTR) { zend_hrtime_t delta_ns = zend_hrtime() - start_ns; - if (delta_ns > timeout_hr) { + /* delta_ns == 0 is only possible with a platform that does not support a high-res timer. */ + if (delta_ns > timeout_hr || UNEXPECTED(delta_ns == 0)) { #ifndef PHP_WIN32 errno = ETIMEDOUT; #endif From f94c11fff8475acc7b8a39eb4433bd594f73ac3b Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Fri, 25 Jul 2025 12:04:40 +0200 Subject: [PATCH 25/66] NEWS for hrtime in FTP and standard --- NEWS | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NEWS b/NEWS index 89f24c4bfc450..2b21969dffbce 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,9 @@ PHP NEWS . Fixed bug GH-18581 (Coerce numeric string keys from iterators when argument unpacking). (ilutov) +- FTP: + . Fix theoretical issues with hrtime() not being available. (nielsdos) + - Hash: . Fix crash on clone failure. (nielsdos) @@ -34,6 +37,7 @@ PHP NEWS - Standard: . Fixed OSS Fuzz #433303828 (Leak in failed unserialize() with opcache). (ilutov) + . Fix theoretical issues with hrtime() not being available. (nielsdos) 31 Jul 2025, PHP 8.3.24 From b63372058552fd7cae24a8684d8b2b8276b3a2fc Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Sat, 26 Jul 2025 10:03:34 +0200 Subject: [PATCH 26/66] Add unique entry point for extra tests We are adding extra (non-phpt) test suites in [1] and [2]. In order to avoid touching CI files too often (which are maintained in 8.1 and merged in upper branches), we add a single entry point to call the extra tests. The entry point can be updated in branches without synchronizing all the way from 8.1. CI files still need to be touched to install dependencies of these tests, but this should be manageable as these do not change often and are the same in every branch. Closes GH-19242. [1] https://github.com/php/php-src/pull/16987 [2] https://github.com/php/php-src/pull/18939 --- .github/actions/apt-x32/action.yml | 5 +- .github/actions/apt-x64/action.yml | 5 +- .github/actions/extra-tests/action.yml | 7 ++ .github/actions/freebsd/action.yml | 13 ++- .github/workflows/nightly.yml | 11 +++ run-extra-tests.php | 123 +++++++++++++++++++++++++ 6 files changed, 161 insertions(+), 3 deletions(-) create mode 100644 .github/actions/extra-tests/action.yml create mode 100755 run-extra-tests.php diff --git a/.github/actions/apt-x32/action.yml b/.github/actions/apt-x32/action.yml index 879300f992747..0147b15cfcf5f 100644 --- a/.github/actions/apt-x32/action.yml +++ b/.github/actions/apt-x32/action.yml @@ -6,6 +6,8 @@ runs: run: | set -x + OPCACHE_TLS_TESTS_DEPS="gcc clang lld" + export DEBIAN_FRONTEND=noninteractive dpkg --add-architecture i386 apt-get update -y | true @@ -50,4 +52,5 @@ runs: re2c \ unzip \ wget \ - zlib1g-dev:i386 + zlib1g-dev:i386 \ + $OPCACHE_TLS_TESTS_DEPS diff --git a/.github/actions/apt-x64/action.yml b/.github/actions/apt-x64/action.yml index 4e1a03dd58cc5..15697ed9f2fa4 100644 --- a/.github/actions/apt-x64/action.yml +++ b/.github/actions/apt-x64/action.yml @@ -6,6 +6,8 @@ runs: run: | set -x + OPCACHE_TLS_TESTS_DEPS="gcc clang lld" + sudo apt-get update sudo apt-get install \ bison \ @@ -58,4 +60,5 @@ runs: libqdbm-dev \ libjpeg-dev \ libpng-dev \ - libfreetype6-dev + libfreetype6-dev \ + $OPCACHE_TLS_TESTS_DEPS diff --git a/.github/actions/extra-tests/action.yml b/.github/actions/extra-tests/action.yml new file mode 100644 index 0000000000000..0537496064008 --- /dev/null +++ b/.github/actions/extra-tests/action.yml @@ -0,0 +1,7 @@ +name: Extra tests +runs: + using: composite + steps: + - shell: sh + run: | + sapi/cli/php run-extra-tests.php diff --git a/.github/actions/freebsd/action.yml b/.github/actions/freebsd/action.yml index 17446311bb864..ce133925e2521 100644 --- a/.github/actions/freebsd/action.yml +++ b/.github/actions/freebsd/action.yml @@ -3,6 +3,9 @@ inputs: configurationParameters: default: '' required: false + runExtraTests: + default: false + required: false runs: using: composite steps: @@ -17,6 +20,8 @@ runs: prepare: | cd $GITHUB_WORKSPACE + OPCACHE_TLS_TESTS_DEPS="gcc" + kldload accf_http pkg install -y \ autoconf \ @@ -41,9 +46,11 @@ runs: webp \ libavif \ `#sqlite3` \ - curl + curl \ + $OPCACHE_TLS_TESTS_DEPS ./buildconf -f + CC=clang CXX=clang++ \ ./configure \ --prefix=/usr/local \ --enable-debug \ @@ -106,3 +113,7 @@ runs: --show-slow 1000 \ --set-timeout 120 \ -d zend_extension=opcache.so + + if test "${{ inputs.runExtraTests }}" = "true"; then + sapi/cli/php run-extra-tests.php + fi diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 5817c647a871a..38ccb05e09928 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -85,6 +85,8 @@ jobs: with: runTestsParameters: >- --asan -x + - name: Extra tests + uses: ./.github/actions/extra-tests ALPINE: if: inputs.run_alpine name: ALPINE_X64_ASAN_UBSAN_DEBUG_ZTS @@ -134,6 +136,8 @@ jobs: --asan -x -d zend_extension=opcache.so -d opcache.enable_cli=1 + - name: Extra tests + uses: ./.github/actions/extra-tests - name: Notify Slack if: failure() uses: ./.github/actions/notify-slack @@ -266,6 +270,8 @@ jobs: ${{ matrix.run_tests_parameters }} -d zend_extension=opcache.so -d opcache.enable_cli=1 + - name: Extra tests + uses: ./.github/actions/extra-tests - name: Verify generated files are up to date uses: ./.github/actions/verify-generated-files - name: Notify Slack @@ -355,6 +361,8 @@ jobs: ${{ matrix.run_tests_parameters }} -d zend_extension=opcache.so -d opcache.enable_cli=1 + - name: Extra tests + uses: ./.github/actions/extra-tests - name: Notify Slack if: failure() uses: ./.github/actions/notify-slack @@ -414,6 +422,8 @@ jobs: runTestsParameters: >- -d zend_extension=opcache.so -d opcache.enable_cli=1 + - name: Extra tests + uses: ./.github/actions/extra-tests - name: Verify generated files are up to date uses: ./.github/actions/verify-generated-files - name: Notify Slack @@ -1076,3 +1086,4 @@ jobs: with: configurationParameters: >- --${{ matrix.zts && 'enable' || 'disable' }}-zts + runExtraTests: true diff --git a/run-extra-tests.php b/run-extra-tests.php new file mode 100755 index 0000000000000..a299addf4044c --- /dev/null +++ b/run-extra-tests.php @@ -0,0 +1,123 @@ +#!/usr/bin/env php +os}\n"; + echo "CPU Arch: {$environment->cpuArch}\n"; + echo "ZTS: " . ($environment->zts ? 'Yes' : 'No') . "\n"; + echo "DEBUG: " . ($environment->debug ? 'Yes' : 'No') . "\n"; + echo "=====================================================================\n"; + + echo "No tests in this branch yet.\n"; + + echo "All OK\n"; +} + +function output_group_start(Environment $environment, string $name): void +{ + if ($environment->githubAction) { + printf("::group::%s\n", $name); + } else { + printf("%s\n", $name); + } +} + +function output_group_end(Environment $environment): void +{ + if ($environment->githubAction) { + printf("::endgroup::\n"); + } +} + +/** + * Returns getenv('TEST_PHP_OS') if defined, otherwise returns one of + * 'Windows NT', 'Linux', 'FreeBSD', 'Darwin', ... + */ +function detect_os(): string +{ + $os = (string) getenv('TEST_PHP_OS'); + if ($os !== '') { + return $os; + } + + return php_uname('s'); +} + +/** + * Returns getenv('TEST_PHP_CPU_ARCH') if defined, otherwise returns one of + * 'x86', 'x86_64', 'aarch64', ... + */ +function detect_cpu_arch(): string +{ + $cpu = (string) getenv('TEST_PHP_CPU_ARCH'); + if ($cpu !== '') { + return $cpu; + } + + $cpu = php_uname('m'); + if (strtolower($cpu) === 'amd64') { + $cpu = 'x86_64'; + } else if (in_array($cpu, ['i386', 'i686'])) { + $cpu = 'x86'; + } else if ($cpu === 'arm64') { + $cpu = 'aarch64'; + } + + return $cpu; +} + +main($argc, $argv); From 5dd965117a1d88cbd99dac9bf097d82c83819f69 Mon Sep 17 00:00:00 2001 From: dixyes Date: Sat, 26 Jul 2025 00:41:59 +0800 Subject: [PATCH 27/66] Free opened_path when opened_path_len >= MAXPATHLEN Closes GH-19240. --- NEWS | 3 +++ main/php_open_temporary_file.c | 1 + 2 files changed, 4 insertions(+) diff --git a/NEWS b/NEWS index 2b21969dffbce..94a14290a8bd4 100644 --- a/NEWS +++ b/NEWS @@ -39,6 +39,9 @@ PHP NEWS (ilutov) . Fix theoretical issues with hrtime() not being available. (nielsdos) +- Windows: + . Free opened_path when opened_path_len >= MAXPATHLEN. (dixyes) + 31 Jul 2025, PHP 8.3.24 - Calendar: diff --git a/main/php_open_temporary_file.c b/main/php_open_temporary_file.c index dcea78358486d..608f3ecf73bae 100644 --- a/main/php_open_temporary_file.c +++ b/main/php_open_temporary_file.c @@ -157,6 +157,7 @@ static int php_do_open_temporary_file(const char *path, const char *pfx, zend_st free(cwdw); free(pfxw); efree(new_state.cwd); + free(opened_path); return -1; } assert(strlen(opened_path) == opened_path_len); From e16df981bffe4b373c5fffd802f4182900feeab6 Mon Sep 17 00:00:00 2001 From: dixyes Date: Sat, 26 Jul 2025 00:34:40 +0800 Subject: [PATCH 28/66] ext/pdo_pgsql: Fix _pdo_pgsql_trim_message bad access close GH-19239 --- NEWS | 4 ++++ ext/pdo_pgsql/pgsql_driver.c | 8 +++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 94a14290a8bd4..9f714c37942ab 100644 --- a/NEWS +++ b/NEWS @@ -27,6 +27,10 @@ PHP NEWS return value check). (nielsdos, botovq) . Fix error return check of EVP_CIPHER_CTX_ctrl(). (nielsdos) +- PDO Pgsql: + . Fixed dangling pointer access on _pdo_pgsql_trim_message helper. + (dixyes) + - SOAP: . Fixed bug GH-18640 (heap-use-after-free ext/soap/php_encoding.c:299:32 in soap_check_zval_ref). (nielsdos) diff --git a/ext/pdo_pgsql/pgsql_driver.c b/ext/pdo_pgsql/pgsql_driver.c index 1cccfd2ab07ae..e9a6efe14fae3 100644 --- a/ext/pdo_pgsql/pgsql_driver.c +++ b/ext/pdo_pgsql/pgsql_driver.c @@ -38,8 +38,14 @@ static bool pgsql_handle_in_transaction(pdo_dbh_t *dbh); static char * _pdo_pgsql_trim_message(const char *message, int persistent) { - size_t i = strlen(message)-1; + size_t i = strlen(message); char *tmp; + if (UNEXPECTED(i == 0)) { + tmp = pemalloc(1, persistent); + tmp[0] = '\0'; + return tmp; + } + --i; if (i>1 && (message[i-1] == '\r' || message[i-1] == '\n') && message[i] == '.') { --i; From e1c4a0ae512c3059ff396b7450dfd4bac44ea289 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sun, 27 Jul 2025 12:20:21 +0100 Subject: [PATCH 29/66] Fixed GH-19261: msgfmt_parse_message leaks on message format failure. close GH-19262 --- NEWS | 4 ++++ ext/intl/msgformat/msgformat_parse.c | 3 ++- ext/intl/tests/gh19261.phpt | 28 ++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 ext/intl/tests/gh19261.phpt diff --git a/NEWS b/NEWS index 9f714c37942ab..b4cc8d8429fac 100644 --- a/NEWS +++ b/NEWS @@ -14,6 +14,10 @@ PHP NEWS - Hash: . Fix crash on clone failure. (nielsdos) +- Intl: + . Fixed GH-19261: msgfmt_parse_message leaks on message creation failure. + (David Carlier) + - LDAP: . Fixed bug GH-18529 (additional inheriting of TLS int options). (Jakub Zelenka) diff --git a/ext/intl/msgformat/msgformat_parse.c b/ext/intl/msgformat/msgformat_parse.c index baa6dfcce74b0..31441c92373da 100644 --- a/ext/intl/msgformat/msgformat_parse.c +++ b/ext/intl/msgformat/msgformat_parse.c @@ -127,10 +127,11 @@ PHP_FUNCTION( msgfmt_parse_message ) if(spattern && spattern_len) { efree(spattern); } - INTL_METHOD_CHECK_STATUS(mfo, "Creating message formatter failed"); + INTL_METHOD_CHECK_STATUS_OR_GOTO(mfo, "Creating message formatter failed", clean); msgfmt_do_parse(mfo, source, src_len, return_value); +clean: /* drop the temporary formatter */ msgformat_data_free(&mfo->mf_data); } diff --git a/ext/intl/tests/gh19261.phpt b/ext/intl/tests/gh19261.phpt new file mode 100644 index 0000000000000..3f281919a9562 --- /dev/null +++ b/ext/intl/tests/gh19261.phpt @@ -0,0 +1,28 @@ +--TEST-- +MessageFormatter::parseMessage() with invalid locale +--EXTENSIONS-- +intl +--CREDITS-- +girgias@php.net +--FILE-- + +--EXPECT-- +bool(false) +string(59) "Creating message formatter failed: U_ILLEGAL_ARGUMENT_ERROR" +bool(false) +string(59) "Creating message formatter failed: U_ILLEGAL_ARGUMENT_ERROR" From 17df11e3f7b96f8fcce9ccf48a7b676256a03255 Mon Sep 17 00:00:00 2001 From: Peter Kokot Date: Mon, 5 Aug 2024 15:54:50 +0200 Subject: [PATCH 30/66] Fix bug #51558: shared readline build fails (#15242) The 'rl_pending_input' is a variable in Readline library and checking it with PHP_CHECK_LIBRARY wouldn't find it on some systems. Library check works on most systems but not on the mentioned AIX in the bug as it exports variables and functions differently whereas the linker couldn't resolve the variable as a function. This should fix the build on systems where this caused issues, such as AIX. The is not self-contained header and needs to also have included before to have FILE type available. This fixes the issue on unpatched default readline installations, such as macOS. Checking this variable ensures that the found library is the correct library and also that it is of minimum version needed by current PHP code (https://bugs.php.net/48608). The library check: ```c | char rl_pending_input (); | int main (void) { | return rl_pending_input (); | } ``` The declaration check: ```c | #include | #include | int main (void) { | #ifndef rl_pending_input | #ifdef __cplusplus | (void) rl_pending_input; | #else | (void) rl_pending_input; | #endif | #endif | ; | return 0; | } ``` Closes https://bugs.php.net/51558 Closes GH-19259. --- NEWS | 4 ++++ ext/readline/config.m4 | 27 ++++++++++++++++++++------- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/NEWS b/NEWS index b4cc8d8429fac..4bf10555b459d 100644 --- a/NEWS +++ b/NEWS @@ -35,6 +35,10 @@ PHP NEWS . Fixed dangling pointer access on _pdo_pgsql_trim_message helper. (dixyes) +- Readline: + . Fixed bug GH-19250 and bug #51360 (Invalid conftest for rl_pending_input). + (petk, nielsdos) + - SOAP: . Fixed bug GH-18640 (heap-use-after-free ext/soap/php_encoding.c:299:32 in soap_check_zval_ref). (nielsdos) diff --git a/ext/readline/config.m4 b/ext/readline/config.m4 index 209f92b6e0a76..873b116fcffb6 100644 --- a/ext/readline/config.m4 +++ b/ext/readline/config.m4 @@ -47,13 +47,6 @@ if test "$PHP_READLINE" && test "$PHP_READLINE" != "no"; then -L$READLINE_DIR/$PHP_LIBDIR $PHP_READLINE_LIBS ]) - PHP_CHECK_LIBRARY(readline, rl_pending_input, - [], [ - AC_MSG_ERROR([invalid readline installation detected. Try --with-libedit instead.]) - ], [ - -L$READLINE_DIR/$PHP_LIBDIR $PHP_READLINE_LIBS - ]) - PHP_CHECK_LIBRARY(readline, rl_callback_read_char, [ AC_DEFINE(HAVE_RL_CALLBACK_READ_CHAR, 1, [ ]) @@ -75,6 +68,26 @@ if test "$PHP_READLINE" && test "$PHP_READLINE" != "no"; then -L$READLINE_DIR/$PHP_LIBDIR $PHP_READLINE_LIBS ]) + CFLAGS_SAVE=$CFLAGS + LDFLAGS_SAVE=$LDFLAGS + LIBS_SAVE=$LIBS + CFLAGS="$CFLAGS $INCLUDES" + LDFLAGS="$LDFLAGS -L$READLINE_DIR/$PHP_LIBDIR" + LIBS="$LIBS -lreadline" + + dnl Sanity and minimum version check if readline library has variable + dnl rl_pending_input. + AC_CHECK_DECL([rl_pending_input],, [AC_MSG_FAILURE([ + Invalid readline installation detected. Try --with-libedit instead. + ])], [ + #include + #include + ]) + + CFLAGS=$CFLAGS_SAVE + LDFLAGS=$LDFLAGS_SAVE + LIBS=$LIBS_SAVE + AC_DEFINE(HAVE_HISTORY_LIST, 1, [ ]) AC_DEFINE(HAVE_LIBREADLINE, 1, [ ]) From 83b8d2c290d24221e95c8fa1dc57a1b85f21fb70 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sat, 12 Jul 2025 18:30:08 +0100 Subject: [PATCH 31/66] ext/gd: Fix comparison with result of php_stream_can_cast() Closes GH-19107 --- NEWS | 3 +++ ext/gd/gd.c | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 4bf10555b459d..704398f5b6c32 100644 --- a/NEWS +++ b/NEWS @@ -11,6 +11,9 @@ PHP NEWS - FTP: . Fix theoretical issues with hrtime() not being available. (nielsdos) +- GD: + . Fix incorrect comparison with result of php_stream_can_cast(). (Girgias) + - Hash: . Fix crash on clone failure. (nielsdos) diff --git a/ext/gd/gd.c b/ext/gd/gd.c index 6b727a211189a..cce2a5ca42fc9 100644 --- a/ext/gd/gd.c +++ b/ext/gd/gd.c @@ -1566,7 +1566,7 @@ static void _php_image_create_from(INTERNAL_FUNCTION_PARAMETERS, int image_type, pefree(pstr, 1); zend_string_release_ex(buff, 0); } - else if (php_stream_can_cast(stream, PHP_STREAM_AS_STDIO)) { + else if (php_stream_can_cast(stream, PHP_STREAM_AS_STDIO) == SUCCESS) { /* try and force the stream to be FILE* */ if (FAILURE == php_stream_cast(stream, PHP_STREAM_AS_STDIO | PHP_STREAM_CAST_TRY_HARD, (void **) &fp, REPORT_ERRORS)) { goto out_err; From 6b0a80903bece4c383708b16cabc4659a0b6774a Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Mon, 28 Jul 2025 14:07:05 +0100 Subject: [PATCH 32/66] ext/intl: Fix return value on failure for resourcebundle count handler Closes GH-19277 --- NEWS | 1 + ext/intl/resourcebundle/resourcebundle_class.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 704398f5b6c32..a850699051ce2 100644 --- a/NEWS +++ b/NEWS @@ -20,6 +20,7 @@ PHP NEWS - Intl: . Fixed GH-19261: msgfmt_parse_message leaks on message creation failure. (David Carlier) + . Fix return value on failure for resourcebundle count handler. (Girgias) - LDAP: . Fixed bug GH-18529 (additional inheriting of TLS int options). diff --git a/ext/intl/resourcebundle/resourcebundle_class.c b/ext/intl/resourcebundle/resourcebundle_class.c index 9e86b41e761a2..8a2ff19d60efc 100644 --- a/ext/intl/resourcebundle/resourcebundle_class.c +++ b/ext/intl/resourcebundle/resourcebundle_class.c @@ -257,7 +257,7 @@ static zend_result resourcebundle_array_count(zend_object *object, zend_long *co if (rb->me == NULL) { intl_errors_set(&rb->error, U_ILLEGAL_ARGUMENT_ERROR, "Found unconstructed ResourceBundle", 0); - return 0; + return FAILURE; } *count = ures_getSize( rb->me ); From 3aaa8d3526c53f3e3a9901853910401bd3eb3aa9 Mon Sep 17 00:00:00 2001 From: Florian Engelhardt Date: Tue, 22 Jul 2025 10:39:56 +0200 Subject: [PATCH 33/66] Reset global pointers to prevent use-after-free Closes GH-19212. --- NEWS | 4 +++- ext/opcache/jit/zend_jit.c | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index d52050e3a8e34..a05b577d101b7 100644 --- a/NEWS +++ b/NEWS @@ -2,7 +2,9 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.1.34 - +- Opcache: + . Reset global pointers to prevent use-after-free in zend_jit_status(). + (Florian Engelhardt) 03 Jul 2025, PHP 8.1.33 diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index 4d2baddb90090..f0225b0c7a889 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -5087,6 +5087,14 @@ ZEND_EXT_API void zend_jit_shutdown(void) #else zend_jit_trace_free_caches(&jit_globals); #endif + + /* Reset global pointers to prevent use-after-free in `zend_jit_status()` + * after gracefully restarting Apache with mod_php, see: + * https://github.com/php/php-src/pull/19212 */ + dasm_ptr = NULL; + dasm_buf = NULL; + dasm_end = NULL; + dasm_size = 0; } static void zend_jit_reset_counters(void) From 80022c035ba153ebf23b17da5fcb30993df7c76b Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Tue, 29 Jul 2025 22:34:47 +0200 Subject: [PATCH 34/66] Fix failed assertion with throwing __toString in binary const expr Solve this with the same pattern as ZEND_AST_GREATER[_EQUAL]. Fixes OSS-Fuzz #434346548 Closes GH-19291 --- NEWS | 2 ++ Zend/tests/oss_fuzz_434346548.phpt | 22 ++++++++++++++++++++++ Zend/zend_ast.c | 3 ++- 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 Zend/tests/oss_fuzz_434346548.phpt diff --git a/NEWS b/NEWS index f9cac590c867e..4cd237ffeedd9 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,8 @@ PHP NEWS (psumbera) . Fixed bug GH-18581 (Coerce numeric string keys from iterators when argument unpacking). (ilutov) + . Fixed OSS-Fuzz #434346548 (Failed assertion with throwing __toString in + binary const expr). (ilutov) - FTP: . Fix theoretical issues with hrtime() not being available. (nielsdos) diff --git a/Zend/tests/oss_fuzz_434346548.phpt b/Zend/tests/oss_fuzz_434346548.phpt new file mode 100644 index 0000000000000..139e36758bc49 --- /dev/null +++ b/Zend/tests/oss_fuzz_434346548.phpt @@ -0,0 +1,22 @@ +--TEST-- +OSS-Fuzz #434346548: Failed assertion with throwing __toString in binary const expr +--FILE-- +getMessage(), "\n"; +} + +?> +--EXPECT-- +Foo::__toString(): Return value must be of type string, none returned diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index f8c4ca17a9b95..1ecb85f1d2a2f 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -548,9 +548,10 @@ ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate_inner( ret = FAILURE; } else { binary_op_type op = get_binary_op(ast->attr); - ret = op(result, &op1, &op2); + op(result, &op1, &op2); zval_ptr_dtor_nogc(&op1); zval_ptr_dtor_nogc(&op2); + ret = EG(exception) ? FAILURE : SUCCESS; } break; case ZEND_AST_GREATER: From bc4b6ce7a87be88ff9d85a32b5fcb4b9477ae788 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 30 Jul 2025 14:57:52 +0200 Subject: [PATCH 35/66] Prevent operands from being released during comparison Fixes GH-19305 Closes GH-19309 --- NEWS | 2 ++ Zend/tests/gh19305-001.phpt | 27 +++++++++++++++++++++++++++ Zend/tests/gh19305-002.phpt | 27 +++++++++++++++++++++++++++ Zend/tests/gh19305-003.phpt | 28 ++++++++++++++++++++++++++++ Zend/zend_object_handlers.c | 33 ++++++++++++++++++++++++++------- Zend/zend_operators.c | 14 +++++++++++++- Zend/zend_types.h | 12 ++++++++++++ 7 files changed, 135 insertions(+), 8 deletions(-) create mode 100644 Zend/tests/gh19305-001.phpt create mode 100644 Zend/tests/gh19305-002.phpt create mode 100644 Zend/tests/gh19305-003.phpt diff --git a/NEWS b/NEWS index 4cd237ffeedd9..fb6a541bbf665 100644 --- a/NEWS +++ b/NEWS @@ -9,6 +9,8 @@ PHP NEWS unpacking). (ilutov) . Fixed OSS-Fuzz #434346548 (Failed assertion with throwing __toString in binary const expr). (ilutov) + . Fixed bug GH-19305 (Operands may be being released during comparison). + (Arnaud) - FTP: . Fix theoretical issues with hrtime() not being available. (nielsdos) diff --git a/Zend/tests/gh19305-001.phpt b/Zend/tests/gh19305-001.phpt new file mode 100644 index 0000000000000..4c9ee37473e34 --- /dev/null +++ b/Zend/tests/gh19305-001.phpt @@ -0,0 +1,27 @@ +--TEST-- +GH-19305 001: Operands may be released during comparison +--FILE-- + 'test', + 'bar' => 2, +]; +$b = (object)[ + 'foo' => new class { + public function __toString() { + global $a, $b; + $a = $b = null; + return ''; + } + }, + 'bar' => 2, +]; + +// Comparison of $a->foo and $b->foo calls __toString(), which releases +// both $a and $b. +var_dump($a > $b); + +?> +--EXPECT-- +bool(true) diff --git a/Zend/tests/gh19305-002.phpt b/Zend/tests/gh19305-002.phpt new file mode 100644 index 0000000000000..790156191317b --- /dev/null +++ b/Zend/tests/gh19305-002.phpt @@ -0,0 +1,27 @@ +--TEST-- +GH-19305 002: Operands may be released during comparison +--FILE-- + 'test', + 'bar' => 2, +]; +$b = [ + 'foo' => new class { + public function __toString() { + global $a, $b; + $a = $b = null; + return ''; + } + }, + 'bar' => 2, +]; + +// Comparison of $a['foo'] and $b['foo'] calls __toString(), which releases +// both $a and $b. +var_dump($a > $b); + +?> +--EXPECT-- +bool(true) diff --git a/Zend/tests/gh19305-003.phpt b/Zend/tests/gh19305-003.phpt new file mode 100644 index 0000000000000..7b071156ef8e3 --- /dev/null +++ b/Zend/tests/gh19305-003.phpt @@ -0,0 +1,28 @@ +--TEST-- +GH-19305 003: Operands may be released during comparison +--SKIPIF-- + +--FILE-- +newLazyProxy(function () { return new C; }); + +// Comparison calls initializers, which releases $o +var_dump($o > +$r->newLazyGhost(function () { + global $o; + $o = null; +})); + +?> +--EXPECT-- +bool(false) diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 180364b248d71..2eacb614f97c4 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -1792,6 +1792,10 @@ ZEND_API int zend_std_compare_objects(zval *o1, zval *o2) /* {{{ */ } Z_PROTECT_RECURSION_P(o1); + GC_ADDREF(zobj1); + GC_ADDREF(zobj2); + int ret; + for (i = 0; i < zobj1->ce->default_properties_count; i++) { zval *p1, *p2; @@ -1806,35 +1810,50 @@ ZEND_API int zend_std_compare_objects(zval *o1, zval *o2) /* {{{ */ if (Z_TYPE_P(p1) != IS_UNDEF) { if (Z_TYPE_P(p2) != IS_UNDEF) { - int ret; - ret = zend_compare(p1, p2); if (ret != 0) { Z_UNPROTECT_RECURSION_P(o1); - return ret; + goto done; } } else { Z_UNPROTECT_RECURSION_P(o1); - return 1; + ret = 1; + goto done; } } else { if (Z_TYPE_P(p2) != IS_UNDEF) { Z_UNPROTECT_RECURSION_P(o1); - return 1; + ret = 1; + goto done; } } } Z_UNPROTECT_RECURSION_P(o1); - return 0; + ret = 0; + +done: + OBJ_RELEASE(zobj1); + OBJ_RELEASE(zobj2); + + return ret; } else { + GC_ADDREF(zobj1); + GC_ADDREF(zobj2); + if (!zobj1->properties) { rebuild_object_properties(zobj1); } if (!zobj2->properties) { rebuild_object_properties(zobj2); } - return zend_compare_symbol_tables(zobj1->properties, zobj2->properties); + + int ret = zend_compare_symbol_tables(zobj1->properties, zobj2->properties); + + OBJ_RELEASE(zobj1); + OBJ_RELEASE(zobj2); + + return ret; } } /* }}} */ diff --git a/Zend/zend_operators.c b/Zend/zend_operators.c index 38c87dfe98dbd..8646fc25be260 100644 --- a/Zend/zend_operators.c +++ b/Zend/zend_operators.c @@ -3384,7 +3384,19 @@ static int hash_zval_compare_function(zval *z1, zval *z2) /* {{{ */ ZEND_API int ZEND_FASTCALL zend_compare_symbol_tables(HashTable *ht1, HashTable *ht2) /* {{{ */ { - return ht1 == ht2 ? 0 : zend_hash_compare(ht1, ht2, (compare_func_t) hash_zval_compare_function, 0); + if (ht1 == ht2) { + return 0; + } + + GC_TRY_ADDREF(ht1); + GC_TRY_ADDREF(ht2); + + int ret = zend_hash_compare(ht1, ht2, (compare_func_t) hash_zval_compare_function, 0); + + GC_TRY_DTOR_NO_REF(ht1); + GC_TRY_DTOR_NO_REF(ht2); + + return ret; } /* }}} */ diff --git a/Zend/zend_types.h b/Zend/zend_types.h index c4a07f58874ab..63f6ddbc97242 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -731,6 +731,18 @@ static zend_always_inline uint8_t zval_get_type(const zval* pz) { } \ } while (0) +#define GC_TRY_DTOR_NO_REF(p) \ + do { \ + zend_refcounted_h *_p = &(p)->gc; \ + if (!(_p->u.type_info & GC_IMMUTABLE)) { \ + if (zend_gc_delref(_p) == 0) { \ + rc_dtor_func((zend_refcounted *)_p); \ + } else { \ + gc_check_possible_root_no_ref((zend_refcounted *)_p); \ + } \ + } \ + } while (0) + #define GC_TYPE_MASK 0x0000000f #define GC_FLAGS_MASK 0x000003f0 #define GC_INFO_MASK 0xfffffc00 From 5bd5f352e5aeeda3154deffee21caa227e0ce48b Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Wed, 30 Jul 2025 18:44:31 +0200 Subject: [PATCH 36/66] Fix GH-19303: Unpacking empty packed array into uninitialized array causes assertion failure Having an empty result array is not a problem, because zend_hash_extend() will initialize it. Except it does not when the number of elements to add equals 0, which leaves the array uninitialized and therefore does not set the packed flag, causing the assertion failure. Technically, removing the assert would also work and save a check. On the other hand, this check could also prevent some real work to be done and should be relatively cheap as we already have to compute the sum anyway. Closes GH-19318. --- NEWS | 2 ++ Zend/tests/array_unpack/gh19303.phpt | 11 +++++++++++ Zend/zend_vm_def.h | 27 ++++++++++++++++----------- Zend/zend_vm_execute.h | 27 ++++++++++++++++----------- 4 files changed, 45 insertions(+), 22 deletions(-) create mode 100644 Zend/tests/array_unpack/gh19303.phpt diff --git a/NEWS b/NEWS index fb6a541bbf665..ced51075fab1e 100644 --- a/NEWS +++ b/NEWS @@ -11,6 +11,8 @@ PHP NEWS binary const expr). (ilutov) . Fixed bug GH-19305 (Operands may be being released during comparison). (Arnaud) + . Fixed bug GH-19303 (Unpacking empty packed array into uninitialized array + causes assertion failure). (nielsdos) - FTP: . Fix theoretical issues with hrtime() not being available. (nielsdos) diff --git a/Zend/tests/array_unpack/gh19303.phpt b/Zend/tests/array_unpack/gh19303.phpt new file mode 100644 index 0000000000000..af594c3740c2d --- /dev/null +++ b/Zend/tests/array_unpack/gh19303.phpt @@ -0,0 +1,11 @@ +--TEST-- +GH-19303 (Unpacking empty packed array into uninitialized array causes assertion failure) +--FILE-- + +--EXPECT-- +array(0) { +} diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 7c9f151134fe2..f1d4f7448ce1e 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -6162,17 +6162,22 @@ ZEND_VM_C_LABEL(add_unpack_again): zval *val; if (HT_IS_PACKED(ht) && (zend_hash_num_elements(result_ht) == 0 || HT_IS_PACKED(result_ht))) { - zend_hash_extend(result_ht, result_ht->nNumUsed + zend_hash_num_elements(ht), 1); - ZEND_HASH_FILL_PACKED(result_ht) { - ZEND_HASH_PACKED_FOREACH_VAL(ht, val) { - if (UNEXPECTED(Z_ISREF_P(val)) && - UNEXPECTED(Z_REFCOUNT_P(val) == 1)) { - val = Z_REFVAL_P(val); - } - Z_TRY_ADDREF_P(val); - ZEND_HASH_FILL_ADD(val); - } ZEND_HASH_FOREACH_END(); - } ZEND_HASH_FILL_END(); + /* zend_hash_extend() skips initialization when the number of elements is 0, + * but the code below expects that result_ht is initialized as packed. + * We can just skip the work in that case. */ + if (result_ht->nNumUsed + zend_hash_num_elements(ht) > 0) { + zend_hash_extend(result_ht, result_ht->nNumUsed + zend_hash_num_elements(ht), 1); + ZEND_HASH_FILL_PACKED(result_ht) { + ZEND_HASH_PACKED_FOREACH_VAL(ht, val) { + if (UNEXPECTED(Z_ISREF_P(val)) && + UNEXPECTED(Z_REFCOUNT_P(val) == 1)) { + val = Z_REFVAL_P(val); + } + Z_TRY_ADDREF_P(val); + ZEND_HASH_FILL_ADD(val); + } ZEND_HASH_FOREACH_END(); + } ZEND_HASH_FILL_END(); + } } else { zend_string *key; diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 9daa00d97c418..5f7f4997ce8d7 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -2657,17 +2657,22 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ADD_ARRAY_UNPACK_SPEC_HANDLER( zval *val; if (HT_IS_PACKED(ht) && (zend_hash_num_elements(result_ht) == 0 || HT_IS_PACKED(result_ht))) { - zend_hash_extend(result_ht, result_ht->nNumUsed + zend_hash_num_elements(ht), 1); - ZEND_HASH_FILL_PACKED(result_ht) { - ZEND_HASH_PACKED_FOREACH_VAL(ht, val) { - if (UNEXPECTED(Z_ISREF_P(val)) && - UNEXPECTED(Z_REFCOUNT_P(val) == 1)) { - val = Z_REFVAL_P(val); - } - Z_TRY_ADDREF_P(val); - ZEND_HASH_FILL_ADD(val); - } ZEND_HASH_FOREACH_END(); - } ZEND_HASH_FILL_END(); + /* zend_hash_extend() skips initialization when the number of elements is 0, + * but the code below expects that result_ht is initialized as packed. + * We can just skip the work in that case. */ + if (result_ht->nNumUsed + zend_hash_num_elements(ht) > 0) { + zend_hash_extend(result_ht, result_ht->nNumUsed + zend_hash_num_elements(ht), 1); + ZEND_HASH_FILL_PACKED(result_ht) { + ZEND_HASH_PACKED_FOREACH_VAL(ht, val) { + if (UNEXPECTED(Z_ISREF_P(val)) && + UNEXPECTED(Z_REFCOUNT_P(val) == 1)) { + val = Z_REFVAL_P(val); + } + Z_TRY_ADDREF_P(val); + ZEND_HASH_FILL_ADD(val); + } ZEND_HASH_FOREACH_END(); + } ZEND_HASH_FILL_END(); + } } else { zend_string *key; From 0406a55c923bc61e3a09ed273ae9b834d01cb951 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 30 Jul 2025 16:56:55 +0200 Subject: [PATCH 37/66] Prevent resumption of generator suspended in yield from Normally we prevent generators from being resumed while they are already running, but we failed to do so for generators delegating to non-Generators. As a result such generator can be resumed, terminated, which causes unexpected results (crashes) later. In gh19306.phpt in particular, the generator delegate It::getIterator() suspends while being called by generator g(). We then resume g(), which throws while trying to resume It::getIterator(). This causes g() and It::getIterator() to be released. We then UAF when resuming the Fiber in It::getIterator(). Fix this by ensuring that generators are marked as running while they fetch the next value from the delegate. Fixes GH-19306 Closes GH-19315 --- NEWS | 2 ++ Zend/tests/gh19306.phpt | 40 ++++++++++++++++++++++++++++++++++++++++ Zend/zend_generators.c | 5 +++-- 3 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 Zend/tests/gh19306.phpt diff --git a/NEWS b/NEWS index ced51075fab1e..9f1f48a9ee958 100644 --- a/NEWS +++ b/NEWS @@ -13,6 +13,8 @@ PHP NEWS (Arnaud) . Fixed bug GH-19303 (Unpacking empty packed array into uninitialized array causes assertion failure). (nielsdos) + . Fixed bug GH-19306 (Generator can be resumed while fetching next value from + delegated Generator). (Arnaud) - FTP: . Fix theoretical issues with hrtime() not being available. (nielsdos) diff --git a/Zend/tests/gh19306.phpt b/Zend/tests/gh19306.phpt new file mode 100644 index 0000000000000..e19735d94c846 --- /dev/null +++ b/Zend/tests/gh19306.phpt @@ -0,0 +1,40 @@ +--TEST-- +GH-19306: Generator suspended in yield from may be resumed +--FILE-- +next(); + echo "Fiber return\n"; +}); +$fiber->start(); +echo "Fiber suspended\n"; +try { + $a->next(); +} catch (Throwable $t) { + echo $t->getMessage(), "\n"; +} +echo "Destroying fiber\n"; +$fiber = null; +echo "Shutdown\n"; +?> +--EXPECT-- +Fiber start +Fiber suspended +Cannot resume an already running generator +Destroying fiber +Shutdown diff --git a/Zend/zend_generators.c b/Zend/zend_generators.c index f82dd4c1daf13..3ba582fef6e71 100644 --- a/Zend/zend_generators.c +++ b/Zend/zend_generators.c @@ -787,6 +787,8 @@ ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */ orig_generator->execute_fake.prev_execute_data = original_execute_data; } + generator->flags |= ZEND_GENERATOR_CURRENTLY_RUNNING; + /* Ensure this is run after executor_data swap to have a proper stack trace */ if (UNEXPECTED(!Z_ISUNDEF(generator->values))) { if (EXPECTED(zend_generator_get_next_delegated_value(generator) == SUCCESS)) { @@ -795,7 +797,7 @@ ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */ EG(jit_trace_num) = original_jit_trace_num; orig_generator->flags &= ~(ZEND_GENERATOR_DO_INIT | ZEND_GENERATOR_IN_FIBER); - generator->flags &= ~ZEND_GENERATOR_IN_FIBER; + generator->flags &= ~(ZEND_GENERATOR_CURRENTLY_RUNNING | ZEND_GENERATOR_IN_FIBER); return; } if (UNEXPECTED(EG(exception))) { @@ -817,7 +819,6 @@ ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */ } /* Resume execution */ - generator->flags |= ZEND_GENERATOR_CURRENTLY_RUNNING; if (!ZEND_OBSERVER_ENABLED) { zend_execute_ex(generator->execute_data); } else { From 6fa8a25a405495c868ef643f41a7b837762b21b3 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Thu, 31 Jul 2025 09:18:23 +0200 Subject: [PATCH 38/66] Prevent throwing in running generator Generator::throw() on a running generator is not allowed. It throws "Cannot resume an already running generator" when trying to resume the generator to handle the provided exception. However, when calling Generator::throw() on a generator with a non-Generator delegate, we release the delegate regardless. If a Fiber was suspended in the delegate, this causes use after frees when the Fiber is resumed. Fix this by throwing "Cannot resume an already running generator" earlier. Fixes GH-19326 Closes GH-19327 --- NEWS | 2 ++ Zend/tests/gh19326.phpt | 36 ++++++++++++++++++++++++++++++++++++ Zend/zend_generators.c | 14 ++++++++++++-- 3 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 Zend/tests/gh19326.phpt diff --git a/NEWS b/NEWS index 9f1f48a9ee958..0b261679e788d 100644 --- a/NEWS +++ b/NEWS @@ -15,6 +15,8 @@ PHP NEWS causes assertion failure). (nielsdos) . Fixed bug GH-19306 (Generator can be resumed while fetching next value from delegated Generator). (Arnaud) + . Fixed bug GH-19326 (Calling Generator::throw() on a running generator with + a non-Generator delegate crashes). (Arnaud) - FTP: . Fix theoretical issues with hrtime() not being available. (nielsdos) diff --git a/Zend/tests/gh19326.phpt b/Zend/tests/gh19326.phpt new file mode 100644 index 0000000000000..335fdd382ea67 --- /dev/null +++ b/Zend/tests/gh19326.phpt @@ -0,0 +1,36 @@ +--TEST-- +GH-19326: Calling Generator::throw() on a running generator with a non-Generator delegate crashes +--FILE-- +rewind(); + +$fiber = new Fiber(function () use ($b) { + $b->next(); +}); + +$fiber->start(); + +try { + $b->throw(new Exception('test')); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +$fiber->resume(); + +?> +--EXPECT-- +Cannot resume an already running generator diff --git a/Zend/zend_generators.c b/Zend/zend_generators.c index 3ba582fef6e71..eeab16b9a1352 100644 --- a/Zend/zend_generators.c +++ b/Zend/zend_generators.c @@ -492,8 +492,14 @@ ZEND_API zend_execute_data *zend_generator_check_placeholder_frame(zend_execute_ return ptr; } -static void zend_generator_throw_exception(zend_generator *generator, zval *exception) +static zend_result zend_generator_throw_exception(zend_generator *generator, zval *exception) { + if (generator->flags & ZEND_GENERATOR_CURRENTLY_RUNNING) { + zval_ptr_dtor(exception); + zend_throw_error(NULL, "Cannot resume an already running generator"); + return FAILURE; + } + zend_execute_data *original_execute_data = EG(current_execute_data); /* Throw the exception in the context of the generator. Decrementing the opline @@ -519,6 +525,8 @@ static void zend_generator_throw_exception(zend_generator *generator, zval *exce } EG(current_execute_data) = original_execute_data; + + return SUCCESS; } static void zend_generator_add_child(zend_generator *generator, zend_generator *child) @@ -1026,7 +1034,9 @@ ZEND_METHOD(Generator, throw) if (generator->execute_data) { zend_generator *root = zend_generator_get_current(generator); - zend_generator_throw_exception(root, exception); + if (zend_generator_throw_exception(root, exception) == FAILURE) { + return; + } zend_generator_resume(generator); From a96b05e63f92c835976a51499e73fea5b9591fdf Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Wed, 30 Jul 2025 19:04:15 +0200 Subject: [PATCH 39/66] Fix GH-19300: Nested array_multisort invocation with error breaks There are 2 issues: 1. When a MULTISORT_ABORT happens, it frees func, but func may point to ARRAYG(multisort_func), which would be a problem with nested invocations as it can destroy that of the "parent" invocation. To solve this, delay assigning to the globals. 2. The old globals were not restored which means that nested invocations with different flags will cause a wrong sorting function to be used. Closes GH-19319. --- NEWS | 2 ++ ext/standard/array.c | 12 +++++--- ext/standard/tests/array/gh19300_1.phpt | 40 +++++++++++++++++++++++++ ext/standard/tests/array/gh19300_2.phpt | 40 +++++++++++++++++++++++++ 4 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 ext/standard/tests/array/gh19300_1.phpt create mode 100644 ext/standard/tests/array/gh19300_2.phpt diff --git a/NEWS b/NEWS index 0b261679e788d..e89bc61148d1f 100644 --- a/NEWS +++ b/NEWS @@ -68,6 +68,8 @@ PHP NEWS . Fixed OSS Fuzz #433303828 (Leak in failed unserialize() with opcache). (ilutov) . Fix theoretical issues with hrtime() not being available. (nielsdos) + . Fixed bug GH-19300 (Nested array_multisort invocation with error breaks). + (nielsdos) - Windows: . Free opened_path when opened_path_len >= MAXPATHLEN. (dixyes) diff --git a/ext/standard/array.c b/ext/standard/array.c index 4d3c03bce5262..a2a0459ec3b29 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -5911,7 +5911,7 @@ PHP_FUNCTION(array_multisort) for (i = 0; i < MULTISORT_LAST; i++) { parse_state[i] = 0; } - func = ARRAYG(multisort_func) = ecalloc(argc, sizeof(bucket_compare_func_t)); + func = ecalloc(argc, sizeof(bucket_compare_func_t)); /* Here we go through the input arguments and parse them. Each one can * be either an array or a sort flag which follows an array. If not @@ -5927,7 +5927,7 @@ PHP_FUNCTION(array_multisort) /* We see the next array, so we update the sort flags of * the previous array and reset the sort flags. */ if (i > 0) { - ARRAYG(multisort_func)[num_arrays - 1] = php_get_data_compare_func_unstable(sort_type, sort_order != PHP_SORT_ASC); + func[num_arrays - 1] = php_get_data_compare_func_unstable(sort_type, sort_order != PHP_SORT_ASC); sort_order = PHP_SORT_ASC; sort_type = PHP_SORT_REGULAR; } @@ -5979,8 +5979,6 @@ PHP_FUNCTION(array_multisort) MULTISORT_ABORT; } } - /* Take care of the last array sort flags. */ - ARRAYG(multisort_func)[num_arrays - 1] = php_get_data_compare_func_unstable(sort_type, sort_order != PHP_SORT_ASC); /* Make sure the arrays are of the same size. */ array_size = zend_hash_num_elements(Z_ARRVAL_P(arrays[0])); @@ -5998,6 +5996,11 @@ PHP_FUNCTION(array_multisort) RETURN_TRUE; } + /* Take care of the last array sort flags. */ + func[num_arrays - 1] = php_get_data_compare_func_unstable(sort_type, sort_order != PHP_SORT_ASC); + bucket_compare_func_t *old_multisort_func = ARRAYG(multisort_func); + ARRAYG(multisort_func) = func; + /* Create the indirection array. This array is of size MxN, where * M is the number of entries in each input array and N is the number * of the input arrays + 1. The last column is UNDEF to indicate the end @@ -6074,6 +6077,7 @@ PHP_FUNCTION(array_multisort) efree(indirect); efree(func); efree(arrays); + ARRAYG(multisort_func) = old_multisort_func; } /* }}} */ diff --git a/ext/standard/tests/array/gh19300_1.phpt b/ext/standard/tests/array/gh19300_1.phpt new file mode 100644 index 0000000000000..18b639bf425b8 --- /dev/null +++ b/ext/standard/tests/array/gh19300_1.phpt @@ -0,0 +1,40 @@ +--TEST-- +GH-19300 (Nested array_multisort invocation with error breaks) - correct invocation variation +--FILE-- +data; + } +} + +$inputs = [ + new MyStringable('3'), + new MyStringable('1'), + new MyStringable('2'), +]; + +var_dump(array_multisort($inputs, SORT_STRING)); +var_dump($inputs); +?> +--EXPECT-- +bool(true) +array(3) { + [0]=> + object(MyStringable)#2 (1) { + ["data":"MyStringable":private]=> + string(1) "1" + } + [1]=> + object(MyStringable)#3 (1) { + ["data":"MyStringable":private]=> + string(1) "2" + } + [2]=> + object(MyStringable)#1 (1) { + ["data":"MyStringable":private]=> + string(1) "3" + } +} diff --git a/ext/standard/tests/array/gh19300_2.phpt b/ext/standard/tests/array/gh19300_2.phpt new file mode 100644 index 0000000000000..41ae7e82bb796 --- /dev/null +++ b/ext/standard/tests/array/gh19300_2.phpt @@ -0,0 +1,40 @@ +--TEST-- +GH-19300 (Nested array_multisort invocation with error breaks) - error variation +--FILE-- +getMessage(), "\n"; + } +} +set_error_handler('error_handle'); + +$inputs = [ + new stdClass, + new stdClass, + new stdClass, +]; + +var_dump(array_multisort($inputs, SORT_NUMERIC)); +var_dump($inputs); +?> +--EXPECT-- +array_multisort(): Argument #1 ($array) must be an array or a sort flag +array_multisort(): Argument #1 ($array) must be an array or a sort flag +array_multisort(): Argument #1 ($array) must be an array or a sort flag +array_multisort(): Argument #1 ($array) must be an array or a sort flag +bool(true) +array(3) { + [0]=> + object(stdClass)#1 (0) { + } + [1]=> + object(stdClass)#2 (0) { + } + [2]=> + object(stdClass)#3 (0) { + } +} From 9ce51dad8bc0847f64b32fbbe623d79d49f6a5f0 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Mon, 21 Jul 2025 22:48:15 +0200 Subject: [PATCH 40/66] Add missing hooks JIT restart code Closes GH-19207. --- NEWS | 1 + ext/opcache/jit/zend_jit.c | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/NEWS b/NEWS index 409c613f6ebb0..aa2961a6ec352 100644 --- a/NEWS +++ b/NEWS @@ -46,6 +46,7 @@ PHP NEWS - Opcache: . Reset global pointers to prevent use-after-free in zend_jit_status(). (Florian Engelhardt) + . Fix issue with JIT restart and hooks. (nielsdos) - OpenSSL: . Fixed bug GH-18986 (OpenSSL backend: incorrect RAND_{load,write}_file() diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index fd1203f086ee1..b0423dc06bd1e 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -3836,6 +3836,24 @@ static void zend_jit_restart_preloaded_script(zend_persistent_script *script) zend_jit_restart_preloaded_op_array(op_array); } } ZEND_HASH_FOREACH_END(); + + if (ce->num_hooked_props > 0) { + zend_property_info *prop; + + ZEND_HASH_MAP_FOREACH_PTR(&ce->properties_info, prop) { + if (prop->hooks) { + for (uint32_t i = 0; i < ZEND_PROPERTY_HOOK_COUNT; i++) { + if (prop->hooks[i]) { + op_array = &prop->hooks[i]->op_array; + ZEND_ASSERT(op_array->type == ZEND_USER_FUNCTION); + if (!(op_array->fn_flags & ZEND_ACC_TRAIT_CLONE)) { + zend_jit_restart_preloaded_op_array(op_array); + } + } + } + } + } ZEND_HASH_FOREACH_END(); + } } ZEND_HASH_FOREACH_END(); } From 771bfaf34d6de1260ee9b02ff7145ea4fc081301 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Mon, 21 Jul 2025 22:44:03 +0200 Subject: [PATCH 41/66] Remove dynamic defs from property hooks Otherwise this hits an assertion failure in pass2 reversal and causes a subsequent crash. Closes GH-19206. --- NEWS | 1 + ext/opcache/ZendAccelerator.c | 18 ++++++++++++++++++ .../tests/preload_dynamic_def_removal.inc | 10 ++++++++++ .../tests/preload_dynamic_def_removal.phpt | 2 ++ 4 files changed, 31 insertions(+) diff --git a/NEWS b/NEWS index aa2961a6ec352..84006fc58f2cd 100644 --- a/NEWS +++ b/NEWS @@ -47,6 +47,7 @@ PHP NEWS . Reset global pointers to prevent use-after-free in zend_jit_status(). (Florian Engelhardt) . Fix issue with JIT restart and hooks. (nielsdos) + . Fix crash with dynamic function defs in hooks during preload. (nielsdos) - OpenSSL: . Fixed bug GH-18986 (OpenSSL backend: incorrect RAND_{load,write}_file() diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 83dedb1e74272..1e661314f2044 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -4132,6 +4132,24 @@ static void preload_link(void) preload_remove_declares(op_array); } } ZEND_HASH_FOREACH_END(); + + if (ce->num_hooked_props > 0) { + zend_property_info *info; + + ZEND_HASH_MAP_FOREACH_PTR(&ce->properties_info, info) { + if (info->hooks) { + for (uint32_t i = 0; i < ZEND_PROPERTY_HOOK_COUNT; i++) { + if (info->hooks[i]) { + op_array = &info->hooks[i]->op_array; + ZEND_ASSERT(op_array->type == ZEND_USER_FUNCTION); + if (!(op_array->fn_flags & ZEND_ACC_TRAIT_CLONE)) { + preload_remove_declares(op_array); + } + } + } + } + } ZEND_HASH_FOREACH_END(); + } } ZEND_HASH_FOREACH_END(); } diff --git a/ext/opcache/tests/preload_dynamic_def_removal.inc b/ext/opcache/tests/preload_dynamic_def_removal.inc index 27ec69120eac5..2a6a44ef509a2 100644 --- a/ext/opcache/tests/preload_dynamic_def_removal.inc +++ b/ext/opcache/tests/preload_dynamic_def_removal.inc @@ -5,6 +5,15 @@ class Test { echo "dynamic\n"; } } + + public int $hook { + get { + function dynamic_in_hook() { + echo "dynamic in hook\n"; + } + return 1; + } + } } function func() { @@ -16,3 +25,4 @@ function func() { $test = new Test; $test->method(); func(); +$test->hook; diff --git a/ext/opcache/tests/preload_dynamic_def_removal.phpt b/ext/opcache/tests/preload_dynamic_def_removal.phpt index d4f2bb070ccd5..acc26873e1867 100644 --- a/ext/opcache/tests/preload_dynamic_def_removal.phpt +++ b/ext/opcache/tests/preload_dynamic_def_removal.phpt @@ -15,7 +15,9 @@ if (PHP_OS_FAMILY == 'Windows') die('skip Preloading is not supported on Windows --EXPECT-- dynamic dynamic2 +dynamic in hook From 5d40592fe28c87e027794a091d08f0d4cf280885 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Wed, 30 Jul 2025 23:57:21 +0200 Subject: [PATCH 42/66] Fix stale nInternalPosition on rehashing Since GH-13188 we're no longer immediately updating iterator positions when deleting array elements. zend_hash_rehash() needs to adapt accordingly by adjusting nInternalPosition for IS_UNDEF elements. This is already the case for array iterators. Fixes GH-19280 Closes GH-19323 --- NEWS | 1 + Zend/tests/gh19280.phpt | 81 +++++++++++++++++++++++++++++++++++++++++ Zend/zend_hash.c | 4 +- 3 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 Zend/tests/gh19280.phpt diff --git a/NEWS b/NEWS index 84006fc58f2cd..93f804bb5d982 100644 --- a/NEWS +++ b/NEWS @@ -21,6 +21,7 @@ PHP NEWS delegated Generator). (Arnaud) . Fixed bug GH-19326 (Calling Generator::throw() on a running generator with a non-Generator delegate crashes). (Arnaud) + . Fixed bug GH-19280 (Stale array iterator position on rehashing). (ilutov) - FTP: . Fix theoretical issues with hrtime() not being available. (nielsdos) diff --git a/Zend/tests/gh19280.phpt b/Zend/tests/gh19280.phpt new file mode 100644 index 0000000000000..d73fc91b623fa --- /dev/null +++ b/Zend/tests/gh19280.phpt @@ -0,0 +1,81 @@ +--TEST-- +GH-19280: Stale nInternalPosition on rehashing +--FILE-- += 0; $i--) { + $a[$i] = $i; + } + for ($i = 0; $i <= 47; $i++) { + next($a); + } + for ($i = 48; $i >= 2; $i--) { + unset($a[$i]); + } + var_dump(key($a)); + $a[64] = 64; + var_dump(key($a)); +} + +rehash_packed(); +rehash_packed_iterated(); +rehash_string(); +rehash_int(); + +?> +--EXPECT-- +int(62) +int(62) +int(62) +int(62) +string(32) "44f683a84163b3523afe57c2e008bc8c" +string(32) "44f683a84163b3523afe57c2e008bc8c" +int(1) +int(1) diff --git a/Zend/zend_hash.c b/Zend/zend_hash.c index a417290b82c49..07dad65f1d274 100644 --- a/Zend/zend_hash.c +++ b/Zend/zend_hash.c @@ -1379,7 +1379,7 @@ ZEND_API void ZEND_FASTCALL zend_hash_rehash(HashTable *ht) q->key = p->key; Z_NEXT(q->val) = HT_HASH(ht, nIndex); HT_HASH(ht, nIndex) = HT_IDX_TO_HASH(j); - if (UNEXPECTED(ht->nInternalPointer == i)) { + if (UNEXPECTED(ht->nInternalPointer > j && ht->nInternalPointer <= i)) { ht->nInternalPointer = j; } q++; @@ -1398,7 +1398,7 @@ ZEND_API void ZEND_FASTCALL zend_hash_rehash(HashTable *ht) q->key = p->key; Z_NEXT(q->val) = HT_HASH(ht, nIndex); HT_HASH(ht, nIndex) = HT_IDX_TO_HASH(j); - if (UNEXPECTED(ht->nInternalPointer == i)) { + if (UNEXPECTED(ht->nInternalPointer > j && ht->nInternalPointer <= i)) { ht->nInternalPointer = j; } if (UNEXPECTED(i >= iter_pos)) { From d0fad34230eaeeac1ade4a9311aaf23ae2969fd6 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Fri, 18 Jul 2025 15:28:29 +0200 Subject: [PATCH 43/66] Fix circumvented type check with return by ref + finally Fixes GH-18736 Closes GH-19172 --- NEWS | 2 ++ Zend/tests/gh18736.phpt | 24 ++++++++++++++++++++++++ Zend/zend_compile.c | 12 ++++++++++++ 3 files changed, 38 insertions(+) create mode 100644 Zend/tests/gh18736.phpt diff --git a/NEWS b/NEWS index e89bc61148d1f..727b25f0521f9 100644 --- a/NEWS +++ b/NEWS @@ -17,6 +17,8 @@ PHP NEWS delegated Generator). (Arnaud) . Fixed bug GH-19326 (Calling Generator::throw() on a running generator with a non-Generator delegate crashes). (Arnaud) + . Fixed bug GH-18736 (Circumvented type check with return by ref + finally). + (ilutov) - FTP: . Fix theoretical issues with hrtime() not being available. (nielsdos) diff --git a/Zend/tests/gh18736.phpt b/Zend/tests/gh18736.phpt new file mode 100644 index 0000000000000..f397ee39a310b --- /dev/null +++ b/Zend/tests/gh18736.phpt @@ -0,0 +1,24 @@ +--TEST-- +GH-18736: Circumvented type check with return by ref + finally +--FILE-- +getMessage(), "\n"; +} + +?> +--EXPECT-- +test(): Return value must be of type int, string returned diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 41113e2f0055b..6c8577cef5881 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -5201,8 +5201,20 @@ static void zend_compile_return(zend_ast *ast) /* {{{ */ expr_ast ? &expr_node : NULL, CG(active_op_array)->arg_info - 1, 0); } + uint32_t opnum_before_finally = get_next_op_number(); + zend_handle_loops_and_finally((expr_node.op_type & (IS_TMP_VAR | IS_VAR)) ? &expr_node : NULL); + /* Content of reference might have changed in finally, repeat type check. */ + if (by_ref + /* Check if any opcodes were emitted since the last return type check. */ + && opnum_before_finally != get_next_op_number() + && !is_generator + && (CG(active_op_array)->fn_flags & ZEND_ACC_HAS_RETURN_TYPE)) { + zend_emit_return_type_check( + expr_ast ? &expr_node : NULL, CG(active_op_array)->arg_info - 1, 0); + } + opline = zend_emit_op(NULL, by_ref ? ZEND_RETURN_BY_REF : ZEND_RETURN, &expr_node, NULL); From ac1cd9c26ef90fd2512587c185db2964bc060f77 Mon Sep 17 00:00:00 2001 From: Dmitry Stogov Date: Mon, 4 Aug 2025 17:26:24 +0300 Subject: [PATCH 44/66] Update IR IR commit: 6e2aea0ebfef2c741ebec30c57aa492df0d4e319 --- ext/opcache/jit/ir/ir.c | 15 +- ext/opcache/jit/ir/ir.h | 17 +- ext/opcache/jit/ir/ir_aarch64.dasc | 94 +++++++++-- ext/opcache/jit/ir/ir_builder.h | 2 + ext/opcache/jit/ir/ir_cfg.c | 247 +++++++++++++++++++++-------- ext/opcache/jit/ir/ir_check.c | 9 +- ext/opcache/jit/ir/ir_emit.c | 4 +- ext/opcache/jit/ir/ir_fold.h | 4 +- ext/opcache/jit/ir/ir_gcm.c | 199 +++++++++++++++++++++-- ext/opcache/jit/ir/ir_private.h | 5 +- ext/opcache/jit/ir/ir_save.c | 11 +- ext/opcache/jit/ir/ir_sccp.c | 38 ++++- ext/opcache/jit/ir/ir_x86.dasc | 167 +++++++++++++++++-- 13 files changed, 685 insertions(+), 127 deletions(-) diff --git a/ext/opcache/jit/ir/ir.c b/ext/opcache/jit/ir/ir.c index a9f55cc0e466c..97d32680e4fdd 100644 --- a/ext/opcache/jit/ir/ir.c +++ b/ext/opcache/jit/ir/ir.c @@ -172,7 +172,7 @@ void ir_print_const(const ir_ctx *ctx, const ir_insn *insn, FILE *f, bool quoted } else if (insn->val.c == '\0') { fprintf(f, "'\\0'"); } else { - fprintf(f, "%u", insn->val.c); + fprintf(f, "%u", (unsigned char)insn->val.c); } break; case IR_I8: @@ -247,6 +247,7 @@ void ir_print_const(const ir_ctx *ctx, const ir_insn *insn, FILE *f, bool quoted #define ir_op_flag_S1X1 (ir_op_flag_S | 1 | (2 << IR_OP_FLAG_OPERANDS_SHIFT)) #define ir_op_flag_S2 (ir_op_flag_S | 2 | (2 << IR_OP_FLAG_OPERANDS_SHIFT)) #define ir_op_flag_S2X1 (ir_op_flag_S | 2 | (3 << IR_OP_FLAG_OPERANDS_SHIFT)) +#define ir_op_flag_S3 (ir_op_flag_S | 3 | (3 << IR_OP_FLAG_OPERANDS_SHIFT)) #define ir_op_flag_SN (ir_op_flag_S | IR_OP_FLAG_VAR_INPUTS) #define ir_op_flag_E (IR_OP_FLAG_CONTROL|IR_OP_FLAG_BB_END) #define ir_op_flag_E1 (ir_op_flag_E | 1 | (1 << IR_OP_FLAG_OPERANDS_SHIFT)) @@ -803,7 +804,9 @@ ir_ref ir_proto(ir_ctx *ctx, uint8_t flags, ir_type ret_type, uint32_t params_co proto->flags = flags; proto->ret_type = ret_type; proto->params_count = params_count; - memcpy(proto->param_types, param_types, params_count); + if (params_count) { + memcpy(proto->param_types, param_types, params_count); + } return ir_strl(ctx, (const char *)proto, offsetof(ir_proto_t, param_types) + params_count); } @@ -1080,7 +1083,7 @@ ir_ref ir_emit_N(ir_ctx *ctx, uint32_t opt, int32_t count) ir_ref *p, ref = ctx->insns_count; ir_insn *insn; - IR_ASSERT(count >= 0); + IR_ASSERT(count >= 0 && count < 65536); while (UNEXPECTED(ref + count/4 >= ctx->insns_limit)) { ir_grow_top(ctx); } @@ -2973,6 +2976,12 @@ void _ir_CASE_VAL(ir_ctx *ctx, ir_ref switch_ref, ir_ref val) ctx->control = ir_emit2(ctx, IR_CASE_VAL, switch_ref, val); } +void _ir_CASE_RANGE(ir_ctx *ctx, ir_ref switch_ref, ir_ref v1, ir_ref v2) +{ + IR_ASSERT(!ctx->control); + ctx->control = ir_emit3(ctx, IR_CASE_RANGE, switch_ref, v1, v2); +} + void _ir_CASE_DEFAULT(ir_ctx *ctx, ir_ref switch_ref) { IR_ASSERT(!ctx->control); diff --git a/ext/opcache/jit/ir/ir.h b/ext/opcache/jit/ir/ir.h index ec5e57129c9ab..9575348ff5450 100644 --- a/ext/opcache/jit/ir/ir.h +++ b/ext/opcache/jit/ir/ir.h @@ -359,6 +359,7 @@ typedef enum _ir_type { _(IF_TRUE, S1X1, src, prb, ___) /* IF TRUE proj. */ \ _(IF_FALSE, S1X1, src, prb, ___) /* IF FALSE proj. */ \ _(CASE_VAL, S2X1, src, def, prb) /* switch proj. */ \ + _(CASE_RANGE, S3, src, def, def) /* switch proj. */ \ _(CASE_DEFAULT, S1X1, src, prb, ___) /* switch proj. */ \ _(MERGE, SN, src, src, src) /* control merge */ \ _(LOOP_BEGIN, SN, src, src, src) /* loop start */ \ @@ -854,6 +855,9 @@ void ir_gdb_unregister_all(void); bool ir_gdb_present(void); /* IR load API (implementation in ir_load.c) */ +#define IR_RESOLVE_SYM_ADD_THUNK (1<<0) +#define IR_RESOLVE_SYM_SILENT (1<<1) + struct _ir_loader { uint32_t default_func_flags; bool (*init_module) (ir_loader *loader, const char *name, const char *filename, const char *target); @@ -870,7 +874,7 @@ struct _ir_loader { bool (*sym_data_end) (ir_loader *loader, uint32_t flags); bool (*func_init) (ir_loader *loader, ir_ctx *ctx, const char *name); bool (*func_process) (ir_loader *loader, ir_ctx *ctx, const char *name); - void*(*resolve_sym_name) (ir_loader *loader, const char *name, bool add_thunk); + void*(*resolve_sym_name) (ir_loader *loader, const char *name, uint32_t flags); bool (*has_sym) (ir_loader *loader, const char *name); bool (*add_sym) (ir_loader *loader, const char *name, void *addr); }; @@ -884,11 +888,12 @@ int ir_load_llvm_bitcode(ir_loader *loader, const char *filename); int ir_load_llvm_asm(ir_loader *loader, const char *filename); /* IR save API (implementation in ir_save.c) */ -#define IR_SAVE_CFG (1<<0) /* add info about CFG */ -#define IR_SAVE_CFG_MAP (1<<1) /* add info about CFG block assignment */ -#define IR_SAVE_USE_LISTS (1<<2) /* add info about def->use lists */ -#define IR_SAVE_RULES (1<<3) /* add info about selected code-generation rules */ -#define IR_SAVE_REGS (1<<4) /* add info about selected registers */ +#define IR_SAVE_CFG (1<<0) /* add info about CFG */ +#define IR_SAVE_CFG_MAP (1<<1) /* add info about CFG block assignment */ +#define IR_SAVE_USE_LISTS (1<<2) /* add info about def->use lists */ +#define IR_SAVE_RULES (1<<3) /* add info about selected code-generation rules */ +#define IR_SAVE_REGS (1<<4) /* add info about selected registers */ +#define IR_SAVE_SAFE_NAMES (1<<5) /* add '@' prefix to symbol names */ void ir_print_proto(const ir_ctx *ctx, ir_ref proto, FILE *f); void ir_save(const ir_ctx *ctx, uint32_t save_flags, FILE *f); diff --git a/ext/opcache/jit/ir/ir_aarch64.dasc b/ext/opcache/jit/ir/ir_aarch64.dasc index 772eea7a5d78a..476529555e93e 100644 --- a/ext/opcache/jit/ir/ir_aarch64.dasc +++ b/ext/opcache/jit/ir/ir_aarch64.dasc @@ -996,6 +996,7 @@ binop_fp: case IR_IF_TRUE: case IR_IF_FALSE: case IR_CASE_VAL: + case IR_CASE_RANGE: case IR_CASE_DEFAULT: case IR_MERGE: case IR_LOOP_BEGIN: @@ -4366,11 +4367,15 @@ static void ir_emit_va_arg(ir_ctx *ctx, ir_ref def, ir_insn *insn) ir_backend_data *data = ctx->data; dasm_State **Dst = &data->dasm_state; ir_type type = insn->type; - ir_reg def_reg = ctx->regs[def][0]; + ir_reg def_reg = IR_REG_NUM(ctx->regs[def][0]); ir_reg op2_reg = ctx->regs[def][2]; ir_reg tmp_reg = ctx->regs[def][3]; int32_t offset; + if (ctx->use_lists[def].count == 1) { + /* dead load */ + return; + } IR_ASSERT(def_reg != IR_REG_NONE && tmp_reg != IR_REG_NONE); if (op2_reg != IR_REG_NONE) { if (IR_REG_SPILLED(op2_reg)) { @@ -4394,11 +4399,15 @@ static void ir_emit_va_arg(ir_ctx *ctx, ir_ref def, ir_insn *insn) ir_backend_data *data = ctx->data; dasm_State **Dst = &data->dasm_state; ir_type type = insn->type; - ir_reg def_reg = ctx->regs[def][0]; + ir_reg def_reg = IR_REG_NUM(ctx->regs[def][0]); ir_reg op2_reg = ctx->regs[def][2]; ir_reg tmp_reg = ctx->regs[def][3]; int32_t offset; + if (ctx->use_lists[def].count == 1) { + /* dead load */ + return; + } IR_ASSERT(def_reg != IR_REG_NONE && tmp_reg != IR_REG_NONE); if (op2_reg != IR_REG_NONE) { if (IR_REG_SPILLED(op2_reg)) { @@ -4465,6 +4474,7 @@ static void ir_emit_switch(ir_ctx *ctx, uint32_t b, ir_ref def, ir_insn *insn) int count = 0; ir_val min, max; ir_reg op1_reg, op2_reg, tmp_reg; + bool has_case_range = 0; type = ctx->ir_base[insn->op2].type; if (IR_IS_TYPE_SIGNED(type)) { @@ -4493,6 +4503,22 @@ static void ir_emit_switch(ir_ctx *ctx, uint32_t b, ir_ref def, ir_insn *insn) max.u64 = (int64_t)IR_MAX(max.u64, val->val.u64); } count++; + } else if (use_insn->op == IR_CASE_RANGE) { + has_case_range = 1; + val = &ctx->ir_base[use_insn->op2]; + IR_ASSERT(!IR_IS_SYM_CONST(val->op)); + ir_insn *val2 = &ctx->ir_base[use_insn->op3]; + IR_ASSERT(!IR_IS_SYM_CONST(val2->op)); + if (IR_IS_TYPE_SIGNED(type)) { + IR_ASSERT(IR_IS_TYPE_SIGNED(val->type)); + min.i64 = IR_MIN(min.i64, val->val.i64); + max.i64 = IR_MAX(max.i64, val2->val.i64); + } else { + IR_ASSERT(!IR_IS_TYPE_SIGNED(val->type)); + min.u64 = (int64_t)IR_MIN(min.u64, val->val.u64); + max.u64 = (int64_t)IR_MAX(max.u64, val2->val.u64); + } + count++; } else { IR_ASSERT(use_insn->op == IR_CASE_DEFAULT); default_label = ir_skip_empty_target_blocks(ctx, use_block); @@ -4510,7 +4536,7 @@ static void ir_emit_switch(ir_ctx *ctx, uint32_t b, ir_ref def, ir_insn *insn) } /* Generate a table jmp or a sequence of calls */ - if (count > 2 && (max.i64-min.i64) < count * 8) { + if (!has_case_range && count > 2 && (max.i64-min.i64) < count * 8) { int *labels = ir_mem_malloc(sizeof(int) * (max.i64 - min.i64 + 1)); for (i = 0; i <= (max.i64 - min.i64); i++) { @@ -4615,6 +4641,38 @@ static void ir_emit_switch(ir_ctx *ctx, uint32_t b, ir_ref def, ir_insn *insn) } | beq =>label + } else if (use_insn->op == IR_CASE_RANGE) { + val = &ctx->ir_base[use_insn->op2]; + IR_ASSERT(!IR_IS_SYM_CONST(val->op)); + label = ir_skip_empty_target_blocks(ctx, use_block); + if (aarch64_may_encode_imm12(val->val.i64)) { + | ASM_REG_IMM_OP cmp, type, op2_reg, val->val.i64 + } else { + ir_emit_load_imm_int(ctx, type, tmp_reg, val->val.i64); + | ASM_REG_REG_OP cmp, type, op2_reg, tmp_reg + + } + if (IR_IS_TYPE_SIGNED(type)) { + | blt >1 + } else { + | blo >1 + } + val = &ctx->ir_base[use_insn->op3]; + IR_ASSERT(!IR_IS_SYM_CONST(val->op)); + label = ir_skip_empty_target_blocks(ctx, use_block); + if (aarch64_may_encode_imm12(val->val.i64)) { + | ASM_REG_IMM_OP cmp, type, op2_reg, val->val.i64 + } else { + ir_emit_load_imm_int(ctx, type, tmp_reg, val->val.i64); + | ASM_REG_REG_OP cmp, type, op2_reg, tmp_reg + + } + if (IR_IS_TYPE_SIGNED(type)) { + | ble =>label + } else { + | bls =>label + } + |1: } } if (default_label) { @@ -4935,6 +4993,28 @@ static void ir_emit_tailcall(ir_ctx *ctx, ir_ref def, ir_insn *insn) return; } + /* Move op2 to a tmp register before epilogue if it's in + * used_preserved_regs, because it will be overridden. */ + + ir_reg op2_reg = IR_REG_NONE; + if (!IR_IS_CONST_REF(insn->op2)) { + op2_reg = ctx->regs[def][2]; + IR_ASSERT(op2_reg != IR_REG_NONE); + + if (IR_REG_SPILLED(op2_reg)) { + op2_reg = IR_REG_INT_TMP; + ir_emit_load(ctx, IR_ADDR, op2_reg, insn->op2); + } else if (IR_REGSET_IN((ir_regset)ctx->used_preserved_regs, IR_REG_NUM(op2_reg))) { + ir_reg orig_op2_reg = op2_reg; + op2_reg = IR_REG_INT_TMP; + + ir_type type = ctx->ir_base[insn->op2].type; + | ASM_REG_REG_OP mov, type, op2_reg, IR_REG_NUM(orig_op2_reg) + } else { + op2_reg = IR_REG_NUM(op2_reg); + } + } + ir_emit_epilogue(ctx); if (IR_IS_CONST_REF(insn->op2)) { @@ -4947,13 +5027,8 @@ static void ir_emit_tailcall(ir_ctx *ctx, ir_ref def, ir_insn *insn) | br Rx(IR_REG_INT_TMP) } } else { - ir_reg op2_reg = ctx->regs[def][2]; - IR_ASSERT(op2_reg != IR_REG_NONE); - if (IR_REG_SPILLED(op2_reg)) { - op2_reg = IR_REG_NUM(op2_reg); - ir_emit_load(ctx, IR_ADDR, op2_reg, insn->op2); - } + IR_ASSERT(!IR_REGSET_IN((ir_regset)ctx->used_preserved_regs, op2_reg)); | br Rx(op2_reg) } } @@ -5590,6 +5665,7 @@ static void ir_allocate_unique_spill_slots(ir_ctx *ctx) case IR_IF_TRUE: case IR_IF_FALSE: case IR_CASE_VAL: + case IR_CASE_RANGE: case IR_CASE_DEFAULT: case IR_MERGE: case IR_LOOP_BEGIN: diff --git a/ext/opcache/jit/ir/ir_builder.h b/ext/opcache/jit/ir/ir_builder.h index 4e4ea53683afb..ba1924f8d65e5 100644 --- a/ext/opcache/jit/ir/ir_builder.h +++ b/ext/opcache/jit/ir/ir_builder.h @@ -603,6 +603,7 @@ extern "C" { #define ir_LOOP_END() _ir_LOOP_END(_ir_CTX) #define ir_SWITCH(_val) _ir_SWITCH(_ir_CTX, (_val)) #define ir_CASE_VAL(_switch, _val) _ir_CASE_VAL(_ir_CTX, (_switch), (_val)) +#define ir_CASE_RANGE(_switch, _v1, _v2) _ir_CASE_RANGE(_ir_CTX, (_switch), (_v1), (_v2)) #define ir_CASE_DEFAULT(_switch) _ir_CASE_DEFAULT(_ir_CTX, (_switch)) #define ir_RETURN(_val) _ir_RETURN(_ir_CTX, (_val)) #define ir_IJMP(_addr) _ir_IJMP(_ir_CTX, (_addr)) @@ -682,6 +683,7 @@ ir_ref _ir_TLS(ir_ctx *ctx, ir_ref index, ir_ref offset); void _ir_UNREACHABLE(ir_ctx *ctx); ir_ref _ir_SWITCH(ir_ctx *ctx, ir_ref val); void _ir_CASE_VAL(ir_ctx *ctx, ir_ref switch_ref, ir_ref val); +void _ir_CASE_RANGE(ir_ctx *ctx, ir_ref switch_ref, ir_ref v1, ir_ref v2); void _ir_CASE_DEFAULT(ir_ctx *ctx, ir_ref switch_ref); void _ir_RETURN(ir_ctx *ctx, ir_ref val); void _ir_IJMP(ir_ctx *ctx, ir_ref addr); diff --git a/ext/opcache/jit/ir/ir_cfg.c b/ext/opcache/jit/ir/ir_cfg.c index 01532c8ea3e30..13d66a7130283 100644 --- a/ext/opcache/jit/ir/ir_cfg.c +++ b/ext/opcache/jit/ir/ir_cfg.c @@ -244,7 +244,6 @@ int ir_build_cfg(ir_ctx *ctx) _blocks[start] = b; _blocks[end] = b; IR_ASSERT(IR_IS_BB_START(insn->op)); - IR_ASSERT(end > start); bb->start = start; bb->end = end; bb->successors = count; @@ -583,7 +582,6 @@ static int ir_remove_unreachable_blocks(ir_ctx *ctx) return 1; } -#if 0 static void compute_postnum(const ir_ctx *ctx, uint32_t *cur, uint32_t b) { uint32_t i, *p; @@ -607,34 +605,42 @@ static void compute_postnum(const ir_ctx *ctx, uint32_t *cur, uint32_t b) /* Computes dominator tree using algorithm from "A Simple, Fast Dominance Algorithm" by * Cooper, Harvey and Kennedy. */ -int ir_build_dominators_tree(ir_ctx *ctx) +static int ir_build_dominators_tree_slow(ir_ctx *ctx) { uint32_t blocks_count, b, postnum; ir_block *blocks, *bb; uint32_t *edges; bool changed; + blocks = ctx->cfg_blocks; + edges = ctx->cfg_edges; + blocks_count = ctx->cfg_blocks_count; + + /* Clear the dominators tree */ + for (b = 0, bb = &blocks[0]; b <= blocks_count; b++, bb++) { + bb->idom = 0; + bb->dom_depth = 0; + bb->dom_child = 0; + bb->dom_next_child = 0; + } + ctx->flags2 &= ~IR_NO_LOOPS; postnum = 1; compute_postnum(ctx, &postnum, 1); - /* Find immediate dominators */ - blocks = ctx->cfg_blocks; - edges = ctx->cfg_edges; - blocks_count = ctx->cfg_blocks_count; + /* Find immediate dominators by iterative fixed-point algorithm */ blocks[1].idom = 1; do { changed = 0; /* Iterating in Reverse Post Order */ for (b = 2, bb = &blocks[2]; b <= blocks_count; b++, bb++) { IR_ASSERT(!(bb->flags & IR_BB_UNREACHABLE)); + IR_ASSERT(bb->predecessors_count > 0); if (bb->predecessors_count == 1) { uint32_t pred_b = edges[bb->predecessors]; - if (blocks[pred_b].idom <= 0) { - //IR_ASSERT("Wrong blocks order: BB is before its single predecessor"); - } else if (bb->idom != pred_b) { + if (blocks[pred_b].idom > 0 && bb->idom != pred_b) { bb->idom = pred_b; changed = 1; } @@ -680,39 +686,53 @@ int ir_build_dominators_tree(ir_ctx *ctx) } } } while (changed); + + /* Build dominators tree */ blocks[1].idom = 0; blocks[1].dom_depth = 0; + for (b = 2, bb = &blocks[2]; b <= blocks_count; b++, bb++) { + uint32_t idom = bb->idom; + ir_block *idom_bb = &blocks[idom]; + + bb->dom_depth = 0; + /* Sort by block number to traverse children in pre-order */ + if (idom_bb->dom_child == 0) { + idom_bb->dom_child = b; + } else if (b < idom_bb->dom_child) { + bb->dom_next_child = idom_bb->dom_child; + idom_bb->dom_child = b; + } else { + int child = idom_bb->dom_child; + ir_block *child_bb = &blocks[child]; - /* Construct dominators tree */ + while (child_bb->dom_next_child > 0 && b > child_bb->dom_next_child) { + child = child_bb->dom_next_child; + child_bb = &blocks[child]; + } + bb->dom_next_child = child_bb->dom_next_child; + child_bb->dom_next_child = b; + } + } + + /* Recalculate dom_depth for all blocks */ for (b = 2, bb = &blocks[2]; b <= blocks_count; b++, bb++) { - IR_ASSERT(!(bb->flags & IR_BB_UNREACHABLE)); - if (bb->idom > 0) { - ir_block *idom_bb = &blocks[bb->idom]; - - bb->dom_depth = idom_bb->dom_depth + 1; - /* Sort by block number to traverse children in pre-order */ - if (idom_bb->dom_child == 0) { - idom_bb->dom_child = b; - } else if (b < idom_bb->dom_child) { - bb->dom_next_child = idom_bb->dom_child; - idom_bb->dom_child = b; + uint32_t idom = bb->idom; + uint32_t dom_depth = 0; + while (idom) { + dom_depth++; + if (blocks[idom].dom_depth > 0) { + dom_depth += blocks[idom].dom_depth; + break; } else { - int child = idom_bb->dom_child; - ir_block *child_bb = &blocks[child]; - - while (child_bb->dom_next_child > 0 && b > child_bb->dom_next_child) { - child = child_bb->dom_next_child; - child_bb = &blocks[child]; - } - bb->dom_next_child = child_bb->dom_next_child; - child_bb->dom_next_child = b; + idom = blocks[idom].idom; } } + bb->dom_depth = dom_depth; } return 1; } -#else + /* A single pass modification of "A Simple, Fast Dominance Algorithm" by * Cooper, Harvey and Kennedy, that relays on IR block ordering. * It may fallback to the general slow fixed-point algorithm. */ @@ -747,7 +767,11 @@ int ir_build_dominators_tree(ir_ctx *ctx) if (UNEXPECTED(idom >= b)) { /* In rare cases, LOOP_BEGIN.op1 may be a back-edge. Skip back-edges. */ ctx->flags2 &= ~IR_NO_LOOPS; - IR_ASSERT(k > 1 && "Wrong blocks order: BB is before its single predecessor"); +// IR_ASSERT(k > 1 && "Wrong blocks order: BB is before its single predecessor"); + if (UNEXPECTED(k <= 1)) { + ir_list_free(&worklist); + return ir_build_dominators_tree_slow(ctx); + } ir_list_push(&worklist, idom); while (1) { k--; @@ -942,7 +966,6 @@ static int ir_build_dominators_tree_iterative(ir_ctx *ctx) return 1; } -#endif static bool ir_dominates(const ir_block *blocks, uint32_t b1, uint32_t b2) { @@ -958,7 +981,7 @@ static bool ir_dominates(const ir_block *blocks, uint32_t b1, uint32_t b2) int ir_find_loops(ir_ctx *ctx) { - uint32_t i, j, n, count; + uint32_t b, j, n, count; uint32_t *entry_times, *exit_times, *sorted_blocks, time = 1; ir_block *blocks = ctx->cfg_blocks; uint32_t *edges = ctx->cfg_edges; @@ -983,13 +1006,13 @@ int ir_find_loops(ir_ctx *ctx) int child; next: - i = ir_worklist_peek(&work); - if (!entry_times[i]) { - entry_times[i] = time++; + b = ir_worklist_peek(&work); + if (!entry_times[b]) { + entry_times[b] = time++; } - /* Visit blocks immediately dominated by i. */ - bb = &blocks[i]; + /* Visit blocks immediately dominated by "b". */ + bb = &blocks[b]; for (child = bb->dom_child; child > 0; child = blocks[child].dom_next_child) { if (ir_worklist_push(&work, child)) { goto next; @@ -999,17 +1022,17 @@ int ir_find_loops(ir_ctx *ctx) /* Visit join edges. */ if (bb->successors_count) { uint32_t *p = edges + bb->successors; - for (j = 0; j < bb->successors_count; j++,p++) { + for (j = 0; j < bb->successors_count; j++, p++) { uint32_t succ = *p; - if (blocks[succ].idom == i) { + if (blocks[succ].idom == b) { continue; } else if (ir_worklist_push(&work, succ)) { goto next; } } } - exit_times[i] = time++; + exit_times[b] = time++; ir_worklist_pop(&work); } @@ -1018,7 +1041,7 @@ int ir_find_loops(ir_ctx *ctx) j = 1; n = 2; while (j != n) { - i = j; + uint32_t i = j; j = n; for (; i < j; i++) { int child; @@ -1030,9 +1053,82 @@ int ir_find_loops(ir_ctx *ctx) count = n; /* Identify loops. See Sreedhar et al, "Identifying Loops Using DJ Graphs". */ + uint32_t prev_dom_depth = blocks[sorted_blocks[n - 1]].dom_depth; + uint32_t prev_irreducible = 0; while (n > 1) { - i = sorted_blocks[--n]; - ir_block *bb = &blocks[i]; + b = sorted_blocks[--n]; + ir_block *bb = &blocks[b]; + + IR_ASSERT(bb->dom_depth <= prev_dom_depth); + if (UNEXPECTED(prev_irreducible) && bb->dom_depth != prev_dom_depth) { + /* process delyed irreducible loops */ + do { + b = sorted_blocks[prev_irreducible]; + bb = &blocks[b]; + if ((bb->flags & IR_BB_IRREDUCIBLE_LOOP) && !bb->loop_depth) { + /* process irreducible loop */ + uint32_t hdr = b; + + bb->loop_depth = 1; + if (ctx->ir_base[bb->start].op == IR_MERGE) { + ctx->ir_base[bb->start].op = IR_LOOP_BEGIN; + } + + /* find the closing edge(s) of the irreucible loop */ + IR_ASSERT(bb->predecessors_count > 1); + uint32_t *p = &edges[bb->predecessors]; + j = bb->predecessors_count; + do { + uint32_t pred = *p; + + if (entry_times[pred] > entry_times[b] && exit_times[pred] < exit_times[b]) { + if (!ir_worklist_len(&work)) { + ir_bitset_clear(work.visited, ir_bitset_len(ir_worklist_capasity(&work))); + } + blocks[pred].loop_header = 0; /* support for merged loops */ + ir_worklist_push(&work, pred); + } + p++; + } while (--j); + if (ir_worklist_len(&work) == 0) continue; + + /* collect members of the irreducible loop */ + while (ir_worklist_len(&work)) { + b = ir_worklist_pop(&work); + if (b != hdr) { + ir_block *bb = &blocks[b]; + bb->loop_header = hdr; + if (bb->predecessors_count) { + uint32_t *p = &edges[bb->predecessors]; + uint32_t n = bb->predecessors_count; + do { + uint32_t pred = *p; + while (blocks[pred].loop_header > 0) { + pred = blocks[pred].loop_header; + } + if (pred != hdr) { + if (entry_times[pred] > entry_times[hdr] && exit_times[pred] < exit_times[hdr]) { + /* "pred" is a descendant of "hdr" */ + ir_worklist_push(&work, pred); + } else { + /* another entry to the irreducible loop */ + bb->flags |= IR_BB_IRREDUCIBLE_LOOP; + if (ctx->ir_base[bb->start].op == IR_MERGE) { + ctx->ir_base[bb->start].op = IR_LOOP_BEGIN; + } + } + } + p++; + } while (--n); + } + } + } + } + } while (--prev_irreducible != n); + prev_irreducible = 0; + b = sorted_blocks[n]; + bb = &blocks[b]; + } if (bb->predecessors_count > 1) { bool irreducible = 0; @@ -1047,7 +1143,7 @@ int ir_find_loops(ir_ctx *ctx) if (bb->idom != pred) { /* In a loop back-edge (back-join edge), the successor dominates the predecessor. */ - if (ir_dominates(blocks, i, pred)) { + if (ir_dominates(blocks, b, pred)) { if (!ir_worklist_len(&work)) { ir_bitset_clear(work.visited, ir_bitset_len(ir_worklist_capasity(&work))); } @@ -1056,8 +1152,9 @@ int ir_find_loops(ir_ctx *ctx) } else { /* Otherwise it's a cross-join edge. See if it's a branch to an ancestor on the DJ spanning tree. */ - if (entry_times[pred] > entry_times[i] && exit_times[pred] < exit_times[i]) { + if (entry_times[pred] > entry_times[b] && exit_times[pred] < exit_times[b]) { irreducible = 1; + break; } } } @@ -1065,46 +1162,56 @@ int ir_find_loops(ir_ctx *ctx) } while (--j); if (UNEXPECTED(irreducible)) { - // TODO: Support for irreducible loops ??? - bb->flags |= IR_BB_IRREDUCIBLE_LOOP; - ctx->flags2 |= IR_IRREDUCIBLE_CFG; - while (ir_worklist_len(&work)) { - ir_worklist_pop(&work); + bb->flags |= IR_BB_LOOP_HEADER | IR_BB_IRREDUCIBLE_LOOP; + ctx->flags2 |= IR_CFG_HAS_LOOPS | IR_IRREDUCIBLE_CFG; + /* Remember the position of the first irreducible loop to process all the irreducible loops + * after the reducible loops with the same dominator tree depth + */ + if (!prev_irreducible) { + prev_irreducible = n; + prev_dom_depth = bb->dom_depth; } + ir_list_clear(&work.l); } else if (ir_worklist_len(&work)) { + /* collect members of the reducible loop */ + uint32_t hdr = b; + bb->flags |= IR_BB_LOOP_HEADER; ctx->flags2 |= IR_CFG_HAS_LOOPS; bb->loop_depth = 1; + if (ctx->ir_base[bb->start].op == IR_MERGE) { + ctx->ir_base[bb->start].op = IR_LOOP_BEGIN; + } while (ir_worklist_len(&work)) { - j = ir_worklist_pop(&work); - while (blocks[j].loop_header > 0) { - j = blocks[j].loop_header; - } - if (j != i) { - ir_block *bb = &blocks[j]; - if (bb->idom == 0 && j != 1) { - /* Ignore blocks that are unreachable or only abnormally reachable. */ - continue; - } - bb->loop_header = i; + b = ir_worklist_pop(&work); + if (b != hdr) { + ir_block *bb = &blocks[b]; + bb->loop_header = hdr; if (bb->predecessors_count) { uint32_t *p = &edges[bb->predecessors]; - j = bb->predecessors_count; + uint32_t n = bb->predecessors_count; do { - ir_worklist_push(&work, *p); + uint32_t pred = *p; + while (blocks[pred].loop_header > 0) { + pred = blocks[pred].loop_header; + } + if (pred != hdr) { + ir_worklist_push(&work, pred); + } p++; - } while (--j); + } while (--n); } } } } } } + IR_ASSERT(!prev_irreducible); if (ctx->flags2 & IR_CFG_HAS_LOOPS) { for (n = 1; n < count; n++) { - i = sorted_blocks[n]; - ir_block *bb = &blocks[i]; + b = sorted_blocks[n]; + ir_block *bb = &blocks[b]; if (bb->loop_header > 0) { ir_block *loop = &blocks[bb->loop_header]; uint32_t loop_depth = loop->loop_depth; @@ -1389,7 +1496,7 @@ static int ir_schedule_blocks_bottom_up(ir_ctx *ctx) goto restart; } } else if (b != predecessor && ctx->cfg_blocks[predecessor].loop_header != b) { - ir_dump_cfg(ctx, stderr); + /* not a loop back-edge */ IR_ASSERT(b == predecessor || ctx->cfg_blocks[predecessor].loop_header == b); } } diff --git a/ext/opcache/jit/ir/ir_check.c b/ext/opcache/jit/ir/ir_check.c index f12b4776fa1e0..a791baef5db92 100644 --- a/ext/opcache/jit/ir/ir_check.c +++ b/ext/opcache/jit/ir/ir_check.c @@ -213,13 +213,18 @@ bool ir_check(const ir_ctx *ctx) ok = 0; } } - break; - case IR_OPND_CONTROL_DEP: if ((ctx->flags2 & IR_LINEAR) && use >= i && !(insn->op == IR_LOOP_BEGIN)) { fprintf(stderr, "ir_base[%d].ops[%d] invalid forward reference (%d)\n", i, j, use); ok = 0; + } + break; + case IR_OPND_CONTROL_DEP: + if ((ctx->flags2 & IR_LINEAR) + && use >= i) { + fprintf(stderr, "ir_base[%d].ops[%d] invalid forward reference (%d)\n", i, j, use); + ok = 0; } else if (insn->op == IR_PHI) { ir_insn *merge_insn = &ctx->ir_base[insn->op1]; if (merge_insn->op != IR_MERGE && merge_insn->op != IR_LOOP_BEGIN) { diff --git a/ext/opcache/jit/ir/ir_emit.c b/ext/opcache/jit/ir/ir_emit.c index c82655daf48db..fab9f56228d80 100644 --- a/ext/opcache/jit/ir/ir_emit.c +++ b/ext/opcache/jit/ir/ir_emit.c @@ -309,7 +309,7 @@ static void* ir_sym_addr(ir_ctx *ctx, const ir_insn *addr_insn) { const char *name = ir_get_str(ctx, addr_insn->val.name); void *addr = (ctx->loader && ctx->loader->resolve_sym_name) ? - ctx->loader->resolve_sym_name(ctx->loader, name, 0) : + ctx->loader->resolve_sym_name(ctx->loader, name, IR_RESOLVE_SYM_SILENT) : ir_resolve_sym_name(name); return addr; @@ -320,7 +320,7 @@ static void* ir_sym_val(ir_ctx *ctx, const ir_insn *addr_insn) { const char *name = ir_get_str(ctx, addr_insn->val.name); void *addr = (ctx->loader && ctx->loader->resolve_sym_name) ? - ctx->loader->resolve_sym_name(ctx->loader, name, addr_insn->op == IR_FUNC) : + ctx->loader->resolve_sym_name(ctx->loader, name, addr_insn->op == IR_FUNC ? IR_RESOLVE_SYM_ADD_THUNK : 0) : ir_resolve_sym_name(name); IR_ASSERT(addr); diff --git a/ext/opcache/jit/ir/ir_fold.h b/ext/opcache/jit/ir/ir_fold.h index 88539e52ab085..90112214d0c8b 100644 --- a/ext/opcache/jit/ir/ir_fold.h +++ b/ext/opcache/jit/ir/ir_fold.h @@ -1909,7 +1909,9 @@ IR_FOLD(SUB(_, SUB)) IR_FOLD(SUB(ADD, ADD)) { if (IR_IS_TYPE_INT(IR_OPT_TYPE(opt))) { - if (op1_insn->op1 == op2_insn->op1) { + if (op1 == op2) { + IR_FOLD_CONST_U(0); + } else if (op1_insn->op1 == op2_insn->op1) { /* (a + b) - (a + c) => b - c */ op1 = op1_insn->op2; op2 = op2_insn->op2; diff --git a/ext/opcache/jit/ir/ir_gcm.c b/ext/opcache/jit/ir/ir_gcm.c index 8bd6be5d10aa0..4d518d20079fb 100644 --- a/ext/opcache/jit/ir/ir_gcm.c +++ b/ext/opcache/jit/ir/ir_gcm.c @@ -785,6 +785,139 @@ IR_ALWAYS_INLINE ir_ref ir_count_constant(ir_ref *_xlat, ir_ref ref) return 0; } +IR_ALWAYS_INLINE bool ir_is_good_bb_order(ir_ctx *ctx, uint32_t b, ir_block *bb, ir_ref start) +{ + ir_insn *insn = &ctx->ir_base[start]; + uint32_t n = insn->inputs_count; + ir_ref *p = insn->ops + 1; + + if (n == 1) { + return *p < start; + } else { + IR_ASSERT(n > 1); + for (; n > 0; p++, n--) { + ir_ref input = *p; + if (input < start) { + /* ordered */ + } else if ((bb->flags & IR_BB_LOOP_HEADER) + && (ctx->cfg_map[input] == b || ctx->cfg_blocks[ctx->cfg_map[input]].loop_header == b)) { + /* back-edge of reducible loop */ + } else if ((bb->flags & IR_BB_IRREDUCIBLE_LOOP) + && (ctx->cfg_blocks[ctx->cfg_map[input]].loop_header == ctx->cfg_blocks[b].loop_header)) { + /* closing edge of irreducible loop */ + } else { + return 0; + } + } + return 1; + } +} + +static IR_NEVER_INLINE void ir_fix_bb_order(ir_ctx *ctx, ir_ref *_prev, ir_ref *_next) +{ + uint32_t b, succ, count, *q, *xlat; + ir_block *bb; + ir_ref ref, n, prev; + ir_worklist worklist; + ir_block *new_blocks; + +#if 0 + for (b = 1, bb = ctx->cfg_blocks + 1; b <= ctx->cfg_blocks_count; b++, bb++) { + if (!ir_is_good_bb_order(ctx, b, bb, bb->start)) { + goto fix; + } + } + return; + +fix: +#endif + count = ctx->cfg_blocks_count + 1; + new_blocks = ir_mem_malloc(count * sizeof(ir_block)); + xlat = ir_mem_malloc(count * sizeof(uint32_t)); + ir_worklist_init(&worklist, count); + ir_worklist_push(&worklist, 1); + while (ir_worklist_len(&worklist) != 0) { +next: + b = ir_worklist_peek(&worklist); + bb = &ctx->cfg_blocks[b]; + n = bb->successors_count; + if (n == 1) { + succ = ctx->cfg_edges[bb->successors]; + if (ir_worklist_push(&worklist, succ)) { + goto next; + } + } else if (n > 1) { + uint32_t best = 0; + uint32_t best_loop_depth = 0; + + q = ctx->cfg_edges + bb->successors + n; + do { + q--; + succ = *q; + if (ir_bitset_in(worklist.visited, succ)) { + /* already processed */ + } else if ((ctx->cfg_blocks[succ].flags & IR_BB_LOOP_HEADER) + && (succ == b || ctx->cfg_blocks[b].loop_header == succ)) { + /* back-edge of reducible loop */ + } else if ((ctx->cfg_blocks[succ].flags & IR_BB_IRREDUCIBLE_LOOP) + && (ctx->cfg_blocks[succ].loop_header == ctx->cfg_blocks[b].loop_header)) { + /* closing edge of irreducible loop */ + } else if (!best) { + best = succ; + best_loop_depth = ctx->cfg_blocks[best].loop_depth; + } else if (ctx->cfg_blocks[succ].loop_depth < best_loop_depth) { + /* prefer deeper loop */ + best = succ; + best_loop_depth = ctx->cfg_blocks[best].loop_depth; + } + n--; + } while (n > 0); + if (best) { + ir_worklist_push(&worklist, best); + goto next; + } + } + ir_worklist_pop(&worklist); + count--; + new_blocks[count] = *bb; + xlat[b] = count; + } + IR_ASSERT(count == 1); + xlat[0] = 0; + ir_worklist_free(&worklist); + + prev = 0; + for (b = 1, bb = new_blocks + 1; b <= ctx->cfg_blocks_count; b++, bb++) { + bb->idom = xlat[bb->idom]; + bb->loop_header = xlat[bb->loop_header]; + n = bb->successors_count; + if (n > 0) { + for (q = ctx->cfg_edges + bb->successors; n > 0; q++, n--) { + *q = xlat[*q]; + } + } + n = bb->predecessors_count; + if (n > 0) { + for (q = ctx->cfg_edges + bb->predecessors; n > 0; q++, n--) { + *q = xlat[*q]; + } + } + _next[prev] = bb->start; + _prev[bb->start] = prev; + prev = bb->end; + } + _next[0] = 0; + _next[prev] = 0; + + for (ref = 2; ref < ctx->insns_count; ref++) { + ctx->cfg_map[ref] = xlat[ctx->cfg_map[ref]]; + } + ir_mem_free(xlat); + + ir_mem_free(ctx->cfg_blocks); + ctx->cfg_blocks = new_blocks; +} + int ir_schedule(ir_ctx *ctx) { ir_ctx new_ctx; @@ -800,6 +933,7 @@ int ir_schedule(ir_ctx *ctx) ir_block *bb; ir_insn *insn, *new_insn; ir_use_list *lists, *use_list, *new_list; + bool bad_bb_order = 0; /* Create a double-linked list of nodes ordered by BB, respecting BB->start and BB->end */ IR_ASSERT(_blocks[1] == 1); @@ -818,27 +952,61 @@ int ir_schedule(ir_ctx *ctx) } else if (b > prev_b) { bb = &ctx->cfg_blocks[b]; if (i == bb->start) { - IR_ASSERT(bb->end > bb->start); - prev_b = b; - prev_b_end = bb->end; - _prev[bb->end] = 0; - /* add to the end of the list */ - _next[j] = i; - _prev[i] = j; - j = i; - } else { - IR_ASSERT(i != bb->end); + if (bb->end > bb->start) { + prev_b = b; + prev_b_end = bb->end; + /* add to the end of the list */ + _next[j] = i; + _prev[i] = j; + j = i; + } else { + prev_b = 0; + prev_b_end = 0; + k = bb->end; + while (_blocks[_prev[k]] == b) { + k = _prev[k]; + } + /* insert before "k" */ + _prev[i] = _prev[k]; + _next[i] = k; + _next[_prev[k]] = i; + _prev[k] = i; + } + if (!ir_is_good_bb_order(ctx, b, bb, i)) { + bad_bb_order = 1; + } + } else if (i != bb->end) { /* move down late (see the following loop) */ _next[i] = _move_down; _move_down = i; + } else { + prev_b = 0; + prev_b_end = 0; + if (bb->start > bb->end) { + /* add to the end of the list */ + _next[j] = i; + _prev[i] = j; + j = i; + } else { + k = bb->start; + while (_blocks[_next[k]] == b) { + k = _next[k]; + } + /* insert after "k" */ + _next[i] = _next[k]; + _prev[i] = k; + _prev[_next[k]] = i; + _next[k] = i; + } } } else if (b) { bb = &ctx->cfg_blocks[b]; IR_ASSERT(i != bb->start); - if (_prev[bb->end]) { + if (i > bb->end) { /* move up, insert before the end of the already scheduled BB */ k = bb->end; } else { + IR_ASSERT(i > bb->start); /* move up, insert at the end of the block */ k = ctx->cfg_blocks[b + 1].start; } @@ -883,6 +1051,10 @@ int ir_schedule(ir_ctx *ctx) } #endif + if (bad_bb_order) { + ir_fix_bb_order(ctx, _prev, _next); + } + _xlat = ir_mem_calloc((ctx->consts_count + ctx->insns_count), sizeof(ir_ref)); _xlat += ctx->consts_count; _xlat[IR_TRUE] = IR_TRUE; @@ -904,6 +1076,11 @@ int ir_schedule(ir_ctx *ctx) if (insn->op == IR_CASE_VAL) { IR_ASSERT(insn->op2 < IR_TRUE); consts_count += ir_count_constant(_xlat, insn->op2); + } else if (insn->op == IR_CASE_RANGE) { + IR_ASSERT(insn->op2 < IR_TRUE); + consts_count += ir_count_constant(_xlat, insn->op2); + IR_ASSERT(insn->op3 < IR_TRUE); + consts_count += ir_count_constant(_xlat, insn->op3); } n = insn->inputs_count; insns_count += ir_insn_inputs_to_len(n); diff --git a/ext/opcache/jit/ir/ir_private.h b/ext/opcache/jit/ir/ir_private.h index 69a0101d24ee2..369b4c34e3793 100644 --- a/ext/opcache/jit/ir/ir_private.h +++ b/ext/opcache/jit/ir/ir_private.h @@ -9,6 +9,7 @@ #define IR_PRIVATE_H #include #include +#include #ifdef IR_DEBUG # include @@ -62,7 +63,7 @@ #define IR_MAX(a, b) (((a) > (b)) ? (a) : (b)) #define IR_MIN(a, b) (((a) < (b)) ? (a) : (b)) -#define IR_IS_POWER_OF_TWO(x) (!((x) & ((x) - 1))) +#define IR_IS_POWER_OF_TWO(x) ((x) && (!((x) & ((x) - 1)))) #define IR_LOG2(x) ir_ntzl(x) @@ -257,7 +258,7 @@ IR_ALWAYS_INLINE void* ir_arena_alloc(ir_arena **arena_ptr, size_t size) ir_arena *arena = *arena_ptr; char *ptr = (char*)IR_ALIGNED_SIZE((uintptr_t)arena->ptr, 8); - if (EXPECTED(size <= (size_t)(arena->end - ptr))) { + if (EXPECTED((ptrdiff_t)size <= (ptrdiff_t)(arena->end - ptr))) { arena->ptr = ptr + size; } else { size_t arena_size = diff --git a/ext/opcache/jit/ir/ir_save.c b/ext/opcache/jit/ir/ir_save.c index b12cc267af607..ea787f162ec1f 100644 --- a/ext/opcache/jit/ir/ir_save.c +++ b/ext/opcache/jit/ir/ir_save.c @@ -97,10 +97,14 @@ void ir_save(const ir_ctx *ctx, uint32_t save_flags, FILE *f) for (i = IR_UNUSED + 1, insn = ctx->ir_base - i; i < ctx->consts_count; i++, insn--) { fprintf(f, "\t%s c_%d = ", ir_type_cname[insn->type], i); if (insn->op == IR_FUNC) { - fprintf(f, "func %s", ir_get_str(ctx, insn->val.name)); + fprintf(f, "func %s%s", + (save_flags & IR_SAVE_SAFE_NAMES) ? "@" : "", + ir_get_str(ctx, insn->val.name)); ir_print_proto(ctx, insn->proto, f); } else if (insn->op == IR_SYM) { - fprintf(f, "sym(%s)", ir_get_str(ctx, insn->val.name)); + fprintf(f, "sym(%s%s)", + (save_flags & IR_SAVE_SAFE_NAMES) ? "@" : "", + ir_get_str(ctx, insn->val.name)); } else if (insn->op == IR_FUNC_ADDR) { fprintf(f, "func *"); ir_print_const(ctx, insn, f, true); @@ -140,6 +144,9 @@ void ir_save(const ir_ctx *ctx, uint32_t save_flags, FILE *f) fprintf(f, ", loop=BB%d(%d)", bb->loop_header, bb->loop_depth); } } + if (bb->flags & IR_BB_IRREDUCIBLE_LOOP) { + fprintf(f, ", IRREDUCIBLE"); + } if (bb->predecessors_count) { uint32_t i; diff --git a/ext/opcache/jit/ir/ir_sccp.c b/ext/opcache/jit/ir/ir_sccp.c index 2e006516df818..ae0eebadee54a 100644 --- a/ext/opcache/jit/ir/ir_sccp.c +++ b/ext/opcache/jit/ir/ir_sccp.c @@ -458,6 +458,22 @@ static bool ir_sccp_is_equal(ir_ctx *ctx, ir_insn *_values, ir_ref a, ir_ref b) return v1->val.u64 == v2->val.u64; } +static bool ir_sccp_in_range(ir_ctx *ctx, ir_insn *_values, ir_ref a, ir_ref b, ir_ref c) +{ + ir_insn *v1 = IR_IS_CONST_REF(a) ? &ctx->ir_base[a] : &_values[a]; + ir_insn *v2 = IR_IS_CONST_REF(b) ? &ctx->ir_base[b] : &_values[b]; + ir_insn *v3 = IR_IS_CONST_REF(c) ? &ctx->ir_base[c] : &_values[c]; + + IR_ASSERT(!IR_IS_SYM_CONST(v1->op)); + IR_ASSERT(!IR_IS_SYM_CONST(v2->op)); + IR_ASSERT(!IR_IS_SYM_CONST(v3->op)); + if (IR_IS_TYPE_SIGNED(v1->type)) { + return v1->val.i64 >= v2->val.i64 && v1->val.i64 <= v3->val.i64; + } else { + return v1->val.u64 >= v2->val.u64 && v1->val.u64 <= v3->val.u64; + } +} + #ifdef IR_SCCP_TRACE static void ir_sccp_trace_val(ir_ctx *ctx, ir_insn *_values, ir_ref i) { @@ -676,6 +692,11 @@ static IR_NEVER_INLINE void ir_sccp_analyze(ir_ctx *ctx, ir_insn *_values, ir_bi } } else if (use_insn->op == IR_CASE_DEFAULT) { use_case = use; + } else if (use_insn->op == IR_CASE_RANGE) { + if (ir_sccp_in_range(ctx, _values, insn->op2, use_insn->op2, use_insn->op3)) { + use_case = use; + break; + } } } if (use_case) { @@ -1732,7 +1753,20 @@ static ir_ref ir_promote_i2i(ir_ctx *ctx, ir_type type, ir_ref ref, ir_ref use, ir_ref *p, n, input; if (IR_IS_CONST_REF(ref)) { - return ir_const(ctx, insn->val, type); + ir_val val; + + switch (type) { + case IR_I8: val.i64 = insn->val.i8; break; + case IR_U8: val.u64 = insn->val.u8; break; + case IR_I16: val.i64 = insn->val.i16; break; + case IR_U16: val.u64 = insn->val.u16; break; + case IR_I32: val.i64 = insn->val.i32; break; + case IR_U32: val.u64 = insn->val.u32; break; + case IR_CHAR:val.i64 = insn->val.i8; break; + case IR_BOOL:val.u64 = insn->val.u8 != 0; break; + default: IR_ASSERT(0); val.u64 = 0; + } + return ir_const(ctx, val, type); } else { ir_bitqueue_add(worklist, ref); switch (insn->op) { @@ -2391,7 +2425,7 @@ static bool ir_try_remove_empty_diamond(ir_ctx *ctx, ir_ref ref, ir_insn *insn, } start_ref = end->op1; start = &ctx->ir_base[start_ref]; - if (start->op != IR_CASE_VAL && start->op != IR_CASE_DEFAULT) { + if (start->op != IR_CASE_VAL && start->op != IR_CASE_RANGE && start->op != IR_CASE_DEFAULT) { return 0; } if (ctx->use_lists[start_ref].count != 1) { diff --git a/ext/opcache/jit/ir/ir_x86.dasc b/ext/opcache/jit/ir/ir_x86.dasc index 76602c2b4bcf5..d9967d24dbea2 100644 --- a/ext/opcache/jit/ir/ir_x86.dasc +++ b/ext/opcache/jit/ir/ir_x86.dasc @@ -1569,6 +1569,20 @@ op2_const: constraints->tmp_regs[0] = IR_TMP_REG(1, IR_ADDR, IR_LOAD_SUB_REF, IR_DEF_SUB_REF); n = 1; break; + case IR_SSE_SQRT: + case IR_SSE_RINT: + case IR_SSE_FLOOR: + case IR_SSE_CEIL: + case IR_SSE_TRUNC: + case IR_SSE_NEARBYINT: + insn = &ctx->ir_base[ref]; + flags = IR_USE_MUST_BE_IN_REG | IR_OP3_MUST_BE_IN_REG; + if (IR_IS_CONST_REF(insn->op3)) { + const ir_insn *val_insn = &ctx->ir_base[insn->op3]; + constraints->tmp_regs[n] = IR_TMP_REG(3, val_insn->type, IR_LOAD_SUB_REF, IR_DEF_SUB_REF); + n = 1; + } + break; } constraints->tmps_count = n; @@ -2630,6 +2644,7 @@ store_int: case IR_IF_TRUE: case IR_IF_FALSE: case IR_CASE_VAL: + case IR_CASE_RANGE: case IR_CASE_DEFAULT: case IR_MERGE: case IR_LOOP_BEGIN: @@ -6868,7 +6883,24 @@ static void ir_emit_return_fp(ir_ctx *ctx, ir_ref ref, ir_insn *insn) ir_backend_data *data = ctx->data; dasm_State **Dst = &data->dasm_state; - if (op2_reg == IR_REG_NONE || IR_REG_SPILLED(op2_reg)) { + if (IR_IS_CONST_REF(insn->op2)) { + ir_insn *value = &ctx->ir_base[insn->op2]; + + if ((type == IR_FLOAT && value->val.f == 0.0) || (type == IR_DOUBLE && value->val.d == 0.0)) { + | fldz + } else if ((type == IR_FLOAT && value->val.f == 1.0) || (type == IR_DOUBLE && value->val.d == 1.0)) { + | fld1 + } else { + int label = ir_const_label(ctx, insn->op2); + + if (type == IR_DOUBLE) { + | fld qword [=>label] + } else { + IR_ASSERT(type == IR_FLOAT); + | fld dword [=>label] + } + } + } else if (op2_reg == IR_REG_NONE || IR_REG_SPILLED(op2_reg)) { ir_reg fp; int32_t offset = ir_ref_spill_slot_offset(ctx, insn->op2, &fp); @@ -8442,11 +8474,15 @@ static void ir_emit_va_arg(ir_ctx *ctx, ir_ref def, ir_insn *insn) ir_backend_data *data = ctx->data; dasm_State **Dst = &data->dasm_state; ir_type type = insn->type; - ir_reg def_reg = ctx->regs[def][0]; + ir_reg def_reg = IR_REG_NUM(ctx->regs[def][0]); ir_reg op2_reg = ctx->regs[def][2]; ir_reg tmp_reg = ctx->regs[def][3]; int32_t offset; + if (ctx->use_lists[def].count == 1) { + /* dead load */ + return; + } IR_ASSERT(def_reg != IR_REG_NONE && tmp_reg != IR_REG_NONE); if (op2_reg != IR_REG_NONE) { if (IR_REG_SPILLED(op2_reg)) { @@ -8471,11 +8507,15 @@ static void ir_emit_va_arg(ir_ctx *ctx, ir_ref def, ir_insn *insn) ir_backend_data *data = ctx->data; dasm_State **Dst = &data->dasm_state; ir_type type = insn->type; - ir_reg def_reg = ctx->regs[def][0]; + ir_reg def_reg = IR_REG_NUM(ctx->regs[def][0]); ir_reg op2_reg = ctx->regs[def][2]; ir_reg tmp_reg = ctx->regs[def][3]; int32_t offset; + if (ctx->use_lists[def].count == 1) { + /* dead load */ + return; + } IR_ASSERT(def_reg != IR_REG_NONE&& tmp_reg != IR_REG_NONE); if (op2_reg != IR_REG_NONE) { if (IR_REG_SPILLED(op2_reg)) { @@ -8541,6 +8581,7 @@ static void ir_emit_switch(ir_ctx *ctx, uint32_t b, ir_ref def, ir_insn *insn) ir_val min, max; ir_reg op2_reg = ctx->regs[def][2]; ir_reg tmp_reg = ctx->regs[def][3]; + bool has_case_range = 0; type = ctx->ir_base[insn->op2].type; IR_ASSERT(tmp_reg != IR_REG_NONE); @@ -8570,6 +8611,21 @@ static void ir_emit_switch(ir_ctx *ctx, uint32_t b, ir_ref def, ir_insn *insn) max.u64 = (int64_t)IR_MAX(max.u64, val->val.u64); } count++; + } else if (use_insn->op == IR_CASE_RANGE) { + has_case_range = 1; + val = &ctx->ir_base[use_insn->op2]; + IR_ASSERT(!IR_IS_SYM_CONST(val->op)); + ir_insn *val2 = &ctx->ir_base[use_insn->op3]; + IR_ASSERT(!IR_IS_SYM_CONST(val2->op)); + if (IR_IS_TYPE_SIGNED(type)) { + IR_ASSERT(IR_IS_TYPE_SIGNED(val->type)); + min.i64 = IR_MIN(min.i64, val->val.i64); + max.i64 = IR_MAX(max.i64, val2->val.i64); + } else { + IR_ASSERT(!IR_IS_TYPE_SIGNED(val->type)); + min.u64 = (int64_t)IR_MIN(min.u64, val->val.u64); + max.u64 = (int64_t)IR_MAX(max.u64, val2->val.u64); + } } else { IR_ASSERT(use_insn->op == IR_CASE_DEFAULT); default_label = ir_skip_empty_target_blocks(ctx, use_block); @@ -8583,7 +8639,7 @@ static void ir_emit_switch(ir_ctx *ctx, uint32_t b, ir_ref def, ir_insn *insn) } /* Generate a table jmp or a seqence of calls */ - if (count > 2 && (max.i64-min.i64) < count * 8) { + if (!has_case_range && count > 2 && (max.i64-min.i64) < count * 8) { int *labels = ir_mem_malloc(sizeof(int) * (size_t)(max.i64 - min.i64 + 1)); for (i = 0; i <= (max.i64 - min.i64); i++) { @@ -8747,6 +8803,42 @@ static void ir_emit_switch(ir_ctx *ctx, uint32_t b, ir_ref def, ir_insn *insn) |.endif } | je =>label + } else if (use_insn->op == IR_CASE_RANGE) { + val = &ctx->ir_base[use_insn->op2]; + IR_ASSERT(!IR_IS_SYM_CONST(val->op)); + label = ir_skip_empty_target_blocks(ctx, use_block); + if (IR_IS_32BIT(type, val->val)) { + | ASM_REG_IMM_OP cmp, type, op2_reg, val->val.i32 + } else { + IR_ASSERT(sizeof(void*) == 8); +|.if X64 + | mov64 Ra(tmp_reg), val->val.i64 + | ASM_REG_REG_OP cmp, type, op2_reg, tmp_reg +|.endif + } + if (IR_IS_TYPE_SIGNED(type)) { + | jl >1 + } else { + | jb >1 + } + val = &ctx->ir_base[use_insn->op3]; + IR_ASSERT(!IR_IS_SYM_CONST(val->op3)); + label = ir_skip_empty_target_blocks(ctx, use_block); + if (IR_IS_32BIT(type, val->val)) { + | ASM_REG_IMM_OP cmp, type, op2_reg, val->val.i32 + } else { + IR_ASSERT(sizeof(void*) == 8); +|.if X64 + | mov64 Ra(tmp_reg), val->val.i64 + | ASM_REG_REG_OP cmp, type, op2_reg, tmp_reg +|.endif + } + if (IR_IS_TYPE_SIGNED(type)) { + | jle =>label + } else { + | jbe =>label + } + |1: } } if (default_label) { @@ -9221,6 +9313,58 @@ static void ir_emit_tailcall(ir_ctx *ctx, ir_ref def, ir_insn *insn) return; } + /* Move op2 to a tmp register before epilogue if it's in + * used_preserved_regs, because it will be overridden. */ + + ir_reg op2_reg = IR_REG_NONE; + ir_mem mem = IR_MEM_B(IR_REG_NONE); + if (!IR_IS_CONST_REF(insn->op2)) { + op2_reg = ctx->regs[def][2]; + + ir_regset preserved_regs = (ir_regset)ctx->used_preserved_regs | IR_REGSET(IR_REG_STACK_POINTER); + if (ctx->flags & IR_USE_FRAME_POINTER) { + preserved_regs |= IR_REGSET(IR_REG_FRAME_POINTER); + } + + bool is_spill_slot = op2_reg != IR_REG_NONE + && IR_REG_SPILLED(op2_reg) + && ctx->vregs[insn->op2]; + + if (op2_reg != IR_REG_NONE && !is_spill_slot) { + if (IR_REGSET_IN(preserved_regs, IR_REG_NUM(op2_reg))) { + ir_ref orig_op2_reg = op2_reg; + op2_reg = IR_REG_RAX; + + if (IR_REG_SPILLED(orig_op2_reg)) { + ir_emit_load(ctx, IR_ADDR, op2_reg, insn->op2); + } else { + ir_type type = ctx->ir_base[insn->op2].type; + | ASM_REG_REG_OP mov, type, op2_reg, IR_REG_NUM(orig_op2_reg) + } + } else { + op2_reg = IR_REG_NUM(op2_reg); + } + } else { + if (ir_rule(ctx, insn->op2) & IR_FUSED) { + IR_ASSERT(op2_reg == IR_REG_NONE); + mem = ir_fuse_load(ctx, def, insn->op2); + } else { + mem = ir_ref_spill_slot(ctx, insn->op2); + } + ir_reg base = IR_MEM_BASE(mem); + ir_reg index = IR_MEM_INDEX(mem); + if ((base != IR_REG_NONE && IR_REGSET_IN(preserved_regs, base)) || + (index != IR_REG_NONE && IR_REGSET_IN(preserved_regs, index))) { + op2_reg = IR_REG_RAX; + + ir_type type = ctx->ir_base[insn->op2].type; + ir_emit_load_mem_int(ctx, type, op2_reg, mem); + } else { + op2_reg = IR_REG_NONE; + } + } + } + ir_emit_epilogue(ctx); if (IR_IS_CONST_REF(insn->op2)) { @@ -9246,22 +9390,10 @@ static void ir_emit_tailcall(ir_ctx *ctx, ir_ref def, ir_insn *insn) |.endif } } else { - ir_reg op2_reg = ctx->regs[def][2]; - if (op2_reg != IR_REG_NONE) { - if (IR_REG_SPILLED(op2_reg)) { - op2_reg = IR_REG_NUM(op2_reg); - ir_emit_load(ctx, IR_ADDR, op2_reg, insn->op2); - } + IR_ASSERT(!IR_REGSET_IN((ir_regset)ctx->used_preserved_regs, op2_reg)); | jmp Ra(op2_reg) } else { - ir_mem mem; - - if (ir_rule(ctx, insn->op2) & IR_FUSED) { - mem = ir_fuse_load(ctx, def, insn->op2); - } else { - mem = ir_ref_spill_slot(ctx, insn->op2); - } | ASM_TMEM_OP jmp, aword, mem } } @@ -10314,6 +10446,7 @@ static void ir_allocate_unique_spill_slots(ir_ctx *ctx) case IR_IF_TRUE: case IR_IF_FALSE: case IR_CASE_VAL: + case IR_CASE_RANGE: case IR_CASE_DEFAULT: case IR_MERGE: case IR_LOOP_BEGIN: From 9d29283392191f660ad767b246d0f4e064b5d174 Mon Sep 17 00:00:00 2001 From: Arndt Kaiser Date: Fri, 1 Aug 2025 19:51:09 +0200 Subject: [PATCH 45/66] Fix filtering of INI directives to respect leading whitespaces Directives are now correctly filtered out if the line in the php.ini file begins with whitespace characters. Closes GH-19348 --- build/Makefile.global | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Makefile.global b/build/Makefile.global index ec19efcbc5894..49d4f8bcb3cfe 100644 --- a/build/Makefile.global +++ b/build/Makefile.global @@ -90,7 +90,7 @@ PHP_TEST_SHARED_EXTENSIONS = ` \ . $$i; $(top_srcdir)/build/shtool echo -n -- " -d zend_extension=$(top_builddir)/modules/$$dlname"; \ done; \ fi` -PHP_DEPRECATED_DIRECTIVES_REGEX = '^(magic_quotes_(gpc|runtime|sybase)?|(zend_)?extension(_debug)?(_ts)?)[\t\ ]*=' +PHP_DEPRECATED_DIRECTIVES_REGEX = '^[\t\ ]*(magic_quotes_(gpc|runtime|sybase)?|(zend_)?extension(_debug)?(_ts)?)[\t\ ]*=' test: all @if test ! -z "$(PHP_EXECUTABLE)" && test -x "$(PHP_EXECUTABLE)"; then \ From e9e432a35d8327d0bac84216d1b8afddae3c535f Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Mon, 4 Aug 2025 16:16:49 +0200 Subject: [PATCH 46/66] Upgrade to macOS 14 and backport necessary changes --- .github/actions/brew/action.yml | 26 ++++++------- .github/actions/configure-macos/action.yml | 37 ++++++++++--------- .../actions/verify-generated-files/action.yml | 2 +- .github/workflows/push.yml | 4 +- 4 files changed, 33 insertions(+), 36 deletions(-) diff --git a/.github/actions/brew/action.yml b/.github/actions/brew/action.yml index 5cd8d33cfa6b7..fa2acd58d2e57 100644 --- a/.github/actions/brew/action.yml +++ b/.github/actions/brew/action.yml @@ -5,31 +5,27 @@ runs: - shell: bash run: | set -x + + # Patch brew to overwrite always + formula_installer="$(brew --repo)"/Library/Homebrew/formula_installer.rb + code=" keg.link\(verbose: verbose\?" + sudo sed -Ei '' "s/$code.*/$code, overwrite: true\)/" "$formula_installer" + + # Some packages exist on x86 but not arm, or vice versa. + # Install them with reinstall to avoid warnings. + brew reinstall autoconf webp tidy-html5 libzip libsodium icu4c brew install \ - pkg-config \ - autoconf \ bison \ re2c brew install \ - openssl@1.1 \ - curl \ + aspell \ krb5 \ bzip2 \ enchant \ libffi \ - libpng \ - webp \ - freetype \ intltool \ libiconv \ - zlib \ t1lib \ - gd \ - libzip \ - gmp \ - tidy-html5 \ libxml2 \ libjpeg \ - libxslt \ - postgresql - brew link gettext --force + libxslt diff --git a/.github/actions/configure-macos/action.yml b/.github/actions/configure-macos/action.yml index 87627d4f84848..071e31708c21b 100644 --- a/.github/actions/configure-macos/action.yml +++ b/.github/actions/configure-macos/action.yml @@ -10,15 +10,15 @@ runs: run: | set -x BREW_OPT="$(brew --prefix)"/opt - export PATH="/usr/local/opt/bison/bin:$PATH" - export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/openssl@1.1/lib/pkgconfig" - export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/curl/lib/pkgconfig" - export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/krb5/lib/pkgconfig" - export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/libffi/lib/pkgconfig" - export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/libxml2/lib/pkgconfig" - export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/libxslt/lib/pkgconfig" - export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/zlib/lib/pkgconfig" - export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/icu4c@74/lib/pkgconfig" + export PATH="$BREW_OPT/bison/bin:$PATH" + export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/openssl@1.1/lib/pkgconfig" + export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/curl/lib/pkgconfig" + export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/krb5/lib/pkgconfig" + export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/libffi/lib/pkgconfig" + export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/libxml2/lib/pkgconfig" + export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/libxslt/lib/pkgconfig" + export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/zlib/lib/pkgconfig" + export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/icu4c/lib/pkgconfig" sed -i -e 's/Requires.private:.*//g' "$BREW_OPT/curl/lib/pkgconfig/libcurl.pc" ./buildconf --force ./configure \ @@ -28,8 +28,8 @@ runs: --enable-fpm \ --with-pdo-mysql=mysqlnd \ --with-mysqli=mysqlnd \ - --with-pgsql=/usr/local/opt/libpq \ - --with-pdo-pgsql=/usr/local/opt/libpq \ + --with-pgsql="$BREW_OPT"/libpq \ + --with-pdo-pgsql="$BREW_OPT"/libpq \ --with-pdo-sqlite \ --without-pear \ --enable-gd \ @@ -42,30 +42,31 @@ runs: --enable-soap \ --enable-xmlreader \ --with-xsl \ - --with-tidy=/usr/local/opt/tidy-html5 \ + --with-tidy="$BREW_OPT"/tidy-html5 \ --with-libxml \ --enable-sysvsem \ --enable-sysvshm \ --enable-shmop \ --enable-pcntl \ - --with-readline=/usr/local/opt/readline \ + --with-readline="$BREW_OPT"/readline \ --enable-mbstring \ --with-curl \ - --with-gettext=/usr/local/opt/gettext \ + --with-gettext="$BREW_OPT"/gettext \ --enable-sockets \ - --with-bz2=/usr/local/opt/bzip2 \ + --with-bz2="$BREW_OPT"/bzip2 \ --with-openssl \ - --with-gmp=/usr/local/opt/gmp \ - --with-iconv=/usr/local/opt/libiconv \ + --with-gmp="$BREW_OPT"/gmp \ + --with-iconv="$BREW_OPT"/libiconv \ --enable-bcmath \ --enable-calendar \ --enable-ftp \ - --with-pspell=/usr/local/opt/aspell \ + --with-pspell="$BREW_OPT"/aspell \ --with-kerberos \ --enable-sysvmsg \ --with-ffi \ --enable-zend-test \ --enable-dl-test=shared \ + --enable-intl \ --with-mhash \ --with-sodium \ --enable-dba \ diff --git a/.github/actions/verify-generated-files/action.yml b/.github/actions/verify-generated-files/action.yml index 4bd838f631728..4d26e5f250274 100644 --- a/.github/actions/verify-generated-files/action.yml +++ b/.github/actions/verify-generated-files/action.yml @@ -5,7 +5,7 @@ runs: - shell: bash run: | set -x - [[ "$OSTYPE" == "darwin"* ]] && export PATH="/usr/local/opt/bison/bin:$PATH" + [[ "$OSTYPE" == "darwin"* ]] && export PATH="$(brew --prefix)/opt/bison/bin:$PATH" scripts/dev/genfiles Zend/zend_vm_gen.php build/gen_stub.php -f diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 5353ef7d0ea44..5c1057dd9164b 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -113,7 +113,7 @@ jobs: uses: ./.github/actions/verify-generated-files MACOS_DEBUG_NTS: if: github.repository == 'php/php-src' || github.event_name == 'pull_request' - runs-on: macos-13 + runs-on: macos-14 steps: - name: git checkout uses: actions/checkout@v4 @@ -130,7 +130,7 @@ jobs: configurationParameters: --enable-debug --disable-zts - name: make run: |- - export PATH="/usr/local/opt/bison/bin:$PATH" + export PATH="$(brew --prefix)/opt/bison/bin:$PATH" make -j$(sysctl -n hw.logicalcpu) >/dev/null - name: make install run: sudo make install From f077c9d233f4fe6096a83cd87a36db96b64012b4 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Mon, 4 Aug 2025 23:10:41 +0200 Subject: [PATCH 47/66] [skip ci] Add zend_jit_arm64.c to gitignore This is not generated in CI since moving to macOS 14, which is arm based. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b8eb03412fa61..49acc9f2e1788 100644 --- a/.gitignore +++ b/.gitignore @@ -169,6 +169,7 @@ php # Miscellaneous extensions files /ext/opcache/jit/zend_jit_x86.c +/ext/opcache/jit/zend_jit_arm64.c /ext/opcache/minilua # Generated by `cd ext/name && phpize && ./configure` From 1c65cc3998a69f8b33a09e16823a64c4954a6214 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Tue, 5 Aug 2025 14:50:18 +0200 Subject: [PATCH 48/66] [skip ci] Group component output in community job --- .github/workflows/nightly.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 38ccb05e09928..ade549e16efea 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -565,14 +565,16 @@ jobs: repositories="amp cache dns file http parallel parser pipeline process serialization socket sync websocket-client websocket-server" X=0 for repository in $repositories; do - printf "Testing amp/%s\n" "$repository" + echo "::group::$repository" git clone "https://github.com/amphp/$repository.git" "amphp-$repository" --depth 1 cd "amphp-$repository" git rev-parse HEAD php /usr/bin/composer install --no-progress --ignore-platform-req=php+ vendor/bin/phpunit || EXIT_CODE=$? + echo "::endgroup::" if [ ${EXIT_CODE:-0} -gt 128 ]; then X=1; + echo "Failed" fi cd .. done @@ -596,14 +598,16 @@ jobs: repositories="async cache child-process datagram dns event-loop promise promise-stream promise-timer stream" X=0 for repository in $repositories; do - printf "Testing reactphp/%s\n" "$repository" + echo "::group::$repository" git clone "https://github.com/reactphp/$repository.git" "reactphp-$repository" --depth 1 cd "reactphp-$repository" git rev-parse HEAD php /usr/bin/composer install --no-progress --ignore-platform-req=php+ vendor/bin/phpunit || EXIT_CODE=$? + echo "::endgroup::" if [ $[EXIT_CODE:-0} -gt 128 ]; then X=1; + echo "Failed" fi cd .. done @@ -634,9 +638,12 @@ jobs: 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 + echo "::group::$component" php ./phpunit $component --exclude-group tty,benchmark,intl-data,transient --exclude-group skip || EXIT_CODE=$? + echo "::endgroup::" if [ ${EXIT_CODE:-0} -gt 128 ]; then X=1; + echo "Failed" fi done exit $X From 8199cad99f9c8c9d18b829680001d5185016690a Mon Sep 17 00:00:00 2001 From: Shivam Mathur Date: Wed, 6 Aug 2025 05:32:36 +0530 Subject: [PATCH 49/66] Fix master branch check in find-target-branch.bat (#19385) --- .github/scripts/windows/find-target-branch.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/windows/find-target-branch.bat b/.github/scripts/windows/find-target-branch.bat index 43b285214971c..a0b47f2488946 100644 --- a/.github/scripts/windows/find-target-branch.bat +++ b/.github/scripts/windows/find-target-branch.bat @@ -3,6 +3,6 @@ for /f "usebackq tokens=3" %%i in (`findstr PHP_MAJOR_VERSION main\php_version.h`) do set BRANCH=%%i for /f "usebackq tokens=3" %%i in (`findstr PHP_MINOR_VERSION main\php_version.h`) do set BRANCH=%BRANCH%.%%i -if /i "%BRANCH%" equ "8.3" ( +if /i "%BRANCH%" equ "8.5" ( set BRANCH=master ) From c286cb8a1e84dbafb85003c456f6b47a77b7818d Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Wed, 6 Aug 2025 14:00:44 +0200 Subject: [PATCH 50/66] [skip ci] Add fuzzer binaries to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 4e95d9a9da33d..cd9ba81306fd9 100644 --- a/.gitignore +++ b/.gitignore @@ -132,6 +132,7 @@ config.h.in /sapi/cgi/php-cgi /sapi/fpm/php-fpm /sapi/phpdbg/phpdbg +/sapi/fuzzer/php-fuzz-* /scripts/php-config /scripts/phpize php From a63e97667e84cd6919bdb2c722f5bd0179852565 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Wed, 6 Aug 2025 14:18:43 +0200 Subject: [PATCH 51/66] COMMUNTIY build grouping adjustments Unconditionally execute assignment of EXIT_CODE. Otherwise, the variable bleeds into the next iteration. Also add newline before ::endgroup::. ASAN does not add a trailing newline. --- .github/workflows/nightly.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index ade549e16efea..b4266921f84ac 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -570,8 +570,9 @@ jobs: cd "amphp-$repository" git rev-parse HEAD php /usr/bin/composer install --no-progress --ignore-platform-req=php+ - vendor/bin/phpunit || EXIT_CODE=$? - echo "::endgroup::" + vendor/bin/phpunit + EXIT_CODE=$? + echo -e "\n::endgroup::" if [ ${EXIT_CODE:-0} -gt 128 ]; then X=1; echo "Failed" @@ -603,8 +604,9 @@ jobs: cd "reactphp-$repository" git rev-parse HEAD php /usr/bin/composer install --no-progress --ignore-platform-req=php+ - vendor/bin/phpunit || EXIT_CODE=$? - echo "::endgroup::" + vendor/bin/phpunit + EXIT_CODE=$? + echo -e "\n::endgroup::" if [ $[EXIT_CODE:-0} -gt 128 ]; then X=1; echo "Failed" @@ -639,8 +641,9 @@ jobs: X=0 for component in $(find src/Symfony -mindepth 2 -type f -name phpunit.xml.dist -printf '%h\n'); do echo "::group::$component" - php ./phpunit $component --exclude-group tty,benchmark,intl-data,transient --exclude-group skip || EXIT_CODE=$? - echo "::endgroup::" + php ./phpunit $component --exclude-group tty,benchmark,intl-data,transient --exclude-group skip + EXIT_CODE=$? + echo -e "\n::endgroup::" if [ ${EXIT_CODE:-0} -gt 128 ]; then X=1; echo "Failed" From 5be04e25fd6dd55cf6f4adcf45bc1daa95f5c7aa Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Wed, 6 Aug 2025 14:27:36 +0200 Subject: [PATCH 52/66] [skip ci] Skip segfaulting OOM test in GH actions on Win This only fails on the PHP-8.3 branch, most likely to be related to the environment as discussed with Niels. --- ext/spl/tests/gh14639.phpt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ext/spl/tests/gh14639.phpt b/ext/spl/tests/gh14639.phpt index 1b6f621d27bd3..db85b2b044efa 100644 --- a/ext/spl/tests/gh14639.phpt +++ b/ext/spl/tests/gh14639.phpt @@ -7,6 +7,9 @@ memory_limit=2M if (getenv("USE_ZEND_ALLOC") === "0") { die("skip Zend MM disabled"); } +if (getenv("GITHUB_ACTIONS") && substr(PHP_OS, 0, 3) == "WIN") { + die("skip Segfaults in GitHub actions on Windows"); +} ?> --FILE-- Date: Wed, 6 Aug 2025 15:07:45 +0200 Subject: [PATCH 53/66] [skip ci] Fix Symfony COMMUNITY build for new phpunit version --exclude-group no longer accepts a comma-separated list. Doc block comments are no longer supported, switch to attributes. --- .github/workflows/nightly.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index b4266921f84ac..b2846da496e93 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -634,14 +634,14 @@ jobs: php /usr/bin/composer install --no-progress --ignore-platform-req=php+ php ./phpunit install # Test causes a heap-buffer-overflow but I cannot reproduce it locally... - php -r '$c = file_get_contents("src/Symfony/Component/HtmlSanitizer/Tests/HtmlSanitizerCustomTest.php"); $c = str_replace("public function testSanitizeDeepNestedString()", "/** @group skip */\n public function testSanitizeDeepNestedString()", $c); file_put_contents("src/Symfony/Component/HtmlSanitizer/Tests/HtmlSanitizerCustomTest.php", $c);' + 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()", "/** @group skip */\n public function testCastNonTrailingCharPointer()", $c); file_put_contents("src/Symfony/Component/VarDumper/Tests/Caster/FFICasterTest.php", $c);' + 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);' 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 echo "::group::$component" - php ./phpunit $component --exclude-group tty,benchmark,intl-data,transient --exclude-group skip + php ./phpunit $component --exclude-group tty --exclude-group benchmark --exclude-group intl-data --exclude-group transient --exclude-group skip EXIT_CODE=$? echo -e "\n::endgroup::" if [ ${EXIT_CODE:-0} -gt 128 ]; then From 44618752f900dcd7a4f9128fe28d01f4d372972d Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Wed, 6 Aug 2025 22:28:56 +0200 Subject: [PATCH 54/66] [skip ci] Bump retries in benchmark diff when looking for benchmarked commits --- benchmark/generate_diff.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmark/generate_diff.php b/benchmark/generate_diff.php index 94c020df4b998..466a6ae0e1ed3 100644 --- a/benchmark/generate_diff.php +++ b/benchmark/generate_diff.php @@ -62,7 +62,7 @@ function formatDiff(?int $baseInstructions, int $headInstructions): string { } function find_benchmarked_commit_hash(string $repo, string $commitHash): ?string { - $repeat = 10; + $repeat = 100; while (true) { if ($repeat-- <= 0) { From bd2766ce795df712923a818017fa2fa1169a7a23 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sat, 3 Feb 2024 14:54:23 +0000 Subject: [PATCH 55/66] zend call stack fixing stack limit for macOs arm64. 8MB sounded a prudent size for older 10.9 macOs release, however with newer mac with arm64, it triggers a stack overflow. Cherry picks b320aabc5e875ef101510eb39ea3b40e351dc6a5 (GH-13319) from PHP-8.4. Closes GH-19390. --- NEWS | 1 + Zend/tests/stack_limit/stack_limit_010.phpt | 5 ++++- Zend/zend_call_stack.c | 9 +++++++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index 727b25f0521f9..ad06a4eef1b7b 100644 --- a/NEWS +++ b/NEWS @@ -19,6 +19,7 @@ PHP NEWS a non-Generator delegate crashes). (Arnaud) . Fixed bug GH-18736 (Circumvented type check with return by ref + finally). (ilutov) + . Fixed zend call stack size for macOs/arm64. (David Carlier) - FTP: . Fix theoretical issues with hrtime() not being available. (nielsdos) diff --git a/Zend/tests/stack_limit/stack_limit_010.phpt b/Zend/tests/stack_limit/stack_limit_010.phpt index 312c2cf551696..9e37555e6decc 100644 --- a/Zend/tests/stack_limit/stack_limit_010.phpt +++ b/Zend/tests/stack_limit/stack_limit_010.phpt @@ -15,7 +15,10 @@ $stack = zend_test_zend_call_stack_get(); var_dump($stack); $expectedMaxSize = match(php_uname('s')) { - 'Darwin' => 8*1024*1024, + 'Darwin' => match(php_uname('m')) { + 'x86_64' => 8*1024*1024, + 'arm64' => 8372224, + }, 'FreeBSD' => match(php_uname('m')) { 'amd64' => 512*1024*1024 - 4096, 'i386' => 64*1024*1024 - 4096, diff --git a/Zend/zend_call_stack.c b/Zend/zend_call_stack.c index bbaa8810af9e1..8e0ce4c32488a 100644 --- a/Zend/zend_call_stack.c +++ b/Zend/zend_call_stack.c @@ -417,7 +417,9 @@ static bool zend_call_stack_get_macos(zend_call_stack *stack) void *base = pthread_get_stackaddr_np(pthread_self()); size_t max_size; - if (pthread_main_np()) { +#if !defined(__aarch64__) + if (pthread_main_np()) + { /* pthread_get_stacksize_np() returns a too low value for the main * thread in OSX 10.9, 10.10: * https://mail.openjdk.org/pipermail/hotspot-dev/2013-October/011353.html @@ -427,7 +429,10 @@ static bool zend_call_stack_get_macos(zend_call_stack *stack) /* Stack size is 8MiB by default for main threads * https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/CreatingThreads.html */ max_size = 8 * 1024 * 1024; - } else { + } + else +#endif + { max_size = pthread_get_stacksize_np(pthread_self()); } From 2cd9233e42a68267d544bd15373521b7d531da6b Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Thu, 7 Aug 2025 13:54:55 +0200 Subject: [PATCH 56/66] Fix ERROR_CODE handling in COMMUNITY build We need || because a non-0 return from the application aborts the execution of the job. --- .github/workflows/nightly.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index b2846da496e93..513ade4a71006 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -570,8 +570,8 @@ jobs: cd "amphp-$repository" git rev-parse HEAD php /usr/bin/composer install --no-progress --ignore-platform-req=php+ - vendor/bin/phpunit - EXIT_CODE=$? + EXIT_CODE=0 + vendor/bin/phpunit || EXIT_CODE=$? echo -e "\n::endgroup::" if [ ${EXIT_CODE:-0} -gt 128 ]; then X=1; @@ -604,8 +604,8 @@ jobs: cd "reactphp-$repository" git rev-parse HEAD php /usr/bin/composer install --no-progress --ignore-platform-req=php+ - vendor/bin/phpunit - EXIT_CODE=$? + EXIT_CODE=0 + vendor/bin/phpunit || EXIT_CODE=$? echo -e "\n::endgroup::" if [ $[EXIT_CODE:-0} -gt 128 ]; then X=1; @@ -641,8 +641,8 @@ jobs: X=0 for component in $(find src/Symfony -mindepth 2 -type f -name phpunit.xml.dist -printf '%h\n'); do echo "::group::$component" - php ./phpunit $component --exclude-group tty --exclude-group benchmark --exclude-group intl-data --exclude-group transient --exclude-group skip - EXIT_CODE=$? + EXIT_CODE=0 + php ./phpunit $component --exclude-group tty --exclude-group benchmark --exclude-group intl-data --exclude-group transient --exclude-group skip || EXIT_CODE=$? echo -e "\n::endgroup::" if [ ${EXIT_CODE:-0} -gt 128 ]; then X=1; From 5cf45ba5aba8d71f2c942254fe321906dfce4041 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Tue, 5 Aug 2025 19:38:15 +0200 Subject: [PATCH 57/66] Fix GH-19371: integer overflow in calendar.c Closes GH-19380. --- NEWS | 3 + ext/calendar/calendar.c | 25 ++++++++ .../tests/cal_days_in_month_error1.phpt | 2 +- ext/calendar/tests/gh19371.phpt | 61 +++++++++++++++++++ 4 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 ext/calendar/tests/gh19371.phpt diff --git a/NEWS b/NEWS index ad06a4eef1b7b..33ce00bc0caf1 100644 --- a/NEWS +++ b/NEWS @@ -21,6 +21,9 @@ PHP NEWS (ilutov) . Fixed zend call stack size for macOs/arm64. (David Carlier) +- Calendar: + . Fixed bug GH-19371 (integer overflow in calendar.c). (nielsdos) + - FTP: . Fix theoretical issues with hrtime() not being available. (nielsdos) diff --git a/ext/calendar/calendar.c b/ext/calendar/calendar.c index 6da7e69529e2e..b387b20c09b18 100644 --- a/ext/calendar/calendar.c +++ b/ext/calendar/calendar.c @@ -194,6 +194,16 @@ PHP_FUNCTION(cal_days_in_month) RETURN_THROWS(); } + if (UNEXPECTED(month <= 0 || month > INT32_MAX - 1)) { + zend_argument_value_error(2, "must be between 1 and %d", INT32_MAX - 1); + RETURN_THROWS(); + } + + if (UNEXPECTED(year > INT32_MAX - 1)) { + zend_argument_value_error(3, "must be less than %d", INT32_MAX - 1); + RETURN_THROWS(); + } + calendar = &cal_conversion_table[cal]; sdn_start = calendar->to_jd(year, month, 1); @@ -239,6 +249,21 @@ PHP_FUNCTION(cal_to_jd) RETURN_THROWS(); } + if (UNEXPECTED(month <= 0 || month > INT32_MAX - 1)) { + zend_argument_value_error(2, "must be between 1 and %d", INT32_MAX - 1); + RETURN_THROWS(); + } + + if (UNEXPECTED(ZEND_LONG_EXCEEDS_INT(day))) { + zend_argument_value_error(3, "must be between %d and %d", INT32_MIN, INT32_MAX); + RETURN_THROWS(); + } + + if (UNEXPECTED(year > INT32_MAX - 1)) { + zend_argument_value_error(4, "must be less than %d", INT32_MAX - 1); + RETURN_THROWS(); + } + RETURN_LONG(cal_conversion_table[cal].to_jd(year, month, day)); } /* }}} */ diff --git a/ext/calendar/tests/cal_days_in_month_error1.phpt b/ext/calendar/tests/cal_days_in_month_error1.phpt index f334888479f20..e110c13cc2a78 100644 --- a/ext/calendar/tests/cal_days_in_month_error1.phpt +++ b/ext/calendar/tests/cal_days_in_month_error1.phpt @@ -12,7 +12,7 @@ try { echo "{$ex->getMessage()}\n"; } try{ - cal_days_in_month(CAL_GREGORIAN,0, 2009); + cal_days_in_month(CAL_GREGORIAN,20, 2009); } catch (ValueError $ex) { echo "{$ex->getMessage()}\n"; } diff --git a/ext/calendar/tests/gh19371.phpt b/ext/calendar/tests/gh19371.phpt new file mode 100644 index 0000000000000..1d807a9838867 --- /dev/null +++ b/ext/calendar/tests/gh19371.phpt @@ -0,0 +1,61 @@ +--TEST-- +GH-19371 (integer overflow in calendar.c) +--SKIPIF-- + +--EXTENSIONS-- +calendar +--FILE-- +getMessage(), "\n"; +} +try { + echo cal_days_in_month(CAL_GREGORIAN, PHP_INT_MIN, 1); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} +try { + echo cal_days_in_month(CAL_GREGORIAN, PHP_INT_MAX, 1); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} + +try { + echo cal_to_jd(CAL_GREGORIAN, PHP_INT_MIN, 1, 1); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} +try { + echo cal_to_jd(CAL_GREGORIAN, PHP_INT_MAX, 1, 1); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} +try { + echo cal_to_jd(CAL_GREGORIAN, 1, PHP_INT_MIN, 1); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} +try { + echo cal_to_jd(CAL_GREGORIAN, 1, PHP_INT_MAX, 1); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} +try { + echo cal_to_jd(CAL_GREGORIAN, 1, 1, PHP_INT_MAX); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +cal_days_in_month(): Argument #3 ($year) must be less than 2147483646 +cal_days_in_month(): Argument #2 ($month) must be between 1 and 2147483646 +cal_days_in_month(): Argument #2 ($month) must be between 1 and 2147483646 +cal_to_jd(): Argument #2 ($month) must be between 1 and 2147483646 +cal_to_jd(): Argument #2 ($month) must be between 1 and 2147483646 +cal_to_jd(): Argument #3 ($day) must be between -2147483648 and 2147483647 +cal_to_jd(): Argument #3 ($day) must be between -2147483648 and 2147483647 +cal_to_jd(): Argument #4 ($year) must be less than 2147483646 From cc93bbb765a15123db0320ff50113de0300bcb11 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Thu, 7 Aug 2025 19:27:58 +0200 Subject: [PATCH 58/66] Fix GH-19397: mb_list_encodings() can cause crashes on shutdown The request shutdown does not necessarily hold the last reference, if there is still a CV that refers to the array. Closes GH-19405. --- NEWS | 4 ++++ ext/mbstring/mbstring.c | 4 ++-- ext/mbstring/tests/gh19397.phpt | 11 +++++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 ext/mbstring/tests/gh19397.phpt diff --git a/NEWS b/NEWS index 33ce00bc0caf1..fd2aad2d4e886 100644 --- a/NEWS +++ b/NEWS @@ -46,6 +46,10 @@ PHP NEWS . Fixed bug GH-19098 (libxml<2.13 segmentation fault caused by php_libxml_node_free). (nielsdos) +- MbString: + . Fixed bug GH-19397 (mb_list_encodings() can cause crashes on shutdown). + (nielsdos) + - Opcache: . Reset global pointers to prevent use-after-free in zend_jit_status(). (Florian Engelhardt) diff --git a/ext/mbstring/mbstring.c b/ext/mbstring/mbstring.c index dec565707fa78..1d5c27a2a3815 100644 --- a/ext/mbstring/mbstring.c +++ b/ext/mbstring/mbstring.c @@ -1165,8 +1165,8 @@ PHP_RSHUTDOWN_FUNCTION(mbstring) MBSTRG(outconv_state) = 0; if (MBSTRG(all_encodings_list)) { - GC_DELREF(MBSTRG(all_encodings_list)); - zend_array_destroy(MBSTRG(all_encodings_list)); + /* must be *array* release to remove from GC root buffer and free the hashtable itself */ + zend_array_release(MBSTRG(all_encodings_list)); MBSTRG(all_encodings_list) = NULL; } diff --git a/ext/mbstring/tests/gh19397.phpt b/ext/mbstring/tests/gh19397.phpt new file mode 100644 index 0000000000000..e6e07b161c089 --- /dev/null +++ b/ext/mbstring/tests/gh19397.phpt @@ -0,0 +1,11 @@ +--TEST-- +GH-19397 (mb_list_encodings() can cause crashes on shutdown) +--EXTENSIONS-- +mbstring +--FILE-- + 0); +?> +--EXPECT-- +bool(true) From 9e2aa658a8a0e6d7cf04df6683c468a08108290d Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Sat, 9 Aug 2025 13:32:56 +0200 Subject: [PATCH 59/66] Fix GH-19428: openssl_pkey_derive segfaults for DH derive with low key_length This happens only for OpenSSL 1.1.1 because key_length is ignored for DH. It means that the provided string is overwritten with longer buffer. --- NEWS | 2 ++ ext/openssl/openssl.c | 18 +++++++++++++- ext/openssl/tests/gh19428.phpt | 44 ++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 ext/openssl/tests/gh19428.phpt diff --git a/NEWS b/NEWS index fd2aad2d4e886..d10f72712e4f3 100644 --- a/NEWS +++ b/NEWS @@ -58,6 +58,8 @@ PHP NEWS . Fixed bug GH-18986 (OpenSSL backend: incorrect RAND_{load,write}_file() return value check). (nielsdos, botovq) . Fix error return check of EVP_CIPHER_CTX_ctrl(). (nielsdos) + . Fixed bug GH-19428 (openssl_pkey_derive segfaults for DH derive with low + key_length param). (Jakub Zelenka) - PDO Pgsql: . Fixed dangling pointer access on _pdo_pgsql_trim_message helper. diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index a0418e74ba7d6..256d152158496 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -5200,12 +5200,20 @@ PHP_FUNCTION(openssl_pkey_get_details) } /* }}} */ -static zend_string *php_openssl_pkey_derive(EVP_PKEY *key, EVP_PKEY *peer_key, size_t key_size) { +static zend_string *php_openssl_pkey_derive(EVP_PKEY *key, EVP_PKEY *peer_key, size_t requested_key_size) { + size_t key_size = requested_key_size; EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(key, NULL); if (!ctx) { return NULL; } +#if OPENSSL_VERSION_NUMBER < 0x30000000L + /* OpenSSL 1.1 does not respect key_size for DH, so force size discovery so it can be compared later. */ + if (EVP_PKEY_base_id(key) == EVP_PKEY_DH && key_size != 0) { + key_size = 0; + } +#endif + if (EVP_PKEY_derive_init(ctx) <= 0 || EVP_PKEY_derive_set_peer(ctx, peer_key) <= 0 || (key_size == 0 && EVP_PKEY_derive(ctx, NULL, &key_size) <= 0)) { @@ -5214,6 +5222,14 @@ static zend_string *php_openssl_pkey_derive(EVP_PKEY *key, EVP_PKEY *peer_key, s return NULL; } +#if OPENSSL_VERSION_NUMBER < 0x30000000L + /* Now compare the computed size for DH to mirror OpenSSL 3.0+ behavior. */ + if (EVP_PKEY_base_id(key) == EVP_PKEY_DH && requested_key_size > 0 && requested_key_size < key_size) { + EVP_PKEY_CTX_free(ctx); + return NULL; + } +#endif + zend_string *result = zend_string_alloc(key_size, 0); if (EVP_PKEY_derive(ctx, (unsigned char *)ZSTR_VAL(result), &key_size) <= 0) { php_openssl_store_errors(); diff --git a/ext/openssl/tests/gh19428.phpt b/ext/openssl/tests/gh19428.phpt new file mode 100644 index 0000000000000..5d290f32e62a5 --- /dev/null +++ b/ext/openssl/tests/gh19428.phpt @@ -0,0 +1,44 @@ +--TEST-- +GH-19428: openssl_pkey_derive() DH with low key_length +--EXTENSIONS-- +openssl +--FILE-- + +--EXPECT-- +bool(false) From b57578f3b13c80761073088166a17e6ac3c38dd4 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sun, 10 Aug 2025 13:25:22 +0200 Subject: [PATCH 60/66] Fix GH-19383: php-8.3.24: Missing type in function definition: fpm_event_kqueue_clean This is just a clean backport. --- sapi/fpm/fpm/events/kqueue.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sapi/fpm/fpm/events/kqueue.c b/sapi/fpm/fpm/events/kqueue.c index 8fc4a6f049b0d..b81650d7741dc 100644 --- a/sapi/fpm/fpm/events/kqueue.c +++ b/sapi/fpm/fpm/events/kqueue.c @@ -96,7 +96,7 @@ static int fpm_event_kqueue_init(int max) /* {{{ */ /* * release kqueue stuff */ -static int fpm_event_kqueue_clean() /* {{{ */ +static int fpm_event_kqueue_clean(void) /* {{{ */ { if (kevents) { free(kevents); From 91665eaa6361bbc939c5b41903061d0487fdf11b Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sun, 10 Aug 2025 14:17:19 +0200 Subject: [PATCH 61/66] Backport Lexbor changes to 8.4 --- ext/dom/lexbor/lexbor/core/in.c | 2 +- ext/dom/lexbor/lexbor/core/str.c | 4 ++++ ext/dom/lexbor/lexbor/dom/interfaces/node.c | 5 ----- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/ext/dom/lexbor/lexbor/core/in.c b/ext/dom/lexbor/lexbor/core/in.c index 9214e198950d8..951e585cbd399 100644 --- a/ext/dom/lexbor/lexbor/core/in.c +++ b/ext/dom/lexbor/lexbor/core/in.c @@ -65,7 +65,7 @@ lexbor_in_node_make(lexbor_in_t *incoming, lexbor_in_node_t *last_node, node->opt = LEXBOR_IN_OPT_UNDEF; node->begin = buf; - node->end = buf + buf_size; + node->end = buf ? (buf + buf_size) : NULL; node->use = buf; if (last_node != NULL) { diff --git a/ext/dom/lexbor/lexbor/core/str.c b/ext/dom/lexbor/lexbor/core/str.c index 0f04286bdea56..d11a08614dd42 100644 --- a/ext/dom/lexbor/lexbor/core/str.c +++ b/ext/dom/lexbor/lexbor/core/str.c @@ -133,6 +133,10 @@ lexbor_str_append(lexbor_str_t *str, lexbor_mraw_t *mraw, { lxb_char_t *data_begin; + if (length == 0) { + return str->data; + } + lexbor_str_check_size_arg_m(str, lexbor_str_size(str), mraw, (length + 1), NULL); diff --git a/ext/dom/lexbor/lexbor/dom/interfaces/node.c b/ext/dom/lexbor/lexbor/dom/interfaces/node.c index 934b350858258..5a582059071a4 100644 --- a/ext/dom/lexbor/lexbor/dom/interfaces/node.c +++ b/ext/dom/lexbor/lexbor/dom/interfaces/node.c @@ -248,11 +248,6 @@ lxb_dom_node_t * lxb_dom_node_destroy(lxb_dom_node_t *node) { lxb_dom_node_remove(node); - - if (node->owner_document->ev_destroy != NULL) { - node->owner_document->ev_destroy(node); - } - return lxb_dom_document_destroy_interface(node); } From 9b86533ce42c8a4aed9e2561010ddf79d063fd27 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Wed, 9 Jul 2025 21:18:18 +0200 Subject: [PATCH 62/66] Fix GH-19065: Long match statement can segfault compiler during recursive SSA renaming On some systems, like Alpine, the thread stack size is small by default. The last step of SSA construction involves variable renaming that is recursive, and also makes copies of their version of the renamed variables on the stack. This combination causes a stack overflow during compilation on Alpine. Triggerable for example with very long match statements. A stop-gap solution would be to use heap allocated arrays for the renamed variable list, but that would only delay the error as increasing the number of match arms increases the depth of the dominator tree, and will eventually run into the same issue. This patch transforms the algorithm into an iterative one. There are two states stored in a worklist stack: positive numbers indicate that the block still needs to undergo variable renaming. Negative numbers indicate that the block and its dominated children are already renamed. Because 0 is also a valid block number, we bias the block numbers by adding 1. To restore to the right variant when backtracking the "recursive" step, we index into an array pointing to the different variable renaming variants. Closes GH-19083. --- NEWS | 2 + Zend/Optimizer/zend_ssa.c | 100 ++++++++++++++++++++++++++++++-------- 2 files changed, 82 insertions(+), 20 deletions(-) diff --git a/NEWS b/NEWS index d10f72712e4f3..ffe2a2136e272 100644 --- a/NEWS +++ b/NEWS @@ -20,6 +20,8 @@ PHP NEWS . Fixed bug GH-18736 (Circumvented type check with return by ref + finally). (ilutov) . Fixed zend call stack size for macOs/arm64. (David Carlier) + . Fixed bug GH-19065 (Long match statement can segfault compiler during + recursive SSA renaming). (nielsdos, Arnaud) - Calendar: . Fixed bug GH-19371 (integer overflow in calendar.c). (nielsdos) diff --git a/Zend/Optimizer/zend_ssa.c b/Zend/Optimizer/zend_ssa.c index 590df8155e395..cb8acc3775d19 100644 --- a/Zend/Optimizer/zend_ssa.c +++ b/Zend/Optimizer/zend_ssa.c @@ -22,6 +22,7 @@ #include "zend_ssa.h" #include "zend_dump.h" #include "zend_inference.h" +#include "zend_worklist.h" #include "Optimizer/zend_optimizer_internal.h" static bool dominates(const zend_basic_block *blocks, int a, int b) { @@ -787,7 +788,7 @@ ZEND_API int zend_ssa_rename_op(const zend_op_array *op_array, const zend_op *op } /* }}} */ -static zend_result zend_ssa_rename(const zend_op_array *op_array, uint32_t build_flags, zend_ssa *ssa, int *var, int n) /* {{{ */ +static void zend_ssa_rename_in_block(const zend_op_array *op_array, uint32_t build_flags, zend_ssa *ssa, int *var, int n) /* {{{ */ { zend_basic_block *blocks = ssa->cfg.blocks; zend_ssa_block *ssa_blocks = ssa->blocks; @@ -795,15 +796,6 @@ static zend_result zend_ssa_rename(const zend_op_array *op_array, uint32_t build int ssa_vars_count = ssa->vars_count; int i, j; zend_op *opline, *end; - int *tmp = NULL; - ALLOCA_FLAG(use_heap = 0); - - // FIXME: Can we optimize this copying out in some cases? - if (blocks[n].next_child >= 0) { - tmp = do_alloca(sizeof(int) * (op_array->last_var + op_array->T), use_heap); - memcpy(tmp, var, sizeof(int) * (op_array->last_var + op_array->T)); - var = tmp; - } if (ssa_blocks[n].phis) { zend_ssa_phi *phi = ssa_blocks[n].phis; @@ -887,22 +879,90 @@ static zend_result zend_ssa_rename(const zend_op_array *op_array, uint32_t build } ssa->vars_count = ssa_vars_count; +} +/* }}} */ - j = blocks[n].children; - while (j >= 0) { - // FIXME: Tail call optimization? - if (zend_ssa_rename(op_array, build_flags, ssa, var, j) == FAILURE) - return FAILURE; - j = blocks[j].next_child; - } +static zend_result zend_ssa_rename(const zend_op_array *op_array, uint32_t build_flags, zend_ssa *ssa, int *var, int n) +{ + /* The worklist contains block numbers, encoded as positive or negative value. + * Positive values indicate that the variable rename still needs to happen for the block. + * Negative values indicate the variable rename was done and all children were handled too. + * In that case, we will clean up. + * Because block 0 is valid, we bias the block numbers by adding 1 such that we can distinguish + * positive and negative values in all cases. */ + zend_worklist_stack work; + ALLOCA_FLAG(work_use_heap); + ZEND_WORKLIST_STACK_ALLOCA(&work, ssa->cfg.blocks_count, work_use_heap); + zend_worklist_stack_push(&work, n + 1); + + /* This is used to backtrack the right version of the renamed variables to use. */ + ALLOCA_FLAG(save_vars_use_heap); + unsigned int save_vars_top = 0; + int **save_vars = do_alloca(sizeof(int *) * (ssa->cfg.blocks_count + 1), save_vars_use_heap); + save_vars[0] = var; + + while (work.len) { + n = zend_worklist_stack_pop(&work); + + /* Enter state: perform SSA variable rename */ + if (n > 0) { + n--; + + // FIXME: Can we optimize this copying out in some cases? + int *new_var; + if (ssa->cfg.blocks[n].next_child >= 0) { + new_var = emalloc(sizeof(int) * (op_array->last_var + op_array->T)); + memcpy(new_var, save_vars[save_vars_top], sizeof(int) * (op_array->last_var + op_array->T)); + save_vars[++save_vars_top] = new_var; + } else { + new_var = save_vars[save_vars_top]; + } - if (tmp) { - free_alloca(tmp, use_heap); + zend_ssa_rename_in_block(op_array, build_flags, ssa, new_var, n); + + int j = ssa->cfg.blocks[n].children; + if (j >= 0) { + /* Push backtrack state */ + zend_worklist_stack_push(&work, -(n + 1)); + + /* Push children in enter state */ + unsigned int child_count = 0; + int len_prior = work.len; + do { + zend_worklist_stack_push(&work, j + 1); + j = ssa->cfg.blocks[j].next_child; + child_count++; + } while (j >= 0); + + /* Reverse block order to maintain SSA variable number order given in previous PHP versions, + * but the data structure doesn't allow reverse dominator tree traversal. */ + for (unsigned int i = 0; i < child_count / 2; i++) { + int tmp = work.buf[len_prior + i]; + work.buf[len_prior + i] = work.buf[work.len - 1 - i]; + work.buf[work.len - 1 - i] = tmp; + } + } else { + /* Leafs jump directly to backtracking */ + goto backtrack; + } + } + /* Leave state: backtrack */ + else { + n = -n; + n--; +backtrack:; + if (ssa->cfg.blocks[n].next_child >= 0) { + efree(save_vars[save_vars_top]); + save_vars_top--; + } + } } + free_alloca(save_vars, save_vars_use_heap); + ZEND_WORKLIST_STACK_FREE_ALLOCA(&work, work_use_heap); + return SUCCESS; } -/* }}} */ ZEND_API zend_result zend_build_ssa(zend_arena **arena, const zend_script *script, const zend_op_array *op_array, uint32_t build_flags, zend_ssa *ssa) /* {{{ */ { From 47f9f3a3f6781c4edd627071f9a892bbcd9fff94 Mon Sep 17 00:00:00 2001 From: Dmitry Stogov Date: Tue, 12 Aug 2025 11:59:08 +0300 Subject: [PATCH 63/66] Fix Nightly workflow Symfony assertion (ir_ra.c:326: ir_fix_live_range: Assertion `ival && p->start == old_start' failed) (#19458) --- ext/opcache/jit/zend_jit_ir.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 6afd768321cb3..1a2c9f122a367 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -14642,6 +14642,13 @@ static int zend_jit_assign_obj(zend_jit_ctx *jit, ir_ref slow_inputs = IR_UNUSED; uint32_t res_info = RES_INFO(); + if (Z_MODE(val_addr) == IS_REG + && Z_LOAD(val_addr) + && jit->ra[Z_SSA_VAR(val_addr)].ref == IR_NULL) { + /* Force load */ + zend_jit_use_reg(jit, val_addr); + } + if (val_addr != val_def_addr && val_def_addr) { if (!zend_jit_update_regs(jit, (opline+1)->op1.var, val_addr, val_def_addr, val_info)) { return 0; From abb7f2879eb46b2e39f146594521d8fb3bb85376 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Tue, 12 Aug 2025 14:17:33 +0200 Subject: [PATCH 64/66] [skip ci] Add timeout for FreeBSD Unfortunately, these jobs routinely fail to boot correctly. In this case, they'll stall and block CI for 6 hours until they finally fail. Add a limit to make them fail earlier. --- .github/workflows/nightly.yml | 1 + .github/workflows/push.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 513ade4a71006..ffa43fe744a26 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -1086,6 +1086,7 @@ jobs: - zts: ${{ !inputs.run_freebsd_zts && true || '*never*' }} name: "FREEBSD_${{ matrix.zts && 'ZTS' || 'NTS' }}" runs-on: ubuntu-latest + timeout-minutes: 50 steps: - name: git checkout uses: actions/checkout@v4 diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 5c1057dd9164b..29fc841dd4e70 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -174,6 +174,7 @@ jobs: if: github.repository == 'php/php-src' || github.event_name == 'pull_request' name: FREEBSD runs-on: ubuntu-latest + timeout-minutes: 50 steps: - name: git checkout uses: actions/checkout@v4 From 845c9748f7cc88a0b7871e917a7e802917173db6 Mon Sep 17 00:00:00 2001 From: Saki Takamachi Date: Tue, 26 Aug 2025 22:36:07 +0900 Subject: [PATCH 65/66] Update versions for PHP 8.4.12 --- Zend/zend.h | 2 +- configure.ac | 2 +- main/php_version.h | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Zend/zend.h b/Zend/zend.h index 0e34aeef9f9e4..197307cc6792d 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -20,7 +20,7 @@ #ifndef ZEND_H #define ZEND_H -#define ZEND_VERSION "4.4.12-dev" +#define ZEND_VERSION "4.4.12" #define ZEND_ENGINE_3 diff --git a/configure.ac b/configure.ac index dd13a9278dec0..9988d17ed474b 100644 --- a/configure.ac +++ b/configure.ac @@ -17,7 +17,7 @@ dnl Basic autoconf initialization, generation of config.nice. dnl ---------------------------------------------------------------------------- AC_PREREQ([2.68]) -AC_INIT([PHP],[8.4.12-dev],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) +AC_INIT([PHP],[8.4.12],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) AC_CONFIG_SRCDIR([main/php_version.h]) AC_CONFIG_AUX_DIR([build]) AC_PRESERVE_HELP_ORDER diff --git a/main/php_version.h b/main/php_version.h index 0758d83aed3d3..908b75b6bdab2 100644 --- a/main/php_version.h +++ b/main/php_version.h @@ -3,6 +3,6 @@ #define PHP_MAJOR_VERSION 8 #define PHP_MINOR_VERSION 4 #define PHP_RELEASE_VERSION 12 -#define PHP_EXTRA_VERSION "-dev" -#define PHP_VERSION "8.4.12-dev" +#define PHP_EXTRA_VERSION "" +#define PHP_VERSION "8.4.12" #define PHP_VERSION_ID 80412 From a5bccd492ab2a098d5dd256135cef5287a659e92 Mon Sep 17 00:00:00 2001 From: Saki Takamachi Date: Tue, 26 Aug 2025 22:36:28 +0900 Subject: [PATCH 66/66] Update NEWS for PHP 8.4.12 --- NEWS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS b/NEWS index af2cd4dc4f8c8..908ce096a6817 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,6 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| -?? ??? ????, PHP 8.4.12 +28 Aug 2025, PHP 8.4.12 - Core: . Fixed GH-19169 build issue with C++17 and ZEND_STATIC_ASSERT macro.