Skip to content

Commit 70af077

Browse files
committed
Merge remote-tracking branch 'upstream/main'
2 parents 41b24b0 + 3ccba97 commit 70af077

17 files changed

+169
-37
lines changed

SECURITY.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<!-- BEGIN MICROSOFT SECURITY.MD V0.0.8 BLOCK -->
2+
3+
## Security
4+
5+
Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
6+
7+
If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below.
8+
9+
## Reporting Security Issues
10+
11+
**Please do not report security vulnerabilities through public GitHub issues.**
12+
13+
Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report).
14+
15+
If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey).
16+
17+
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc).
18+
19+
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
20+
21+
* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
22+
* Full paths of source file(s) related to the manifestation of the issue
23+
* The location of the affected source code (tag/branch/commit or direct URL)
24+
* Any special configuration required to reproduce the issue
25+
* Step-by-step instructions to reproduce the issue
26+
* Proof-of-concept or exploit code (if possible)
27+
* Impact of the issue, including how an attacker might exploit the issue
28+
29+
This information will help us triage your report more quickly.
30+
31+
If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs.
32+
33+
## Preferred Languages
34+
35+
We prefer all communications to be in English.
36+
37+
## Policy
38+
39+
Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd).
40+
41+
<!-- END MICROSOFT SECURITY.MD BLOCK -->

composer.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,8 @@
1818
],
1919
"autoload": {
2020
"psr-4": { "Microsoft\\PhpParser\\": ["src/"] }
21+
},
22+
"autoload-dev": {
23+
"psr-4": { "Microsoft\\PhpParser\\Tests\\Unit\\": ["tests/unit/"] }
2124
}
2225
}

docs/HowItWorks.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ Note that for this error case, it is more of an art than a science. That is to s
306306
would add special casing for anticipated scenarios, rather than construct some general-purpose rule.
307307
308308
#### Other notes
309-
* Just as it's imporant to understand the assumptions that *will* hold true,
309+
* Just as it's important to understand the assumptions that *will* hold true,
310310
it is also important to understand the assumptions that will not hold true.
311311
One such **non-invariant** is that not every token generated by the lexer ends up in the tree.
312312
@@ -387,6 +387,6 @@ existing, more battle-tested, work to validate our own correctness.
387387
degree of variance, we should set up some infrastructure to help us ensure that performance work results in a statistically
388388
significant boost and works on a wide variety of machine configurations.
389389
* Fuzz testing - test the parser against automatically generated inputs to exercise edge cases in a bulk fashion, and
390-
and ensure expected properties of the tree.
390+
ensure expected properties of the tree.
391391
* Community feedback - try and get the parser in the hands of as many people as possible so we can validate a
392392
wide range of use cases. The Syntax Visualizer tool is one tool to help us increase reach.

docs/Overview.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,5 +75,5 @@ all incorrect constructs (e.g. including a non-constant expression as the defaul
7575
a method parameter). Instead, it attaches these errors on a post-parse walk of the tree.
7676

7777
## Next Steps
78-
Check out the [Readme](../README.md) and [Getting Started](GettingStarted.md) pages for more information on how consume
79-
the parser, or the [How It Works](HowItWorks.md) section if you want to dive deeper into the implementation.
78+
Check out the [Readme](../README.md) and [Getting Started](GettingStarted.md) pages for more information on how to
79+
consume the parser, or the [How It Works](HowItWorks.md) section if you want to dive deeper into the implementation.

phpstan.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
parameters:
2-
level: 3
2+
level: 4
33
paths:
44
- src/
55
ignoreErrors:

src/DiagnosticsProvider.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ public static function checkDiagnostics($node) {
5151
if ($node instanceof Node) {
5252
return $node->getDiagnosticForNode();
5353
}
54+
55+
/** @phpstan-ignore-next-line because it says "unreachable" statement */
5456
return null;
5557
}
5658

src/Node.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -437,10 +437,7 @@ public function getLastChild() {
437437
public function getDescendantNodeAtPosition(int $pos) {
438438
foreach ($this->getChildNodes() as $child) {
439439
if ($child->containsPosition($pos)) {
440-
$node = $child->getDescendantNodeAtPosition($pos);
441-
if (!is_null($node)) {
442-
return $node;
443-
}
440+
return $child->getDescendantNodeAtPosition($pos);
444441
}
445442
}
446443

@@ -590,6 +587,7 @@ public function getNamespaceDefinition() {
590587
? $this
591588
: $this->getFirstAncestor(NamespaceDefinition::class, SourceFileNode::class);
592589

590+
/** @phpstan-ignore-next-line result is always false -- not sure this can happen */
593591
if ($namespaceDefinition instanceof NamespaceDefinition && !($namespaceDefinition->parent instanceof SourceFileNode)) {
594592
$namespaceDefinition = $namespaceDefinition->getFirstAncestor(SourceFileNode::class);
595593
}

src/Node/Expression/ArgumentExpression.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
namespace Microsoft\PhpParser\Node\Expression;
88

9+
use Microsoft\PhpParser\MissingToken;
910
use Microsoft\PhpParser\Node\Expression;
1011
use Microsoft\PhpParser\Token;
1112

@@ -19,7 +20,7 @@ class ArgumentExpression extends Expression {
1920
/** @var Token|null */
2021
public $dotDotDotToken;
2122

22-
/** @var Expression|null null for first-class callable syntax */
23+
/** @var Expression|MissingToken|null for first-class callable syntax */
2324
public $expression;
2425

2526
const CHILD_NAMES = [

src/Node/InterfaceBaseClause.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
use Microsoft\PhpParser\Token;
1111

1212
class InterfaceBaseClause extends Node {
13-
/** @var Token */
13+
/** @var Token|null */
1414
public $extendsKeyword;
1515

1616
/** @var DelimitedList\QualifiedNameList */

src/Node/NamespaceUseGroupClause.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
class NamespaceUseGroupClause extends Node {
1313

14-
/** @var Token */
14+
/** @var Token|null */
1515
public $functionOrConst;
1616
/** @var QualifiedName */
1717
public $namespaceName;

src/Node/QualifiedName.php

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@
2020
class QualifiedName extends Node implements NamespacedNameInterface {
2121
use NamespacedNameTrait;
2222

23-
/** @var Token */
23+
/** @var Token|null */
2424
public $globalSpecifier; // \_opt
25-
/** @var RelativeSpecifier */
25+
/** @var RelativeSpecifier|null */
2626
public $relativeSpecifier; // namespace\
2727
/** @var array */
2828
public $nameParts;
@@ -85,10 +85,7 @@ public function getResolvedName($namespaceDefinition = null) {
8585
$this->parent instanceof Node\Statement\NamespaceUseDeclaration ||
8686
$this->parent instanceof Node\NamespaceUseClause ||
8787
$this->parent instanceof Node\NamespaceUseGroupClause ||
88-
$this->parent->parent instanceof Node\TraitUseClause ||
89-
$this->parent instanceof Node\TraitSelectOrAliasClause ||
90-
($this->parent instanceof TraitSelectOrAliasClause &&
91-
($this->parent->asOrInsteadOfKeyword == null || $this->parent->asOrInsteadOfKeyword->kind === TokenKind::AsKeyword))
88+
$this->parent instanceof Node\TraitSelectOrAliasClause
9289
) {
9390
return null;
9491
}
@@ -157,10 +154,8 @@ public function getLastNamePart() {
157154

158155
/**
159156
* @param ResolvedName[] $importTable
160-
* @param bool $isCaseSensitive
161-
* @return string|null
162157
*/
163-
private function tryResolveFromImportTable($importTable, bool $isCaseSensitive = false) {
158+
private function tryResolveFromImportTable($importTable, bool $isCaseSensitive = false): ?ResolvedName {
164159
$content = $this->getFileContents();
165160
$index = $this->nameParts[0]->getText($content);
166161
// if (!$isCaseSensitive) {

src/Node/RelativeSpecifier.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class RelativeSpecifier extends Node {
1313
/** @var Token */
1414
public $namespaceKeyword;
1515

16-
/** @var Token */
16+
/** @var Token|null */
1717
public $backslash;
1818

1919
const CHILD_NAMES = [

src/Node/Statement/NamespaceUseDeclaration.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class NamespaceUseDeclaration extends StatementNode {
1818
public $useKeyword;
1919
/** @var Token */
2020
public $functionOrConst;
21-
/** @var DelimitedList\NamespaceUseClauseList */
21+
/** @var DelimitedList\NamespaceUseClauseList|null */
2222
public $useClauses;
2323
/** @var Token */
2424
public $semicolon;

src/Node/StringLiteral.php

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
use Microsoft\PhpParser\Token;
1111

1212
class StringLiteral extends Expression {
13-
/** @var Token */
13+
/** @var Token|null */
1414
public $startQuote;
1515

1616
/** @var Token[]|Node[]|Token */
@@ -25,17 +25,29 @@ class StringLiteral extends Expression {
2525
'endQuote',
2626
];
2727

28-
public function getStringContentsText() {
29-
// TODO add tests
28+
public function getStringContentsText(): string {
3029
$stringContents = "";
3130
if (isset($this->startQuote)) {
3231
foreach ($this->children as $child) {
3332
$contents = $this->getFileContents();
3433
$stringContents .= $child->getFullText($contents);
3534
}
3635
} else {
37-
// TODO ensure string consistency (all strings should have start / end quote)
38-
$stringContents = trim($this->children->getText($this->getFileContents()), '"\'');
36+
$children = $this->children;
37+
if ($children instanceof Token) {
38+
$value = (string)$children->getText($this->getFileContents());
39+
$startQuote = substr($value, 0, 1);
40+
41+
if ($startQuote === '\'') {
42+
return rtrim(substr($value, 1), '\'');
43+
}
44+
45+
if ($startQuote === '"') {
46+
return rtrim(substr($value, 1), '"');
47+
}
48+
}
49+
50+
return '';
3951
}
4052
return $stringContents;
4153
}

src/Parser.php

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1407,7 +1407,7 @@ private function parseStringLiteralExpression($parentNode) {
14071407
return $expression;
14081408
}
14091409

1410-
private function parseStringLiteralExpression2($parentNode) {
1410+
private function parseStringLiteralExpression2($parentNode): StringLiteral {
14111411
// TODO validate input token
14121412
$expression = new StringLiteral();
14131413
$expression->parent = $parentNode;
@@ -1419,6 +1419,11 @@ private function parseStringLiteralExpression2($parentNode) {
14191419
case TokenKind::DollarOpenBraceToken:
14201420
case TokenKind::OpenBraceDollarToken:
14211421
$expression->children[] = $this->eat(TokenKind::DollarOpenBraceToken, TokenKind::OpenBraceDollarToken);
1422+
/**
1423+
* @phpstan-ignore-next-line "Strict comparison using
1424+
* === between 403|404 and 408 will always evaluate to
1425+
* false" is wrong because those tokens were eaten above
1426+
*/
14221427
if ($this->getCurrentToken()->kind === TokenKind::StringVarname) {
14231428
$expression->children[] = $this->parseComplexDollarTemplateStringExpression($expression);
14241429
} else {
@@ -1657,7 +1662,7 @@ private function parseDelimitedList($className, $delimiter, $isElementStartFn, $
16571662
do {
16581663
if ($isElementStartFn($token)) {
16591664
$node->addElement($parseElementFn($node));
1660-
} elseif (!$allowEmptyElements || ($allowEmptyElements && !$this->checkAnyToken($delimiter))) {
1665+
} elseif (!$allowEmptyElements || !$this->checkAnyToken($delimiter)) {
16611666
break;
16621667
}
16631668

@@ -1767,7 +1772,7 @@ function ($parentNode) {
17671772
};
17681773
}
17691774

1770-
private function parseRelativeSpecifier($parentNode) {
1775+
private function parseRelativeSpecifier($parentNode): ?RelativeSpecifier {
17711776
$node = new RelativeSpecifier();
17721777
$node->parent = $parentNode;
17731778
$node->namespaceKeyword = $this->eatOptional1(TokenKind::NamespaceKeyword);
@@ -2116,7 +2121,7 @@ private function parseUnaryExpressionOrHigher($parentNode) {
21162121
/**
21172122
* @param int $precedence
21182123
* @param Node $parentNode
2119-
* @return Expression
2124+
* @return Expression|MissingToken
21202125
*/
21212126
private function parseBinaryExpressionOrHigher($precedence, $parentNode) {
21222127
$leftOperand = $this->parseUnaryExpressionOrHigher($parentNode);
@@ -2217,6 +2222,7 @@ private function parseBinaryExpressionOrHigher($precedence, $parentNode) {
22172222
}
22182223
break;
22192224
case TokenKind::QuestionToken:
2225+
/** @phpstan-ignore-next-line This seems impossible, questionToken is always set AFAICS but ignoring to be safe */
22202226
if ($parentNode instanceof TernaryExpression && !isset($parentNode->questionToken)) {
22212227
// Workaround to parse "a ? b : c ? d : e" as "(a ? b : c) ? d : e"
22222228
break 2;
@@ -3590,7 +3596,7 @@ private function parseNamespaceUseDeclaration($parentNode) {
35903596
return $namespaceUseDeclaration;
35913597
}
35923598

3593-
private function parseNamespaceUseClauseList($parentNode) {
3599+
private function parseNamespaceUseClauseList($parentNode): ?DelimitedList\NamespaceUseClauseList {
35943600
return $this->parseDelimitedList(
35953601
DelimitedList\NamespaceUseClauseList::class,
35963602
TokenKind::CommaToken,
@@ -3620,7 +3626,7 @@ function ($parentNode) {
36203626
);
36213627
}
36223628

3623-
private function parseNamespaceUseGroupClauseList($parentNode) {
3629+
private function parseNamespaceUseGroupClauseList($parentNode): ?DelimitedList\NamespaceUseGroupClauseList {
36243630
return $this->parseDelimitedList(
36253631
DelimitedList\NamespaceUseGroupClauseList::class,
36263632
TokenKind::CommaToken,
@@ -3750,11 +3756,27 @@ private function parseEnumDeclaration($parentNode) {
37503756
}
37513757
$enumDeclaration->enumInterfaceClause = $this->parseEnumInterfaceClause($enumDeclaration);
37523758

3759+
$enumDeclaration->enumInterfaceClause = $this->parseEnumInterfaceClause($enumDeclaration);
37533760
$enumDeclaration->enumMembers = $this->parseEnumMembers($enumDeclaration);
37543761

37553762
return $enumDeclaration;
37563763
}
37573764

3765+
private function parseEnumInterfaceClause(EnumDeclaration $enumDeclaration): ?EnumInterfaceClause {
3766+
$enumInterfaceClause = new EnumInterfaceClause();
3767+
$enumInterfaceClause->parent = $enumDeclaration;
3768+
$enumInterfaceClause->implementsKeyword = $this->eatOptional1(TokenKind::ImplementsKeyword);
3769+
3770+
if ($enumInterfaceClause->implementsKeyword === null) {
3771+
return null;
3772+
}
3773+
3774+
$enumInterfaceClause->interfaceNameList =
3775+
$this->parseQualifiedNameList($enumInterfaceClause);
3776+
return $enumInterfaceClause;
3777+
}
3778+
3779+
37583780
private function parseEnumMembers($parentNode) {
37593781
$enumMembers = new EnumMembers();
37603782
$enumMembers->parent = $parentNode;

tests/api/getResolvedName.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@ class GetResolvedNameTest extends TestCase {
2525
'use _A as C' => null,
2626
'use A\B\{C, D_}' => null,
2727
'use A\B\{C, D_}' => null,
28-
'class Foo { use _BarTrait; }' => null,
29-
'class Foo { use BarTrait, Baz_Trait; }' => null,
3028
'class Foo { use A, B { A::Foo as _foo }' => null,
3129

3230
// Relative scope resolution qualifiers
@@ -39,7 +37,8 @@ class GetResolvedNameTest extends TestCase {
3937
'\Fo_o' => 'Foo',
4038
'use A\Foo;\_Foo\Bar' => 'Foo\Bar',
4139
'$f = new \_Foo\Bar()' => 'Foo\Bar',
42-
'class Foo { use A, B { \Foo\_Bar::A as _foo }' => 'Foo\Bar',
40+
'class Foo { use A, B { \Foo\_Bar::A as foo }' => 'Foo\Bar',
41+
'class Foo { use \A\BTr_ait; }' => 'A\BTrait',
4342

4443
// Relative name
4544
'namespace Foo; namespace\Bar_' => 'Foo\Bar',
@@ -65,6 +64,9 @@ class GetResolvedNameTest extends TestCase {
6564
'use A\B; new _B()' => 'A\B', // Class imported
6665
'use A\B; _B()' => 'B', // Not imported as function
6766
'use A\B; function xyz(): _B { };' => 'A\B',
67+
'class Foo { use _BarTrait; }' => 'BarTrait',
68+
'class Foo { use BarTrait, Baz_Trait; }' => 'BazTrait',
69+
'use A\BarTrait; class Foo { use _BarTrait; }' => 'A\BarTrait',
6870
);
6971

7072
public function dataProvider() {

0 commit comments

Comments
 (0)