From 218aad0804bebd8f0ad60eea69afb24d8fa4ca48 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 12 Jul 2023 14:10:30 +0200 Subject: [PATCH] Fix access to uninitialized property via extension in additional constructor --- src/Node/ClassPropertiesNode.php | 36 +++++++++---------- .../UninitializedPropertyRuleTest.php | 28 +++++++++++++++ .../Rules/Properties/data/bug-9619.php | 23 ++++++++++++ 3 files changed, 68 insertions(+), 19 deletions(-) create mode 100644 tests/PHPStan/Rules/Properties/data/bug-9619.php diff --git a/src/Node/ClassPropertiesNode.php b/src/Node/ClassPropertiesNode.php index b2dea4ac11..e9d9a36b80 100644 --- a/src/Node/ClassPropertiesNode.php +++ b/src/Node/ClassPropertiesNode.php @@ -109,6 +109,9 @@ public function getUninitializedProperties( $originalProperties = []; $initialInitializedProperties = []; $initializedProperties = []; + if ($extensions === null) { + $extensions = $this->readWritePropertiesExtensionProvider->getExtensions(); + } foreach ($this->getProperties() as $property) { if ($property->isStatic()) { continue; @@ -121,34 +124,29 @@ public function getUninitializedProperties( } $originalProperties[$property->getName()] = $property; $is = TrinaryLogic::createFromBoolean($property->isPromoted() && !$property->isPromotedFromTrait()); + if (!$is->yes()) { + foreach ($extensions as $extension) { + if (!$classReflection->hasNativeProperty($property->getName())) { + continue; + } + $propertyReflection = $classReflection->getNativeProperty($property->getName()); + if (!$extension->isInitialized($propertyReflection, $property->getName())) { + continue; + } + $is = TrinaryLogic::createYes(); + break; + } + } $initialInitializedProperties[$property->getName()] = $is; foreach ($constructors as $constructor) { $initializedProperties[$constructor][$property->getName()] = $is; } - if ($property->isPromoted() && !$property->isPromotedFromTrait()) { + if ($is->yes()) { continue; } $uninitializedProperties[$property->getName()] = $property; } - if ($extensions === null) { - $extensions = $this->readWritePropertiesExtensionProvider->getExtensions(); - } - - foreach (array_keys($uninitializedProperties) as $name) { - foreach ($extensions as $extension) { - if (!$classReflection->hasNativeProperty($name)) { - continue; - } - $propertyReflection = $classReflection->getNativeProperty($name); - if (!$extension->isInitialized($propertyReflection, $name)) { - continue; - } - unset($uninitializedProperties[$name]); - break; - } - } - if ($constructors === []) { return [$uninitializedProperties, [], []]; } diff --git a/tests/PHPStan/Rules/Properties/UninitializedPropertyRuleTest.php b/tests/PHPStan/Rules/Properties/UninitializedPropertyRuleTest.php index ba1fc29f10..7db22f6860 100644 --- a/tests/PHPStan/Rules/Properties/UninitializedPropertyRuleTest.php +++ b/tests/PHPStan/Rules/Properties/UninitializedPropertyRuleTest.php @@ -6,6 +6,7 @@ use PHPStan\Reflection\PropertyReflection; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; +use function strpos; /** * @extends RuleTestCase @@ -20,6 +21,7 @@ protected function getRule(): Rule self::getContainer(), [ 'UninitializedProperty\\TestCase::setUp', + 'Bug9619\\AdminPresenter::startup', ], ), ); @@ -46,6 +48,27 @@ public function isInitialized(PropertyReflection $property, string $propertyName } }, + + // bug-9619 + new class() implements ReadWritePropertiesExtension { + + public function isAlwaysRead(PropertyReflection $property, string $propertyName): bool + { + return false; + } + + public function isAlwaysWritten(PropertyReflection $property, string $propertyName): bool + { + return $this->isInitialized($property, $propertyName); + } + + public function isInitialized(PropertyReflection $property, string $propertyName): bool + { + return $property->isPublic() && + strpos($property->getDocComment() ?? '', '@inject') !== false; + } + + }, ]; } @@ -156,4 +179,9 @@ public function testEfabricaLatteBug(): void $this->analyse([__DIR__ . '/data/efabrica-latte-bug.php'], []); } + public function testBug9619(): void + { + $this->analyse([__DIR__ . '/data/bug-9619.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/bug-9619.php b/tests/PHPStan/Rules/Properties/data/bug-9619.php new file mode 100644 index 0000000000..c80367633c --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-9619.php @@ -0,0 +1,23 @@ += 7.4 + +namespace Bug9619; + +interface User +{ + + public function isLoggedIn(): bool; + +} + +class AdminPresenter +{ + /** @inject */ + public User $user; + + public function startup() + { + if (!$this->user->isLoggedIn()) { + // do something + } + } +}