Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RuntimeException: Undefined variable: childOutput in squizlabs/php_codesniffer/src/Runner.php on line 705 #2304

Closed
yoeunes opened this issue Dec 11, 2018 · 52 comments

Comments

@yoeunes
Copy link

yoeunes commented Dec 11, 2018

  • phpcs version : 3.3.2
  • php version: 7.2 and 7.3
  • operating system: osx

Description

I get this exception when i run phpcs using a parallel value greater than 1

Fatal error: Uncaught PHP_CodeSniffer\Exceptions\RuntimeException: Undefined variable: childOutput in /Users/yoeunes/.composer/vendor/squizlabs/php_codesniffer/src/Runner.php on line 705 in /Users/yoeunes/.composer/vendor/squizlabs/php_codesniffer/src/Runner.php:562
Stack trace:
#0 /Users/yoeunes/.composer/vendor/squizlabs/php_codesniffer/src/Runner.php(705): PHP_CodeSniffer\Runner->handleErrors(8, 'Undefined varia...', '/Users/yoeunes/...', 705, Array)
#1 /Users/yoeunes/.composer/vendor/squizlabs/php_codesniffer/src/Runner.php(502): PHP_CodeSniffer\Runner->processChildProcs(Array)
#2 /Users/yoeunes/.composer/vendor/squizlabs/php_codesniffer/src/Runner.php(114): PHP_CodeSniffer\Runner->run()
#3 /Users/yoeunes/.composer/vendor/squizlabs/php_codesniffer/bin/phpcs(18): PHP_CodeSniffer\Runner->runPHPCS()
#4 {main}
  thrown in /Users/yoeunes/.composer/vendor/squizlabs/php_codesniffer/src/Runner.php on line 562

this is my phpcs.xml file:

<?xml version="1.0"?>
<ruleset name="coding standard">
    <description>coding standard</description>

    <arg name="extensions" value="php"/>
    
    <arg name="parallel" value="2"/>

    <file>src</file>

    <rule ref="PSR2" />

</ruleset>
@gsherwood
Copy link
Member

I can't replicate this (php 7.3, mac os), but it would mean that something pretty bad has gone wrong. If you are using a parallel value of 2, it means at least one of the two forks has exited but not written any data to the output file. I assume this means it died early, but I'm not sure why. It could be that the temp output file couldn't be written for some reason.

Without adding debug information into the runner, I'm not really sure how to figure out where it is dying. But there are some things to try:

  • Run phpcs with the -v command line argument to see debug output and ensure it is at least checking files
  • Run phpcs over a single file (still with parallel=2) to see if the temp file creation is working
  • Run phpcs over exactly 2 files, ideally with the same content

If you can try those things and post your findings, maybe something might show up.

Alternatively, if you'd like to add some debug code, I'd start in src/Runner.php at about line 497 to see if it is getting to the place where the file is written. Something like this:

$output .= ";\n?".'>';
echo "ABOUT TO WRITE TO $childOutFilename\n";
file_put_contents($childOutFilename, $output);
echo "ABOUT TO EXIT\n";
exit($pid);

@gsherwood gsherwood added this to Backlog in PHPCS v3 Development via automation Dec 11, 2018
@gsherwood gsherwood moved this from Backlog to Track in PHPCS v3 Development Dec 11, 2018
@yoeunes
Copy link
Author

yoeunes commented Dec 12, 2018

Thank you @gsherwood for your response.

This is the output with -v argument:

$ ./vendor/bin/phpcs -v
Registering sniffs in the Tamtam coding standard standard... DONE (42 sniffs registered)
Creating file list... DONE (1 files in queue)

I get the error in this line:

// Fake a processed file so we can print progress output for the batch.
$file = new DummyFile(null, $this->ruleset, $this->config);
$file->setErrorCounts(
$childOutput['totalErrors'],
$childOutput['totalWarnings'],
$childOutput['totalFixable'],
$childOutput['totalFixed']
);

The existence of $childOutput variable was checked for another bloc above and not for line 705:

if (isset($childOutput) === true) {
$this->reporter->totalFiles += $childOutput['totalFiles'];
$this->reporter->totalErrors += $childOutput['totalErrors'];
$this->reporter->totalWarnings += $childOutput['totalWarnings'];
$this->reporter->totalFixable += $childOutput['totalFixable'];
$this->reporter->totalFixed += $childOutput['totalFixed'];
}

adding and isset condition arround that bloc fixed my problem

@adaoudi
Copy link

adaoudi commented Dec 12, 2018

I had the same issue :(

@gsherwood
Copy link
Member

adding and isset condition arround that bloc fixed my problem

I figured it would, but I asked for more testing because it would be good to find out why your child processes are not outputting data. If they aren't producing output, it is quite likely that you are not seeing errors that are being generated.

This is why I asked if you could do some test runs over a single file, then 2 identical files. You need to be sure that the results you are getting are still valid, or else you are just hiding a very serious error.

@alies-dev
Copy link

I have the same error when a use --parallel value >1.
PHP 7.3.0 (cli)

@kevinfodness
Copy link

@gsherwood You're right, this is more complicated. The child processes aren't outputting any data. It dies quietly on this line, and then continues the loop, therefore never reaching the code that outputs the file contents: https://github.com/squizlabs/PHP_CodeSniffer/blob/master/src/Runner.php#L454

@kevinfodness
Copy link

More info: this function call successfully runs, but anything placed after this function call does not: https://github.com/squizlabs/PHP_CodeSniffer/blob/master/src/Files/LocalFile.php#L74

Adding logging to the setContent function demonstrates that it is successfully completing the function call without an error getting caught, but nothing after that is doing anything.

Due to the absence of error/warning/notice output and the fact that the script seems to mysteriously die, this feels to me like a possible race condition, in which the thing that is causing the script to stop is the fatal itself.

@Axent96
Copy link

Axent96 commented Jan 18, 2019

I have the same problem with php 7.3. One Project ist okey and another have the problem. I can it reproduce only on mac and on alpine is fine.

@esetnik
Copy link

esetnik commented Jan 27, 2019

I have the same issue on mac using brew install php version 7.3.1:

❯ php -v
PHP 7.3.1 (cli) (built: Jan 10 2019 13:15:37) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.1, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.3.1, Copyright (c) 1999-2018, by Zend Technologies

Downgrading back to v7.1.23 works correctly

@sebastiaanluca
Copy link

sebastiaanluca commented Jan 31, 2019

Getting these too in PHPStorm with CodeSniffer enabled since updating to 7.3 (brew). Don't have the parallel option set in my config, but maybe PHPStorm sets it.

phpcs: PHP Fatal error: Uncaught PHP_CodeSniffer\Exceptions\RuntimeException: Undefined variable: childOutput in …/vendor/squizlabs/php_codesniffer/src/Runner.php on line 712

@icetee
Copy link

icetee commented Jan 31, 2019

Yep, crash in PHP 7.3.1 (cli) (built: Jan 10 2019 13:15:37) ( NTS ) (homebrew)

@gsherwood
Copy link
Member

The only way I can get the error to happen is if the child processes run out of memory.

Is it possible that the fatal error is being suppressed for anyone, and that the child processes are actually dying due to memory limits?

@GaryJones
Copy link
Contributor

GaryJones commented Feb 9, 2019

PHP 7.3.1 and 7.3.2
PHPCS 3.4.0

My notes for what I tried in getting to the outcome below
  • Run phpcs with the -v command line argument to see debug output and ensure it is at least checking files
➜  vipcs git:(fix/ruleset-test-improvements) ✗ vendor/bin/phpcs tests/RulesetTest.php -v
Registering sniffs in the VIP Coding Standards standard... DONE (227 sniffs registered)
Creating file list... DONE (1 files in queue)
PHP Fatal error:  Uncaught PHP_CodeSniffer\Exceptions\RuntimeException: Undefined variable: childOutput in /path/to/vendor/squizlabs/php_codesniffer/src/Runner.php on line 712 in /path/to/vendor/squizlabs/php_codesniffer/src/Runner.php:569
  • Run phpcs over a single file (still with parallel=2) to see if the temp file creation is working

As above.

  • Run phpcs over exactly 2 files, ideally with the same content

Duplicated RulsetTest.php and the command. Output as above.

As expected, commenting out the parallel arg, and it all works fine.

Alternatively, if you'd like to add some debug code

As above (though with different line numbers due to the additional debug code).

I added my own debugging within processChildProcs(). When I had parallel of 5, then I had 5 items in my $childProcs. Of those, the first file (/private/var/folders/3_/nz1_2g_927sf2hp3k2xb3mr40000gn/T/phpcs-childFKGge9) did not exist (but commenting out the later unlink, and future runs showed the first child process output did exist), but the other 4 child process files did exist. They were empty.

When I added a debug inside of the file_exists($procData['out']) === true, and ran it again, it suggested the file exists (and the include returned int(1), but may have been empty, since none of the three isset variable checks returned as true.

At a guess, this does seem to suggest that when parallel is set, the child process files are not being written.

I did some more debugging earlier Runner::run(), at the // Batching and forking. point.

When running with parallel 5, I each batch is looped through and a child process is created ($pid !== 0). Batch 0 and 1 had a child process created immediately. Batch 2, 3 and 4 took a detour through the else first (// Move forward to the start of the batch.). That suggests that one or two child processes are not going through the code logic that ends up writing to the file.

In terms of that file writing process, there's some output buffering - I could get some echos working just before the ob_start(), but could not get anything inside the buffering to output (even by echoing $debugOutput), or any echo after it. It seems as though the logic just stops somewhere in or after that output buffering.

The was my output - note the inconsistent position of ob_start - perhaps there's some sort of race condition going on?:

Batch: 0
Child process created. PID = 39705
Batch: 1
Child process created. PID = 39706
Batch: 2
ob_start about to be called
Child process created. PID = 39707
Batch: 3
ob_start about to be called
Child process created. PID = 39708
Batch: 4
ob_start about to be called
Child process created. PID = 39709
Batch: 5
ob_start about to be called
Child process created. PID = 39710
ob_start about to be called
Batch: 6
Child process created. PID = 39711
Batch: 7
ob_start about to be called
Child process created. PID = 39712
ob_start about to be called
Batch: 8
Child process created. PID = 39713
Batch: 9
ob_start about to be called
Child process created. PID = 39714
ob_start about to be called
File /private/var/folders/3_/nz1_2g_927sf2hp3k2xb3mr40000gn/T/phpcs-childddCFiP exists.ob_start about to be called
PHP Fatal error:  Uncaught PHP_CodeSniffer\Exceptions\RuntimeException: Undefined variable: childOutput in /Users/gary/code/vipcs/vendor/squizlabs/php_codesniffer/src/Runner.php on line 724 in /Users/gary/code/vipcs/vendor/squizlabs/php_codesniffer/src/Runner.php:578

Either way, with no processing getting past that output buffering, no child process temp files are even getting to the $childOutput array assignment, let alone building the $output and eventually writing it to file.

From the docs (my emphasis):

output_buffering boolean/integer
You can enable output buffering for all files by setting this directive to 'On'. If you wish to limit the size of the buffer to a certain size - you can use a maximum number of bytes instead of 'On', as a value for this directive (e.g., output_buffering=4096). This directive is always Off in PHP-CLI.

Outcome

Taking it in a different direction, I added an echo 'FINISHED LOOP after the loops had finished, right before the $this->processChildProcs($childProcs);, and some debugging inside the loop right before and after each file was supposedly written.

Here's my extra debug code (starting [here])(

$output .= ";\n?".'>';
):

                    $output .= ";\n?".'>';
echo 'About to write file ' . $childOutFilename . "\n";
                    file_put_contents($childOutFilename, $output);
//var_dump( file_get_contents( $childOutFilename ) );
                    exit($pid);
                }//end if
            }//end for

echo 'FINISHED LOOP' . "\n";

And my output:

Batch: 0
Child process created. PID = 40174

Batch: 1
Child process created. PID = 40175

Batch: 2
About to write file /private/var/folders/3_/nz1_2g_927sf2hp3k2xb3mr40000gn/T/phpcs-child4FQCyE
Child process created. PID = 40176

Batch: 3
About to write file /private/var/folders/3_/nz1_2g_927sf2hp3k2xb3mr40000gn/T/phpcs-childedI8Ol
Child process created. PID = 40177

Batch: 4
About to write file /private/var/folders/3_/nz1_2g_927sf2hp3k2xb3mr40000gn/T/phpcs-childE3LXir
Child process created. PID = 40178

FINISHED LOOP
About to write file /private/var/folders/3_/nz1_2g_927sf2hp3k2xb3mr40000gn/T/phpcs-child6DuKPE
About to write file /private/var/folders/3_/nz1_2g_927sf2hp3k2xb3mr40000gn/T/phpcs-childujhpCQ
File /private/var/folders/3_/nz1_2g_927sf2hp3k2xb3mr40000gn/T/phpcs-child4FQCyE exists.

(That last "File ... exists" message is in processChildProcs().)

In this case, the FINISHED LOOP was appearing, before all of the the var_dump() was output - suggesting that some processing is hitting it, before all loops have actually finished (back to the race condition possibility). That in turns means the processChildProcs() call (where the reported undefined variable is), is being called before all child process temp files have been written.

With PHP 7.2, my output is:

Batch: 0
Child process created. PID = 46277
Batch: 1
Child process created. PID = 46278
Batch: 2
Child process created. PID = 46279
Batch: 3
Child process created. PID = 46280
Batch: 4
Child process created. PID = 46281
FINISHED LOOP
About to write file /private/var/folders/3_/nz1_2g_927sf2hp3k2xb3mr40000gn/T/phpcs-childr37H17
File /private/var/folders/3_/nz1_2g_927sf2hp3k2xb3mr40000gn/T/phpcs-childr37H17 exists.
.About to write file /private/var/folders/3_/nz1_2g_927sf2hp3k2xb3mr40000gn/T/phpcs-childRI5nsH
File /private/var/folders/3_/nz1_2g_927sf2hp3k2xb3mr40000gn/T/phpcs-childRI5nsH exists.
EAbout to write file /private/var/folders/3_/nz1_2g_927sf2hp3k2xb3mr40000gn/T/phpcs-childjTwoSr
File /private/var/folders/3_/nz1_2g_927sf2hp3k2xb3mr40000gn/T/phpcs-childjTwoSr exists.
.About to write file /private/var/folders/3_/nz1_2g_927sf2hp3k2xb3mr40000gn/T/phpcs-childvQ8Fjk
File /private/var/folders/3_/nz1_2g_927sf2hp3k2xb3mr40000gn/T/phpcs-childvQ8Fjk exists.
.About to write file /private/var/folders/3_/nz1_2g_927sf2hp3k2xb3mr40000gn/T/phpcs-childe4qV6Z
File /private/var/folders/3_/nz1_2g_927sf2hp3k2xb3mr40000gn/T/phpcs-childe4qV6Z exists.
. 5 / 5 (100%)

Which again suggests the same regarding initial file-writing, but it is in a different order, but more importantly, each file exists before it is processed.

Both PHP 7.3 and 7.2 have been compiled with --enable-pcntl.

If I echo $res; near the top of processChildProcs(), for PHP 7.2, there are screens and screens of zeros, even for the first file. With 7.3, there are considerably fewer for the first one. Maybe a speed optimisation for PHP 7.3 has revealed a race condition? If there was a way to not have $this->processChildProcs($childProcs); called until all child processes had been created and their temp files written to, then I think this would resolve the issue.

@gsherwood
Copy link
Member

In this case, the FINISHED LOOP was appearing, before all of the the var_dump() was output - suggesting that some processing is hitting it, before all loops have actually finished (back to the race condition possibility). That in turns means the processChildProcs() call (where the reported undefined variable is), is being called before all child process temp files have been written.

The way this is supposed to work means that FINISHED LOOP should almost always appear before any files are written. The call to processChildProcs() would normally happen before any of the child processes have completed their processing because they are normally checking a large number of files each. Not always, but often.

The processChildProcs() method uses pcntl_waitpid() with the WNOHANG flag to see if any of the child processes have completed. Once it finds one, it goes ahead and opens the file that the child process should have written. If the file doesn't exist, it doesn't try to open anything. If it does exist, it assumes there will be a var inside called $childOutput. It shouldn't matter when this loop starts as it is waiting for specific process IDs to be completed. If they are already done by the time the loop starts, they will be processed immediately. If not, the loop will wait.

Given that the error here is $childOutput does not exist, it means that the file_exists() call inside processChildProcs() passed, and so the pcntl_waitpid() call also found that the child process had ended. The file content itself was empty though. So this means that the child process did start correctly and was able to make a call to tempnam() to create the temp file (the one that is later empty) but pcntl_waitpid() tells the parent process that the child has completed before the file was written.

So either the child died before it could write to the file, or the pcntl_waitpid() function is saying that the child process has ended before it actually ended.

Debug code right before the exit($pid); line in the child would be good. If the processChildProcs() loop thinks the process has ended before the exit() call is made, something has gone wrong. The debug output you have commented out is probably the most important bit. Are you able to put that back in and see if you are able to retrieve the file contents before the process ends?

@GaryJones
Copy link
Contributor

GaryJones commented Feb 11, 2019

Thank you for that explanation. It would be useful for future readers to have some of that added to DocBlocks / inline comments, for those of us who aren't familiar with child processes in PHP.

Are you able to put that back in and see if you are able to retrieve the file contents before the process ends?

Yes. For brevity, I commented out the var_dump() and hadn't been including it by dumps here, but I thought that yes, the file was being written and I could retrieve the contents. However, I've just tried it again, and the results are different:

For PHP 7.2, all looks good:

PHP 7.2 output
Batch: 0
Child process created. PID = 3574
Batch: 1
Child process created. PID = 3575
Batch: 2
Child process created. PID = 3576
Batch: 3
Child process created. PID = 3577
Batch: 4
Child process created. PID = 3578
About to write file /private/var/folders/3_/nz1_2g_927sf2hp3k2xb3mr40000gn/T/phpcs-childTt63fF
Retrieving contents of file /private/var/folders/3_/nz1_2g_927sf2hp3k2xb3mr40000gn/T/phpcs-childTt63fF
string(165) "<?php
 $childOutput = array (
  'totalFiles' => 0,
  'totalErrors' => 0,
  'totalWarnings' => 0,
  'totalFixable' => 0,
  'totalFixed' => 0,
);
$debugOutput = '';
?>"
.About to write file /private/var/folders/3_/nz1_2g_927sf2hp3k2xb3mr40000gn/T/phpcs-childMqOLG4
Retrieving contents of file /private/var/folders/3_/nz1_2g_927sf2hp3k2xb3mr40000gn/T/phpcs-childMqOLG4
string(165) "<?php
 $childOutput = array (
  'totalFiles' => 0,
  'totalErrors' => 0,
  'totalWarnings' => 0,
  'totalFixable' => 0,
  'totalFixed' => 0,
);
$debugOutput = '';
?>"
.About to write file /private/var/folders/3_/nz1_2g_927sf2hp3k2xb3mr40000gn/T/phpcs-childwA0158
Retrieving contents of file /private/var/folders/3_/nz1_2g_927sf2hp3k2xb3mr40000gn/T/phpcs-childwA0158
string(165) "<?php
 $childOutput = array (
  'totalFiles' => 0,
  'totalErrors' => 0,
  'totalWarnings' => 0,
  'totalFixable' => 0,
  'totalFixed' => 0,
);
$debugOutput = '';
?>"
.About to write file /private/var/folders/3_/nz1_2g_927sf2hp3k2xb3mr40000gn/T/phpcs-childFU5kxL
Retrieving contents of file /private/var/folders/3_/nz1_2g_927sf2hp3k2xb3mr40000gn/T/phpcs-childFU5kxL
string(165) "<?php
 $childOutput = array (
  'totalFiles' => 0,
  'totalErrors' => 0,
  'totalWarnings' => 0,
  'totalFixable' => 0,
  'totalFixed' => 0,
);
$debugOutput = '';
?>"
.About to write file /private/var/folders/3_/nz1_2g_927sf2hp3k2xb3mr40000gn/T/phpcs-childSgSQwZ
Retrieving contents of file /private/var/folders/3_/nz1_2g_927sf2hp3k2xb3mr40000gn/T/phpcs-childSgSQwZ
string(165) "<?php
 $childOutput = array (
  'totalFiles' => 0,
  'totalErrors' => 0,
  'totalWarnings' => 0,
  'totalFixable' => 0,
  'totalFixed' => 0,
);
$debugOutput = '';
?>"
. 5 / 5 (100%)

For PHP 7.3, it doesn't get to the var_dump call:

PHP 7.3 output
Batch: 0
Child process created. PID = 12676
Batch: 1
Child process created. PID = 12677
Batch: 2
Child process created. PID = 12678
Batch: 3
Child process created. PID = 12679
Batch: 4
Child process created. PID = 12680
PHP Fatal error:  Uncaught PHP_CodeSniffer\Exceptions\RuntimeException: Undefined variable: childOutput in /Users/gary/code/vipcs/vendor/squizlabs/php_codesniffer/src/Runner.php on line 718 in /Users/gary/code/vipcs/vendor/squizlabs/php_codesniffer/src/Runner.php:575

There's a debug line after ob_end_clean(); that is not getting called for PHP 7.3.

If I comment out the block of code from ob_start() down to ob_end_clean(), and add a $debugOutput = '';, then it seems to work:

PHP 7.3 with output buffering disabled
Batch: 0
Child process created. PID = 29038
Batch: 1
Child process created. PID = 29039
Batch: 2
About to call ob_start().
About to call ob_start().
About to write file /private/var/folders/3_/nz1_2g_927sf2hp3k2xb3mr40000gn/T/phpcs-child5eVgKr
Child process created. PID = 29040
Batch: 3
About to call ob_start().
About to call ob_start().
Retrieving contents of file /private/var/folders/3_/nz1_2g_927sf2hp3k2xb3mr40000gn/T/phpcs-child5eVgKr
About to write file /private/var/folders/3_/nz1_2g_927sf2hp3k2xb3mr40000gn/T/phpcs-child4wstxf
Child process created. PID = 29041
string(165) "<?php
 $childOutput = array (
  'totalFiles' => 0,
  'totalErrors' => 0,
  'totalWarnings' => 0,
  'totalFixable' => 0,
  'totalFixed' => 0,
);
$debugOutput = '';
?>"
Retrieving contents of file /private/var/folders/3_/nz1_2g_927sf2hp3k2xb3mr40000gn/T/phpcs-child4wstxf
Batch: 4
About to call ob_start().
About to call ob_start().
string(165) "<?php
 $childOutput = array (
  'totalFiles' => 0,
  'totalErrors' => 0,
  'totalWarnings' => 0,
  'totalFixable' => 0,
  'totalFixed' => 0,
);
$debugOutput = '';
?>"
About to write file /private/var/folders/3_/nz1_2g_927sf2hp3k2xb3mr40000gn/T/phpcs-childLZlUc5
Child process created. PID = 29042
Retrieving contents of file /private/var/folders/3_/nz1_2g_927sf2hp3k2xb3mr40000gn/T/phpcs-childLZlUc5
About to call ob_start().
About to call ob_start().
string(165) "<?php
 $childOutput = array (
  'totalFiles' => 0,
  'totalErrors' => 0,
  'totalWarnings' => 0,
  'totalFixable' => 0,
  'totalFixed' => 0,
);
$debugOutput = '';
?>"
About to write file /private/var/folders/3_/nz1_2g_927sf2hp3k2xb3mr40000gn/T/phpcs-child7cSNNY
Retrieving contents of file /private/var/folders/3_/nz1_2g_927sf2hp3k2xb3mr40000gn/T/phpcs-child7cSNNY
About to call ob_start().
About to call ob_start().
string(165) "<?php
 $childOutput = array (
  'totalFiles' => 0,
  'totalErrors' => 0,
  'totalWarnings' => 0,
  'totalFixable' => 0,
  'totalFixed' => 0,
);
$debugOutput = '';
?>"
About to write file /private/var/folders/3_/nz1_2g_927sf2hp3k2xb3mr40000gn/T/phpcs-childkRG6dF
Retrieving contents of file /private/var/folders/3_/nz1_2g_927sf2hp3k2xb3mr40000gn/T/phpcs-childkRG6dF
string(165) "<?php
 $childOutput = array (
  'totalFiles' => 0,
  'totalErrors' => 0,
  'totalWarnings' => 0,
  'totalFixable' => 0,
  'totalFixed' => 0,
);
$debugOutput = '';
?>"
..... 5 / 5 (100%)

I can't find any reference to what's intentionally changed regarding output buffering for PHP 7.3. I use brew install php for my version of PHP 7.3 (and 7.2), and php -i | grep ouput_ shows it has the same values as with PHP 7.2.


I commented out the ob_*() start, get and end lines, and it still didn't give file outputs, so it doesn't appear to be anything to do with OB itself, only something within that block.

I commented out the following, it worked:

$file = $todo->current();

if ($file->ignored === true) {
	continue;
}

...

$this->processFile($file);

If any of them was uncommented, it failed again (that latter two because $file was undefined).

I looked more into FileList::current() - if I returned early from this, it worked.

@rdohms
Copy link

rdohms commented Feb 15, 2019

If it helps I had the same issue in PHP 7.3.1, but it stopped when I upgraded to PHP 7.3.2.

@rdohms
Copy link

rdohms commented Feb 15, 2019

Actually that may be a false positive, it started happening again after the first run, so upon php upgrade the first run was ok, the second and all since then, not.

@gagan0123
Copy link

Brought to this issue upon searching for the error message. Using OSX with Homebrew, PHPCS version 3.4.0 with PHP 7.3.2
Here's more info for debugging:

PHP Version output
$ php --version
PHP 7.3.2 (cli) (built: Feb  5 2019 22:19:09) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.2, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.3.2, Copyright (c) 1999-2018, by Zend Technologies
PHPCS Version output
$ phpcs --version
PHP_CodeSniffer version 3.4.0 (stable) by Squiz (http://www.squiz.net)
Error output
$ phpcs       
PHP Fatal error:  Uncaught PHP_CodeSniffer\Exceptions\RuntimeException: Undefined variable: childOutput in phar:///usr/local/Cellar/php-code-sniffer/3.4.0/bin/phpcs/src/Runner.php on line 2 in phar:///usr/local/Cellar/php-code-sniffer/3.4.0/bin/phpcs/src/Runner.php:2
Stack trace:
#0 phar:///usr/local/Cellar/php-code-sniffer/3.4.0/bin/phpcs/src/Runner.php(2): PHP_CodeSniffer\Runner->handleErrors(8, 'Undefined varia...', 'phar:///usr/loc...', 2, Array)
#1 phar:///usr/local/Cellar/php-code-sniffer/3.4.0/bin/phpcs/src/Runner.php(2): PHP_CodeSniffer\Runner->processChildProcs(Array)
#2 phar:///usr/local/Cellar/php-code-sniffer/3.4.0/bin/phpcs/src/Runner.php(2): PHP_CodeSniffer\Runner->run()
#3 /usr/local/Cellar/php-code-sniffer/3.4.0/bin/phpcs(6): PHP_CodeSniffer\Runner->runPHPCS()
#4 {main}
  thrown in phar:///usr/local/Cellar/php-code-sniffer/3.4.0/bin/phpcs/src/Runner.php on line 2

Fatal error: Uncaught PHP_CodeSniffer\Exceptions\RuntimeException: Undefined variable: childOutput in phar:///usr/local/Cellar/php-code-sniffer/3.4.0/bin/phpcs/src/Runner.php on line 2 in phar:///usr/local/Cellar/php-code-sniffer/3.4.0/bin/phpcs/src/Runner.php:2
Stack trace:
#0 phar:///usr/local/Cellar/php-code-sniffer/3.4.0/bin/phpcs/src/Runner.php(2): PHP_CodeSniffer\Runner->handleErrors(8, 'Undefined varia...', 'phar:///usr/loc...', 2, Array)
#1 phar:///usr/local/Cellar/php-code-sniffer/3.4.0/bin/phpcs/src/Runner.php(2): PHP_CodeSniffer\Runner->processChildProcs(Array)
#2 phar:///usr/local/Cellar/php-code-sniffer/3.4.0/bin/phpcs/src/Runner.php(2): PHP_CodeSniffer\Runner->run()
#3 /usr/local/Cellar/php-code-sniffer/3.4.0/bin/phpcs(6): PHP_CodeSniffer\Runner->runPHPCS()
#4 {main}
  thrown in phar:///usr/local/Cellar/php-code-sniffer/3.4.0/bin/phpcs/src/Runner.php on line 2

@gagan0123
Copy link

Just an update, after switching to php 7.2.15, error disappeared.

$ brew unlink php
$ brew link php@7.2 --force

@ryancco
Copy link

ryancco commented Mar 5, 2019

I am also running into this this issue running macOS Mojave (10.14.3)

❯ php --version
PHP 7.3.2 (cli) (built: Feb  5 2019 22:19:09) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.2, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.3.2, Copyright (c) 1999-2018, by Zend Technologies

❯ phpcs --version
PHP_CodeSniffer version 3.4.0 (stable) by Squiz (http://www.squiz.net)

❯ phpcs . 
PHP Fatal error:  Uncaught PHP_CodeSniffer\Exceptions\RuntimeException: Undefined variable: childOutput in /Users/ryan/.composer/vendor/squizlabs/php_codesniffer/src/Runner.php on line 712 in /Users/ryan/.composer/vendor/squizlabs/php_codesniffer/src/Runner.php:569
Stack trace:
#0 /Users/ryan/.composer/vendor/squizlabs/php_codesniffer/src/Runner.php(712): PHP_CodeSniffer\Runner->handleErrors(8, 'Undefined varia...', '/Users/ryan/.co...', 712, Array)
#1 /Users/ryan/.composer/vendor/squizlabs/php_codesniffer/src/Runner.php(509): PHP_CodeSniffer\Runner->processChildProcs(Array)
#2 /Users/ryan/.composer/vendor/squizlabs/php_codesniffer/src/Runner.php(114): PHP_CodeSniffer\Runner->run()
#3 /Users/ryan/.composer/vendor/squizlabs/php_codesniffer/bin/phpcs(18): PHP_CodeSniffer\Runner->runPHPCS()
#4 {main}
  thrown in /Users/ryan/.composer/vendor/squizlabs/php_codesniffer/src/Runner.php on line 569

Fatal error: Uncaught PHP_CodeSniffer\Exceptions\RuntimeException: Undefined variable: childOutput in /Users/ryan/.composer/vendor/squizlabs/php_codesniffer/src/Runner.php on line 712 in /Users/ryan/.composer/vendor/squizlabs/php_codesniffer/src/Runner.php:569
Stack trace:
#0 /Users/ryan/.composer/vendor/squizlabs/php_codesniffer/src/Runner.php(712): PHP_CodeSniffer\Runner->handleErrors(8, 'Undefined varia...', '/Users/ryan/.co...', 712, Array)
#1 /Users/ryan/.composer/vendor/squizlabs/php_codesniffer/src/Runner.php(509): PHP_CodeSniffer\Runner->processChildProcs(Array)
#2 /Users/ryan/.composer/vendor/squizlabs/php_codesniffer/src/Runner.php(114): PHP_CodeSniffer\Runner->run()
#3 /Users/ryan/.composer/vendor/squizlabs/php_codesniffer/bin/phpcs(18): PHP_CodeSniffer\Runner->runPHPCS()
#4 {main}
  thrown in /Users/ryan/.composer/vendor/squizlabs/php_codesniffer/src/Runner.php on line 569

@derotune
Copy link

Update on this?

@gsherwood
Copy link
Member

Update on this?

I'm still unable to replicate this issue, even using the same OS and PHP versions as stated here. If anyone can provide a repo or container or anything to help replicate the problem, I'd be very grateful.

@miguelci
Copy link

miguelci commented Apr 3, 2019

had the same issue on a Mac with php 7.3.3.
downgraded to php 7.2.16 and its working fine again.

@voku
Copy link

voku commented Apr 3, 2019

We had the same issue and for us it seems related to "symlinked" files and "parallel" execution?

PHP Fatal error:  Uncaught PHP_CodeSniffer\Exceptions\RuntimeException: Undefined index:  in phar:///home/USER/PROJECT/Framework/thirdparty/phpcs.phar/src/Files/FileList.php on line 2 in phar:///home/USER/PROJECT/Framework/thirdparty/phpcs.phar/src/Runner.php:2
Stack trace:
#0 phar:///home/USER/PROJECT/Framework/thirdparty/phpcs.phar/src/Files/FileList.php(2): PHP_CodeSniffer\Runner->handleErrors(8, 'Undefined index...', 'phar:///home/co...', 2, Array)
#1 phar:///home/USER/PROJECT/Framework/thirdparty/phpcs.phar/src/Runner.php(2): PHP_CodeSniffer\Files\FileList->current()
#2 phar:///home/USER/PROJECT/Framework/thirdparty/phpcs.phar/src/Runner.php(2): PHP_CodeSniffer\Runner->run()
#3 /home/USER/PROJECT/Framework/thirdparty/phpcs.phar(6): PHP_CodeSniffer\Runner->runPHPCS()
#4 {main}
  thrown in phar:///home/USER/PROJECT/Framework/thirdparty/phpcs.phar/src/Runner.php on line 2
PHP Fatal error:  Uncaught PHP_CodeSniffer\Exceptions\RuntimeException: Undefined variable: childOutput in phar:///home/USER/PROJECT/Framework/thirdparty/phpcs.phar/src/Runner.php on line 2 in phar:///home/USER/PROJECT/Framework/thirdparty/phpcs.phar/src/Runner.php:2
Stack trace:
#0 phar:///home/USER/PROJECT/Framework/thirdparty/phpcs.phar/src/Runner.php(2): PHP_CodeSniffer\Runner->handleErrors(8, 'Undefined varia...', 'phar:///home/co...', 2, Array)
#1 phar:///home/USER/PROJECT/Framework/thirdparty/phpcs.phar/src/Runner.php(2): PHP_CodeSniffer\Runner->processChildProcs(Array)
#2 phar:///home/USER/PROJECT/Framework/thirdparty/phpcs.phar/src/Runner.php(2): PHP_CodeSniffer\Runner->run()
#3 /home/USER/PROJECT/Framework/thirdparty/phpcs.phar(6): PHP_CodeSniffer\Runner->runPHPCS()
#4 {main}
  thrown in phar:///home/USER/PROJECT/Framework/thirdparty/phpcs.phar/src/Runner.php on line 2

@gsherwood
Copy link
Member

We had the same issue and for us it seems related to "symlinked" files and "parallel" execution?

I've just tried running over symlinked files with parallel execution of PHP 7.3 and still can't replicate any failures.

@gagan0123
Copy link

@gsherwood I was earlier running PHPCS 3.4.0, today I upgraded to 3.4.1 but still the error persists
Here's my terminal recording, can you point out what I did wrong?
asciicast

@gagan0123
Copy link

Update: We don't even need that repo to replicate this, nor any third party standards list, here's the simplest replication I could do, with a clean install of PHPCS and PHP7.3 using Homebrew:

asciicast

@gsherwood
Copy link
Member

I've managed to replicate this with a new brew installed version of PHP 7.3.4. Comparing that to my known working versions of 7.3, I've found that this php.ini setting appears to be the problem:

; Enables or disables JIT compilation of patterns. This requires the PCRE
; library to be compiled with JIT support.
;pcre.jit=1

Note that it is commented out, but the default value is obviously to enable this. Disabling this fixes the problems for me, and reflects what my working 7.3 installs have already done by default:

; Enables or disables JIT compilation of patterns. This requires the PCRE
; library to be compiled with JIT support.
pcre.jit=0

I debugged PHPCS as far as I could to determine why the child processes were failing, but it looked like the process just died between function calls. But this is a regex extension and I did notice that leaving the EOL char of files empty did allow the script to continue longer, but die from other reasons.

I'll keep looking into this to see if there is a particular regex that the jit is breaking. If I can't find one, I'll see if I can disable or detect this ini setting during a PHPCS run, or just detect that the child process has died and fail the entire run (means you wont be able to use the parallel option with the jit enabled).

@gsherwood
Copy link
Member

From the Generic standard, these are the sniffs that each cause errors when the jit is enabled:

Generic.PHP.Syntax
Generic.Commenting.Todo
Generic.WhiteSpace.DisallowTabIndent
Generic.PHP.DisallowAlternativePHPTags
Generic.WhiteSpace.DisallowSpaceIndent
Generic.Commenting.Fixme

@gsherwood
Copy link
Member

I thought this was caused by complex regular expressions, but changing the TODO code to just be:

$matches = [];
preg_match('/(.*)/', 'foo', $matches);

Also causes the process to die. I'm not sure that I can actually fix this. I might need to disable the jit during PHPCS runs.

gsherwood added a commit that referenced this issue Apr 9, 2019
@gsherwood gsherwood added this to the 3.4.2 milestone Apr 9, 2019
@gsherwood
Copy link
Member

I ended up just disabling the pcre jit for PHPCS (c09a4e1). I couldn't figure out why the processes were dying and why a simple regex would kill it.

I've also changed the process handling code so that PHPCS will die with a more specific error message rather than just an undefined var error.

If anyone is able to test this fix, I'd be very grateful. Or even just disabling the jit in your php.ini to confirm that is the cause for you as well.

@GaryJones
Copy link
Contributor

I can confirm that turning off JIT for PHP 7.3, allows PHPCS to run with parallel > 1.

@GaryJones
Copy link
Contributor

GaryJones commented Apr 9, 2019

And the fix seems to work too:

Screenshot 2019-04-09 at 01 57 30

(That's with my php.ini set back to ;pcre.jit=1)

@gsherwood
Copy link
Member

@GaryJones Thanks for testing that for me.

@gagan0123
Copy link

It Works 😃

@gsherwood Thanks for being so patient with this bug and helping us out

🤔 Still wondering why it works with JIT enabled on PHP 7.2 but not on PHP 7.3

@gagan0123
Copy link

Found this on PHP 7.3 Migration notes, worth looking into to resolve this without disabling JIT performance benefits

https://www.php.net/manual/en/migration73.other-changes.php#migration73.other-changes.pcre

So in PHP 7.3 they upgraded PCRE to PCRE2. This has caused issues in many other projects as well.

@gsherwood
Copy link
Member

worth looking into to resolve this without disabling JIT performance benefits

That's what I was trying to do, but it appeared that simple calling preg_match was enough to silently segfault or something, no matter what regex or string I used. But only when running after forking, and even then it wasn't the first regex that PHPCS was using. So I'm a bit lost on this.

@gsherwood
Copy link
Member

Thanks @GaryJones and @gagan0123 for testing. I'm going to close this so this change can be released in 3.4.2.

If anyone is continuing to have issues that this fix does not resolve, then it is likely to be a different problem and a new issue should be opened. If anyone has any idea how to get the parallel option working with the jit, please open an issue to discuss that as well.

Thanks to everyone who provided info to help track this down.

PHPCS v3 Development automation moved this from Track to Ready for Release Apr 9, 2019
@jrfnl
Copy link
Contributor

jrfnl commented Apr 9, 2019

This may need to be investigated by PHP itself - has anyone checked if there are bugs reported against PHP 7.3 for PCRE2 vs the pcre.jit option ?

@gagan0123
Copy link

@jrfnl Yes there are several projects that started reporting issues because of PCRE2 update, but most of them simply disabled pcre.jit for the time being, and haven't resolved the underlying issue.

@gsherwood Creating new issue for support of pcre.jit. Having it disabled is quite a performance hit, as it optimises the performance of same regular expressions running again and again which in PHPCS provides a performance boost.

@jrfnl
Copy link
Contributor

jrfnl commented Apr 9, 2019

@gagan0123 Thanks for checking.

If disabling pcre.jit solves the issue, I doubt the issue is a regex being incompatible with PCRE2 as in that case, the regex wouldn't (shouldn't) work on PHP 7.3 with pcre.jit turned off either.

This, to me, still sounds more like a problem with the PCRE2 implementation in PHP 7.3 itself and possibly support for pcre.jit not being fully implemented, not so much something which can be solved in userland code.

@gagan0123
Copy link

@jrfnl You're right, its not compatibility with PCRE2 thats causing the issue. I profiled the command using xdebug and with some luck found the exact line causing the issue.

Currently child processes in PHPCS are failing at
https://github.com/squizlabs/PHP_CodeSniffer/blob/master/src/Util/Common.php#L143

But I doubt this line if of any concern, since child processes are quitting at any preg_match statement in a forked process with Segmentation Fault signal(SIGSEGV).

Its an issue localised to PHP 7.3.x running on MacOS. I've tested on Ubuntu 18.04 with PHP 7.3.3-1+ubuntu18.04.1+deb.sury.org+1 and it works perfectly fine.

So I don't think we should hamper the performance benefits of having pcre.jit enabled, for everyone.

Right now building docker image with PHP 7.3 on alpine, once its built, will test on that as well.
https://travis-ci.com/gagan0123/wp-testing-docker/jobs/191527120

@gagan0123
Copy link

Update: It even works with PHP 7.3.4 on Alpine as well.

@jrfnl
Copy link
Contributor

jrfnl commented Apr 9, 2019

@gagan0123 Oh wow! Well done with the debugging!

If it helps: I was never able to reproduce the issue on Windows, so can confirm, based on one user testing, that things work fine on Windows.

https://github.com/squizlabs/PHP_CodeSniffer/blob/master/src/Util/Common.php#L143

Just had a quick look at that regex, even though it may not be the actual issue. I know PCRE2 is stricter regarding syntax and the | should really be within parentheses, so I'd be interested to see if changing the line to the below would make a difference:

if (preg_match("/(?:\r\n?|\n)/", $contents, $matches) !== 1) {

@gagan0123
Copy link

Thanks for confirming it works on Windows 😃

I tested with parentheses as well, but it didn't work either, thing is, on MacOS, any statement with preg_match within a forked child process results in segmentation fault, no matter what you put inside.

Learnt a lot while debugging this issue, thanks to @yoeunes for bringing this up 👍

@jrfnl
Copy link
Contributor

jrfnl commented Apr 9, 2019

I tested with parentheses as well, but it didn't work either, thing is, on MacOS, any statement with preg_match within a forked child process results in segmentation fault, no matter what you put inside.

So, if I understand you correctly, that basically just confirms that this is an issue which needs to be solved in PHP Core (or possibly MacOS), not in PHP userland code.

A search of the PHP bug tracker yielded this issue which I believe is the closest match: https://bugs.php.net/bug.php?id=77260

@gagan0123 It might help get the issue resolved if you (or someone else who can reproduce the issue) would add details of the findings from this ticket to that thread.

@gsherwood
Copy link
Member

Currently child processes in PHPCS are failing at
https://github.com/squizlabs/PHP_CodeSniffer/blob/master/src/Util/Common.php#L143

Just a note that during my debugging yesterday, my child processes were not directly failing here. I was able to echo content after the preg_match had completed and have that successfully output to screen for the child process. I could also exit after the preg_match and have the child process exit normally. But by the time the function had returned its value back to the caller, the process was dead.

I've been unable to replicate the issue in a test script, otherwise I'd report it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
PHPCS v3 Development
Ready for Release
Development

No branches or pull requests