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(&params[0], queue->signo);
  #ifdef HAVE_STRUCT_SIGINFO_T
  				array_init(&params[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