diff --git a/NEWS b/NEWS
index 42534ef4c376..6fcc446e5ee0 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,10 @@ PHP NEWS
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
?? ??? ????, PHP 8.4.27
+- CLI
+ . Fix GH-22567 (Windows ZTS CLI SAPI should refresh its TSRMLS cache during
+ request activation). (matyhtf)
+
- DOM:
. Fixed use-after-free when re-constructing a DOMXPath whose php:function
registrations are freed while still reachable from the cycle collector.
@@ -13,6 +17,8 @@ PHP NEWS
. Fixed Dom\HTMLDocument giving attributes the namespace of their element
when a fragment is parsed with an xlink, xml or xmlns context element.
(Ilia Alshanetsky)
+ . Fixed bug GH-23729 (DOMXPath::__construct() use-after-free during an
+ evaluation). (David Carlier)
- Intl:
. Fixed cloning IntlDateFormatter and MessageFormatter losing PHP-side state
@@ -28,10 +34,20 @@ PHP NEWS
. Fixed bug GH-23106 (mb_strpos() reads past the end of a haystack ending in
a truncated UTF-8 sequence). (Lazizbek Ergashev)
+- Opcache:
+ . Fixed OSS-Fuzz #546798343 (Heap-buffer-overflow in optimizer with
+ FCCs and inlining). (ndossche)
+ . Fixed bug GH-23693 (Tracing JIT produces wrong results for a guard on a
+ loop-invariant addition). (Ilia Alshanetsky)
+
- PDO:
. Fixed PDOStatement::getColumnMeta() reading out of bounds for an invalid
column index. (Ilia Alshanetsky)
+- Readline:
+ . Fixed a heap over-read in the interactive shell prompt when cli.prompt is
+ set to an empty string. (Ilia Alshanetsky)
+
- Sockets:
. Fixed socket_select() silently truncating sets larger than FD_SETSIZE on
Windows. (David Carlier)
@@ -40,9 +56,22 @@ PHP NEWS
. Fixed a crash when SQLite3::close() is called from a userland callback.
(Ilia Alshanetsky)
+- Standard:
+ . Fixed three Windows-only proc_open() defects: an uninitialized
+ PROCESS_INFORMATION, an indeterminate comspec pointer after a failed
+ lookup, and an unchecked CreateFileA() failure. (Ilia Alshanetsky)
+ . Fixed out-of-bounds read when parsing a malformed MX answer in
+ dns_get_mx(). (Ilia Alshanetsky)
+
+- XSL:
+ . Fixed bug GH-23730 (use-after-free when XSLTProcessor::importStylesheet()
+ is called during a transformation). (David Carlier)
+
- Zip:
. Fixed ZipArchive::extractTo() ignoring files given in a non-list array.
(David Carlier)
+ . Fixed bug GH-23747 (ZipArchive::close() use-after-free from a progress or
+ cancel callback). (David Carlier)
24 Sep 2026, PHP 8.4.26
diff --git a/Zend/Optimizer/optimize_func_calls.c b/Zend/Optimizer/optimize_func_calls.c
index ce6c43afaedb..5449535c560a 100644
--- a/Zend/Optimizer/optimize_func_calls.c
+++ b/Zend/Optimizer/optimize_func_calls.c
@@ -63,6 +63,7 @@ static void zend_delete_call_instructions(zend_op_array *op_array, zend_op *opli
case ZEND_DO_ICALL:
case ZEND_DO_UCALL:
case ZEND_DO_FCALL_BY_NAME:
+ case ZEND_CALLABLE_CONVERT:
call++;
break;
case ZEND_SEND_VAL:
diff --git a/ext/dom/php_dom.h b/ext/dom/php_dom.h
index 13f49879bb38..d399d745084b 100644
--- a/ext/dom/php_dom.h
+++ b/ext/dom/php_dom.h
@@ -66,6 +66,7 @@ extern zend_module_entry dom_module_entry;
typedef struct dom_xpath_object {
php_dom_xpath_callbacks xpath_callbacks;
+ uint32_t evaluation_depth;
bool register_node_ns;
dom_object dom;
} dom_xpath_object;
diff --git a/ext/dom/tests/gh23729.phpt b/ext/dom/tests/gh23729.phpt
new file mode 100644
index 000000000000..93ad67fde005
--- /dev/null
+++ b/ext/dom/tests/gh23729.phpt
@@ -0,0 +1,73 @@
+--TEST--
+GH-23729 (Use-after-free when DOMXPath is reconstructed during an evaluation)
+--CREDITS--
+djarfluka
+--EXTENSIONS--
+dom
+--FILE--
+__construct($GLOBALS['other']);
+ } catch (Error $e) {
+ echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+ }
+ return 'r';
+}
+
+function nested() {
+ echo 'nested: ', $GLOBALS['xpath']->evaluate('string(/root/b)'), PHP_EOL;
+ return 'n';
+}
+
+function test(string $class, object $doc, object $other) {
+ $xpath = new $class($doc);
+ $xpath->registerNamespace('php', 'http://php.net/xpath');
+ $xpath->registerPhpFunctions();
+
+ $GLOBALS['xpath'] = $xpath;
+ $GLOBALS['other'] = $other;
+
+ var_dump($xpath->evaluate('string(php:function("reconstruct"))'));
+ /* The evaluation the callback tried to destroy must still be usable. */
+ var_dump($xpath->evaluate('string(/root/a)'));
+ /* A nested evaluation must not lift the guard of the outer one. */
+ var_dump($xpath->evaluate('concat(php:function("nested"), php:function("reconstruct"))'));
+ var_dump($xpath->query('//b[php:function("reconstruct")]')->length);
+
+ /* Reconstructing outside of an evaluation is still allowed. */
+ $xpath->__construct($other);
+ var_dump($xpath->document->documentElement->nodeName);
+}
+
+$doc = new DOMDocument();
+$doc->loadXML('12');
+$other = new DOMDocument();
+$other->loadXML('');
+test(DOMXPath::class, $doc, $other);
+
+$doc = Dom\XMLDocument::createFromString('12');
+$other = Dom\XMLDocument::createFromString('');
+test(Dom\XPath::class, $doc, $other);
+
+?>
+--EXPECT--
+Error: Cannot call DOMXPath::__construct() while an XPath evaluation is in progress
+string(1) "r"
+string(1) "1"
+nested: 2
+Error: Cannot call DOMXPath::__construct() while an XPath evaluation is in progress
+string(2) "nr"
+Error: Cannot call DOMXPath::__construct() while an XPath evaluation is in progress
+int(1)
+string(5) "other"
+Error: Cannot call Dom\XPath::__construct() while an XPath evaluation is in progress
+string(1) "r"
+string(1) "1"
+nested: 2
+Error: Cannot call Dom\XPath::__construct() while an XPath evaluation is in progress
+string(2) "nr"
+Error: Cannot call Dom\XPath::__construct() while an XPath evaluation is in progress
+int(1)
+string(5) "other"
diff --git a/ext/dom/xpath.c b/ext/dom/xpath.c
index 4f1b3b52714e..d4367c9aaa60 100644
--- a/ext/dom/xpath.c
+++ b/ext/dom/xpath.c
@@ -126,6 +126,13 @@ static void dom_xpath_construct(INTERNAL_FUNCTION_PARAMETERS, zend_class_entry *
RETURN_THROWS();
}
+ dom_xpath_object *intern = Z_XPATHOBJ_P(ZEND_THIS);
+ if (UNEXPECTED(intern->evaluation_depth > 0)) {
+ zend_throw_error(NULL, "Cannot call %s::__construct() while an XPath evaluation is in progress",
+ ZSTR_VAL(Z_OBJCE_P(ZEND_THIS)->name));
+ RETURN_THROWS();
+ }
+
DOM_GET_OBJ(docp, doc, xmlDocPtr, docobj);
xmlXPathContextPtr ctx = xmlXPathNewContext(docp);
@@ -134,7 +141,6 @@ static void dom_xpath_construct(INTERNAL_FUNCTION_PARAMETERS, zend_class_entry *
RETURN_THROWS();
}
- dom_xpath_object *intern = Z_XPATHOBJ_P(ZEND_THIS);
xmlXPathContextPtr oldctx = intern->dom.ptr;
if (oldctx != NULL) {
php_libxml_decrement_doc_ref((php_libxml_node_object *) &intern->dom);
@@ -301,7 +307,9 @@ static void php_xpath_eval(INTERNAL_FUNCTION_PARAMETERS, int type, bool modern)
ctxp->nsNr = in_scope_ns.count;
}
+ intern->evaluation_depth++;
xmlXPathObjectPtr xpathobjp = xmlXPathEvalExpression(BAD_CAST expr, ctxp);
+ intern->evaluation_depth--;
ctxp->node = NULL;
if (register_node_ns && nodep != NULL) {
diff --git a/ext/opcache/jit/ir/ir_x86.dasc b/ext/opcache/jit/ir/ir_x86.dasc
index ca42001a8816..f5efb66698da 100644
--- a/ext/opcache/jit/ir/ir_x86.dasc
+++ b/ext/opcache/jit/ir/ir_x86.dasc
@@ -1923,6 +1923,21 @@ static bool ir_match_has_mem_deps(ir_ctx *ctx, ir_ref ref, ir_ref root)
return 0;
}
+/* A naive check if anything that emits code, and so clobbers the flags, is
+ * scheduled between the flags setting instruction and the fusion root */
+static bool ir_match_has_flags_deps(ir_ctx *ctx, ir_ref ref, ir_ref root)
+{
+ ir_ref pos = ctx->prev_ref[root];
+
+ while (pos > ref) {
+ if (ctx->ir_base[pos].op != IR_SNAPSHOT) {
+ return 1;
+ }
+ pos = ctx->prev_ref[pos];
+ }
+ return pos != ref;
+}
+
static void ir_match_fuse_load(ir_ctx *ctx, ir_ref ref, ir_ref root)
{
if (ir_in_same_block(ctx, ref) &&
@@ -3089,7 +3104,9 @@ store_int:
if (IR_IS_CONST_REF(op2_insn->op2)
&& !IR_IS_SYM_CONST(ctx->ir_base[op2_insn->op2].op)
&& ctx->ir_base[op2_insn->op2].val.i64 == 0) {
- if (op2_insn->op1 == insn->op2 - 1) { /* previous instruction */
+ if (op2_insn->op1 == insn->op2 - 1 /* previous instruction */
+ && ir_in_same_block(ctx, op2_insn->op1)
+ && !ir_match_has_flags_deps(ctx, insn->op2, ref)) {
ir_insn *op1_insn = &ctx->ir_base[op2_insn->op1];
if ((op1_insn->op == IR_OR || op1_insn->op == IR_AND || op1_insn->op == IR_XOR) ||
diff --git a/ext/opcache/tests/jit/gh23693.phpt b/ext/opcache/tests/jit/gh23693.phpt
new file mode 100644
index 000000000000..1540f0c1368f
--- /dev/null
+++ b/ext/opcache/tests/jit/gh23693.phpt
@@ -0,0 +1,41 @@
+--TEST--
+GH-23693: Tracing JIT reads stale flags for a guard on a hoisted addition
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.jit_buffer_size=64M
+opcache.jit=tracing
+opcache.jit_hot_func=1
+--EXTENSIONS--
+opcache
+--FILE--
+> 2;
+ $s = 0;
+ for ($dy = -1; $dy <= 1; ++$dy) {
+ for ($i = 0; $i < $n; ++$i) {
+ $ny = $y + $dy;
+ if ($ny < 0) {
+ continue;
+ }
+ if ($ny >= 4) {
+ continue;
+ }
+ $s += ($ny << 2) | ($i & 3);
+ }
+ }
+ return $s;
+}
+for ($k = 0; $k < 30; ++$k) {
+ f(16, 200);
+}
+var_dump(f(16, 200));
+var_dump(f(0, 200));
+?>
+--EXPECT--
+int(2700)
+int(1400)
diff --git a/ext/opcache/tests/opt/oss_fuzz_546798343.phpt b/ext/opcache/tests/opt/oss_fuzz_546798343.phpt
new file mode 100644
index 000000000000..f7f058a11652
--- /dev/null
+++ b/ext/opcache/tests/opt/oss_fuzz_546798343.phpt
@@ -0,0 +1,19 @@
+--TEST--
+OSS-Fuzz #546798343 (Heap-buffer-overflow in zend_delete_call_instructions with callable conversion)
+--EXTENSIONS--
+opcache
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+--FILE--
+
+--EXPECT--
+Done
diff --git a/ext/readline/readline_cli.c b/ext/readline/readline_cli.c
index ff5caee9eb7d..8319b3b43641 100644
--- a/ext/readline/readline_cli.c
+++ b/ext/readline/readline_cli.c
@@ -130,7 +130,7 @@ static zend_string *cli_get_prompt(char *block, char prompt) /* {{{ */
char *prompt_spec = CLIR_G(prompt) ? CLIR_G(prompt) : DEFAULT_PROMPT;
bool unicode_warned = false;
- do {
+ while (*prompt_spec) {
if (*prompt_spec == '\\') {
switch (prompt_spec[1]) {
case '\\':
@@ -198,9 +198,9 @@ static zend_string *cli_get_prompt(char *block, char prompt) /* {{{ */
smart_str_appendc(&retval, '?');
}
}
- } while (++prompt_spec && *prompt_spec);
- smart_str_0(&retval);
- return retval.s;
+ ++prompt_spec;
+ }
+ return smart_str_extract(&retval);
}
/* }}} */
diff --git a/ext/standard/dns.c b/ext/standard/dns.c
index 64301ed04161..61319565da7b 100644
--- a/ext/standard/dns.c
+++ b/ext/standard/dns.c
@@ -1120,6 +1120,10 @@ PHP_FUNCTION(dns_get_mx)
RETURN_FALSE;
}
cp += i;
+ if (cp + 10 > end) {
+ php_dns_free_handle(handle);
+ RETURN_FALSE;
+ }
GETSHORT(type, cp);
cp += INT16SZ + INT32SZ;
GETSHORT(i, cp);
@@ -1127,6 +1131,10 @@ PHP_FUNCTION(dns_get_mx)
cp += i;
continue;
}
+ if (cp + 2 > end) {
+ php_dns_free_handle(handle);
+ RETURN_FALSE;
+ }
GETSHORT(weight, cp);
if ((i = dn_expand(answer.qb2, end, cp, buf, sizeof(buf)-1)) < 0) {
php_dns_free_handle(handle);
diff --git a/ext/standard/proc_open.c b/ext/standard/proc_open.c
index d2d51de5a856..bd4cf7a0a0ee 100644
--- a/ext/standard/proc_open.c
+++ b/ext/standard/proc_open.c
@@ -695,7 +695,7 @@ static void init_startup_info(STARTUPINFOW *si, descriptorspec_item *descriptors
static void init_process_info(PROCESS_INFORMATION *pi)
{
- memset(&pi, 0, sizeof(pi));
+ memset(pi, 0, sizeof(*pi));
}
/* on success, returns length of *comspec, which then needs to be efree'd by caller */
@@ -746,7 +746,7 @@ static size_t find_comspec_nt(wchar_t **comspec)
static zend_result convert_command_to_use_shell(wchar_t **cmdw, size_t cmdw_len)
{
- wchar_t *comspec;
+ wchar_t *comspec = NULL;
size_t len = find_comspec_nt(&comspec);
if (len == 0) {
php_error_docref(NULL, E_WARNING, "Command conversion failed");
@@ -829,7 +829,7 @@ static zend_result set_proc_descriptor_to_blackhole(descriptorspec_item *desc)
#ifdef PHP_WIN32
desc->childend = CreateFileA("nul", GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
- if (desc->childend == NULL) {
+ if (desc->childend == INVALID_HANDLE_VALUE) {
php_error_docref(NULL, E_WARNING, "Failed to open nul");
return FAILURE;
}
diff --git a/ext/xsl/php_xsl.h b/ext/xsl/php_xsl.h
index 36bd9cc72844..ac4ac5fc2fba 100644
--- a/ext/xsl/php_xsl.h
+++ b/ext/xsl/php_xsl.h
@@ -55,6 +55,7 @@ extern zend_module_entry xsl_module_entry;
typedef struct xsl_object {
void *ptr;
HashTable *parameter;
+ uint32_t transform_depth;
bool hasKeys;
php_libxml_ref_obj *sheet_ref_obj;
zend_long securityPrefs;
diff --git a/ext/xsl/tests/gh23730.phpt b/ext/xsl/tests/gh23730.phpt
new file mode 100644
index 000000000000..893f6277734b
--- /dev/null
+++ b/ext/xsl/tests/gh23730.phpt
@@ -0,0 +1,81 @@
+--TEST--
+GH-23730 (Use-after-free when a stylesheet is imported during a transformation)
+--EXTENSIONS--
+dom
+xsl
+--CREDITS--
+djarfluka
+--FILE--
+importStylesheet($GLOBALS['other']);
+ echo $from, ': no error', PHP_EOL;
+ } catch (Error $e) {
+ echo $from, ': ', $e::class, ': ', $e->getMessage(), PHP_EOL;
+ }
+}
+
+function callback($nodes) {
+ import_other('callback');
+ return $nodes[0];
+}
+
+$xml = new DOMDocument();
+$xml->registerNodeClass(DOMElement::class, MyElement::class);
+$xml->loadXML('- a
');
+
+$xsl = new DOMDocument();
+$xsl->loadXML(<<
+
+
+
+XML);
+
+$other = new DOMDocument();
+$other->loadXML('REPLACED');
+
+$proc = new XSLTProcessor();
+$proc->registerPHPFunctions();
+$proc->importStylesheet($xsl);
+
+$GLOBALS['proc'] = $proc;
+$GLOBALS['other'] = $other;
+
+var_dump($proc->transformToXml($xml));
+var_dump($proc->transformToDoc($xml)->textContent);
+
+$uri = tempnam(sys_get_temp_dir(), 'gh23730');
+var_dump($proc->transformToUri($xml, $uri) > 0);
+@unlink($uri);
+
+/* Importing outside of a transformation is still allowed. */
+var_dump($proc->importStylesheet($other));
+var_dump($proc->transformToXml($xml));
+
+?>
+--EXPECT--
+callback: Error: Cannot call XSLTProcessor::importStylesheet() while a transformation is in progress
+destructor: Error: Cannot call XSLTProcessor::importStylesheet() while a transformation is in progress
+string(24) "
+a
+"
+callback: Error: Cannot call XSLTProcessor::importStylesheet() while a transformation is in progress
+destructor: Error: Cannot call XSLTProcessor::importStylesheet() while a transformation is in progress
+string(1) "a"
+callback: Error: Cannot call XSLTProcessor::importStylesheet() while a transformation is in progress
+destructor: Error: Cannot call XSLTProcessor::importStylesheet() while a transformation is in progress
+bool(true)
+bool(true)
+string(31) "
+REPLACED
+"
diff --git a/ext/xsl/xsltprocessor.c b/ext/xsl/xsltprocessor.c
index cf5a941d95ca..fa6b2d41e601 100644
--- a/ext/xsl/xsltprocessor.c
+++ b/ext/xsl/xsltprocessor.c
@@ -175,6 +175,12 @@ PHP_METHOD(XSLTProcessor, importStylesheet)
RETURN_THROWS();
}
+ xsl_object *intern = Z_XSL_P(id);
+ if (UNEXPECTED(intern->transform_depth > 0)) {
+ zend_throw_error(NULL, "Cannot call XSLTProcessor::importStylesheet() while a transformation is in progress");
+ RETURN_THROWS();
+ }
+
nodep = php_libxml_import_node(docp);
if (nodep == NULL) {
zend_argument_type_error(1, "must be a valid XML node");
@@ -251,8 +257,6 @@ PHP_METHOD(XSLTProcessor, importStylesheet)
RETURN_FALSE;
}
- xsl_object *intern = Z_XSL_P(id);
-
/* Detach object */
clone_lxml_obj->document->ptr = NULL;
/* The namespace mappings need to be kept alive.
@@ -333,6 +337,8 @@ static xmlDocPtr php_xsl_apply_stylesheet(zval *id, xsl_object *intern, xsltStyl
return NULL;
}
+ intern->transform_depth++;
+
if (intern->profiling) {
if (php_check_open_basedir(ZSTR_VAL(intern->profiling))) {
f = NULL;
@@ -438,6 +444,8 @@ static xmlDocPtr php_xsl_apply_stylesheet(zval *id, xsl_object *intern, xsltStyl
efree(intern->doc);
intern->doc = NULL;
+ intern->transform_depth--;
+
return newdocp;
}
diff --git a/ext/zip/php_zip.c b/ext/zip/php_zip.c
index 682736d4fe76..42a5a527cfc7 100644
--- a/ext/zip/php_zip.c
+++ b/ext/zip/php_zip.c
@@ -1582,8 +1582,16 @@ PHP_METHOD(ZipArchive, open)
if (ze_obj->archive) {
/* we already have an opened zip, free it */
+ if (ze_obj->archive->close) {
+ efree(resolved_path);
+ zend_throw_error(NULL, "Already being closed");
+ RETURN_THROWS();
+ }
intern = ze_obj->archive->za;
- if (zip_close(intern) != 0) {
+ ze_obj->archive->close = true;
+ err = zip_close(intern);
+ ze_obj->archive->close = false;
+ if (err != 0) {
php_error_docref(NULL, E_WARNING, "Empty string as source");
efree(resolved_path);
RETURN_FALSE;
@@ -1668,7 +1676,14 @@ PHP_METHOD(ZipArchive, close)
ze_obj = Z_ZIP_P(self);
+ if (ze_obj->archive->close) {
+ zend_throw_error(NULL, "Already being closed");
+ RETURN_THROWS();
+ }
+
+ ze_obj->archive->close = true;
err = zip_close(intern);
+ ze_obj->archive->close = false;
if (err) {
php_error_docref(NULL, E_WARNING, "%s", zip_strerror(intern));
/* Save error for property reader */
diff --git a/ext/zip/php_zip.h b/ext/zip/php_zip.h
index 8385674a1cf0..e5930c87aada 100644
--- a/ext/zip/php_zip.h
+++ b/ext/zip/php_zip.h
@@ -73,6 +73,7 @@ typedef struct _php_zip_archive {
/* libzip reads buffers until the archive is closed, can outlive the object. */
char **buffers;
int buffers_cnt;
+ bool close;
#ifdef HAVE_PROGRESS_CALLBACK
zval progress_callback;
#endif
diff --git a/ext/zip/tests/gh23747.phpt b/ext/zip/tests/gh23747.phpt
new file mode 100644
index 000000000000..24e977410e17
--- /dev/null
+++ b/ext/zip/tests/gh23747.phpt
@@ -0,0 +1,102 @@
+--TEST--
+GH-23747 (ZipArchive::close() from inside a progress or cancel callback causes segv)
+--CREDITS--
+djarfluka
+--EXTENSIONS--
+zip
+--SKIPIF--
+
+--FILE--
+open($filename, ZipArchive::CREATE | ZipArchive::OVERWRITE);
+ for ($i = 0; $i < 64; $i++) {
+ $zip->addFromString("f$i.txt", str_repeat('x', 2000));
+ }
+}
+
+$filename = __DIR__ . '/gh23747.zip';
+
+$zip = new ZipArchive();
+populate($zip, $filename);
+$zip->registerProgressCallback(0.0, function ($rate) use ($zip) {
+ static $done = false;
+ if (!$done) {
+ $done = true;
+ try {
+ $zip->close();
+ } catch (Error $e) {
+ echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+ }
+ }
+});
+var_dump($zip->close());
+
+$zip = new ZipArchive();
+populate($zip, $filename);
+$zip->registerCancelCallback(function () use ($zip) {
+ static $done = false;
+ if (!$done) {
+ $done = true;
+ try {
+ $zip->close();
+ } catch (Error $e) {
+ echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+ }
+ }
+ return 0;
+});
+var_dump($zip->close());
+
+$zip = new ZipArchive();
+populate($zip, $filename);
+$zip->registerProgressCallback(0.0, function ($rate) use ($zip) {
+ static $done = false;
+ if (!$done) {
+ $done = true;
+ try {
+ $zip->close();
+ } catch (Error $e) {
+ echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+ }
+ }
+});
+var_dump($zip->open($filename));
+var_dump($zip->count());
+
+$zip = new ZipArchive();
+populate($zip, $filename);
+$zip->registerProgressCallback(0.0, function ($rate) use ($zip, $filename) {
+ static $done = false;
+ if (!$done) {
+ $done = true;
+ try {
+ $zip->open($filename);
+ } catch (Error $e) {
+ echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+ }
+ }
+});
+var_dump($zip->close());
+?>
+--CLEAN--
+
+--EXPECT--
+Error: Already being closed
+bool(true)
+Error: Already being closed
+bool(true)
+Error: Already being closed
+bool(true)
+int(64)
+Error: Already being closed
+bool(true)
diff --git a/sapi/cli/php_cli.c b/sapi/cli/php_cli.c
index 4b19cf0604c6..6894f57c12c0 100644
--- a/sapi/cli/php_cli.c
+++ b/sapi/cli/php_cli.c
@@ -356,6 +356,15 @@ static void sapi_cli_log_message(const char *message, int syslog_type_int) /* {{
}
/* }}} */
+static int sapi_cli_activate(void) /* {{{ */
+{
+#if defined(PHP_WIN32) && defined(ZTS)
+ ZEND_TSRMLS_CACHE_UPDATE();
+#endif
+ return SUCCESS;
+}
+/* }}} */
+
static int sapi_cli_deactivate(void) /* {{{ */
{
fflush(stdout);
@@ -420,7 +429,7 @@ static sapi_module_struct cli_sapi_module = {
php_cli_startup, /* startup */
php_module_shutdown_wrapper, /* shutdown */
- NULL, /* activate */
+ sapi_cli_activate, /* activate */
sapi_cli_deactivate, /* deactivate */
sapi_cli_ub_write, /* unbuffered write */