From 110ffc7be646334e751e56ccc78bb8fc5fd40c11 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Sun, 9 Aug 2026 21:10:47 +0800 Subject: [PATCH] fix(include): make ZendVM unwind lifetime safe --- src/CompilerBase.php | 34 +++++++++++----------- tests/compiler/include_require/001.phpt | 28 ++++++++++++++---- tests/compiler/include_require/Hello.php | 2 +- tests/compiler/include_require/no-leak.ini | 1 + 4 files changed, 42 insertions(+), 23 deletions(-) create mode 100644 tests/compiler/include_require/no-leak.ini diff --git a/src/CompilerBase.php b/src/CompilerBase.php index beb3484b..4acfb5fe 100644 --- a/src/CompilerBase.php +++ b/src/CompilerBase.php @@ -3544,26 +3544,26 @@ class CompilerBase implements PropertyAccessContext break; } + $fileName = $this->parseIdentifier($expr->expr); + /** - * If an included PHP file encounters a fatal error during execution—for example, - * incorrect function arguments—an exception will be thrown, unwinding directly from - * the Zend VM stack through the C++ stack. In the original implementation, - * there were several "undestroyed temporary objects." When the exception propagates back, - * those objects need to be destroyed. However, by that time the environment - * is already in a corrupted/inconsistent state, and attempting to destroy them causes a crash. + * The included script may call a compiled function through ZendVM and + * throw back into C++. Destroy request-allocated path temporaries before + * entering ZendVM, then keep only C++ storage alive across that + * boundary. PHPX owns the Zend string lifetime. The IIFE preserves + * include's expression semantics and isolates every occurrence. */ - $fileName = $this->parseIdentifier($expr->expr); - return << php::Variant { + std::string typephp_include_path; + { + php::Str typephp_include_path_tmp = $fileName; + typephp_include_path = typephp_include_path_tmp.toStdString(); + } - { - php::Str temp = $fileName; - filename = temp.str(); - zend_string_addref(filename); - } - - php::include(filename, $type) -EOL; + return php::include(typephp_include_path, $type); + })() +CPP; } protected function parseScalarFloat(Node\Scalar\Float_ $expr): string diff --git a/tests/compiler/include_require/001.phpt b/tests/compiler/include_require/001.phpt index c99c7b16..56eedaa1 100644 --- a/tests/compiler/include_require/001.phpt +++ b/tests/compiler/include_require/001.phpt @@ -1,17 +1,35 @@ --TEST-- -The compiled program calls its own functions via the Zend VM. +include/require unwind safely when ZendVM calls a compiled function that throws +--ENV-- +PHPRC=tests/compiler/include_require/no-leak.ini --FILE-- --EXPECT-- -ArgumentCountError Error +include ArgumentCountError +require ArgumentCountError diff --git a/tests/compiler/include_require/Hello.php b/tests/compiler/include_require/Hello.php index 91224ec5..96b684de 100644 --- a/tests/compiler/include_require/Hello.php +++ b/tests/compiler/include_require/Hello.php @@ -1,3 +1,3 @@