diff --git a/docs/release-process.md b/docs/release-process.md index 4215002a7b4d..e65a85c101a2 100644 --- a/docs/release-process.md +++ b/docs/release-process.md @@ -105,6 +105,7 @@ releases. * php-8.4.0 (initial GA) * php-8.4.9 (periodic bugfix or security release) +12. Ensure you are familiar with our procedure for [merging upwards][]. ## Packaging a non-stable release (alpha/beta/RC) @@ -321,7 +322,7 @@ slightly different steps. We'll call attention where the steps differ. git add main/php_version.h Zend/zend.h configure.ac git merge --continue ``` - + Be sure to set up a merge driver for the `NEWS` file as described in the [Git FAQ page on the PHP wiki][gitfaq-mandatory]. @@ -543,25 +544,38 @@ slightly different steps. We'll call attention where the steps differ. ## Packaging a stable release -1. Check out the *patch-level version branch* for the release - (e.g., `PHP-8.1.7`). +1. Check out the *patch-level version branch* for the release. + + ``` + git switch PHP-X.Y.Z + ``` > 💬 **Hint** \ > You should have created this branch when packaging the non-stable release > candidate for this version. If it is for a PHP-X.Y.0 version, then the branch > was created as part of the final planned release candidate, PHP-X.Y.0RC4. -2. If a CVE commit needs to be merged to the release, have it committed to - the base branches and [merged upwards as usual][] (e.g. commit the CVE fix - to 7.2, merge to 7.3, 7.4, etc.). Then, you can cherry-pick it into the - patch-level version branch for this release. +2. If the upcoming release is a security release, you will have been informed + about it by the security release manager (SRM) by Tuesday noon (UTC). - Commit these changes and push the patch-level version branch. Ensure - that CI is still passing (see above). + > 💬 **Hint** \ + > If you haven't set up a git remote for the security repo yet, do so: + > ```bash + > git remote add security git@github.com:php/php-src-security.git + > ``` - > 💡 **Tip** \ - > Don't forget to update `NEWS` manually in an extra commit to the - > patch-level version branch. + The SRM will provide you with a branch to merge in your + *patch-level version branch*. + + ```bash + git fetch security + git merge security/PHP-X.Y.Z-security + git push upstream PHP-X.Y.Z + ``` + + > 💬 **Hint** \ + > You do not need to merge this back into PHP-X.Y; the SRM will take care + > of it. 3. Run the `./scripts/dev/credits` script in the patch-level version branch, and commit the changes in the credits files in `ext/standard`. @@ -1182,7 +1196,7 @@ volunteers to begin the selection process for the next release managers. [Update NEWS for PHP 8.2.0RC6]: https://github.com/php/php-src/commit/4ccc414961a70200d638ca281a35f893226d74e2 [PHP 8.3 is now for PHP 8.3.21-dev]: https://github.com/php/php-src/commit/b57f425cfe20a11003253427424cc0517483550b [GitHub command line tool]: https://cli.github.com -[merged upwards as usual]: https://wiki.php.net/vcs/gitworkflow +[merging upwards]: https://wiki.php.net/vcs/gitworkflow [Update versions for PHP 8.1.7]: https://github.com/php/php-src/commit/d35e577a1bd0b35b9386cea97cddc73fd98eed6d [Update NEWS for PHP 8.1.7]: https://github.com/php/php-src/commit/b241f07f52ca9f87bf52be81817f475e6e727439 [Announce PHP 8.1.6]: https://github.com/php/web-php/commit/9f796a96c65f07e45845ec248933bfb0010b94a9 diff --git a/ext/standard/ftp_fopen_wrapper.c b/ext/standard/ftp_fopen_wrapper.c index 2597269bad41..4944310fad60 100644 --- a/ext/standard/ftp_fopen_wrapper.c +++ b/ext/standard/ftp_fopen_wrapper.c @@ -127,8 +127,6 @@ static php_stream *php_ftp_fopen_connect(php_stream_wrapper *wrapper, const char php_uri *resource = NULL; int result, use_ssl, use_ssl_on_data = 0; char tmp_line[512]; - char *transport; - int transport_len; const php_uri_parser *uri_parser = php_stream_context_get_uri_parser("ftp", context); if (uri_parser == NULL) { @@ -150,7 +148,8 @@ static php_stream *php_ftp_fopen_connect(php_stream_wrapper *wrapper, const char if (resource->port == 0) resource->port = 21; - transport_len = (int)spprintf(&transport, 0, "tcp://%s:" ZEND_LONG_FMT, ZSTR_VAL(resource->host), resource->port); + char *transport; + size_t transport_len = spprintf(&transport, 0, "tcp://%s:" ZEND_LONG_FMT, ZSTR_VAL(resource->host), resource->port); stream = php_stream_xport_create(transport, transport_len, REPORT_ERRORS, STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT, NULL, NULL, context, NULL, NULL); efree(transport); if (stream == NULL) { @@ -420,8 +419,6 @@ php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, const char *pa zval *tmpzval; bool allow_overwrite = false; int8_t read_write = 0; - char *transport; - int transport_len; zend_string *error_message = NULL; tmp_line[0] = '\0'; @@ -554,7 +551,9 @@ php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, const char *pa if (hoststart == NULL) { hoststart = ZSTR_VAL(resource->host); } - transport_len = (int)spprintf(&transport, 0, "tcp://%s:%d", hoststart, portno); + + char *transport; + size_t transport_len = spprintf(&transport, 0, "tcp://%s:%d", hoststart, portno); datastream = php_stream_xport_create(transport, transport_len, REPORT_ERRORS, STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT, NULL, NULL, context, &error_message, NULL); efree(transport); if (datastream == NULL) { diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c index 4b2aa7116a24..9301536458a6 100644 --- a/ext/standard/http_fopen_wrapper.c +++ b/ext/standard/http_fopen_wrapper.c @@ -206,7 +206,7 @@ static zend_string *php_stream_http_response_headers_parse(php_stream_wrapper *w /* Process folding headers if starting with a space or a tab. */ if (header_line && (*header_line == ' ' || *header_line == '\t')) { - char *http_folded_header_line = header_line; + const char *http_folded_header_line = header_line; size_t http_folded_header_line_length = *header_line_length; /* Remove the leading white spaces. */ while (*http_folded_header_line == ' ' || *http_folded_header_line == '\t') { @@ -232,7 +232,7 @@ static zend_string *php_stream_http_response_headers_parse(php_stream_wrapper *w char *last_header_value = memchr(last_header_line, ':', last_header_line_length); if (last_header_value) { /* Verify there is no space in header name */ - char *last_header_name = last_header_line + 1; + const char *last_header_name = last_header_line + 1; while (last_header_name < last_header_value) { if (*last_header_name == ' ' || *last_header_name == '\t') { header_info->error = true; diff --git a/ext/standard/tests/serialize/serialization_objects_007.phpt b/ext/standard/tests/serialize/unserialize_callback_func/autoload_must_be_called_twice.phpt similarity index 100% rename from ext/standard/tests/serialize/serialization_objects_007.phpt rename to ext/standard/tests/serialize/unserialize_callback_func/autoload_must_be_called_twice.phpt diff --git a/ext/standard/tests/serialize/bug26762.phpt b/ext/standard/tests/serialize/unserialize_callback_func/bug26762.phpt similarity index 100% rename from ext/standard/tests/serialize/bug26762.phpt rename to ext/standard/tests/serialize/unserialize_callback_func/bug26762.phpt diff --git a/ext/standard/tests/serialize/bug70213.phpt b/ext/standard/tests/serialize/unserialize_callback_func/bug70213.phpt similarity index 100% rename from ext/standard/tests/serialize/bug70213.phpt rename to ext/standard/tests/serialize/unserialize_callback_func/bug70213.phpt diff --git a/ext/standard/tests/serialize/unserialize_callback_func/deprecated_partially_supported_callable.phpt b/ext/standard/tests/serialize/unserialize_callback_func/deprecated_partially_supported_callable.phpt new file mode 100644 index 000000000000..a7806581abe5 --- /dev/null +++ b/ext/standard/tests/serialize/unserialize_callback_func/deprecated_partially_supported_callable.phpt @@ -0,0 +1,51 @@ +--TEST-- +unserialize_callback_func with partially deprecated callable string +--INI-- +unserialize_callback_func=parent::my_unserialize +--FILE-- +getMessage(), PHP_EOL; +} + +try { + $tester = new TesterChild(); + $o = $tester->unserialize($s); + var_dump($o); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +echo "Done"; + +?> +--EXPECTF-- +Error: Invalid callback parent::my_unserialize, cannot access "parent" when no class scope is active + +Deprecated: Use of "parent" in callables is deprecated in %s on line %d +callback_called in TesterParent +object(Foo)#3 (0) { +} +Done diff --git a/ext/standard/tests/serialize/unserialize_callback_func/fn_with_fqn.phpt b/ext/standard/tests/serialize/unserialize_callback_func/fn_with_fqn.phpt new file mode 100644 index 000000000000..67ce94c00dad --- /dev/null +++ b/ext/standard/tests/serialize/unserialize_callback_func/fn_with_fqn.phpt @@ -0,0 +1,22 @@ +--TEST-- +unserialize_callback_func with fully qualified name function +--INI-- +unserialize_callback_func=\my_global_fn +--FILE-- + +--EXPECT-- +callback_called +object(Foo)#1 (0) { +} +Done diff --git a/ext/standard/tests/serialize/unserialize_callback_func/fn_with_fqn_namespace.phpt b/ext/standard/tests/serialize/unserialize_callback_func/fn_with_fqn_namespace.phpt new file mode 100644 index 000000000000..28718bdb2417 --- /dev/null +++ b/ext/standard/tests/serialize/unserialize_callback_func/fn_with_fqn_namespace.phpt @@ -0,0 +1,26 @@ +--TEST-- +unserialize_callback_func with fully qualified named namespaced function +--INI-- +unserialize_callback_func=\php\test\my_global_fn +--FILE-- + +--EXPECT-- +callback_called +object(Foo)#1 (0) { +} +Done diff --git a/ext/standard/tests/serialize/unserialize_callback_func/fn_with_null_bytes.phpt b/ext/standard/tests/serialize/unserialize_callback_func/fn_with_null_bytes.phpt new file mode 100644 index 000000000000..5341d4bb6dd8 --- /dev/null +++ b/ext/standard/tests/serialize/unserialize_callback_func/fn_with_null_bytes.phpt @@ -0,0 +1,25 @@ +--TEST-- +unserialize_callback_func with function name containing null bytes +--FILE-- +getMessage(), PHP_EOL; +} + +echo "Done"; +?> +--EXPECT-- +Error: Invalid callback foo, function "foo" not found or invalid function name +Done diff --git a/ext/standard/tests/serialize/serialization_objects_008.phpt b/ext/standard/tests/serialize/unserialize_callback_func/non_existing.phpt similarity index 100% rename from ext/standard/tests/serialize/serialization_objects_008.phpt rename to ext/standard/tests/serialize/unserialize_callback_func/non_existing.phpt diff --git a/ext/standard/tests/serialize/unserialize_callback_func/non_static_method_private.phpt b/ext/standard/tests/serialize/unserialize_callback_func/non_static_method_private.phpt new file mode 100644 index 000000000000..2b3fb93f3d4a --- /dev/null +++ b/ext/standard/tests/serialize/unserialize_callback_func/non_static_method_private.phpt @@ -0,0 +1,42 @@ +--TEST-- +unserialize_callback_func with private non-static method +--INI-- +unserialize_callback_func=Tester::my_unserialize +--FILE-- +getMessage(), PHP_EOL; +} + +try { + $tester = new Tester(); + $o = $tester->unserialize($s); + var_dump($o); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +echo "Done"; + +?> +--EXPECT-- +Error: Invalid callback Tester::my_unserialize, non-static method Tester::my_unserialize() cannot be called statically +callback_called +object(Foo)#3 (0) { +} +Done diff --git a/ext/standard/tests/serialize/unserialize_callback_func/non_static_method_public.phpt b/ext/standard/tests/serialize/unserialize_callback_func/non_static_method_public.phpt new file mode 100644 index 000000000000..ec09c8285a5e --- /dev/null +++ b/ext/standard/tests/serialize/unserialize_callback_func/non_static_method_public.phpt @@ -0,0 +1,42 @@ +--TEST-- +unserialize_callback_func with public non-static method +--INI-- +unserialize_callback_func=Tester::my_unserialize +--FILE-- +getMessage(), PHP_EOL; +} + +try { + $tester = new Tester(); + $o = $tester->unserialize($s); + var_dump($o); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +echo "Done"; + +?> +--EXPECT-- +Error: Invalid callback Tester::my_unserialize, non-static method Tester::my_unserialize() cannot be called statically +callback_called +object(Foo)#3 (0) { +} +Done diff --git a/ext/standard/tests/serialize/unserialize_callback_func/static_method.phpt b/ext/standard/tests/serialize/unserialize_callback_func/static_method.phpt new file mode 100644 index 000000000000..a96d998cd8c6 --- /dev/null +++ b/ext/standard/tests/serialize/unserialize_callback_func/static_method.phpt @@ -0,0 +1,26 @@ +--TEST-- +unserialize_callback_func with public static method +--INI-- +unserialize_callback_func=Tester::my_unserialize +--FILE-- + +--EXPECT-- +callback_called +object(Foo)#1 (0) { +} +Done diff --git a/ext/standard/tests/serialize/unserialize_callback_func/static_method_private.phpt b/ext/standard/tests/serialize/unserialize_callback_func/static_method_private.phpt new file mode 100644 index 000000000000..4bc4e760bed7 --- /dev/null +++ b/ext/standard/tests/serialize/unserialize_callback_func/static_method_private.phpt @@ -0,0 +1,41 @@ +--TEST-- +unserialize_callback_func with private static method +--INI-- +unserialize_callback_func=Tester::my_unserialize +--FILE-- +getMessage(), PHP_EOL; +} + +try { + $o = Tester::unserialize($s); + var_dump($o); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +echo "Done"; + +?> +--EXPECT-- +Error: Invalid callback Tester::my_unserialize, cannot access private method Tester::my_unserialize() +callback_called +object(Foo)#2 (0) { +} +Done diff --git a/ext/standard/tests/serialize/unserialize_callback_func_INI_modifications.phpt b/ext/standard/tests/serialize/unserialize_callback_func/unserialize_callback_func_INI_modifications.phpt similarity index 100% rename from ext/standard/tests/serialize/unserialize_callback_func_INI_modifications.phpt rename to ext/standard/tests/serialize/unserialize_callback_func/unserialize_callback_func_INI_modifications.phpt diff --git a/ext/standard/var_unserializer.re b/ext/standard/var_unserializer.re index 458ad2f12baa..27647c907d3a 100644 --- a/ext/standard/var_unserializer.re +++ b/ext/standard/var_unserializer.re @@ -1256,7 +1256,7 @@ object ":" uiv ":" ["] { } /* Call unserialize callback */ - ZVAL_STR_COPY(&user_func, PG(unserialize_callback_func)); + ZVAL_STR(&user_func, zend_string_dup(PG(unserialize_callback_func), false)); ZVAL_STR(&args[0], class_name); BG(serialize_lock)++; diff --git a/main/main.c b/main/main.c index d0ffe1895e9c..753625643508 100644 --- a/main/main.c +++ b/main/main.c @@ -1884,7 +1884,7 @@ zend_result php_request_startup(void) if (PG(output_handler)) { zval oh; - ZVAL_STR_COPY(&oh, PG(output_handler)); + ZVAL_STR(&oh, zend_string_dup(PG(output_handler), false)); php_output_start_user(&oh, 0, PHP_OUTPUT_HANDLER_STDFLAGS); zval_ptr_dtor(&oh); } else if (PG(output_buffering)) { diff --git a/main/output.c b/main/output.c index 664adf7f1688..8221425f5cf6 100644 --- a/main/output.c +++ b/main/output.c @@ -468,7 +468,6 @@ PHPAPI php_output_handler *php_output_handler_create_user(zval *output_handler, char *error = NULL; php_output_handler *handler = NULL; php_output_handler_alias_ctor_t alias = NULL; - php_output_handler_user_func_t *user = NULL; switch (Z_TYPE_P(output_handler)) { case IS_NULL: @@ -480,22 +479,23 @@ PHPAPI php_output_handler *php_output_handler_create_user(zval *output_handler, break; } ZEND_FALLTHROUGH; - default: - user = ecalloc(1, sizeof(php_output_handler_user_func_t)); - if (SUCCESS == zend_fcall_info_init(output_handler, 0, &user->fci, &user->fcc, &handler_name, &error)) { + default: { + zend_fcall_info_cache *fcc = ecalloc(1, sizeof(*fcc)); + + if (zend_is_callable_ex(output_handler, NULL, 0, &handler_name, fcc, &error)) { handler = php_output_handler_init(handler_name, chunk_size, PHP_OUTPUT_HANDLER_ABILITY_FLAGS(flags) | PHP_OUTPUT_HANDLER_USER); - ZVAL_COPY(&user->zoh, output_handler); - handler->func.user = user; + zend_fcc_addref(fcc); + handler->func.user_fcc = fcc; } else { - efree(user); - } - if (error) { + efree(fcc); + ZEND_ASSERT(error); php_error_docref("ref.outcontrol", E_WARNING, "%s", error); efree(error); } if (handler_name) { zend_string_release_ex(handler_name, 0); } + } } return handler; @@ -707,8 +707,8 @@ PHPAPI void php_output_handler_dtor(php_output_handler *handler) efree(handler->buffer.data); } if (handler->flags & PHP_OUTPUT_HANDLER_USER) { - zval_ptr_dtor(&handler->func.user->zoh); - efree(handler->func.user); + zend_fcc_dtor(handler->func.user_fcc); + efree(handler->func.user_fcc); } if (handler->dtor && handler->opaq) { handler->dtor(handler->opaq); @@ -966,13 +966,12 @@ static inline php_output_handler_status_t php_output_handler_op(php_output_handl /* ob_mode */ ZVAL_LONG(&ob_args[1], (zend_long) context->op); - /* Set FCI info */ - handler->func.user->fci.param_count = 2; - handler->func.user->fci.params = ob_args; - handler->func.user->fci.retval = &retval; - handler->func.user->fci.consumed_args = zend_fci_consumed_arg(0); + zend_call_known_fcc_ex(handler->func.user_fcc, &retval, 2, ob_args, NULL, zend_fci_consumed_arg(0)); - if (SUCCESS == zend_call_function(&handler->func.user->fci, &handler->func.user->fcc) && Z_TYPE(retval) != IS_UNDEF) { + zval_ptr_dtor(&ob_args[0]); + zval_ptr_dtor(&ob_args[1]); + + if (Z_TYPE(retval) != IS_UNDEF) { if (handler->flags & PHP_OUTPUT_HANDLER_PRODUCED_OUTPUT) { // Make sure that we don't get lost in the current output buffer // by disabling it @@ -1026,9 +1025,7 @@ static inline php_output_handler_status_t php_output_handler_op(php_output_handl status = PHP_OUTPUT_HANDLER_FAILURE; } - /* Free arguments and return value */ - zval_ptr_dtor(&ob_args[0]); - zval_ptr_dtor(&ob_args[1]); + /* Free return value */ zval_ptr_dtor(&retval); } else { diff --git a/main/php_output.h b/main/php_output.h index f0a26824936a..4db6809d4936 100644 --- a/main/php_output.h +++ b/main/php_output.h @@ -112,12 +112,6 @@ typedef zend_result (*php_output_handler_conflict_check_t)(const char *handler_n /* ctor for aliases */ typedef struct _php_output_handler *(*php_output_handler_alias_ctor_t)(const char *handler_name, size_t handler_name_len, size_t chunk_size, int flags); -typedef struct _php_output_handler_user_func_t { - zend_fcall_info fci; - zend_fcall_info_cache fcc; - zval zoh; -} php_output_handler_user_func_t; - typedef struct _php_output_handler { zend_string *name; int flags; @@ -129,7 +123,7 @@ typedef struct _php_output_handler { void (*dtor)(void *opaq); union { - php_output_handler_user_func_t *user; + zend_fcall_info_cache *user_fcc; php_output_handler_context_func_t internal; } func; } php_output_handler;