Skip to content

Commit 4baf513

Browse files
ondrejmirtesclaude
andcommitted
Do not identify the last initialized container by its spl_object_id()
An id is recycled as soon as its object is freed, so a container created after an earlier one was destroyed can inherit that one's id - and postInitializeContainer() would then return early and leave the static reflection provider, the PhpVersion and the feature toggles pointing at the container that is gone. Hold a WeakReference and compare identities instead. ExprHandlerRegistry and StmtHandlerRegistry memoize handler instances under the same recycled id, and a handler carries its own container's services and parameters, so both memos are dropped whenever another container takes over. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019isZYNh3Zms6xGKGGXqxgp
1 parent 93ff20a commit 4baf513

3 files changed

Lines changed: 39 additions & 5 deletions

File tree

‎src/Analyser/ExprHandlerRegistry.php‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,18 @@ final class ExprHandlerRegistry
1818
/** @var array<int, array<non-empty-string, ExprHandler<Expr>|false>> */
1919
private static array $exprHandlersByClass = [];
2020

21+
/**
22+
* Handlers belong to the container they came from, and spl_object_id() is
23+
* recycled as soon as a container is freed - so ContainerFactory drops the
24+
* memo whenever another container takes over the global state.
25+
*
26+
* @internal
27+
*/
28+
public static function clearCache(): void
29+
{
30+
self::$exprHandlersByClass = [];
31+
}
32+
2133
/**
2234
* @return ExprHandler<Expr>|null
2335
*/

‎src/Analyser/StmtHandlerRegistry.php‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,15 @@ final class StmtHandlerRegistry
2323
/** @var array<int, array<class-string<Stmt>, StmtHandler<Stmt>|false>> */
2424
private static array $stmtHandlersByClass = [];
2525

26+
/**
27+
* @see ExprHandlerRegistry::clearCache()
28+
* @internal
29+
*/
30+
public static function clearCache(): void
31+
{
32+
self::$stmtHandlersByClass = [];
33+
}
34+
2635
/**
2736
* @return StmtHandler<Stmt>|null
2837
*/

‎src/DependencyInjection/ContainerFactory.php‎

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
use Nette\Utils\Validators;
1818
use Phar;
1919
use PhpParser\Parser;
20+
use PHPStan\Analyser\ExprHandlerRegistry;
21+
use PHPStan\Analyser\StmtHandlerRegistry;
2022
use PHPStan\BetterReflection\BetterReflection;
2123
use PHPStan\BetterReflection\Reflector\Reflector;
2224
use PHPStan\BetterReflection\SourceLocator\SourceStubber\PhpStormStubsSourceStubber;
@@ -32,6 +34,7 @@
3234
use PHPStan\ShouldNotHappenException;
3335
use PHPStan\Type\ObjectType;
3436
use PHPStan\Type\TypeCombinator;
37+
use WeakReference;
3538
use function array_diff_key;
3639
use function array_intersect;
3740
use function array_key_exists;
@@ -49,7 +52,6 @@
4952
use function is_file;
5053
use function is_readable;
5154
use function is_string;
52-
use function spl_object_id;
5355
use function sprintf;
5456
use function str_ends_with;
5557
use function substr;
@@ -66,7 +68,8 @@ final class ContainerFactory
6668

6769
private string $configDirectory;
6870

69-
private static ?int $lastInitializedContainerId = null;
71+
/** @var WeakReference<Container>|null */
72+
private static ?WeakReference $lastInitializedContainer = null;
7073

7174
private bool $journalContainer = false;
7275

@@ -172,12 +175,22 @@ public function create(
172175
/** @internal */
173176
public static function postInitializeContainer(Container $container): void
174177
{
175-
$containerId = spl_object_id($container);
176-
if ($containerId === self::$lastInitializedContainerId) {
178+
// Comparing spl_object_id()s would be wrong: an id is recycled as soon as its
179+
// container is freed, so a fresh container could inherit the last initialized
180+
// one's id and silently skip installing its own global state.
181+
$lastInitializedContainer = self::$lastInitializedContainer !== null ? self::$lastInitializedContainer->get() : null;
182+
if ($lastInitializedContainer === $container) {
177183
return;
178184
}
179185

180-
self::$lastInitializedContainerId = $containerId;
186+
self::$lastInitializedContainer = WeakReference::create($container);
187+
188+
// Both registries memoize handler instances per container, keyed by the
189+
// container's spl_object_id() - a recycled id would otherwise hand this
190+
// container the freed one's handlers, which carry the other container's
191+
// services and parameters.
192+
ExprHandlerRegistry::clearCache();
193+
StmtHandlerRegistry::clearCache();
181194

182195
/** @var SourceLocator $sourceLocator */
183196
$sourceLocator = $container->getService('betterReflectionSourceLocator');

0 commit comments

Comments
 (0)