Commit d75f79e2acf for php.net

commit d75f79e2acfb28511923f90647ae7908933feb04
Author: Gina Peter Banyard <girgias@php.net>
Date:   Mon Jul 20 22:17:04 2026 +0100

    streams: refactor _php_stream_open_wrapper_ex() to use early returns

diff --git a/main/streams/streams.c b/main/streams/streams.c
index 740a1d7a6e0..3ec92b2a221 100644
--- a/main/streams/streams.c
+++ b/main/streams/streams.c
@@ -2118,14 +2118,28 @@ PHPAPI php_stream *_php_stream_open_wrapper_ex(const char *path, const char *mod
 	path_to_open = path;

 	wrapper = php_stream_locate_url_wrapper(path, &path_to_open, options);
-	if ((options & STREAM_USE_URL) && (!wrapper || !wrapper->is_url)) {
-		if (wrapper) {
-			php_stream_wrapper_warn(wrapper, context, options,
-					ProtocolUnsupported,
-					"This function may only be used against URLs");
-		} else {
-			php_error_docref(NULL, E_WARNING, "This function may only be used against URLs");
+	if (UNEXPECTED(!wrapper)) {
+		php_stream_wrapper_warn_name(PHP_STREAM_ERROR_WRAPPER_DEFAULT_NAME, context, options, OpenFailed,
+			"Failed to open stream: no suitable wrapper could be found");
+		if (resolved_path) {
+			zend_string_release_ex(resolved_path, 0);
+		}
+		return NULL;
+	}
+	if ((options & STREAM_USE_URL) && !wrapper->is_url) {
+		php_stream_wrapper_warn(wrapper, context, options,
+			ProtocolUnsupported,
+			"This function may only be used against URLs");
+		if (resolved_path) {
+			zend_string_release_ex(resolved_path, 0);
 		}
+		return NULL;
+	}
+
+	if (!wrapper->wops->stream_opener) {
+		php_stream_wrapper_warn(wrapper, context, options,
+			NoOpener,
+			"wrapper does not support stream open");
 		if (resolved_path) {
 			zend_string_release_ex(resolved_path, 0);
 		}
@@ -2134,56 +2148,74 @@ PHPAPI php_stream *_php_stream_open_wrapper_ex(const char *path, const char *mod

 	/* wrapper name needs to be stored as wrapper can be removed in opener (user stream) */
 	char *wrapper_name = pestrdup(PHP_STREAM_ERROR_WRAPPER_NAME(wrapper), persistent);
-	if (wrapper) {
-		if (!wrapper->wops->stream_opener) {
-			php_stream_wrapper_log_warn(wrapper, context, options & ~REPORT_ERRORS,
-					NoOpener,
-					"wrapper does not support stream open");
-		} else {
-			stream = wrapper->wops->stream_opener(wrapper,
-				path_to_open, mode, options & ~REPORT_ERRORS,
-				opened_path, context STREAMS_REL_CC);
-		}
+	stream = wrapper->wops->stream_opener(wrapper,
+		path_to_open, mode, options & ~REPORT_ERRORS,
+		opened_path, context STREAMS_REL_CC);

-		/* if the caller asked for a persistent stream but the wrapper did not
-		 * return one, force an error here */
-		if (stream && persistent && !stream->is_persistent) {
-			php_stream_wrapper_log_warn(wrapper, context, options & ~REPORT_ERRORS,
-					PersistentNotSupported,
-					"wrapper does not support persistent streams");
-			php_stream_close(stream);
-			stream = NULL;
+	if (UNEXPECTED(!stream)) {
+		if (options & REPORT_ERRORS) {
+			php_stream_display_wrapper_name_errors(wrapper_name, context, PHP_STREAM_EC(OpenFailed),
+					"Failed to open stream");
+			if (opened_path && *opened_path) {
+				zend_string_release_ex(*opened_path, 0);
+				*opened_path = NULL;
+			}
 		}
-
-		if (stream) {
-			stream->wrapper = wrapper;
+		php_stream_tidy_wrapper_name_error_log(wrapper_name);
+		pefree(wrapper_name, persistent);
+		if (resolved_path) {
+			zend_string_release_ex(resolved_path, 0);
 		}
+		return NULL;
 	}

-	if (stream) {
-		if (opened_path && !*opened_path && resolved_path) {
-			*opened_path = resolved_path;
-			resolved_path = NULL;
+	/* if the caller asked for a persistent stream but the wrapper did not
+	 * return one, force an error here */
+	if (persistent && !stream->is_persistent) {
+		php_stream_wrapper_log_warn(wrapper, context, options & ~REPORT_ERRORS,
+				PersistentNotSupported,
+				"wrapper does not support persistent streams");
+		php_stream_close(stream);
+		if (options & REPORT_ERRORS) {
+			php_stream_display_wrapper_name_errors(wrapper_name, context, PHP_STREAM_EC(OpenFailed),
+					"Failed to open stream");
+			if (opened_path && *opened_path) {
+				zend_string_release_ex(*opened_path, 0);
+				*opened_path = NULL;
+			}
 		}
-		if (stream->orig_path) {
-			pefree(stream->orig_path, persistent);
+		php_stream_tidy_wrapper_name_error_log(wrapper_name);
+		pefree(wrapper_name, persistent);
+		if (resolved_path) {
+			zend_string_release_ex(resolved_path, 0);
 		}
-		stream->orig_path = pestrdup(path, persistent);
+		return NULL;
+	}
+
+	stream->wrapper = wrapper;
+
+	if (opened_path && !*opened_path && resolved_path) {
+		*opened_path = resolved_path;
+		resolved_path = NULL;
+	}
+	if (stream->orig_path) {
+		pefree(stream->orig_path, persistent);
+	}
+	stream->orig_path = pestrdup(path, persistent);
 #if ZEND_DEBUG
 		stream->open_filename = __zend_orig_filename ? __zend_orig_filename : __zend_filename;
 		stream->open_lineno = __zend_orig_lineno ? __zend_orig_lineno : __zend_lineno;
 #endif
-		/* Attach an explicitly provided context to the stream, but never the
-		 * default context: sharing it by reference would let a later
-		 * stream_context_set_option() on the stream mutate the global default
-		 * context, leaking options into every other stream. Stream errors fall
-		 * back to the default context on their own when the stream has none. */
-		if (stream->ctx == NULL && context != NULL && context != FG(default_context) && !persistent) {
-			php_stream_context_set(stream, context);
-		}
+	/* Attach an explicitly provided context to the stream, but never the
+	 * default context: sharing it by reference would let a later
+	 * stream_context_set_option() on the stream mutate the global default
+	 * context, leaking options into every other stream. Stream errors fall
+	 * back to the default context on their own when the stream has none. */
+	if (stream->ctx == NULL && context != NULL && context != FG(default_context) && !persistent) {
+		php_stream_context_set(stream, context);
 	}

-	if (stream != NULL && (options & STREAM_MUST_SEEK)) {
+	if (options & STREAM_MUST_SEEK) {
 		php_stream *newstream;

 		switch(php_stream_make_seekable_rel(stream, &newstream,
@@ -2207,16 +2239,19 @@ PHPAPI php_stream *_php_stream_open_wrapper_ex(const char *path, const char *mod
 				return newstream;
 			default:
 				php_stream_close(stream);
-				stream = NULL;
 				php_stream_wrapper_warn(wrapper, context, options,
 						SeekNotSupported,
 						"could not make seekable - %s", path);
-				/* We do not want multiple errors so we negate it */
-				options &= ~REPORT_ERRORS;
+				php_stream_tidy_wrapper_name_error_log(wrapper_name);
+				pefree(wrapper_name, persistent);
+				if (resolved_path) {
+					zend_string_release_ex(resolved_path, 0);
+				}
+				return NULL;
 		}
 	}

-	if (stream && stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0 && strchr(mode, 'a') && stream->position == 0) {
+	if (stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0 && strchr(mode, 'a') && stream->position == 0) {
 		zend_off_t newpos = 0;

 		/* if opened for append, we need to revise our idea of the initial file position */
@@ -2225,15 +2260,6 @@ PHPAPI php_stream *_php_stream_open_wrapper_ex(const char *path, const char *mod
 		}
 	}

-	if (stream == NULL && (options & REPORT_ERRORS)) {
-		php_stream_display_wrapper_name_errors(wrapper_name, context, PHP_STREAM_EC(OpenFailed),
-				"Failed to open stream");
-		if (opened_path && *opened_path) {
-			zend_string_release_ex(*opened_path, 0);
-			*opened_path = NULL;
-		}
-	}
-	php_stream_tidy_wrapper_name_error_log(wrapper_name);
 	pefree(wrapper_name, persistent);
 	if (resolved_path) {
 		zend_string_release_ex(resolved_path, 0);