From 0ac6dfe499724de5fb20dcafdf9663f8f9458453 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Tue, 25 Aug 2026 20:30:12 +0800 Subject: [PATCH] fix(compiler): keep method cache lifetime aligned with class --- phpunit/src/CompilerBaseApiTest.php | 20 ++++++++++++++++++++ src/CompilerBase.php | 13 +++++++++++++ 2 files changed, 33 insertions(+) diff --git a/phpunit/src/CompilerBaseApiTest.php b/phpunit/src/CompilerBaseApiTest.php index 922dd14d..db39cc17 100644 --- a/phpunit/src/CompilerBaseApiTest.php +++ b/phpunit/src/CompilerBaseApiTest.php @@ -78,6 +78,26 @@ class CompilerBaseApiTest extends TestCase return $m->invoke($this->compiler, ...$args); } + public function testMethodCacheKeepsPreviouslyAssignedClassLifetime(): void + { + // The two maps have independent ID spaces. This reproduces the tpc + // bootstrap ordering where both classes occupied slot zero. + $this->setPropertyValue('classMap', ['LateKnownClass' => 0]); + $this->setPropertyValue('classIndex', 1); + $this->setPropertyValue('persistentClassMap', ['StableResolver' => 0]); + $this->setPropertyValue('persistentClassIndex', 1); + + $methodPtr = $this->invokeMethod('getMethodPtr', 'LateKnownClass', 'run'); + + $this->assertStringStartsWith('get_method(0, ', $methodPtr); + $this->assertStringContainsString(', 0, ', $methodPtr); + $this->assertSame( + ['LateKnownClass::run' => 0], + $this->getPropertyValue('funcMap'), + ); + $this->assertSame([], $this->getPropertyValue('persistentFuncMap')); + } + private function fixturePath(string $file): string { return __DIR__ . '/../code/compiler_api/' . $file; diff --git a/src/CompilerBase.php b/src/CompilerBase.php index 06d3bc14..607ab094 100644 --- a/src/CompilerBase.php +++ b/src/CompilerBase.php @@ -1277,6 +1277,19 @@ class CompilerBase implements PropertyAccessContext { if (str_contains($funcName, '::')) { [$class] = explode('::', $funcName, 2); + + // Class and method caches use parallel lifetime domains. Once a + // class has been assigned an ID, keep every subsequently resolved + // method in the same domain even if the class becomes visible in + // the symbol repository later in the prepare pass. Otherwise a + // request-local class ID may be used to index persistentClassMap. + if (isset($this->classMap[$class])) { + return false; + } + if (isset($this->persistentClassMap[$class])) { + return true; + } + return $this->isProcessStableClass($class); } if ($this->hasFunction($funcName)) {