修复在RINIT初始化$_SERVER在-02的优化等级下会出现内存错误和悬空指针 #41

Merged
韩天峰 merged 2 commits from fix/memory-error into master 4 weeks ago
  1. 40
      phpunit/src/ServerEnvironmentTest.php
  2. 10
      run-tests.php
  3. 31
      src/Translator.php
  4. 16
      tests/compiler/exception/main-eval-error-lifecycle.phpt

@ -43,18 +43,42 @@ class ServerEnvironmentTest extends TestCase
$method = new ReflectionMethod(Translator::class, 'registerServerEnvironment');
$code = $method->invoke($compiler, 'C:\\project\\"quoted"\\main.php');
$this->assertStringContainsString(
'const char *value = "' . $compiler->escapeString('C:\\project\\"quoted"\\main.php') . '";',
$code
);
$this->assertStringContainsString('php::Var &_SERVER = _global_var__SERVER;', $code);
$this->assertStringContainsString('php::Str php_self = "PHP_SELF";', $code);
$this->assertStringContainsString('php::Str script_name = "SCRIPT_NAME";', $code);
$this->assertStringContainsString('php::Str script_filename = "SCRIPT_FILENAME";', $code);
$this->assertStringContainsString('php::Str path_translated = "PATH_TRANSLATED";', $code);
$this->assertStringContainsString('php::Str document_root = "DOCUMENT_ROOT";', $code);
$this->assertStringContainsString('_SERVER.item("PHP_SELF", true) = value;', $code);
$this->assertStringContainsString('_SERVER.item("SCRIPT_NAME", true) = value;', $code);
$this->assertStringContainsString('_SERVER.item("SCRIPT_FILENAME", true) = value;', $code);
$this->assertStringContainsString('_SERVER.item("PATH_TRANSLATED", true) = value;', $code);
$this->assertStringContainsString('_SERVER.item("DOCUMENT_ROOT", true) = "";', $code);
$this->assertStringNotContainsString('php::Str', $code);
}
public function testCaseInsensitiveMainWrapperInitializesServerEnvironment(): void
{
global $translator;
$previousTranslator = $translator ?? null;
$file = $this->testDir . '/uppercase-main.php';
file_put_contents($file, "<?php\nfunction MAIN(): void {}\n");
try {
$compiler = CompilerTest::create($this->testDir);
$translator = $compiler;
$compiler->addFiles([$file]);
$compiler->prepareFile($file);
$cppFile = $compiler->convertFile($file);
$code = file_get_contents($cppFile);
} finally {
$translator = $previousTranslator;
}
$this->assertStringContainsString(
'php::Str value = "' . $compiler->escapeString('C:\\project\\"quoted"\\main.php') . '";',
'const char *value = "' . $compiler->escapeString(realpath($file)) . '";',
$code
);
$this->assertStringContainsString('_SERVER.item(path_translated, true) = value;', $code);
$this->assertStringContainsString('_SERVER.item(document_root, true) = "";', $code);
$this->assertStringContainsString('_SERVER.item("PHP_SELF", true) = value;', $code);
}
public function testServerGlobalIsForcedOnlyForBinaryBuilds(): void

@ -2427,7 +2427,8 @@ TEST $file
global $no_aot;
if (!$no_aot) {
try {
$bin_file = compile_php_file($test_file);
$aot_args = $test->hasSection('AOT_ARGS') ? trim($test->getSection('AOT_ARGS')) : '';
$bin_file = compile_php_file($test_file, $aot_args);
} catch (Throwable $e) {
$compileOutput = trim($e instanceof CompilationFailureException ? $e->getCompilerOutput() : '');
$message = $e->getMessage();
@ -3740,7 +3741,7 @@ class TestFile
private const ALLOWED_SECTIONS = [
'EXPECT', 'EXPECTF', 'EXPECTREGEX', 'EXPECTREGEX_EXTERNAL', 'EXPECT_EXTERNAL', 'EXPECTF_EXTERNAL', 'EXPECTHEADERS',
'POST', 'POST_RAW', 'GZIP_POST', 'DEFLATE_POST', 'PUT', 'GET', 'COOKIE', 'ARGS',
'POST', 'POST_RAW', 'GZIP_POST', 'DEFLATE_POST', 'PUT', 'GET', 'COOKIE', 'ARGS', 'AOT_ARGS',
'FILE', 'FILEEOF', 'FILE_EXTERNAL', 'REDIRECTTEST',
'CAPTURE_STDIO', 'STDIN', 'CGI', 'PHPDBG',
'INI', 'ENV', 'EXTENSIONS',
@ -4240,7 +4241,7 @@ function debug()
exit;
}
function compile_php_file(string $file): string
function compile_php_file(string $file, string $compiler_args = ''): string
{
global $compiler_path;
@ -4269,6 +4270,9 @@ function compile_php_file(string $file): string
if (IS_WINDOWS && str_ends_with($cmd, '.php')) {
$cmd = escapeshellarg(PHP_BINARY) . ' ' . $cmd;
}
if ($compiler_args !== '') {
$cmd .= ' ' . $compiler_args;
}
exec($cmd . ' ' . escapeshellarg($file) . ' 2>&1', $output, $exitCode);
clearstatcache(true, $binary_file);

@ -1048,7 +1048,6 @@ CODE;
// FunctionDef::sourceFile comes from loadFile()'s realpath(), so the
// CLI script fields always identify main()'s canonical absolute file.
$entryFile = $entryFunction->sourceFile;
$code .= $this->registerServerEnvironment($entryFile);
$entryFileArg = $this->genCharPtr($entryFile, true);
$entryPrefix = str_repeat("\n", max(0, $entryFunction->startLine - 1));
if (count($entryFunction->argInfoList) == 2) {
@ -3216,6 +3215,12 @@ CODE;
$methodArgs = implode(', ', array_merge(['this_'], $implicitMethodArgs));
$callParams = $functionDef->argInfoList ? $methodArgs . ', ' . rtrim($callParams, ',') : $methodArgs;
} else {
$isEntryFunction = $this->hasFunction(self::ENTRY_FUNCTION)
&& $functionDef === $this->getFunction(self::ENTRY_FUNCTION);
if ($this->isBuildModeBin() && $isEntryFunction) {
// $_SERVER 的初始化必须置于 main 入口函数内,以确保在其被访问前,运行环境及超全局上下文已完全就绪。
$cppCode .= $this->registerServerEnvironment($functionDef->sourceFile);
}
$callParams = $functionDef->argInfoList ? rtrim($callParams, ',') : '';
}
@ -3235,19 +3240,17 @@ CODE;
private function registerServerEnvironment(string $entryFile): string
{
$cppCode = 'php::Var &_SERVER = ' . $this->escapeGlobalVar('_SERVER') . ';' . PHP_EOL;
$cppCode .= 'php::Str php_self = "PHP_SELF";' . PHP_EOL;
$cppCode .= 'php::Str script_name = "SCRIPT_NAME";' . PHP_EOL;
$cppCode .= 'php::Str script_filename = "SCRIPT_FILENAME";' . PHP_EOL;
$cppCode .= 'php::Str path_translated = "PATH_TRANSLATED";' . PHP_EOL;
$cppCode .= 'php::Str document_root = "DOCUMENT_ROOT";' . PHP_EOL;
$cppCode .= 'php::Str value = ' . $this->genCharPtr($entryFile, true) . ';' . PHP_EOL;
$cppCode .= '_SERVER.item(php_self, true) = value;' . PHP_EOL;
$cppCode .= '_SERVER.item(script_name, true) = value;' . PHP_EOL;
$cppCode .= '_SERVER.item(script_filename, true) = value;' . PHP_EOL;
$cppCode .= '_SERVER.item(path_translated, true) = value;' . PHP_EOL;
$cppCode .= '_SERVER.item(document_root, true) = "";' . PHP_EOL;
/**
* 对于常驻内存型应用,执行完当前逻辑后,会立即进入长时间的事件循环等待。
* 因此,这些变量仅作为临时用途,用完后应即刻销毁,无需长期持有。
*/
$cppCode = "const char *value = " . $this->genCharPtr($entryFile, true) . ';' . PHP_EOL;
$cppCode .= 'php::Var &_SERVER = ' . $this->escapeGlobalVar('_SERVER') . ';' . PHP_EOL;
$cppCode .= '_SERVER.item("PHP_SELF", true) = value;'. PHP_EOL;
$cppCode .= '_SERVER.item("SCRIPT_NAME", true) = value;'. PHP_EOL;
$cppCode .= '_SERVER.item("SCRIPT_FILENAME", true) = value;'. PHP_EOL;
$cppCode .= '_SERVER.item("PATH_TRANSLATED", true) = value;'. PHP_EOL;
$cppCode .= '_SERVER.item("DOCUMENT_ROOT", true) = "";' . PHP_EOL;
return $cppCode . PHP_EOL;
}

@ -0,0 +1,16 @@
--TEST--
Uncaught main error does not access request memory after php::eval
--AOT_ARGS--
-O2
--FILE--
<?php
function main(): void
{
throw new RuntimeException('main lifecycle error');
}
?>
--EXPECTF--
Fatal error: Uncaught RuntimeException: main lifecycle error in %smain-eval-error-lifecycle.php:%d
Stack trace:
%A
Loading…
Cancel
Save