Commit c7273b9ba89 for php

commit c7273b9ba89cc8c0beaf19c03a77332361e7a89e
Merge: 73e2bad976d 8a94bb3822b
Author: Ilija Tovilo <ilija.tovilo@me.com>
Date:   Wed Oct 21 16:30:04 2026 +0200

    Merge branch 'PHP-8.4' into PHP-8.5

    * PHP-8.4:
      Add NEWS entry
      Avoid state corruption via bailout

diff --cc NEWS
index b2ac87b48cc,12d15e699f1..36221c702ae
--- a/NEWS
+++ b/NEWS
@@@ -124,31 -132,12 +124,16 @@@ PH
    . Fixed use-after-free when an error handler removes a failing zlib.inflate
      filter (GH-24176). (Edmond)

 -22 Oct 2026, PHP 8.4.27
 +22 Oct 2026, PHP 8.5.12

- - BCMath:
-   . Fixed BcMath\Number results that truncate to zero keeping a negative sign
-     and comparing less than zero. (Ilia Alshanetsky)
-
- - BZ2:
-   . Fixed double close of the descriptor and use-after-free of the inner
-     stream when bzopen() is given a stream resource. (Jakub Zelenka)
-
- - CLI
-   . Fix GH-22567 (Windows ZTS CLI SAPI should refresh its TSRMLS cache during
-     request activation). (matyhtf)
-   . Fixed bug GH-23764 (Built-in server leaks a file descriptor on every HEAD
-     request for a static file). (jakubskopal)
-   . Fixed crash in the built-in server when a client is reset before being
-     accepted. (David Carlier)
-   . Fixed the built-in server truncating an error page and leaking its
-     unsent part after a partial socket write. (Ilia Alshanetsky)
-
- - Core
+ - Core:
+   . Fixed GHSA-w8hw-w9fx-j79w, GHSA-6wmf-4rv5-p3rp (Avoid state corruption via
+     bailout). (CVE-2026-109618, CVE-2026-109619) (Arnaud, ilutov, Jakub Zelenka,
+     ndossche, Nicolas Grekas)
 +  . Fixed bug GH-23644 (Optimizer leaves a constant-vs-constant comparison
 +    unfolded, crashing the VM in zval_undefined_cv). (ndossche)
 +  . Fixed OSS-Fuzz 532353396 (assertion failure with static type). (Girgias)
 +  . Fix GH-23662 (Avoid NAN warning in print_r()). (CodedByManish)
    . Fix GH-21999: GC inconsistency with lazy object, var_dump(), and object
      comparison. (Arnaud)
    . Fixed OSS-Fuzz #536440507 (Immutable class incorrect assertion).
@@@ -160,9 -151,34 +145,27 @@@
      function and a later request calls it. (Ilia Alshanetsky)
    . Fixed System V shared memory emulation for Windows attaching segments past
      the end of their mapping. (Ilia Alshanetsky)
 -  . Fixed bug GH-24050 (GC frees an object still held by a resurrected closure
 -    when (object) and use share an array). (David Carlier)
 -  . Fixed use-after-free when a Fiber callable resolves to $this of the method
 -    creating the Fiber ([A::class, 'm'] or "A::m") and that object dies before
 -    the Fiber runs. (Edmond Dantes)
    . Fixed exception thrown by destructor during GC in a Fiber not being rethrown
      into the frame that triggered the GC. (Nicolas Grekas)
 -  . Fixed bug GH-24139 (NULL pointer dereference in php_ini.c when
 -    expand_filepath() fails). (lazerg)

+ - BCMath:
+   . Fixed BcMath\Number results that truncate to zero keeping a negative sign
+     and comparing less than zero. (Ilia Alshanetsky)
+
+ - BZ2:
+   . Fixed double close of the descriptor and use-after-free of the inner
+     stream when bzopen() is given a stream resource. (Jakub Zelenka)
+
+ - CLI:
+   . Fix GH-22567 (Windows ZTS CLI SAPI should refresh its TSRMLS cache during
+     request activation). (matyhtf)
+   . Fixed bug GH-23764 (Built-in server leaks a file descriptor on every HEAD
+     request for a static file). (jakubskopal)
+   . Fixed crash in the built-in server when a client is reset before being
+     accepted. (David Carlier)
+   . Fixed the built-in server truncating an error page and leaking its
+     unsent part after a partial socket write. (Ilia Alshanetsky)
+
  - DOM:
    . Fixed use-after-free when re-constructing a DOMXPath whose php:function
      registrations are freed while still reachable from the cycle collector.
diff --cc Zend/tests/oom_request_parsing.phpt
index 00000000000,a39e562000f..e532acfe244
mode 000000,100644..100644
--- a/Zend/tests/oom_request_parsing.phpt
+++ b/Zend/tests/oom_request_parsing.phpt
@@@ -1,0 -1,130 +1,130 @@@
+ --TEST--
+ Request parsing doesn't reach hard limit
+ --SKIPIF--
+ <?php
+ if (!getenv('TEST_PHP_CGI_EXECUTABLE')) {
+     die("skip php-cgi not available");
+ }
+ if (getenv('SKIP_ASAN')) {
+     die("skip ASan-instrumented realloc() is slow");
+ }
+ ?>
+ --FILE--
+ <?php
+ $base = __DIR__ . '/' . basename(__FILE__, '.php');
+
+ $tiny = $base . '.target.php';
+ file_put_contents($tiny, <<<'TARGET'
+ <?php
+ echo "no OOM\n";
+ TARGET);
+
+ $big = $base . '.big.target.php';
+ file_put_contents($big, "<?php\n" . str_repeat("\$x = 1 + 2;\n", 60000) . "echo \"no OOM\\n\";\n");
+
+ /* The body goes through a file rather than a pipe: some of these are several
+  * megabytes, and the child stops reading as soon as it reports the error. */
+ function oom_post(string $body, string $script, array $ini): void
+ {
+     $bodyFile = tempnam(sys_get_temp_dir(), 'oom_request_parsing');
+     file_put_contents($bodyFile, $body);
+
 -    $cmd = [getenv('TEST_PHP_CGI_EXECUTABLE'), '-n'];
++    $cmd = [getenv('TEST_PHP_CGI_EXECUTABLE'), '-n', '-d', 'fatal_error_backtraces=Off'];
+     foreach ($ini as $entry) {
+         $cmd[] = '-d';
+         $cmd[] = $entry;
+     }
+
+     $p = proc_open(
+         $cmd,
+         [['file', $bodyFile, 'r'], STDOUT, STDERR],
+         $pipes,
+         null,
+         [
+             'REQUEST_METHOD'  => 'POST',
+             'CONTENT_TYPE'    => 'application/x-www-form-urlencoded',
+             'CONTENT_LENGTH'  => (string) strlen($body),
+             'SCRIPT_FILENAME' => $script,
+             'REDIRECT_STATUS' => '1',
+         ]
+     );
+     proc_close($p);
+     unlink($bodyFile);
+ }
+
+ /* Deeply nested variable names, at the default max_input_vars and
+  * max_input_nesting_level. */
+ oom_post(
+     implode('&', array_fill(0, 1000, 'a[]' . str_repeat('[b]', 63) . '=c')),
+     $tiny,
+     ['memory_limit=8M', 'html_errors=0']
+ );
+
+ $ini = ['memory_limit=8M', 'post_max_size=1G', 'max_input_vars=10000000', 'html_errors=0'];
+
+ /* One value with no '&' in it: nothing consumes it, so add_post_vars() never
+  * gets a complete variable to check and only the read loop can notice. */
+ $oneValue = 'a=' . str_repeat('x', 6 * 1024 * 1024);
+
+ /* Many small variables: the limit is reached while registering them. */
+ $manyVars = '';
+ for ($i = 0; $i < 200000; $i++) {
+     $manyVars .= ($i ? '&' : '') . "k$i=v$i";
+ }
+
+ /* A big value followed by many small ones: add_post_var() copies the value
+  * once with estrndup() and again when registering it, and each half fits even
+  * though the pair doesn't. */
+ $mixed = 'a=' . str_repeat('x', 3 * 1024 * 1024);
+ for ($i = 0; $i < 100000; $i++) {
+     $mixed .= "&k$i=v$i";
+ }
+
+ foreach ([$oneValue, $manyVars, $mixed] as $body) {
+     foreach ([$tiny, $big] as $script) {
+         oom_post($body, $script, $ini);
+     }
+ }
+ ?>
+ --CLEAN--
+ <?php
+ $base = __DIR__ . '/' . basename(__FILE__, '.clean.php');
+ @unlink($base . '.target.php');
+ @unlink($base . '.big.target.php');
+ ?>
+ --EXPECTF--
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc Zend/zend.c
index acd105ce43e,32028c9c5a0..b86601cd16e
--- a/Zend/zend.c
+++ b/Zend/zend.c
@@@ -1490,23 -1464,13 +1495,23 @@@ ZEND_API ZEND_COLD void zend_error_zstr

  		/* This is very inefficient for a large number of errors.
  		 * Use pow2 realloc if it becomes a problem. */
 -		EG(num_errors)++;
 -		EG(errors) = erealloc(EG(errors), sizeof(zend_error_info*) * EG(num_errors));
 -		EG(errors)[EG(num_errors)-1] = info;
 +		uint32_t new_num_errors = EG(num_errors) + 1;
 +		EG(errors) = safe_erealloc(EG(errors), new_num_errors, sizeof(zend_error_info*), 0);
 +		EG(errors)[EG(num_errors)] = info;
 +		EG(num_errors) = new_num_errors;
 +
 +		/* Do not process non-fatal recorded error */
 +		if (!(type & E_FATAL_ERRORS) || (type & E_DONT_BAIL)) {
 +			return;
 +		}
  	}

 +	// Always clear the last backtrace.
 +	zval_ptr_dtor(&EG(last_fatal_error_backtrace));
 +	ZVAL_UNDEF(&EG(last_fatal_error_backtrace));
 +
  	/* Report about uncaught exception in case of fatal errors */
- 	if (EG(exception)) {
+ 	if (EG(exception) && EXPECTED(EG(delayed_oom).state != ZEND_OOM_STATE_HANDLING)) {
  		zend_execute_data *ex;
  		const zend_op *opline;

diff --cc Zend/zend_alloc.c
index c5ab74c70bb,3cbdd1e5f5d..ba5a70287a4
--- a/Zend/zend_alloc.c
+++ b/Zend/zend_alloc.c
@@@ -305,21 -316,10 +316,22 @@@ struct _zend_mm_heap
  		size_t     (*_gc)(void);
  		void       (*_shutdown)(bool full, bool silent);
  	} custom_heap;
 -	HashTable *tracked_allocs;
 +	union {
 +		HashTable *tracked_allocs;
 +		struct {
 +			bool    poison_alloc;
 +			uint8_t poison_alloc_value;
 +			bool    poison_free;
 +			uint8_t poison_free_value;
 +			uint8_t padding;
 +			bool    check_freelists_on_shutdown;
 +		} debug;
 +	};
+ 	uint64_t max_allocs;
  #endif
 +#if ZEND_DEBUG
  	pid_t pid;
 +#endif
  	zend_random_bytes_insecure_state rand_state;
  };

diff --cc Zend/zend_alloc.h
index 264e13848d1,315b9f3dcda..0fce08d6c1d
--- a/Zend/zend_alloc.h
+++ b/Zend/zend_alloc.h
@@@ -317,8 -368,17 +369,19 @@@ struct _zend_mm_storage
  ZEND_API zend_mm_storage *zend_mm_get_storage(zend_mm_heap *heap);
  ZEND_API zend_mm_heap *zend_mm_startup_ex(const zend_mm_handlers *handlers, void *data, size_t data_size);

 +ZEND_API void zend_mm_refresh_key_child(zend_mm_heap *heap);
 +
+ ZEND_API ZEND_COLD void zend_mm_delayed_error_or_panic(bool delay, const char *format, ...) ZEND_ATTRIBUTE_FORMAT(printf, 2, 3);
+ ZEND_API void ZEND_FASTCALL zend_handle_delayed_oom(void);
+ ZEND_COLD void zend_mm_oom_info_dtor(zend_oom_info *info);
+
+ /* Exit status of a process that stopped because of ZEND_MM_MAX_ALLOCS, so that
+  * it can be told apart from an ordinary fatal error. */
+ #define ZEND_MM_MAX_ALLOCS_EXIT_STATUS 121
+
+ ZEND_API void zend_mm_set_max_allocs(uint64_t max_allocs);
+ ZEND_API bool zend_mm_max_allocs_hit(void);
+
  /*

  // The following example shows how to use zend_mm_heap API with custom storage
diff --cc Zend/zend_execute_API.c
index 076471c4e65,30eb6ec8af3..e3c9fc49c90
--- a/Zend/zend_execute_API.c
+++ b/Zend/zend_execute_API.c
@@@ -428,6 -428,13 +435,14 @@@ ZEND_API void zend_shutdown_executor_va
  	}

  	zend_objects_store_free_object_storage(&EG(objects_store), fast_shutdown);
+
+ 	/* may be used by zend_error_zstr_at before init_executor */
++	ZVAL_UNDEF(&EG(last_fatal_error_backtrace));
+ 	EG(record_errors) = false;
+ 	EG(num_errors) = 0;
+ 	EG(errors) = NULL;
+ 	EG(error_handling) = EH_NORMAL;
+ 	EG(exception) = NULL;
  }

  void shutdown_executor(void) /* {{{ */
diff --cc Zend/zend_hash.c
index b8c33548f93,3d88d179f7c..0ed782ddd98
--- a/Zend/zend_hash.c
+++ b/Zend/zend_hash.c
@@@ -292,7 -356,21 +355,21 @@@ ZEND_API HashTable* ZEND_FASTCALL _zend
  	return ht;
  }

+ ZEND_API HashTable* ZEND_FASTCALL _zend_fnew_array(uint32_t nSize)
+ {
+ 	HashTable *ht = femalloc(sizeof(HashTable), /* fallible */ true);
+ 	if (UNEXPECTED(ht == NULL)) {
+ 		return NULL;
+ 	}
+
+ 	if (UNEXPECTED(_zend_hash_finit_int(ht, nSize, ZVAL_PTR_DTOR, /* persistent */ false, /* fallible */ true) == FAILURE)) {
+ 		efree(ht);
+ 		return NULL;
+ 	}
+ 	return ht;
+ }
+
 -ZEND_API HashTable* ZEND_FASTCALL zend_new_pair(zval *val1, zval *val2)
 +ZEND_API HashTable* ZEND_FASTCALL zend_new_pair(const zval *val1, const zval *val2)
  {
  	zval *zv;
  	HashTable *ht = emalloc(sizeof(HashTable));
diff --cc Zend/zend_hash.h
index 57020bbcad0,3c37cc064e9..ecbcedffcfa
--- a/Zend/zend_hash.h
+++ b/Zend/zend_hash.h
@@@ -26,19 -26,17 +26,20 @@@
  #include "zend_string.h"
  #include "zend_sort.h"

 -#define HASH_KEY_IS_STRING 1
 -#define HASH_KEY_IS_LONG 2
 -#define HASH_KEY_NON_EXISTENT 3
 -
 -#define HASH_UPDATE 			(1<<0)
 -#define HASH_ADD				(1<<1)
 -#define HASH_UPDATE_INDIRECT	(1<<2)
 -#define HASH_ADD_NEW			(1<<3)
 -#define HASH_ADD_NEXT			(1<<4)
 -#define HASH_LOOKUP				(1<<5)
 +typedef enum {
 +	HASH_KEY_IS_STRING = 1,
 +	HASH_KEY_IS_LONG,
 +	HASH_KEY_NON_EXISTENT
 +} zend_hash_key_type;
 +
 +#define HASH_UPDATE 			(1<<0) /* Create new entry, or update the existing one. */
 +#define HASH_ADD				(1<<1) /* Create new entry, or fail if it exists. */
 +#define HASH_UPDATE_INDIRECT	(1<<2) /* If the given ht entry is an indirect zval, unwrap it before writing to it. \
 +										* When used with HASH_ADD, writing is allowed if the target zval is IS_UNDEF. */
 +#define HASH_ADD_NEW			(1<<3) /* Used when the offset is known not to exist. */
 +#define HASH_ADD_NEXT			(1<<4) /* Append to an array. (e.g. $array[] = 42;) */
 +#define HASH_LOOKUP				(1<<5) /* Look up an existing entry, or create one with a NULL value. */
+ #define HASH_FALLIBLE			(1<<6) /* Use fallible allocation. Return FAILURE or HASH_NOMEM when allocation fails (for functions with zend_result or zval* types, respectively). */

  #define HASH_FLAG_CONSISTENCY      ((1<<0) | (1<<1))
  #define HASH_FLAG_PACKED           (1<<2)
@@@ -343,12 -369,13 +372,13 @@@ ZEND_API void ZEND_FASTCALL zend_hash_r
  #endif

  ZEND_API HashTable* ZEND_FASTCALL _zend_new_array_0(void);
+ ZEND_API HashTable* ZEND_FASTCALL zend_new_array_singleton(void);
  ZEND_API HashTable* ZEND_FASTCALL _zend_new_array(uint32_t size);
 -ZEND_API HashTable* ZEND_FASTCALL zend_new_pair(zval *val1, zval *val2);
 +ZEND_API HashTable* ZEND_FASTCALL zend_new_pair(const zval *val1, const zval *val2);
  ZEND_API uint32_t zend_array_count(HashTable *ht);
 -ZEND_API HashTable* ZEND_FASTCALL zend_array_dup(HashTable *source);
 +ZEND_API HashTable* ZEND_FASTCALL zend_array_dup(const HashTable *source);
  ZEND_API void ZEND_FASTCALL zend_array_destroy(HashTable *ht);
 -ZEND_API HashTable* zend_array_to_list(HashTable *source);
 +ZEND_API HashTable* zend_array_to_list(const HashTable *source);
  ZEND_API void ZEND_FASTCALL zend_symtable_clean(HashTable *ht);
  ZEND_API HashTable* ZEND_FASTCALL zend_symtable_to_proptable(HashTable *ht);
  ZEND_API HashTable* ZEND_FASTCALL zend_proptable_to_symtable(HashTable *ht, bool always_duplicate);
diff --cc Zend/zend_vm_def.h
index 21074bc688d,df255c5a868..533a7d342a1
--- a/Zend/zend_vm_def.h
+++ b/Zend/zend_vm_def.h
@@@ -10530,14 -10424,12 +10537,17 @@@ ZEND_VM_DEFINE_OP(137, ZEND_OP_DATA)
  ZEND_VM_HELPER(zend_interrupt_helper, ANY, ANY)
  {
  	zend_atomic_bool_store_ex(&EG(vm_interrupt), false);
 +#if ZEND_VM_KIND == ZEND_VM_KIND_TAILCALL
 +	/* opline is &call_interrupt_op. Load orig opline. */
 +	LOAD_OPLINE();
 +#else
  	SAVE_OPLINE();
 +#endif
  	if (zend_atomic_bool_load_ex(&EG(timed_out))) {
  		zend_timeout();
+ 	} else if (ZEND_HAS_OOM()) {
+ 		zend_handle_delayed_oom();
+ 		HANDLE_EXCEPTION();
  	} else if (zend_interrupt_function) {
  		zend_interrupt_function(execute_data);
  		if (EG(exception)) {
diff --cc Zend/zend_vm_execute.h
index 2bc51f2841a,4b313f20cd3..df733f525d5
Binary files differ
diff --cc ext/bz2/bz2.c
index 319518f2ace,f272e6d1741..b67bb8dbd7e
--- a/ext/bz2/bz2.c
+++ b/ext/bz2/bz2.c
@@@ -515,9 -497,21 +515,12 @@@ PHP_FUNCTION(bzcompress
  	dest_len = (unsigned int) chunk_len;

  	/* Allocate the destination buffer */
- 	dest = zend_string_alloc(dest_len, 0);
+ 	dest = zend_string_falloc(dest_len, 0, /* fallible */ true);
+ 	if (!dest) {
+ 		RETURN_OOM();
+ 	}

 -	/* Handle the optional arguments */
 -	if (argc > 1) {
 -		block_size = zblock_size;
 -	}
 -
 -	if (argc > 2) {
 -		work_factor = zwork_factor;
 -	}
 -
 -	error = BZ2_bzBuffToBuffCompress(ZSTR_VAL(dest), &dest_len, source, source_len, block_size, 0, work_factor);
 +	int error = BZ2_bzBuffToBuffCompress(ZSTR_VAL(dest), &dest_len, source, source_len, block_size, 0, work_factor);
  	if (error != BZ_OK) {
  		zend_string_efree(dest);
  		RETURN_LONG(error);
diff --cc ext/curl/tests/oom_curl_exec.phpt
index 00000000000,afbb948fda9..466842e7bbe
mode 000000,100644..100644
--- a/ext/curl/tests/oom_curl_exec.phpt
+++ b/ext/curl/tests/oom_curl_exec.phpt
@@@ -1,0 -1,37 +1,37 @@@
+ --TEST--
+ curl_exec() doesn't reach hard limit
+ --EXTENSIONS--
+ curl
+ --FILE--
+ <?php
+ require __DIR__ . '/server.inc';
+ $host = curl_cli_server_start();
+
+ $extension = ini_get('extension_dir') . '/' . (PHP_OS_FAMILY === 'Windows' ? 'php_' : '') . 'curl.' . PHP_SHLIB_SUFFIX;
+ $args = [];
+ if (is_file($extension)) {
+     $args[] = '-d';
+     $args[] = "extension=$extension";
+ }
+
+ $code = <<<'PHP'
+ $ch = curl_init($argv[1]);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_exec($ch);
+ echo "no OOM\n";
+ PHP;
+
+ $php = getenv('TEST_PHP_EXECUTABLE') ?: PHP_BINARY;
+ $p = proc_open(
 -    [$php, '-n', ...$args, '-d', 'memory_limit=8M', '-r', $code, "http://$host/get.inc?test=bigbody&mib=16"],
++    [$php, '-n', ...$args, '-d', 'memory_limit=8M', '-d', 'fatal_error_backtraces=Off', '-r', $code, "http://$host/get.inc?test=bigbody&mib=16"],
+     [1 => ['pipe', 'w'], 2 => ['pipe', 'w']],
+     $pipes
+ );
+ echo stream_get_contents($pipes[1]);
+ echo stream_get_contents($pipes[2]);
+ fclose($pipes[1]);
+ fclose($pipes[2]);
+ proc_close($p);
+ ?>
+ --EXPECTF--
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/curl/tests/oom_curl_multi_exec.phpt
index 00000000000,4a233166e70..d2e19a7e9c4
mode 000000,100644..100644
--- a/ext/curl/tests/oom_curl_multi_exec.phpt
+++ b/ext/curl/tests/oom_curl_multi_exec.phpt
@@@ -1,0 -1,43 +1,43 @@@
+ --TEST--
+ curl_multi_exec() doesn't reach hard limit
+ --EXTENSIONS--
+ curl
+ --FILE--
+ <?php
+ require __DIR__ . '/server.inc';
+ $host = curl_cli_server_start();
+
+ $extension = ini_get('extension_dir') . '/' . (PHP_OS_FAMILY === 'Windows' ? 'php_' : '') . 'curl.' . PHP_SHLIB_SUFFIX;
+ $args = [];
+ if (is_file($extension)) {
+     $args[] = '-d';
+     $args[] = "extension=$extension";
+ }
+
+ $code = <<<'PHP'
+ $mh = curl_multi_init();
+ $ch = curl_init($argv[1]);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_BUFFERSIZE, 10 * 1024 * 1024);
+ curl_multi_add_handle($mh, $ch);
+ do {
+     curl_multi_exec($mh, $running);
+     curl_multi_select($mh, 0.05);
+ } while ($running);
+ echo "no OOM\n";
+ PHP;
+
+ $php = getenv('TEST_PHP_EXECUTABLE') ?: PHP_BINARY;
+ $p = proc_open(
 -    [$php, '-n', ...$args, '-d', 'memory_limit=8M', '-r', $code, "http://$host/get.inc?test=bigbody&mib=16"],
++    [$php, '-n', ...$args, '-d', 'memory_limit=8M', '-d', 'fatal_error_backtraces=Off', '-r', $code, "http://$host/get.inc?test=bigbody&mib=16"],
+     [1 => ['pipe', 'w'], 2 => ['pipe', 'w']],
+     $pipes
+ );
+ echo stream_get_contents($pipes[1]);
+ echo stream_get_contents($pipes[2]);
+ fclose($pipes[1]);
+ fclose($pipes[2]);
+ proc_close($p);
+ ?>
+ --EXPECTF--
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/date/lib/parse_date.c
index ac03ebbe39d,31103a81fa0..9a9ae5523eb
--- a/ext/date/lib/parse_date.c
+++ b/ext/date/lib/parse_date.c
@@@ -1,4 -1,4 +1,4 @@@
- /* Generated by re2c 1.0.3 on Sun Oct  4 18:31:01 2026 */
 -/* Generated by re2c 1.0.3 on Wed Sep 30 23:15:59 2026 */
++/* Generated by re2c 1.0.3 on Thu Oct  1 14:54:40 2026 */
  #line 1 "ext/date/lib/parse_date.re"
  /*
   * The MIT License (MIT)
diff --cc ext/date/lib/parse_iso_intervals.c
index 5dcbfe35063,bbc9cd2337f..aef0838909b
--- a/ext/date/lib/parse_iso_intervals.c
+++ b/ext/date/lib/parse_iso_intervals.c
@@@ -1,4 -1,4 +1,4 @@@
- /* Generated by re2c 1.0.3 on Mon Sep 15 10:40:09 2025 */
 -/* Generated by re2c 1.0.3 on Wed Sep 30 15:20:07 2026 */
++/* Generated by re2c 1.0.3 on Thu Oct  1 14:55:09 2026 */
  #line 1 "ext/date/lib/parse_iso_intervals.re"
  /*
   * The MIT License (MIT)
diff --cc ext/dom/tests/oom_xpath_quote_concat.phpt
index 00000000000,00000000000..69e6f1c186a
new file mode 100644
--- /dev/null
+++ b/ext/dom/tests/oom_xpath_quote_concat.phpt
@@@ -1,0 -1,0 +1,15 @@@
++--TEST--
++DOMXPath::quote() with both quote kinds doesn't reach hard memory limit
++--EXTENSIONS--
++dom
++--INI--
++memory_limit=8M
++--FILE--
++<?php
++/* A string holding both quote kinds takes the concat() path */
++DOMXPath::quote(str_repeat("'\"", 2 * 1024 * 1024));
++
++echo "not reached\n";
++?>
++--EXPECTF--
++Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/dom/tests/oom_xpath_quote_single.phpt
index 00000000000,00000000000..9b21f00ef28
new file mode 100644
--- /dev/null
+++ b/ext/dom/tests/oom_xpath_quote_single.phpt
@@@ -1,0 -1,0 +1,15 @@@
++--TEST--
++DOMXPath::quote() with one quote kind doesn't reach hard memory limit
++--EXTENSIONS--
++dom
++--INI--
++memory_limit=8M
++memory_hard_limit_percent=125
++--FILE--
++<?php
++DOMXPath::quote(str_repeat("a", 4 * 1024 * 1024));
++
++echo "not reached\n";
++?>
++--EXPECTF--
++Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/dom/xpath.c
index bb0207e526a,c3d15fe44dd..f5ed6dc4a65
--- a/ext/dom/xpath.c
+++ b/ext/dom/xpath.c
@@@ -406,24 -378,27 +408,34 @@@ static void php_xpath_eval(INTERNAL_FUN

  						node = php_dom_create_fake_namespace_decl(nsparent, original, &child, parent_intern);
  					} else {
 -						php_dom_create_object(node, &child, &intern->dom);
 +						dom_object *parent = dom_xpath_intern_for_doc(intern, node->doc);
 +						php_dom_create_object(node, &child, parent);
  					}
  					add_next_index_zval(&retval, &child);
+ 					if (ZEND_HAS_OOM()) {
+ 						goto oom_nodeset;
+ 					}
  				}
 +				release_array = true;
  			} else {
  				ZVAL_EMPTY_ARRAY(&retval);
  			}
 -			php_dom_create_iterator(return_value, DOM_NODELIST, modern);
 +
 +			object_init_ex(return_value, dom_get_nodelist_ce(modern));
  			nodeobj = Z_DOMOBJ_P(return_value);
 -			dom_xpath_iter(&retval, nodeobj);
 +			dom_nnodemap_object *mapptr = nodeobj->ptr;
 +
 +			mapptr->array = Z_ARR(retval);
 +			mapptr->release_array = release_array;
 +			mapptr->handler = &php_dom_obj_map_nodeset;
  			break;
+
+ oom_nodeset:
+ 			zval_ptr_dtor(&retval);
+ 			xmlXPathFreeObject(xpathobjp);
+ 			zval_ptr_dtor(return_value);
+ 			ZVAL_NULL(return_value);
+ 			RETURN_OOM();
  		}

  		case XPATH_BOOLEAN:
@@@ -542,14 -517,14 +554,20 @@@ PHP_METHOD(DOMXPath, quote)
  		RETURN_THROWS();
  	}
  	if (memchr(input, '\'', input_len) == NULL) {
--		zend_string *const output = zend_string_safe_alloc(1, input_len, 2, false);
++		zend_string *const output = zend_string_safe_falloc(1, input_len, 2, false, /* fallible */ true);
++		if (UNEXPECTED(!output)) {
++			RETURN_OOM();
++		}
  		ZSTR_VAL(output)[0] = '\'';
  		memcpy(ZSTR_VAL(output) + 1, input, input_len);
  		ZSTR_VAL(output)[input_len + 1] = '\'';
  		ZSTR_VAL(output)[input_len + 2] = '\0';
 -		RETURN_STR(output);
 +		RETURN_NEW_STR(output);
  	} else if (memchr(input, '"', input_len) == NULL) {
--		zend_string *const output = zend_string_safe_alloc(1, input_len, 2, false);
++		zend_string *const output = zend_string_safe_falloc(1, input_len, 2, false, /* fallible */ true);
++		if (UNEXPECTED(!output)) {
++			RETURN_OOM();
++		}
  		ZSTR_VAL(output)[0] = '"';
  		memcpy(ZSTR_VAL(output) + 1, input, input_len);
  		ZSTR_VAL(output)[input_len + 1] = '"';
@@@ -573,10 -548,10 +591,14 @@@
  			smart_str_appendc(&output, quote_method);
  			ptr += bytes_until_quote;
  			smart_str_appendc(&output, ',');
++			if (ZEND_HAS_OOM()) {
++				smart_str_free(&output);
++				RETURN_OOM();
++			}
  		}
  		ZEND_ASSERT(ptr == end);
 -		ZSTR_VAL(output.s)[output.s->len - 1] = ')';
 -		RETURN_STR(smart_str_extract(&output));
 +		ZSTR_VAL(output.s)[ZSTR_LEN(output.s) - 1] = ')';
 +		RETURN_NEW_STR(smart_str_extract(&output));
  	}
  }
  /* }}} */
diff --cc ext/exif/exif.c
index a2eb8259ac7,a72acb4f2c5..b63c29751e5
--- a/ext/exif/exif.c
+++ b/ext/exif/exif.c
@@@ -2461,8 -2455,9 +2468,8 @@@ static void exif_iif_free(image_info_ty

  /* {{{ add_assoc_image_info
   * Add image_info to associative array value. */
- static void add_assoc_image_info(zval *value, int sub_array, image_info_type *image_info, int section_index)
+ static zend_result add_assoc_image_info(zval *value, int sub_array, image_info_type *image_info, int section_index)
  {
 -	char buffer[64], uname[64];
  	int idx = 0, unknown = 0;

  	if (!image_info->info_list[section_index].count) {
@@@ -4211,10 -4250,13 +4260,13 @@@ static bool exif_process_IFD_in_TIFF_im
  #ifdef EXIF_DEBUG
  								exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "%s THUMBNAIL @0x%04X + 0x%04X", ImageInfo->Thumbnail.data ? "Ignore" : "Read", ImageInfo->Thumbnail.offset, ImageInfo->Thumbnail.size);
  #endif
- 								if (!ImageInfo->Thumbnail.data) {
- 									ImageInfo->Thumbnail.data = safe_emalloc(ImageInfo->Thumbnail.size, 1, 0);
+ 								if (!ImageInfo->Thumbnail.data && exif_thumbnail_in_file(ImageInfo)) {
+ 									ImageInfo->Thumbnail.data = safe_femalloc(ImageInfo->Thumbnail.size, 1, 0, /* fallible */ true);
+ 									if (!ImageInfo->Thumbnail.data) {
+ 										return false;
+ 									}
  									php_stream_seek(ImageInfo->infile, ImageInfo->Thumbnail.offset, SEEK_SET);
 -									fgot = exif_read_from_stream_file_looped(ImageInfo->infile, ImageInfo->Thumbnail.data, ImageInfo->Thumbnail.size);
 +									size_t fgot = exif_read_from_stream_file_looped(ImageInfo->infile, ImageInfo->Thumbnail.data, ImageInfo->Thumbnail.size);
  									if (fgot != ImageInfo->Thumbnail.size) {
  										EXIF_ERRLOG_THUMBEOF(ImageInfo)
  										efree(ImageInfo->Thumbnail.data);
@@@ -4251,10 -4293,14 +4303,14 @@@
  #ifdef EXIF_DEBUG
  					exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "%s THUMBNAIL @0x%04X + 0x%04X", ImageInfo->Thumbnail.data ? "Ignore" : "Read", ImageInfo->Thumbnail.offset, ImageInfo->Thumbnail.size);
  #endif
- 					if (!ImageInfo->Thumbnail.data && ImageInfo->Thumbnail.offset && ImageInfo->Thumbnail.size && ImageInfo->read_thumbnail) {
- 						ImageInfo->Thumbnail.data = safe_emalloc(ImageInfo->Thumbnail.size, 1, 0);
+ 					if (!ImageInfo->Thumbnail.data && ImageInfo->Thumbnail.offset && ImageInfo->Thumbnail.size && ImageInfo->read_thumbnail
+ 					 && exif_thumbnail_in_file(ImageInfo)) {
+ 						ImageInfo->Thumbnail.data = safe_femalloc(ImageInfo->Thumbnail.size, 1, 0, /* fallible */ true);
+ 						if (!ImageInfo->Thumbnail.data) {
+ 							return false;
+ 						}
  						php_stream_seek(ImageInfo->infile, ImageInfo->Thumbnail.offset, SEEK_SET);
 -						fgot = exif_read_from_stream_file_looped(ImageInfo->infile, ImageInfo->Thumbnail.data, ImageInfo->Thumbnail.size);
 +						size_t fgot = exif_read_from_stream_file_looped(ImageInfo->infile, ImageInfo->Thumbnail.data, ImageInfo->Thumbnail.size);
  						if (fgot != ImageInfo->Thumbnail.size) {
  							EXIF_ERRLOG_THUMBEOF(ImageInfo)
  							efree(ImageInfo->Thumbnail.data);
@@@ -4948,19 -4831,17 +5016,19 @@@ PHP_FUNCTION(exif_thumbnail
  	exif_error_docref(NULL EXIFERR_CC, &ImageInfo, E_NOTICE, "Returning thumbnail(%d)", ImageInfo.Thumbnail.size);
  #endif

- 	RETVAL_STRINGL(ImageInfo.Thumbnail.data, ImageInfo.Thumbnail.size);
+ 	RETVAL_FSTRINGL(ImageInfo.Thumbnail.data, ImageInfo.Thumbnail.size);
 -	if (arg_c >= 3) {
 -		if (!ImageInfo.Thumbnail.width || !ImageInfo.Thumbnail.height) {
 -			if (!exif_scan_thumbnail(&ImageInfo)) {
 -				ImageInfo.Thumbnail.width = ImageInfo.Thumbnail.height = 0;
 -			}
 +	if ((z_width || z_height) && (!ImageInfo.Thumbnail.width || !ImageInfo.Thumbnail.height)) {
 +		if (!exif_scan_thumbnail(&ImageInfo)) {
 +			ImageInfo.Thumbnail.width = ImageInfo.Thumbnail.height = 0;
  		}
 +	}
 +	if (z_width) {
  		ZEND_TRY_ASSIGN_REF_LONG(z_width,  ImageInfo.Thumbnail.width);
 +	}
 +	if (z_height) {
  		ZEND_TRY_ASSIGN_REF_LONG(z_height, ImageInfo.Thumbnail.height);
  	}
 -	if (arg_c >= 4)	{
 +	if (z_imagetype) {
  		ZEND_TRY_ASSIGN_REF_LONG(z_imagetype, ImageInfo.Thumbnail.filetype);
  	}

diff --cc ext/fileinfo/libmagic.patch
index 4eb7ccd336f,ed95a40221c..041562d48a9
--- a/ext/fileinfo/libmagic.patch
+++ b/ext/fileinfo/libmagic.patch
@@@ -1,15 -1,6 +1,15 @@@
  diff -u libmagic.orig/apprentice.c libmagic/apprentice.c
 ---- libmagic.orig/apprentice.c	2023-07-17 16:38:35.000000000 +0200
 -+++ libmagic/apprentice.c	2026-09-30 22:55:28.668770846 +0200
 +--- libmagic.orig/apprentice.c	2024-11-27 16:37:00.000000000 +0100
- +++ libmagic/apprentice.c	2025-02-09 02:25:02.364884555 +0100
+++++ libmagic/apprentice.c	2026-10-01 14:49:48.677051351 +0200
 +@@ -32,7 +32,7 @@
 + #include "file.h"
 +
 + #ifndef	lint
 +-FILE_RCSID("@(#)$File: apprentice.c,v 1.356 2024/11/27 15:37:00 christos Exp $")
 ++FILE_RCSID("@(#)$File: apprentice.c,v 1.355 2024/11/25 22:31:53 christos Exp $")
 + #endif	/* lint */
 +
 + #include "magic.h"
  @@ -48,7 +48,9 @@
   #ifdef QUICK
   #include <sys/mman.h>
@@@ -311,29 -283,12 +311,29 @@@
   		fn = p;
   	}

 --	free(mfn);
  +	efree(mfn);
 -
 ++
   	if (errs == -1) {
   		for (i = 0; i < MAGIC_SETS; i++) {
 -@@ -1289,7 +1240,7 @@
 + 			mlist_free(ms->mlist[i]);
 +@@ -1143,6 +1065,8 @@
 + 		mpa.lineno = mpb.lineno = 0;
 + 		int x = memcmp(&mpa, &mpb, sizeof(mpa));
 + 		if (x == 0) {
 ++			/* For PHP BC: don't warn */
 ++#if 0
 + 			// Don't warn for DER
 + 			if (mpa.type == FILE_DER)
 + 				return 0;
- @@ -1151,6 +1075,7 @@
++@@ -1150,6 +1074,7 @@
++ 			    ma->mp->desc);
 + 			file_mdump(ma->mp);
 + 			file_mdump(mb->mp);
 ++#endif
 + 			return 0;
 + 		}
 + 		return x > 0 ? -1 : 1;
-  	}
 +@@ -1303,7 +1228,7 @@

   		size_t incr = mset[i].max + ALLOC_INCR;
   		if ((mp = CAST(struct magic_entry *,
@@@ -1055,8 -976,8 +1055,8 @@@ diff -u libmagic.orig/buffer.c libmagic
   		goto out;
   	}
  diff -u libmagic.orig/cdf.c libmagic/cdf.c
 ---- libmagic.orig/cdf.c	2022-09-24 22:56:49.000000000 +0200
 -+++ libmagic/cdf.c	2026-10-02 19:35:01.765068091 +0200
 +--- libmagic.orig/cdf.c	2024-11-25 22:24:59.000000000 +0100
- +++ libmagic/cdf.c	2025-02-09 01:25:00.187641434 +0100
+++++ libmagic/cdf.c	2026-10-02 19:35:44.175918710 +0200
  @@ -43,7 +43,9 @@
   #include <err.h>
   #endif
@@@ -1067,44 -988,48 +1067,51 @@@
   #include <string.h>
   #include <time.h>
   #include <ctype.h>
- @@ -91,44 +93,9 @@
-  			    CDF_TOLE8(CAST(uint64_t, x))))
-  #define CDF_GETUINT32(x, y)	cdf_getuint32(x, y)
+ @@ -95,39 +97,39 @@
+  #define CDF_REALLOC(p, n) cdf_realloc(__FILE__, __LINE__, (p), (n))
+  #define CDF_CALLOC(n, u) cdf_calloc(__FILE__, __LINE__, (n), (u))

- -#define CDF_MALLOC(n) cdf_malloc(__FILE__, __LINE__, (n))
- -#define CDF_REALLOC(p, n) cdf_realloc(__FILE__, __LINE__, (p), (n))
- -#define CDF_CALLOC(n, u) cdf_calloc(__FILE__, __LINE__, (n), (u))
- -
  -
- -/*ARGSUSED*/
+  /*ARGSUSED*/
 - static void *
 +-file_private void *
  -cdf_malloc(const char *file __attribute__((__unused__)),
  -    size_t line __attribute__((__unused__)), size_t n)
- -{
- -	DPRINTF(("%s,%" SIZE_T_FORMAT "u: %s %" SIZE_T_FORMAT "u\n",
- -	    file, line, __func__, n));
+++static void *
+ +cdf_malloc(const char *file,
+ +    size_t line, size_t n)
+  {
+  	DPRINTF(("%s,%" SIZE_T_FORMAT "u: %s %" SIZE_T_FORMAT "u\n",
+  	    file, line, __func__, n));
  -	if (n == 0)
  -	    n++;
  -	return malloc(n);
- -}
- -
- -/*ARGSUSED*/
+ +	return femalloc(n, /* fallible */ true);
+  }
+
+  /*ARGSUSED*/
 - static void *
 +-file_private void *
  -cdf_realloc(const char *file __attribute__((__unused__)),
  -    size_t line __attribute__((__unused__)), void *p, size_t n)
- -{
- -	DPRINTF(("%s,%" SIZE_T_FORMAT "u: %s %" SIZE_T_FORMAT "u\n",
- -	    file, line, __func__, n));
+++static void *
+ +cdf_realloc(const char *file,
+ +    size_t line, void *p, size_t n)
+  {
+  	DPRINTF(("%s,%" SIZE_T_FORMAT "u: %s %" SIZE_T_FORMAT "u\n",
+  	    file, line, __func__, n));
  -	return realloc(p, n);
- -}
- -
- -/*ARGSUSED*/
+ +	return safe_ferealloc(p, 1, n, 0, /* fallible */ true);
+  }
+
+  /*ARGSUSED*/
 - static void *
 +-file_private void *
  -cdf_calloc(const char *file __attribute__((__unused__)),
  -    size_t line __attribute__((__unused__)), size_t n, size_t u)
- -{
- -	DPRINTF(("%s,%" SIZE_T_FORMAT "u: %s %" SIZE_T_FORMAT "u %"
- -	    SIZE_T_FORMAT "u\n", file, line, __func__, n, u));
+++static void *
+ +cdf_calloc(const char *file,
+ +    size_t line, size_t n, size_t u)
+  {
+  	DPRINTF(("%s,%" SIZE_T_FORMAT "u: %s %" SIZE_T_FORMAT "u %"
+  	    SIZE_T_FORMAT "u\n", file, line, __func__, n, u));
  -	if (n == 0)
  -	    n++;
  -	return calloc(n, u);
@@@ -1124,8 -1051,8 +1133,8 @@@
   	scn->sst_tab = NULL;
   	return -1;
   }
- @@ -342,9 +309,11 @@
+ @@ -342,9 +344,11 @@
 - static size_t
 + file_private size_t
   cdf_check_stream(const cdf_stream_t *sst, const cdf_header_t *h)
   {
  +#ifndef NDEBUG
@@@ -1136,10 -1063,10 +1145,10 @@@
   	return sst->sst_ss;
   }

- @@ -367,11 +336,11 @@
+ @@ -367,11 +371,11 @@
   }

 - static ssize_t
 + file_private ssize_t
  -cdf_read(const cdf_info_t *info, off_t off, void *buf, size_t len)
  +cdf_read(const cdf_info_t *info, zend_off_t off, void *buf, size_t len)
   {
diff --cc ext/filter/filter.c
index 608d2172a7a,7e1a0311099..c0682037c8c
--- a/ext/filter/filter.c
+++ b/ext/filter/filter.c
@@@ -365,11 -339,14 +370,14 @@@ static unsigned int php_sapi_filter(in

  	if (val_len) {
  		/* Register mangled variable */
+ 		if (UNEXPECTED(!ZVAL_FSTRINGL(&new_var, *val, val_len))) {
+ 			return 0;
+ 		}
  		if (IF_G(default_filter) != FILTER_UNSAFE_RAW) {
- 			ZVAL_STRINGL(&new_var, *val, val_len);
 -			php_zval_filter(&new_var, IF_G(default_filter), IF_G(default_filter_flags), NULL, NULL, 0);
 +			php_zval_filter(&new_var, IF_G(default_filter), IF_G(default_filter_flags), NULL, NULL);
- 		} else {
- 			ZVAL_STRINGL(&new_var, *val, val_len);
+ 			if (ZEND_HAS_OOM()) {
+ 				return 0;
+ 			}
  		}
  	} else { /* empty string */
  		ZVAL_EMPTY_STRING(&new_var);
diff --cc ext/filter/sanitizing_filters.c
index 865065491d3,4e549a3d65d..de108915547
--- a/ext/filter/sanitizing_filters.c
+++ b/ext/filter/sanitizing_filters.c
@@@ -31,10 -31,9 +31,10 @@@ static bool php_filter_encode_html(zva
  	size_t len = Z_STRLEN_P(value);
  	unsigned char *s = (unsigned char *)Z_STRVAL_P(value);
  	unsigned char *e = s + len;
 +	unsigned char *last_output = s;

  	if (Z_STRLEN_P(value) == 0) {
- 		return;
+ 		return true;
  	}

  	while (s < e) {
@@@ -43,15 -41,23 +43,23 @@@
  			smart_str_appendl(&str, "&#", 2);
  			smart_str_append_unsigned(&str, (zend_ulong)*s);
  			smart_str_appendc(&str, ';');
 -		} else {
 -			/* XXX: this needs to be optimized to work with blocks of 'safe' chars */
 -			smart_str_appendc(&str, *s);
 +			last_output = s + 1;
  		}
  		s++;
+
+ 		if (ZEND_HAS_OOM()) {
+ 			smart_str_free(&str);
+ 			zval_ptr_dtor(value);
+ 			ZVAL_EMPTY_STRING(value);
+ 			return false;
+ 		}
  	}

 +	smart_str_appendl(&str, (const char *) last_output, s - last_output);
 +
  	zval_ptr_dtor(value);
 -	ZVAL_STR(value, smart_str_extract(&str));
 +	ZVAL_NEW_STR(value, smart_str_extract(&str));
+ 	return true;
  }

  static const unsigned char hexchars[] = "0123456789ABCDEF";
@@@ -76,9 -82,14 +84,14 @@@ static void php_filter_encode_url(zval
  		tmp[*s++] = '\0';
  	}

- 	str = zend_string_safe_alloc(Z_STRLEN_P(value), 3, 0, 0);
+ 	str = zend_string_safe_falloc(Z_STRLEN_P(value), 3, 0, /* persistent */ false, /* fallible */ true);
+ 	if (!str) {
+ 		zval_ptr_dtor(value);
+ 		ZVAL_EMPTY_STRING(value);
+ 		return;
+ 	}
 -	p = (unsigned char *) ZSTR_VAL(str);
 -	s = (unsigned char *) Z_STRVAL_P(value);
 +	p = (unsigned char*)ZSTR_VAL(str);
 +	s = (const unsigned char*)Z_STRVAL_P(value);
  	e = s + Z_STRLEN_P(value);

  	while (s < e) {
@@@ -193,7 -205,9 +206,9 @@@ zend_result php_filter_string(PHP_INPUT
  		memset(enc + 127, 1, sizeof(enc) - 127);
  	}

- 	php_filter_encode_html(value, enc);
+ 	if (!php_filter_encode_html(value, enc)) {
 -		return;
++		return FAILURE;
+ 	}

  	/* strip tags, implicitly also removes \0 chars */
  	new_len = php_strip_tags_ex(Z_STRVAL_P(value), Z_STRLEN_P(value), NULL, 0, 1);
diff --cc ext/filter/tests/oom_filter_default_encoded.phpt
index 00000000000,4e81326639a..eb63678a68d
mode 000000,100644..100644
--- a/ext/filter/tests/oom_filter_default_encoded.phpt
+++ b/ext/filter/tests/oom_filter_default_encoded.phpt
@@@ -1,0 -1,126 +1,127 @@@
+ --TEST--
+ filter.default=encoded doesn't reach hard limit while parsing POST data
+ --EXTENSIONS--
+ filter
+ --SKIPIF--
+ <?php
+ if (!getenv('TEST_PHP_CGI_EXECUTABLE')) {
+     die("skip php-cgi not available");
+ }
+ ?>
+ --FILE--
+ <?php
+ $script = __DIR__ . '/' . basename(__FILE__, '.php') . '.target.php';
+ file_put_contents($script, <<<'TARGET'
+ <?php
+ echo "no OOM\n";
+ TARGET);
+
+ function oom_post_encoded(string $body, string $script): void
+ {
+     $p = proc_open(
+         [
+             getenv('TEST_PHP_CGI_EXECUTABLE'), '-n',
+             '-d', 'memory_limit=8M',
+             '-d', 'post_max_size=1G',
+             '-d', 'html_errors=0',
+             '-d', 'filter.default=encoded',
+             '-d', 'filter.default_flags=0',
++            '-d', 'fatal_error_backtraces=Off',
+             /* the conversion is what is under test, not the deprecation */
+             '-d', 'error_reporting=' . (E_ALL & ~E_DEPRECATED),
+         ],
+         [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']],
+         $pipes,
+         null,
+         [
+             'REQUEST_METHOD'  => 'POST',
+             'CONTENT_TYPE'    => 'application/x-www-form-urlencoded',
+             'CONTENT_LENGTH'  => (string) strlen($body),
+             'SCRIPT_FILENAME' => $script,
+             'REDIRECT_STATUS' => '1',
+         ]
+     );
+     fwrite($pipes[0], $body);
+     fclose($pipes[0]);
+     $out = stream_get_contents($pipes[1]) . stream_get_contents($pipes[2]);
+     fclose($pipes[1]);
+     fclose($pipes[2]);
+     proc_close($p);
+
+     echo $out;
+ }
+
+ for ($kb = 2016; $kb <= 2064; $kb += 4) {
+     oom_post_encoded('a=' . str_repeat('x', $kb * 1024), $script);
+ }
+ ?>
+ --CLEAN--
+ <?php
+ @unlink(__DIR__ . '/' . basename(__FILE__, '.clean.php') . '.target.php');
+ ?>
+ --EXPECTF--
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/filter/tests/oom_filter_input.phpt
index 00000000000,aef71dff563..68d56072f22
mode 000000,100644..100644
--- a/ext/filter/tests/oom_filter_input.phpt
+++ b/ext/filter/tests/oom_filter_input.phpt
@@@ -1,0 -1,56 +1,56 @@@
+ --TEST--
+ filter_input() doesn't reach hard limit
+ --EXTENSIONS--
+ filter
+ --SKIPIF--
+ <?php
+ if (!getenv('TEST_PHP_CGI_EXECUTABLE')) {
+     die("skip php-cgi not available");
+ }
+ ?>
+ --FILE--
+ <?php
+ $script = __DIR__ . '/' . basename(__FILE__, '.php') . '.target.php';
+ file_put_contents($script, <<<'TARGET'
+ <?php
+ filter_input(INPUT_POST, 'k', FILTER_SANITIZE_SPECIAL_CHARS, FILTER_FLAG_ENCODE_HIGH);
+ echo "no OOM\n";
+ TARGET);
+
+ $boundary = '----X';
+ $body = "--$boundary\r\nContent-Disposition: form-data; name=\"k\"\r\n\r\n"
+       . str_repeat("\xFF", 2 * 1024 * 1024)
+       . "\r\n--$boundary--\r\n";
+
+ $p = proc_open(
 -    [getenv('TEST_PHP_CGI_EXECUTABLE'), '-n', '-d', 'memory_limit=12M', '-d', 'html_errors=0'],
++    [getenv('TEST_PHP_CGI_EXECUTABLE'), '-n', '-d', 'memory_limit=12M', '-d', 'html_errors=0', '-d', 'fatal_error_backtraces=Off'],
+     [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']],
+     $pipes,
+     null,
+     [
+         'REQUEST_METHOD'  => 'POST',
+         'CONTENT_TYPE'    => "multipart/form-data; boundary=$boundary",
+         'CONTENT_LENGTH'  => (string) strlen($body),
+         'SCRIPT_FILENAME' => $script,
+         'REDIRECT_STATUS' => '1',
+     ]
+ );
+ fwrite($pipes[0], $body);
+ fclose($pipes[0]);
+ $out = stream_get_contents($pipes[1]) . stream_get_contents($pipes[2]);
+ fclose($pipes[1]);
+ fclose($pipes[2]);
+ proc_close($p);
+
+ echo $out;
+ ?>
+ --CLEAN--
+ <?php
+ @unlink(__DIR__ . '/' . basename(__FILE__, '.clean.php') . '.target.php');
+ ?>
+ --EXPECTF--
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 12582912 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/filter/tests/oom_filter_input_array.phpt
index 00000000000,a8e2712271c..b36261f1dd5
mode 000000,100644..100644
--- a/ext/filter/tests/oom_filter_input_array.phpt
+++ b/ext/filter/tests/oom_filter_input_array.phpt
@@@ -1,0 -1,56 +1,56 @@@
+ --TEST--
+ filter_input_array() doesn't reach hard limit
+ --EXTENSIONS--
+ filter
+ --SKIPIF--
+ <?php
+ if (!getenv('TEST_PHP_CGI_EXECUTABLE')) {
+     die("skip php-cgi not available");
+ }
+ ?>
+ --FILE--
+ <?php
+ $script = __DIR__ . '/' . basename(__FILE__, '.php') . '.target.php';
+ file_put_contents($script, <<<'TARGET'
+ <?php
+ filter_input_array(INPUT_POST, ['k' => ['filter' => FILTER_SANITIZE_SPECIAL_CHARS, 'flags' => FILTER_FLAG_ENCODE_HIGH]]);
+ echo "no OOM\n";
+ TARGET);
+
+ $boundary = '----X';
+ $body = "--$boundary\r\nContent-Disposition: form-data; name=\"k\"\r\n\r\n"
+       . str_repeat("\xFF", 2 * 1024 * 1024)
+       . "\r\n--$boundary--\r\n";
+
+ $p = proc_open(
 -    [getenv('TEST_PHP_CGI_EXECUTABLE'), '-n', '-d', 'memory_limit=12M', '-d', 'html_errors=0'],
++    [getenv('TEST_PHP_CGI_EXECUTABLE'), '-n', '-d', 'memory_limit=12M', '-d', 'html_errors=0', '-d', 'fatal_error_backtraces=Off'],
+     [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']],
+     $pipes,
+     null,
+     [
+         'REQUEST_METHOD'  => 'POST',
+         'CONTENT_TYPE'    => "multipart/form-data; boundary=$boundary",
+         'CONTENT_LENGTH'  => (string) strlen($body),
+         'SCRIPT_FILENAME' => $script,
+         'REDIRECT_STATUS' => '1',
+     ]
+ );
+ fwrite($pipes[0], $body);
+ fclose($pipes[0]);
+ $out = stream_get_contents($pipes[1]) . stream_get_contents($pipes[2]);
+ fclose($pipes[1]);
+ fclose($pipes[2]);
+ proc_close($p);
+
+ echo $out;
+ ?>
+ --CLEAN--
+ <?php
+ @unlink(__DIR__ . '/' . basename(__FILE__, '.clean.php') . '.target.php');
+ ?>
+ --EXPECTF--
+ X-Powered-By: PHP/%s
+ Content-type: text/html; charset=UTF-8
+
+
+ Fatal error: Allowed memory size of 12582912 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/gd/libgd/gd.c
index 47c4dbc0d77,6f0a7abc1e9..b088aadc840
--- a/ext/gd/libgd/gd.c
+++ b/ext/gd/libgd/gd.c
@@@ -3195,8 -3194,8 +3219,8 @@@ int gdImagePaletteToTrueColor(gdImagePt
  		// Note: do not revert back to gdMalloc() below ; reason here,
  		// due to a bug with a certain memory_limit INI value treshold,
  		// imagepalettetotruecolor crashes with even unrelated ZendMM allocations.
 -		// See GH-17772 for an use case.
 -		src->tpixels = (int **) gdFCalloc(sizeof(int *), sy);
 +		// See GH-17772 for a use case.
- 		src->tpixels = (int **) gdCalloc(sy, sizeof(int *));
++		src->tpixels = (int **) gdFCalloc(sy, sizeof(int *));
  		if (src->tpixels == NULL) {
  			return 0;
  		}
diff --cc ext/iconv/iconv.c
index de1eb76ac52,0212d1063c7..2713e4cd70d
--- a/ext/iconv/iconv.c
+++ b/ext/iconv/iconv.c
@@@ -442,8 -445,8 +446,8 @@@ PHP_ICONV_API php_iconv_err_t php_iconv
  	char *out_p;
  	size_t bsz, result = 0;
  	php_iconv_err_t retval = PHP_ICONV_ERR_SUCCESS;
- 	zend_string *out_buf;
+ 	zend_string *out_buf = NULL;
 -	int ignore_ilseq = _php_check_ignore(out_charset);
 +	bool ignore_ilseq = _php_check_ignore(out_charset);

  	*out = NULL;

diff --cc ext/intl/converter/converter.c
index bf89691dd6e,c9baeac9c1f..7d107c39ca1
--- a/ext/intl/converter/converter.c
+++ b/ext/intl/converter/converter.c
@@@ -639,10 -639,13 +639,13 @@@ static zend_string* php_converter_do_co
  	/* Get necessary buffer size first */
  	temp_len = 1 + ucnv_toUChars(src_cnv, NULL, 0, src, src_len, &error);
  	if (U_FAILURE(error) && error != U_BUFFER_OVERFLOW_ERROR) {
 -		THROW_UFAILURE(objval, "ucnv_toUChars", error);
 +		THROW_UFAILURE(objval, error);
  		return NULL;
  	}
- 	temp = safe_emalloc(sizeof(UChar), temp_len, sizeof(UChar));
+ 	temp = safe_femalloc(sizeof(UChar), temp_len, sizeof(UChar), /* fallible */ true);
+ 	if (!temp) {
+ 		return NULL;
+ 	}

  	/* Convert to intermediate UChar* array */
  	error = U_ZERO_ERROR;
diff --cc ext/intl/grapheme/grapheme_string.c
index 64aa62d7458,59c3e3cddce..743939a9b66
--- a/ext/intl/grapheme/grapheme_string.c
+++ b/ext/intl/grapheme/grapheme_string.c
@@@ -919,213 -915,12 +922,232 @@@ PHP_FUNCTION(grapheme_str_split

  	utext_close(ut);
  	ubrk_close(bi);
+ 	return;
+
+ oom:
+ 	utext_close(ut);
+ 	ubrk_close(bi);
+ 	RETURN_OOM();
  }

 +PHP_FUNCTION(grapheme_levenshtein)
 +{
 +	zend_string *string1, *string2;
 +	zend_long cost_ins = 1;
 +	zend_long cost_rep = 1;
 +	zend_long cost_del = 1;
 +	char *locale = "";
 +	size_t locale_len = 0;
 +
 +	ZEND_PARSE_PARAMETERS_START(2, 6)
 +		Z_PARAM_STR(string1)
 +		Z_PARAM_STR(string2)
 +		Z_PARAM_OPTIONAL
 +		Z_PARAM_LONG(cost_ins)
 +		Z_PARAM_LONG(cost_rep)
 +		Z_PARAM_LONG(cost_del)
 +		Z_PARAM_PATH(locale, locale_len)
 +	ZEND_PARSE_PARAMETERS_END();
 +
 +	if (cost_ins <= 0 || cost_ins > UINT_MAX / 4) {
 +		zend_argument_value_error(3, "must be greater than 0 and less than or equal to %d", UINT_MAX / 4);
 +		RETURN_THROWS();
 +	}
 +
 +	if (cost_rep <= 0 || cost_rep > UINT_MAX / 4) {
 +		zend_argument_value_error(4, "must be greater than 0 and less than or equal to %d", UINT_MAX / 4);
 +		RETURN_THROWS();
 +	}
 +
 +	if (cost_del <= 0 || cost_del > UINT_MAX / 4) {
 +		zend_argument_value_error(5, "must be greater than 0 and less than or equal to %d", UINT_MAX / 4);
 +		RETURN_THROWS();
 +	}
 +
 +	zend_long c0, c1, c2;
 +	zend_long retval;
 +	size_t i2;
 +	char *pstr1, *pstr2;
 +
 +	UChar *ustring1 = NULL;
 +	UChar *ustring2 = NULL;
 +
 +	int32_t ustring1_len = 0;
 +	int32_t ustring2_len = 0;
 +
 +	UErrorCode ustatus = U_ZERO_ERROR;
 +
 +	/* When all costs are equal, levenshtein fulfills the requirements of a metric, which means
 +	 * that the distance is symmetric. If string1 is shorter than string2 we can save memory (and CPU time)
 +	 * by having shorter rows (p1 & p2). */
 +	if (ZSTR_LEN(string1) < ZSTR_LEN(string2) && cost_ins == cost_rep && cost_rep == cost_del) {
 +		zend_string *tmp = string1;
 +		string1 = string2;
 +		string2 = tmp;
 +	}
 +
 +	pstr1 = ZSTR_VAL(string1);
 +	pstr2 = ZSTR_VAL(string2);
 +
 +	intl_convert_utf8_to_utf16(&ustring1, &ustring1_len, pstr1, ZSTR_LEN(string1), &ustatus);
 +
++	if (ZEND_HAS_OOM()) {
++		goto out_ustring1;
++	}
 +	if (U_FAILURE(ustatus)) {
 +		intl_error_set_code(NULL, ustatus);
 +
 +		intl_error_set_custom_msg(NULL, "Error converting input string to UTF-16");
 +		RETVAL_FALSE;
 +		goto out_ustring1;
 +	}
 +
 +	intl_convert_utf8_to_utf16(&ustring2, &ustring2_len, pstr2, ZSTR_LEN(string2), &ustatus);
 +
++	if (ZEND_HAS_OOM()) {
++		goto out_ustring2;
++	}
 +	if (U_FAILURE(ustatus)) {
 +		intl_error_set_code(NULL, ustatus);
 +
 +		intl_error_set_custom_msg(NULL, "Error converting input string to UTF-16");
 +		RETVAL_FALSE;
 +		goto out_ustring2;
 +	}
 +
 +	UBreakIterator *bi1, *bi2;
 +
 +	int32_t strlen_1, strlen_2;
 +	strlen_1 = grapheme_split_string(ustring1, ustring1_len, NULL, 0);
 +	strlen_2 = grapheme_split_string(ustring2, ustring2_len, NULL, 0);
 +	if (UNEXPECTED(strlen_1 < 0 || strlen_2 < 0)) {
 +		RETVAL_FALSE;
 +		goto out_ustring2;
 +	}
 +
 +	if (strlen_1 == 0) {
 +		RETVAL_LONG(strlen_2 * cost_ins);
 +		goto out_ustring2;
 +	}
 +	if (strlen_2 == 0) {
 +		RETVAL_LONG(strlen_1 * cost_del);
 +		goto out_ustring2;
 +	}
 +
 +	unsigned char u_break_iterator_buffer1[U_BRK_SAFECLONE_BUFFERSIZE];
 +	unsigned char u_break_iterator_buffer2[U_BRK_SAFECLONE_BUFFERSIZE];
 +	bi1 = grapheme_get_break_iterator(u_break_iterator_buffer1, &ustatus);
 +	if (U_FAILURE(ustatus)) {
 +		intl_error_set_code(NULL, ustatus);
 +		intl_error_set_custom_msg(NULL, "Error on grapheme_get_break_iterator for argument #1 ($string1)");
 +		RETVAL_FALSE;
 +		goto out_bi1;
 +	}
 +
 +	bi2 = grapheme_get_break_iterator(u_break_iterator_buffer2, &ustatus);
 +	if (U_FAILURE(ustatus)) {
 +		intl_error_set_code(NULL, ustatus);
 +		intl_error_set_custom_msg(NULL, "Error on grapheme_get_break_iterator for argument #2 ($string2)");
 +		RETVAL_FALSE;
 +		goto out_bi2;
 +	}
 +
 +	ubrk_setText(bi1, ustring1, ustring1_len, &ustatus);
 +	if (U_FAILURE(ustatus)) {
 +		intl_error_set_code(NULL, ustatus);
 +
 +		intl_error_set_custom_msg(NULL, "Error on ubrk_setText for argument #1 ($string1)");
 +		RETVAL_FALSE;
 +		goto out_bi2;
 +	}
 +
 +	ubrk_setText(bi2, ustring2, ustring2_len, &ustatus);
 +	if (U_FAILURE(ustatus)) {
 +		intl_error_set_code(NULL, ustatus);
 +
 +		intl_error_set_custom_msg(NULL, "Error on ubrk_setText for argument #2 ($string2)");
 +		RETVAL_FALSE;
 +		goto out_bi2;
 +	}
 +	UCollator *collator = ucol_open(locale, &ustatus);
 +	if (U_FAILURE(ustatus)) {
 +		intl_error_set_code(NULL, ustatus);
 +
 +		intl_error_set_custom_msg(NULL, "Error on ucol_open");
 +		RETVAL_FALSE;
 +		goto out_collator;
 +	}
 +
 +	zend_long *p1, *p2, *tmp;
- 	p1 = safe_emalloc((size_t) strlen_2 + 1, sizeof(zend_long), 0);
- 	p2 = safe_emalloc((size_t) strlen_2 + 1, sizeof(zend_long), 0);
++	p1 = safe_femalloc((size_t) strlen_2 + 1, sizeof(zend_long), 0, /* fallible */ true);
++	if (!p1) {
++		goto out_collator;
++	}
++	p2 = safe_femalloc((size_t) strlen_2 + 1, sizeof(zend_long), 0, /* fallible */ true);
++	if (!p2) {
++		efree(p1);
++		goto out_collator;
++	}
 +
 +	for (i2 = 0; i2 <= strlen_2; i2++) {
 +		p1[i2] = i2 * cost_ins;
 +	}
 +
 +	int32_t current1 = 0;
 +	int32_t current2 = 0;
 +	int32_t pos1 = 0;
 +	int32_t pos2 = 0;
 +
 +	while (true) {
 +		current1 = ubrk_current(bi1);
 +		pos1 = ubrk_next(bi1);
 +		if (pos1 == UBRK_DONE) {
 +			break;
 +		}
 +		p2[0] = p1[0] + cost_del;
 +		for (i2 = 0, pos2 = 0; pos2 != UBRK_DONE; i2++) {
 +			current2 = ubrk_current(bi2);
 +			pos2 = ubrk_next(bi2);
 +			if (pos2 == UBRK_DONE) {
 +				break;
 +			}
 +			if (ucol_strcoll(collator, ustring1 + current1, pos1 - current1, ustring2 + current2, pos2 - current2) == UCOL_EQUAL) {
 +				c0 = p1[i2];
 +			} else {
 +				c0 = p1[i2] + cost_rep;
 +			}
 +			c1 = p1[i2 + 1] + cost_del;
 +			if (c1 < c0) {
 +				c0 = c1;
 +			}
 +			c2 = p2[i2] + cost_ins;
 +			if (c2 < c0) {
 +				c0 = c2;
 +			}
 +			p2[i2 + 1] = c0;
 +		}
 +		ubrk_first(bi2);
 +		tmp = p1;
 +		p1 = p2;
 +		p2 = tmp;
 +	}
 +
 +	retval = p1[strlen_2];
 +	RETVAL_LONG(retval);
 +
 +	efree(p2);
 +	efree(p1);
 +
 +out_collator:
 +	ucol_close(collator);
 +out_bi2:
 +	ubrk_close(bi2);
 +out_bi1:
 +	ubrk_close(bi1);
 +out_ustring2:
 +	efree(ustring2);
 +out_ustring1:
 +	efree(ustring1);
 +}
 +
  /* }}} */
diff --cc ext/intl/normalizer/normalizer_normalize.cpp
index 92e8f1166ad,0e270c41f92..99877c398f8
--- a/ext/intl/normalizer/normalizer_normalize.cpp
+++ b/ext/intl/normalizer/normalizer_normalize.cpp
@@@ -147,10 -148,17 +147,13 @@@ U_CFUNC PHP_FUNCTION( normalizer_normal

  	/* Allocate memory for the destination buffer for normalization */
  	uret_len = uinput_len * expansion_factor;
- 	uret_buf = eumalloc( uret_len + 1 );
 -	uret_buf = safe_femalloc(uret_len + 1, sizeof(UChar), 0, /* fallible */ true);
++	uret_buf = (UChar*)safe_femalloc(uret_len + 1, sizeof(UChar), 0, /* fallible */ true);
+ 	if (!uret_buf) {
+ 		RETURN_OOM();
+ 	}

  	/* normalize */
 -#if U_ICU_VERSION_MAJOR_NUM < 56
 -	size_needed = unorm_normalize( uinput, uinput_len, form, (int32_t) 0 /* options */, uret_buf, uret_len, &status);
 -#else
  	size_needed = intl_normalize(form, uinput, uinput_len, uret_buf, uret_len, &status);
 -#endif

  	/* Bail out if an unexpected error occurred.
  	 * (U_BUFFER_OVERFLOW_ERROR means that *target buffer is not large enough).
@@@ -168,7 -176,11 +171,11 @@@
  		 * uret_buf =  eurealloc(uret_buf, size_needed + 1);
  		 */
  		efree( uret_buf );
- 		uret_buf = eumalloc( size_needed + 1 );
 -		uret_buf = safe_femalloc(size_needed + 1, sizeof(UChar), 0, /* fallible */ true);
++		uret_buf = (UChar*)safe_femalloc(size_needed + 1, sizeof(UChar), 0, /* fallible */ true);
+ 		if (!uret_buf) {
+ 			efree(uinput);
+ 			RETURN_OOM();
+ 		}
  		uret_len = size_needed;

  		status = U_ZERO_ERROR;
diff --cc ext/intl/tests/oom_grapheme_levenshtein_rows.phpt
index 00000000000,00000000000..2bed9ff5e08
new file mode 100644
--- /dev/null
+++ b/ext/intl/tests/oom_grapheme_levenshtein_rows.phpt
@@@ -1,0 -1,0 +1,14 @@@
++--TEST--
++grapheme_levenshtein() doesn't reach hard limit - cost rows
++--EXTENSIONS--
++intl
++--INI--
++memory_limit=8M
++--FILE--
++<?php
++grapheme_levenshtein("a", str_repeat("b", 1024 * 1024), 1, 2, 3);
++
++echo "not reached\n";
++?>
++--EXPECTF--
++Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/intl/tests/oom_grapheme_levenshtein_utf16.phpt
index 00000000000,00000000000..e6309ee288b
new file mode 100644
--- /dev/null
+++ b/ext/intl/tests/oom_grapheme_levenshtein_utf16.phpt
@@@ -1,0 -1,0 +1,15 @@@
++--TEST--
++grapheme_levenshtein() doesn't reach hard limit - UTF-16 conversion
++--EXTENSIONS--
++intl
++--INI--
++memory_limit=8M
++--FILE--
++<?php
++$size = (int) (2.5 * 1024 * 1024);
++grapheme_levenshtein(str_repeat("a", $size), str_repeat("b", $size), 1, 2, 3);
++
++echo "not reached\n";
++?>
++--EXPECTF--
++Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/lexbor/php_lexbor.c
index f18a09a5c4f,00000000000..aa69f684e49
mode 100644,000000..100644
--- a/ext/lexbor/php_lexbor.c
+++ b/ext/lexbor/php_lexbor.c
@@@ -1,80 -1,0 +1,80 @@@
 +/*
 +   +----------------------------------------------------------------------+
 +   | Copyright (c) The PHP Group                                          |
 +   +----------------------------------------------------------------------+
 +   | This source file is subject to version 3.01 of the PHP license,      |
 +   | that is bundled with this package in the file LICENSE, and is        |
 +   | available through the world-wide-web at the following url:           |
 +   | https://www.php.net/license/3_01.txt                                 |
 +   | If you did not receive a copy of the PHP license and are unable to   |
 +   | obtain it through the world-wide-web, please send a note to          |
 +   | license@php.net so we can mail you a copy immediately.               |
 +   +----------------------------------------------------------------------+
 +   | Authors: Nora Dossche  <ndossche@php.net>                            |
 +   |          Mate Kocsis <kocsismate@php.net>                            |
 +   +----------------------------------------------------------------------+
 +*/
 +
 +#ifdef HAVE_CONFIG_H
 +#include <config.h>
 +#endif
 +
 +#include "php.h"
 +#include "zend_globals.h"
 +#include "ext/standard/info.h"
 +#include "lexbor/core/base.h"
 +#include "lexbor/core/types.h"
 +#include "lexbor/core/lexbor.h"
 +
 +#ifdef HAVE_LEXBOR
 +
 +#include "php_lexbor.h"
 +
 +static void *php_lexbor_malloc(size_t size)
 +{
- 	return emalloc(size);
++	return femalloc(size, /* fallible */ true);
 +}
 +
 +static void *php_lexbor_realloc(void *dst, size_t size)
 +{
- 	return erealloc(dst, size);
++	return safe_ferealloc(dst, 1, size, 0, /* fallible */ true);
 +}
 +
 +static void *php_lexbor_calloc(size_t num, size_t size)
 +{
- 	return ecalloc(num, size);
++	return fecalloc(num, size, /* fallible */ true);
 +}
 +
 +static void php_lexbor_free(void *ptr)
 +{
 +	efree(ptr);
 +}
 +
 +static PHP_MINFO_FUNCTION(lexbor)
 +{
 +	php_info_print_table_start();
 +	php_info_print_table_row(2, "Lexbor support", "active");
 +	php_info_print_table_row(2, "Lexbor version", LEXBOR_VERSION_STRING);
 +	php_info_print_table_end();
 +}
 +
 +static PHP_MINIT_FUNCTION(lexbor)
 +{
 +	lexbor_memory_setup(php_lexbor_malloc, php_lexbor_realloc, php_lexbor_calloc, php_lexbor_free);
 +	return SUCCESS;
 +}
 +
 +zend_module_entry lexbor_module_entry = {
 +	STANDARD_MODULE_HEADER,
 +	"lexbor",                   /* extension name */
 +	NULL,                       /* extension function list */
 +	PHP_MINIT(lexbor),          /* extension-wide startup function */
 +	NULL,                       /* extension-wide shutdown function */
 +	NULL,                       /* per-request startup function */
 +	NULL,                       /* per-request shutdown function */
 +	PHP_MINFO(lexbor),          /* information function */
 +	PHP_VERSION,
 +	STANDARD_MODULE_PROPERTIES
 +};
 +
 +#endif
diff --cc ext/libxml/libxml.c
index dcbdd6bca57,cb8a1df0efd..72e93e7dce2
--- a/ext/libxml/libxml.c
+++ b/ext/libxml/libxml.c
@@@ -1199,10 -1202,12 +1203,10 @@@ PHP_FUNCTION(libxml_get_errors
  	ZEND_PARSE_PARAMETERS_NONE();

  	if (LIBXML(error_list)) {
- 		array_init(return_value);
+ 		array_init_size(return_value, zend_llist_count(LIBXML(error_list)));
 -		error = zend_llist_get_first(LIBXML(error_list));
 +		xmlErrorPtr error = zend_llist_get_first(LIBXML(error_list));

- 		while (error != NULL) {
+ 		while (error != NULL && !ZEND_HAS_OOM()) {
  			zval z_error;
  			php_libxml_create_error_object(&z_error, error);
  			add_next_index_zval(return_value, &z_error);
diff --cc ext/opcache/jit/zend_jit_ir.c
index 78e7c94cea2,830dbdf3272..c41447de2fb
--- a/ext/opcache/jit/zend_jit_ir.c
+++ b/ext/opcache/jit/zend_jit_ir.c
@@@ -2093,10 -2083,12 +2102,12 @@@ static int zend_jit_interrupt_handler_s
  		jit_STORE_IP(jit, ir_LOAD_A(jit_EX(opline)));
  	}

+ 	ir_MERGE_WITH(if_delayed_oom_end);
+
 -	if (GCC_GLOBAL_REGS) {
 -		ir_TAILCALL(IR_VOID, ir_LOAD_A(jit_IP(jit)));
 +	if (GCC_GLOBAL_REGS || ZEND_VM_KIND == ZEND_VM_KIND_TAILCALL) {
 +		zend_jit_tailcall_handler(jit, ir_LOAD_A(jit_IP(jit)));
  	} else {
 -		ir_RETURN(ir_CONST_I32(1));
 +		zend_jit_vm_enter(jit, jit_IP(jit));
  	}
  	return 1;
  }
diff --cc ext/opcache/zend_persist.c
index a4aea9fae65,8b8312ff81e..866dc01d102
--- a/ext/opcache/zend_persist.c
+++ b/ext/opcache/zend_persist.c
@@@ -335,13 -314,58 +335,58 @@@ static HashTable *zend_persist_attribut
  	return ptr;
  }

- uint32_t zend_accel_get_class_name_map_ptr(zend_string *type_name)
+ /* We delay the publishing of CE cache slots until after ZCSG(map_ptr_last) is
+  * published.
+  * CE cache slots stored in interned strings are immediately visible to other
+  * processes, so if we fail to update ZCSG(map_ptr_last) after publishing a
+  * slot (due to a bailout/exit), that slot may be re-allocated for something
+  * else by other processes. */
+ void zend_accel_pending_ce_cache_begin(void)
+ {
+ 	ZEND_ASSERT(ZCG(locked));
+
+ 	/* A persist window left through a bailout caught by the caller (e.g.
+ 	 * opcache_compile_file()) never published its slots. Drop them. */
+ 	zend_accel_pending_ce_cache_discard();
+
+ 	ZCG(pending_ce_cache) = emalloc(sizeof(HashTable));
+ 	zend_hash_init(ZCG(pending_ce_cache), 8, NULL, NULL, 0);
+ }
+
+ void zend_accel_pending_ce_cache_commit(void)
+ {
+ 	ZEND_ASSERT(ZCG(pending_ce_cache));
+ 	/* Every pending slot must be covered by ZCSG(map_ptr_last) */
+ 	ZEND_ASSERT(ZCSG(map_ptr_last) == CG(map_ptr_last));
+
+ 	ZEND_HASH_FOREACH_NUM_KEY_VAL(ZCG(pending_ce_cache), zend_ulong h, zval *zv) {
+ 		zend_string *type_name = (zend_string*)(uintptr_t)h;
+
+ 		ZEND_ASSERT(!ZSTR_HAS_CE_CACHE(type_name));
+ 		GC_SET_REFCOUNT(type_name, (uint32_t)Z_LVAL_P(zv));
+ 		GC_ADD_FLAGS(type_name, IS_STR_CLASS_NAME_MAP_PTR);
+ 	} ZEND_HASH_FOREACH_END();
+
+ 	zend_accel_pending_ce_cache_discard();
+ }
+
+ void zend_accel_pending_ce_cache_discard(void)
+ {
+ 	if (ZCG(pending_ce_cache)) {
+ 		zend_hash_destroy(ZCG(pending_ce_cache));
+ 		efree(ZCG(pending_ce_cache));
+ 		ZCG(pending_ce_cache) = NULL;
+ 	}
+ }
+
+ void zend_accel_get_class_name_map_ptr(zend_string *type_name)
  {
  	uint32_t ret;
+ 	zval *slot;

 -	if (zend_string_equals_literal_ci(type_name, "self") ||
 -			zend_string_equals_literal_ci(type_name, "parent")) {
 +	if (zend_string_equals_ci(type_name, ZSTR_KNOWN(ZEND_STR_SELF)) ||
 +			zend_string_equals_ci(type_name, ZSTR_KNOWN(ZEND_STR_PARENT))) {
- 		return 0;
+ 		return;
  	}

  	/* We use type.name.gc.refcount to keep map_ptr of corresponding type */
diff --cc ext/pcre/php_pcre.c
index 50e2e8a93f3,5146260b323..090cfd8da9a
--- a/ext/pcre/php_pcre.c
+++ b/ext/pcre/php_pcre.c
@@@ -1031,8 -1040,8 +1031,8 @@@ static inline void add_offset_pair
  }
  /* }}} */

- static void populate_subpat_array(
+ static zend_result populate_subpat_array(
 -		zval *subpats, const char *subject, PCRE2_SIZE *offsets, zend_string **subpat_names,
 +		HashTable *subpats_ht, const char *subject, PCRE2_SIZE *offsets, zend_string **subpat_names,
  		uint32_t num_subpats, int count, const PCRE2_SPTR mark, zend_long flags) {
  	zend_long offset_capture = flags & PREG_OFFSET_CAPTURE;
  	zend_long unmatched_as_null = flags & PREG_UNMATCHED_AS_NULL;
@@@ -1085,20 -1113,25 +1103,27 @@@
  				populate_match_value(
  					&val, subject, offsets[2*i], offsets[2*i+1], unmatched_as_null);
  				zend_hash_next_index_insert_new(subpats_ht, &val);
+ 				if (ZEND_HAS_OOM()) {
+ 					return FAILURE;
+ 				}
  			}
  			if (unmatched_as_null) {
 +				ZVAL_NULL(&val);
  				for (i = count; i < num_subpats; i++) {
 -					add_next_index_null(subpats);
 +					zend_hash_next_index_insert_new(subpats_ht, &val);
+ 					if (ZEND_HAS_OOM()) {
+ 						return FAILURE;
+ 					}
  				}
  			}
  		}
  	}
  	/* Add MARK, if available */
  	if (mark) {
 -		add_assoc_string_ex(subpats, "MARK", sizeof("MARK") - 1, (char *)mark);
 +		ZVAL_STRING(&val, (char *)mark);
 +		zend_hash_str_add_new(subpats_ht, ZEND_STRL("MARK"), &val);
  	}
+ 	return ZEND_HAS_OOM() ? FAILURE : SUCCESS;
  }

  static void php_do_pcre_match(INTERNAL_FUNCTION_PARAMETERS, bool global) /* {{{ */
@@@ -1367,17 -1413,24 +1405,24 @@@ matched
  						/* Allocate and populate the result set array */
  						mark = pcre2_get_mark(match_data);
  						array_init_size(&result_set, count + (mark ? 1 : 0));
- 						populate_subpat_array(
+ 						zend_result result = populate_subpat_array(
 -							&result_set, subject, offsets, subpat_names,
 +							Z_ARRVAL(result_set), subject, offsets, subpat_names,
  							num_subpats, count, mark, flags);
+ 						if (UNEXPECTED(result == FAILURE)) {
+ 							zval_ptr_dtor(&result_set);
+ 							goto oom;
+ 						}
  						/* And add it to the output array */
  						zend_hash_next_index_insert_new(Z_ARRVAL_P(subpats), &result_set);
  					}
  				} else {			/* single pattern matching */
  					/* For each subpattern, insert it into the subpatterns array. */
  					mark = pcre2_get_mark(match_data);
- 					populate_subpat_array(
+ 					zend_result result = populate_subpat_array(
 -						subpats, subject, offsets, subpat_names, num_subpats, count, mark, flags);
 +						Z_ARRVAL_P(subpats), subject, offsets, subpat_names, num_subpats, count, mark, flags);
+ 					if (UNEXPECTED(result == FAILURE)) {
+ 						goto oom;
+ 					}
  					break;
  				}
  			}
@@@ -1572,7 -1644,11 +1636,11 @@@ static zend_string *preg_do_repl_func(z
  	zval	     arg;				/* Argument to pass to function */

  	array_init_size(&arg, count + (mark ? 1 : 0));
- 	populate_subpat_array(Z_ARRVAL(arg), subject, offsets, subpat_names, num_subpats, count, mark, flags);
 -	zend_result result = populate_subpat_array(&arg, subject, offsets, subpat_names, num_subpats, count, mark, flags);
++	zend_result result = populate_subpat_array(Z_ARRVAL(arg), subject, offsets, subpat_names, num_subpats, count, mark, flags);
+ 	if (UNEXPECTED(result == FAILURE)) {
+ 		zval_ptr_dtor(&arg);
+ 		return NULL;
+ 	}

  	fci->retval = &retval;
  	fci->param_count = 1;
@@@ -1956,17 -2050,16 +2033,17 @@@ matched
  			}

  			/* Set the match location in subject */
 -			match = subject + offsets[0];
 +			match = ZSTR_VAL(subject_str) + offsets[0];

 -			new_len = result_len + offsets[0] - last_end_offset; /* part before the match */
 +			/* Length of needed storage */
 +			size_t new_len = result_len + offsets[0] - last_end_offset; /* part before the match */

  			/* Use custom function to get replacement string and its length. */
 -			eval_result = preg_do_repl_func(
 -				fci, fcc, subject, offsets, subpat_names, num_subpats, count,
 +			zend_string *eval_result = preg_do_repl_func(
 +				fci, fcc, ZSTR_VAL(subject_str), offsets, subpat_names, num_subpats, count,
  				pcre2_get_mark(match_data), flags);

- 			if (UNEXPECTED(eval_result == NULL)) {
+ 			if (UNEXPECTED(!eval_result)) {
  				goto error;
  			}
  			new_len = zend_safe_address_guarded(1, ZSTR_LEN(eval_result) + ZSTR_MAX_OVERHEAD, new_len) -ZSTR_MAX_OVERHEAD;
diff --cc ext/session/session.c
index cf42be929c4,7a57652f55d..dd2c22e5d6d
--- a/ext/session/session.c
+++ b/ext/session/session.c
@@@ -108,10 -105,9 +108,9 @@@ static void proposed_session_id_to_sess
  static int my_module_number = 0;

  /* Dispatched by RINIT and by php_session_destroy */
 -static inline void php_rinit_session_globals(void) /* {{{ */
 +static inline void php_rinit_session_globals(void)
  {
  	/* Do NOT init PS(mod_user_names) here! */
- 	/* TODO: These could be moved to MINIT and removed. These should be initialized by php_rshutdown_session_globals() always when execution is finished. */
  	PS(id) = NULL;
  	PS(session_status) = php_session_none;
  	PS(in_save_handler) = 0;
@@@ -121,31 -117,12 +120,32 @@@
  	PS(define_sid) = 1;
  	PS(session_vars) = NULL;
  	PS(module_number) = my_module_number;
+ 	PS(in_rfc1867_update) = false;
  	ZVAL_UNDEF(&PS(http_session_vars));
  }
 -/* }}} */

 -static inline void php_session_cleanup_filename(void) /* {{{ */
 +static inline void php_session_headers_already_sent_error(int severity, const char *message) {
 +	const char *output_start_filename = php_output_get_start_filename();
 +	int output_start_lineno = php_output_get_start_lineno();
 +	if (output_start_filename != NULL) {
 +		php_error_docref(NULL, severity, "%s (sent from %s on line %d)", message, output_start_filename, output_start_lineno);
 +	} else {
 +		php_error_docref(NULL, severity, "%s", message);
 +	}
 +}
 +
 +static inline void php_session_session_already_started_error(int severity, const char *message) {
 +	if (PS(session_started_filename) != NULL) {
 +		php_error_docref(NULL, severity, "%s (started from %s on line %"PRIu32")", message, ZSTR_VAL(PS(session_started_filename)), PS(session_started_lineno));
 +	} else if (PS(auto_start)) {
 +		/* This option can't be changed at runtime, so we can assume it's because of this */
 +		php_error_docref(NULL, severity, "%s (session started automatically)", message);
 +	} else {
 +		php_error_docref(NULL, severity, "%s", message);
 +	}
 +}
 +
 +static inline void php_session_cleanup_filename(void)
  {
  	if (PS(session_started_filename)) {
  		zend_string_release(PS(session_started_filename));
@@@ -954,7 -961,29 +954,29 @@@ PHP_INI_END(
  /* ***************
     * Serializers *
     *************** */
+
+ /* The upload progress handler (php_session_rfc1867_update()) decodes and
+  * re-encodes the session during request startup, before RINIT, when no user
+  * code may run. Objects are then kept as __PHP_Incomplete_Class: no
+  * autoloading, no unserialize_callback_func, no __wakeup() or __unserialize(),
+  * and they are written back unchanged. */
+ static void php_session_unserialize_init(php_unserialize_data_t *var_hash)
+ {
+ 	PHP_VAR_UNSERIALIZE_INIT(*var_hash);
+ 	if (PS(in_rfc1867_update)) {
+ 		php_var_unserialize_set_incomplete_classes_only(*var_hash, true);
+ 	}
+ }
+
+ static void php_session_serialize_init(php_serialize_data_t *var_hash)
+ {
+ 	PHP_VAR_SERIALIZE_INIT(*var_hash);
+ 	if (PS(in_rfc1867_update)) {
+ 		php_var_serialize_set_incomplete_classes_only(*var_hash, true);
+ 	}
+ }
+
 -PS_SERIALIZER_ENCODE_FUNC(php_serialize) /* {{{ */
 +PS_SERIALIZER_ENCODE_FUNC(php_serialize)
  {
  	smart_str buf = {0};
  	php_serialize_data_t var_hash;
@@@ -1072,10 -1105,10 +1094,10 @@@ PS_SERIALIZER_ENCODE_FUNC(php
  	bool fail = false;
  	PS_ENCODE_VARS;

- 	PHP_VAR_SERIALIZE_INIT(var_hash);
+ 	php_session_serialize_init(&var_hash);

  	PS_ENCODE_LOOP(
 -		smart_str_appendl(&buf, ZSTR_VAL(key), ZSTR_LEN(key));
 +		smart_str_append(&buf, key);
  		if (memchr(ZSTR_VAL(key), PS_DELIMITER, ZSTR_LEN(key))) {
  			PHP_VAR_SERIALIZE_DESTROY(var_hash);
  			smart_str_free(&buf);
@@@ -1485,108 -1559,14 +1507,108 @@@ PHPAPI const ps_serializer *_php_find_p
  			break;
  		}
  	}
 -	return ret;
 +	return found_serializer;
 +}
 +
 +static bool should_invalidate_session_for_external_referer(void)
 +{
 +	zval *referer_data;
 +
 +	/* No external referer check configured */
 +	if (!PS(id) || PS(extern_referer_chk)[0] == '\0') {
 +		return false;
 +	}
 +
 +	/* No SERVER globals available */
 +	if (Z_ISUNDEF(PG(http_globals)[TRACK_VARS_SERVER])) {
 +		return false;
 +	}
 +
 +	/* Get HTTP_REFERER header */
 +	referer_data = zend_hash_str_find(Z_ARRVAL(PG(http_globals)[TRACK_VARS_SERVER]), ZEND_STRL("HTTP_REFERER"));
 +	if (!referer_data || Z_TYPE_P(referer_data) != IS_STRING || Z_STRLEN_P(referer_data) == 0) {
 +		return false;
 +	}
 +
 +	/* Check if referer contains expected string */
 +	return strstr(Z_STRVAL_P(referer_data), PS(extern_referer_chk)) == NULL;
 +}
 +
 +static void try_find_session_id_in_global(const char *global_name, size_t global_name_len)
 +{
 +	zval *global_data, *potential_session_id;
 +
 +	if (PS(id)) {
 +		return;
 +	}
 +
 +	global_data = zend_hash_str_find(&EG(symbol_table), global_name, global_name_len);
 +	if (!global_data) {
 +		return;
 +	}
 +
 +	ZVAL_DEREF(global_data);
 +	if (Z_TYPE_P(global_data) != IS_ARRAY) {
 +		return;
 +	}
 +
 +	potential_session_id = zend_hash_find(Z_ARRVAL_P(global_data), PS(session_name));
 +	if (potential_session_id) {
 +		proposed_session_id_to_session_id(potential_session_id);
 +	}
 +}
 +
 +static bool php_can_change_session_setting(const char *setting_name, bool check_cookies)
 +{
 +	if (PS(session_status) == php_session_active) {
 +		char error_msg[256];
 +		snprintf(error_msg, sizeof(error_msg), "Session %s cannot be changed when a session is active", setting_name);
 +		php_session_session_already_started_error(E_WARNING, error_msg);
 +
 +		return false;
 +	}
-
++
 +	if (SG(headers_sent) && (!check_cookies || PS(use_cookies))) {
 +		char error_msg[256];
 +		snprintf(error_msg, sizeof(error_msg), "Session %s cannot be changed after headers have already been sent", setting_name);
 +		php_session_headers_already_sent_error(E_WARNING, error_msg);
 +
 +		return false;
 +	}
-
++
 +	return true;
 +}
 +
 +static void try_find_session_id_in_cookies(void)
 +{
 +	zval *cookie_data, *potential_session_id;
 +
 +	if (!PS(use_cookies) || PS(id)) {
 +		return;
 +	}
 +
 +	cookie_data = zend_hash_str_find(&EG(symbol_table), ZEND_STRL("_COOKIE"));
 +	if (!cookie_data) {
 +		return;
 +	}
 +
 +	ZVAL_DEREF(cookie_data);
 +	if (Z_TYPE_P(cookie_data) != IS_ARRAY) {
 +		return;
 +	}
 +
 +	potential_session_id = zend_hash_find(Z_ARRVAL_P(cookie_data), PS(session_name));
 +	if (potential_session_id) {
 +		proposed_session_id_to_session_id(potential_session_id);
 +		PS(send_cookie) = 0;
 +		PS(define_sid) = 0;
 +	}
  }
 -/* }}} */

 -static void ppid2sid(zval *ppid) {
 -	ZVAL_DEREF(ppid);
 -	if (Z_TYPE_P(ppid) == IS_STRING) {
 -		PS(id) = zend_string_init(Z_STRVAL_P(ppid), Z_STRLEN_P(ppid), 0);
 +static void proposed_session_id_to_session_id(zval *proposed_session_id) {
 +	ZVAL_DEREF(proposed_session_id);
 +	if (Z_TYPE_P(proposed_session_id) == IS_STRING) {
 +		PS(id) = zend_string_copy(Z_STR_P(proposed_session_id));
  		PS(send_cookie) = 0;
  	} else {
  		PS(id) = NULL;
@@@ -3171,21 -3241,26 +3198,26 @@@ static void php_session_rfc1867_update(
  		zend_hash_update(Z_ARRVAL_P(sess_var), progress->key.s, &progress->data);
  	}
  	php_session_flush(1);
+ 	PS(in_rfc1867_update) = false;
 -} /* }}} */
 +}

 -static void php_session_rfc1867_cleanup(php_session_rfc1867_progress *progress) /* {{{ */
 +static void php_session_rfc1867_cleanup(php_session_rfc1867_progress *progress)
  {
- 	php_session_initialize();
- 	PS(session_status) = php_session_active;
+ 	PS(in_rfc1867_update) = true;
+ 	if (php_session_initialize() == FAILURE || PS(session_status) != php_session_active) {
+ 		PS(in_rfc1867_update) = false;
+ 		return;
+ 	}
  	IF_SESSION_VARS() {
  		zval *sess_var = Z_REFVAL(PS(http_session_vars));
  		SEPARATE_ARRAY(sess_var);
  		zend_hash_del(Z_ARRVAL_P(sess_var), progress->key.s);
  	}
  	php_session_flush(1);
+ 	PS(in_rfc1867_update) = false;
 -} /* }}} */
 +}

 -static zend_result php_session_rfc1867_callback(unsigned int event, void *event_data, void **extra) /* {{{ */
 +static zend_result php_session_rfc1867_callback(unsigned int event, void *event_data, void **extra)
  {
  	php_session_rfc1867_progress *progress;
  	zend_result retval = SUCCESS;
@@@ -3202,8 -3281,13 +3238,12 @@@
  	switch(event) {
  		case MULTIPART_EVENT_START: {
  			multipart_event_start *data = (multipart_event_start *) event_data;
+
+ 			/* This runs before RINIT, so we need to clear these early */
+ 			php_rinit_session_globals();
+
  			progress = ecalloc(1, sizeof(php_session_rfc1867_progress));
  			progress->content_length = data->content_length;
 -			progress->sname_len  = strlen(PS(session_name));
  			PS(rfc1867_progress) = progress;
  		}
  		break;
diff --cc ext/simplexml/simplexml.c
index 42944d25844,7d4337ae384..9610b3a0bf1
--- a/ext/simplexml/simplexml.c
+++ b/ext/simplexml/simplexml.c
@@@ -946,7 -961,9 +945,8 @@@ static bool get_base_node_value(php_sxe
  		php_libxml_increment_node_ptr((php_libxml_node_object *)subnode, node, NULL);

  		ZVAL_OBJ(value, &subnode->zo);
 -		/*zval_add_ref(value);*/
  	}
+ 	return true;
  }
  /* }}} */

@@@ -2039,8 -2081,8 +2085,8 @@@ PHP_METHOD(SimpleXMLElement, key
  		RETURN_THROWS();
  	}

 -	curnode = (xmlNodePtr)((php_libxml_node_ptr *)intern->node)->node;
 +	curnode = intern->node->node;
- 	RETURN_STRINGL((char*)curnode->name, xmlStrlen(curnode->name));
+ 	RETURN_FSTRINGL((const char *)curnode->name, xmlStrlen(curnode->name));
  }
  /* }}} */

diff --cc ext/soap/tests/oom_soap_response.inc
index 00000000000,ab7bea9b8ca..1f6f2f055ef
mode 000000,100644..100644
--- a/ext/soap/tests/oom_soap_response.inc
+++ b/ext/soap/tests/oom_soap_response.inc
@@@ -1,0 -1,33 +1,33 @@@
+ <?php
+
+ /* Builds a compact SOAP response for OOM tests */
+ function oom_soap_response(int $dimensions, int $items): string
+ {
+     $arrayType = 'xsd:string[' . implode(',', array_fill(0, $dimensions, 1)) . ']';
+     $items = str_repeat('<i/>', $items);
+
+     return <<<XML
+         <?xml version="1.0"?>
+         <SOAP-ENV:Envelope
+             xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
+             xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
+             xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+             xmlns:ns1="http://test-uri/">
+           <SOAP-ENV:Body>
+             <ns1:fResponse><p SOAP-ENC:arrayType="$arrayType">$items</p></ns1:fResponse>
+           </SOAP-ENV:Body>
+         </SOAP-ENV:Envelope>
+         XML;
+ }
+
+ /* A SoapClient whose transport is replaced by a canned response, so that the
+  * reproducer exercises only the response decoder. */
+ class OomSoapClient extends SoapClient
+ {
+     public string $response = '';
+
 -    public function __doRequest($request, $location, $action, $version, $oneWay = false): ?string
++    public function __doRequest($request, $location, $action, $version, $oneWay = false, $uriParserClass = null): ?string
+     {
+         return $this->response;
+     }
+ }
diff --cc ext/spl/spl_fixedarray.c
index 61f6da4cff2,16126ff31ba..71f5c9598c8
--- a/ext/spl/spl_fixedarray.c
+++ b/ext/spl/spl_fixedarray.c
@@@ -296,9 -314,21 +316,12 @@@ static zend_object *spl_fixedarray_obje
  	zend_object_std_init(&intern->std, class_type);
  	object_properties_init(&intern->std, class_type);

 -	while (parent) {
 -		if (parent == spl_ce_SplFixedArray) {
 -			break;
 -		}
 -
 -		parent = parent->parent;
 -		inherited = true;
 -	}
 -
  	if (orig && clone_orig) {
  		spl_fixedarray_object *other = spl_fixed_array_from_obj(orig);
- 		spl_fixedarray_copy_ctor(&intern->array, &other->array);
+ 		if (UNEXPECTED(spl_fixedarray_copy_ctor(&intern->array, &other->array) == FAILURE)) {
+ 			ZEND_ASSERT(intern->std.handlers);
+ 			return &intern->std;
+ 		}
  	} else {
  		/* The zeroed struct would mean "resizing"; set the sentinel. */
  		spl_fixedarray_default_ctor(&intern->array);
@@@ -735,30 -765,25 +764,32 @@@ PHP_METHOD(SplFixedArray, fromArray
  		zend_ulong num_index, max_index = 0;
  		zend_long tmp;

 -		ZEND_HASH_FOREACH_KEY(Z_ARRVAL_P(data), num_index, str_index) {
 -			if (str_index != NULL || (zend_long)num_index < 0) {
 -				zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0, "array must contain only positive integer keys");
 +		if (HT_IS_PACKED(Z_ARRVAL_P(data))) {
 +			/* If there are no holes, then nNumUsed is the number of elements.
 +			 * If there are holes, then nNumUsed is the index of the last element. */
 +			tmp = Z_ARRVAL_P(data)->nNumUsed;
 +		} else {
 +			ZEND_HASH_MAP_FOREACH_KEY(Z_ARRVAL_P(data), num_index, str_index) {
 +				if (str_index != NULL || (zend_long)num_index < 0) {
 +					zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0, "array must contain only positive integer keys");
 +					RETURN_THROWS();
 +				}
 +
 +				if (num_index > max_index) {
 +					max_index = num_index;
 +				}
 +			} ZEND_HASH_FOREACH_END();
 +
 +			tmp = max_index + 1;
 +			if (tmp <= 0) {
 +				zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0, "integer overflow detected");
  				RETURN_THROWS();
  			}
 -
 -			if (num_index > max_index) {
 -				max_index = num_index;
 -			}
 -		} ZEND_HASH_FOREACH_END();
 -
 -		tmp = max_index + 1;
 -		if (tmp <= 0) {
 -			zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0, "integer overflow detected");
 -			RETURN_THROWS();
  		}
 +
- 		spl_fixedarray_init(&array, tmp);
+ 		if (UNEXPECTED(spl_fixedarray_init(&array, tmp) == FAILURE)) {
+ 			RETURN_OOM();
+ 		}

  		ZEND_HASH_FOREACH_NUM_KEY_VAL(Z_ARRVAL_P(data), num_index, element) {
  			ZVAL_COPY_DEREF(&array.elements[num_index], element);
diff --cc ext/standard/image.c
index 7ea8f6010b3,f58569495d3..041442baba5
--- a/ext/standard/image.c
+++ b/ext/standard/image.c
@@@ -201,14 -203,16 +201,21 @@@ static struct php_gfxinfo *php_handle_s
  		*/

  		do {
 -			szlength = ZSTR_LEN(bufz) * (1<<factor++);
 +			factor <<= 1;
 +			if (ZSTR_LEN(bufz) > ULONG_MAX / factor) {
 +				status = Z_MEM_ERROR;
 +				break;
 +			}
 +			szlength = (unsigned long) (ZSTR_LEN(bufz) * factor);
- 			buf = erealloc(buf, szlength);
+ 			unsigned char *new_buf = safe_ferealloc(buf, 1, szlength, 0, /* fallible */ true);
+ 			if (UNEXPECTED(!new_buf)) {
+ 				efree(b);
+ 				zend_string_release_ex(bufz, 0);
+ 				efree(buf);
+ 				return NULL;
+ 			}
+ 			buf = new_buf;
 -			status = uncompress(buf, &szlength, (unsigned char *) ZSTR_VAL(bufz), ZSTR_LEN(bufz));
 +			status = uncompress(buf, &szlength, (unsigned char *) ZSTR_VAL(bufz), (unsigned long) ZSTR_LEN(bufz));
  		} while ((status==Z_BUF_ERROR)&&(factor<maxfactor));

  		if (bufz) {
diff --cc ext/tokenizer/tokenizer.c
index 203a9b9c363,2b61b4e3d4d..adc6ed957c8
--- a/ext/tokenizer/tokenizer.c
+++ b/ext/tokenizer/tokenizer.c
@@@ -279,8 -282,8 +282,8 @@@ static zend_string *make_str(unsigned c
  	}
  }

- static void add_token(
+ static bool add_token(
 -		zval *return_value, int token_type, unsigned char *text, size_t leng, int lineno,
 +		HashTable *return_value_ht, int token_type, unsigned char *text, size_t leng, int lineno,
  		zend_class_entry *token_class, HashTable *interned_strings) {
  	zval token;
  	if (token_class) {
@@@ -313,9 -326,14 +326,14 @@@
  			ZEND_HASH_FILL_NEXT();
  		} ZEND_HASH_FILL_END();
  	} else {
- 		ZVAL_STR(&token, make_str(text, leng, interned_strings));
+ 		zend_string *value = make_str(text, leng, interned_strings);
+ 		if (UNEXPECTED(!value)) {
+ 			return false;
+ 		}
+ 		ZVAL_STR(&token, value);
  	}
 -	zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &token);
 +	zend_hash_next_index_insert_new(return_value_ht, &token);
+ 	return true;
  }

  static bool tokenize(zval *return_value, zend_string *source, zend_class_entry *token_class)
@@@ -342,9 -359,12 +361,12 @@@
  	while ((token_type = lex_scan(&token, NULL))) {
  		ZEND_ASSERT(token_type != T_ERROR);

- 		add_token(
+ 		if (!add_token(
 -			return_value, token_type, zendtext, zendleng, token_line,
 +			return_value_ht, token_type, zendtext, zendleng, token_line,
- 			token_class, &interned_strings);
+ 			token_class, &interned_strings)) {
+ 			success = false;
+ 			break;
+ 		}

  		if (Z_TYPE(token) != IS_UNDEF) {
  			zval_ptr_dtor_nogc(&token);
@@@ -359,9 -379,12 +381,12 @@@
  			) {
  				/* fetch the rest into a T_INLINE_HTML */
  				if (zendcursor < zendlimit) {
- 					add_token(
+ 					if (!add_token(
 -						return_value, T_INLINE_HTML, zendcursor, zendlimit - zendcursor,
 +						return_value_ht, T_INLINE_HTML, zendcursor, zendlimit - zendcursor,
- 						token_line, token_class, &interned_strings);
+ 						token_line, token_class, &interned_strings)) {
+ 						success = false;
+ 						break;
+ 					}
  				}
  				break;
  			}
@@@ -385,8 -408,9 +410,9 @@@
  }

  struct event_context {
 -	zval *tokens;
 +	HashTable *tokens;
  	zend_class_entry *token_class;
+ 	bool oom;
  };

  static zval *extract_token_id_to_replace(zval *token_zv, const char *text, size_t length) {
@@@ -426,11 -454,14 +456,14 @@@ static void on_event
  			} else if (token == T_ECHO && LANG_SCNG(yy_leng) == sizeof("<?=") - 1) {
  				token = T_OPEN_TAG_WITH_ECHO;
  			}
- 			add_token(
- 				ctx->tokens, token, (unsigned char *) text, length, line, ctx->token_class, NULL);
+ 			if (UNEXPECTED(!add_token(
+ 				ctx->tokens, token, (unsigned char *) text, length, line, ctx->token_class, NULL))) {
+ 				ZEND_ASSERT(ZEND_HAS_OOM());
+ 				ctx->oom = true;
+ 			}
  			break;
  		case ON_FEEDBACK: {
 -			HashTable *tokens_ht = Z_ARRVAL_P(ctx->tokens);
 +			HashTable *tokens_ht = ctx->tokens;
  			zval *token_zv, *id_zv = NULL;
  			ZEND_HASH_REVERSE_FOREACH_VAL(tokens_ht, token_zv) {
  				id_zv = extract_token_id_to_replace(token_zv, text, length);
@@@ -471,8 -502,9 +504,9 @@@ static bool tokenize_parse
  	zend_prepare_string_for_scanning(&source_zval, ZSTR_EMPTY_ALLOC());
  	array_init(&token_stream);

 -	ctx.tokens = &token_stream;
 +	ctx.tokens = Z_ARRVAL(token_stream);
  	ctx.token_class = token_class;
+ 	ctx.oom = false;

  	CG(ast) = NULL;
  	CG(ast_arena) = zend_arena_create(1024 * 32);
diff --cc ext/uri/php_uri.c
index 77fc627b6a9,00000000000..076c9fef88a
mode 100644,000000..100644
--- a/ext/uri/php_uri.c
+++ b/ext/uri/php_uri.c
@@@ -1,1172 -1,0 +1,1197 @@@
 +/*
 +   +----------------------------------------------------------------------+
 +   | Copyright (c) The PHP Group                                          |
 +   +----------------------------------------------------------------------+
 +   | This source file is subject to version 3.01 of the PHP license,      |
 +   | that is bundled with this package in the file LICENSE, and is        |
 +   | available through the world-wide-web at the following url:           |
 +   | https://www.php.net/license/3_01.txt                                 |
 +   | If you did not receive a copy of the PHP license and are unable to   |
 +   | obtain it through the world-wide-web, please send a note to          |
 +   | license@php.net so we can mail you a copy immediately.               |
 +   +----------------------------------------------------------------------+
 +   | Authors: Máté Kocsis <kocsismate@php.net>                            |
 +   +----------------------------------------------------------------------+
 +*/
 +
 +#ifdef HAVE_CONFIG_H
 +# include <config.h>
 +#endif
 +
 +#include "php.h"
 +#include "Zend/zend_interfaces.h"
 +#include "Zend/zend_exceptions.h"
 +#include "Zend/zend_attributes.h"
 +#include "Zend/zend_enum.h"
 +#include "ext/standard/info.h"
 +
 +#include "php_uri.h"
 +#include "uri_parser_whatwg.h"
 +#include "uri_parser_rfc3986.h"
 +#include "uri_parser_php_parse_url.h"
 +#include "php_uri_arginfo.h"
 +#include "uriparser/Uri.h"
 +
 +zend_class_entry *php_uri_ce_rfc3986_uri;
 +zend_class_entry *php_uri_ce_whatwg_url;
 +zend_class_entry *php_uri_ce_comparison_mode;
 +zend_class_entry *php_uri_ce_exception;
 +zend_class_entry *php_uri_ce_error;
 +zend_class_entry *php_uri_ce_invalid_uri_exception;
 +zend_class_entry *php_uri_ce_whatwg_invalid_url_exception;
 +zend_class_entry *php_uri_ce_whatwg_url_validation_error_type;
 +zend_class_entry *php_uri_ce_whatwg_url_validation_error;
 +
 +static zend_object_handlers object_handlers_rfc3986_uri;
 +static zend_object_handlers object_handlers_whatwg_uri;
 +
 +static const zend_module_dep uri_deps[] = {
 +	ZEND_MOD_REQUIRED("lexbor")
 +	ZEND_MOD_END
 +};
 +
 +static zend_array uri_parsers;
 +
 +static HashTable *uri_get_debug_properties(php_uri_object *object)
 +{
 +	const HashTable *std_properties = zend_std_get_properties(&object->std);
 +	HashTable *result = zend_array_dup(std_properties);
 +
 +	const php_uri_parser * const parser = object->parser;
 +	void * const uri = object->uri;
 +
 +	if (UNEXPECTED(uri == NULL)) {
 +		return result;
 +	}
 +
 +	zval tmp;
 +	if (parser->property_handler.scheme.read(uri, PHP_URI_COMPONENT_READ_MODE_RAW, &tmp) == SUCCESS) {
 +		zend_hash_update(result, ZSTR_KNOWN(ZEND_STR_SCHEME), &tmp);
 +	}
 +
 +	if (parser->property_handler.username.read(uri, PHP_URI_COMPONENT_READ_MODE_RAW, &tmp) == SUCCESS) {
 +		zend_hash_update(result, ZSTR_KNOWN(ZEND_STR_USERNAME), &tmp);
 +	}
 +
 +	if (parser->property_handler.password.read(uri, PHP_URI_COMPONENT_READ_MODE_RAW, &tmp) == SUCCESS) {
 +		zend_hash_update(result, ZSTR_KNOWN(ZEND_STR_PASSWORD), &tmp);
 +	}
 +
 +	if (parser->property_handler.host.read(uri, PHP_URI_COMPONENT_READ_MODE_RAW, &tmp) == SUCCESS) {
 +		zend_hash_update(result, ZSTR_KNOWN(ZEND_STR_HOST), &tmp);
 +	}
 +
 +	if (parser->property_handler.port.read(uri, PHP_URI_COMPONENT_READ_MODE_RAW, &tmp) == SUCCESS) {
 +		zend_hash_update(result, ZSTR_KNOWN(ZEND_STR_PORT), &tmp);
 +	}
 +
 +	if (parser->property_handler.path.read(uri, PHP_URI_COMPONENT_READ_MODE_RAW, &tmp) == SUCCESS) {
 +		zend_hash_update(result, ZSTR_KNOWN(ZEND_STR_PATH), &tmp);
 +	}
 +
 +	if (parser->property_handler.query.read(uri, PHP_URI_COMPONENT_READ_MODE_RAW, &tmp) == SUCCESS) {
 +		zend_hash_update(result, ZSTR_KNOWN(ZEND_STR_QUERY), &tmp);
 +	}
 +
 +	if (parser->property_handler.fragment.read(uri, PHP_URI_COMPONENT_READ_MODE_RAW, &tmp) == SUCCESS) {
 +		zend_hash_update(result, ZSTR_KNOWN(ZEND_STR_FRAGMENT), &tmp);
 +	}
 +
 +	return result;
 +}
 +
 +PHPAPI const php_uri_parser *php_uri_get_parser(zend_string *uri_parser_name)
 +{
 +	if (uri_parser_name == NULL) {
 +		return zend_hash_str_find_ptr(&uri_parsers, PHP_URI_PARSER_PHP_PARSE_URL, sizeof(PHP_URI_PARSER_PHP_PARSE_URL) - 1);
 +	}
 +
 +	return zend_hash_find_ptr(&uri_parsers, uri_parser_name);
 +}
 +
 +ZEND_ATTRIBUTE_NONNULL PHPAPI php_uri_internal *php_uri_parse(const php_uri_parser *uri_parser, const char *uri_str, size_t uri_str_len, bool silent)
 +{
 +	void *uri = uri_parser->parse(uri_str, uri_str_len, NULL, NULL, silent);
 +
 +	if (uri == NULL) {
 +		return NULL;
 +	}
 +
 +	php_uri_internal *internal_uri = emalloc(sizeof(*internal_uri));
 +	internal_uri->parser = uri_parser;
 +	internal_uri->uri = uri;
 +
 +	return internal_uri;
 +}
 +
 +ZEND_ATTRIBUTE_NONNULL PHPAPI zend_result php_uri_get_scheme(const php_uri_internal *internal_uri, php_uri_component_read_mode read_mode, zval *zv)
 +{
 +	return internal_uri->parser->property_handler.scheme.read(internal_uri->uri, read_mode, zv);
 +}
 +
 +ZEND_ATTRIBUTE_NONNULL PHPAPI zend_result php_uri_get_username(const php_uri_internal *internal_uri, php_uri_component_read_mode read_mode, zval *zv)
 +{
 +	return internal_uri->parser->property_handler.username.read(internal_uri->uri, read_mode, zv);
 +}
 +
 +ZEND_ATTRIBUTE_NONNULL PHPAPI zend_result php_uri_get_password(const php_uri_internal *internal_uri, php_uri_component_read_mode read_mode, zval *zv)
 +{
 +	return internal_uri->parser->property_handler.password.read(internal_uri->uri, read_mode, zv);
 +}
 +
 +ZEND_ATTRIBUTE_NONNULL PHPAPI zend_result php_uri_get_host(const php_uri_internal *internal_uri, php_uri_component_read_mode read_mode, zval *zv)
 +{
 +	return internal_uri->parser->property_handler.host.read(internal_uri->uri, read_mode, zv);
 +}
 +
 +ZEND_ATTRIBUTE_NONNULL PHPAPI zend_result php_uri_get_port(const php_uri_internal *internal_uri, php_uri_component_read_mode read_mode, zval *zv)
 +{
 +	return internal_uri->parser->property_handler.port.read(internal_uri->uri, read_mode, zv);
 +}
 +
 +ZEND_ATTRIBUTE_NONNULL PHPAPI zend_result php_uri_get_path(const php_uri_internal *internal_uri, php_uri_component_read_mode read_mode, zval *zv)
 +{
 +	return internal_uri->parser->property_handler.path.read(internal_uri->uri, read_mode, zv);
 +}
 +
 +ZEND_ATTRIBUTE_NONNULL PHPAPI zend_result php_uri_get_query(const php_uri_internal *internal_uri, php_uri_component_read_mode read_mode, zval *zv)
 +{
 +	return internal_uri->parser->property_handler.query.read(internal_uri->uri, read_mode, zv);
 +}
 +
 +ZEND_ATTRIBUTE_NONNULL PHPAPI zend_result php_uri_get_fragment(const php_uri_internal *internal_uri, php_uri_component_read_mode read_mode, zval *zv)
 +{
 +	return internal_uri->parser->property_handler.fragment.read(internal_uri->uri, read_mode, zv);
 +}
 +
 +ZEND_ATTRIBUTE_NONNULL PHPAPI void php_uri_free(php_uri_internal *internal_uri)
 +{
 +	internal_uri->parser->destroy(internal_uri->uri);
 +	internal_uri->uri = NULL;
 +	internal_uri->parser = NULL;
 +	efree(internal_uri);
 +}
 +
 +ZEND_ATTRIBUTE_NONNULL PHPAPI php_uri *php_uri_parse_to_struct(
 +	const php_uri_parser *uri_parser, const char *uri_str, size_t uri_str_len, php_uri_component_read_mode read_mode, bool silent
 +) {
 +	php_uri_internal *uri_internal = php_uri_parse(uri_parser, uri_str, uri_str_len, silent);
 +	if (uri_internal == NULL) {
 +		return NULL;
 +	}
 +
 +	php_uri *uri = ecalloc(1, sizeof(*uri));
 +	zval tmp;
 +	zend_result result;
 +
 +	result = php_uri_get_scheme(uri_internal, read_mode, &tmp);
 +	if (result == FAILURE) {
 +		goto error;
 +	}
 +	if (Z_TYPE(tmp) == IS_STRING) {
 +		uri->scheme = Z_STR(tmp);
 +	}
 +
 +	result = php_uri_get_username(uri_internal, read_mode, &tmp);
 +	if (result == FAILURE) {
 +		goto error;
 +	}
 +	if (Z_TYPE(tmp) == IS_STRING) {
 +		uri->user = Z_STR(tmp);
 +	}
 +
 +	result = php_uri_get_password(uri_internal, read_mode, &tmp);
 +	if (result == FAILURE) {
 +		goto error;
 +	}
 +	if (Z_TYPE(tmp) == IS_STRING) {
 +		uri->password = Z_STR(tmp);
 +	}
 +
 +	result = php_uri_get_host(uri_internal, read_mode, &tmp);
 +	if (result == FAILURE) {
 +		goto error;
 +	}
 +	if (Z_TYPE(tmp) == IS_STRING) {
 +		uri->host = Z_STR(tmp);
 +	}
 +
 +	result = php_uri_get_port(uri_internal, read_mode, &tmp);
 +	if (result == FAILURE) {
 +		goto error;
 +	}
 +	if (Z_TYPE(tmp) == IS_LONG) {
 +		uri->port = Z_LVAL(tmp);
 +	}
 +
 +	result = php_uri_get_path(uri_internal, read_mode, &tmp);
 +	if (result == FAILURE) {
 +		goto error;
 +	}
 +	if (Z_TYPE(tmp) == IS_STRING) {
 +		uri->path = Z_STR(tmp);
 +	}
 +
 +	result = php_uri_get_query(uri_internal, read_mode, &tmp);
 +	if (result == FAILURE) {
 +		goto error;
 +	}
 +	if (Z_TYPE(tmp) == IS_STRING) {
 +		uri->query = Z_STR(tmp);
 +	}
 +
 +	result = php_uri_get_fragment(uri_internal, read_mode, &tmp);
 +	if (result == FAILURE) {
 +		goto error;
 +	}
 +	if (Z_TYPE(tmp) == IS_STRING) {
 +		uri->fragment = Z_STR(tmp);
 +	}
 +
 +	php_uri_free(uri_internal);
 +
 +	return uri;
 +
 +error:
 +	php_uri_free(uri_internal);
 +	php_uri_struct_free(uri);
 +
 +	return NULL;
 +}
 +
 +ZEND_ATTRIBUTE_NONNULL PHPAPI void php_uri_struct_free(php_uri *uri)
 +{
 +	if (uri->scheme) {
 +		zend_string_release(uri->scheme);
 +	}
 +	if (uri->user) {
 +		zend_string_release(uri->user);
 +	}
 +	if (uri->password) {
 +		zend_string_release(uri->password);
 +	}
 +	if (uri->host) {
 +		zend_string_release(uri->host);
 +	}
 +	if (uri->path) {
 +		zend_string_release(uri->path);
 +	}
 +	if (uri->query) {
 +		zend_string_release(uri->query);
 +	}
 +	if (uri->fragment) {
 +		zend_string_release(uri->fragment);
 +	}
 +
 +	efree(uri);
 +}
 +
 +/**
 + * Pass the errors parameter by ref to errors_zv for userland, and frees it if
 + * it is not not needed anymore.
 + */
 +static zend_result pass_errors_by_ref_and_free(zval *errors_zv, zval *errors)
 +{
 +	ZEND_ASSERT(Z_TYPE_P(errors) == IS_UNDEF || Z_TYPE_P(errors) == IS_ARRAY);
 +
 +	/* There was no error during parsing */
 +	if (Z_ISUNDEF_P(errors)) {
 +		return SUCCESS;
 +	}
 +
 +	/* The errors parameter is an array, but the pass-by ref argument stored by
 +	 * errors_zv was not passed - the URI implementation either doesn't support
 +	 * returning additional error information, or the caller is not interested in it */
 +	if (errors_zv == NULL) {
 +		zval_ptr_dtor(errors);
 +		return SUCCESS;
 +	}
 +
 +	ZEND_TRY_ASSIGN_REF_TMP(errors_zv, errors);
 +	if (EG(exception)) {
 +		return FAILURE;
 +	}
 +
 +	return SUCCESS;
 +}
 +
 +ZEND_ATTRIBUTE_NONNULL_ARGS(1, 2) PHPAPI void php_uri_instantiate_uri(
 +		INTERNAL_FUNCTION_PARAMETERS, const zend_string *uri_str, const php_uri_object *base_url_object,
 +		bool should_throw, bool should_update_this_object, zval *errors_zv
 +) {
 +
 +	php_uri_object *uri_object;
 +	if (should_update_this_object) {
 +		uri_object = Z_URI_OBJECT_P(ZEND_THIS);
 +		if (uri_object->uri != NULL) {
 +			zend_throw_error(NULL, "Cannot modify readonly object of class %s", ZSTR_VAL(Z_OBJCE_P(ZEND_THIS)->name));
 +			RETURN_THROWS();
 +		}
 +	} else {
 +		if (EX(func)->common.fn_flags & ZEND_ACC_STATIC) {
 +			object_init_ex(return_value, Z_CE_P(ZEND_THIS));
 +		} else {
 +			object_init_ex(return_value, Z_OBJCE_P(ZEND_THIS));
 +		}
 +		uri_object = Z_URI_OBJECT_P(return_value);
 +	}
 +
 +	const php_uri_parser *uri_parser = uri_object->parser;
 +
 +	zval errors;
 +	ZVAL_UNDEF(&errors);
 +
 +	void *base_url = NULL;
 +	if (base_url_object != NULL) {
 +		ZEND_ASSERT(base_url_object->std.ce == uri_object->std.ce);
 +		ZEND_ASSERT(base_url_object->uri != NULL);
 +		ZEND_ASSERT(base_url_object->parser == uri_parser);
 +		base_url = base_url_object->uri;
 +	}
 +
 +	void *uri = uri_parser->parse(ZSTR_VAL(uri_str), ZSTR_LEN(uri_str), base_url, errors_zv != NULL ? &errors : NULL, !should_throw);
 +	if (UNEXPECTED(uri == NULL)) {
 +		if (should_throw) {
 +			zval_ptr_dtor(&errors);
 +			RETURN_THROWS();
 +		} else {
 +			if (pass_errors_by_ref_and_free(errors_zv, &errors) == FAILURE) {
 +				RETURN_THROWS();
 +			}
 +			zval_ptr_dtor(return_value);
 +			RETURN_NULL();
 +		}
 +	}
 +
 +	if (pass_errors_by_ref_and_free(errors_zv, &errors) == FAILURE) {
 +		uri_parser->destroy(uri);
 +		RETURN_THROWS();
 +	}
 +
 +	uri_object->uri = uri;
 +}
 +
 +static void create_rfc3986_uri(INTERNAL_FUNCTION_PARAMETERS, bool is_constructor)
 +{
 +	zend_string *uri_str;
 +	zend_object *base_url_object = NULL;
 +
 +	ZEND_PARSE_PARAMETERS_START(1, 2)
 +		Z_PARAM_STR(uri_str)
 +		Z_PARAM_OPTIONAL
 +		Z_PARAM_OBJ_OF_CLASS_OR_NULL(base_url_object, php_uri_ce_rfc3986_uri)
 +	ZEND_PARSE_PARAMETERS_END();
 +
 +	php_uri_instantiate_uri(INTERNAL_FUNCTION_PARAM_PASSTHRU,
 +		uri_str, base_url_object ? php_uri_object_from_obj(base_url_object) : NULL, is_constructor, is_constructor, NULL);
 +}
 +
 +static bool is_list_of_whatwg_validation_errors(const HashTable *array)
 +{
 +	if (!zend_array_is_list(array)) {
 +		return false;
 +	}
 +
 +	ZEND_HASH_FOREACH_VAL(array, zval *val) {
 +		/* Do not allow references as they may change types after checking. */
 +
 +		if (Z_TYPE_P(val) != IS_OBJECT) {
 +			return false;
 +		}
 +
 +		if (!instanceof_function(Z_OBJCE_P(val), php_uri_ce_whatwg_url_validation_error)) {
 +			return false;
 +		}
 +	} ZEND_HASH_FOREACH_END();
 +
 +	return true;
 +}
 +
 +PHP_METHOD(Uri_Rfc3986_Uri, parse)
 +{
 +	create_rfc3986_uri(INTERNAL_FUNCTION_PARAM_PASSTHRU, false);
 +}
 +
 +PHP_METHOD(Uri_Rfc3986_Uri, __construct)
 +{
 +	create_rfc3986_uri(INTERNAL_FUNCTION_PARAM_PASSTHRU, true);
 +}
 +
 +PHP_METHOD(Uri_WhatWg_InvalidUrlException, __construct)
 +{
 +	zend_string *message = NULL;
 +	zval *errors = NULL;
 +	zend_long code = 0;
 +	zval *previous = NULL;
 +
 +	ZEND_PARSE_PARAMETERS_START(0, 4)
 +		Z_PARAM_OPTIONAL
 +		Z_PARAM_STR(message)
 +		Z_PARAM_ARRAY(errors)
 +		Z_PARAM_LONG(code)
 +		Z_PARAM_OBJECT_OF_CLASS_OR_NULL(previous, zend_ce_throwable)
 +	ZEND_PARSE_PARAMETERS_END();
 +
 +	if (zend_update_exception_properties(INTERNAL_FUNCTION_PARAM_PASSTHRU, message, code, previous) == FAILURE) {
 +		RETURN_THROWS();
 +	}
 +
 +	if (errors == NULL) {
 +		zval tmp;
 +		ZVAL_EMPTY_ARRAY(&tmp);
 +		zend_update_property(php_uri_ce_whatwg_invalid_url_exception, Z_OBJ_P(ZEND_THIS), ZEND_STRL("errors"), &tmp);
 +	} else {
 +		if (!is_list_of_whatwg_validation_errors(Z_ARR_P(errors))) {
 +			zend_argument_value_error(2, "must be a list of %s", ZSTR_VAL(php_uri_ce_whatwg_url_validation_error->name));
 +			RETURN_THROWS();
 +		}
 +
 +		zend_update_property(php_uri_ce_whatwg_invalid_url_exception, Z_OBJ_P(ZEND_THIS), ZEND_STRL("errors"), errors);
 +	}
 +	if (EG(exception)) {
 +		RETURN_THROWS();
 +	}
 +}
 +
 +PHP_METHOD(Uri_WhatWg_UrlValidationError, __construct)
 +{
 +	zend_string *context;
 +	zval *type;
 +	bool failure;
 +
 +	ZEND_PARSE_PARAMETERS_START(3, 3)
 +		Z_PARAM_STR(context)
 +		Z_PARAM_OBJECT_OF_CLASS(type, php_uri_ce_whatwg_url_validation_error_type)
 +		Z_PARAM_BOOL(failure)
 +	ZEND_PARSE_PARAMETERS_END();
 +
 +	zend_update_property_str(php_uri_ce_whatwg_url_validation_error, Z_OBJ_P(ZEND_THIS), ZEND_STRL("context"), context);
 +	if (EG(exception)) {
 +		RETURN_THROWS();
 +	}
 +
 +	zend_update_property_ex(php_uri_ce_whatwg_url_validation_error, Z_OBJ_P(ZEND_THIS), ZSTR_KNOWN(ZEND_STR_TYPE), type);
 +	if (EG(exception)) {
 +		RETURN_THROWS();
 +	}
 +
 +	zval failure_zv;
 +	ZVAL_BOOL(&failure_zv, failure);
 +	zend_update_property(php_uri_ce_whatwg_url_validation_error, Z_OBJ_P(ZEND_THIS), ZEND_STRL("failure"), &failure_zv);
 +	if (EG(exception)) {
 +		RETURN_THROWS();
 +	}
 +}
 +
 +static void create_whatwg_uri(INTERNAL_FUNCTION_PARAMETERS, bool is_constructor)
 +{
 +	zend_string *uri_str;
 +	zend_object *base_url_object = NULL;
 +	zval *errors = NULL;
 +
 +	ZEND_PARSE_PARAMETERS_START(1, 3)
 +		Z_PARAM_STR(uri_str)
 +		Z_PARAM_OPTIONAL
 +		Z_PARAM_OBJ_OF_CLASS_OR_NULL(base_url_object, php_uri_ce_whatwg_url)
 +		Z_PARAM_ZVAL(errors)
 +	ZEND_PARSE_PARAMETERS_END();
 +
 +	php_uri_instantiate_uri(INTERNAL_FUNCTION_PARAM_PASSTHRU,
 +		uri_str, base_url_object ? php_uri_object_from_obj(base_url_object) : NULL, is_constructor, is_constructor, errors);
 +}
 +
 +PHP_METHOD(Uri_WhatWg_Url, parse)
 +{
 +	create_whatwg_uri(INTERNAL_FUNCTION_PARAM_PASSTHRU, false);
 +}
 +
 +PHP_METHOD(Uri_WhatWg_Url, __construct)
 +{
 +	create_whatwg_uri(INTERNAL_FUNCTION_PARAM_PASSTHRU, true);
 +}
 +
 +PHP_METHOD(Uri_Rfc3986_Uri, getScheme)
 +{
 +	php_uri_property_read_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_SCHEME, PHP_URI_COMPONENT_READ_MODE_NORMALIZED_ASCII);
 +}
 +
 +PHP_METHOD(Uri_Rfc3986_Uri, getRawScheme)
 +{
 +	php_uri_property_read_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_SCHEME, PHP_URI_COMPONENT_READ_MODE_RAW);
 +}
 +
 +PHP_METHOD(Uri_Rfc3986_Uri, withScheme)
 +{
 +	php_uri_property_write_str_or_null_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_SCHEME);
 +}
 +
 +static void rfc3986_userinfo_read(INTERNAL_FUNCTION_PARAMETERS, php_uri_component_read_mode read_mode)
 +{
 +	ZEND_PARSE_PARAMETERS_NONE();
 +
 +	php_uri_object *uri_object = Z_URI_OBJECT_P(ZEND_THIS);
 +	ZEND_ASSERT(uri_object->uri != NULL);
 +
 +	if (UNEXPECTED(php_uri_parser_rfc3986_userinfo_read(uri_object->uri, read_mode, return_value) == FAILURE)) {
 +		zend_throw_error(NULL, "The userinfo component cannot be retrieved");
 +		RETURN_THROWS();
 +	}
 +}
 +
 +PHP_METHOD(Uri_Rfc3986_Uri, getUserInfo)
 +{
 +	rfc3986_userinfo_read(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_COMPONENT_READ_MODE_NORMALIZED_ASCII);
 +}
 +
 +PHP_METHOD(Uri_Rfc3986_Uri, getRawUserInfo)
 +{
 +	rfc3986_userinfo_read(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_COMPONENT_READ_MODE_RAW);
 +}
 +
 +PHP_METHOD(Uri_Rfc3986_Uri, withUserInfo)
 +{
 +	zend_string *value;
 +
 +	ZEND_PARSE_PARAMETERS_START(1, 1)
 +		Z_PARAM_STR_OR_NULL(value)
 +	ZEND_PARSE_PARAMETERS_END();
 +
 +	zval zv;
 +	if (value == NULL) {
 +		ZVAL_NULL(&zv);
 +	} else {
 +		ZVAL_STR(&zv, value);
 +	}
 +
 +	php_uri_object *old_uri_object = php_uri_object_from_obj(Z_OBJ_P(ZEND_THIS));
 +	ZEND_ASSERT(old_uri_object->uri != NULL);
 +
 +	zend_object *new_object = old_uri_object->std.handlers->clone_obj(&old_uri_object->std);
 +	if (new_object == NULL) {
 +		RETURN_THROWS();
 +	}
 +
 +	/* Assign the object early. The engine will take care of destruction in
 +	 * case of an exception being thrown. */
 +	RETVAL_OBJ(new_object);
 +
 +	php_uri_object *new_uri_object = php_uri_object_from_obj(new_object);
 +	ZEND_ASSERT(new_uri_object->uri != NULL);
 +
 +	if (UNEXPECTED(php_uri_parser_rfc3986_userinfo_write(new_uri_object->uri, &zv, NULL) == FAILURE)) {
 +		RETURN_THROWS();
 +	}
 +}
 +
 +PHP_METHOD(Uri_Rfc3986_Uri, getUsername)
 +{
 +	php_uri_property_read_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_USERNAME, PHP_URI_COMPONENT_READ_MODE_NORMALIZED_ASCII);
 +}
 +
 +PHP_METHOD(Uri_Rfc3986_Uri, getRawUsername)
 +{
 +	php_uri_property_read_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_USERNAME, PHP_URI_COMPONENT_READ_MODE_RAW);
 +}
 +
 +PHP_METHOD(Uri_Rfc3986_Uri, getPassword)
 +{
 +	php_uri_property_read_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_PASSWORD, PHP_URI_COMPONENT_READ_MODE_NORMALIZED_ASCII);
 +}
 +
 +PHP_METHOD(Uri_Rfc3986_Uri, getRawPassword)
 +{
 +	php_uri_property_read_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_PASSWORD, PHP_URI_COMPONENT_READ_MODE_RAW);
 +}
 +
 +PHP_METHOD(Uri_Rfc3986_Uri, getHost)
 +{
 +	php_uri_property_read_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_HOST, PHP_URI_COMPONENT_READ_MODE_NORMALIZED_ASCII);
 +}
 +
 +PHP_METHOD(Uri_Rfc3986_Uri, getRawHost)
 +{
 +	php_uri_property_read_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_HOST, PHP_URI_COMPONENT_READ_MODE_RAW);
 +}
 +
 +PHP_METHOD(Uri_Rfc3986_Uri, withHost)
 +{
 +	php_uri_property_write_str_or_null_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_HOST);
 +}
 +
 +PHP_METHOD(Uri_Rfc3986_Uri, getPort)
 +{
 +	php_uri_property_read_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_PORT, PHP_URI_COMPONENT_READ_MODE_RAW);
 +}
 +
 +PHP_METHOD(Uri_Rfc3986_Uri, withPort)
 +{
 +	php_uri_property_write_long_or_null_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_PORT);
 +}
 +
 +PHP_METHOD(Uri_Rfc3986_Uri, getPath)
 +{
 +	php_uri_property_read_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_PATH, PHP_URI_COMPONENT_READ_MODE_NORMALIZED_ASCII);
 +}
 +
 +PHP_METHOD(Uri_Rfc3986_Uri, getRawPath)
 +{
 +	php_uri_property_read_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_PATH, PHP_URI_COMPONENT_READ_MODE_RAW);
 +}
 +
 +PHP_METHOD(Uri_Rfc3986_Uri, withPath)
 +{
 +	php_uri_property_write_str_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_PATH);
 +}
 +
 +PHP_METHOD(Uri_Rfc3986_Uri, getQuery)
 +{
 +	php_uri_property_read_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_QUERY, PHP_URI_COMPONENT_READ_MODE_NORMALIZED_ASCII);
 +}
 +
 +PHP_METHOD(Uri_Rfc3986_Uri, getRawQuery)
 +{
 +	php_uri_property_read_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_QUERY, PHP_URI_COMPONENT_READ_MODE_RAW);
 +}
 +
 +PHP_METHOD(Uri_Rfc3986_Uri, withQuery)
 +{
 +	php_uri_property_write_str_or_null_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_QUERY);
 +}
 +
 +PHP_METHOD(Uri_Rfc3986_Uri, getFragment)
 +{
 +	php_uri_property_read_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_FRAGMENT, PHP_URI_COMPONENT_READ_MODE_NORMALIZED_ASCII);
 +}
 +
 +PHP_METHOD(Uri_Rfc3986_Uri, getRawFragment)
 +{
 +	php_uri_property_read_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_FRAGMENT, PHP_URI_COMPONENT_READ_MODE_RAW);
 +}
 +
 +PHP_METHOD(Uri_Rfc3986_Uri, withFragment)
 +{
 +	php_uri_property_write_str_or_null_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_FRAGMENT);
 +}
 +
 +static void throw_cannot_recompose_uri_to_string(php_uri_object *object)
 +{
++	if (ZEND_HAS_OOM()) {
++		zend_handle_delayed_oom();
++		return;
++	}
++
 +	zend_throw_exception_ex(php_uri_ce_error, 0, "Cannot recompose %s to a string", ZSTR_VAL(object->std.ce->name));
 +}
 +
 +static void uri_equals(INTERNAL_FUNCTION_PARAMETERS, php_uri_object *that_object, zend_object *comparison_mode)
 +{
 +	php_uri_object *this_object = Z_URI_OBJECT_P(ZEND_THIS);
 +	ZEND_ASSERT(this_object->uri != NULL);
 +	ZEND_ASSERT(that_object->uri != NULL);
 +
 +	if (this_object->std.ce != that_object->std.ce &&
 +		!instanceof_function(this_object->std.ce, that_object->std.ce) &&
 +		!instanceof_function(that_object->std.ce, this_object->std.ce)
 +	) {
 +		RETURN_FALSE;
 +	}
 +
 +	bool exclude_fragment = true;
 +	if (comparison_mode) {
 +		zval *case_name = zend_enum_fetch_case_name(comparison_mode);
 +		exclude_fragment = zend_string_equals_literal(Z_STR_P(case_name), "ExcludeFragment");
 +	}
 +
 +	zend_string *this_str = this_object->parser->to_string(
 +		this_object->uri, PHP_URI_RECOMPOSITION_MODE_NORMALIZED_ASCII, exclude_fragment);
 +	if (this_str == NULL) {
 +		throw_cannot_recompose_uri_to_string(this_object);
 +		RETURN_THROWS();
 +	}
 +
 +	zend_string *that_str = that_object->parser->to_string(
 +		that_object->uri, PHP_URI_RECOMPOSITION_MODE_NORMALIZED_ASCII, exclude_fragment);
 +	if (that_str == NULL) {
 +		zend_string_release(this_str);
 +		throw_cannot_recompose_uri_to_string(that_object);
 +		RETURN_THROWS();
 +	}
 +
 +	RETVAL_BOOL(zend_string_equals(this_str, that_str));
 +
 +	zend_string_release(this_str);
 +	zend_string_release(that_str);
 +}
 +
 +PHP_METHOD(Uri_Rfc3986_Uri, equals)
 +{
 +	zend_object *that_object;
 +	zend_object *comparison_mode = NULL;
 +
 +	ZEND_PARSE_PARAMETERS_START(1, 2)
 +		Z_PARAM_OBJ_OF_CLASS(that_object, php_uri_ce_rfc3986_uri)
 +		Z_PARAM_OPTIONAL
 +		Z_PARAM_OBJ_OF_CLASS(comparison_mode, php_uri_ce_comparison_mode)
 +	ZEND_PARSE_PARAMETERS_END();
 +
 +	uri_equals(INTERNAL_FUNCTION_PARAM_PASSTHRU, php_uri_object_from_obj(that_object), comparison_mode);
 +}
 +
 +PHP_METHOD(Uri_Rfc3986_Uri, toRawString)
 +{
 +	ZEND_PARSE_PARAMETERS_NONE();
 +
 +	php_uri_object *uri_object = Z_URI_OBJECT_P(ZEND_THIS);
 +	ZEND_ASSERT(uri_object->uri != NULL);
 +
 +	zend_string *uri_str = uri_object->parser->to_string(uri_object->uri, PHP_URI_RECOMPOSITION_MODE_RAW_ASCII, false);
 +	if (uri_str == NULL) {
 +		throw_cannot_recompose_uri_to_string(uri_object);
 +		RETURN_THROWS();
 +	}
 +
 +	RETURN_STR(uri_str);
 +}
 +
 +PHP_METHOD(Uri_Rfc3986_Uri, toString)
 +{
 +	ZEND_PARSE_PARAMETERS_NONE();
 +
 +	php_uri_object *uri_object = Z_URI_OBJECT_P(ZEND_THIS);
 +	ZEND_ASSERT(uri_object->uri != NULL);
 +
 +	zend_string *uri_str = uri_object->parser->to_string(uri_object->uri, PHP_URI_RECOMPOSITION_MODE_NORMALIZED_ASCII, false);
 +	if (uri_str == NULL) {
 +		throw_cannot_recompose_uri_to_string(uri_object);
 +		RETURN_THROWS();
 +	}
 +
 +	RETURN_STR(uri_str);
 +}
 +
 +PHP_METHOD(Uri_Rfc3986_Uri, resolve)
 +{
 +	zend_string *uri_str;
 +
 +	ZEND_PARSE_PARAMETERS_START(1, 1)
 +		Z_PARAM_STR(uri_str)
 +	ZEND_PARSE_PARAMETERS_END();
 +
 +	php_uri_instantiate_uri(INTERNAL_FUNCTION_PARAM_PASSTHRU,
 +		uri_str, Z_URI_OBJECT_P(ZEND_THIS), true, false, NULL);
 +}
 +
 +PHP_METHOD(Uri_Rfc3986_Uri, __serialize)
 +{
 +	ZEND_PARSE_PARAMETERS_NONE();
 +
 +	php_uri_object *uri_object = Z_URI_OBJECT_P(ZEND_THIS);
 +	ZEND_ASSERT(uri_object->uri != NULL);
 +
 +	/* Serialize state: "uri" key in the first array */
 +	zend_string *uri_str = uri_object->parser->to_string(uri_object->uri, PHP_URI_RECOMPOSITION_MODE_RAW_ASCII, false);
 +	if (uri_str == NULL) {
 +		throw_cannot_recompose_uri_to_string(uri_object);
 +		RETURN_THROWS();
 +	}
 +	zval tmp;
 +	ZVAL_STR(&tmp, uri_str);
 +
 +	array_init(return_value);
 +
 +	zval arr;
 +	array_init(&arr);
 +	zend_hash_str_add_new(Z_ARRVAL(arr), PHP_URI_SERIALIZE_URI_FIELD_NAME, sizeof(PHP_URI_SERIALIZE_URI_FIELD_NAME) - 1, &tmp);
 +	zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &arr);
 +
 +	/* Serialize regular properties: second array */
 +	ZVAL_EMPTY_ARRAY(&arr);
 +	zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &arr);
 +}
 +
 +static void uri_unserialize(INTERNAL_FUNCTION_PARAMETERS)
 +{
 +	HashTable *data;
 +
 +	ZEND_PARSE_PARAMETERS_START(1, 1)
 +		Z_PARAM_ARRAY_HT(data)
 +	ZEND_PARSE_PARAMETERS_END();
 +
 +	php_uri_object *uri_object = php_uri_object_from_obj(Z_OBJ_P(ZEND_THIS));
 +	if (uri_object->uri != NULL) {
 +		/* Intentionally throw two exceptions for proper chaining. */
 +		zend_throw_error(NULL, "Cannot modify readonly object of class %s", ZSTR_VAL(uri_object->std.ce->name));
 +		zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(uri_object->std.ce->name));
 +		RETURN_THROWS();
 +	}
 +
 +	/* Verify the expected number of elements, this implicitly ensures that no additional elements are present. */
 +	if (zend_hash_num_elements(data) != 2) {
 +		zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(uri_object->std.ce->name));
 +		RETURN_THROWS();
 +	}
 +
 +	/* Unserialize state: "uri" key in the first array */
 +	zval *arr = zend_hash_index_find(data, 0);
 +	if (arr == NULL || Z_TYPE_P(arr) != IS_ARRAY) {
 +		zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(uri_object->std.ce->name));
 +		RETURN_THROWS();
 +	}
 +
 +	/* Verify the expected number of elements inside the first array, this implicitly ensures that no additional elements are present. */
 +	if (zend_hash_num_elements(Z_ARRVAL_P(arr)) != 1) {
 +		zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(uri_object->std.ce->name));
 +		RETURN_THROWS();
 +	}
 +
 +	zval *uri_zv = zend_hash_str_find(Z_ARRVAL_P(arr), ZEND_STRL(PHP_URI_SERIALIZE_URI_FIELD_NAME));
 +	if (uri_zv == NULL || Z_TYPE_P(uri_zv) != IS_STRING) {
 +		zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(uri_object->std.ce->name));
 +		RETURN_THROWS();
 +	}
 +
 +	uri_object->uri = uri_object->parser->parse(Z_STRVAL_P(uri_zv), Z_STRLEN_P(uri_zv), NULL, NULL, true);
 +	if (uri_object->uri == NULL) {
++		if (ZEND_HAS_OOM()) {
++			RETURN_THROWS();
++		}
 +		zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(uri_object->std.ce->name));
 +		RETURN_THROWS();
 +	}
 +
 +	/* Unserialize regular properties: second array */
 +	arr = zend_hash_index_find(data, 1);
 +	if (arr == NULL || Z_TYPE_P(arr) != IS_ARRAY) {
 +		zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(uri_object->std.ce->name));
 +		RETURN_THROWS();
 +	}
 +
 +	/* Verify that there is no regular property in the second array, because the URI classes have no properties and they are final. */
 +	if (zend_hash_num_elements(Z_ARRVAL_P(arr)) > 0) {
 +		zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(uri_object->std.ce->name));
 +		RETURN_THROWS();
 +	}
 +}
 +
 +PHP_METHOD(Uri_Rfc3986_Uri, __unserialize)
 +{
 +	uri_unserialize(INTERNAL_FUNCTION_PARAM_PASSTHRU);
 +}
 +
 +PHP_METHOD(Uri_Rfc3986_Uri, __debugInfo)
 +{
 +	ZEND_PARSE_PARAMETERS_NONE();
 +
 +	php_uri_object *uri_object = Z_URI_OBJECT_P(ZEND_THIS);
 +
 +	RETURN_ARR(uri_get_debug_properties(uri_object));
 +}
 +
 +PHP_METHOD(Uri_WhatWg_Url, getScheme)
 +{
 +	php_uri_property_read_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_SCHEME, PHP_URI_COMPONENT_READ_MODE_NORMALIZED_ASCII);
 +}
 +
 +PHP_METHOD(Uri_WhatWg_Url, withScheme)
 +{
 +	php_uri_property_write_str_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_SCHEME);
 +}
 +
 +PHP_METHOD(Uri_WhatWg_Url, withUsername)
 +{
 +	php_uri_property_write_str_or_null_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_USERNAME);
 +}
 +
 +PHP_METHOD(Uri_WhatWg_Url, withPassword)
 +{
 +	php_uri_property_write_str_or_null_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_PASSWORD);
 +}
 +
 +PHP_METHOD(Uri_WhatWg_Url, getAsciiHost)
 +{
 +	php_uri_property_read_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_HOST, PHP_URI_COMPONENT_READ_MODE_NORMALIZED_ASCII);
 +}
 +
 +PHP_METHOD(Uri_WhatWg_Url, getUnicodeHost)
 +{
 +	php_uri_property_read_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_HOST, PHP_URI_COMPONENT_READ_MODE_NORMALIZED_UNICODE);
 +}
 +
 +PHP_METHOD(Uri_WhatWg_Url, getFragment)
 +{
 +	php_uri_property_read_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PROPERTY_NAME_FRAGMENT, PHP_URI_COMPONENT_READ_MODE_NORMALIZED_UNICODE);
 +}
 +
 +PHP_METHOD(Uri_WhatWg_Url, equals)
 +{
 +	zend_object *that_object;
 +	zend_object *comparison_mode = NULL;
 +
 +	ZEND_PARSE_PARAMETERS_START(1, 2)
 +		Z_PARAM_OBJ_OF_CLASS(that_object, php_uri_ce_whatwg_url)
 +		Z_PARAM_OPTIONAL
 +		Z_PARAM_OBJ_OF_CLASS(comparison_mode, php_uri_ce_comparison_mode)
 +	ZEND_PARSE_PARAMETERS_END();
 +
 +	uri_equals(INTERNAL_FUNCTION_PARAM_PASSTHRU, php_uri_object_from_obj(that_object), comparison_mode);
 +}
 +
 +PHP_METHOD(Uri_WhatWg_Url, toUnicodeString)
 +{
 +	ZEND_PARSE_PARAMETERS_NONE();
 +
 +	zend_object *this_object = Z_OBJ_P(ZEND_THIS);
 +	php_uri_object *uri_object = php_uri_object_from_obj(this_object);
 +	ZEND_ASSERT(uri_object->uri != NULL);
 +
- 	RETURN_STR(uri_object->parser->to_string(uri_object->uri, PHP_URI_RECOMPOSITION_MODE_RAW_UNICODE, false));
++	zend_string *uri_str = uri_object->parser->to_string(uri_object->uri, PHP_URI_RECOMPOSITION_MODE_RAW_UNICODE, false);
++	if (uri_str == NULL) {
++		throw_cannot_recompose_uri_to_string(uri_object);
++		RETURN_THROWS();
++	}
++
++	RETURN_STR(uri_str);
 +}
 +
 +PHP_METHOD(Uri_WhatWg_Url, toAsciiString)
 +{
 +	ZEND_PARSE_PARAMETERS_NONE();
 +
 +	zend_object *this_object = Z_OBJ_P(ZEND_THIS);
 +	php_uri_object *uri_object = php_uri_object_from_obj(this_object);
 +	ZEND_ASSERT(uri_object->uri != NULL);
 +
- 	RETURN_STR(uri_object->parser->to_string(uri_object->uri, PHP_URI_RECOMPOSITION_MODE_RAW_ASCII, false));
++	zend_string *uri_str = uri_object->parser->to_string(uri_object->uri, PHP_URI_RECOMPOSITION_MODE_RAW_ASCII, false);
++	if (uri_str == NULL) {
++		throw_cannot_recompose_uri_to_string(uri_object);
++		RETURN_THROWS();
++	}
++
++	RETURN_STR(uri_str);
 +}
 +
 +PHP_METHOD(Uri_WhatWg_Url, resolve)
 +{
 +	zend_string *uri_str;
 +	zval *errors = NULL;
 +
 +	ZEND_PARSE_PARAMETERS_START(1, 2)
 +		Z_PARAM_STR(uri_str)
 +		Z_PARAM_OPTIONAL
 +		Z_PARAM_ZVAL(errors)
 +	ZEND_PARSE_PARAMETERS_END();
 +
 +	php_uri_instantiate_uri(INTERNAL_FUNCTION_PARAM_PASSTHRU,
 +		uri_str, Z_URI_OBJECT_P(ZEND_THIS), true, false, errors);
 +}
 +
 +PHP_METHOD(Uri_WhatWg_Url, __serialize)
 +{
 +	ZEND_PARSE_PARAMETERS_NONE();
 +
 +	php_uri_object *this_object = Z_URI_OBJECT_P(ZEND_THIS);
 +	ZEND_ASSERT(this_object->uri != NULL);
 +
 +	/* Serialize state: "uri" key in the first array */
 +	zend_string *uri_str = this_object->parser->to_string(this_object->uri, PHP_URI_RECOMPOSITION_MODE_RAW_ASCII, false);
 +	if (uri_str == NULL) {
 +		throw_cannot_recompose_uri_to_string(this_object);
 +		RETURN_THROWS();
 +	}
 +	zval tmp;
 +	ZVAL_STR(&tmp, uri_str);
 +
 +	array_init(return_value);
 +
 +	zval arr;
 +	array_init(&arr);
 +	zend_hash_str_add_new(Z_ARRVAL(arr), ZEND_STRL(PHP_URI_SERIALIZE_URI_FIELD_NAME), &tmp);
 +	zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &arr);
 +
 +	/* Serialize regular properties: second array */
 +	ZVAL_EMPTY_ARRAY(&arr);
 +	zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &arr);
 +}
 +
 +PHP_METHOD(Uri_WhatWg_Url, __unserialize)
 +{
 +	uri_unserialize(INTERNAL_FUNCTION_PARAM_PASSTHRU);
 +}
 +
 +PHP_METHOD(Uri_WhatWg_Url, __debugInfo)
 +{
 +	ZEND_PARSE_PARAMETERS_NONE();
 +
 +	php_uri_object *uri_object = Z_URI_OBJECT_P(ZEND_THIS);
 +
 +	RETURN_ARR(uri_get_debug_properties(uri_object));
 +}
 +
 +PHPAPI php_uri_object *php_uri_object_create(zend_class_entry *class_type, const php_uri_parser *parser)
 +{
 +	php_uri_object *uri_object = zend_object_alloc(sizeof(*uri_object), class_type);
 +
 +	zend_object_std_init(&uri_object->std, class_type);
 +	object_properties_init(&uri_object->std, class_type);
 +
 +	uri_object->parser = parser;
 +	uri_object->uri = NULL;
 +
 +	return uri_object;
 +}
 +
 +static zend_object *php_uri_object_create_rfc3986(zend_class_entry *ce)
 +{
 +	return &php_uri_object_create(ce, &php_uri_parser_rfc3986)->std;
 +}
 +
 +static zend_object *php_uri_object_create_whatwg(zend_class_entry *ce)
 +{
 +	return &php_uri_object_create(ce, &php_uri_parser_whatwg)->std;
 +}
 +
 +PHPAPI void php_uri_object_handler_free(zend_object *object)
 +{
 +	php_uri_object *uri_object = php_uri_object_from_obj(object);
 +
 +	uri_object->parser->destroy(uri_object->uri);
 +	zend_object_std_dtor(&uri_object->std);
 +}
 +
 +PHPAPI zend_object *php_uri_object_handler_clone(zend_object *object)
 +{
 +	php_uri_object *uri_object = php_uri_object_from_obj(object);
 +
 +	ZEND_ASSERT(uri_object->uri != NULL);
 +
 +	php_uri_object *new_uri_object = php_uri_object_from_obj(object->ce->create_object(object->ce));
 +	ZEND_ASSERT(new_uri_object->parser == uri_object->parser);
 +
 +	void *uri = uri_object->parser->clone(uri_object->uri);
- 	ZEND_ASSERT(uri != NULL);
++	if (UNEXPECTED(uri == NULL)) {
++		ZEND_ASSERT(ZEND_HAS_OOM());
++		zend_object_release(&new_uri_object->std);
++		zend_handle_delayed_oom();
++		return NULL;
++	}
 +
 +	new_uri_object->uri = uri;
 +
 +	zend_objects_clone_members(&new_uri_object->std, &uri_object->std);
 +
 +	return &new_uri_object->std;
 +}
 +
 +PHPAPI zend_result php_uri_parser_register(const php_uri_parser *uri_parser)
 +{
 +	zend_string *key = zend_string_init_interned(uri_parser->name, strlen(uri_parser->name), true);
 +
 +	ZEND_ASSERT(uri_parser->name != NULL);
 +	ZEND_ASSERT(uri_parser->parse != NULL);
 +	ZEND_ASSERT(uri_parser->clone != NULL || strcmp(uri_parser->name, PHP_URI_PARSER_PHP_PARSE_URL) == 0);
 +	ZEND_ASSERT(uri_parser->to_string != NULL || strcmp(uri_parser->name, PHP_URI_PARSER_PHP_PARSE_URL) == 0);
 +	ZEND_ASSERT(uri_parser->destroy != NULL);
 +
 +	zend_result result = zend_hash_add_ptr(&uri_parsers, key, (void *) uri_parser) != NULL ? SUCCESS : FAILURE;
 +
 +	zend_string_release_ex(key, true);
 +
 +	return result;
 +}
 +
 +static PHP_MINIT_FUNCTION(uri)
 +{
 +	php_uri_ce_rfc3986_uri = register_class_Uri_Rfc3986_Uri();
 +	php_uri_ce_rfc3986_uri->create_object = php_uri_object_create_rfc3986;
 +	php_uri_ce_rfc3986_uri->default_object_handlers = &object_handlers_rfc3986_uri;
 +	memcpy(&object_handlers_rfc3986_uri, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
 +	object_handlers_rfc3986_uri.offset = XtOffsetOf(php_uri_object, std);
 +	object_handlers_rfc3986_uri.free_obj = php_uri_object_handler_free;
 +	object_handlers_rfc3986_uri.clone_obj = php_uri_object_handler_clone;
 +
 +	php_uri_ce_whatwg_url = register_class_Uri_WhatWg_Url();
 +	php_uri_ce_whatwg_url->create_object = php_uri_object_create_whatwg;
 +	php_uri_ce_whatwg_url->default_object_handlers = &object_handlers_whatwg_uri;
 +	memcpy(&object_handlers_whatwg_uri, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
 +	object_handlers_whatwg_uri.offset = XtOffsetOf(php_uri_object, std);
 +	object_handlers_whatwg_uri.free_obj = php_uri_object_handler_free;
 +	object_handlers_whatwg_uri.clone_obj = php_uri_object_handler_clone;
 +
 +	php_uri_ce_comparison_mode = register_class_Uri_UriComparisonMode();
 +	php_uri_ce_exception = register_class_Uri_UriException(zend_ce_exception);
 +	php_uri_ce_error = register_class_Uri_UriError(zend_ce_error);
 +	php_uri_ce_invalid_uri_exception = register_class_Uri_InvalidUriException(php_uri_ce_exception);
 +	php_uri_ce_whatwg_invalid_url_exception = register_class_Uri_WhatWg_InvalidUrlException(php_uri_ce_invalid_uri_exception);
 +	php_uri_ce_whatwg_url_validation_error = register_class_Uri_WhatWg_UrlValidationError();
 +	php_uri_ce_whatwg_url_validation_error_type = register_class_Uri_WhatWg_UrlValidationErrorType();
 +
 +	zend_hash_init(&uri_parsers, 4, NULL, NULL, true);
 +
 +	if (php_uri_parser_register(&php_uri_parser_rfc3986) == FAILURE) {
 +		return FAILURE;
 +	}
 +
 +	if (php_uri_parser_register(&php_uri_parser_whatwg) == FAILURE) {
 +		return FAILURE;
 +	}
 +
 +	if (php_uri_parser_register(&php_uri_parser_php_parse_url) == FAILURE) {
 +		return FAILURE;
 +	}
 +
 +	return SUCCESS;
 +}
 +
 +static PHP_MINFO_FUNCTION(uri)
 +{
 +	php_info_print_table_start();
 +	php_info_print_table_row(2, "URI support", "active");
 +#ifdef URI_STATIC_BUILD
 +	php_info_print_table_row(2, "uriparser bundled version", URI_VER_ANSI);
 +#else
 +	php_info_print_table_row(2, "uriparser compiled version", URI_VER_ANSI);
 +	php_info_print_table_row(2, "uriparser loaded version", uriBaseRuntimeVersionA());
 +#endif
 +	php_info_print_table_end();
 +}
 +
 +static PHP_MSHUTDOWN_FUNCTION(uri)
 +{
 +	zend_hash_destroy(&uri_parsers);
 +
 +	return SUCCESS;
 +}
 +
 +PHP_RINIT_FUNCTION(uri)
 +{
 +	if (PHP_RINIT(uri_parser_whatwg)(INIT_FUNC_ARGS_PASSTHRU) == FAILURE) {
 +		return FAILURE;
 +	}
 +
 +	return SUCCESS;
 +}
 +
 +ZEND_MODULE_POST_ZEND_DEACTIVATE_D(uri)
 +{
 +	if (ZEND_MODULE_POST_ZEND_DEACTIVATE_N(uri_parser_whatwg)() == FAILURE) {
 +		return FAILURE;
 +	}
 +
 +	return SUCCESS;
 +}
 +
 +zend_module_entry uri_module_entry = {
 +	STANDARD_MODULE_HEADER_EX, NULL,
 +	uri_deps,
 +	"uri",                          /* Extension name */
 +	NULL,                           /* zend_function_entry */
 +	PHP_MINIT(uri),                 /* PHP_MINIT - Module initialization */
 +	PHP_MSHUTDOWN(uri),             /* PHP_MSHUTDOWN - Module shutdown */
 +	PHP_RINIT(uri),                 /* PHP_RINIT - Request initialization */
 +	NULL,                           /* PHP_RSHUTDOWN - Request shutdown */
 +	PHP_MINFO(uri),                 /* PHP_MINFO - Module info */
 +	PHP_VERSION,                    /* Version */
 +	NO_MODULE_GLOBALS,
 +	ZEND_MODULE_POST_ZEND_DEACTIVATE_N(uri),
 +	STANDARD_MODULE_PROPERTIES_EX
 +};
diff --cc ext/uri/php_uri_common.c
index da73bc59bf9,00000000000..5b2b708710f
mode 100644,000000..100644
--- a/ext/uri/php_uri_common.c
+++ b/ext/uri/php_uri_common.c
@@@ -1,143 -1,0 +1,147 @@@
 +/*
 +   +----------------------------------------------------------------------+
 +   | Copyright (c) The PHP Group                                          |
 +   +----------------------------------------------------------------------+
 +   | This source file is subject to version 3.01 of the PHP license,      |
 +   | that is bundled with this package in the file LICENSE, and is        |
 +   | available through the world-wide-web at the following url:           |
 +   | https://www.php.net/license/3_01.txt                                 |
 +   | If you did not receive a copy of the PHP license and are unable to   |
 +   | obtain it through the world-wide-web, please send a note to          |
 +   | license@php.net so we can mail you a copy immediately.               |
 +   +----------------------------------------------------------------------+
 +   | Authors: Máté Kocsis <kocsismate@php.net>                            |
 +   +----------------------------------------------------------------------+
 +*/
 +
 +#include "php.h"
 +#include "Zend/zend_interfaces.h"
 +#include "Zend/zend_exceptions.h"
 +#include "php_uri_common.h"
 +
 +static zend_string *get_known_string_by_property_name(php_uri_property_name property_name)
 +{
 +	switch (property_name) {
 +		case PHP_URI_PROPERTY_NAME_SCHEME:
 +			return ZSTR_KNOWN(ZEND_STR_SCHEME);
 +		case PHP_URI_PROPERTY_NAME_USERNAME:
 +			return ZSTR_KNOWN(ZEND_STR_USERNAME);
 +		case PHP_URI_PROPERTY_NAME_PASSWORD:
 +			return ZSTR_KNOWN(ZEND_STR_PASSWORD);
 +		case PHP_URI_PROPERTY_NAME_HOST:
 +			return ZSTR_KNOWN(ZEND_STR_HOST);
 +		case PHP_URI_PROPERTY_NAME_PORT:
 +			return ZSTR_KNOWN(ZEND_STR_PORT);
 +		case PHP_URI_PROPERTY_NAME_PATH:
 +			return ZSTR_KNOWN(ZEND_STR_PATH);
 +		case PHP_URI_PROPERTY_NAME_QUERY:
 +			return ZSTR_KNOWN(ZEND_STR_QUERY);
 +		case PHP_URI_PROPERTY_NAME_FRAGMENT:
 +			return ZSTR_KNOWN(ZEND_STR_FRAGMENT);
 +		EMPTY_SWITCH_DEFAULT_CASE()
 +	}
 +}
 +
 +void php_uri_property_read_helper(INTERNAL_FUNCTION_PARAMETERS, php_uri_property_name property_name, php_uri_component_read_mode component_read_mode)
 +{
 +	ZEND_PARSE_PARAMETERS_NONE();
 +
 +	php_uri_object *uri_object = Z_URI_OBJECT_P(ZEND_THIS);
 +	ZEND_ASSERT(uri_object->uri != NULL);
 +
 +	const php_uri_property_handler *property_handler = php_uri_parser_property_handler_by_name(uri_object->parser, property_name);
 +
 +	if (UNEXPECTED(property_handler->read(uri_object->uri, component_read_mode, return_value) == FAILURE)) {
++		if (ZEND_HAS_OOM()) {
++			zend_handle_delayed_oom();
++			RETURN_THROWS();
++		}
 +		zend_throw_exception_ex(php_uri_ce_error, 0, "The %s component cannot be retrieved", ZSTR_VAL(get_known_string_by_property_name(property_name)));
 +		RETURN_THROWS();
 +	}
 +}
 +
 +static void php_uri_property_write_helper(INTERNAL_FUNCTION_PARAMETERS, php_uri_property_name property_name, zval *property_zv)
 +{
 +	php_uri_object *old_uri_object = Z_URI_OBJECT_P(ZEND_THIS);
 +	ZEND_ASSERT(old_uri_object->uri != NULL);
 +
 +	zend_object *new_object = old_uri_object->std.handlers->clone_obj(&old_uri_object->std);
 +	if (new_object == NULL) {
 +		RETURN_THROWS();
 +	}
 +
 +	/* Assign the object early. The engine will take care of destruction in
 +	 * case of an exception being thrown. */
 +	RETVAL_OBJ(new_object);
 +
 +	const php_uri_property_handler *property_handler = php_uri_parser_property_handler_by_name(old_uri_object->parser, property_name);
 +
 +	php_uri_object *new_uri_object = php_uri_object_from_obj(new_object);
 +	ZEND_ASSERT(new_uri_object->uri != NULL);
 +	if (UNEXPECTED(property_handler->write == NULL)) {
 +		zend_readonly_property_modification_error_ex(ZSTR_VAL(old_uri_object->std.ce->name),
 +			ZSTR_VAL(get_known_string_by_property_name(property_name)));
 +		RETURN_THROWS();
 +	}
 +
 +	zval errors;
 +	ZVAL_UNDEF(&errors);
 +	if (UNEXPECTED(property_handler->write(new_uri_object->uri, property_zv, &errors) == FAILURE)) {
 +		zval_ptr_dtor(&errors);
 +		RETURN_THROWS();
 +	}
 +
 +	ZEND_ASSERT(Z_ISUNDEF(errors));
 +}
 +
 +void php_uri_property_write_str_helper(INTERNAL_FUNCTION_PARAMETERS, php_uri_property_name property_name)
 +{
 +	zend_string *value;
 +
 +	ZEND_PARSE_PARAMETERS_START(1, 1)
 +		Z_PARAM_STR(value)
 +	ZEND_PARSE_PARAMETERS_END();
 +
 +	zval zv;
 +	ZVAL_STR(&zv, value);
 +
 +	php_uri_property_write_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, property_name, &zv);
 +}
 +
 +void php_uri_property_write_str_or_null_helper(INTERNAL_FUNCTION_PARAMETERS, php_uri_property_name property_name)
 +{
 +	zend_string *value;
 +
 +	ZEND_PARSE_PARAMETERS_START(1, 1)
 +		Z_PARAM_STR_OR_NULL(value)
 +	ZEND_PARSE_PARAMETERS_END();
 +
 +	zval zv;
 +	if (value == NULL) {
 +		ZVAL_NULL(&zv);
 +	} else {
 +		ZVAL_STR(&zv, value);
 +	}
 +
 +	php_uri_property_write_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, property_name, &zv);
 +}
 +
 +void php_uri_property_write_long_or_null_helper(INTERNAL_FUNCTION_PARAMETERS, php_uri_property_name property_name)
 +{
 +	zend_long value;
 +	bool value_is_null;
 +
 +	ZEND_PARSE_PARAMETERS_START(1, 1)
 +		Z_PARAM_LONG_OR_NULL(value, value_is_null)
 +	ZEND_PARSE_PARAMETERS_END();
 +
 +	zval zv;
 +	if (value_is_null) {
 +		ZVAL_NULL(&zv);
 +	} else {
 +		ZVAL_LONG(&zv, value);
 +	}
 +
 +	php_uri_property_write_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, property_name, &zv);
 +}
diff --cc ext/uri/tests/oom_rfc3986_component_read.phpt
index 00000000000,00000000000..97fff11c5eb
new file mode 100644
--- /dev/null
+++ b/ext/uri/tests/oom_rfc3986_component_read.phpt
@@@ -1,0 -1,0 +1,17 @@@
++--TEST--
++Uri\Rfc3986\Uri::getPath() doesn't reach hard memory limit
++--EXTENSIONS--
++uri
++--INI--
++memory_limit=8M
++memory_hard_limit_percent=125
++--FILE--
++<?php
++$uri = new Uri\Rfc3986\Uri("http://EXAMPLE.COM/" . str_repeat("a/../", 100000));
++
++$uri->getPath();
++
++echo "not reached\n";
++?>
++--EXPECTF--
++Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/uri/tests/oom_rfc3986_equals.phpt
index 00000000000,00000000000..c4d0e98223a
new file mode 100644
--- /dev/null
+++ b/ext/uri/tests/oom_rfc3986_equals.phpt
@@@ -1,0 -1,0 +1,18 @@@
++--TEST--
++Uri\Rfc3986\Uri::equals() doesn't reach hard memory limit
++--EXTENSIONS--
++uri
++--INI--
++memory_limit=8M
++memory_hard_limit_percent=125
++--FILE--
++<?php
++$a = new Uri\Rfc3986\Uri("http://EXAMPLE.COM/" . str_repeat("a/../", 60000));
++$b = clone $a;
++
++$a->equals($b);
++
++echo "not reached\n";
++?>
++--EXPECTF--
++Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/uri/tests/oom_rfc3986_parse.phpt
index 00000000000,00000000000..0388b6e8832
new file mode 100644
--- /dev/null
+++ b/ext/uri/tests/oom_rfc3986_parse.phpt
@@@ -1,0 -1,0 +1,14 @@@
++--TEST--
++Uri\Rfc3986\Uri::parse() doesn't reach hard memory limit
++--EXTENSIONS--
++uri
++--INI--
++memory_limit=8M
++--FILE--
++<?php
++Uri\Rfc3986\Uri::parse(str_repeat("/", 1024 * 1024));
++
++echo "not reached\n";
++?>
++--EXPECTF--
++Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/uri/tests/oom_rfc3986_path_segments.phpt
index 00000000000,00000000000..4fb2c6088ec
new file mode 100644
--- /dev/null
+++ b/ext/uri/tests/oom_rfc3986_path_segments.phpt
@@@ -1,0 -1,0 +1,14 @@@
++--TEST--
++Uri\Rfc3986\Uri with many path segments doesn't reach hard memory limit
++--EXTENSIONS--
++uri
++--INI--
++memory_limit=8M
++--FILE--
++<?php
++new Uri\Rfc3986\Uri(str_repeat("/", 1024 * 1024));
++
++echo "not reached\n";
++?>
++--EXPECTF--
++Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/uri/tests/oom_rfc3986_resolve.phpt
index 00000000000,00000000000..3116bcb4660
new file mode 100644
--- /dev/null
+++ b/ext/uri/tests/oom_rfc3986_resolve.phpt
@@@ -1,0 -1,0 +1,16 @@@
++--TEST--
++Uri\Rfc3986\Uri::resolve() doesn't reach hard memory limit
++--EXTENSIONS--
++uri
++--INI--
++memory_limit=8M
++--FILE--
++<?php
++$base = new Uri\Rfc3986\Uri("http://example.com/a/b");
++
++$base->resolve(str_repeat("/", 1024 * 1024));
++
++echo "not reached\n";
++?>
++--EXPECTF--
++Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/uri/tests/oom_rfc3986_to_string.phpt
index 00000000000,00000000000..ffc62b6b9a1
new file mode 100644
--- /dev/null
+++ b/ext/uri/tests/oom_rfc3986_to_string.phpt
@@@ -1,0 -1,0 +1,17 @@@
++--TEST--
++Uri\Rfc3986\Uri::toString() doesn't reach hard memory limit
++--EXTENSIONS--
++uri
++--INI--
++memory_limit=8M
++memory_hard_limit_percent=125
++--FILE--
++<?php
++$uri = new Uri\Rfc3986\Uri("http://EXAMPLE.COM/" . str_repeat("a/../", 100000));
++
++$uri->toString();
++
++echo "not reached\n";
++?>
++--EXPECTF--
++Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/uri/tests/oom_rfc3986_with_path.phpt
index 00000000000,00000000000..f4a35d29f92
new file mode 100644
--- /dev/null
+++ b/ext/uri/tests/oom_rfc3986_with_path.phpt
@@@ -1,0 -1,0 +1,16 @@@
++--TEST--
++Uri\Rfc3986\Uri::withPath() doesn't reach hard memory limit
++--EXTENSIONS--
++uri
++--INI--
++memory_limit=8M
++--FILE--
++<?php
++$uri = new Uri\Rfc3986\Uri("http://example.com/");
++
++$uri->withPath("/" . str_repeat("a/", 300000));
++
++echo "not reached\n";
++?>
++--EXPECTF--
++Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/uri/tests/oom_whatwg_idna_host.phpt
index 00000000000,00000000000..321ed6091a7
new file mode 100644
--- /dev/null
+++ b/ext/uri/tests/oom_whatwg_idna_host.phpt
@@@ -1,0 -1,0 +1,17 @@@
++--TEST--
++Uri\WhatWg\Url with a long non-ASCII host doesn't reach hard memory limit
++--EXTENSIONS--
++uri
++--INI--
++memory_limit=8M
++--FILE--
++<?php
++/* A non-ASCII host goes through IDNA */
++$host = str_repeat("\u{e4}", (int) (1.5 * 1024 * 1024));
++
++Uri\WhatWg\Url::parse("http://" . $host . "/");
++
++echo "not reached\n";
++?>
++--EXPECTF--
++Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/uri/tests/oom_whatwg_idna_host_write.phpt
index 00000000000,00000000000..a1e2fa11a12
new file mode 100644
--- /dev/null
+++ b/ext/uri/tests/oom_whatwg_idna_host_write.phpt
@@@ -1,0 -1,0 +1,17 @@@
++--TEST--
++Uri\WhatWg\Url::withHost() with a long non-ASCII host reports the memory limit
++--EXTENSIONS--
++uri
++--INI--
++memory_limit=8M
++--FILE--
++<?php
++$host = str_repeat("\u{e4}", (int) (1.5 * 1024 * 1024));
++
++$url = Uri\WhatWg\Url::parse("http://example.com/");
++$url->withHost($host);
++
++echo "not reached\n";
++?>
++--EXPECTF--
++Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/uri/tests/oom_whatwg_resolve.phpt
index 00000000000,00000000000..7c2b5e65113
new file mode 100644
--- /dev/null
+++ b/ext/uri/tests/oom_whatwg_resolve.phpt
@@@ -1,0 -1,0 +1,18 @@@
++--TEST--
++Uri\WhatWg\Url::resolve() doesn't reach hard memory limit
++--EXTENSIONS--
++uri
++--INI--
++memory_limit=8M
++--FILE--
++<?php
++/* The relative reference carries the host, so resolving it runs IDNA while
++ * the base URL is still held. */
++$base = Uri\WhatWg\Url::parse("http://example.com/");
++
++$base->resolve("//" . str_repeat("\u{e4}", 1200 * 1024) . "/");
++
++echo "not reached\n";
++?>
++--EXPECTF--
++Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/uri/tests/oom_whatwg_to_unicode_string.phpt
index 00000000000,00000000000..bb9317acafa
new file mode 100644
--- /dev/null
+++ b/ext/uri/tests/oom_whatwg_to_unicode_string.phpt
@@@ -1,0 -1,0 +1,16 @@@
++--TEST--
++Uri\WhatWg\Url::toUnicodeString() doesn't reach hard memory limit
++--EXTENSIONS--
++uri
++--INI--
++memory_limit=8M
++--FILE--
++<?php
++$url = Uri\WhatWg\Url::parse("http://" . str_repeat("\u{e4}", 1200 * 1024) . "/");
++
++$url->toUnicodeString();
++
++echo "not reached\n";
++?>
++--EXPECTF--
++Fatal error: Allowed memory size of 8388608 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/uri/tests/oom_whatwg_validation_errors.phpt
index 00000000000,00000000000..0624b0bb7e8
new file mode 100644
--- /dev/null
+++ b/ext/uri/tests/oom_whatwg_validation_errors.phpt
@@@ -1,0 -1,0 +1,16 @@@
++--TEST--
++Uri\WhatWg\Url validation errors don't reach hard memory limit
++--EXTENSIONS--
++uri
++--INI--
++memory_limit=16M
++--FILE--
++<?php
++$errors = null;
++
++new Uri\WhatWg\Url("https://example.com/" . str_repeat("%", 100 * 1024), null, $errors);
++
++echo "not reached\n";
++?>
++--EXPECTF--
++Fatal error: Allowed memory size of 16777216 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
diff --cc ext/uri/uri_parser_rfc3986.c
index 6c0bdec4b11,00000000000..d56d053aae5
mode 100644,000000..100644
--- a/ext/uri/uri_parser_rfc3986.c
+++ b/ext/uri/uri_parser_rfc3986.c
@@@ -1,635 -1,0 +1,725 @@@
 +/*
 +   +----------------------------------------------------------------------+
 +   | Copyright (c) The PHP Group                                          |
 +   +----------------------------------------------------------------------+
 +   | This source file is subject to version 3.01 of the PHP license,      |
 +   | that is bundled with this package in the file LICENSE, and is        |
 +   | available through the world-wide-web at the following url:           |
 +   | https://www.php.net/license/3_01.txt                                 |
 +   | If you did not receive a copy of the PHP license and are unable to   |
 +   | obtain it through the world-wide-web, please send a note to          |
 +   | license@php.net so we can mail you a copy immediately.               |
 +   +----------------------------------------------------------------------+
 +   | Authors: Máté Kocsis <kocsismate@php.net>                            |
 +   +----------------------------------------------------------------------+
 +*/
 +
 +#include "php.h"
 +#include "uri_parser_rfc3986.h"
 +#include "php_uri_common.h"
 +#include "Zend/zend_smart_str.h"
 +#include "Zend/zend_exceptions.h"
 +
 +#include <uriparser/Uri.h>
 +
 +struct php_uri_parser_rfc3986_uris {
 +	UriUriA uri;
 +	UriUriA normalized_uri;
 +	bool normalized_uri_initialized;
 +};
 +
 +static void *php_uri_parser_rfc3986_memory_manager_malloc(UriMemoryManager *memory_manager, size_t size)
 +{
- 	return emalloc(size);
++	return femalloc(size, /* fallible */ true);
 +}
 +
 +static void *php_uri_parser_rfc3986_memory_manager_calloc(UriMemoryManager *memory_manager, size_t nmemb, size_t size)
 +{
- 	return ecalloc(nmemb, size);
++	return fecalloc(nmemb, size, /* fallible */ true);
 +}
 +
 +static void *php_uri_parser_rfc3986_memory_manager_realloc(UriMemoryManager *memory_manager, void *ptr, size_t size)
 +{
- 	return erealloc(ptr, size);
++	return safe_ferealloc(ptr, 1, size, 0, /* fallible */ true);
 +}
 +
 +static void *php_uri_parser_rfc3986_memory_manager_reallocarray(UriMemoryManager *memory_manager, void *ptr, size_t nmemb, size_t size)
 +{
- 	return safe_erealloc(ptr, nmemb, size, 0);
++	return safe_ferealloc(ptr, nmemb, size, 0, /* fallible */ true);
 +}
 +
 +static void php_uri_parser_rfc3986_memory_manager_destroy(UriMemoryManager *memory_manager, void *ptr)
 +{
 +	efree(ptr);
 +}
 +
 +static const UriMemoryManager php_uri_parser_rfc3986_memory_manager = {
 +	.malloc = php_uri_parser_rfc3986_memory_manager_malloc,
 +	.calloc = php_uri_parser_rfc3986_memory_manager_calloc,
 +	.realloc = php_uri_parser_rfc3986_memory_manager_realloc,
 +	.reallocarray = php_uri_parser_rfc3986_memory_manager_reallocarray,
 +	.free = php_uri_parser_rfc3986_memory_manager_destroy,
 +	.userData = NULL,
 +};
 +
 +/* The library expects a pointer to a non-const UriMemoryManager, but does
 + * not actually modify it (and neither does our implementation). Use a
 + * const struct with a non-const pointer for convenience. */
 +static UriMemoryManager* const mm = (UriMemoryManager*)&php_uri_parser_rfc3986_memory_manager;
 +
++static bool handle_pending_oom(void)
++{
++	if (ZEND_HAS_OOM()) {
++		zend_handle_delayed_oom();
++		return true;
++	}
++
++	return false;
++}
++
 +static inline size_t get_text_range_length(const UriTextRangeA *range)
 +{
 +	return range->afterLast - range->first;
 +}
 +
 +static inline bool has_text_range(const UriTextRangeA *range)
 +{
 +	return range->first != NULL && range->afterLast != NULL;
 +}
 +
- ZEND_ATTRIBUTE_NONNULL static void copy_uri(UriUriA *new_uriparser_uri, const UriUriA *uriparser_uri)
++/* Returns false when the copy could not be allocated. */
++ZEND_ATTRIBUTE_NONNULL static bool copy_uri(UriUriA *new_uriparser_uri, const UriUriA *uriparser_uri)
 +{
 +	int result = uriCopyUriMmA(new_uriparser_uri, uriparser_uri, mm);
- 	ZEND_ASSERT(result == URI_SUCCESS);
++	ZEND_ASSERT(result == URI_SUCCESS || result == URI_ERROR_MALLOC);
++
++	return result == URI_SUCCESS;
 +}
 +
++/* Returns NULL when the normalized form could not be allocated. */
 +ZEND_ATTRIBUTE_NONNULL static UriUriA *get_normalized_uri(php_uri_parser_rfc3986_uris *uriparser_uris) {
 +	if (!uriparser_uris->normalized_uri_initialized) {
- 		copy_uri(&uriparser_uris->normalized_uri, &uriparser_uris->uri);
++		if (UNEXPECTED(!copy_uri(&uriparser_uris->normalized_uri, &uriparser_uris->uri))) {
++			return NULL;
++		}
 +		int result = uriNormalizeSyntaxExMmA(&uriparser_uris->normalized_uri, (unsigned int)-1, mm);
- 		ZEND_ASSERT(result == URI_SUCCESS);
++		ZEND_ASSERT(result == URI_SUCCESS || result == URI_ERROR_MALLOC);
++		if (UNEXPECTED(result != URI_SUCCESS)) {
++			uriFreeUriMembersMmA(&uriparser_uris->normalized_uri, mm);
++			memset(&uriparser_uris->normalized_uri, 0, sizeof(uriparser_uris->normalized_uri));
++			return NULL;
++		}
 +		uriparser_uris->normalized_uri_initialized = true;
 +	}
 +
 +	return &uriparser_uris->normalized_uri;
 +}
 +
 +ZEND_ATTRIBUTE_NONNULL static UriUriA *get_uri_for_reading(php_uri_parser_rfc3986_uris *uriparser_uris, php_uri_component_read_mode read_mode)
 +{
 +	switch (read_mode) {
 +		case PHP_URI_COMPONENT_READ_MODE_RAW:
 +			return &uriparser_uris->uri;
 +		case PHP_URI_COMPONENT_READ_MODE_NORMALIZED_ASCII:
 +			ZEND_FALLTHROUGH;
 +		case PHP_URI_COMPONENT_READ_MODE_NORMALIZED_UNICODE:
 +			return get_normalized_uri(uriparser_uris);
 +		EMPTY_SWITCH_DEFAULT_CASE()
 +	}
 +}
 +
 +ZEND_ATTRIBUTE_NONNULL static UriUriA *get_uri_for_writing(php_uri_parser_rfc3986_uris *uriparser_uris)
 +{
 +	return &uriparser_uris->uri;
 +}
 +
 +ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_scheme_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
 +{
 +	const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
++	if (UNEXPECTED(uriparser_uri == NULL)) {
++		return FAILURE;
++	}
 +
 +	if (has_text_range(&uriparser_uri->scheme)) {
 +		ZVAL_STRINGL(retval, uriparser_uri->scheme.first, get_text_range_length(&uriparser_uri->scheme));
 +	} else {
 +		ZVAL_NULL(retval);
 +	}
 +
 +	return SUCCESS;
 +}
 +
 +static zend_result php_uri_parser_rfc3986_scheme_write(void *uri, zval *value, zval *errors)
 +{
 +	UriUriA *uriparser_uri = get_uri_for_writing(uri);
 +	int result;
 +
 +	if (Z_TYPE_P(value) == IS_NULL) {
 +		result = uriSetSchemeMmA(uriparser_uri, NULL, NULL, mm);
 +	} else {
 +		result = uriSetSchemeMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm);
 +	}
 +
 +	switch (result) {
 +		case URI_SUCCESS:
 +			return SUCCESS;
 +		case URI_ERROR_SYNTAX:
 +			zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified scheme is malformed", 0);
 +			return FAILURE;
 +		default:
++			if (handle_pending_oom()) {
++				return FAILURE;
++			}
++
 +			/* This should be unreachable in practice. */
 +			zend_throw_exception(php_uri_ce_error, "Failed to update the scheme", 0);
 +			return FAILURE;
 +	}
 +}
 +
 +ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_userinfo_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
 +{
 +	const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
++	if (UNEXPECTED(uriparser_uri == NULL)) {
++		return FAILURE;
++	}
 +
 +	if (has_text_range(&uriparser_uri->userInfo)) {
 +		ZVAL_STRINGL(retval, uriparser_uri->userInfo.first, get_text_range_length(&uriparser_uri->userInfo));
 +	} else {
 +		ZVAL_NULL(retval);
 +	}
 +
 +	return SUCCESS;
 +}
 +
 +zend_result php_uri_parser_rfc3986_userinfo_write(void *uri, zval *value, zval *errors)
 +{
 +	UriUriA *uriparser_uri = get_uri_for_writing(uri);
 +	int result;
 +
 +	if (Z_TYPE_P(value) == IS_NULL) {
 +		result = uriSetUserInfoMmA(uriparser_uri, NULL, NULL, mm);
 +	} else {
 +		result = uriSetUserInfoMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm);
 +	}
 +
 +	switch (result) {
 +		case URI_SUCCESS:
 +			return SUCCESS;
 +		case URI_ERROR_SETUSERINFO_HOST_NOT_SET:
 +			zend_throw_exception(php_uri_ce_invalid_uri_exception, "Cannot set a userinfo without having a host", 0);
 +			return FAILURE;
 +		case URI_ERROR_SYNTAX:
 +			zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified userinfo is malformed", 0);
 +			return FAILURE;
 +		default:
++			if (handle_pending_oom()) {
++				return FAILURE;
++			}
++
 +			/* This should be unreachable in practice. */
 +			zend_throw_exception(php_uri_ce_error, "Failed to update the userinfo", 0);
 +			return FAILURE;
 +	}
 +}
 +
 +ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_username_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
 +{
 +	const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
++	if (UNEXPECTED(uriparser_uri == NULL)) {
++		return FAILURE;
++	}
 +
 +	if (has_text_range(&uriparser_uri->userInfo)) {
 +		size_t length = get_text_range_length(&uriparser_uri->userInfo);
 +		const char *c = memchr(uriparser_uri->userInfo.first, ':', length);
 +
 +		if (c == NULL && length > 0) {
 +			ZVAL_STRINGL(retval, uriparser_uri->userInfo.first, length);
 +		} else if (c != NULL && c - uriparser_uri->userInfo.first > 0) {
 +			ZVAL_STRINGL(retval, uriparser_uri->userInfo.first, c - uriparser_uri->userInfo.first);
 +		} else {
 +			ZVAL_EMPTY_STRING(retval);
 +		}
 +	} else {
 +		ZVAL_NULL(retval);
 +	}
 +
 +	return SUCCESS;
 +}
 +
 +ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_password_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
 +{
 +	const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
++	if (UNEXPECTED(uriparser_uri == NULL)) {
++		return FAILURE;
++	}
 +
 +	if (has_text_range(&uriparser_uri->userInfo)) {
 +		const char *c = memchr(uriparser_uri->userInfo.first, ':', get_text_range_length(&uriparser_uri->userInfo));
 +
 +		if (c != NULL && uriparser_uri->userInfo.afterLast - c - 1 > 0) {
 +			ZVAL_STRINGL(retval, c + 1, uriparser_uri->userInfo.afterLast - c - 1);
 +		} else {
 +			ZVAL_EMPTY_STRING(retval);
 +		}
 +	} else {
 +		ZVAL_NULL(retval);
 +	}
 +
 +	return SUCCESS;
 +}
 +
 +ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_host_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
 +{
 +	const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
++	if (UNEXPECTED(uriparser_uri == NULL)) {
++		return FAILURE;
++	}
 +
 +	if (has_text_range(&uriparser_uri->hostText)) {
 +		if (uriparser_uri->hostData.ip6 != NULL || uriparser_uri->hostData.ipFuture.first != NULL) {
 +			/* the textual representation of the host is always accessible in the .hostText field no matter what the host is */
 +			smart_str host_str = {0};
 +
 +			smart_str_appendc(&host_str, '[');
 +			smart_str_appendl(&host_str, uriparser_uri->hostText.first, get_text_range_length(&uriparser_uri->hostText));
 +			smart_str_appendc(&host_str, ']');
 +
 +			ZVAL_NEW_STR(retval, smart_str_extract(&host_str));
 +		} else {
 +			ZVAL_STRINGL(retval, uriparser_uri->hostText.first, get_text_range_length(&uriparser_uri->hostText));
 +		}
 +	} else {
 +		ZVAL_NULL(retval);
 +	}
 +
 +	return SUCCESS;
 +}
 +
 +static zend_result php_uri_parser_rfc3986_host_write(void *uri, zval *value, zval *errors)
 +{
 +	UriUriA *uriparser_uri = get_uri_for_writing(uri);
 +	int result;
 +
 +	if (Z_TYPE_P(value) == IS_NULL) {
 +		result = uriSetHostAutoMmA(uriparser_uri, NULL, NULL, mm);
 +	} else {
 +		result = uriSetHostAutoMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm);
 +	}
 +
 +	switch (result) {
 +		case URI_SUCCESS:
 +			return SUCCESS;
 +		case URI_ERROR_SETHOST_PORT_SET:
 +			zend_throw_exception(php_uri_ce_invalid_uri_exception, "Cannot remove the host from a URI that has a port", 0);
 +			return FAILURE;
 +		case URI_ERROR_SETHOST_USERINFO_SET:
 +			zend_throw_exception(php_uri_ce_invalid_uri_exception, "Cannot remove the host from a URI that has a userinfo", 0);
 +			return FAILURE;
 +		case URI_ERROR_SYNTAX:
 +			zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified host is malformed", 0);
 +			return FAILURE;
 +		default:
++			if (handle_pending_oom()) {
++				return FAILURE;
++			}
++
 +			/* This should be unreachable in practice. */
 +			zend_throw_exception(php_uri_ce_error, "Failed to update the host", 0);
 +			return FAILURE;
 +	}
 +}
 +
 +ZEND_ATTRIBUTE_NONNULL static zend_long port_str_to_zend_long_checked(const char *str, size_t len)
 +{
 +	if (len > MAX_LENGTH_OF_LONG) {
 +		return -1;
 +	}
 +
 +	char buf[MAX_LENGTH_OF_LONG + 1];
 +	*(char*)zend_mempcpy(buf, str, len) = 0;
 +
 +	zend_ulong result = ZEND_STRTOUL(buf, NULL, 10);
 +
 +	if (result > ZEND_LONG_MAX) {
 +		return -1;
 +	}
 +
 +	return (zend_long)result;
 +}
 +
 +ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_port_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
 +{
 +	const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
++	if (UNEXPECTED(uriparser_uri == NULL)) {
++		return FAILURE;
++	}
 +
 +	if (has_text_range(&uriparser_uri->portText) && get_text_range_length(&uriparser_uri->portText) > 0) {
 +		ZVAL_LONG(retval, port_str_to_zend_long_checked(uriparser_uri->portText.first, get_text_range_length(&uriparser_uri->portText)));
 +	} else {
 +		ZVAL_NULL(retval);
 +	}
 +
 +	return SUCCESS;
 +}
 +
 +static zend_result php_uri_parser_rfc3986_port_write(void *uri, zval *value, zval *errors)
 +{
 +	UriUriA *uriparser_uri = get_uri_for_writing(uri);
 +	int result;
 +
 +	if (Z_TYPE_P(value) == IS_NULL) {
 +		result = uriSetPortTextMmA(uriparser_uri, NULL, NULL, mm);
 +	} else {
 +		zend_string *tmp = zend_long_to_str(Z_LVAL_P(value));
 +		result = uriSetPortTextMmA(uriparser_uri, ZSTR_VAL(tmp), ZSTR_VAL(tmp) + ZSTR_LEN(tmp), mm);
 +		zend_string_release_ex(tmp, false);
 +	}
 +
 +	switch (result) {
 +		case URI_SUCCESS:
 +			return SUCCESS;
 +		case URI_ERROR_SETPORT_HOST_NOT_SET:
 +			zend_throw_exception(php_uri_ce_invalid_uri_exception, "Cannot set a port without having a host", 0);
 +			return FAILURE;
 +		case URI_ERROR_SYNTAX:
 +			zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified port is malformed", 0);
 +			return FAILURE;
 +		default:
++			if (handle_pending_oom()) {
++				return FAILURE;
++			}
++
 +			/* This should be unreachable in practice. */
 +			zend_throw_exception(php_uri_ce_error, "Failed to update the port", 0);
 +			return FAILURE;
 +	}
 +}
 +
 +ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_path_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
 +{
 +	const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
++	if (UNEXPECTED(uriparser_uri == NULL)) {
++		return FAILURE;
++	}
 +
 +	if (uriparser_uri->pathHead != NULL) {
 +		smart_str str = {0};
 +
 +		if (uriparser_uri->absolutePath || uriHasHostA(uriparser_uri)) {
 +			smart_str_appendc(&str, '/');
 +		}
 +
 +		for (const UriPathSegmentA *p = uriparser_uri->pathHead; p; p = p->next) {
 +			smart_str_appendl(&str, p->text.first, get_text_range_length(&p->text));
 +			if (p->next) {
 +				smart_str_appendc(&str, '/');
 +			}
 +		}
 +
 +		ZVAL_NEW_STR(retval, smart_str_extract(&str));
 +	} else if (uriparser_uri->absolutePath) {
 +		ZVAL_CHAR(retval, '/');
 +	} else {
 +		ZVAL_EMPTY_STRING(retval);
 +	}
 +
 +	return SUCCESS;
 +}
 +
 +static zend_result php_uri_parser_rfc3986_path_write(void *uri, zval *value, zval *errors)
 +{
 +	UriUriA *uriparser_uri = get_uri_for_writing(uri);
 +	int result;
 +
 +	if (Z_STRLEN_P(value) == 0) {
 +		result = uriSetPathMmA(uriparser_uri, NULL, NULL, mm);
 +	} else {
 +		result = uriSetPathMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm);
 +	}
 +
 +	switch (result) {
 +		case URI_SUCCESS:
 +			return SUCCESS;
 +		case URI_ERROR_SYNTAX:
 +			zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified path is malformed", 0);
 +			return FAILURE;
 +		default:
++			if (handle_pending_oom()) {
++				return FAILURE;
++			}
++
 +			/* This should be unreachable in practice. */
 +			zend_throw_exception(php_uri_ce_error, "Failed to update the path", 0);
 +			return FAILURE;
 +	}
 +}
 +
 +ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_query_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
 +{
 +	const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
++	if (UNEXPECTED(uriparser_uri == NULL)) {
++		return FAILURE;
++	}
 +
 +	if (has_text_range(&uriparser_uri->query)) {
 +		ZVAL_STRINGL(retval, uriparser_uri->query.first, get_text_range_length(&uriparser_uri->query));
 +	} else {
 +		ZVAL_NULL(retval);
 +	}
 +
 +	return SUCCESS;
 +}
 +
 +static zend_result php_uri_parser_rfc3986_query_write(void *uri, zval *value, zval *errors)
 +{
 +	UriUriA *uriparser_uri = get_uri_for_writing(uri);
 +	int result;
 +
 +	if (Z_TYPE_P(value) == IS_NULL) {
 +		result = uriSetQueryMmA(uriparser_uri, NULL, NULL, mm);
 +	} else {
 +		result = uriSetQueryMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm);
 +	}
 +
 +	switch (result) {
 +		case URI_SUCCESS:
 +			return SUCCESS;
 +		case URI_ERROR_SYNTAX:
 +			zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified query is malformed", 0);
 +			return FAILURE;
 +		default:
++			if (handle_pending_oom()) {
++				return FAILURE;
++			}
++
 +			/* This should be unreachable in practice. */
 +			zend_throw_exception(php_uri_ce_error, "Failed to update the query", 0);
 +			return FAILURE;
 +	}
 +}
 +
 +ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_fragment_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
 +{
 +	const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
++	if (UNEXPECTED(uriparser_uri == NULL)) {
++		return FAILURE;
++	}
 +
 +	if (has_text_range(&uriparser_uri->fragment)) {
 +		ZVAL_STRINGL(retval, uriparser_uri->fragment.first, get_text_range_length(&uriparser_uri->fragment));
 +	} else {
 +		ZVAL_NULL(retval);
 +	}
 +
 +	return SUCCESS;
 +}
 +
 +static zend_result php_uri_parser_rfc3986_fragment_write(void *uri, zval *value, zval *errors)
 +{
 +	UriUriA *uriparser_uri = get_uri_for_writing(uri);
 +	int result;
 +
 +	if (Z_TYPE_P(value) == IS_NULL) {
 +		result = uriSetFragmentMmA(uriparser_uri, NULL, NULL, mm);
 +	} else {
 +		result = uriSetFragmentMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm);
 +	}
 +
 +	switch (result) {
 +		case URI_SUCCESS:
 +			return SUCCESS;
 +		case URI_ERROR_SYNTAX:
 +			zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified fragment is malformed", 0);
 +			return FAILURE;
 +		default:
++			if (handle_pending_oom()) {
++				return FAILURE;
++			}
++
 +			/* This should be unreachable in practice. */
 +			zend_throw_exception(php_uri_ce_error, "Failed to update the fragment", 0);
 +			return FAILURE;
 +	}
 +}
 +
 +static php_uri_parser_rfc3986_uris *uriparser_create_uris(void)
 +{
 +	php_uri_parser_rfc3986_uris *uriparser_uris = ecalloc(1, sizeof(*uriparser_uris));
 +	uriparser_uris->normalized_uri_initialized = false;
 +
 +	return uriparser_uris;
 +}
 +
 +php_uri_parser_rfc3986_uris *php_uri_parser_rfc3986_parse_ex(const char *uri_str, size_t uri_str_len, const php_uri_parser_rfc3986_uris *uriparser_base_urls, bool silent)
 +{
 +	UriUriA uri = {0};
 +
 +	/* Parse the URI. */
 +	int result = uriParseSingleUriExMmA(&uri, uri_str, uri_str + uri_str_len, NULL, mm);
 +	if (result != URI_SUCCESS) {
- 		if (!silent) {
++		if (!handle_pending_oom() && !silent) {
 +			switch (result) {
 +				case URI_ERROR_SYNTAX:
 +					zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified URI is malformed", 0);
 +					break;
 +				default:
 +					/* This should be unreachable in practice. */
 +					zend_throw_exception(php_uri_ce_error, "Failed to parse the specified URI", 0);
 +					break;
 +			}
 +		}
 +
 +		goto fail;
 +	}
 +
 +	if (uriparser_base_urls != NULL) {
 +		UriUriA tmp = {0};
 +
 +		/* Combine the parsed URI with the base URI and store the result in 'tmp',
 +		 * since the target and source URLs must be distinct. */
 +		int result = uriAddBaseUriExMmA(&tmp, &uri, &uriparser_base_urls->uri, URI_RESOLVE_STRICTLY, mm);
 +		if (result != URI_SUCCESS) {
- 			if (!silent) {
++			if (!handle_pending_oom() && !silent) {
 +				switch (result) {
 +					case URI_ERROR_ADDBASE_REL_BASE:
 +						zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified base URI must be absolute", 0);
 +						break;
 +					default:
 +						/* This should be unreachable in practice. */
 +						zend_throw_exception(php_uri_ce_error, "Failed to resolve the specified URI against the base URI", 0);
 +						break;
 +				}
 +			}
 +
 +			goto fail;
 +		}
 +
 +		/* Store the combined URI back into 'uri'. */
 +		uriFreeUriMembersMmA(&uri, mm);
 +		uri = tmp;
 +	}
 +
 +	/* Make the resulting URI independent of the 'uri_str'. */
- 	uriMakeOwnerMmA(&uri, mm);
++	if (UNEXPECTED(uriMakeOwnerMmA(&uri, mm) != URI_SUCCESS)) {
++		/* The only documented failure is a failed allocation, and the caller
++		 * expects either a URI or a reported error. */
++		if (!handle_pending_oom() && !silent) {
++			zend_throw_exception(php_uri_ce_error, "Failed to parse the specified URI", 0);
++		}
++
++		goto fail;
++	}
 +
 +	if (has_text_range(&uri.portText) && get_text_range_length(&uri.portText) > 0) {
 +		if (port_str_to_zend_long_checked(uri.portText.first, get_text_range_length(&uri.portText)) == -1) {
 +			if (!silent) {
 +				zend_throw_exception(php_uri_ce_invalid_uri_exception, "The port is out of range", 0);
 +			}
 +
 +			goto fail;
 +		}
 +	}
 +
 +	php_uri_parser_rfc3986_uris *uriparser_uris = uriparser_create_uris();
 +	uriparser_uris->uri = uri;
 +
 +	return uriparser_uris;
 +
 + fail:
 +
 +	uriFreeUriMembersMmA(&uri, mm);
 +
 +	return NULL;
 +}
 +
 +void *php_uri_parser_rfc3986_parse(const char *uri_str, size_t uri_str_len, const void *base_url, zval *errors, bool silent)
 +{
 +	return php_uri_parser_rfc3986_parse_ex(uri_str, uri_str_len, base_url, silent);
 +}
 +
 +ZEND_ATTRIBUTE_NONNULL static void *php_uri_parser_rfc3986_clone(void *uri)
 +{
 +	const php_uri_parser_rfc3986_uris *uriparser_uris = uri;
 +
 +	php_uri_parser_rfc3986_uris *new_uriparser_uris = uriparser_create_uris();
- 	copy_uri(&new_uriparser_uris->uri, &uriparser_uris->uri);
++	if (UNEXPECTED(!copy_uri(&new_uriparser_uris->uri, &uriparser_uris->uri))) {
++		efree(new_uriparser_uris);
++		return NULL;
++	}
 +	/* Do not copy the normalized URI: The expected action after cloning is
 +	 * modifying the cloned URI (which will invalidate the cached normalized
 +	 * URI). */
 +
 +	return new_uriparser_uris;
 +}
 +
 +ZEND_ATTRIBUTE_NONNULL static zend_string *php_uri_parser_rfc3986_to_string(void *uri, php_uri_recomposition_mode recomposition_mode, bool exclude_fragment)
 +{
 +	php_uri_parser_rfc3986_uris *uriparser_uris = uri;
 +	const UriUriA *uriparser_uri;
 +
 +	if (recomposition_mode == PHP_URI_RECOMPOSITION_MODE_RAW_ASCII || recomposition_mode == PHP_URI_RECOMPOSITION_MODE_RAW_UNICODE) {
 +		uriparser_uri = &uriparser_uris->uri;
 +	} else {
 +		uriparser_uri = get_normalized_uri(uriparser_uris);
++		if (UNEXPECTED(uriparser_uri == NULL)) {
++			return NULL;
++		}
 +	}
 +
 +	int charsRequired = 0;
 +	int result = uriToStringCharsRequiredA(uriparser_uri, &charsRequired);
 +	ZEND_ASSERT(result == URI_SUCCESS);
 +
 +	charsRequired++;
 +
 +	zend_string *uri_string = zend_string_alloc(charsRequired - 1, false);
 +	result = uriToStringA(ZSTR_VAL(uri_string), uriparser_uri, charsRequired, NULL);
 +	ZEND_ASSERT(result == URI_SUCCESS);
 +
 +	if (exclude_fragment) {
 +		const char *pos = zend_memrchr(ZSTR_VAL(uri_string), '#', ZSTR_LEN(uri_string));
 +		if (pos != NULL) {
 +			uri_string = zend_string_truncate(uri_string, (pos - ZSTR_VAL(uri_string)), false);
 +			ZSTR_VAL(uri_string)[ZSTR_LEN(uri_string)] = '\0';
 +		}
 +	}
 +
 +	return uri_string;
 +}
 +
 +static void php_uri_parser_rfc3986_destroy(void *uri)
 +{
 +	php_uri_parser_rfc3986_uris *uriparser_uris = uri;
 +
 +	if (UNEXPECTED(uriparser_uris == NULL)) {
 +		return;
 +	}
 +
 +	uriFreeUriMembersMmA(&uriparser_uris->uri, mm);
 +	uriFreeUriMembersMmA(&uriparser_uris->normalized_uri, mm);
 +
 +	efree(uriparser_uris);
 +}
 +
 +PHPAPI const php_uri_parser php_uri_parser_rfc3986 = {
 +	.name = PHP_URI_PARSER_RFC3986,
 +	.parse = php_uri_parser_rfc3986_parse,
 +	.clone = php_uri_parser_rfc3986_clone,
 +	.to_string = php_uri_parser_rfc3986_to_string,
 +	.destroy = php_uri_parser_rfc3986_destroy,
 +	{
 +		.scheme = {.read = php_uri_parser_rfc3986_scheme_read, .write = php_uri_parser_rfc3986_scheme_write},
 +		.username = {.read = php_uri_parser_rfc3986_username_read, .write = NULL},
 +		.password = {.read = php_uri_parser_rfc3986_password_read, .write = NULL},
 +		.host = {.read = php_uri_parser_rfc3986_host_read, .write = php_uri_parser_rfc3986_host_write},
 +		.port = {.read = php_uri_parser_rfc3986_port_read, .write = php_uri_parser_rfc3986_port_write},
 +		.path = {.read = php_uri_parser_rfc3986_path_read, .write = php_uri_parser_rfc3986_path_write},
 +		.query = {.read = php_uri_parser_rfc3986_query_read, .write = php_uri_parser_rfc3986_query_write},
 +		.fragment = {.read = php_uri_parser_rfc3986_fragment_read, .write = php_uri_parser_rfc3986_fragment_write},
 +	}
 +};
diff --cc ext/uri/uri_parser_whatwg.c
index 9364fd5d3d8,00000000000..32988d5929b
mode 100644,000000..100644
--- a/ext/uri/uri_parser_whatwg.c
+++ b/ext/uri/uri_parser_whatwg.c
@@@ -1,662 -1,0 +1,715 @@@
 +/*
 +   +----------------------------------------------------------------------+
 +   | Copyright (c) The PHP Group                                          |
 +   +----------------------------------------------------------------------+
 +   | This source file is subject to version 3.01 of the PHP license,      |
 +   | that is bundled with this package in the file LICENSE, and is        |
 +   | available through the world-wide-web at the following url:           |
 +   | https://www.php.net/license/3_01.txt                                 |
 +   | If you did not receive a copy of the PHP license and are unable to   |
 +   | obtain it through the world-wide-web, please send a note to          |
 +   | license@php.net so we can mail you a copy immediately.               |
 +   +----------------------------------------------------------------------+
 +   | Authors: Máté Kocsis <kocsismate@php.net>                            |
 +   +----------------------------------------------------------------------+
 +*/
 +
 +#include "php.h"
 +#include "uri_parser_whatwg.h"
 +#include "php_uri_common.h"
 +#include "Zend/zend_enum.h"
 +#include "Zend/zend_smart_str.h"
 +#include "Zend/zend_exceptions.h"
 +#ifdef HAVE_ARPA_INET_H
 +#include <arpa/inet.h>
 +#endif
 +
 +ZEND_TLS lexbor_mraw_t lexbor_mraw = {0};
 +ZEND_TLS lxb_url_parser_t lexbor_parser = {0};
 +ZEND_TLS lxb_unicode_idna_t lexbor_idna = {0};
 +
 +static const size_t lexbor_mraw_byte_size = 8192;
 +
 +static zend_always_inline void zval_string_or_null_to_lexbor_str(zval *value, lexbor_str_t *lexbor_str)
 +{
 +	if (Z_TYPE_P(value) == IS_STRING && Z_STRLEN_P(value) > 0) {
 +		lexbor_str->data = (lxb_char_t *) Z_STRVAL_P(value);
 +		lexbor_str->length = Z_STRLEN_P(value);
 +	} else {
 +		ZEND_ASSERT(Z_ISNULL_P(value) || (Z_TYPE_P(value) == IS_STRING && Z_STRLEN_P(value) == 0));
 +		lexbor_str->data = (lxb_char_t *) "";
 +		lexbor_str->length = 0;
 +	}
 +}
 +
- static zend_always_inline void zval_long_or_null_to_lexbor_str(zval *value, lexbor_str_t *lexbor_str)
++static zend_always_inline bool zval_long_or_null_to_lexbor_str(zval *value, lexbor_str_t *lexbor_str)
 +{
 +	if (Z_TYPE_P(value) == IS_LONG) {
 +		ZVAL_STR(value, zend_long_to_str(Z_LVAL_P(value)));
- 		lexbor_str_init_append(lexbor_str, lexbor_parser.mraw, (const lxb_char_t *) Z_STRVAL_P(value), Z_STRLEN_P(value));
++		const lxb_char_t *appended = lexbor_str_init_append(lexbor_str, lexbor_parser.mraw, (const lxb_char_t *) Z_STRVAL_P(value), Z_STRLEN_P(value));
 +		zval_ptr_dtor_str(value);
++		if (UNEXPECTED(appended == NULL)) {
++			return false;
++		}
 +	} else {
 +		ZEND_ASSERT(Z_ISNULL_P(value));
 +		lexbor_str->data = (lxb_char_t *) "";
 +		lexbor_str->length = 0;
 +	}
++
++	return true;
 +}
 +
 +/**
 + * Creates a Uri\WhatWg\UrlValidationError class by mapping error codes listed in
 + * https://url.spec.whatwg.org/#writing to a Uri\WhatWg\UrlValidationErrorType enum.
 + * The result is passed by reference to the errors parameter.
 + */
 +static const char *fill_errors(zval *errors)
 +{
 +	if (lexbor_parser.log == NULL || lexbor_plog_length(lexbor_parser.log) == 0) {
 +		ZVAL_EMPTY_ARRAY(errors);
 +		return NULL;
 +	}
 +
 +	array_init(errors);
 +	const char *result = NULL;
 +
 +	lexbor_plog_entry_t *lxb_error;
 +	while ((lxb_error = lexbor_array_obj_pop(&lexbor_parser.log->list)) != NULL) {
 +		zval error;
 +		object_init_ex(&error, php_uri_ce_whatwg_url_validation_error);
 +		zend_update_property_string(php_uri_ce_whatwg_url_validation_error, Z_OBJ(error), ZEND_STRL("context"), (const char *) lxb_error->data);
 +
 +		const char *error_str;
 +		zval failure;
 +		switch (lxb_error->id) {
 +			case LXB_URL_ERROR_TYPE_DOMAIN_TO_ASCII:
 +				error_str = "DomainToAscii";
 +				ZVAL_TRUE(&failure);
 +				break;
 +			case LXB_URL_ERROR_TYPE_DOMAIN_TO_UNICODE:
 +				error_str = "DomainToUnicode";
 +				ZVAL_FALSE(&failure);
 +				break;
 +			case LXB_URL_ERROR_TYPE_DOMAIN_INVALID_CODE_POINT:
 +				error_str = "DomainInvalidCodePoint";
 +				ZVAL_TRUE(&failure);
 +				break;
 +			case LXB_URL_ERROR_TYPE_HOST_INVALID_CODE_POINT:
 +				error_str = "HostInvalidCodePoint";
 +				ZVAL_TRUE(&failure);
 +				break;
 +			case LXB_URL_ERROR_TYPE_IPV4_EMPTY_PART:
 +				error_str = "Ipv4EmptyPart";
 +				ZVAL_FALSE(&failure);
 +				break;
 +			case LXB_URL_ERROR_TYPE_IPV4_TOO_MANY_PARTS:
 +				error_str = "Ipv4TooManyParts";
 +				ZVAL_TRUE(&failure);
 +				break;
 +			case LXB_URL_ERROR_TYPE_IPV4_NON_NUMERIC_PART:
 +				error_str = "Ipv4NonNumericPart";
 +				ZVAL_TRUE(&failure);
 +				break;
 +			case LXB_URL_ERROR_TYPE_IPV4_NON_DECIMAL_PART:
 +				error_str = "Ipv4NonDecimalPart";
 +				ZVAL_FALSE(&failure);
 +				break;
 +			case LXB_URL_ERROR_TYPE_IPV4_OUT_OF_RANGE_PART:
 +				error_str = "Ipv4OutOfRangePart";
 +				ZVAL_TRUE(&failure);
 +				break;
 +			case LXB_URL_ERROR_TYPE_IPV6_UNCLOSED:
 +				error_str = "Ipv6Unclosed";
 +				ZVAL_TRUE(&failure);
 +				break;
 +			case LXB_URL_ERROR_TYPE_IPV6_INVALID_COMPRESSION:
 +				error_str = "Ipv6InvalidCompression";
 +				ZVAL_TRUE(&failure);
 +				break;
 +			case LXB_URL_ERROR_TYPE_IPV6_TOO_MANY_PIECES:
 +				error_str = "Ipv6TooManyPieces";
 +				ZVAL_TRUE(&failure);
 +				break;
 +			case LXB_URL_ERROR_TYPE_IPV6_MULTIPLE_COMPRESSION:
 +				error_str = "Ipv6MultipleCompression";
 +				ZVAL_TRUE(&failure);
 +				break;
 +			case LXB_URL_ERROR_TYPE_IPV6_INVALID_CODE_POINT:
 +				error_str = "Ipv6InvalidCodePoint";
 +				ZVAL_TRUE(&failure);
 +				break;
 +			case LXB_URL_ERROR_TYPE_IPV6_TOO_FEW_PIECES:
 +				error_str = "Ipv6TooFewPieces";
 +				ZVAL_TRUE(&failure);
 +				break;
 +			case LXB_URL_ERROR_TYPE_IPV4_IN_IPV6_TOO_MANY_PIECES:
 +				error_str = "Ipv4InIpv6TooManyPieces";
 +				ZVAL_TRUE(&failure);
 +				break;
 +			case LXB_URL_ERROR_TYPE_IPV4_IN_IPV6_INVALID_CODE_POINT:
 +				error_str = "Ipv4InIpv6InvalidCodePoint";
 +				ZVAL_TRUE(&failure);
 +				break;
 +			case LXB_URL_ERROR_TYPE_IPV4_IN_IPV6_OUT_OF_RANGE_PART:
 +				error_str = "Ipv4InIpv6OutOfRangePart";
 +				ZVAL_TRUE(&failure);
 +				break;
 +			case LXB_URL_ERROR_TYPE_IPV4_IN_IPV6_TOO_FEW_PARTS:
 +				error_str = "Ipv4InIpv6TooFewParts";
 +				ZVAL_TRUE(&failure);
 +				break;
 +			case LXB_URL_ERROR_TYPE_INVALID_URL_UNIT:
 +				error_str = "InvalidUrlUnit";
 +				ZVAL_FALSE(&failure);
 +				break;
 +			case LXB_URL_ERROR_TYPE_SPECIAL_SCHEME_MISSING_FOLLOWING_SOLIDUS:
 +				error_str = "SpecialSchemeMissingFollowingSolidus";
 +				ZVAL_FALSE(&failure);
 +				break;
 +			case LXB_URL_ERROR_TYPE_MISSING_SCHEME_NON_RELATIVE_URL:
 +				error_str = "MissingSchemeNonRelativeUrl";
 +				ZVAL_TRUE(&failure);
 +				break;
 +			case LXB_URL_ERROR_TYPE_INVALID_REVERSE_SOLIDUS:
 +				error_str = "InvalidReverseSoldius";
 +				ZVAL_FALSE(&failure);
 +				break;
 +			case LXB_URL_ERROR_TYPE_INVALID_CREDENTIALS:
 +				error_str = "InvalidCredentials";
 +				ZVAL_FALSE(&failure);
 +				break;
 +			case LXB_URL_ERROR_TYPE_HOST_MISSING:
 +				error_str = "HostMissing";
 +				ZVAL_TRUE(&failure);
 +				break;
 +			case LXB_URL_ERROR_TYPE_PORT_OUT_OF_RANGE:
 +				error_str = "PortOutOfRange";
 +				ZVAL_TRUE(&failure);
 +				break;
 +			case LXB_URL_ERROR_TYPE_PORT_INVALID:
 +				error_str = "PortInvalid";
 +				ZVAL_TRUE(&failure);
 +				break;
 +			case LXB_URL_ERROR_TYPE_FILE_INVALID_WINDOWS_DRIVE_LETTER:
 +				error_str = "FileInvalidWindowsDriveLetter";
 +				ZVAL_FALSE(&failure);
 +				break;
 +			case LXB_URL_ERROR_TYPE_FILE_INVALID_WINDOWS_DRIVE_LETTER_HOST:
 +				error_str = "FileInvalidWindowsDriveLetterHost";
 +				ZVAL_FALSE(&failure);
 +				break;
 +			EMPTY_SWITCH_DEFAULT_CASE()
 +		}
 +
 +		zval error_type;
 +		ZVAL_OBJ(&error_type, zend_enum_get_case_cstr(php_uri_ce_whatwg_url_validation_error_type, error_str));
 +		zend_update_property_ex(php_uri_ce_whatwg_url_validation_error, Z_OBJ(error), ZSTR_KNOWN(ZEND_STR_TYPE), &error_type);
 +
 +		zend_update_property(php_uri_ce_whatwg_url_validation_error, Z_OBJ(error), ZEND_STRL("failure"), &failure);
 +
 +		if (Z_TYPE(failure) == IS_TRUE) {
 +			result = error_str;
 +		}
 +
 +		add_next_index_zval(errors, &error);
++
++		if (ZEND_HAS_OOM()) {
++			break;
++		}
 +	}
 +
 +	return result;
 +}
 +
 +static void throw_invalid_url_exception_during_write(zval *errors, const char *component)
 +{
++	if (ZEND_HAS_OOM()) {
++		zend_handle_delayed_oom();
++		return;
++	}
++
 +	zval err;
 +	const char *reason = fill_errors(&err);
++	if (ZEND_HAS_OOM()) {
++		zval_ptr_dtor(&err);
++		zend_handle_delayed_oom();
++		return;
++	}
++
 +	zend_object *exception = zend_throw_exception_ex(
 +		php_uri_ce_whatwg_invalid_url_exception,
 +		0,
 +		"The specified %s is malformed%s%s%s",
 +		component,
 +		reason ? " (" : "",
 +		reason ? reason : "",
 +		reason ? ")" : ""
 +	);
 +	zend_update_property(exception->ce, exception, ZEND_STRL("errors"), &err);
 +	if (errors) {
 +		zval_ptr_dtor(errors);
 +		ZVAL_COPY_VALUE(errors, &err);
 +	} else {
 +		zval_ptr_dtor(&err);
 +	}
 +}
 +
 +static lxb_status_t serialize_to_smart_str_callback(const lxb_char_t *data, size_t length, void *ctx)
 +{
 +	smart_str *uri_str = ctx;
 +
 +	if (data != NULL && length > 0) {
 +		smart_str_appendl(uri_str, (const char *) data, length);
 +	}
 +
 +	return LXB_STATUS_OK;
 +}
 +
 +static zend_result php_uri_parser_whatwg_scheme_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
 +{
 +	const lxb_url_t *lexbor_uri = uri;
 +
 +	ZEND_ASSERT(lexbor_uri->scheme.type != LXB_URL_SCHEMEL_TYPE__UNDEF);
 +
 +	ZVAL_STRINGL(retval, (const char *) lexbor_uri->scheme.name.data, lexbor_uri->scheme.name.length);
 +
 +	return SUCCESS;
 +}
 +
 +static zend_result php_uri_parser_whatwg_scheme_write(void *uri, zval *value, zval *errors)
 +{
 +	lxb_url_t *lexbor_uri = uri;
 +	lexbor_str_t str = {0};
 +
 +	zval_string_or_null_to_lexbor_str(value, &str);
 +
 +	lxb_url_parser_clean(&lexbor_parser);
 +
 +	if (lxb_url_api_protocol_set(lexbor_uri, &lexbor_parser, str.data, str.length) != LXB_STATUS_OK) {
 +		throw_invalid_url_exception_during_write(errors, "scheme");
 +
 +		return FAILURE;
 +	}
 +
 +	return SUCCESS;
 +}
 +
 +/* 4.2. URL miscellaneous: A URL includes credentials if its username or password is not the empty string. */
 +static bool includes_credentials(const lxb_url_t *lexbor_uri)
 +{
 +	return lexbor_uri->username.length > 0 || lexbor_uri->password.length > 0;
 +}
 +
 +static zend_result php_uri_parser_whatwg_username_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
 +{
 +	const lxb_url_t *lexbor_uri = uri;
 +
 +	if (includes_credentials(lexbor_uri)) {
 +		ZVAL_STRINGL_FAST(retval, (const char *) lexbor_uri->username.data, lexbor_uri->username.length);
 +	} else {
 +		ZVAL_NULL(retval);
 +	}
 +
 +	return SUCCESS;
 +}
 +
 +static zend_result php_uri_parser_whatwg_username_write(void *uri, zval *value, zval *errors)
 +{
 +	lxb_url_t *lexbor_uri = uri;
 +	lexbor_str_t str = {0};
 +
 +	zval_string_or_null_to_lexbor_str(value, &str);
 +
 +	lxb_url_parser_clean(&lexbor_parser);
 +
 +	if (lxb_url_api_username_set(lexbor_uri, str.data, str.length) != LXB_STATUS_OK) {
 +		throw_invalid_url_exception_during_write(errors, "username");
 +
 +		return FAILURE;
 +	}
 +
 +	return SUCCESS;
 +}
 +
 +static zend_result php_uri_parser_whatwg_password_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
 +{
 +	const lxb_url_t *lexbor_uri = uri;
 +
 +	if (includes_credentials(lexbor_uri)) {
 +		ZVAL_STRINGL_FAST(retval, (const char *) lexbor_uri->password.data, lexbor_uri->password.length);
 +	} else {
 +		ZVAL_NULL(retval);
 +	}
 +
 +	return SUCCESS;
 +}
 +
 +static zend_result php_uri_parser_whatwg_password_write(void *uri, zval *value, zval *errors)
 +{
 +	lxb_url_t *lexbor_uri = uri;
 +	lexbor_str_t str = {0};
 +
 +	zval_string_or_null_to_lexbor_str(value, &str);
 +
 +	lxb_url_parser_clean(&lexbor_parser);
 +
 +	if (lxb_url_api_password_set(lexbor_uri, str.data, str.length) != LXB_STATUS_OK) {
 +		throw_invalid_url_exception_during_write(errors, "password");
 +
 +		return FAILURE;
 +	}
 +
 +	return SUCCESS;
 +}
 +
 +static zend_result php_uri_parser_whatwg_host_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
 +{
 +	const lxb_url_t *lexbor_uri = uri;
 +
 +	if (lexbor_uri->host.type == LXB_URL_HOST_TYPE_IPV4) {
 +		smart_str host_str = {0};
 +
 +		lxb_url_serialize_host_ipv4(lexbor_uri->host.u.ipv4, serialize_to_smart_str_callback, &host_str);
++		if (ZEND_HAS_OOM()) {
++			smart_str_free(&host_str);
++			return FAILURE;
++		}
 +
 +		ZVAL_NEW_STR(retval, smart_str_extract(&host_str));
 +	} else if (lexbor_uri->host.type == LXB_URL_HOST_TYPE_IPV6) {
 +		smart_str host_str = {0};
 +
 +		smart_str_appendc(&host_str, '[');
 +		lxb_url_serialize_host_ipv6(lexbor_uri->host.u.ipv6, serialize_to_smart_str_callback, &host_str);
 +		smart_str_appendc(&host_str, ']');
++		if (ZEND_HAS_OOM()) {
++			smart_str_free(&host_str);
++			return FAILURE;
++		}
 +
 +		ZVAL_NEW_STR(retval, smart_str_extract(&host_str));
 +	} else if (lexbor_uri->host.type == LXB_URL_HOST_TYPE_EMPTY) {
 +		ZVAL_EMPTY_STRING(retval);
 +	} else if (lexbor_uri->host.type != LXB_URL_HOST_TYPE__UNDEF) {
 +		switch (read_mode) {
 +			case PHP_URI_COMPONENT_READ_MODE_NORMALIZED_UNICODE: {
 +				smart_str host_str = {0};
 +				lxb_url_serialize_host_unicode(&lexbor_idna, &lexbor_uri->host, serialize_to_smart_str_callback, &host_str);
 +				lxb_unicode_idna_clean(&lexbor_idna);
++				if (ZEND_HAS_OOM()) {
++					smart_str_free(&host_str);
++					return FAILURE;
++				}
 +
 +				ZVAL_STR(retval, smart_str_extract(&host_str));
 +				break;
 +			}
 +			case PHP_URI_COMPONENT_READ_MODE_NORMALIZED_ASCII:
 +				ZEND_FALLTHROUGH;
 +			case PHP_URI_COMPONENT_READ_MODE_RAW:
 +				ZVAL_STRINGL(retval, (const char *) lexbor_uri->host.u.domain.data, lexbor_uri->host.u.domain.length);
 +				break;
 +			EMPTY_SWITCH_DEFAULT_CASE()
 +		}
 +	} else {
 +		ZVAL_NULL(retval);
 +	}
 +
 +	return SUCCESS;
 +}
 +
 +static zend_result php_uri_parser_whatwg_host_write(void *uri, zval *value, zval *errors)
 +{
 +	lxb_url_t *lexbor_uri = uri;
 +	lexbor_str_t str = {0};
 +
 +	zval_string_or_null_to_lexbor_str(value, &str);
 +
 +	lxb_url_parser_clean(&lexbor_parser);
 +
 +	if (lxb_url_api_hostname_set(lexbor_uri, &lexbor_parser, str.data, str.length) != LXB_STATUS_OK) {
 +		throw_invalid_url_exception_during_write(errors, "host");
 +
 +		return FAILURE;
 +	}
 +
 +	return SUCCESS;
 +}
 +
 +static zend_result php_uri_parser_whatwg_port_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
 +{
 +	const lxb_url_t *lexbor_uri = uri;
 +
 +	if (lexbor_uri->has_port) {
 +		ZVAL_LONG(retval, lexbor_uri->port);
 +	} else {
 +		ZVAL_NULL(retval);
 +	}
 +
 +	return SUCCESS;
 +}
 +
 +static zend_result php_uri_parser_whatwg_port_write(void *uri, zval *value, zval *errors)
 +{
 +	lxb_url_t *lexbor_uri = uri;
 +	lexbor_str_t str = {0};
 +
- 	zval_long_or_null_to_lexbor_str(value, &str);
++	if (UNEXPECTED(!zval_long_or_null_to_lexbor_str(value, &str))) {
++		ZEND_ASSERT(ZEND_HAS_OOM());
++		zend_handle_delayed_oom();
++		return FAILURE;
++	}
 +
 +	lxb_url_parser_clean(&lexbor_parser);
 +
 +	if (lxb_url_api_port_set(lexbor_uri, &lexbor_parser, str.data, str.length) != LXB_STATUS_OK) {
 +		throw_invalid_url_exception_during_write(errors, "port");
 +
 +		return FAILURE;
 +	}
 +
 +	return SUCCESS;
 +}
 +
 +static zend_result php_uri_parser_whatwg_path_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
 +{
 +	const lxb_url_t *lexbor_uri = uri;
 +
 +	if (lexbor_uri->path.str.length > 0) {
 +		ZVAL_STRINGL(retval, (const char *) lexbor_uri->path.str.data, lexbor_uri->path.str.length);
 +	} else {
 +		ZVAL_EMPTY_STRING(retval);
 +	}
 +
 +	return SUCCESS;
 +}
 +
 +static zend_result php_uri_parser_whatwg_path_write(void *uri, zval *value, zval *errors)
 +{
 +	lxb_url_t *lexbor_uri = uri;
 +	lexbor_str_t str = {0};
 +
 +	zval_string_or_null_to_lexbor_str(value, &str);
 +
 +	lxb_url_parser_clean(&lexbor_parser);
 +
 +	if (lxb_url_api_pathname_set(lexbor_uri, &lexbor_parser, str.data, str.length) != LXB_STATUS_OK) {
 +		throw_invalid_url_exception_during_write(errors, "path");
 +
 +		return FAILURE;
 +	}
 +
 +	return SUCCESS;
 +}
 +
 +static zend_result php_uri_parser_whatwg_query_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
 +{
 +	const lxb_url_t *lexbor_uri = uri;
 +
 +	if (lexbor_uri->query.data != NULL) {
 +		ZVAL_STRINGL(retval, (const char *) lexbor_uri->query.data, lexbor_uri->query.length);
 +	} else {
 +		ZVAL_NULL(retval);
 +	}
 +
 +	return SUCCESS;
 +}
 +
 +static zend_result php_uri_parser_whatwg_query_write(void *uri, zval *value, zval *errors)
 +{
 +	lxb_url_t *lexbor_uri = uri;
 +	lexbor_str_t str = {0};
 +
 +	zval_string_or_null_to_lexbor_str(value, &str);
 +
 +	lxb_url_parser_clean(&lexbor_parser);
 +
 +	if (lxb_url_api_search_set(lexbor_uri, &lexbor_parser, str.data, str.length) != LXB_STATUS_OK) {
 +		throw_invalid_url_exception_during_write(errors, "query string");
 +
 +		return FAILURE;
 +	}
 +
 +	return SUCCESS;
 +}
 +
 +static zend_result php_uri_parser_whatwg_fragment_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
 +{
 +	const lxb_url_t *lexbor_uri = uri;
 +
 +	if (lexbor_uri->fragment.data != NULL) {
 +		ZVAL_STRINGL(retval, (const char *) lexbor_uri->fragment.data, lexbor_uri->fragment.length);
 +	} else {
 +		ZVAL_NULL(retval);
 +	}
 +
 +	return SUCCESS;
 +}
 +
 +static zend_result php_uri_parser_whatwg_fragment_write(void *uri, zval *value, zval *errors)
 +{
 +	lxb_url_t *lexbor_uri = uri;
 +	lexbor_str_t str = {0};
 +
 +	zval_string_or_null_to_lexbor_str(value, &str);
 +
 +	lxb_url_parser_clean(&lexbor_parser);
 +
 +	if (lxb_url_api_hash_set(lexbor_uri, &lexbor_parser, str.data, str.length) != LXB_STATUS_OK) {
 +		throw_invalid_url_exception_during_write(errors, "fragment");
 +
 +		return FAILURE;
 +	}
 +
 +	return SUCCESS;
 +}
 +
 +PHP_RINIT_FUNCTION(uri_parser_whatwg)
 +{
 +	lxb_status_t status;
 +
 +	status = lexbor_mraw_init(&lexbor_mraw, lexbor_mraw_byte_size);
 +	if (status != LXB_STATUS_OK) {
 +		goto fail;
 +	}
 +
 +	status = lxb_url_parser_init(&lexbor_parser, &lexbor_mraw);
 +	if (status != LXB_STATUS_OK) {
 +		goto fail;
 +	}
 +
 +	status = lxb_unicode_idna_init(&lexbor_idna);
 +	if (status != LXB_STATUS_OK) {
 +		goto fail;
 +	}
 +
 +	return SUCCESS;
 +
 + fail:
 +
 +	/* Unconditionally calling the _destroy() functions is
 +	 * safe on a zeroed structure. */
 +	lxb_unicode_idna_destroy(&lexbor_idna, false);
 +	memset(&lexbor_idna, 0, sizeof(lexbor_idna));
 +	lxb_url_parser_destroy(&lexbor_parser, false);
 +	memset(&lexbor_parser, 0, sizeof(lexbor_parser));
 +	lexbor_mraw_destroy(&lexbor_mraw, false);
 +	memset(&lexbor_mraw, 0, sizeof(lexbor_mraw));
 +
 +	return FAILURE;
 +}
 +
 +ZEND_MODULE_POST_ZEND_DEACTIVATE_D(uri_parser_whatwg)
 +{
 +	lxb_unicode_idna_destroy(&lexbor_idna, false);
 +	memset(&lexbor_idna, 0, sizeof(lexbor_idna));
 +	lxb_url_parser_destroy(&lexbor_parser, false);
 +	memset(&lexbor_parser, 0, sizeof(lexbor_parser));
 +	lexbor_mraw_destroy(&lexbor_mraw, false);
 +	memset(&lexbor_mraw, 0, sizeof(lexbor_mraw));
 +
 +	return SUCCESS;
 +}
 +
 +lxb_url_t *php_uri_parser_whatwg_parse_ex(const char *uri_str, size_t uri_str_len, const lxb_url_t *lexbor_base_url, zval *errors, bool silent)
 +{
 +	lxb_url_parser_clean(&lexbor_parser);
 +
 +	lxb_url_t *url = lxb_url_parse(&lexbor_parser, lexbor_base_url, (unsigned char *) uri_str, uri_str_len);
 +
++	if (ZEND_HAS_OOM()) {
++		zend_handle_delayed_oom();
++		return url;
++	}
++
 +	if ((url == NULL && !silent) || errors != NULL) {
 +		zval err;
 +		const char *reason = fill_errors(&err);
++		if (ZEND_HAS_OOM()) {
++			zval_ptr_dtor(&err);
++			zend_handle_delayed_oom();
++
++			return url;
++		}
++
 +		if (url == NULL && !silent) {
 +			zend_object *exception = zend_throw_exception_ex(php_uri_ce_whatwg_invalid_url_exception, 0, "The specified URI is malformed%s%s%s", reason ? " (" : "", reason ? reason : "", reason ? ")" : "");
 +			zend_update_property(exception->ce, exception, ZEND_STRL("errors"), &err);
 +		}
 +		if (errors != NULL) {
 +			zval_ptr_dtor(errors);
 +			ZVAL_COPY_VALUE(errors, &err);
 +		} else {
 +			zval_ptr_dtor(&err);
 +		}
 +	}
 +
 +	return url;
 +}
 +
 +static void *php_uri_parser_whatwg_parse(const char *uri_str, size_t uri_str_len, const void *base_url, zval *errors, bool silent)
 +{
 +	return php_uri_parser_whatwg_parse_ex(uri_str, uri_str_len, base_url, errors, silent);
 +}
 +
 +static void *php_uri_parser_whatwg_clone(void *uri)
 +{
 +	const lxb_url_t *lexbor_uri = uri;
 +
 +	return lxb_url_clone(lexbor_parser.mraw, lexbor_uri);
 +}
 +
 +static zend_string *php_uri_parser_whatwg_to_string(void *uri, php_uri_recomposition_mode recomposition_mode, bool exclude_fragment)
 +{
 +	const lxb_url_t *lexbor_uri = uri;
 +	smart_str uri_str = {0};
 +
 +	switch (recomposition_mode) {
 +		case PHP_URI_RECOMPOSITION_MODE_RAW_UNICODE:
 +			ZEND_FALLTHROUGH;
 +		case PHP_URI_RECOMPOSITION_MODE_NORMALIZED_UNICODE:
 +			lxb_url_serialize_idna(&lexbor_idna, lexbor_uri, serialize_to_smart_str_callback, &uri_str, exclude_fragment);
 +			lxb_unicode_idna_clean(&lexbor_idna);
 +			break;
 +		case PHP_URI_RECOMPOSITION_MODE_RAW_ASCII:
 +			ZEND_FALLTHROUGH;
 +		case PHP_URI_RECOMPOSITION_MODE_NORMALIZED_ASCII:
 +			lxb_url_serialize(lexbor_uri, serialize_to_smart_str_callback, &uri_str, exclude_fragment);
 +			break;
 +		EMPTY_SWITCH_DEFAULT_CASE()
 +	}
 +
++	if (ZEND_HAS_OOM()) {
++		smart_str_free(&uri_str);
++		return NULL;
++	}
++
 +	return smart_str_extract(&uri_str);
 +}
 +
 +static void php_uri_parser_whatwg_destroy(void *uri)
 +{
 +	lxb_url_t *lexbor_uri = uri;
 +
 +	lxb_url_destroy(lexbor_uri);
 +}
 +
 +PHPAPI const php_uri_parser php_uri_parser_whatwg = {
 +	.name = PHP_URI_PARSER_WHATWG,
 +	.parse = php_uri_parser_whatwg_parse,
 +	.clone = php_uri_parser_whatwg_clone,
 +	.to_string = php_uri_parser_whatwg_to_string,
 +	.destroy = php_uri_parser_whatwg_destroy,
 +	{
 +		.scheme = {.read = php_uri_parser_whatwg_scheme_read, .write = php_uri_parser_whatwg_scheme_write},
 +		.username = {.read = php_uri_parser_whatwg_username_read, .write = php_uri_parser_whatwg_username_write},
 +		.password = {.read = php_uri_parser_whatwg_password_read, .write = php_uri_parser_whatwg_password_write},
 +		.host = {.read = php_uri_parser_whatwg_host_read, .write = php_uri_parser_whatwg_host_write},
 +		.port = {.read = php_uri_parser_whatwg_port_read, .write = php_uri_parser_whatwg_port_write},
 +		.path = {.read = php_uri_parser_whatwg_path_read, .write = php_uri_parser_whatwg_path_write},
 +		.query = {.read = php_uri_parser_whatwg_query_read, .write = php_uri_parser_whatwg_query_write},
 +		.fragment = {.read = php_uri_parser_whatwg_fragment_read, .write = php_uri_parser_whatwg_fragment_write},
 +	}
 +};
diff --cc ext/xmlwriter/php_xmlwriter.c
index f9dd7f48c86,61927e81e2a..b30411bdbf0
--- a/ext/xmlwriter/php_xmlwriter.c
+++ b/ext/xmlwriter/php_xmlwriter.c
@@@ -1020,13 -1007,15 +1020,17 @@@ static void php_xmlwriter_flush(INTERNA
  		RETURN_EMPTY_STRING();
  	}
  	output_bytes = xmlTextWriterFlush(ptr);
 -	if (buffer) {
 -		const xmlChar *content = xmlBufferContent(buffer);
 -		zend_string *ret = zend_string_finit((const char *) content, strlen((const char *) content), /* persistent */ false, /* fallible */ true);
 -		if (UNEXPECTED(!ret)) {
 -			RETURN_OOM();
 -		}
 -		RETVAL_NEW_STR(ret);
 +	if (output) {
  		if (empty) {
 -			xmlBufferEmpty(buffer);
 +			RETURN_STR(smart_str_extract(output));
 +		} else if (smart_str_get_len(output) > 0) {
- 			RETURN_NEW_STR(zend_string_dup(output->s, false));
++			zend_string *ret = zend_string_finit(ZSTR_VAL(output->s), ZSTR_LEN(output->s), /* persistent */ false, /* fallible */ true);
++			if (UNEXPECTED(!ret)) {
++				RETURN_OOM();
++			}
++			RETURN_NEW_STR(ret);
 +		} else {
 +			RETURN_EMPTY_STRING();
  		}
  	} else {
  		RETVAL_LONG(output_bytes);
diff --cc ext/zend_test/test.stub.php
index 1e693707e3f,caf3fbac08e..754ac9a693a
--- a/ext/zend_test/test.stub.php
+++ b/ext/zend_test/test.stub.php
@@@ -262,9 -246,11 +262,13 @@@ namespace

      function zend_leak_bytes(int $bytes = 3): void {}

 -    function zend_test_erealloc_block_size(int $old_size, int $new_size): array {}
 +    function zend_delref(mixed $variable): void {}

 +    function zend_test_erealloc_block_size(int $old_size, int $new_size): array {}
++
+     function zend_test_emalloc(int $size): void {}
+
+     function zend_test_callback(callable $callback): void {}

      function zend_string_or_object(object|string $param): object|string {}

@@@ -346,16 -330,15 +350,21 @@@ function zend_test_override_libxml_glob

      function zend_test_log_err_debug(string $str): void {}

 +    function zend_test_compile_to_ast(string $str): string {}
 +
      function zend_test_gh18756(): void {}

 +    function zend_test_opcache_preloading(): bool {}
 +
 +    function zend_test_uri_parser(string $uri, string $parser): array { }
 +
      /** @compile-time-eval */
      function zend_test_gh19792(): void {}
+
+     /* @param resource $stream */
+     function zend_test_handle_post_data(mixed $stream, string $content_type, int $content_length): void {}
+
+     function zend_new_singleton_array(): array {}
  }

  namespace ZendTestNS {
diff --cc ext/zend_test/test_arginfo.h
index f4bd4c9ee7d,e87fd554522..820ac82fb01
Binary files differ
diff --cc ext/zlib/zlib.c
index 003ffa80111,e165d50adb3..8db7d5a1871
--- a/ext/zlib/zlib.c
+++ b/ext/zlib/zlib.c
@@@ -935,6 -942,22 +946,22 @@@ PHP_FUNCTION(inflate_init
  }
  /* }}} */

+ /* Releases *out and returns false on failure. */
+ static bool php_zlib_inflate_grow(zend_string **out, size_t buffer_used, size_t chunk_size, z_stream *Z)
+ {
 -	zend_string *grown = zend_string_frealloc(*out, ZSTR_LEN(*out) + chunk_size, 0, /* fallible */ true);
++	zend_string *grown = zend_string_fextend(*out, ZSTR_LEN(*out) + chunk_size, 0, /* fallible */ true);
+
+ 	if (UNEXPECTED(grown == NULL)) {
+ 		zend_string_release_ex(*out, 0);
+ 		return false;
+ 	}
+
+ 	*out = grown;
+ 	Z->avail_out = chunk_size;
+ 	Z->next_out = (Bytef *) ZSTR_VAL(grown) + buffer_used;
+ 	return true;
+ }
+
  /* {{{ Incrementally inflate encoded data in the specified context */
  PHP_FUNCTION(inflate_add)
  {
diff --cc main/main.c
index ad7ae8a5177,bc1725fa9bf..0db01692170
--- a/main/main.c
+++ b/main/main.c
@@@ -368,22 -335,26 +368,46 @@@ static PHP_INI_MH(OnChangeMemoryLimit
  }
  /* }}} */

 +static PHP_INI_MH(OnChangeMaxMemoryLimit)
 +{
 +	size_t value;
 +	if (new_value) {
 +		value = zend_ini_parse_uquantity_warn(new_value, entry->name);
 +	} else {
 +		value = Z_L(1) << 30; /* effectively, no limit */
 +	}
 +
 +	if (zend_set_memory_limit(value) == FAILURE) {
 +		zend_error(E_ERROR, "Failed to set memory limit to %zd bytes (Current memory usage is %zd bytes)", value, zend_memory_usage(true));
 +		return FAILURE;
 +	}
 +
 +	PG(memory_limit) = value;
 +	PG(max_memory_limit) = value;
++
++	return SUCCESS;
++}
++
+ static PHP_INI_MH(OnChangeMemoryHardLimitPercent)
+ {
+ 	zend_long value;
+ 	if (new_value) {
+ 		value = zend_ini_parse_quantity_warn(new_value, entry->name);
+ 	} else {
+ 		value = -1; /* default: 200% if memory limit < 1G, fixed 1G buffer otherwise */
+ 	}
+
+ 	if (value < 100 && value != -1) {
+ 		zend_error(E_WARNING, "memory_hard_limit_percent must be >= 100, or -1");
+ 		return FAILURE;
+ 	}
+
+ 	if (zend_set_memory_hard_limit_percent(value) == FAILURE) {
+ 		if (stage != ZEND_INI_STAGE_DEACTIVATE) {
+ 			zend_error(E_WARNING, "Failed to set memory_hard_limit_percent to " ZEND_LONG_FMT, value);
+ 			return FAILURE;
+ 		}
+ 	}

  	return SUCCESS;
  }
@@@ -858,13 -822,10 +882,14 @@@ PHP_INI_BEGIN(
  	PHP_INI_ENTRY("smtp_port",					"25",		PHP_INI_ALL,		NULL)
  	STD_PHP_INI_BOOLEAN("mail.add_x_header",			"0",		PHP_INI_SYSTEM|PHP_INI_PERDIR,		OnUpdateBool,			mail_x_header,			php_core_globals,	core_globals)
  	STD_PHP_INI_BOOLEAN("mail.mixed_lf_and_crlf",			"0",		PHP_INI_SYSTEM|PHP_INI_PERDIR,		OnUpdateBool,			mail_mixed_lf_and_crlf,			php_core_globals,	core_globals)
 +	STD_PHP_INI_ENTRY("mail.cr_lf_mode",				"crlf",		PHP_INI_SYSTEM|PHP_INI_PERDIR,		OnUpdateMailCrLfMode,		mail_cr_lf_mode,		php_core_globals,	core_globals)
  	STD_PHP_INI_ENTRY("mail.log",					NULL,		PHP_INI_SYSTEM|PHP_INI_PERDIR,		OnUpdateMailLog,			mail_log,			php_core_globals,	core_globals)
  	PHP_INI_ENTRY("browscap",					NULL,		PHP_INI_SYSTEM,		OnChangeBrowscap)
 -	PHP_INI_ENTRY("memory_limit",				"128M",		PHP_INI_ALL,		OnChangeMemoryLimit)
 -	PHP_INI_ENTRY("memory_hard_limit_percent",	NULL,		PHP_INI_ALL,		OnChangeMemoryHardLimitPercent)
 +
 +	PHP_INI_ENTRY("max_memory_limit",		"-1",		PHP_INI_SYSTEM,		OnChangeMaxMemoryLimit)
 +	PHP_INI_ENTRY("memory_limit",			"128M",		PHP_INI_ALL,		OnChangeMemoryLimit)
++	PHP_INI_ENTRY("memory_hard_limit_percent",	NULL,	PHP_INI_ALL,		OnChangeMemoryHardLimitPercent)
 +
  	PHP_INI_ENTRY("precision",					"14",		PHP_INI_ALL,		OnSetPrecision)
  	PHP_INI_ENTRY("sendmail_from",				NULL,		PHP_INI_ALL,		NULL)
  	PHP_INI_ENTRY("sendmail_path",	DEFAULT_SENDMAIL_PATH,	PHP_INI_SYSTEM,		NULL)
diff --cc main/streams/streams.c
index 123c74222e9,b76c2531d94..9b3c51149a5
--- a/main/streams/streams.c
+++ b/main/streams/streams.c
@@@ -1618,13 -1656,15 +1655,16 @@@ PHPAPI zend_string *_php_stream_copy_to
  			buflen = maxlen;
  		}
  	} else {
 -		buflen = step;
 +		buflen = CHUNK_SIZE;
  	}

- 	result = zend_string_alloc(buflen, persistent);
+ 	result = zend_string_falloc(buflen, persistent, /* fallible */ true);
+ 	if (UNEXPECTED(!result)) {
+ 		return NULL;
+ 	}
  	ptr = ZSTR_VAL(result);

 +	const int min_room = CHUNK_SIZE / 4;
  	// TODO: Propagate error?
  	while ((ret = php_stream_read(src, ptr, buflen - len)) > 0) {
  		len += ret;
@@@ -1632,12 -1672,17 +1672,17 @@@
  			if (maxlen == len) {
  				break;
  			}
 -			if (maxlen > 0 && buflen + step > maxlen) {
 +			if (maxlen > 0 && buflen + CHUNK_SIZE > maxlen) {
  				buflen = maxlen;
  			} else {
 -				buflen += step;
 +				buflen += CHUNK_SIZE;
  			}
- 			result = zend_string_extend(result, buflen, persistent);
+ 			zend_string *grown = zend_string_fextend(result, buflen, persistent, /* fallible */ true);
+ 			if (UNEXPECTED(!grown)) {
+ 				zend_string_free(result);
+ 				return NULL;
+ 			}
+ 			result = grown;
  			ptr = ZSTR_VAL(result) + len;
  		} else {
  			ptr += ret;
diff --cc tests/basic/oom_rfc1867.inc
index 00000000000,5750745975c..145fefec3a4
mode 000000,100644..100644
--- a/tests/basic/oom_rfc1867.inc
+++ b/tests/basic/oom_rfc1867.inc
@@@ -1,0 -1,88 +1,89 @@@
+ <?php
+
+ /* Helpers for the oom_rfc1867_*.phpt tests.
+  *
+  * Each test posts a multipart/form-data body to php-cgi with a small
+  * memory_limit and every input limit raised, so that only the memory limit
+  * bounds what the parser accumulates. */
+
+ const OOM_RFC1867_BOUNDARY = '---------------------------12345';
+
+ function oom_rfc1867_part($fh, string $headers, string $body): void
+ {
+     fwrite($fh, '--' . OOM_RFC1867_BOUNDARY . "\r\n" . $headers . "\r\n\r\n" . $body . "\r\n");
+ }
+
+ /* Writes $bytes bytes of $char, a chunk at a time, so the test itself doesn't
+  * need a multi-megabyte string in memory. */
+ function oom_rfc1867_fill($fh, int $bytes, string $char = 'A'): void
+ {
+     while ($bytes > 0) {
+         $chunk = min($bytes, 1024 * 1024);
+         fwrite($fh, str_repeat($char, $chunk));
+         $bytes -= $chunk;
+     }
+ }
+
+ /* Calls $writeBody() to write the body parts, appends the closing boundary,
+  * posts the result and prints what php-cgi reported.
+  *
+  * $extraIni is passed as additional -d options, for the paths that only run
+  * with a given extension configured (mbstring translation, upload progress). */
+ function oom_rfc1867_request(callable $writeBody, string $memoryLimit = '8M', array $extraIni = [], ?string $target = null): void
+ {
+     $bodyFile = tempnam(sys_get_temp_dir(), 'oom_rfc1867');
+     $fh = fopen($bodyFile, 'wb');
+     $writeBody($fh);
+     fwrite($fh, '--' . OOM_RFC1867_BOUNDARY . "--\r\n");
+     fclose($fh);
+
+     $cmd = [
+         getenv('TEST_PHP_CGI_EXECUTABLE'),
+         '-C',
+         '-n',
+         '-d', 'memory_limit=' . $memoryLimit,
+         '-d', 'post_max_size=1G',
+         '-d', 'upload_max_filesize=1G',
+         '-d', 'max_input_vars=10000000',
+         '-d', 'max_file_uploads=1000000',
+         '-d', 'max_multipart_body_parts=10000000',
+         '-d', 'max_input_time=-1',
+         '-d', 'html_errors=0',
++        '-d', 'fatal_error_backtraces=Off',
+     ];
+
+     foreach ($extraIni as $ini) {
+         $cmd[] = '-d';
+         $cmd[] = $ini;
+     }
+
+     $env = array_merge($_ENV, [
+         'REDIRECT_STATUS' => '1',
+         'CONTENT_TYPE' => 'multipart/form-data; boundary=' . OOM_RFC1867_BOUNDARY,
+         'CONTENT_LENGTH' => filesize($bodyFile),
+         'REQUEST_METHOD' => 'POST',
+         'SCRIPT_FILENAME' => $target ?? __DIR__ . '/oom_rfc1867_target.inc',
+     ]);
+
+     $spec = [
+         0 => ['file', $bodyFile, 'r'],
+         1 => ['pipe', 'w'],
+         2 => ['pipe', 'w'],
+     ];
+
+     $pipes = [];
+     $handle = proc_open($cmd, $spec, $pipes, getcwd(), $env);
+     $out = stream_get_contents($pipes[1]);
+     $err = stream_get_contents($pipes[2]);
+     fclose($pipes[1]);
+     fclose($pipes[2]);
+     proc_close($handle);
+     unlink($bodyFile);
+
+     /* Drop the CGI headers, keep the body. */
+     $out = preg_replace("/^.*?\r\n\r\n/s", '', $out);
+     echo trim(str_replace("\r\n", "\n", $out)), "\n";
+     if ($err !== '') {
+         echo 'stderr: ', trim($err), "\n";
+     }
+ }