Commit 631bf8acdf4 for php.net

commit 631bf8acdf4b0e108da1718a594e77c9bcc8e88a
Author: David CARLIER <devnexen@gmail.com>
Date:   Thu Jun 4 17:25:40 2026 +0100

    Fix GH-22200: stream api memory leaks. (#22204)

    Report the stream errors before popping the operation off the stack, so
    a reentrant error handler that runs another stream operation no longer
    reuses the in-flight operation pool slot and orphans its error entry.

    While at it, free the docref in php_stream_error_entry_free(), matching the
    legacy list destructor.

diff --git a/ext/standard/tests/streams/gh22200.phpt b/ext/standard/tests/streams/gh22200.phpt
new file mode 100644
index 00000000000..01a58bbf8cf
--- /dev/null
+++ b/ext/standard/tests/streams/gh22200.phpt
@@ -0,0 +1,22 @@
+--TEST--
+GH-22200 (Memory leaks in the stream errors code on reentrant error handler)
+--FILE--
+<?php
+
+// When the warning emitted for the failing outer stream operation is reported,
+// php_stream_error_operation_end() has already popped the operation off the
+// stack. The user error handler then runs another stream operation, which
+// reuses the same operation pool slot and orphans the in-flight error entry,
+// leaking it.
+set_error_handler(function ($errno, $errstr) {
+    @file_get_contents(__DIR__ . '/gh22200-does-not-exist-inner');
+    return true;
+});
+
+file_get_contents(__DIR__ . '/gh22200-does-not-exist-outer');
+
+echo "done\n";
+
+?>
+--EXPECT--
+done
diff --git a/main/streams/stream_errors.c b/main/streams/stream_errors.c
index c4a2f74db8a..3d7056c44d1 100644
--- a/main/streams/stream_errors.c
+++ b/main/streams/stream_errors.c
@@ -215,6 +215,7 @@ static void php_stream_error_entry_free(php_stream_error_entry *entry)
 		zend_string_release(entry->message);
 		efree(entry->wrapper_name);
 		efree(entry->param);
+		efree(entry->docref);
 		efree(entry);
 		entry = next;
 	}
@@ -449,9 +450,6 @@ PHPAPI void php_stream_error_operation_end(php_stream_context *context)
 		return;
 	}

-	state->operation_depth--;
-	state->current_operation = php_stream_get_parent_operation();
-
 	if (op->error_count > 0) {
 		if (context == NULL) {
 			context = FG(default_context);
@@ -527,6 +525,9 @@ PHPAPI void php_stream_error_operation_end(php_stream_context *context)
 		}
 	}

+	state->operation_depth--;
+	state->current_operation = php_stream_get_parent_operation();
+
 	op->first_error = NULL;
 	op->last_error = NULL;
 	op->error_count = 0;