Skip to content

Commit 9e352b2

Browse files
committed
Support __halt_compiler statement
https://www.php.net/manual/en/function.halt-compiler.php can be used to embed data in php scripts from the outermost scope. In inner scope, it is parsed in php-src only for the sake of error messages about it needing to be in the outermost scope, so treat it as an unexpected token in other contexts. Closes #381
1 parent a60544e commit 9e352b2

16 files changed

+435
-3
lines changed

.travis.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ matrix:
1919
- php: 7.2
2020
env: STATIC_ANALYSIS=true
2121
fast_finish: true
22-
allow_failures:
23-
- env: VALIDATION=true
2422

2523
cache:
2624
directories:

src/Parser.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@
9797
ForStatement,
9898
FunctionDeclaration,
9999
GotoStatement,
100+
HaltCompilerStatement,
100101
IfStatementNode,
101102
InlineHtml,
102103
InterfaceDeclaration,
@@ -615,6 +616,15 @@ private function parseStatementFn() {
615616

616617
case TokenKind::UnsetKeyword:
617618
return $this->parseUnsetStatement($parentNode);
619+
620+
case TokenKind::HaltCompilerKeyword:
621+
if ($parentNode instanceof SourceFileNode) {
622+
return $this->parseHaltCompilerStatement($parentNode);
623+
}
624+
// __halt_compiler is a fatal compile error anywhere other than the top level.
625+
// It won't be seen elsewhere in other programs - warn about the token being unexpected.
626+
$this->advanceToken();
627+
return new SkippedToken($token);
618628
}
619629

620630
$expressionStatement = new ExpressionStatement();
@@ -1141,6 +1151,9 @@ private function isStatementStart(Token $token) {
11411151

11421152
// attributes
11431153
case TokenKind::AttributeToken:
1154+
1155+
// __halt_compiler
1156+
case TokenKind::HaltCompilerKeyword:
11441157
return true;
11451158

11461159
default:
@@ -2844,6 +2857,18 @@ private function parseUnsetStatement($parentNode) {
28442857
return $unsetStatement;
28452858
}
28462859

2860+
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;
2870+
}
2871+
28472872
private function parseArrayCreationExpression($parentNode) {
28482873
$arrayExpression = new ArrayCreationExpression();
28492874
$arrayExpression->parent = $parentNode;

src/PhpTokenizer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,6 @@ protected static function tokenGetAll(string $content, $parseContext): array
228228
T_DIR => TokenKind::Name,
229229
T_FILE => TokenKind::Name,
230230
T_FUNC_C => TokenKind::Name,
231-
T_HALT_COMPILER => TokenKind::Name,
232231
T_METHOD_C => TokenKind::Name,
233232
T_NS_C => TokenKind::Name,
234233
T_TRAIT_C => TokenKind::Name,
@@ -274,6 +273,7 @@ protected static function tokenGetAll(string $content, $parseContext): array
274273
T_FUNCTION => TokenKind::FunctionKeyword,
275274
T_GLOBAL => TokenKind::GlobalKeyword,
276275
T_GOTO => TokenKind::GotoKeyword,
276+
T_HALT_COMPILER => TokenKind::HaltCompilerKeyword,
277277
T_IF => TokenKind::IfKeyword,
278278
T_IMPLEMENTS => TokenKind::ImplementsKeyword,
279279
T_INCLUDE => TokenKind::IncludeKeyword,

src/TokenKind.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ class TokenKind {
9191
const IterableKeyword = self::IterableReservedWord;
9292
const EnumKeyword = 171;
9393
const ReadonlyKeyword = 172;
94+
const HaltCompilerKeyword = 173;
9495

9596
const OpenBracketToken = 201;
9697
const CloseBracketToken = 202;

tests/cases/parser/haltCompiler1.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: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[]
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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": {
29+
"kind": "SemicolonToken",
30+
"textLength": 1
31+
},
32+
"data": {
33+
"kind": "InlineHtml",
34+
"textLength": 1
35+
}
36+
}
37+
}
38+
],
39+
"endOfFileToken": {
40+
"kind": "EndOfFileToken",
41+
"textLength": 0
42+
}
43+
}
44+
}

tests/cases/parser/haltCompiler2.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?php
2+
if (true) {
3+
__halt_compiler();
4+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[
2+
{
3+
"kind": 0,
4+
"message": "Unexpected 'HaltCompilerKeyword'",
5+
"start": 22,
6+
"length": 15
7+
},
8+
{
9+
"kind": 0,
10+
"message": "'Expression' expected.",
11+
"start": 38,
12+
"length": 0
13+
},
14+
{
15+
"kind": 0,
16+
"message": "Unexpected 'InlineHtml'",
17+
"start": 40,
18+
"length": 3
19+
},
20+
{
21+
"kind": 0,
22+
"message": "'}' expected.",
23+
"start": 43,
24+
"length": 0
25+
}
26+
]
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
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+
"IfStatementNode": {
16+
"ifKeyword": {
17+
"kind": "IfKeyword",
18+
"textLength": 2
19+
},
20+
"openParen": {
21+
"kind": "OpenParenToken",
22+
"textLength": 1
23+
},
24+
"expression": {
25+
"ReservedWord": {
26+
"children": {
27+
"kind": "TrueReservedWord",
28+
"textLength": 4
29+
}
30+
}
31+
},
32+
"closeParen": {
33+
"kind": "CloseParenToken",
34+
"textLength": 1
35+
},
36+
"colon": null,
37+
"statements": {
38+
"CompoundStatementNode": {
39+
"openBrace": {
40+
"kind": "OpenBraceToken",
41+
"textLength": 1
42+
},
43+
"statements": [
44+
{
45+
"error": "SkippedToken",
46+
"kind": "HaltCompilerKeyword",
47+
"textLength": 15
48+
},
49+
{
50+
"ExpressionStatement": {
51+
"expression": {
52+
"ParenthesizedExpression": {
53+
"openParen": {
54+
"kind": "OpenParenToken",
55+
"textLength": 1
56+
},
57+
"expression": {
58+
"error": "MissingToken",
59+
"kind": "Expression",
60+
"textLength": 0
61+
},
62+
"closeParen": {
63+
"kind": "CloseParenToken",
64+
"textLength": 1
65+
}
66+
}
67+
},
68+
"semicolon": {
69+
"kind": "SemicolonToken",
70+
"textLength": 1
71+
}
72+
}
73+
},
74+
{
75+
"error": "SkippedToken",
76+
"kind": "InlineHtml",
77+
"textLength": 3
78+
}
79+
],
80+
"closeBrace": {
81+
"error": "MissingToken",
82+
"kind": "CloseBraceToken",
83+
"textLength": 0
84+
}
85+
}
86+
},
87+
"elseIfClauses": [],
88+
"elseClause": null,
89+
"endifKeyword": null,
90+
"semicolon": null
91+
}
92+
}
93+
],
94+
"endOfFileToken": {
95+
"kind": "EndOfFileToken",
96+
"textLength": 0
97+
}
98+
}
99+
}

0 commit comments

Comments
 (0)