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

Squiz/NonExecutableCode: fix false positives when code goes in and out of PHP/HTML #3770

Merged

Conversation

fredden
Copy link
Contributor

@fredden fredden commented Feb 27, 2023

The following code snippet is currently flagging as Squiz.PHP.NonExecutableCode.Unreachable, but probably shouldn't.

<?php if (true): ?>
    <?php foreach ([] as $item): ?>
        <?php continue; ?>
    <?php endforeach; ?>
<?php endif; ?>

This pull request adds a check to avoid this snippet from being flagged as problematic.

@fredden fredden force-pushed the NonExecutableCode.Unreachable/false-positive branch from fbff4a1 to 1231c93 Compare March 3, 2023 10:36
@fredden fredden requested a review from jrfnl March 3, 2023 10:36
@fredden fredden force-pushed the NonExecutableCode.Unreachable/false-positive branch from 1231c93 to ab62641 Compare March 3, 2023 13:25
@fredden fredden force-pushed the NonExecutableCode.Unreachable/false-positive branch from ab62641 to 33f38f1 Compare March 3, 2023 13:37
Copy link
Contributor

@jrfnl jrfnl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not done a full review yet, but still noticed some things when taking a second look which I believe need to be addressed before this PR can progress.

Just thinking: PHP will automatically add a semi-colon if one is missing before a PHP close tag. Will this fix work correctly with code missing the semi-colon ?

<!-- no problem here -->
<?php if (true): ?>
    <?php foreach ([] as $item): ?>
        <?php continue ?>
    <?php endforeach; ?>
<?php endif; ?>

<!-- should detect an error here -->
<?php if (true): ?>
    <?php foreach ([] as $item): ?>
        <?php continue ?>
        <div>non-executable</div>
    <?php endforeach; ?>
<?php endif; ?>

}

// Skip PHP re-open tag (eg, after inline HTML).
if ($tokens[$i]['code'] === T_OPEN_TAG) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about if the "re-open tag" is a PHP open tag with echo <?= ?

Might be good to add some tests for that as well.

Copy link
Contributor Author

@fredden fredden Jun 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the sniff already handles this case correctly. I've added a test (in b055898) to confirm.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've just tested this, but as far as I can see, the sniff does not handle this correctly, but the test used for this is flawed.

Try this:

<!-- no problem here -->
<?php if (true): ?>
    <?php foreach ([] as $item): ?>
        <?php continue; ?>
        <?= ?>
    <?php endforeach; ?>
<?php endif; ?>

<!-- should detect an error here -->
<?php if (true): ?>
    <?php foreach ([] as $item): ?>
        <?php continue; ?>
        <?=
        'unreachable'
        ?>
    <?php endforeach; ?>
<?php endif; ?>

While an empty <?= ?> isn't very useful, that's not the concern of the sniff.
If the <?= was handled correctly, the first and only error for the above code would be on the line containing the 'unreachable'. As things are, both lines containing a <?= also throw errors.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback. I've looked into both of these suggested test cases.

The sniff will complain about any line of code which is unreachable. (See

<?php
exit();
// Errors are thrown from here down from the exit() above.
foreach ($vars as $var) {
if ($something === TRUE) {
break;
break;
}
}
and - one complaint for each of lines 7-10 from the exit on line 3, and an additional complaint for line 10 from the break on line 9.)

I didn't see an existing test showing the expected behaviour when there are multiple unreachable statements on one line. I've added a test showing the existing behaviour in 34ba861.

Looking at these two suggested sniffs:

  • <?= ?> is a parse error, so I don't think it needs any special treatment in this sniff. https://3v4l.org/MXtbr
  • One statement split across multiple lines also isn't covered by existing tests; I've added a test showing the existing behaviour in 2067e7c.
  • We could add some logic to detect something like <?= ''; ?> which isn't a parse error, and will produce no output, but such a line of code seems like something that should be flagged to developers for review, so I'm comfortable with this sniff reporting that as unreachable. I don't think it's feasible for this sniff to detect output of a variable which happens to be empty and alert differently. A human can review the line of code in that case and make a sensible judgement.

@jrfnl do these cover the concerns raised here, or would you like me to add these specific tests as well?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to confirm - the tests added in the 2 case file just document existing behaviour. The behaviour regarding these is not changed by this PR. Correct ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the changes in src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.2.inc (and the corresponding additions in src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.php) are documenting existing behaviour. I can move these to a separate pull request if you would prefer.

@fredden fredden marked this pull request as draft June 7, 2023 11:21
@fredden
Copy link
Contributor Author

fredden commented Jun 8, 2023

Will this fix work correctly with code missing the semi-colon ?

No, the sniff has a false-negative in this case. While technically out of scope for this pull request, I will investigate adding code to solve this.

Edit: Fixed in 01410b4

@fredden fredden marked this pull request as ready for review June 8, 2023 16:42
@fredden fredden requested a review from jrfnl June 8, 2023 16:43
Copy link
Contributor

@jrfnl jrfnl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fredden Reviewed again and left some comments.

While technically out of scope for this pull request, I will investigate adding code to solve this.

Thanks for looking into this. In my opinion, it's not actually out of scope as the situation you are trying to address - code moving in and out of PHP/HTML - is the only time this comes into play.

src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php Outdated Show resolved Hide resolved
}

// Skip PHP re-open tag (eg, after inline HTML).
if ($tokens[$i]['code'] === T_OPEN_TAG) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've just tested this, but as far as I can see, the sniff does not handle this correctly, but the test used for this is flawed.

Try this:

<!-- no problem here -->
<?php if (true): ?>
    <?php foreach ([] as $item): ?>
        <?php continue; ?>
        <?= ?>
    <?php endforeach; ?>
<?php endif; ?>

<!-- should detect an error here -->
<?php if (true): ?>
    <?php foreach ([] as $item): ?>
        <?php continue; ?>
        <?=
        'unreachable'
        ?>
    <?php endforeach; ?>
<?php endif; ?>

While an empty <?= ?> isn't very useful, that's not the concern of the sniff.
If the <?= was handled correctly, the first and only error for the above code would be on the line containing the 'unreachable'. As things are, both lines containing a <?= also throw errors.

@jrfnl jrfnl added the Type: bug label Jun 8, 2023
@fredden fredden requested a review from jrfnl June 12, 2023 18:05
@jrfnl jrfnl changed the title Ignore white-space between PHP tags in unreachable test Squiz/NonExecutableCode: ignore white-space between PHP tags in unreachable test Aug 7, 2023
@jrfnl jrfnl changed the title Squiz/NonExecutableCode: ignore white-space between PHP tags in unreachable test Squiz/NonExecutableCode: fix false positives when code goes in and out of PHP/HTML Oct 12, 2023
Copy link
Contributor

@jrfnl jrfnl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fredden Sorry it took me a while to get back to this PR. I've done some extensive testing with it now and IMO this is good to go. Thank you for your work on this!

@jrfnl jrfnl merged commit ebb0a96 into squizlabs:master Oct 12, 2023
@jrfnl jrfnl added this to the 3.8.0 milestone Oct 12, 2023
jrfnl added a commit that referenced this pull request Oct 12, 2023
jrfnl pushed a commit that referenced this pull request Oct 12, 2023
…t of PHP/HTML (#3770)

Ignore HTML whitespace and PHP re-open tags when determining whether code should be flagged as non executable.
@fredden fredden deleted the NonExecutableCode.Unreachable/false-positive branch October 13, 2023 13:34
@jrfnl
Copy link
Contributor

jrfnl commented Dec 8, 2023

FYI: this fix is included in today's PHP_CodeSniffer 3.8.0 release.

As per #3932, development on PHP_CodeSniffer will continue in the PHPCSStandards/PHP_CodeSniffer repository. If you want to stay informed, you may want to start "watching" that repo (or watching releases from that repo).

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

Successfully merging this pull request may close these issues.

2 participants