fix(include): make ZendVM unwind lifetime safe

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

@ -3544,26 +3544,15 @@ class CompilerBase implements PropertyAccessContext
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);
return <<<EOL
zend_string *filename = nullptr;
{
php::Str temp = $fileName;
filename = temp.str();
zend_string_addref(filename);
}
php::include(filename, $type)
EOL;
/**
* PHPX evaluates the path provider in an isolated scope and destroys
* request-allocated temporaries before entering ZendVM. This keeps the
* compiler lowering expression-only while preserving include's return
* value and side effects.
*/
return "php::includeFile([&]() { return $fileName; }, $type)";
}
protected function parseScalarFloat(Node\Scalar\Float_ $expr): string

@ -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--
<?php
declare(strict_types=1);
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 {
require __DIR__ . '/Hello.php';
require includePath('hello');
} catch (ArgumentCountError $e) {
echo "ArgumentCountError Error";
echo "require ArgumentCountError\n";
}
}
?>
--EXPECT--
ArgumentCountError Error
include ArgumentCountError
require ArgumentCountError

Loading…
Cancel
Save