From c46a9ddbf2c24a5c8a33f74a32125a6056bc8b6b Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Fri, 31 Jul 2026 14:47:10 +0800 Subject: [PATCH] fix: cover eval error lifecycle regressions --- phpunit/src/ServerEnvironmentTest.php | 40 +++++++++++++++---- run-tests.php | 10 +++-- src/Translator.php | 4 +- .../exception/main-eval-error-lifecycle.phpt | 16 ++++++++ 4 files changed, 58 insertions(+), 12 deletions(-) create mode 100644 tests/compiler/exception/main-eval-error-lifecycle.phpt 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 acf82083..db4cd0c9 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -3215,7 +3215,9 @@ CODE; $methodArgs = implode(', ', array_merge(['this_'], $implicitMethodArgs)); $callParams = $functionDef->argInfoList ? $methodArgs . ', ' . rtrim($callParams, ',') : $methodArgs; } else { - if ($this->isBuildModeBin() && $functionDef->name === self::ENTRY_FUNCTION) { + $isEntryFunction = $this->hasFunction(self::ENTRY_FUNCTION) + && $functionDef === $this->getFunction(self::ENTRY_FUNCTION); + if ($this->isBuildModeBin() && $isEntryFunction) { // $_SERVER 的初始化必须置于 main 入口函数内,以确保在其被访问前,运行环境及超全局上下文已完全就绪。 $cppCode .= $this->registerServerEnvironment($functionDef->sourceFile); } 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