diff --git a/phpunit/src/ServerEnvironmentTest.php b/phpunit/src/ServerEnvironmentTest.php index 38f2b1e1..315d90af 100644 --- a/phpunit/src/ServerEnvironmentTest.php +++ b/phpunit/src/ServerEnvironmentTest.php @@ -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, "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 diff --git a/run-tests.php b/run-tests.php index 01319706..fbcc1ca3 100755 --- a/run-tests.php +++ b/run-tests.php @@ -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); diff --git a/src/Translator.php b/src/Translator.php index 6aa5a7b8..db4cd0c9 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -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; } diff --git a/tests/compiler/exception/main-eval-error-lifecycle.phpt b/tests/compiler/exception/main-eval-error-lifecycle.phpt new file mode 100644 index 00000000..0b98169c --- /dev/null +++ b/tests/compiler/exception/main-eval-error-lifecycle.phpt @@ -0,0 +1,16 @@ +--TEST-- +Uncaught main error does not access request memory after php::eval +--AOT_ARGS-- +-O2 +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught RuntimeException: main lifecycle error in %smain-eval-error-lifecycle.php:%d +Stack trace: +%A