annotate vendor/nikic/php-parser/lib/PhpParser/ParserAbstract.php @ 13:5fb285c0d0e3

Update Drupal core to 8.4.7 via Composer. Security update; I *think* we've been lucky to get away with this so far, as we don't support self-registration which seems to be used by the so-called "drupalgeddon 2" attack that 8.4.5 was vulnerable to.
author Chris Cannam
date Mon, 23 Apr 2018 09:33:26 +0100
parents 4c8ae668cc8c
children c2387f117808
rev   line source
Chris@13 1 <?php declare(strict_types=1);
Chris@0 2
Chris@0 3 namespace PhpParser;
Chris@0 4
Chris@0 5 /*
Chris@0 6 * This parser is based on a skeleton written by Moriyoshi Koizumi, which in
Chris@0 7 * turn is based on work by Masato Bito.
Chris@0 8 */
Chris@13 9 use PhpParser\Node\Expr;
Chris@0 10 use PhpParser\Node\Name;
Chris@0 11 use PhpParser\Node\Param;
Chris@0 12 use PhpParser\Node\Scalar\LNumber;
Chris@0 13 use PhpParser\Node\Scalar\String_;
Chris@0 14 use PhpParser\Node\Stmt\Class_;
Chris@0 15 use PhpParser\Node\Stmt\ClassConst;
Chris@0 16 use PhpParser\Node\Stmt\ClassMethod;
Chris@0 17 use PhpParser\Node\Stmt\Interface_;
Chris@0 18 use PhpParser\Node\Stmt\Namespace_;
Chris@0 19 use PhpParser\Node\Stmt\Property;
Chris@0 20 use PhpParser\Node\Stmt\TryCatch;
Chris@0 21 use PhpParser\Node\Stmt\UseUse;
Chris@13 22 use PhpParser\Node\VarLikeIdentifier;
Chris@0 23
Chris@0 24 abstract class ParserAbstract implements Parser
Chris@0 25 {
Chris@0 26 const SYMBOL_NONE = -1;
Chris@0 27
Chris@0 28 /*
Chris@0 29 * The following members will be filled with generated parsing data:
Chris@0 30 */
Chris@0 31
Chris@0 32 /** @var int Size of $tokenToSymbol map */
Chris@0 33 protected $tokenToSymbolMapSize;
Chris@0 34 /** @var int Size of $action table */
Chris@0 35 protected $actionTableSize;
Chris@0 36 /** @var int Size of $goto table */
Chris@0 37 protected $gotoTableSize;
Chris@0 38
Chris@0 39 /** @var int Symbol number signifying an invalid token */
Chris@0 40 protected $invalidSymbol;
Chris@0 41 /** @var int Symbol number of error recovery token */
Chris@0 42 protected $errorSymbol;
Chris@0 43 /** @var int Action number signifying default action */
Chris@0 44 protected $defaultAction;
Chris@0 45 /** @var int Rule number signifying that an unexpected token was encountered */
Chris@0 46 protected $unexpectedTokenRule;
Chris@0 47
Chris@0 48 protected $YY2TBLSTATE;
Chris@13 49 /** @var int Number of non-leaf states */
Chris@13 50 protected $numNonLeafStates;
Chris@0 51
Chris@13 52 /** @var int[] Map of lexer tokens to internal symbols */
Chris@0 53 protected $tokenToSymbol;
Chris@13 54 /** @var string[] Map of symbols to their names */
Chris@0 55 protected $symbolToName;
Chris@0 56 /** @var array Names of the production rules (only necessary for debugging) */
Chris@0 57 protected $productions;
Chris@0 58
Chris@13 59 /** @var int[] Map of states to a displacement into the $action table. The corresponding action for this
Chris@0 60 * state/symbol pair is $action[$actionBase[$state] + $symbol]. If $actionBase[$state] is 0, the
Chris@0 61 action is defaulted, i.e. $actionDefault[$state] should be used instead. */
Chris@0 62 protected $actionBase;
Chris@13 63 /** @var int[] Table of actions. Indexed according to $actionBase comment. */
Chris@0 64 protected $action;
Chris@13 65 /** @var int[] Table indexed analogously to $action. If $actionCheck[$actionBase[$state] + $symbol] != $symbol
Chris@0 66 * then the action is defaulted, i.e. $actionDefault[$state] should be used instead. */
Chris@0 67 protected $actionCheck;
Chris@13 68 /** @var int[] Map of states to their default action */
Chris@0 69 protected $actionDefault;
Chris@13 70 /** @var callable[] Semantic action callbacks */
Chris@13 71 protected $reduceCallbacks;
Chris@0 72
Chris@13 73 /** @var int[] Map of non-terminals to a displacement into the $goto table. The corresponding goto state for this
Chris@0 74 * non-terminal/state pair is $goto[$gotoBase[$nonTerminal] + $state] (unless defaulted) */
Chris@0 75 protected $gotoBase;
Chris@13 76 /** @var int[] Table of states to goto after reduction. Indexed according to $gotoBase comment. */
Chris@0 77 protected $goto;
Chris@13 78 /** @var int[] Table indexed analogously to $goto. If $gotoCheck[$gotoBase[$nonTerminal] + $state] != $nonTerminal
Chris@0 79 * then the goto state is defaulted, i.e. $gotoDefault[$nonTerminal] should be used. */
Chris@0 80 protected $gotoCheck;
Chris@13 81 /** @var int[] Map of non-terminals to the default state to goto after their reduction */
Chris@0 82 protected $gotoDefault;
Chris@0 83
Chris@13 84 /** @var int[] Map of rules to the non-terminal on their left-hand side, i.e. the non-terminal to use for
Chris@0 85 * determining the state to goto after reduction. */
Chris@0 86 protected $ruleToNonTerminal;
Chris@13 87 /** @var int[] Map of rules to the length of their right-hand side, which is the number of elements that have to
Chris@0 88 * be popped from the stack(s) on reduction. */
Chris@0 89 protected $ruleToLength;
Chris@0 90
Chris@0 91 /*
Chris@0 92 * The following members are part of the parser state:
Chris@0 93 */
Chris@0 94
Chris@0 95 /** @var Lexer Lexer that is used when parsing */
Chris@0 96 protected $lexer;
Chris@0 97 /** @var mixed Temporary value containing the result of last semantic action (reduction) */
Chris@0 98 protected $semValue;
Chris@0 99 /** @var array Semantic value stack (contains values of tokens and semantic action results) */
Chris@0 100 protected $semStack;
Chris@0 101 /** @var array[] Start attribute stack */
Chris@0 102 protected $startAttributeStack;
Chris@0 103 /** @var array[] End attribute stack */
Chris@0 104 protected $endAttributeStack;
Chris@0 105 /** @var array End attributes of last *shifted* token */
Chris@0 106 protected $endAttributes;
Chris@0 107 /** @var array Start attributes of last *read* token */
Chris@0 108 protected $lookaheadStartAttributes;
Chris@0 109
Chris@0 110 /** @var ErrorHandler Error handler */
Chris@0 111 protected $errorHandler;
Chris@0 112 /** @var Error[] Errors collected during last parse */
Chris@0 113 protected $errors;
Chris@0 114 /** @var int Error state, used to avoid error floods */
Chris@0 115 protected $errorState;
Chris@0 116
Chris@0 117 /**
Chris@13 118 * Initialize $reduceCallbacks map.
Chris@13 119 */
Chris@13 120 abstract protected function initReduceCallbacks();
Chris@13 121
Chris@13 122 /**
Chris@0 123 * Creates a parser instance.
Chris@0 124 *
Chris@13 125 * Options: Currently none.
Chris@13 126 *
Chris@0 127 * @param Lexer $lexer A lexer
Chris@13 128 * @param array $options Options array.
Chris@0 129 */
Chris@13 130 public function __construct(Lexer $lexer, array $options = []) {
Chris@0 131 $this->lexer = $lexer;
Chris@13 132 $this->errors = [];
Chris@0 133
Chris@0 134 if (isset($options['throwOnError'])) {
Chris@0 135 throw new \LogicException(
Chris@0 136 '"throwOnError" is no longer supported, use "errorHandler" instead');
Chris@0 137 }
Chris@13 138
Chris@13 139 $this->initReduceCallbacks();
Chris@0 140 }
Chris@0 141
Chris@0 142 /**
Chris@0 143 * Parses PHP code into a node tree.
Chris@0 144 *
Chris@0 145 * If a non-throwing error handler is used, the parser will continue parsing after an error
Chris@0 146 * occurred and attempt to build a partial AST.
Chris@0 147 *
Chris@0 148 * @param string $code The source code to parse
Chris@0 149 * @param ErrorHandler|null $errorHandler Error handler to use for lexer/parser errors, defaults
Chris@0 150 * to ErrorHandler\Throwing.
Chris@0 151 *
Chris@13 152 * @return Node\Stmt[]|null Array of statements (or null non-throwing error handler is used and
Chris@13 153 * the parser was unable to recover from an error).
Chris@0 154 */
Chris@13 155 public function parse(string $code, ErrorHandler $errorHandler = null) {
Chris@0 156 $this->errorHandler = $errorHandler ?: new ErrorHandler\Throwing;
Chris@0 157
Chris@0 158 $this->lexer->startLexing($code, $this->errorHandler);
Chris@13 159 $result = $this->doParse();
Chris@0 160
Chris@13 161 // Clear out some of the interior state, so we don't hold onto unnecessary
Chris@13 162 // memory between uses of the parser
Chris@13 163 $this->startAttributeStack = [];
Chris@13 164 $this->endAttributeStack = [];
Chris@13 165 $this->semStack = [];
Chris@13 166 $this->semValue = null;
Chris@13 167
Chris@13 168 return $result;
Chris@13 169 }
Chris@13 170
Chris@13 171 protected function doParse() {
Chris@0 172 // We start off with no lookahead-token
Chris@0 173 $symbol = self::SYMBOL_NONE;
Chris@0 174
Chris@0 175 // The attributes for a node are taken from the first and last token of the node.
Chris@0 176 // From the first token only the startAttributes are taken and from the last only
Chris@0 177 // the endAttributes. Both are merged using the array union operator (+).
Chris@13 178 $startAttributes = [];
Chris@13 179 $endAttributes = [];
Chris@0 180 $this->endAttributes = $endAttributes;
Chris@0 181
Chris@0 182 // Keep stack of start and end attributes
Chris@13 183 $this->startAttributeStack = [];
Chris@13 184 $this->endAttributeStack = [$endAttributes];
Chris@0 185
Chris@0 186 // Start off in the initial state and keep a stack of previous states
Chris@0 187 $state = 0;
Chris@13 188 $stateStack = [$state];
Chris@0 189
Chris@0 190 // Semantic value stack (contains values of tokens and semantic action results)
Chris@13 191 $this->semStack = [];
Chris@0 192
Chris@0 193 // Current position in the stack(s)
Chris@13 194 $stackPos = 0;
Chris@0 195
Chris@0 196 $this->errorState = 0;
Chris@0 197
Chris@0 198 for (;;) {
Chris@0 199 //$this->traceNewState($state, $symbol);
Chris@0 200
Chris@13 201 if ($this->actionBase[$state] === 0) {
Chris@0 202 $rule = $this->actionDefault[$state];
Chris@0 203 } else {
Chris@0 204 if ($symbol === self::SYMBOL_NONE) {
Chris@0 205 // Fetch the next token id from the lexer and fetch additional info by-ref.
Chris@0 206 // The end attributes are fetched into a temporary variable and only set once the token is really
Chris@0 207 // shifted (not during read). Otherwise you would sometimes get off-by-one errors, when a rule is
Chris@0 208 // reduced after a token was read but not yet shifted.
Chris@0 209 $tokenId = $this->lexer->getNextToken($tokenValue, $startAttributes, $endAttributes);
Chris@0 210
Chris@0 211 // map the lexer token id to the internally used symbols
Chris@0 212 $symbol = $tokenId >= 0 && $tokenId < $this->tokenToSymbolMapSize
Chris@0 213 ? $this->tokenToSymbol[$tokenId]
Chris@0 214 : $this->invalidSymbol;
Chris@0 215
Chris@0 216 if ($symbol === $this->invalidSymbol) {
Chris@0 217 throw new \RangeException(sprintf(
Chris@0 218 'The lexer returned an invalid token (id=%d, value=%s)',
Chris@0 219 $tokenId, $tokenValue
Chris@0 220 ));
Chris@0 221 }
Chris@0 222
Chris@0 223 // This is necessary to assign some meaningful attributes to /* empty */ productions. They'll get
Chris@0 224 // the attributes of the next token, even though they don't contain it themselves.
Chris@13 225 $this->startAttributeStack[$stackPos+1] = $startAttributes;
Chris@13 226 $this->endAttributeStack[$stackPos+1] = $endAttributes;
Chris@0 227 $this->lookaheadStartAttributes = $startAttributes;
Chris@0 228
Chris@0 229 //$this->traceRead($symbol);
Chris@0 230 }
Chris@0 231
Chris@0 232 $idx = $this->actionBase[$state] + $symbol;
Chris@13 233 if ((($idx >= 0 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol)
Chris@0 234 || ($state < $this->YY2TBLSTATE
Chris@13 235 && ($idx = $this->actionBase[$state + $this->numNonLeafStates] + $symbol) >= 0
Chris@13 236 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol))
Chris@13 237 && ($action = $this->action[$idx]) !== $this->defaultAction) {
Chris@0 238 /*
Chris@13 239 * >= numNonLeafStates: shift and reduce
Chris@0 240 * > 0: shift
Chris@0 241 * = 0: accept
Chris@0 242 * < 0: reduce
Chris@0 243 * = -YYUNEXPECTED: error
Chris@0 244 */
Chris@0 245 if ($action > 0) {
Chris@0 246 /* shift */
Chris@0 247 //$this->traceShift($symbol);
Chris@0 248
Chris@13 249 ++$stackPos;
Chris@13 250 $stateStack[$stackPos] = $state = $action;
Chris@13 251 $this->semStack[$stackPos] = $tokenValue;
Chris@13 252 $this->startAttributeStack[$stackPos] = $startAttributes;
Chris@13 253 $this->endAttributeStack[$stackPos] = $endAttributes;
Chris@0 254 $this->endAttributes = $endAttributes;
Chris@0 255 $symbol = self::SYMBOL_NONE;
Chris@0 256
Chris@0 257 if ($this->errorState) {
Chris@0 258 --$this->errorState;
Chris@0 259 }
Chris@0 260
Chris@13 261 if ($action < $this->numNonLeafStates) {
Chris@0 262 continue;
Chris@0 263 }
Chris@0 264
Chris@13 265 /* $yyn >= numNonLeafStates means shift-and-reduce */
Chris@13 266 $rule = $action - $this->numNonLeafStates;
Chris@0 267 } else {
Chris@0 268 $rule = -$action;
Chris@0 269 }
Chris@0 270 } else {
Chris@0 271 $rule = $this->actionDefault[$state];
Chris@0 272 }
Chris@0 273 }
Chris@0 274
Chris@0 275 for (;;) {
Chris@0 276 if ($rule === 0) {
Chris@0 277 /* accept */
Chris@0 278 //$this->traceAccept();
Chris@0 279 return $this->semValue;
Chris@0 280 } elseif ($rule !== $this->unexpectedTokenRule) {
Chris@0 281 /* reduce */
Chris@0 282 //$this->traceReduce($rule);
Chris@0 283
Chris@0 284 try {
Chris@13 285 $this->reduceCallbacks[$rule]($stackPos);
Chris@0 286 } catch (Error $e) {
Chris@0 287 if (-1 === $e->getStartLine() && isset($startAttributes['startLine'])) {
Chris@0 288 $e->setStartLine($startAttributes['startLine']);
Chris@0 289 }
Chris@0 290
Chris@0 291 $this->emitError($e);
Chris@0 292 // Can't recover from this type of error
Chris@0 293 return null;
Chris@0 294 }
Chris@0 295
Chris@0 296 /* Goto - shift nonterminal */
Chris@13 297 $lastEndAttributes = $this->endAttributeStack[$stackPos];
Chris@13 298 $stackPos -= $this->ruleToLength[$rule];
Chris@0 299 $nonTerminal = $this->ruleToNonTerminal[$rule];
Chris@13 300 $idx = $this->gotoBase[$nonTerminal] + $stateStack[$stackPos];
Chris@13 301 if ($idx >= 0 && $idx < $this->gotoTableSize && $this->gotoCheck[$idx] === $nonTerminal) {
Chris@0 302 $state = $this->goto[$idx];
Chris@0 303 } else {
Chris@0 304 $state = $this->gotoDefault[$nonTerminal];
Chris@0 305 }
Chris@0 306
Chris@13 307 ++$stackPos;
Chris@13 308 $stateStack[$stackPos] = $state;
Chris@13 309 $this->semStack[$stackPos] = $this->semValue;
Chris@13 310 $this->endAttributeStack[$stackPos] = $lastEndAttributes;
Chris@0 311 } else {
Chris@0 312 /* error */
Chris@0 313 switch ($this->errorState) {
Chris@0 314 case 0:
Chris@0 315 $msg = $this->getErrorMessage($symbol, $state);
Chris@0 316 $this->emitError(new Error($msg, $startAttributes + $endAttributes));
Chris@0 317 // Break missing intentionally
Chris@0 318 case 1:
Chris@0 319 case 2:
Chris@0 320 $this->errorState = 3;
Chris@0 321
Chris@0 322 // Pop until error-expecting state uncovered
Chris@0 323 while (!(
Chris@0 324 (($idx = $this->actionBase[$state] + $this->errorSymbol) >= 0
Chris@13 325 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $this->errorSymbol)
Chris@0 326 || ($state < $this->YY2TBLSTATE
Chris@13 327 && ($idx = $this->actionBase[$state + $this->numNonLeafStates] + $this->errorSymbol) >= 0
Chris@13 328 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $this->errorSymbol)
Chris@13 329 ) || ($action = $this->action[$idx]) === $this->defaultAction) { // Not totally sure about this
Chris@13 330 if ($stackPos <= 0) {
Chris@0 331 // Could not recover from error
Chris@0 332 return null;
Chris@0 333 }
Chris@13 334 $state = $stateStack[--$stackPos];
Chris@0 335 //$this->tracePop($state);
Chris@0 336 }
Chris@0 337
Chris@0 338 //$this->traceShift($this->errorSymbol);
Chris@13 339 ++$stackPos;
Chris@13 340 $stateStack[$stackPos] = $state = $action;
Chris@0 341
Chris@0 342 // We treat the error symbol as being empty, so we reset the end attributes
Chris@0 343 // to the end attributes of the last non-error symbol
Chris@13 344 $this->endAttributeStack[$stackPos] = $this->endAttributeStack[$stackPos - 1];
Chris@13 345 $this->endAttributes = $this->endAttributeStack[$stackPos - 1];
Chris@0 346 break;
Chris@0 347
Chris@0 348 case 3:
Chris@0 349 if ($symbol === 0) {
Chris@0 350 // Reached EOF without recovering from error
Chris@0 351 return null;
Chris@0 352 }
Chris@0 353
Chris@0 354 //$this->traceDiscard($symbol);
Chris@0 355 $symbol = self::SYMBOL_NONE;
Chris@0 356 break 2;
Chris@0 357 }
Chris@0 358 }
Chris@0 359
Chris@13 360 if ($state < $this->numNonLeafStates) {
Chris@0 361 break;
Chris@0 362 }
Chris@0 363
Chris@13 364 /* >= numNonLeafStates means shift-and-reduce */
Chris@13 365 $rule = $state - $this->numNonLeafStates;
Chris@0 366 }
Chris@0 367 }
Chris@0 368
Chris@0 369 throw new \RuntimeException('Reached end of parser loop');
Chris@0 370 }
Chris@0 371
Chris@0 372 protected function emitError(Error $error) {
Chris@0 373 $this->errorHandler->handleError($error);
Chris@0 374 }
Chris@0 375
Chris@13 376 /**
Chris@13 377 * Format error message including expected tokens.
Chris@13 378 *
Chris@13 379 * @param int $symbol Unexpected symbol
Chris@13 380 * @param int $state State at time of error
Chris@13 381 *
Chris@13 382 * @return string Formatted error message
Chris@13 383 */
Chris@13 384 protected function getErrorMessage(int $symbol, int $state) : string {
Chris@0 385 $expectedString = '';
Chris@0 386 if ($expected = $this->getExpectedTokens($state)) {
Chris@0 387 $expectedString = ', expecting ' . implode(' or ', $expected);
Chris@0 388 }
Chris@0 389
Chris@0 390 return 'Syntax error, unexpected ' . $this->symbolToName[$symbol] . $expectedString;
Chris@0 391 }
Chris@0 392
Chris@13 393 /**
Chris@13 394 * Get limited number of expected tokens in given state.
Chris@13 395 *
Chris@13 396 * @param int $state State
Chris@13 397 *
Chris@13 398 * @return string[] Expected tokens. If too many, an empty array is returned.
Chris@13 399 */
Chris@13 400 protected function getExpectedTokens(int $state) : array {
Chris@13 401 $expected = [];
Chris@0 402
Chris@0 403 $base = $this->actionBase[$state];
Chris@0 404 foreach ($this->symbolToName as $symbol => $name) {
Chris@0 405 $idx = $base + $symbol;
Chris@0 406 if ($idx >= 0 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol
Chris@0 407 || $state < $this->YY2TBLSTATE
Chris@13 408 && ($idx = $this->actionBase[$state + $this->numNonLeafStates] + $symbol) >= 0
Chris@0 409 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol
Chris@0 410 ) {
Chris@13 411 if ($this->action[$idx] !== $this->unexpectedTokenRule
Chris@13 412 && $this->action[$idx] !== $this->defaultAction
Chris@13 413 && $symbol !== $this->errorSymbol
Chris@0 414 ) {
Chris@13 415 if (count($expected) === 4) {
Chris@0 416 /* Too many expected tokens */
Chris@13 417 return [];
Chris@0 418 }
Chris@0 419
Chris@0 420 $expected[] = $name;
Chris@0 421 }
Chris@0 422 }
Chris@0 423 }
Chris@0 424
Chris@0 425 return $expected;
Chris@0 426 }
Chris@0 427
Chris@0 428 /*
Chris@0 429 * Tracing functions used for debugging the parser.
Chris@0 430 */
Chris@0 431
Chris@0 432 /*
Chris@0 433 protected function traceNewState($state, $symbol) {
Chris@0 434 echo '% State ' . $state
Chris@0 435 . ', Lookahead ' . ($symbol == self::SYMBOL_NONE ? '--none--' : $this->symbolToName[$symbol]) . "\n";
Chris@0 436 }
Chris@0 437
Chris@0 438 protected function traceRead($symbol) {
Chris@0 439 echo '% Reading ' . $this->symbolToName[$symbol] . "\n";
Chris@0 440 }
Chris@0 441
Chris@0 442 protected function traceShift($symbol) {
Chris@0 443 echo '% Shift ' . $this->symbolToName[$symbol] . "\n";
Chris@0 444 }
Chris@0 445
Chris@0 446 protected function traceAccept() {
Chris@0 447 echo "% Accepted.\n";
Chris@0 448 }
Chris@0 449
Chris@0 450 protected function traceReduce($n) {
Chris@0 451 echo '% Reduce by (' . $n . ') ' . $this->productions[$n] . "\n";
Chris@0 452 }
Chris@0 453
Chris@0 454 protected function tracePop($state) {
Chris@0 455 echo '% Recovering, uncovered state ' . $state . "\n";
Chris@0 456 }
Chris@0 457
Chris@0 458 protected function traceDiscard($symbol) {
Chris@0 459 echo '% Discard ' . $this->symbolToName[$symbol] . "\n";
Chris@0 460 }
Chris@0 461 */
Chris@0 462
Chris@0 463 /*
Chris@0 464 * Helper functions invoked by semantic actions
Chris@0 465 */
Chris@0 466
Chris@0 467 /**
Chris@0 468 * Moves statements of semicolon-style namespaces into $ns->stmts and checks various error conditions.
Chris@0 469 *
Chris@13 470 * @param Node\Stmt[] $stmts
Chris@13 471 * @return Node\Stmt[]
Chris@0 472 */
Chris@13 473 protected function handleNamespaces(array $stmts) : array {
Chris@0 474 $hasErrored = false;
Chris@0 475 $style = $this->getNamespacingStyle($stmts);
Chris@0 476 if (null === $style) {
Chris@0 477 // not namespaced, nothing to do
Chris@0 478 return $stmts;
Chris@0 479 } elseif ('brace' === $style) {
Chris@0 480 // For braced namespaces we only have to check that there are no invalid statements between the namespaces
Chris@0 481 $afterFirstNamespace = false;
Chris@0 482 foreach ($stmts as $stmt) {
Chris@0 483 if ($stmt instanceof Node\Stmt\Namespace_) {
Chris@0 484 $afterFirstNamespace = true;
Chris@0 485 } elseif (!$stmt instanceof Node\Stmt\HaltCompiler
Chris@0 486 && !$stmt instanceof Node\Stmt\Nop
Chris@0 487 && $afterFirstNamespace && !$hasErrored) {
Chris@0 488 $this->emitError(new Error(
Chris@0 489 'No code may exist outside of namespace {}', $stmt->getAttributes()));
Chris@0 490 $hasErrored = true; // Avoid one error for every statement
Chris@0 491 }
Chris@0 492 }
Chris@0 493 return $stmts;
Chris@0 494 } else {
Chris@0 495 // For semicolon namespaces we have to move the statements after a namespace declaration into ->stmts
Chris@13 496 $resultStmts = [];
Chris@0 497 $targetStmts =& $resultStmts;
Chris@13 498 $lastNs = null;
Chris@0 499 foreach ($stmts as $stmt) {
Chris@0 500 if ($stmt instanceof Node\Stmt\Namespace_) {
Chris@13 501 if ($lastNs !== null) {
Chris@13 502 $this->fixupNamespaceAttributes($lastNs);
Chris@13 503 }
Chris@0 504 if ($stmt->stmts === null) {
Chris@13 505 $stmt->stmts = [];
Chris@0 506 $targetStmts =& $stmt->stmts;
Chris@0 507 $resultStmts[] = $stmt;
Chris@0 508 } else {
Chris@0 509 // This handles the invalid case of mixed style namespaces
Chris@0 510 $resultStmts[] = $stmt;
Chris@0 511 $targetStmts =& $resultStmts;
Chris@0 512 }
Chris@13 513 $lastNs = $stmt;
Chris@0 514 } elseif ($stmt instanceof Node\Stmt\HaltCompiler) {
Chris@0 515 // __halt_compiler() is not moved into the namespace
Chris@0 516 $resultStmts[] = $stmt;
Chris@0 517 } else {
Chris@0 518 $targetStmts[] = $stmt;
Chris@0 519 }
Chris@0 520 }
Chris@13 521 if ($lastNs !== null) {
Chris@13 522 $this->fixupNamespaceAttributes($lastNs);
Chris@13 523 }
Chris@0 524 return $resultStmts;
Chris@0 525 }
Chris@0 526 }
Chris@0 527
Chris@13 528 private function fixupNamespaceAttributes(Node\Stmt\Namespace_ $stmt) {
Chris@13 529 // We moved the statements into the namespace node, as such the end of the namespace node
Chris@13 530 // needs to be extended to the end of the statements.
Chris@13 531 if (empty($stmt->stmts)) {
Chris@13 532 return;
Chris@13 533 }
Chris@13 534
Chris@13 535 // We only move the builtin end attributes here. This is the best we can do with the
Chris@13 536 // knowledge we have.
Chris@13 537 $endAttributes = ['endLine', 'endFilePos', 'endTokenPos'];
Chris@13 538 $lastStmt = $stmt->stmts[count($stmt->stmts) - 1];
Chris@13 539 foreach ($endAttributes as $endAttribute) {
Chris@13 540 if ($lastStmt->hasAttribute($endAttribute)) {
Chris@13 541 $stmt->setAttribute($endAttribute, $lastStmt->getAttribute($endAttribute));
Chris@13 542 }
Chris@13 543 }
Chris@13 544 }
Chris@13 545
Chris@13 546 /**
Chris@13 547 * Determine namespacing style (semicolon or brace)
Chris@13 548 *
Chris@13 549 * @param Node[] $stmts Top-level statements.
Chris@13 550 *
Chris@13 551 * @return null|string One of "semicolon", "brace" or null (no namespaces)
Chris@13 552 */
Chris@0 553 private function getNamespacingStyle(array $stmts) {
Chris@0 554 $style = null;
Chris@0 555 $hasNotAllowedStmts = false;
Chris@0 556 foreach ($stmts as $i => $stmt) {
Chris@0 557 if ($stmt instanceof Node\Stmt\Namespace_) {
Chris@0 558 $currentStyle = null === $stmt->stmts ? 'semicolon' : 'brace';
Chris@0 559 if (null === $style) {
Chris@0 560 $style = $currentStyle;
Chris@0 561 if ($hasNotAllowedStmts) {
Chris@0 562 $this->emitError(new Error(
Chris@0 563 'Namespace declaration statement has to be the very first statement in the script',
Chris@0 564 $stmt->getLine() // Avoid marking the entire namespace as an error
Chris@0 565 ));
Chris@0 566 }
Chris@0 567 } elseif ($style !== $currentStyle) {
Chris@0 568 $this->emitError(new Error(
Chris@0 569 'Cannot mix bracketed namespace declarations with unbracketed namespace declarations',
Chris@0 570 $stmt->getLine() // Avoid marking the entire namespace as an error
Chris@0 571 ));
Chris@0 572 // Treat like semicolon style for namespace normalization
Chris@0 573 return 'semicolon';
Chris@0 574 }
Chris@0 575 continue;
Chris@0 576 }
Chris@0 577
Chris@0 578 /* declare(), __halt_compiler() and nops can be used before a namespace declaration */
Chris@0 579 if ($stmt instanceof Node\Stmt\Declare_
Chris@0 580 || $stmt instanceof Node\Stmt\HaltCompiler
Chris@0 581 || $stmt instanceof Node\Stmt\Nop) {
Chris@0 582 continue;
Chris@0 583 }
Chris@0 584
Chris@0 585 /* There may be a hashbang line at the very start of the file */
Chris@13 586 if ($i === 0 && $stmt instanceof Node\Stmt\InlineHTML && preg_match('/\A#!.*\r?\n\z/', $stmt->value)) {
Chris@0 587 continue;
Chris@0 588 }
Chris@0 589
Chris@0 590 /* Everything else if forbidden before namespace declarations */
Chris@0 591 $hasNotAllowedStmts = true;
Chris@0 592 }
Chris@0 593 return $style;
Chris@0 594 }
Chris@0 595
Chris@13 596 /**
Chris@13 597 * Fix up parsing of static property calls in PHP 5.
Chris@13 598 *
Chris@13 599 * In PHP 5 A::$b[c][d] and A::$b[c][d]() have very different interpretation. The former is
Chris@13 600 * interpreted as (A::$b)[c][d], while the latter is the same as A::{$b[c][d]}(). We parse the
Chris@13 601 * latter as the former initially and this method fixes the AST into the correct form when we
Chris@13 602 * encounter the "()".
Chris@13 603 *
Chris@13 604 * @param Node\Expr\StaticPropertyFetch|Node\Expr\ArrayDimFetch $prop
Chris@13 605 * @param Node\Arg[] $args
Chris@13 606 * @param array $attributes
Chris@13 607 *
Chris@13 608 * @return Expr\StaticCall
Chris@13 609 */
Chris@13 610 protected function fixupPhp5StaticPropCall($prop, array $args, array $attributes) : Expr\StaticCall {
Chris@13 611 if ($prop instanceof Node\Expr\StaticPropertyFetch) {
Chris@13 612 $name = $prop->name instanceof VarLikeIdentifier
Chris@13 613 ? $prop->name->toString() : $prop->name;
Chris@13 614 $var = new Expr\Variable($name, $prop->name->getAttributes());
Chris@13 615 return new Expr\StaticCall($prop->class, $var, $args, $attributes);
Chris@13 616 } elseif ($prop instanceof Node\Expr\ArrayDimFetch) {
Chris@13 617 $tmp = $prop;
Chris@13 618 while ($tmp->var instanceof Node\Expr\ArrayDimFetch) {
Chris@13 619 $tmp = $tmp->var;
Chris@13 620 }
Chris@13 621
Chris@13 622 /** @var Expr\StaticPropertyFetch $staticProp */
Chris@13 623 $staticProp = $tmp->var;
Chris@13 624
Chris@13 625 // Set start attributes to attributes of innermost node
Chris@13 626 $tmp = $prop;
Chris@13 627 $this->fixupStartAttributes($tmp, $staticProp->name);
Chris@13 628 while ($tmp->var instanceof Node\Expr\ArrayDimFetch) {
Chris@13 629 $tmp = $tmp->var;
Chris@13 630 $this->fixupStartAttributes($tmp, $staticProp->name);
Chris@13 631 }
Chris@13 632
Chris@13 633 $name = $staticProp->name instanceof VarLikeIdentifier
Chris@13 634 ? $staticProp->name->toString() : $staticProp->name;
Chris@13 635 $tmp->var = new Expr\Variable($name, $staticProp->name->getAttributes());
Chris@13 636 return new Expr\StaticCall($staticProp->class, $prop, $args, $attributes);
Chris@13 637 } else {
Chris@13 638 throw new \Exception;
Chris@13 639 }
Chris@13 640 }
Chris@13 641
Chris@13 642 protected function fixupStartAttributes(Node $to, Node $from) {
Chris@13 643 $startAttributes = ['startLine', 'startFilePos', 'startTokenPos'];
Chris@13 644 foreach ($startAttributes as $startAttribute) {
Chris@13 645 if ($from->hasAttribute($startAttribute)) {
Chris@13 646 $to->setAttribute($startAttribute, $from->getAttribute($startAttribute));
Chris@13 647 }
Chris@13 648 }
Chris@13 649 }
Chris@13 650
Chris@0 651 protected function handleBuiltinTypes(Name $name) {
Chris@0 652 $scalarTypes = [
Chris@0 653 'bool' => true,
Chris@0 654 'int' => true,
Chris@0 655 'float' => true,
Chris@0 656 'string' => true,
Chris@0 657 'iterable' => true,
Chris@0 658 'void' => true,
Chris@0 659 'object' => true,
Chris@0 660 ];
Chris@0 661
Chris@0 662 if (!$name->isUnqualified()) {
Chris@0 663 return $name;
Chris@0 664 }
Chris@0 665
Chris@13 666 $lowerName = $name->toLowerString();
Chris@13 667 if (!isset($scalarTypes[$lowerName])) {
Chris@13 668 return $name;
Chris@13 669 }
Chris@13 670
Chris@13 671 return new Node\Identifier($lowerName, $name->getAttributes());
Chris@0 672 }
Chris@0 673
Chris@13 674 /**
Chris@13 675 * Get combined start and end attributes at a stack location
Chris@13 676 *
Chris@13 677 * @param int $pos Stack location
Chris@13 678 *
Chris@13 679 * @return array Combined start and end attributes
Chris@13 680 */
Chris@13 681 protected function getAttributesAt(int $pos) : array {
Chris@0 682 return $this->startAttributeStack[$pos] + $this->endAttributeStack[$pos];
Chris@0 683 }
Chris@0 684
Chris@0 685 protected function parseLNumber($str, $attributes, $allowInvalidOctal = false) {
Chris@0 686 try {
Chris@0 687 return LNumber::fromString($str, $attributes, $allowInvalidOctal);
Chris@0 688 } catch (Error $error) {
Chris@0 689 $this->emitError($error);
Chris@0 690 // Use dummy value
Chris@0 691 return new LNumber(0, $attributes);
Chris@0 692 }
Chris@0 693 }
Chris@0 694
Chris@13 695 /**
Chris@13 696 * Parse a T_NUM_STRING token into either an integer or string node.
Chris@13 697 *
Chris@13 698 * @param string $str Number string
Chris@13 699 * @param array $attributes Attributes
Chris@13 700 *
Chris@13 701 * @return LNumber|String_ Integer or string node.
Chris@13 702 */
Chris@13 703 protected function parseNumString(string $str, array $attributes) {
Chris@0 704 if (!preg_match('/^(?:0|-?[1-9][0-9]*)$/', $str)) {
Chris@0 705 return new String_($str, $attributes);
Chris@0 706 }
Chris@0 707
Chris@0 708 $num = +$str;
Chris@0 709 if (!is_int($num)) {
Chris@0 710 return new String_($str, $attributes);
Chris@0 711 }
Chris@0 712
Chris@0 713 return new LNumber($num, $attributes);
Chris@0 714 }
Chris@0 715
Chris@0 716 protected function checkModifier($a, $b, $modifierPos) {
Chris@0 717 // Jumping through some hoops here because verifyModifier() is also used elsewhere
Chris@0 718 try {
Chris@0 719 Class_::verifyModifier($a, $b);
Chris@0 720 } catch (Error $error) {
Chris@0 721 $error->setAttributes($this->getAttributesAt($modifierPos));
Chris@0 722 $this->emitError($error);
Chris@0 723 }
Chris@0 724 }
Chris@0 725
Chris@0 726 protected function checkParam(Param $node) {
Chris@0 727 if ($node->variadic && null !== $node->default) {
Chris@0 728 $this->emitError(new Error(
Chris@0 729 'Variadic parameter cannot have a default value',
Chris@0 730 $node->default->getAttributes()
Chris@0 731 ));
Chris@0 732 }
Chris@0 733 }
Chris@0 734
Chris@0 735 protected function checkTryCatch(TryCatch $node) {
Chris@0 736 if (empty($node->catches) && null === $node->finally) {
Chris@0 737 $this->emitError(new Error(
Chris@0 738 'Cannot use try without catch or finally', $node->getAttributes()
Chris@0 739 ));
Chris@0 740 }
Chris@0 741 }
Chris@0 742
Chris@0 743 protected function checkNamespace(Namespace_ $node) {
Chris@13 744 if ($node->name && $node->name->isSpecialClassName()) {
Chris@0 745 $this->emitError(new Error(
Chris@0 746 sprintf('Cannot use \'%s\' as namespace name', $node->name),
Chris@0 747 $node->name->getAttributes()
Chris@0 748 ));
Chris@0 749 }
Chris@0 750
Chris@0 751 if (null !== $node->stmts) {
Chris@0 752 foreach ($node->stmts as $stmt) {
Chris@0 753 if ($stmt instanceof Namespace_) {
Chris@0 754 $this->emitError(new Error(
Chris@0 755 'Namespace declarations cannot be nested', $stmt->getAttributes()
Chris@0 756 ));
Chris@0 757 }
Chris@0 758 }
Chris@0 759 }
Chris@0 760 }
Chris@0 761
Chris@0 762 protected function checkClass(Class_ $node, $namePos) {
Chris@13 763 if (null !== $node->name && $node->name->isSpecialClassName()) {
Chris@0 764 $this->emitError(new Error(
Chris@0 765 sprintf('Cannot use \'%s\' as class name as it is reserved', $node->name),
Chris@0 766 $this->getAttributesAt($namePos)
Chris@0 767 ));
Chris@0 768 }
Chris@0 769
Chris@13 770 if ($node->extends && $node->extends->isSpecialClassName()) {
Chris@0 771 $this->emitError(new Error(
Chris@0 772 sprintf('Cannot use \'%s\' as class name as it is reserved', $node->extends),
Chris@0 773 $node->extends->getAttributes()
Chris@0 774 ));
Chris@0 775 }
Chris@0 776
Chris@0 777 foreach ($node->implements as $interface) {
Chris@13 778 if ($interface->isSpecialClassName()) {
Chris@0 779 $this->emitError(new Error(
Chris@0 780 sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface),
Chris@0 781 $interface->getAttributes()
Chris@0 782 ));
Chris@0 783 }
Chris@0 784 }
Chris@0 785 }
Chris@0 786
Chris@0 787 protected function checkInterface(Interface_ $node, $namePos) {
Chris@13 788 if (null !== $node->name && $node->name->isSpecialClassName()) {
Chris@0 789 $this->emitError(new Error(
Chris@0 790 sprintf('Cannot use \'%s\' as class name as it is reserved', $node->name),
Chris@0 791 $this->getAttributesAt($namePos)
Chris@0 792 ));
Chris@0 793 }
Chris@0 794
Chris@0 795 foreach ($node->extends as $interface) {
Chris@13 796 if ($interface->isSpecialClassName()) {
Chris@0 797 $this->emitError(new Error(
Chris@0 798 sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface),
Chris@0 799 $interface->getAttributes()
Chris@0 800 ));
Chris@0 801 }
Chris@0 802 }
Chris@0 803 }
Chris@0 804
Chris@0 805 protected function checkClassMethod(ClassMethod $node, $modifierPos) {
Chris@0 806 if ($node->flags & Class_::MODIFIER_STATIC) {
Chris@13 807 switch ($node->name->toLowerString()) {
Chris@0 808 case '__construct':
Chris@0 809 $this->emitError(new Error(
Chris@0 810 sprintf('Constructor %s() cannot be static', $node->name),
Chris@0 811 $this->getAttributesAt($modifierPos)));
Chris@0 812 break;
Chris@0 813 case '__destruct':
Chris@0 814 $this->emitError(new Error(
Chris@0 815 sprintf('Destructor %s() cannot be static', $node->name),
Chris@0 816 $this->getAttributesAt($modifierPos)));
Chris@0 817 break;
Chris@0 818 case '__clone':
Chris@0 819 $this->emitError(new Error(
Chris@0 820 sprintf('Clone method %s() cannot be static', $node->name),
Chris@0 821 $this->getAttributesAt($modifierPos)));
Chris@0 822 break;
Chris@0 823 }
Chris@0 824 }
Chris@0 825 }
Chris@0 826
Chris@0 827 protected function checkClassConst(ClassConst $node, $modifierPos) {
Chris@0 828 if ($node->flags & Class_::MODIFIER_STATIC) {
Chris@0 829 $this->emitError(new Error(
Chris@0 830 "Cannot use 'static' as constant modifier",
Chris@0 831 $this->getAttributesAt($modifierPos)));
Chris@0 832 }
Chris@0 833 if ($node->flags & Class_::MODIFIER_ABSTRACT) {
Chris@0 834 $this->emitError(new Error(
Chris@0 835 "Cannot use 'abstract' as constant modifier",
Chris@0 836 $this->getAttributesAt($modifierPos)));
Chris@0 837 }
Chris@0 838 if ($node->flags & Class_::MODIFIER_FINAL) {
Chris@0 839 $this->emitError(new Error(
Chris@0 840 "Cannot use 'final' as constant modifier",
Chris@0 841 $this->getAttributesAt($modifierPos)));
Chris@0 842 }
Chris@0 843 }
Chris@0 844
Chris@0 845 protected function checkProperty(Property $node, $modifierPos) {
Chris@0 846 if ($node->flags & Class_::MODIFIER_ABSTRACT) {
Chris@0 847 $this->emitError(new Error('Properties cannot be declared abstract',
Chris@0 848 $this->getAttributesAt($modifierPos)));
Chris@0 849 }
Chris@0 850
Chris@0 851 if ($node->flags & Class_::MODIFIER_FINAL) {
Chris@0 852 $this->emitError(new Error('Properties cannot be declared final',
Chris@0 853 $this->getAttributesAt($modifierPos)));
Chris@0 854 }
Chris@0 855 }
Chris@0 856
Chris@0 857 protected function checkUseUse(UseUse $node, $namePos) {
Chris@13 858 if ($node->alias && $node->alias->isSpecialClassName()) {
Chris@0 859 $this->emitError(new Error(
Chris@0 860 sprintf(
Chris@0 861 'Cannot use %s as %s because \'%2$s\' is a special class name',
Chris@0 862 $node->name, $node->alias
Chris@0 863 ),
Chris@0 864 $this->getAttributesAt($namePos)
Chris@0 865 ));
Chris@0 866 }
Chris@0 867 }
Chris@0 868 }