Skip to content

phpcs hangs when using arrow functions that return heredoc #2926

@bcremer

Description

@bcremer

The following combination of arrow function and heredoc will lead to an endless loop:

fn () => <<<HTML
foo
HTML;

Steps to reproduce:

curl -OL https://squizlabs.github.io/PHP_CodeSniffer/phpcs.phar

cat <<EOT >> repro.php
<?php
fn () => <<<HTML
foo
HTML;
EOT

php -l repro.php 
No syntax errors detected in repro.php

$ php phpcs.phar -v repro.php  
Registering sniffs in the PEAR standard... DONE (28 sniffs registered)
Creating file list... DONE (1 files in queue)
Changing into directory /tmp/repro
Processing repro.php 

This will run infinitely.

Full log

This also happens with the current master branch commit 7c406b75a4e53a50aa1eba2d3dda28e3d55f9cfe.

Following the very verbose output of 7c406b75a4e53a50aa1eba2d3dda28e3d55f9cfe:

$ ./PHP_CodeSniffer/bin/phpcs -vvv repro.php
Processing ruleset /tmp/repro/PHP_CodeSniffer/src/Standards/PEAR/ruleset.xml
	Adding sniff files from /tmp/repro/PHP_CodeSniffer/src/Standards/PEAR/Sniffs directory
		=> /tmp/repro/PHP_CodeSniffer/src/Standards/PEAR/Sniffs/WhiteSpace/ScopeIndentSniff.php
		=> /tmp/repro/PHP_CodeSniffer/src/Standards/PEAR/Sniffs/WhiteSpace/ScopeClosingBraceSniff.php
		=> /tmp/repro/PHP_CodeSniffer/src/Standards/PEAR/Sniffs/WhiteSpace/ObjectOperatorIndentSniff.php
		=> /tmp/repro/PHP_CodeSniffer/src/Standards/PEAR/Sniffs/NamingConventions/ValidVariableNameSniff.php
		=> /tmp/repro/PHP_CodeSniffer/src/Standards/PEAR/Sniffs/NamingConventions/ValidFunctionNameSniff.php
		=> /tmp/repro/PHP_CodeSniffer/src/Standards/PEAR/Sniffs/NamingConventions/ValidClassNameSniff.php
		=> /tmp/repro/PHP_CodeSniffer/src/Standards/PEAR/Sniffs/Functions/ValidDefaultValueSniff.php
		=> /tmp/repro/PHP_CodeSniffer/src/Standards/PEAR/Sniffs/Functions/FunctionDeclarationSniff.php
		=> /tmp/repro/PHP_CodeSniffer/src/Standards/PEAR/Sniffs/Functions/FunctionCallSignatureSniff.php
		=> /tmp/repro/PHP_CodeSniffer/src/Standards/PEAR/Sniffs/Formatting/MultiLineAssignmentSniff.php
		=> /tmp/repro/PHP_CodeSniffer/src/Standards/PEAR/Sniffs/Files/IncludingFileSniff.php
		=> /tmp/repro/PHP_CodeSniffer/src/Standards/PEAR/Sniffs/ControlStructures/MultiLineConditionSniff.php
		=> /tmp/repro/PHP_CodeSniffer/src/Standards/PEAR/Sniffs/ControlStructures/ControlSignatureSniff.php
		=> /tmp/repro/PHP_CodeSniffer/src/Standards/PEAR/Sniffs/Commenting/InlineCommentSniff.php
		=> /tmp/repro/PHP_CodeSniffer/src/Standards/PEAR/Sniffs/Commenting/FunctionCommentSniff.php
		=> /tmp/repro/PHP_CodeSniffer/src/Standards/PEAR/Sniffs/Commenting/FileCommentSniff.php
		=> /tmp/repro/PHP_CodeSniffer/src/Standards/PEAR/Sniffs/Commenting/ClassCommentSniff.php
		=> /tmp/repro/PHP_CodeSniffer/src/Standards/PEAR/Sniffs/Classes/ClassDeclarationSniff.php
	Processing rule "Generic.Functions.FunctionCallArgumentSpacing"
		=> /tmp/repro/PHP_CodeSniffer/src/Standards/Generic/Sniffs/Functions/FunctionCallArgumentSpacingSniff.php
	Processing rule "Generic.NamingConventions.UpperCaseConstantName"
		=> /tmp/repro/PHP_CodeSniffer/src/Standards/Generic/Sniffs/NamingConventions/UpperCaseConstantNameSniff.php
	Processing rule "Generic.PHP.LowerCaseConstant"
		=> /tmp/repro/PHP_CodeSniffer/src/Standards/Generic/Sniffs/PHP/LowerCaseConstantSniff.php
	Processing rule "Generic.PHP.DisallowShortOpenTag"
		=> /tmp/repro/PHP_CodeSniffer/src/Standards/Generic/Sniffs/PHP/DisallowShortOpenTagSniff.php
	Processing rule "Generic.WhiteSpace.DisallowTabIndent"
		=> /tmp/repro/PHP_CodeSniffer/src/Standards/Generic/Sniffs/WhiteSpace/DisallowTabIndentSniff.php
	Processing rule "Generic.Commenting.DocComment"
		=> /tmp/repro/PHP_CodeSniffer/src/Standards/Generic/Sniffs/Commenting/DocCommentSniff.php
	Processing rule "Squiz.Commenting.DocCommentAlignment"
		=> /tmp/repro/PHP_CodeSniffer/src/Standards/Squiz/Sniffs/Commenting/DocCommentAlignmentSniff.php
	Processing rule "Generic.Files.LineLength"
		=> /tmp/repro/PHP_CodeSniffer/src/Standards/Generic/Sniffs/Files/LineLengthSniff.php
		=> property "lineLimit" set to "85"
		=> property "absoluteLineLimit" set to "0"
	Processing rule "Generic.Files.LineEndings"
		=> /tmp/repro/PHP_CodeSniffer/src/Standards/Generic/Sniffs/Files/LineEndingsSniff.php
		=> property "eolChar" set to "\n"
	Processing rule "Generic.Functions.FunctionCallArgumentSpacing.TooMuchSpaceAfterComma"
		=> /tmp/repro/PHP_CodeSniffer/src/Standards/Generic/Sniffs/Functions/FunctionCallArgumentSpacingSniff.php
		=> severity set to 0
	Processing rule "Generic.ControlStructures.InlineControlStructure"
		=> /tmp/repro/PHP_CodeSniffer/src/Standards/Generic/Sniffs/ControlStructures/InlineControlStructureSniff.php
		=> property "error" set to "false"
=> Ruleset processing complete; included 28 sniffs and excluded 0
Registered PHP_CodeSniffer\Standards\PEAR\Sniffs\WhiteSpace\ScopeIndentSniff
Registered PHP_CodeSniffer\Standards\PEAR\Sniffs\WhiteSpace\ScopeClosingBraceSniff
Registered PHP_CodeSniffer\Standards\PEAR\Sniffs\WhiteSpace\ObjectOperatorIndentSniff
Registered PHP_CodeSniffer\Standards\PEAR\Sniffs\NamingConventions\ValidVariableNameSniff
Registered PHP_CodeSniffer\Standards\PEAR\Sniffs\NamingConventions\ValidFunctionNameSniff
Registered PHP_CodeSniffer\Standards\PEAR\Sniffs\NamingConventions\ValidClassNameSniff
Registered PHP_CodeSniffer\Standards\PEAR\Sniffs\Functions\ValidDefaultValueSniff
Registered PHP_CodeSniffer\Standards\PEAR\Sniffs\Functions\FunctionDeclarationSniff
Registered PHP_CodeSniffer\Standards\PEAR\Sniffs\Functions\FunctionCallSignatureSniff
Registered PHP_CodeSniffer\Standards\PEAR\Sniffs\Formatting\MultiLineAssignmentSniff
Registered PHP_CodeSniffer\Standards\PEAR\Sniffs\Files\IncludingFileSniff
Registered PHP_CodeSniffer\Standards\PEAR\Sniffs\ControlStructures\MultiLineConditionSniff
Registered PHP_CodeSniffer\Standards\PEAR\Sniffs\ControlStructures\ControlSignatureSniff
Registered PHP_CodeSniffer\Standards\PEAR\Sniffs\Commenting\InlineCommentSniff
Registered PHP_CodeSniffer\Standards\PEAR\Sniffs\Commenting\FunctionCommentSniff
Registered PHP_CodeSniffer\Standards\PEAR\Sniffs\Commenting\FileCommentSniff
Registered PHP_CodeSniffer\Standards\PEAR\Sniffs\Commenting\ClassCommentSniff
Registered PHP_CodeSniffer\Standards\PEAR\Sniffs\Classes\ClassDeclarationSniff
Registered PHP_CodeSniffer\Standards\Generic\Sniffs\Functions\FunctionCallArgumentSpacingSniff
Registered PHP_CodeSniffer\Standards\Generic\Sniffs\NamingConventions\UpperCaseConstantNameSniff
Registered PHP_CodeSniffer\Standards\Generic\Sniffs\PHP\LowerCaseConstantSniff
Registered PHP_CodeSniffer\Standards\Generic\Sniffs\PHP\DisallowShortOpenTagSniff
Registered PHP_CodeSniffer\Standards\Generic\Sniffs\WhiteSpace\DisallowTabIndentSniff
Registered PHP_CodeSniffer\Standards\Generic\Sniffs\Commenting\DocCommentSniff
Registered PHP_CodeSniffer\Standards\Squiz\Sniffs\Commenting\DocCommentAlignmentSniff
Registered PHP_CodeSniffer\Standards\Generic\Sniffs\Files\LineLengthSniff
Registered PHP_CodeSniffer\Standards\Generic\Sniffs\Files\LineEndingsSniff
Registered PHP_CodeSniffer\Standards\Generic\Sniffs\ControlStructures\InlineControlStructureSniff
	*** START PHP TOKENIZING ***
	Process token [0]: T_OPEN_TAG => <?php·
	Process token [1]: T_DO => do
	Process token [2]: T_WHITESPACE => ·
	Process token  3 : T_OPEN_CURLY_BRACKET => {
	Process token [4]: T_CLOSE_TAG => ?>
	*** END PHP TOKENIZING ***
	*** START PHP TOKENIZING ***
	Process token [0]: T_OPEN_TAG => <?php·
	Process token [1]: T_WHITESPACE => ·
	Process token [2]: T_WHILE => while
	Process token [3]: T_WHITESPACE => ·
	Process token  4 : T_OPEN_PARENTHESIS => (
	Process token [5]: T_CLOSE_TAG => ?>
	*** END PHP TOKENIZING ***
	*** START PHP TOKENIZING ***
	Process token [0]: T_OPEN_TAG => <?php·
	Process token  1 : T_SEMICOLON => ;
	Process token [2]: T_CLOSE_TAG => ?>
	*** END PHP TOKENIZING ***
	*** START PHP TOKENIZING ***
	Process token [0]: T_OPEN_TAG => <?php·
	Process token [1]: T_WHILE => while
	Process token [2]: T_WHITESPACE => ·
	Process token  3 : T_OPEN_PARENTHESIS => (
	Process token [4]: T_CLOSE_TAG => ?>
	*** END PHP TOKENIZING ***
	*** START PHP TOKENIZING ***
	Process token [0]: T_OPEN_TAG => <?php·
	Process token [1]: T_WHITESPACE => ·
	Process token  2 : T_OPEN_CURLY_BRACKET => {
	Process token [3]: T_CLOSE_TAG => ?>
	*** END PHP TOKENIZING ***
	*** START PHP TOKENIZING ***
	Process token [0]: T_OPEN_TAG => <?php·
	Process token [1]: T_FOR => for
	Process token [2]: T_WHITESPACE => ·
	Process token  3 : T_OPEN_PARENTHESIS => (
	Process token [4]: T_CLOSE_TAG => ?>
	*** END PHP TOKENIZING ***
	*** START PHP TOKENIZING ***
	Process token [0]: T_OPEN_TAG => <?php·
	Process token [1]: T_WHITESPACE => ·
	Process token  2 : T_OPEN_CURLY_BRACKET => {
	Process token [3]: T_CLOSE_TAG => ?>
	*** END PHP TOKENIZING ***
	*** START PHP TOKENIZING ***
	Process token [0]: T_OPEN_TAG => <?php·
	Process token [1]: T_IF => if
	Process token [2]: T_WHITESPACE => ·
	Process token  3 : T_OPEN_PARENTHESIS => (
	Process token [4]: T_CLOSE_TAG => ?>
	*** END PHP TOKENIZING ***
	*** START PHP TOKENIZING ***
	Process token [0]: T_OPEN_TAG => <?php·
	Process token [1]: T_WHITESPACE => ·
	Process token  2 : T_OPEN_CURLY_BRACKET => {
	Process token [3]: T_CLOSE_TAG => ?>
	*** END PHP TOKENIZING ***
	*** START PHP TOKENIZING ***
	Process token [0]: T_OPEN_TAG => <?php·
	Process token [1]: T_FOREACH => foreach
	Process token [2]: T_WHITESPACE => ·
	Process token  3 : T_OPEN_PARENTHESIS => (
	Process token [4]: T_CLOSE_TAG => ?>
	*** END PHP TOKENIZING ***
	*** START PHP TOKENIZING ***
	Process token [0]: T_OPEN_TAG => <?php·
	Process token [1]: T_WHITESPACE => ·
	Process token  2 : T_OPEN_CURLY_BRACKET => {
	Process token [3]: T_CLOSE_TAG => ?>
	*** END PHP TOKENIZING ***
	*** START PHP TOKENIZING ***
	Process token [0]: T_OPEN_TAG => <?php·
	Process token  1 : T_CLOSE_CURLY_BRACKET => }
	Process token [2]: T_WHITESPACE => ·
	Process token [3]: T_ELSE => else
	Process token [4]: T_WHITESPACE => ·
	Process token [5]: T_IF => if
	Process token [6]: T_WHITESPACE => ·
	Process token  7 : T_OPEN_PARENTHESIS => (
	Process token [8]: T_CLOSE_TAG => ?>
	*** END PHP TOKENIZING ***
	*** START PHP TOKENIZING ***
	Process token [0]: T_OPEN_TAG => <?php·
	Process token [1]: T_WHITESPACE => ·
	Process token  2 : T_OPEN_CURLY_BRACKET => {
	Process token [3]: T_CLOSE_TAG => ?>
	*** END PHP TOKENIZING ***
	*** START PHP TOKENIZING ***
	Process token [0]: T_OPEN_TAG => <?php·
	Process token  1 : T_CLOSE_CURLY_BRACKET => }
	Process token [2]: T_WHITESPACE => ·
	Process token [3]: T_ELSEIF => elseif
	Process token [4]: T_WHITESPACE => ·
	Process token  5 : T_OPEN_PARENTHESIS => (
	Process token [6]: T_CLOSE_TAG => ?>
	*** END PHP TOKENIZING ***
	*** START PHP TOKENIZING ***
	Process token [0]: T_OPEN_TAG => <?php·
	Process token [1]: T_WHITESPACE => ·
	Process token  2 : T_OPEN_CURLY_BRACKET => {
	Process token [3]: T_CLOSE_TAG => ?>
	*** END PHP TOKENIZING ***
	*** START PHP TOKENIZING ***
	Process token [0]: T_OPEN_TAG => <?php·
	Process token  1 : T_CLOSE_CURLY_BRACKET => }
	Process token [2]: T_WHITESPACE => ·
	Process token [3]: T_ELSE => else
	Process token [4]: T_WHITESPACE => ·
	Process token  5 : T_OPEN_CURLY_BRACKET => {
	Process token [6]: T_CLOSE_TAG => ?>
	*** END PHP TOKENIZING ***
	*** START PHP TOKENIZING ***
	Process token [0]: T_OPEN_TAG => <?php·
	Process token [1]: T_DO => do
	Process token [2]: T_WHITESPACE => ·
	Process token  3 : T_OPEN_CURLY_BRACKET => {
	Process token [4]: T_CLOSE_TAG => ?>
	*** END PHP TOKENIZING ***
Creating file list... DONE (1 files in queue)
Changing into directory /tmp/repro
Processing repro.php 
	*** START PHP TOKENIZING ***
	Process token [0]: T_OPEN_TAG => <?php\n
	Process token [1]: T_FN => fn
	Process token [2]: T_WHITESPACE => ·
	Process token  3 : T_OPEN_PARENTHESIS => (
	Process token  4 : T_CLOSE_PARENTHESIS => )
	Process token [5]: T_WHITESPACE => ·
	Process token [6]: T_DOUBLE_ARROW => =>
	Process token [7]: T_WHITESPACE => ·
	Process token [8]: T_START_HEREDOC => <<<HTML\n
	Process token  11 : T_SEMICOLON => ;
	Process token [12]: T_WHITESPACE => \n
	*** END PHP TOKENIZING ***
	*** START TOKEN MAP ***
	=> Found unowned parenthesis opener at 3
	=> Found unowned parenthesis closer at 4 for 3
	*** END TOKEN MAP ***
	*** START SCOPE MAP ***
	Start scope map at 8:T_START_HEREDOC => <<<HTML\n
	=> Begin scope map recursion at token 8 with depth 1
	Process token 9 on line 3 [opener:8;]: T_HEREDOC => foo\n
	Process token 10 on line 4 [opener:8;]: T_END_HEREDOC => HTML
	=> Found scope closer (10:T_END_HEREDOC) for 8:T_START_HEREDOC
	*** END SCOPE MAP ***
	*** START LEVEL MAP ***
	Process token 0 on line 1 [col:1;len:5;lvl:0;]: T_OPEN_TAG => <?php\n
	Process token 1 on line 2 [col:1;len:2;lvl:0;]: T_FN => fn
	Process token 2 on line 2 [col:3;len:1;lvl:0;]: T_WHITESPACE => ·
	Process token 3 on line 2 [col:4;len:1;lvl:0;]: T_OPEN_PARENTHESIS => (
	Process token 4 on line 2 [col:5;len:1;lvl:0;]: T_CLOSE_PARENTHESIS => )
	Process token 5 on line 2 [col:6;len:1;lvl:0;]: T_WHITESPACE => ·
	Process token 6 on line 2 [col:7;len:2;lvl:0;]: T_DOUBLE_ARROW => =>
	Process token 7 on line 2 [col:9;len:1;lvl:0;]: T_WHITESPACE => ·
	Process token 8 on line 2 [col:10;len:7;lvl:0;]: T_START_HEREDOC => <<<HTML\n
	=> Found scope opener for 8:T_START_HEREDOC
		* level increased *
		* token 8:T_START_HEREDOC added to conditions array *
		Process token 9 on line 3 [col:1;len:3;lvl:1;conds;T_START_HEREDOC;]: T_HEREDOC => foo\n
		Process token 10 on line 4 [col:1;len:4;lvl:1;conds;T_START_HEREDOC;]: T_END_HEREDOC => HTML
		=> Found scope closer for 8:T_START_HEREDOC
		* token T_START_HEREDOC removed from conditions array *
		* level decreased *
	Process token 11 on line 4 [col:5;len:1;lvl:0;]: T_SEMICOLON => ;
	Process token 12 on line 4 [col:6;len:0;lvl:0;]: T_WHITESPACE => \n
	*** END LEVEL MAP ***
	*** START ADDITIONAL PHP PROCESSING ***

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions