Skip to content

Commit b43bdcb

Browse files
LubuSebstaabm
andauthored
Fix global constant discovery after class name tokens (#6756)
Co-authored-by: LubuSeb <187313664+LubuSeb@users.noreply.github.com> Co-authored-by: Markus Staab <maggus.staab@googlemail.com>
1 parent 49861be commit b43bdcb

11 files changed

Lines changed: 87 additions & 5 deletions

File tree

‎.github/workflows/e2e-tests.yml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,9 @@ jobs:
264264
../../bin/phpstan analyse src/usage.php
265265
../../bin/phpstan clear-result-cache
266266
../../bin/phpstan analyse
267+
- script: |
268+
cd e2e/bug-15482b
269+
../../bin/phpstan analyse
267270
- script: |
268271
cd e2e/bug-15102
269272
composer install

‎Makefile‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ lint:
4040
--exclude tests/PHPStan/Rules/Operators/data/invalid-assign-var.php \
4141
--exclude tests/PHPStan/Rules/Functions/data/arrow-function-nullsafe-by-ref.php \
4242
--exclude tests/PHPStan/Levels/data/namedArguments.php \
43+
--exclude tests/PHPStan/Reflection/BetterReflection/SourceLocator/data/symbol-finder/class-name-before-global-constant.php \
4344
--exclude tests/PHPStan/Rules/Keywords/data/continue-break.php \
4445
--exclude tests/PHPStan/Rules/Keywords/data/continue-break-property-hook.php \
4546
--exclude tests/PHPStan/Rules/Keywords/data/goto-undefined-label.php \

‎e2e/bug-15482b/phpstan.neon‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
parameters:
2+
level: max
3+
paths:
4+
- src

‎e2e/bug-15482b/src/a_usage.php‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<?php
2+
3+
echo BAR;

‎e2e/bug-15482b/src/z_functions.php‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
function foo(): string
4+
{
5+
return stdClass::class;
6+
}
7+
8+
const BAR = 'bar';

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,8 @@ public function clean(string $contents, int $maxMatches): string
9797
if ($inType) {
9898
$typeLevel--;
9999

100-
if ($typeLevel === 0) {
100+
if ($typeLevel <= 0) {
101+
$typeLevel = 0;
101102
$inType = false;
102103
}
103104
}
@@ -146,7 +147,10 @@ public function clean(string $contents, int $maxMatches): string
146147
if (isset($this->typeConfig[$char])) {
147148
$type = $this->typeConfig[$char];
148149

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

‎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', 'symbolfindernamedargument', 'symbolfindernamedargumentcall', 'symbolfinderspacedclassconstant'],
21+
['AFTER_CLASS_NAME', 'AFTER_CLASS_PREFIX', 'AFTER_ANONYMOUS_CLASS', 'AFTER_NAMED_CLASS_ARGUMENT', 'AFTER_SPACED_CLASS_CONSTANT'],
22+
],
23+
];
24+
1625
yield 'namespaced constants do not leak a class entry' => [
1726
__DIR__ . '/data/symbol-finder/namespaced-constants.php',
1827
[
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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';
25+
26+
function symbolFinderNamedArgument(string $class): string
27+
{
28+
return $class;
29+
}
30+
31+
function symbolFinderNamedArgumentCall(): string
32+
{
33+
return symbolFinderNamedArgument(class: 'named argument');
34+
}
35+
36+
const AFTER_NAMED_CLASS_ARGUMENT = 'found after named argument';
37+
38+
function symbolFinderSpacedClassConstant(): string
39+
{
40+
return \stdClass:: class;
41+
}
42+
43+
const AFTER_SPACED_CLASS_CONSTANT = 'found after spaced class constant';

‎turbo-ext/src/SymbolScan.h‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,8 @@ inline void PhpFileCleaner::clean(zend_long maxMatches, std::string &out)
378378
if (inType) {
379379
typeLevel--;
380380

381-
if (typeLevel == 0) {
381+
if (typeLevel <= 0) {
382+
typeLevel = 0;
382383
inType = false;
383384
}
384385
}
@@ -448,8 +449,12 @@ inline void PhpFileCleaner::clean(zend_long maxMatches, std::string &out)
448449
for (const TypeConfig &type : types) {
449450
if (type.firstByte != c) continue;
450451

451-
if (index + type.length <= len && memcmp(contents + index, type.name, type.length) == 0) {
452-
if (maxMatches == 1 && prevByteOpensKeyword(index)) {
452+
if (index + type.length <= len
453+
&& memcmp(contents + index, type.name, type.length) == 0
454+
&& prevByteOpensKeyword(index)
455+
&& (index + type.length == len || !isWordByte((unsigned char) contents[index + type.length]))
456+
) {
457+
if (maxMatches == 1) {
453458
size_t end;
454459
if (matchSpacesAndName(index + type.length, &end)) {
455460
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",

0 commit comments

Comments
 (0)