fix(include): make ZendVM unwind lifetime safe

韩天峰 3 weeks ago
parent c10431f697
commit 110ffc7be6
  1. 34
      src/CompilerBase.php
  2. 28
      tests/compiler/include_require/001.phpt
  3. 1
      tests/compiler/include_require/no-leak.ini

@ -3544,26 +3544,26 @@ class CompilerBase implements PropertyAccessContext
break; break;
} }
/**
* 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.
*/
$fileName = $this->parseIdentifier($expr->expr); $fileName = $this->parseIdentifier($expr->expr);
return <<<EOL
zend_string *filename = nullptr;
{ /**
php::Str temp = $fileName; * The included script may call a compiled function through ZendVM and
filename = temp.str(); * throw back into C++. Destroy request-allocated path temporaries before
zend_string_addref(filename); * 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.
*/
return <<<CPP
([&]() -> php::Variant {
std::string typephp_include_path;
{
php::Str typephp_include_path_tmp = $fileName;
typephp_include_path = typephp_include_path_tmp.toStdString();
}
php::include(filename, $type) return php::include(typephp_include_path, $type);
EOL; })()
CPP;
} }
protected function parseScalarFloat(Node\Scalar\Float_ $expr): string protected function parseScalarFloat(Node\Scalar\Float_ $expr): string

@ -1,17 +1,35 @@
--TEST-- --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-- --FILE--
<?php <?php
declare(strict_types=1); declare(strict_types=1);
function Hello(string $value) {} function Hello(string $value) {}
function main() { function includePath(string $name): string
{
return implode('', [__DIR__, '/', ucfirst(strtolower($name)), '.php']);
}
function main(): void
{
// Keep this name: generated helper variables must not collide with PHP locals.
$filename = includePath('hello');
try {
include $filename;
} catch (ArgumentCountError $e) {
echo "include ArgumentCountError\n";
}
try { try {
require __DIR__ . '/Hello.php'; require includePath('hello');
} catch (ArgumentCountError $e) { } catch (ArgumentCountError $e) {
echo "ArgumentCountError Error"; echo "require ArgumentCountError\n";
} }
} }
?> ?>
--EXPECT-- --EXPECT--
ArgumentCountError Error include ArgumentCountError
require ArgumentCountError

Loading…
Cancel
Save