Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ce1ff9f

Browse files
[VarExporter] Fix possible memory-leak when using lazy-objects
1 parent 7c50d8c commit ce1ff9f

File tree

9 files changed

+78
-107
lines changed

9 files changed

+78
-107
lines changed

src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
3030
use Symfony\Component\DependencyInjection\Exception\LogicException;
3131
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
32-
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
3332
use Symfony\Component\DependencyInjection\ExpressionLanguage;
3433
use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface as ProxyDumper;
3534
use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\LazyServiceDumper;
@@ -168,15 +167,7 @@ public function dump(array $options = []): string|array
168167

169168
if ($this->getProxyDumper() instanceof NullDumper) {
170169
(new AnalyzeServiceReferencesPass(true, false))->process($this->container);
171-
try {
172-
(new CheckCircularReferencesPass())->process($this->container);
173-
} catch (ServiceCircularReferenceException $e) {
174-
$path = $e->getPath();
175-
end($path);
176-
$path[key($path)] .= '". Try running "composer require symfony/proxy-manager-bridge';
177-
178-
throw new ServiceCircularReferenceException($e->getServiceId(), $path);
179-
}
170+
(new CheckCircularReferencesPass())->process($this->container);
180171
}
181172

182173
$this->analyzeReferences();

src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -765,7 +765,7 @@ public function testCircularReferenceAllowanceForLazyServices()
765765
$dumper = new PhpDumper($container);
766766
$dumper->setProxyDumper(new NullDumper());
767767

768-
$message = 'Circular reference detected for service "foo", path: "foo -> bar -> foo". Try running "composer require symfony/proxy-manager-bridge".';
768+
$message = 'Circular reference detected for service "foo", path: "foo -> bar -> foo".';
769769
$this->expectException(ServiceCircularReferenceException::class);
770770
$this->expectExceptionMessage($message);
771771

src/Symfony/Component/DependencyInjection/composer.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,7 @@
3131
"symfony/yaml": "",
3232
"symfony/config": "",
3333
"symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required",
34-
"symfony/expression-language": "For using expressions in service container configuration",
35-
"symfony/proxy-manager-bridge": "Generate service proxies to lazy load them"
34+
"symfony/expression-language": "For using expressions in service container configuration"
3635
},
3736
"conflict": {
3837
"ext-psr": "<1.1|>=2",

src/Symfony/Component/VarDumper/Caster/SymfonyCaster.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\Uid\Ulid;
1616
use Symfony\Component\Uid\Uuid;
1717
use Symfony\Component\VarDumper\Cloner\Stub;
18+
use Symfony\Component\VarExporter\Internal\LazyObjectState;
1819

1920
/**
2021
* @final
@@ -67,6 +68,22 @@ public static function castHttpClientResponse($response, array $a, Stub $stub, b
6768
return $a;
6869
}
6970

71+
public static function castLazyObjectState($state, array $a, Stub $stub, bool $isNested)
72+
{
73+
if (!$isNested) {
74+
return $a;
75+
}
76+
77+
$stub->cut += \count($a) - 1;
78+
79+
return ['status' => new ConstStub(match ($a['status']) {
80+
LazyObjectState::STATUS_INITIALIZED_FULL => 'INITIALIZED_FULL',
81+
LazyObjectState::STATUS_INITIALIZED_PARTIAL => 'INITIALIZED_PARTIAL',
82+
LazyObjectState::STATUS_UNINITIALIZED_FULL => 'UNINITIALIZED_FULL',
83+
LazyObjectState::STATUS_UNINITIALIZED_PARTIAL => 'UNINITIALIZED_PARTIAL',
84+
}, $a['status'])];
85+
}
86+
7087
public static function castUuid(Uuid $uuid, array $a, Stub $stub, bool $isNested)
7188
{
7289
$a[Caster::PREFIX_VIRTUAL.'toBase58'] = $uuid->toBase58();

src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ abstract class AbstractCloner implements ClonerInterface
8989
'Symfony\Component\HttpFoundation\Request' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castRequest'],
9090
'Symfony\Component\Uid\Ulid' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castUlid'],
9191
'Symfony\Component\Uid\Uuid' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castUuid'],
92+
'Symfony\Component\VarExporter\Internal\LazyObjectState' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castLazyObjectState'],
9293
'Symfony\Component\VarDumper\Exception\ThrowingCasterException' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castThrowingCasterException'],
9394
'Symfony\Component\VarDumper\Caster\TraceStub' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castTraceStub'],
9495
'Symfony\Component\VarDumper\Caster\FrameStub' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castFrameStub'],

src/Symfony/Component/VarExporter/Internal/LazyObjectRegistry.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,6 @@
2020
*/
2121
class LazyObjectRegistry
2222
{
23-
/**
24-
* @var array<int, LazyObjectState>
25-
*/
26-
public static $states = [];
27-
2823
/**
2924
* @var array<class-string, \ReflectionClass>
3025
*/
@@ -50,6 +45,11 @@ class LazyObjectRegistry
5045
*/
5146
public static $parentMethods = [];
5247

48+
/**
49+
* @var LazyObjectState
50+
*/
51+
public static $noInitializerState;
52+
5353
public static function getClassResetters($class)
5454
{
5555
$classProperties = [];
@@ -63,7 +63,7 @@ public static function getClassResetters($class)
6363
foreach ($propertyScopes as $key => [$scope, $name, $readonlyScope]) {
6464
$propertyScopes[$k = "\0$scope\0$name"] ?? $propertyScopes[$k = "\0*\0$name"] ?? $k = $name;
6565

66-
if ($k === $key && "\0$class\0lazyObjectId" !== $k) {
66+
if ($k === $key && "\0$class\0lazyObjectState" !== $k) {
6767
$classProperties[$readonlyScope ?? $scope][$name] = $key;
6868
}
6969
}

src/Symfony/Component/VarExporter/LazyGhostTrait.php

Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
trait LazyGhostTrait
1919
{
20-
private int $lazyObjectId;
20+
private LazyObjectState $lazyObjectState;
2121

2222
/**
2323
* Creates a lazy-loading ghost instance.
@@ -47,15 +47,14 @@ public static function createLazyGhost(\Closure|array $initializer, array $skipp
4747
$onlyProperties = null === $skippedProperties && \is_array($initializer) ? $initializer : null;
4848

4949
if (self::class !== $class = $instance ? $instance::class : static::class) {
50-
$skippedProperties["\0".self::class."\0lazyObjectId"] = true;
50+
$skippedProperties["\0".self::class."\0lazyObjectState"] = true;
5151
} elseif (\defined($class.'::LAZY_OBJECT_PROPERTY_SCOPES')) {
5252
Hydrator::$propertyScopes[$class] ??= $class::LAZY_OBJECT_PROPERTY_SCOPES;
5353
}
5454

5555
$instance ??= (Registry::$classReflectors[$class] ??= new \ReflectionClass($class))->newInstanceWithoutConstructor();
5656
Registry::$defaultProperties[$class] ??= (array) $instance;
57-
$instance->lazyObjectId = $id = spl_object_id($instance);
58-
Registry::$states[$id] = new LazyObjectState($initializer, $skippedProperties ??= []);
57+
$instance->lazyObjectState = new LazyObjectState($initializer, $skippedProperties ??= []);
5958

6059
foreach (Registry::$classResetters[$class] ??= Registry::getClassResetters($class) as $reset) {
6160
$reset($instance, $skippedProperties, $onlyProperties);
@@ -71,7 +70,7 @@ public static function createLazyGhost(\Closure|array $initializer, array $skipp
7170
*/
7271
public function isLazyObjectInitialized(bool $partial = false): bool
7372
{
74-
if (!$state = Registry::$states[$this->lazyObjectId ?? ''] ?? null) {
73+
if (!$state = $this->lazyObjectState ?? null) {
7574
return true;
7675
}
7776

@@ -101,7 +100,7 @@ public function isLazyObjectInitialized(bool $partial = false): bool
101100
*/
102101
public function initializeLazyObject(): static
103102
{
104-
if (!$state = Registry::$states[$this->lazyObjectId ?? ''] ?? null) {
103+
if (!$state = $this->lazyObjectState ?? null) {
105104
return $this;
106105
}
107106

@@ -151,7 +150,7 @@ public function initializeLazyObject(): static
151150
*/
152151
public function resetLazyObject(): bool
153152
{
154-
if (!$state = Registry::$states[$this->lazyObjectId ?? ''] ?? null) {
153+
if (!$state = $this->lazyObjectState ?? null) {
155154
return false;
156155
}
157156

@@ -169,7 +168,7 @@ public function &__get($name): mixed
169168

170169
if ([$class, , $readonlyScope] = $propertyScopes[$name] ?? null) {
171170
$scope = Registry::getScope($propertyScopes, $class, $name);
172-
$state = Registry::$states[$this->lazyObjectId ?? ''] ?? null;
171+
$state = $this->lazyObjectState ?? null;
173172

174173
if ($state && (null === $scope || isset($propertyScopes["\0$scope\0$name"]))
175174
&& LazyObjectState::STATUS_UNINITIALIZED_PARTIAL !== $state->initialize($this, $name, $readonlyScope ?? $scope)
@@ -215,7 +214,7 @@ public function __set($name, $value): void
215214

216215
if ([$class, , $readonlyScope] = $propertyScopes[$name] ?? null) {
217216
$scope = Registry::getScope($propertyScopes, $class, $name, $readonlyScope);
218-
$state = Registry::$states[$this->lazyObjectId ?? ''] ?? null;
217+
$state = $this->lazyObjectState ?? null;
219218

220219
if ($state && ($readonlyScope === $scope || isset($propertyScopes["\0$scope\0$name"]))) {
221220
if (LazyObjectState::STATUS_UNINITIALIZED_FULL === $state->status) {
@@ -248,7 +247,7 @@ public function __isset($name): bool
248247

249248
if ([$class, , $readonlyScope] = $propertyScopes[$name] ?? null) {
250249
$scope = Registry::getScope($propertyScopes, $class, $name);
251-
$state = Registry::$states[$this->lazyObjectId ?? ''] ?? null;
250+
$state = $this->lazyObjectState ?? null;
252251

253252
if ($state && (null === $scope || isset($propertyScopes["\0$scope\0$name"]))
254253
&& LazyObjectState::STATUS_UNINITIALIZED_PARTIAL !== $state->initialize($this, $name, $readonlyScope ?? $scope)
@@ -278,7 +277,7 @@ public function __unset($name): void
278277

279278
if ([$class, , $readonlyScope] = $propertyScopes[$name] ?? null) {
280279
$scope = Registry::getScope($propertyScopes, $class, $name, $readonlyScope);
281-
$state = Registry::$states[$this->lazyObjectId ?? ''] ?? null;
280+
$state = $this->lazyObjectState ?? null;
282281

283282
if ($state && ($readonlyScope === $scope || isset($propertyScopes["\0$scope\0$name"]))) {
284283
if (LazyObjectState::STATUS_UNINITIALIZED_FULL === $state->status) {
@@ -306,8 +305,8 @@ public function __unset($name): void
306305

307306
public function __clone(): void
308307
{
309-
if ($state = Registry::$states[$this->lazyObjectId ?? ''] ?? null) {
310-
Registry::$states[$this->lazyObjectId = spl_object_id($this)] = clone $state;
308+
if ($state = $this->lazyObjectState ?? null) {
309+
$this->lazyObjectState = clone $state;
311310
}
312311

313312
if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['clone']) {
@@ -325,7 +324,7 @@ public function __serialize(): array
325324
$this->initializeLazyObject();
326325
$properties = (array) $this;
327326
}
328-
unset($properties["\0$class\0lazyObjectId"]);
327+
unset($properties["\0$class\0lazyObjectState"]);
329328

330329
if (Registry::$parentMethods[$class]['serialize'] || !Registry::$parentMethods[$class]['sleep']) {
331330
return $properties;
@@ -349,26 +348,20 @@ public function __serialize(): array
349348

350349
public function __destruct()
351350
{
352-
$state = Registry::$states[$this->lazyObjectId ?? ''] ?? null;
351+
$state = $this->lazyObjectState ?? null;
353352

354-
try {
355-
if ($state && \in_array($state->status, [LazyObjectState::STATUS_UNINITIALIZED_FULL, LazyObjectState::STATUS_UNINITIALIZED_PARTIAL], true)) {
356-
return;
357-
}
353+
if ($state && \in_array($state->status, [LazyObjectState::STATUS_UNINITIALIZED_FULL, LazyObjectState::STATUS_UNINITIALIZED_PARTIAL], true)) {
354+
return;
355+
}
358356

359-
if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['destruct']) {
360-
parent::__destruct();
361-
}
362-
} finally {
363-
if ($state) {
364-
unset(Registry::$states[$this->lazyObjectId]);
365-
}
357+
if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['destruct']) {
358+
parent::__destruct();
366359
}
367360
}
368361

369362
private function setLazyObjectAsInitialized(bool $initialized): void
370363
{
371-
$state = Registry::$states[$this->lazyObjectId ?? ''];
364+
$state = $this->lazyObjectState ?? null;
372365

373366
if ($state && !\is_array($state->initializer)) {
374367
$state->status = $initialized ? LazyObjectState::STATUS_INITIALIZED_FULL : LazyObjectState::STATUS_UNINITIALIZED_FULL;

src/Symfony/Component/VarExporter/LazyProxyTrait.php

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
trait LazyProxyTrait
2020
{
21-
private int $lazyObjectId;
21+
private LazyObjectState $lazyObjectState;
2222
private object $lazyObjectReal;
2323

2424
/**
@@ -29,14 +29,13 @@ trait LazyProxyTrait
2929
public static function createLazyProxy(\Closure $initializer, self $instance = null): static
3030
{
3131
if (self::class !== $class = $instance ? $instance::class : static::class) {
32-
$skippedProperties = ["\0".self::class."\0lazyObjectId" => true];
32+
$skippedProperties = ["\0".self::class."\0lazyObjectState" => true];
3333
} elseif (\defined($class.'::LAZY_OBJECT_PROPERTY_SCOPES')) {
3434
Hydrator::$propertyScopes[$class] ??= $class::LAZY_OBJECT_PROPERTY_SCOPES;
3535
}
3636

3737
$instance ??= (Registry::$classReflectors[$class] ??= new \ReflectionClass($class))->newInstanceWithoutConstructor();
38-
$instance->lazyObjectId = $id = spl_object_id($instance);
39-
Registry::$states[$id] = new LazyObjectState($initializer);
38+
$instance->lazyObjectState = new LazyObjectState($initializer);
4039

4140
foreach (Registry::$classResetters[$class] ??= Registry::getClassResetters($class) as $reset) {
4241
$reset($instance, $skippedProperties ??= []);
@@ -52,7 +51,7 @@ public static function createLazyProxy(\Closure $initializer, self $instance = n
5251
*/
5352
public function isLazyObjectInitialized(bool $partial = false): bool
5453
{
55-
if (0 >= ($this->lazyObjectId ?? 0)) {
54+
if (!isset($this->lazyObjectState) || Registry::$noInitializerState === $this->lazyObjectState) {
5655
return true;
5756
}
5857

@@ -76,7 +75,7 @@ public function initializeLazyObject(): parent
7675
*/
7776
public function resetLazyObject(): bool
7877
{
79-
if (0 >= ($this->lazyObjectId ?? 0)) {
78+
if (!isset($this->lazyObjectState) || Registry::$noInitializerState === $this->lazyObjectState) {
8079
return false;
8180
}
8281

@@ -97,10 +96,9 @@ public function &__get($name): mixed
9796
$scope = Registry::getScope($propertyScopes, $class, $name);
9897

9998
if (null === $scope || isset($propertyScopes["\0$scope\0$name"])) {
100-
if (isset($this->lazyObjectId)) {
99+
if ($state = $this->lazyObjectState ?? null) {
101100
if ('lazyObjectReal' === $name && self::class === $scope) {
102-
$state = Registry::$states[$this->lazyObjectId] ?? null;
103-
$this->lazyObjectReal = $state ? ($state->initializer)() : null;
101+
$this->lazyObjectReal = ($state->initializer)();
104102

105103
return $this->lazyObjectReal;
106104
}
@@ -155,7 +153,7 @@ public function __set($name, $value): void
155153
$scope = Registry::getScope($propertyScopes, $class, $name, $readonlyScope);
156154

157155
if ($readonlyScope === $scope || isset($propertyScopes["\0$scope\0$name"])) {
158-
if (isset($this->lazyObjectId)) {
156+
if (isset($this->lazyObjectState)) {
159157
if ('lazyObjectReal' === $name && self::class === $scope) {
160158
$this->lazyObjectReal = $value;
161159

@@ -197,9 +195,9 @@ public function __isset($name): bool
197195
$scope = Registry::getScope($propertyScopes, $class, $name);
198196

199197
if (null === $scope || isset($propertyScopes["\0$scope\0$name"])) {
200-
if (isset($this->lazyObjectId)) {
198+
if (isset($this->lazyObjectState)) {
201199
if ('lazyObjectReal' === $name && self::class === $scope) {
202-
$state = Registry::$states[$this->lazyObjectId] ?? null;
200+
$state = $this->lazyObjectState ?? null;
203201

204202
return null !== $this->lazyObjectReal = $state ? ($state->initializer)() : null;
205203
}
@@ -237,7 +235,7 @@ public function __unset($name): void
237235
$scope = Registry::getScope($propertyScopes, $class, $name, $readonlyScope);
238236

239237
if ($readonlyScope === $scope || isset($propertyScopes["\0$scope\0$name"])) {
240-
if (isset($this->lazyObjectId)) {
238+
if (isset($this->lazyObjectState)) {
241239
if ('lazyObjectReal' === $name && self::class === $scope) {
242240
unset($this->lazyObjectReal);
243241

@@ -271,7 +269,7 @@ public function __unset($name): void
271269

272270
public function __clone(): void
273271
{
274-
if (!isset($this->lazyObjectId)) {
272+
if (!isset($this->lazyObjectState)) {
275273
if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['clone']) {
276274
parent::__clone();
277275
}
@@ -282,8 +280,8 @@ public function __clone(): void
282280
if (\array_key_exists("\0".self::class."\0lazyObjectReal", (array) $this)) {
283281
$this->lazyObjectReal = clone $this->lazyObjectReal;
284282
}
285-
if ($state = Registry::$states[$this->lazyObjectId] ?? null) {
286-
Registry::$states[$this->lazyObjectId = spl_object_id($this)] = clone $state;
283+
if ($state = $this->lazyObjectState ?? null) {
284+
$this->lazyObjectState = clone $state;
287285
}
288286
}
289287

@@ -296,7 +294,7 @@ public function __serialize(): array
296294
} else {
297295
$properties = (array) $this;
298296
}
299-
unset($properties["\0$class\0lazyObjectId"]);
297+
unset($properties["\0$class\0lazyObjectState"]);
300298

301299
if (isset($this->lazyObjectReal) || Registry::$parentMethods[$class]['serialize'] || !Registry::$parentMethods[$class]['sleep']) {
302300
return $properties;
@@ -332,8 +330,7 @@ public function __unserialize(array $data): void
332330
} else {
333331
$this->lazyObjectReal = $data["\0$class\0lazyObjectReal"];
334332
}
335-
Registry::$states[-1] ??= new LazyObjectState(static fn () => throw new \LogicException('Lazy proxy has no initializer.'));
336-
$this->lazyObjectId = -1;
333+
$this->lazyObjectState = Registry::$noInitializerState ??= new LazyObjectState(static fn () => throw new \LogicException('Lazy proxy has no initializer.'));
337334
} elseif ((Registry::$parentMethods[$class] ??= Registry::getParentMethods($class))['unserialize']) {
338335
parent::__unserialize($data);
339336
} else {
@@ -347,11 +344,7 @@ public function __unserialize(array $data): void
347344

348345
public function __destruct()
349346
{
350-
if (isset($this->lazyObjectId)) {
351-
if (0 < $this->lazyObjectId) {
352-
unset(Registry::$states[$this->lazyObjectId]);
353-
}
354-
347+
if (isset($this->lazyObjectState)) {
355348
return;
356349
}
357350

0 commit comments

Comments
 (0)