Commit 3cab493029a for php
commit 3cab493029a1e11843e54535ac1564d6153050fe
Merge: 2cb6586c7bb 8cb57d2e221
Author: Arnaud Le Blanc <arnaud.lb@gmail.com>
Date: Fri Sep 25 15:29:59 2026 +0200
Merge branch 'PHP-8.4' into PHP-8.5
* PHP-8.4:
ext/pcntl: do not drop queued signals when an exception is pending (#23624)
diff --cc NEWS
index 23ce2db98b9,8d34aad3fe6..cc86e4e8be3
--- a/NEWS
+++ b/NEWS
@@@ -64,11 -64,13 +64,17 @@@ PH
. Fixed OSS-Fuzz #5674034779193344 (Read of uninitialized memory in
is_cacheable_stream_path()). (ndossche)
. Fix zend_analyze_calls() call_stack buffer overrun. (Mrmaxmeier)
+ . Fixed bug GH-23628 (Tracing JIT reads undefined property slots of lazy
+ proxy objects instead of forwarding to the real instance). (lisachenko)
+ . Fixed bug GH-23679 (Tracing JIT writes a parent private property into a
+ child's shadowing public property). (Ilia Alshanetsky)
+ - PCNTL:
+ . Fixed pcntl_signal_dispatch() dropping the queued signals when it runs while
+ an exception is pending. (nicolas-grekas)
+ . Fixed pcntl_signal_dispatch() dropping the signals queued behind a handler
+ that throws. (nicolas-grekas)
+
- PDO:
. Fixed PDOStatement::getColumnMeta() reading out of bounds for an invalid
column index. (Ilia Alshanetsky)
diff --cc ext/pcntl/pcntl.c
index 4eb58997f4c,b55e1417d11..52beffd1c6e
--- a/ext/pcntl/pcntl.c
+++ b/ext/pcntl/pcntl.c
@@@ -31,8 -31,8 +31,9 @@@
#include "ext/standard/info.h"
#include "php_signal.h"
#include "php_ticks.h"
+ #include "zend_exceptions.h"
#include "zend_fibers.h"
+#include "main/php_main.h"
#if defined(HAVE_GETPRIORITY) || defined(HAVE_SETPRIORITY) || defined(HAVE_WAIT3)
#include <sys/wait.h>
@@@ -1394,10 -1349,27 +1398,26 @@@ void pcntl_signal_dispatch(void
PCNTL_G(head) = NULL; /* simple stores are atomic */
PCNTL_G(tail) = NULL;
+ /* Dispatching can happen with an exception pending, e.g. from the interrupt check that runs
+ * right after an internal function threw. call_user_function() does nothing in that state,
+ * so set the exception aside while the handlers run. The frame is left as found: depending
+ * on the caller, the exception may not be registered on it yet, or may already be on its
+ * way to a catch block, and the caller takes it from there once we return. */
+ old_exception = EG(exception);
+ if (old_exception) {
+ if (EG(current_execute_data)) {
+ old_opline = EG(current_execute_data)->opline;
+ }
+ old_opline_before_exception = EG(opline_before_exception);
+ EG(exception) = NULL;
+ }
+
/* Allocate */
while (queue) {
+ bool handler_threw = false;
+
if ((handle = zend_hash_index_find(&PCNTL_G(php_signal_table), queue->signo)) != NULL) {
if (Z_TYPE_P(handle) != IS_LONG) {
- ZVAL_NULL(&retval);
ZVAL_LONG(¶ms[0], queue->signo);
#ifdef HAVE_STRUCT_SIGINFO_T
array_init(¶ms[1]);
@@@ -1422,20 -1393,47 +1440,47 @@@
queue->next = PCNTL_G(spares);
PCNTL_G(spares) = queue;
queue = next;
+
+ /* No other handler can be called while the exception propagates */
+ if (handler_threw) {
+ break;
+ }
}
- /* drain the remaining in case of exception thrown */
- while (queue) {
- next = queue->next;
- queue->next = PCNTL_G(spares);
- PCNTL_G(spares) = queue;
- queue = next;
+ if (old_exception) {
+ if (EG(current_execute_data)) {
+ EG(current_execute_data)->opline = old_opline;
+ }
+ EG(opline_before_exception) = old_opline_before_exception;
+ if (EG(exception)) {
+ zend_exception_set_previous(EG(exception), old_exception);
+ } else {
+ EG(exception) = old_exception;
+ }
}
- PCNTL_G(pending_signals) = false;
+ if (UNEXPECTED(queue)) {
+ /* Put back what the throwing handler did not get to, instead of dropping it, and ask
+ * the engine to come back once the exception has been handled. Signals are still
+ * blocked here, so PCNTL_G(head) cannot have been repopulated in the meantime. */
+ next = queue;
+
+ while (next->next) {
+ next = next->next;
+ }
+
+ PCNTL_G(head) = queue;
+ PCNTL_G(tail) = next;
+
+ if (PCNTL_G(async_signals)) {
+ zend_atomic_bool_store_ex(&EG(vm_interrupt), true);
+ }
+ } else {
- PCNTL_G(pending_signals) = 0;
++ PCNTL_G(pending_signals) = false;
+ }
/* Re-enable queue */
- PCNTL_G(processing_signal_queue) = 0;
+ PCNTL_G(processing_signal_queue) = false;
/* Re-enable fiber switching */
zend_fiber_switch_unblock();
diff --cc ext/zend_test/test.c
index 310386dd42d,c4be7ff42fb..e1db746297f
--- a/ext/zend_test/test.c
+++ b/ext/zend_test/test.c
@@@ -40,8 -37,14 +40,10 @@@
#include "zend_call_stack.h"
#include "zend_exceptions.h"
#include "zend_mm_custom_handlers.h"
-#include <signal.h>
+#include "ext/uri/php_uri.h"
-// `php.h` sets `NDEBUG` when not `PHP_DEBUG` which will make `assert()` from
-// assert.h a no-op. In order to have `assert()` working on NDEBUG builds, we
-// undefine `NDEBUG` and re-include assert.h
-#undef NDEBUG
-#include "assert.h"
++#include <signal.h>
+
#if defined(HAVE_LIBXML) && !defined(PHP_WIN32)
# include <libxml/globals.h>
# include <libxml/parser.h>
@@@ -727,126 -673,22 +729,142 @@@ static ZEND_FUNCTION(zend_test_crash
php_printf("%s", invalid);
}
+static ZEND_FUNCTION(zend_test_uri_parser)
+{
+ zend_string *uri_string;
+ zend_string *parser_name;
+
+ ZEND_PARSE_PARAMETERS_START(2, 2)
+ Z_PARAM_STR(uri_string)
+ Z_PARAM_STR(parser_name)
+ ZEND_PARSE_PARAMETERS_END();
+
+ const php_uri_parser *parser = php_uri_get_parser(parser_name);
+ if (parser == NULL) {
+ zend_argument_value_error(1, "Unknown parser");
+ RETURN_THROWS();
+ }
+
+ php_uri_internal *uri = php_uri_parse(parser, ZSTR_VAL(uri_string), ZSTR_LEN(uri_string), false);
+ if (uri == NULL) {
+ RETURN_THROWS();
+ }
+
+ php_uri *uri_struct = php_uri_parse_to_struct(parser, ZSTR_VAL(uri_string), ZSTR_LEN(uri_string), PHP_URI_COMPONENT_READ_MODE_RAW, false);
+ if (uri_struct == NULL) {
+ RETURN_THROWS();
+ }
+
+ zval value;
+
+ array_init(return_value);
+ zval normalized;
+ array_init(&normalized);
+ php_uri_get_scheme(uri, PHP_URI_COMPONENT_READ_MODE_NORMALIZED_ASCII, &value);
+ zend_hash_add(Z_ARR(normalized), ZSTR_KNOWN(ZEND_STR_SCHEME), &value);
+ php_uri_get_username(uri, PHP_URI_COMPONENT_READ_MODE_NORMALIZED_ASCII, &value);
+ zend_hash_add(Z_ARR(normalized), ZSTR_KNOWN(ZEND_STR_USERNAME), &value);
+ php_uri_get_password(uri, PHP_URI_COMPONENT_READ_MODE_NORMALIZED_ASCII, &value);
+ zend_hash_add(Z_ARR(normalized), ZSTR_KNOWN(ZEND_STR_PASSWORD), &value);
+ php_uri_get_host(uri, PHP_URI_COMPONENT_READ_MODE_NORMALIZED_ASCII, &value);
+ zend_hash_add(Z_ARR(normalized), ZSTR_KNOWN(ZEND_STR_HOST), &value);
+ php_uri_get_port(uri, PHP_URI_COMPONENT_READ_MODE_NORMALIZED_ASCII, &value);
+ zend_hash_add(Z_ARR(normalized), ZSTR_KNOWN(ZEND_STR_PORT), &value);
+ php_uri_get_path(uri, PHP_URI_COMPONENT_READ_MODE_NORMALIZED_ASCII, &value);
+ zend_hash_add(Z_ARR(normalized), ZSTR_KNOWN(ZEND_STR_PATH), &value);
+ php_uri_get_query(uri, PHP_URI_COMPONENT_READ_MODE_NORMALIZED_ASCII, &value);
+ zend_hash_add(Z_ARR(normalized), ZSTR_KNOWN(ZEND_STR_QUERY), &value);
+ php_uri_get_fragment(uri, PHP_URI_COMPONENT_READ_MODE_NORMALIZED_ASCII, &value);
+ zend_hash_add(Z_ARR(normalized), ZSTR_KNOWN(ZEND_STR_FRAGMENT), &value);
+ zend_hash_str_add(Z_ARR_P(return_value), "normalized", strlen("normalized"), &normalized);
+ zval raw;
+ array_init(&raw);
+ php_uri_get_scheme(uri, PHP_URI_COMPONENT_READ_MODE_RAW, &value);
+ zend_hash_add(Z_ARR(raw), ZSTR_KNOWN(ZEND_STR_SCHEME), &value);
+ php_uri_get_username(uri, PHP_URI_COMPONENT_READ_MODE_RAW, &value);
+ zend_hash_add(Z_ARR(raw), ZSTR_KNOWN(ZEND_STR_USERNAME), &value);
+ php_uri_get_password(uri, PHP_URI_COMPONENT_READ_MODE_RAW, &value);
+ zend_hash_add(Z_ARR(raw), ZSTR_KNOWN(ZEND_STR_PASSWORD), &value);
+ php_uri_get_host(uri, PHP_URI_COMPONENT_READ_MODE_RAW, &value);
+ zend_hash_add(Z_ARR(raw), ZSTR_KNOWN(ZEND_STR_HOST), &value);
+ php_uri_get_port(uri, PHP_URI_COMPONENT_READ_MODE_RAW, &value);
+ zend_hash_add(Z_ARR(raw), ZSTR_KNOWN(ZEND_STR_PORT), &value);
+ php_uri_get_path(uri, PHP_URI_COMPONENT_READ_MODE_RAW, &value);
+ zend_hash_add(Z_ARR(raw), ZSTR_KNOWN(ZEND_STR_PATH), &value);
+ php_uri_get_query(uri, PHP_URI_COMPONENT_READ_MODE_RAW, &value);
+ zend_hash_add(Z_ARR(raw), ZSTR_KNOWN(ZEND_STR_QUERY), &value);
+ php_uri_get_fragment(uri, PHP_URI_COMPONENT_READ_MODE_RAW, &value);
+ zend_hash_add(Z_ARR(raw), ZSTR_KNOWN(ZEND_STR_FRAGMENT), &value);
+ zend_hash_str_add(Z_ARR_P(return_value), "raw", strlen("raw"), &raw);
+ zval from_struct;
+ zval dummy;
+ array_init(&from_struct);
+ if (uri_struct->scheme) {
+ ZVAL_STR_COPY(&dummy, uri_struct->scheme);
+ } else {
+ ZVAL_NULL(&dummy);
+ }
+ zend_hash_add(Z_ARR(from_struct), ZSTR_KNOWN(ZEND_STR_SCHEME), &dummy);
+ if (uri_struct->user) {
+ ZVAL_STR_COPY(&dummy, uri_struct->user);
+ } else {
+ ZVAL_NULL(&dummy);
+ }
+ zend_hash_add(Z_ARR(from_struct), ZSTR_KNOWN(ZEND_STR_USERNAME), &dummy);
+ if (uri_struct->password) {
+ ZVAL_STR_COPY(&dummy, uri_struct->password);
+ } else {
+ ZVAL_NULL(&dummy);
+ }
+ zend_hash_add(Z_ARR(from_struct), ZSTR_KNOWN(ZEND_STR_PASSWORD), &dummy);
+ if (uri_struct->host) {
+ ZVAL_STR_COPY(&dummy, uri_struct->host);
+ } else {
+ ZVAL_NULL(&dummy);
+ }
+ zend_hash_add(Z_ARR(from_struct), ZSTR_KNOWN(ZEND_STR_HOST), &dummy);
+ ZVAL_LONG(&dummy, uri_struct->port);
+ zend_hash_add(Z_ARR(from_struct), ZSTR_KNOWN(ZEND_STR_PORT), &dummy);
+ if (uri_struct->path) {
+ ZVAL_STR_COPY(&dummy, uri_struct->path);
+ } else {
+ ZVAL_NULL(&dummy);
+ }
+ zend_hash_add(Z_ARR(from_struct), ZSTR_KNOWN(ZEND_STR_PATH), &dummy);
+ if (uri_struct->query) {
+ ZVAL_STR_COPY(&dummy, uri_struct->query);
+ } else {
+ ZVAL_NULL(&dummy);
+ }
+ zend_hash_add(Z_ARR(from_struct), ZSTR_KNOWN(ZEND_STR_QUERY), &dummy);
+ if (uri_struct->fragment) {
+ ZVAL_STR_COPY(&dummy, uri_struct->fragment);
+ } else {
+ ZVAL_NULL(&dummy);
+ }
+ zend_hash_add(Z_ARR(from_struct), ZSTR_KNOWN(ZEND_STR_FRAGMENT), &dummy);
+ zend_hash_str_add(Z_ARR_P(return_value), "struct", strlen("struct"), &from_struct);
+
+ php_uri_struct_free(uri_struct);
+ php_uri_free(uri);
+}
+
+ static ZEND_FUNCTION(zend_test_raise_and_throw)
+ {
+ zend_long signo;
+
+ ZEND_PARSE_PARAMETERS_START(1, 1)
+ Z_PARAM_LONG(signo)
+ ZEND_PARSE_PARAMETERS_END();
+
+ if (raise((int) signo) != 0) {
+ zend_throw_error(NULL, "raise() failed");
+ RETURN_THROWS();
+ }
+
+ zend_throw_exception(NULL, "Exception after raise()", 0);
+ }
+
static bool has_opline(zend_execute_data *execute_data)
{
return execute_data
diff --cc ext/zend_test/test_arginfo.h
index 3834ae1fc21,c08feb90046..d605cff30ef
Binary files differ