Skip to content

Commit 2e6da2e

Browse files
authored
Merge pull request #389 from TysonAndre/halt-compiler-fix
Fix handling of HaltCompilerStatement
2 parents e966047 + 8d3d0bb commit 2e6da2e

17 files changed

+259
-85
lines changed

src/Node/Statement/HaltCompilerStatement.php

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,26 @@ class HaltCompilerStatement extends Expression {
2020
/** @var Token */
2121
public $closeParen;
2222

23-
/** @var Token */
24-
public $semicolon;
23+
/** @var Token (there is an implicit ')' before php close tags (`?>`)) */
24+
public $semicolonOrCloseTag;
2525

26-
/** @var Token */
26+
/** @var Token|null TokenKind::InlineHtml data unless there are no bytes (This is optional if there is nothing after the semicolon) */
2727
public $data;
2828

2929
const CHILD_NAMES = [
3030
'haltCompilerKeyword',
3131
'openParen',
3232
'closeParen',
33-
'semicolon',
33+
'semicolonOrCloseTag',
3434
'data',
3535
];
36+
37+
/**
38+
* @return int
39+
*/
40+
public function getHaltCompilerOffset() {
41+
// This accounts for the fact that PHP close tags may include a single newline,
42+
// and that $this->data may be null.
43+
return $this->semicolonOrCloseTag->getEndPosition();
44+
}
3645
}

src/Parser.php

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2858,15 +2858,27 @@ private function parseUnsetStatement($parentNode) {
28582858
}
28592859

28602860
private function parseHaltCompilerStatement($parentNode) {
2861-
$unsetStatement = new HaltCompilerStatement();
2862-
$unsetStatement->parent = $parentNode;
2863-
2864-
$unsetStatement->haltCompilerKeyword = $this->eat1(TokenKind::HaltCompilerKeyword);
2865-
$unsetStatement->openParen = $this->eat1(TokenKind::OpenParenToken);
2866-
$unsetStatement->closeParen = $this->eat1(TokenKind::CloseParenToken);
2867-
$unsetStatement->semicolon = $this->eatSemicolonOrAbortStatement();
2868-
$unsetStatement->data = $this->eatOptional1(TokenKind::InlineHtml);
2869-
return $unsetStatement;
2861+
$haltCompilerStatement = new HaltCompilerStatement();
2862+
$haltCompilerStatement->parent = $parentNode;
2863+
2864+
$haltCompilerStatement->haltCompilerKeyword = $this->eat1(TokenKind::HaltCompilerKeyword);
2865+
$haltCompilerStatement->openParen = $this->eat1(TokenKind::OpenParenToken);
2866+
$haltCompilerStatement->closeParen = $this->eat1(TokenKind::CloseParenToken);
2867+
// There is an implicit ';' before the closing php tag.
2868+
$haltCompilerStatement->semicolonOrCloseTag = $this->eat(TokenKind::SemicolonToken, TokenKind::ScriptSectionEndTag);
2869+
// token_get_all() will return up to 3 tokens after __halt_compiler regardless of whether they're the right ones.
2870+
// For invalid php snippets, combine the remaining tokens into InlineHtml
2871+
$remainingTokens = [];
2872+
while ($this->token->kind !== TokenKind::EndOfFileToken) {
2873+
$remainingTokens[] = $this->token;
2874+
$this->advanceToken();
2875+
}
2876+
if ($remainingTokens) {
2877+
$firstToken = $remainingTokens[0];
2878+
$lastToken = end($remainingTokens);
2879+
$haltCompilerStatement->data = new Token(TokenKind::InlineHtml, $firstToken->fullStart, $firstToken->fullStart, $lastToken->fullStart + $lastToken->length - $firstToken->fullStart);
2880+
}
2881+
return $haltCompilerStatement;
28702882
}
28712883

28722884
private function parseArrayCreationExpression($parentNode) {

src/Token.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class Token implements \JsonSerializable {
1818
public $fullStart;
1919
/** @var int */
2020
public $start;
21-
/** @var int */
21+
/** @var int the length is equal to $this->getEndPosition() - $this->fullStart. */
2222
public $length;
2323

2424
/**

tests/cases/parser/haltCompiler1.php.tree

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"kind": "CloseParenToken",
2626
"textLength": 1
2727
},
28-
"semicolon": {
28+
"semicolonOrCloseTag": {
2929
"kind": "SemicolonToken",
3030
"textLength": 1
3131
},

tests/cases/parser/haltCompiler4.php.diag

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,29 +16,5 @@
1616
"message": "';' expected.",
1717
"start": 32,
1818
"length": 0
19-
},
20-
{
21-
"kind": 0,
22-
"message": "Unexpected '::'",
23-
"start": 32,
24-
"length": 2
25-
},
26-
{
27-
"kind": 0,
28-
"message": "')' expected.",
29-
"start": 38,
30-
"length": 0
31-
},
32-
{
33-
"kind": 0,
34-
"message": "';' expected.",
35-
"start": 38,
36-
"length": 0
37-
},
38-
{
39-
"kind": 0,
40-
"message": "Unexpected 'InlineHtml'",
41-
"start": 38,
42-
"length": 3
4319
}
4420
]

tests/cases/parser/haltCompiler4.php.tree

Lines changed: 4 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -27,58 +27,16 @@
2727
"kind": "CloseParenToken",
2828
"textLength": 0
2929
},
30-
"semicolon": {
30+
"semicolonOrCloseTag": {
3131
"error": "MissingToken",
3232
"kind": "SemicolonToken",
3333
"textLength": 0
3434
},
35-
"data": null
36-
}
37-
},
38-
{
39-
"error": "SkippedToken",
40-
"kind": "ColonColonToken",
41-
"textLength": 2
42-
},
43-
{
44-
"ExpressionStatement": {
45-
"expression": {
46-
"CallExpression": {
47-
"callableExpression": {
48-
"QualifiedName": {
49-
"globalSpecifier": null,
50-
"relativeSpecifier": null,
51-
"nameParts": [
52-
{
53-
"kind": "Name",
54-
"textLength": 3
55-
}
56-
]
57-
}
58-
},
59-
"openParen": {
60-
"kind": "OpenParenToken",
61-
"textLength": 1
62-
},
63-
"argumentExpressionList": null,
64-
"closeParen": {
65-
"error": "MissingToken",
66-
"kind": "CloseParenToken",
67-
"textLength": 0
68-
}
69-
}
70-
},
71-
"semicolon": {
72-
"error": "MissingToken",
73-
"kind": "SemicolonToken",
74-
"textLength": 0
35+
"data": {
36+
"kind": "InlineHtml",
37+
"textLength": 9
7538
}
7639
}
77-
},
78-
{
79-
"error": "SkippedToken",
80-
"kind": "InlineHtml",
81-
"textLength": 3
8240
}
8341
],
8442
"endOfFileToken": {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[]
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"SourceFileNode": {
3+
"statementList": [
4+
{
5+
"InlineHtml": {
6+
"scriptSectionEndTag": null,
7+
"text": null,
8+
"scriptSectionStartTag": {
9+
"kind": "ScriptSectionStartTag",
10+
"textLength": 6
11+
}
12+
}
13+
},
14+
{
15+
"HaltCompilerStatement": {
16+
"haltCompilerKeyword": {
17+
"kind": "HaltCompilerKeyword",
18+
"textLength": 15
19+
},
20+
"openParen": {
21+
"kind": "OpenParenToken",
22+
"textLength": 1
23+
},
24+
"closeParen": {
25+
"kind": "CloseParenToken",
26+
"textLength": 1
27+
},
28+
"semicolon": null,
29+
"data": null
30+
}
31+
},
32+
{
33+
"InlineHtml": {
34+
"scriptSectionEndTag": {
35+
"kind": "ScriptSectionEndTag",
36+
"textLength": 3
37+
},
38+
"text": null,
39+
"scriptSectionStartTag": null
40+
}
41+
}
42+
],
43+
"endOfFileToken": {
44+
"kind": "EndOfFileToken",
45+
"textLength": 0
46+
}
47+
}
48+
}

tests/cases/parser/haltCompiler6.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<?php /* has implicit ';' and no trailing newline byte */ __halt_compiler() ?>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[]
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"SourceFileNode": {
3+
"statementList": [
4+
{
5+
"InlineHtml": {
6+
"scriptSectionEndTag": null,
7+
"text": null,
8+
"scriptSectionStartTag": {
9+
"kind": "ScriptSectionStartTag",
10+
"textLength": 6
11+
}
12+
}
13+
},
14+
{
15+
"HaltCompilerStatement": {
16+
"haltCompilerKeyword": {
17+
"kind": "HaltCompilerKeyword",
18+
"textLength": 15
19+
},
20+
"openParen": {
21+
"kind": "OpenParenToken",
22+
"textLength": 1
23+
},
24+
"closeParen": {
25+
"kind": "CloseParenToken",
26+
"textLength": 1
27+
},
28+
"semicolonOrCloseTag": {
29+
"kind": "ScriptSectionEndTag",
30+
"textLength": 2
31+
},
32+
"data": null
33+
}
34+
}
35+
],
36+
"endOfFileToken": {
37+
"kind": "EndOfFileToken",
38+
"textLength": 0
39+
}
40+
}
41+
}

tests/cases/parser/haltCompiler7.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
// A MissingToken should be generated for the missing `;` or close php tag.
3+
// NOTE: token_get_all() will yield up to 3 tokens after T_HALT_COMPILER,
4+
// no matter what those tokens happen to be, so tolerant-php-parser combines unexpected tokens into T_INLINE_HTML
5+
// so that no subsequent statements get emitted.
6+
// (T_HALT_COMPILER is forbidden in other node types)
7+
// In this invalid AST, treat " + 1;\n" as the inline data after the missing semicolon.
8+
__halt_compiler() + 1;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[
2+
{
3+
"kind": 0,
4+
"message": "';' expected.",
5+
"start": 482,
6+
"length": 0
7+
}
8+
]
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
{
2+
"SourceFileNode": {
3+
"statementList": [
4+
{
5+
"InlineHtml": {
6+
"scriptSectionEndTag": null,
7+
"text": null,
8+
"scriptSectionStartTag": {
9+
"kind": "ScriptSectionStartTag",
10+
"textLength": 6
11+
}
12+
}
13+
},
14+
{
15+
"HaltCompilerStatement": {
16+
"haltCompilerKeyword": {
17+
"kind": "HaltCompilerKeyword",
18+
"textLength": 15
19+
},
20+
"openParen": {
21+
"kind": "OpenParenToken",
22+
"textLength": 1
23+
},
24+
"closeParen": {
25+
"kind": "CloseParenToken",
26+
"textLength": 1
27+
},
28+
"semicolonOrCloseTag": {
29+
"error": "MissingToken",
30+
"kind": "SemicolonToken",
31+
"textLength": 0
32+
},
33+
"data": {
34+
"kind": "InlineHtml",
35+
"textLength": 10
36+
}
37+
}
38+
}
39+
],
40+
"endOfFileToken": {
41+
"kind": "EndOfFileToken",
42+
"textLength": 0
43+
}
44+
}
45+
}

tests/cases/parser/haltCompiler8.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<?php
2+
__halt_compiler
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[
2+
{
3+
"kind": 0,
4+
"message": "'(' expected.",
5+
"start": 21,
6+
"length": 0
7+
},
8+
{
9+
"kind": 0,
10+
"message": "')' expected.",
11+
"start": 21,
12+
"length": 0
13+
},
14+
{
15+
"kind": 0,
16+
"message": "';' expected.",
17+
"start": 21,
18+
"length": 0
19+
}
20+
]

0 commit comments

Comments
 (0)