Skip to content

Commit b66058f

Browse files
Add support for internal classes that overload offset access
Co-authored-by: Ondřej Mirtes <[email protected]>
1 parent 2d64686 commit b66058f

6 files changed

+293
-1
lines changed

src/Type/ObjectType.php

+13-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,19 @@ class ObjectType implements TypeWithClassName, SubtractableType
6767
use UndecidedComparisonTypeTrait;
6868
use NonGeneralizableTypeTrait;
6969

70-
private const EXTRA_OFFSET_CLASSES = ['SimpleXMLElement', 'DOMNodeList', 'Threaded'];
70+
private const EXTRA_OFFSET_CLASSES = [
71+
'DOMNamedNodeMap', // Only read and existence
72+
'Dom\NamedNodeMap', // Only read and existence
73+
'DOMNodeList', // Only read and existence
74+
'Dom\NodeList', // Only read and existence
75+
'Dom\HTMLCollection', // Only read and existence
76+
'Dom\DtdNamedNodeMap', // Only read and existence
77+
'PDORow', // Only read and existence
78+
'ResourceBundle', // Only read
79+
'FFI\CData', // Very funky and weird
80+
'SimpleXMLElement',
81+
'Threaded',
82+
];
7183

7284
private ?Type $subtractedType;
7385

tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php

+26
Original file line numberDiff line numberDiff line change
@@ -898,4 +898,30 @@ public function testBug2634(): void
898898
$this->analyse([__DIR__ . '/data/bug-2634.php'], []);
899899
}
900900

901+
public function testInternalClassesWithOverloadedOffsetAccess(): void
902+
{
903+
$this->analyse([__DIR__ . '/data/internal-classes-overload-offset-access.php'], []);
904+
}
905+
906+
public function testInternalClassesWithOverloadedOffsetAccess84(): void
907+
{
908+
if (PHP_VERSION_ID < 80400) {
909+
$this->markTestSkipped('Test requires PHP 8.4.');
910+
}
911+
$this->analyse([__DIR__ . '/data/internal-classes-overload-offset-access-php84.php'], []);
912+
}
913+
914+
public function testInternalClassesWithOverloadedOffsetAccessInvalid(): void
915+
{
916+
$this->analyse([__DIR__ . '/data/internal-classes-overload-offset-access-invalid.php'], []);
917+
}
918+
919+
public function testInternalClassesWithOverloadedOffsetAccessInvalid84(): void
920+
{
921+
if (PHP_VERSION_ID < 80400) {
922+
$this->markTestSkipped('Test requires PHP 8.4.');
923+
}
924+
$this->analyse([__DIR__ . '/data/internal-classes-overload-offset-access-invalid-php84.php'], []);
925+
}
926+
901927
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<?php
2+
/**
3+
* All of these offset accesses are invalid
4+
* ++ and -- are also disallowed in general are they operate "by ref"
5+
*/
6+
namespace InternalClassesOverloadOffsetAccessInvalid\Php84;
7+
8+
function test1(\DOMNamedNodeMap $v): void
9+
{
10+
$v[] = 'append';
11+
$v[0] = 'update';
12+
$v[0] .= ' and again';
13+
$r1 = &$v[0];
14+
unset($v[0]);
15+
var_dump($r1);
16+
}
17+
18+
function test2(\Dom\NamedNodeMap $v): void
19+
{
20+
$v[] = 'append';
21+
$v[0] = 'update';
22+
$v[0] .= ' and again';
23+
$r1 = &$v[0];
24+
unset($v[0]);
25+
var_dump($r1);
26+
}
27+
28+
function test3(\DOMNodeList $v): void
29+
{
30+
$v[] = 'append';
31+
$v[0] = 'update';
32+
$v[0] .= ' and again';
33+
$r1 = &$v[0];
34+
unset($v[0]);
35+
var_dump($r1);
36+
}
37+
38+
function test4(\Dom\NodeList $v): void
39+
{
40+
$v[] = 'append';
41+
$v[0] = 'update';
42+
$v[0] .= ' and again';
43+
$r1 = &$v[0];
44+
unset($v[0]);
45+
var_dump($r1);
46+
}
47+
48+
function test5(\Dom\HTMLCollection $v): void
49+
{
50+
$v[] = 'append';
51+
$v[0] = 'update';
52+
$v[0] .= ' and again';
53+
$r1 = &$v[0];
54+
unset($v[0]);
55+
var_dump($r1);
56+
}
57+
58+
function test6(\Dom\DtdNamedNodeMap $v): void
59+
{
60+
$v[] = 'append';
61+
$v[0] = 'update';
62+
$v[0] .= ' and again';
63+
$r1 = &$v[0];
64+
unset($v[0]);
65+
var_dump($r1);
66+
}
67+
68+
function test7(\PDORow $v): void
69+
{
70+
$v[] = 'append';
71+
$v[0] = 'update';
72+
$v[0] .= ' and again';
73+
$r1 = &$v[0];
74+
unset($v[0]);
75+
var_dump($r1);
76+
}
77+
78+
function test8(\ResourceBundle $v): void
79+
{
80+
if ($v[0]) {
81+
var_dump($v[0]);
82+
}
83+
$v[] = 'append';
84+
$v[0] = 'update';
85+
$v[0] .= ' and again';
86+
$r1 = &$v[0];
87+
unset($v[0]);
88+
var_dump($r1);
89+
var_dump($v['name']);
90+
var_dump($v[0]);
91+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
/**
3+
* All of these offset accesses are invalid
4+
* ++ and -- are also disallowed in general are they operate "by ref"
5+
*/
6+
namespace InternalClassesOverloadOffsetAccessInvalid;
7+
8+
function test1(\DOMNamedNodeMap $v): void
9+
{
10+
$v[] = 'append';
11+
$v[0] = 'update';
12+
$v[0] .= ' and again';
13+
$r1 = &$v[0];
14+
unset($v[0]);
15+
var_dump($r1);
16+
}
17+
18+
function test3(\DOMNodeList $v): void
19+
{
20+
$v[] = 'append';
21+
$v[0] = 'update';
22+
$v[0] .= ' and again';
23+
$r1 = &$v[0];
24+
unset($v[0]);
25+
var_dump($r1);
26+
}
27+
28+
function test7(\PDORow $v): void
29+
{
30+
$v[] = 'append';
31+
$v[0] = 'update';
32+
$v[0] .= ' and again';
33+
$r1 = &$v[0];
34+
unset($v[0]);
35+
var_dump($r1);
36+
}
37+
38+
function test8(\ResourceBundle $v): void
39+
{
40+
if ($v[0]) {
41+
var_dump($v[0]);
42+
}
43+
$v[] = 'append';
44+
$v[0] = 'update';
45+
$v[0] .= ' and again';
46+
$r1 = &$v[0];
47+
unset($v[0]);
48+
var_dump($r1);
49+
var_dump($v['name']);
50+
var_dump($v[0]);
51+
}
52+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?php
2+
3+
namespace InternalClassesOverloadOffsetAccess\Php84;
4+
5+
function test1(\DOMNamedNodeMap $v): void
6+
{
7+
if ($v['attribute_name']) {
8+
var_dump($v['attribute_name']);
9+
}
10+
if ($v[0]) {
11+
var_dump($v[0]);
12+
}
13+
}
14+
15+
function test2(\Dom\NamedNodeMap $v): void
16+
{
17+
if ($v['attribute_name']) {
18+
var_dump($v['attribute_name']);
19+
}
20+
if ($v[0]) {
21+
var_dump($v[0]);
22+
}
23+
}
24+
25+
function test3(\DOMNodeList $v): void
26+
{
27+
if ($v[0]) {
28+
var_dump($v[0]);
29+
}
30+
}
31+
32+
function test4(\Dom\NodeList $v): void
33+
{
34+
//var_dump($v['attribute_name']);
35+
if ($v[0]) {
36+
var_dump($v[0]);
37+
}
38+
}
39+
40+
function test5(\Dom\HTMLCollection $v): void
41+
{
42+
if ($v['name']) {
43+
var_dump($v['name']);
44+
}
45+
if ($v[0]) {
46+
var_dump($v[0]);
47+
}
48+
}
49+
50+
function test6(\Dom\DtdNamedNodeMap $v): void
51+
{
52+
if ($v['name']) {
53+
var_dump($v['name']);
54+
}
55+
if ($v[0]) {
56+
var_dump($v[0]);
57+
}
58+
}
59+
60+
function test7(\PDORow $v): void
61+
{
62+
if ($v['name']) {
63+
var_dump($v['name']);
64+
}
65+
if ($v[0]) {
66+
var_dump($v[0]);
67+
}
68+
}
69+
70+
function test8(\ResourceBundle $v): void
71+
{
72+
var_dump($v['name']);
73+
var_dump($v[0]);
74+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
namespace InternalClassesOverloadOffsetAccess;
4+
5+
function test1(\DOMNamedNodeMap $v): void
6+
{
7+
if ($v['attribute_name']) {
8+
var_dump($v['attribute_name']);
9+
}
10+
if ($v[0]) {
11+
var_dump($v[0]);
12+
}
13+
}
14+
15+
function test3(\DOMNodeList $v): void
16+
{
17+
if ($v[0]) {
18+
var_dump($v[0]);
19+
}
20+
}
21+
22+
function test7(\PDORow $v): void
23+
{
24+
if ($v['name']) {
25+
var_dump($v['name']);
26+
}
27+
if ($v[0]) {
28+
var_dump($v[0]);
29+
}
30+
}
31+
32+
function test8(\ResourceBundle $v): void
33+
{
34+
var_dump($v['name']);
35+
var_dump($v[0]);
36+
}
37+

0 commit comments

Comments
 (0)