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

Skip to content

Commit c14a2d7

Browse files
authored
fix: WeakMap indirect modification bug (#157)
1 parent 213f780 commit c14a2d7

File tree

5 files changed

+116
-11
lines changed

5 files changed

+116
-11
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## 2.2.2
44

55
* fix: fix generics on `AttributeReconstitutor`
6+
* fix: `WeakMap` indirect modification bug
67

78
## 2.2.1
89

packages/file-association/src/PropertyRecorder/PropertyRecorder.php

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
final class PropertyRecorder implements ResetInterface
1919
{
2020
/**
21-
* @var \WeakMap<object,array<string,mixed>>
21+
* @var \WeakMap<object,\ArrayObject<string,mixed>>
2222
*/
2323
private \WeakMap $properties;
2424

@@ -35,8 +35,9 @@ public function reset(): void
3535

3636
private function init(): void
3737
{
38-
/** @var \WeakMap<object,array<string,mixed>> */
38+
/** @var \WeakMap<object,\ArrayObject<string,mixed>> */
3939
$properties = new \WeakMap();
40+
4041
$this->properties = $properties;
4142
}
4243

@@ -46,11 +47,15 @@ public function saveInitialProperty(
4647
mixed $value,
4748
): void {
4849
if (!isset($this->properties[$object])) {
49-
$this->properties[$object] = [];
50+
/** @var \ArrayObject<string,mixed> */
51+
$arrayObject = new \ArrayObject();
52+
53+
$this->properties->offsetSet($object, $arrayObject);
5054
}
5155

52-
/** @psalm-suppress MixedArgument */
53-
$this->properties[$object][$propertyName] = $value;
56+
$this->properties
57+
->offsetGet($object)
58+
->offsetSet($propertyName, $value);
5459
}
5560

5661
public function getInitialProperty(
@@ -61,8 +66,9 @@ public function getInitialProperty(
6166
return null;
6267
}
6368

64-
/** @psalm-suppress PossiblyNullArrayAccess */
65-
return $this->properties[$object][$propertyName] ?? null;
69+
return $this->properties
70+
->offsetGet($object)
71+
->offsetGet($propertyName);
6672
}
6773

6874
public function hasInitialProperty(
@@ -73,8 +79,9 @@ public function hasInitialProperty(
7379
return false;
7480
}
7581

76-
/** @psalm-suppress PossiblyNullArrayAccess */
77-
return isset($this->properties[$object][$propertyName]);
82+
return $this->properties
83+
->offsetGet($object)
84+
->offsetExists($propertyName);
7885
}
7986

8087
public function removeInitialProperty(
@@ -85,7 +92,8 @@ public function removeInitialProperty(
8592
return;
8693
}
8794

88-
/** @psalm-suppress PossiblyNullArrayAccess */
89-
unset($this->properties[$object][$propertyName]);
95+
$this->properties
96+
->offsetGet($object)
97+
->offsetUnset($propertyName);
9098
}
9199
}

psalm.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@
6666
<file name="tests/src/Tests/FileImage/TwigTest.php" />
6767
</errorLevel>
6868
</MissingOverrideAttribute>
69+
<PossiblyNullReference>
70+
<errorLevel type="suppress">
71+
<file name="packages/file-association/src/PropertyRecorder/PropertyRecorder.php"/>
72+
</errorLevel>
73+
</PossiblyNullReference>
6974
</issueHandlers>
7075

7176
<plugins>

tests/src/Tests/Architecture/Architecture.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ public function testFileAssociation(): Rule
208208
Selector::classname(\DateTimeInterface::class),
209209
Selector::classname(\DateTimeImmutable::class),
210210
Selector::classname(\WeakMap::class),
211+
Selector::classname(\ArrayObject::class),
211212
Selector::classname(\UnitEnum::class),
212213

213214
// reflection
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of rekalogika/file-src package.
7+
*
8+
* (c) Priyadi Iman Nurcahyo <https://rekalogika.dev>
9+
*
10+
* For the full copyright and license information, please view the LICENSE file
11+
* that was distributed with this source code.
12+
*/
13+
14+
namespace Rekalogika\File\Tests\Tests\FileAssociation;
15+
16+
use PHPUnit\Framework\TestCase;
17+
use Rekalogika\File\Association\PropertyRecorder\PropertyRecorder;
18+
19+
final class PropertyRecorderTest extends TestCase
20+
{
21+
private PropertyRecorder $propertyRecorder;
22+
23+
#[\Override]
24+
protected function setUp(): void
25+
{
26+
$this->propertyRecorder = new PropertyRecorder();
27+
}
28+
29+
public function testSaveInitialProperty(): void
30+
{
31+
$object = new \stdClass();
32+
$propertyName = 'testProperty';
33+
$value = 'testValue';
34+
35+
$this->propertyRecorder->saveInitialProperty($object, $propertyName, $value);
36+
37+
$this->assertTrue($this->propertyRecorder->hasInitialProperty($object, $propertyName));
38+
$this->assertSame($value, $this->propertyRecorder->getInitialProperty($object, $propertyName));
39+
}
40+
41+
public function testGetInitialProperty(): void
42+
{
43+
$object = new \stdClass();
44+
$propertyName = 'testProperty';
45+
$value = 'testValue';
46+
47+
$this->propertyRecorder->saveInitialProperty($object, $propertyName, $value);
48+
49+
$this->assertSame($value, $this->propertyRecorder->getInitialProperty($object, $propertyName));
50+
$this->assertNull($this->propertyRecorder->getInitialProperty(new \stdClass(), 'nonExistentProperty'));
51+
}
52+
53+
public function testHasInitialProperty(): void
54+
{
55+
$object = new \stdClass();
56+
$propertyName = 'testProperty';
57+
58+
$this->assertFalse($this->propertyRecorder->hasInitialProperty($object, $propertyName));
59+
60+
$this->propertyRecorder->saveInitialProperty($object, $propertyName, 'testValue');
61+
62+
$this->assertTrue($this->propertyRecorder->hasInitialProperty($object, $propertyName));
63+
}
64+
65+
public function testRemoveInitialProperty(): void
66+
{
67+
$object = new \stdClass();
68+
$propertyName = 'testProperty';
69+
$value = 'testValue';
70+
71+
$this->propertyRecorder->saveInitialProperty($object, $propertyName, $value);
72+
$this->assertTrue($this->propertyRecorder->hasInitialProperty($object, $propertyName));
73+
74+
$this->propertyRecorder->removeInitialProperty($object, $propertyName);
75+
$this->assertFalse($this->propertyRecorder->hasInitialProperty($object, $propertyName));
76+
}
77+
78+
public function testReset(): void
79+
{
80+
$object = new \stdClass();
81+
$propertyName = 'testProperty';
82+
$value = 'testValue';
83+
84+
$this->propertyRecorder->saveInitialProperty($object, $propertyName, $value);
85+
$this->assertTrue($this->propertyRecorder->hasInitialProperty($object, $propertyName));
86+
87+
$this->propertyRecorder->reset();
88+
$this->assertFalse($this->propertyRecorder->hasInitialProperty($object, $propertyName));
89+
}
90+
}

0 commit comments

Comments
 (0)