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. 2
      tests/compiler/include_require/Hello.php
  4. 1
      tests/compiler/include_require/no-leak.ini

@ -3544,26 +3544,26 @@ class CompilerBase implements PropertyAccessContext
break; break;
} }
$fileName = $this->parseIdentifier($expr->expr);
/** /**
* If an included PHP file encounters a fatal error during execution—for example, * The included script may call a compiled function through ZendVM and
* incorrect function arguments—an exception will be thrown, unwinding directly from * throw back into C++. Destroy request-allocated path temporaries before
* the Zend VM stack through the C++ stack. In the original implementation, * entering ZendVM, then keep only C++ storage alive across that
* there were several "undestroyed temporary objects." When the exception propagates back, * boundary. PHPX owns the Zend string lifetime. The IIFE preserves
* those objects need to be destroyed. However, by that time the environment * include's expression semantics and isolates every occurrence.
* is already in a corrupted/inconsistent state, and attempting to destroy them causes a crash.
*/ */
$fileName = $this->parseIdentifier($expr->expr); return <<<CPP
return <<<EOL ([&]() -> php::Variant {
zend_string *filename = nullptr; std::string typephp_include_path;
{
php::Str typephp_include_path_tmp = $fileName;
typephp_include_path = typephp_include_path_tmp.toStdString();
}
{ return php::include(typephp_include_path, $type);
php::Str temp = $fileName; })()
filename = temp.str(); CPP;
zend_string_addref(filename);
}
php::include(filename, $type)
EOL;
} }
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

@ -1,3 +1,3 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
Hello(); Hello();

Loading…
Cancel
Save