Skip to content

Commit a7127be

Browse files
LubuSebstaabm
authored andcommitted
Fix symbol discovery after class name tokens
1 parent bb2b515 commit a7127be

6 files changed

Lines changed: 45 additions & 3 deletions

File tree

‎src/Reflection/BetterReflection/SourceLocator/PhpFileCleaner.php‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,10 @@ public function clean(string $contents, int $maxMatches): string
146146
if (isset($this->typeConfig[$char])) {
147147
$type = $this->typeConfig[$char];
148148

149-
if (substr($this->contents, $this->index, $type['length']) === $type['name']) {
149+
if (
150+
substr($this->contents, $this->index, $type['length']) === $type['name']
151+
&& $this->match('~.\\b(?<![\\$:>])' . $type['name'] . '\\b~Ais', offset: $this->index - 1)
152+
) {
150153
if ($maxMatches === 1 && $this->match($type['pattern'], $match, $this->index - 1)) {
151154
return $clean . $match[0];
152155
}

‎tests/PHPStan/Reflection/BetterReflection/SourceLocator/SymbolFinderInFilesTest.php‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,15 @@ final class SymbolFinderInFilesTest extends PHPStanTestCase
1313
*/
1414
public static function dataFindSymbols(): iterable
1515
{
16+
yield 'class name expressions do not hide later global constants' => [
17+
__DIR__ . '/data/symbol-finder/class-name-before-global-constant.php',
18+
[
19+
[],
20+
['symbolfinderclassname', 'classname', 'symbolfinderanonymousclass'],
21+
['AFTER_CLASS_NAME', 'AFTER_CLASS_PREFIX', 'AFTER_ANONYMOUS_CLASS'],
22+
],
23+
];
24+
1625
yield 'namespaced constants do not leak a class entry' => [
1726
__DIR__ . '/data/symbol-finder/namespaced-constants.php',
1827
[
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php declare(strict_types = 1);
2+
3+
function symbolFinderClassName(): string
4+
{
5+
return \stdClass::class;
6+
}
7+
8+
const AFTER_CLASS_NAME = 'found';
9+
10+
function className(): string
11+
{
12+
return 'not a class declaration';
13+
}
14+
15+
const AFTER_CLASS_PREFIX = 'still found';
16+
17+
function symbolFinderAnonymousClass(): object
18+
{
19+
return new class {
20+
public const MEMBER = 'not global';
21+
};
22+
}
23+
24+
const AFTER_ANONYMOUS_CLASS = 'also found';

‎turbo-ext/src/SymbolScan.h‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -448,8 +448,12 @@ inline void PhpFileCleaner::clean(zend_long maxMatches, std::string &out)
448448
for (const TypeConfig &type : types) {
449449
if (type.firstByte != c) continue;
450450

451-
if (index + type.length <= len && memcmp(contents + index, type.name, type.length) == 0) {
452-
if (maxMatches == 1 && prevByteOpensKeyword(index)) {
451+
if (index + type.length <= len
452+
&& memcmp(contents + index, type.name, type.length) == 0
453+
&& prevByteOpensKeyword(index)
454+
&& (index + type.length == len || !isWordByte((unsigned char) contents[index + type.length]))
455+
) {
456+
if (maxMatches == 1) {
453457
size_t end;
454458
if (matchSpacesAndName(index + type.length, &end)) {
455459
clean.append(contents + index - 1, end - (index - 1));

‎turbo-ext/tests/php-file-cleaner-corpus.php‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
'short echo tag' => "<?php\nclass A {}\n?>\n<?= 'class NotAClass' ?>\n<?php class B {}\n",
8989
'anon class' => "<?php\n\$a = new class extends Foo implements Bar {};\nclass Real {}\n",
9090
'property named class' => "<?php\nclass A { public \$class = 1; }\n\$x = Foo::class;\n\$y = \$obj->class;\n",
91+
'class name before global constant' => "<?php\nfunction f() { return Foo::class; }\nconst AFTER_CLASS_NAME = 1;\nfunction className() {}\nconst AFTER_CLASS_PREFIX = 2;\n",
9192
'function by reference' => "<?php\nfunction &refFn() {}\nfunction plain(): void {}\n",
9293
'namespace braces' => "<?php\nnamespace A\\B { class C {} }\nnamespace D { const E = 1; }\n",
9394
'no php tag' => "just text, no php at all\n",

‎turbo-ext/tests/symbol-finder-corpus.php‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
'interface const' => "<?php\ninterface I { const X = 1; }\n",
6565
'enum const' => "<?php\nenum E: string { case A = 'a'; const X = 1; }\n",
6666
'global constants' => "<?php\nconst A = 1;\nconst B = 2;\n",
67+
'class name before global constant' => "<?php\nfunction f() { return Foo::class; }\nconst AFTER_CLASS_NAME = 1;\nfunction className() {}\nconst AFTER_CLASS_PREFIX = 2;\n",
6768
'define' => "<?php\ndefine('FOO', 1);\ndefine(\"N\\\\BAR\", 2);\ndefine('N\\\\\\\\BAZ', 3);\n",
6869
'define leading slash' => "<?php\n\\define('SLASHED', 1);\n",
6970
'define with variable' => "<?php\ndefine(\$name, 1);\n\$x = 'function evil(';\n",

0 commit comments

Comments
 (0)