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

Skip to content

[VarExporter] Fix possible memory-leak when using lazy-objects #48461

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 1 addition & 10 deletions src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
use Symfony\Component\DependencyInjection\ExpressionLanguage;
use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface as ProxyDumper;
use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\LazyServiceDumper;
Expand Down Expand Up @@ -168,15 +167,7 @@ public function dump(array $options = []): string|array

if ($this->getProxyDumper() instanceof NullDumper) {
(new AnalyzeServiceReferencesPass(true, false))->process($this->container);
try {
(new CheckCircularReferencesPass())->process($this->container);
} catch (ServiceCircularReferenceException $e) {
$path = $e->getPath();
end($path);
$path[key($path)] .= '". Try running "composer require symfony/proxy-manager-bridge';

throw new ServiceCircularReferenceException($e->getServiceId(), $path);
}
(new CheckCircularReferencesPass())->process($this->container);
}

$this->analyzeReferences();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -765,7 +765,7 @@ public function testCircularReferenceAllowanceForLazyServices()
$dumper = new PhpDumper($container);
$dumper->setProxyDumper(new NullDumper());

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

Expand Down
3 changes: 1 addition & 2 deletions src/Symfony/Component/DependencyInjection/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@
"symfony/yaml": "",
"symfony/config": "",
"symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required",
"symfony/expression-language": "For using expressions in service container configuration",
"symfony/proxy-manager-bridge": "Generate service proxies to lazy load them"
"symfony/expression-language": "For using expressions in service container configuration"
},
"conflict": {
"ext-psr": "<1.1|>=2",
Expand Down
17 changes: 17 additions & 0 deletions src/Symfony/Component/VarDumper/Caster/SymfonyCaster.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Symfony\Component\Uid\Ulid;
use Symfony\Component\Uid\Uuid;
use Symfony\Component\VarDumper\Cloner\Stub;
use Symfony\Component\VarExporter\Internal\LazyObjectState;

/**
* @final
Expand Down Expand Up @@ -67,6 +68,22 @@ public static function castHttpClientResponse($response, array $a, Stub $stub, b
return $a;
}

public static function castLazyObjectState($state, array $a, Stub $stub, bool $isNested)
{
if (!$isNested) {
return $a;
}

$stub->cut += \count($a) - 1;

return ['status' => new ConstStub(match ($a['status']) {
LazyObjectState::STATUS_INITIALIZED_FULL => 'INITIALIZED_FULL',
LazyObjectState::STATUS_INITIALIZED_PARTIAL => 'INITIALIZED_PARTIAL',
LazyObjectState::STATUS_UNINITIALIZED_FULL => 'UNINITIALIZED_FULL',
LazyObjectState::STATUS_UNINITIALIZED_PARTIAL => 'UNINITIALIZED_PARTIAL',
}, $a['status'])];
}

public static function castUuid(Uuid $uuid, array $a, Stub $stub, bool $isNested)
{
$a[Caster::PREFIX_VIRTUAL.'toBase58'] = $uuid->toBase58();
Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ abstract class AbstractCloner implements ClonerInterface
'Symfony\Component\HttpFoundation\Request' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castRequest'],
'Symfony\Component\Uid\Ulid' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castUlid'],
'Symfony\Component\Uid\Uuid' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castUuid'],
'Symfony\Component\VarExporter\Internal\LazyObjectState' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castLazyObjectState'],
'Symfony\Component\VarDumper\Exception\ThrowingCasterException' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castThrowingCasterException'],
'Symfony\Component\VarDumper\Caster\TraceStub' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castTraceStub'],
'Symfony\Component\VarDumper\Caster\FrameStub' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castFrameStub'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,6 @@
*/
class LazyObjectRegistry
{
/**
* @var array<int, LazyObjectState>
*/
public static $states = [];

/**
* @var array<class-string, \ReflectionClass>
*/
Expand All @@ -50,6 +45,11 @@ class LazyObjectRegistry
*/
public static $parentMethods = [];

/**
* @var LazyObjectState
*/
public static $noInitializerState;

public static function getClassResetters($class)
{
$classProperties = [];
Expand All @@ -63,7 +63,7 @@ public static function getClassResetters($class)
foreach ($propertyScopes as $key => [$scope, $name, $readonlyScope]) {
$propertyScopes[$k = "\0$scope\0$name"] ?? $propertyScopes[$k = "\0*\0$name"] ?? $k = $name;

if ($k === $key && "\0$class\0lazyObjectId" !== $k) {
if ($k === $key && "\0$class\0lazyObjectState" !== $k) {
$classProperties[$readonlyScope ?? $scope][$name] = $key;
}
}
Expand Down
47 changes: 20 additions & 27 deletions src/Symfony/Component/VarExporter/LazyGhostTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

trait LazyGhostTrait
{
private int $lazyObjectId;
private LazyObjectState $lazyObjectState;

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

if (self::class !== $class = $instance ? $instance::class : static::class) {
$skippedProperties["\0".self::class."\0lazyObjectId"] = true;
$skippedProperties["\0".self::class."\0lazyObjectState"] = true;
} elseif (\defined($class.'::LAZY_OBJECT_PROPERTY_SCOPES')) {
Hydrator::$propertyScopes[$class] ??= $class::LAZY_OBJECT_PROPERTY_SCOPES;
}

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

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

Expand Down Expand Up @@ -101,7 +100,7 @@ public function isLazyObjectInitialized(bool $partial = false): bool
*/
public function initializeLazyObject(): static
{
if (!$state = Registry::$states[$this->lazyObjectId ?? ''] ?? null) {
if (!$state = $this->lazyObjectState ?? null) {
return $this;
}

Expand Down Expand Up @@ -151,7 +150,7 @@ public function initializeLazyObject(): static
*/
public function resetLazyObject(): bool
{
if (!$state = Registry::$states[$this->lazyObjectId ?? ''] ?? null) {
if (!$state = $this->lazyObjectState ?? null) {
return false;
}

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

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

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

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

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

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

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

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

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

public function __clone(): void
{
if ($state = Registry::$states[$this->lazyObjectId ?? ''] ?? null) {
Registry::$states[$this->lazyObjectId = spl_object_id($this)] = clone $state;
if ($state = $this->lazyObjectState ?? null) {
$this->lazyObjectState = clone $state;
}

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

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

public function __destruct()
{
$state = Registry::$states[$this->lazyObjectId ?? ''] ?? null;
$state = $this->lazyObjectState ?? null;

try {
if ($state && \in_array($state->status, [LazyObjectState::STATUS_UNINITIALIZED_FULL, LazyObjectState::STATUS_UNINITIALIZED_PARTIAL], true)) {
return;
}
if ($state && \in_array($state->status, [LazyObjectState::STATUS_UNINITIALIZED_FULL, LazyObjectState::STATUS_UNINITIALIZED_PARTIAL], true)) {
return;
}

if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['destruct']) {
parent::__destruct();
}
} finally {
if ($state) {
unset(Registry::$states[$this->lazyObjectId]);
}
if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['destruct']) {
parent::__destruct();
}
}

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

if ($state && !\is_array($state->initializer)) {
$state->status = $initialized ? LazyObjectState::STATUS_INITIALIZED_FULL : LazyObjectState::STATUS_UNINITIALIZED_FULL;
Expand Down
41 changes: 17 additions & 24 deletions src/Symfony/Component/VarExporter/LazyProxyTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

trait LazyProxyTrait
{
private int $lazyObjectId;
private LazyObjectState $lazyObjectState;
private object $lazyObjectReal;

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

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

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

Expand All @@ -76,7 +75,7 @@ public function initializeLazyObject(): parent
*/
public function resetLazyObject(): bool
{
if (0 >= ($this->lazyObjectId ?? 0)) {
if (!isset($this->lazyObjectState) || Registry::$noInitializerState === $this->lazyObjectState) {
return false;
}

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

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

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

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

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

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

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

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

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

public function __clone(): void
{
if (!isset($this->lazyObjectId)) {
if (!isset($this->lazyObjectState)) {
if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['clone']) {
parent::__clone();
}
Expand All @@ -282,8 +280,8 @@ public function __clone(): void
if (\array_key_exists("\0".self::class."\0lazyObjectReal", (array) $this)) {
$this->lazyObjectReal = clone $this->lazyObjectReal;
}
if ($state = Registry::$states[$this->lazyObjectId] ?? null) {
Registry::$states[$this->lazyObjectId = spl_object_id($this)] = clone $state;
if ($state = $this->lazyObjectState ?? null) {
$this->lazyObjectState = clone $state;
}
}

Expand All @@ -296,7 +294,7 @@ public function __serialize(): array
} else {
$properties = (array) $this;
}
unset($properties["\0$class\0lazyObjectId"]);
unset($properties["\0$class\0lazyObjectState"]);

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

public function __destruct()
{
if (isset($this->lazyObjectId)) {
if (0 < $this->lazyObjectId) {
unset(Registry::$states[$this->lazyObjectId]);
}

if (isset($this->lazyObjectState)) {
return;
}

Expand Down
Loading