Commit 0c3799a4cc0 for php.net
commit 0c3799a4cc07dfd768320a186bc6ed815fd95db9
Merge: 98b02fff3de f49b94e96b0
Author: Weilin Du <weilindu@php.net>
Date: Tue Aug 25 00:43:29 2026 +0800
Merge branch 'PHP-8.4' into PHP-8.5
* PHP-8.4:
Fix GH-23276: Collect ZipArchive subclasses holding their own streams (#23282)
diff --cc NEWS
index bb7c34323ce,5607f31081b..247fcb340ef
--- a/NEWS
+++ b/NEWS
@@@ -41,13 -45,15 +41,15 @@@ PH
. Fixed a leak when a persistent connection failed a liveness check
with no other live PDO handle. (iliaal)
-- Standard:
- . Fixed a memory leak in array_merge_recursive() when the recursive merge of
- an object converted to an array fails. (David Carlier)
+- Readline:
+ . Fixed the interactive shell not waiting for the pager process to exit.
+ (Weilin Du)
- Zip:
+ . Fixed bug GH-17787 (ZipArchive stream stops reading early when the archive
+ is freed while the stream is still open). (Eyüp Can Akman)
+ . Fixed bug GH-23276 (ZipArchive subclass storing its own stream cannot be
+ garbage collected). (Weilin Du, ndossche)
- . Fixed ZipArchive::extractTo() and ZipArchive::getFrom*() reporting success
- on corrupted entries. (David Carlier)
- SAPI:
. Fixed fuzzer targets failing to build in isolation. (Mrmaxmeier)
diff --cc ext/zip/php_zip.c
index 6071f065f51,5e640df9a10..74c8db3fefd
--- a/ext/zip/php_zip.c
+++ b/ext/zip/php_zip.c
@@@ -1035,23 -1085,25 +1042,23 @@@ static HashTable *php_zip_get_propertie
/* }}} */
#ifdef HAVE_PROGRESS_CALLBACK
-static void _php_zip_progress_callback_free(void *ptr)
+static void php_zip_progress_callback_free(void *ptr)
{
- ze_zip_object *obj = ptr;
+ php_zip_archive *archive = ptr;
- if (ZEND_FCC_INITIALIZED(obj->progress_callback)) {
- zend_fcc_dtor(&obj->progress_callback);
- if (!Z_ISUNDEF(archive->progress_callback)) {
- zval_ptr_dtor(&archive->progress_callback);
- ZVAL_UNDEF(&archive->progress_callback);
++ if (ZEND_FCC_INITIALIZED(archive->progress_callback)) {
++ zend_fcc_dtor(&archive->progress_callback);
}
}
#endif
#ifdef HAVE_CANCEL_CALLBACK
-static void _php_zip_cancel_callback_free(void *ptr)
+static void php_zip_cancel_callback_free(void *ptr)
{
- ze_zip_object *obj = ptr;
+ php_zip_archive *archive = ptr;
- if (ZEND_FCC_INITIALIZED(obj->cancel_callback)) {
- zend_fcc_dtor(&obj->cancel_callback);
- if (!Z_ISUNDEF(archive->cancel_callback)) {
- zval_ptr_dtor(&archive->cancel_callback);
- ZVAL_UNDEF(&archive->cancel_callback);
++ if (ZEND_FCC_INITIALIZED(archive->cancel_callback)) {
++ zend_fcc_dtor(&archive->cancel_callback);
}
}
#endif
@@@ -1075,17 -1139,43 +1094,43 @@@ void php_zip_archive_release(php_zip_ar
}
#ifdef HAVE_PROGRESS_CALLBACK
- /* if not properly called by libzip */
- php_zip_progress_callback_free(intern);
+ /* In case libzip did not invoke the callback state destructor. */
- _php_zip_progress_callback_free(archive);
++ php_zip_progress_callback_free(archive);
#endif
#ifdef HAVE_CANCEL_CALLBACK
- /* if not properly called by libzip */
- php_zip_cancel_callback_free(intern);
+ /* In case libzip did not invoke the callback state destructor. */
- _php_zip_cancel_callback_free(archive);
++ php_zip_cancel_callback_free(archive);
#endif
- intern->za = NULL;
+ if (archive->buffers) {
+ for (int i = 0; i < archive->buffers_cnt; i++) {
+ efree(archive->buffers[i]);
+ }
+ efree(archive->buffers);
+ }
+
+ efree(archive);
+ }
+
+ /* The caller must close or discard released_za before detaching it. */
+ static void php_zip_object_detach_archive(ze_zip_object *ze_obj, struct zip *released_za)
+ {
+ ZEND_ASSERT(ze_obj->archive != NULL);
+ ZEND_ASSERT(ze_obj->archive->za == released_za);
+ ze_obj->archive->za = NULL;
+ php_zip_archive_release(ze_obj->archive);
+ ze_obj->archive = NULL;
+ }
+
+ static void php_zip_object_free_storage(zend_object *object) /* {{{ */
+ {
+ ze_zip_object * intern = php_zip_fetch_object(object);
+
+ if (intern->archive) {
+ php_zip_archive_release(intern->archive);
+ intern->archive = NULL;
+ }
zend_object_std_dtor(&intern->zo);
if (intern->filename) {
@@@ -3081,43 -3180,41 +3135,44 @@@ PHP_METHOD(ZipArchive, getStream
}
#ifdef HAVE_PROGRESS_CALLBACK
-static void _php_zip_progress_callback(zip_t *arch, double state, void *ptr)
+static void php_zip_progress_callback(zip_t *arch, double state, void *ptr)
{
zval cb_args[1];
- ze_zip_object *obj = ptr;
- zval cb_retval;
+ php_zip_archive *archive = ptr;
ZVAL_DOUBLE(&cb_args[0], state);
- zend_call_known_fcc(&obj->progress_callback, NULL, 1, cb_args, NULL);
- if (call_user_function(EG(function_table), NULL, &archive->progress_callback, &cb_retval, 1, cb_args) == SUCCESS && !Z_ISUNDEF(cb_retval)) {
- zval_ptr_dtor(&cb_retval);
- }
++ zend_call_known_fcc(&archive->progress_callback, NULL, 1, cb_args, NULL);
}
/* {{{ register a progression callback: void callback(double state); */
PHP_METHOD(ZipArchive, registerProgressCallback)
{
struct zip *intern;
- zval *self = ZEND_THIS;
double rate;
- zend_fcall_info fci;
+ zend_fcall_info dummy_fci;
zend_fcall_info_cache fcc;
+ php_zip_archive *archive;
+ ze_zip_object *obj;
- if (zend_parse_parameters(ZEND_NUM_ARGS(), "df", &rate, &fci, &fcc) == FAILURE) {
+ if (zend_parse_parameters(ZEND_NUM_ARGS(), "dF", &rate, &dummy_fci, &fcc) == FAILURE) {
RETURN_THROWS();
}
-
- ZIP_FROM_OBJECT(intern, self);
--
- archive = Z_ZIP_P(self)->archive;
+ /* Inline ZIP_FROM_OBJECT(intern, self); */
+ obj = Z_ZIP_P(ZEND_THIS);
- intern = obj->za;
- if (!intern) { \
++ intern = php_zip_object_za(obj);
++ if (!intern) {
+ zend_value_error("Invalid or uninitialized Zip object");
+ zend_release_fcall_info_cache(&fcc);
+ RETURN_THROWS();
+ }
++ archive = obj->archive;
/* register */
- if (zip_register_progress_callback_with_state(intern, rate, php_zip_progress_callback, php_zip_progress_callback_free, obj)) {
- if (zip_register_progress_callback_with_state(intern, rate, _php_zip_progress_callback, _php_zip_progress_callback_free, archive)) {
++ if (zip_register_progress_callback_with_state(intern, rate, php_zip_progress_callback, php_zip_progress_callback_free, archive)) {
+ zend_release_fcall_info_cache(&fcc);
RETURN_FALSE;
}
- zend_fcc_dup(&obj->progress_callback, &fcc);
- ZVAL_COPY(&archive->progress_callback, &fci.function_name);
++ zend_fcc_dup(&archive->progress_callback, &fcc);
RETURN_TRUE;
}
@@@ -3125,55 -3222,41 +3180,57 @@@
#endif
#ifdef HAVE_CANCEL_CALLBACK
-static int _php_zip_cancel_callback(zip_t *arch, void *ptr)
+static int php_zip_cancel_callback(zip_t *arch, void *ptr)
{
zval cb_retval;
- ze_zip_object *obj = ptr;
- int retval = 0;
+ php_zip_archive *archive = ptr;
- zend_call_known_fcc(&obj->cancel_callback, &cb_retval, 0, NULL, NULL);
- if (call_user_function(EG(function_table), NULL, &archive->cancel_callback, &cb_retval, 0, NULL) == SUCCESS && !Z_ISUNDEF(cb_retval)) {
- retval = zval_get_long(&cb_retval);
++ zend_call_known_fcc(&archive->cancel_callback, &cb_retval, 0, NULL, NULL);
+ if (Z_ISUNDEF(cb_retval)) {
+ /* Cancel if an exception has been thrown */
+ return -1;
+ }
+ bool failed;
+ zend_long retval = zval_try_get_long(&cb_retval, &failed);
+ if (failed) {
+ zend_type_error("Return value of callback provided to ZipArchive::registerCancelCallback()"
+ " must be of type int, %s returned", zend_zval_value_name(&cb_retval));
zval_ptr_dtor(&cb_retval);
+ return -1;
}
+ zval_ptr_dtor(&cb_retval);
- return retval;
+ return (int) retval;
}
/* {{{ register a progression callback: int callback(double state); */
PHP_METHOD(ZipArchive, registerCancelCallback)
{
struct zip *intern;
- zval *self = ZEND_THIS;
- zend_fcall_info fci;
+ zend_fcall_info dummy_fci;
zend_fcall_info_cache fcc;
+ php_zip_archive *archive;
- if (zend_parse_parameters(ZEND_NUM_ARGS(), "f", &fci, &fcc) == FAILURE) {
+ ze_zip_object *obj;
+ if (zend_parse_parameters(ZEND_NUM_ARGS(), "F", &dummy_fci, &fcc) == FAILURE) {
RETURN_THROWS();
}
- ZIP_FROM_OBJECT(intern, self);
-
- archive = Z_ZIP_P(self)->archive;
+ /* Inline ZIP_FROM_OBJECT(intern, self); */
+ obj = Z_ZIP_P(ZEND_THIS);
- intern = obj->za;
- if (!intern) { \
++ intern = php_zip_object_za(obj);
++ if (!intern) {
+ zend_value_error("Invalid or uninitialized Zip object");
+ zend_release_fcall_info_cache(&fcc);
+ RETURN_THROWS();
+ }
++ archive = obj->archive;
/* register */
- if (zip_register_cancel_callback_with_state(intern, php_zip_cancel_callback, php_zip_cancel_callback_free, obj)) {
- if (zip_register_cancel_callback_with_state(intern, _php_zip_cancel_callback, _php_zip_cancel_callback_free, archive)) {
++ if (zip_register_cancel_callback_with_state(intern, php_zip_cancel_callback, php_zip_cancel_callback_free, archive)) {
+ zend_release_fcall_info_cache(&fcc);
RETURN_FALSE;
}
- zend_fcc_dup(&obj->cancel_callback, &fcc);
- ZVAL_COPY(&archive->cancel_callback, &fci.function_name);
++ zend_fcc_dup(&archive->cancel_callback, &fcc);
RETURN_TRUE;
}
diff --cc ext/zip/php_zip.h
index 382e4a70b98,8385674a1cf..e761364d0cc
--- a/ext/zip/php_zip.h
+++ b/ext/zip/php_zip.h
@@@ -65,23 -65,33 +65,33 @@@ typedef struct _ze_zip_read_rsrc
zend_long zip_rsrc_handle;
} zip_read_rsrc;
- /* Extends zend object */
- typedef struct _ze_zip_object {
+ /* Refcounted holder for the native archive state.
+ * Owned by a ZipArchive object and streams opened from it. */
+ typedef struct _php_zip_archive {
struct zip *za;
+ uint32_t refcount;
+ /* libzip reads buffers until the archive is closed, can outlive the object. */
char **buffers;
- HashTable *prop_handler;
- char *filename;
- int filename_len;
int buffers_cnt;
- zip_int64_t last_id;
- int err_zip;
- int err_sys;
#ifdef HAVE_PROGRESS_CALLBACK
- zval progress_callback;
+ zend_fcall_info_cache progress_callback;
#endif
#ifdef HAVE_CANCEL_CALLBACK
- zval cancel_callback;
+ zend_fcall_info_cache cancel_callback;
#endif
+ } php_zip_archive;
+
+ /* Extends zend object */
+ typedef struct _ze_zip_object {
+ /* NULL when there is no open archive, non-NULL otherwise.
+ * Owns one ref to the struct. */
+ php_zip_archive *archive;
+ HashTable *prop_handler;
+ char *filename;
+ int filename_len;
+ zip_int64_t last_id;
+ int err_zip;
+ int err_sys;
zend_object zo;
} ze_zip_object;