You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Tokenizer/PHP: investigate root cause of a particular PHP 8.5 deprecation notice #1216
Commit 6b82a86 in PR #1215 temporarily silences a PHP 8.5 "Using null as an array offset" deprecation in the Tokenizer layer dealing with the ? character.
As per the commit message:
When running the NullsafeObjectOperatorTest, the "Using null as an array offset" deprecation gets triggered in the tokenizer layer handling re-tokenization to T_NULLABLE and/or T_INLINE_THEN.
This should only be possible if the below code at the top of the loop would result in $tokenType being null, which shouldn't be possible....
With this conundrum in mind, I'm electing to (temporarily) silence the deprecation notice for now until there is more time to investigate in more depth.
This issue is a reminder to investigate the above before PHP 9.0 comes round and to determine a permanent solution.
As that part of the Tokenizer is not sufficiently covered by dedicated tests, more tests will need to be put in place as well to validate and safeguard the permanent solution.
@andrewnicols I appreciate you taking a look. Which version of PHP 8.5 did you use to test ? The deprecation was only merged last week, so if you are using pre-compiled versions of PHP 8.5, you'd need PHP 8.5-beta3, which was only tagged today (and won't be officially released until Thursday).
@andrewnicols I just installed beta3 to investigate another one of these in the 4.x branch. Will update when I know more (just in case they turn out to be related).
So the combination of a comment on the line before, and the invalid code, and none of the other lines matching a break, mean that we hit the null token, which we expect to skip.
I'm not sure what the best solution is in this case. You know the code far better than me and the intent. I'm about to jump on a longhaul flight for the next 19 hours.
It feels wrong to add a specific check here for the value being null, but that may be the best solution in this case.
@andrewnicols Thank you for doing that analysis. I've followed your steps and agree with your conclusion about the cause.
Makes me wonder if there are other places in the Tokenizer which needs additional defensive coding for $tokens[$stackPtr] potentially being null, but that's for another ticket.
I'm not sure what the best solution is in this case.
It feels wrong to add a specific check here for the value being null, but that may be the best solution in this case.
I concur that it feels wrong to explicitly check for null here and I believe it can be avoided.
This is about determining whether a "?" character is a T_NULLABLE token or a T_INLINE_THEN.
Later on, when we get to the ? on the final line it's looking for the next non-empty line:
There is a break there but we never get to it because it's linting invalid code at the end of the file. There is no next non-empty line.
So instead it works backwards to look for a nullable type or ternary:
I think 👆🏻 this is the crucial part.
We first loop forward and if we didn't reach a conclusion, we loop backwards, but if the "?" is the last non-empty token in the file, I think we can already draw a conclusion after that first forward-loop, namely that we can never determine what the ? represents for definite, so we should short-circuit to the T_INLINE_THEN conclusion (= maintain existing behaviour).
So, I would suggest a check between the first (forward) loop and the second (backward) loop to see if the first loop reached the end of the file ($i === $numTokens) and if so, set the token to T_INLINE_THEN and continue to prevent it from even entering the second loop.
How does that sound to you ? I've locally tested a fix like described above and it seems to hold up fine.
If you like I could update your PR with the code I have locally (and add an extra test), though I'm also perfectly happy to let you fix it up further. Just let me know what you'd prefer.
Makes me wonder if there are other places in the Tokenizer which needs additional defensive coding for $tokens[$stackPtr] potentially being null, but that's for another ticket.
I've opened up a separate ticket as a reminder for this task: #1231
@andrewnicols Just checking in - had a chance to look at this ? Would you like me to push my alternative solution to your branch so you could have a look at it ?
Activity
andrewnicols commentedon Sep 9, 2025
I'm on a long train journey and thought I'd take a look at this, but after reverting #1215 I still can't reproduce this on PHP 8.5.
jrfnl commentedon Sep 9, 2025
@andrewnicols I appreciate you taking a look. Which version of PHP 8.5 did you use to test ? The deprecation was only merged last week, so if you are using pre-compiled versions of PHP 8.5, you'd need PHP 8.5-beta3, which was only tagged today (and won't be officially released until Thursday).
andrewnicols commentedon Sep 9, 2025
Ah thanks - I've got an older version at the moment. I'll see about installing a newer version and looking when I get a chance.
jrfnl commentedon Sep 9, 2025
@andrewnicols I just installed beta3 to investigate another one of these in the 4.x branch. Will update when I know more (just in case they turn out to be related).
jrfnl commentedon Sep 9, 2025
@andrewnicols Update: the issue in 4.x is in the same "layer" of the Tokenizer, but it is not the same issue.
I'll add the tests to cover the 4.x change to both the
masterand the4.xbranch.jrfnl commentedon Sep 9, 2025
FYI: I pulled the tests for the issue I was looking into in #1225 and the fix for 4.x in #1226.
The above still needs investigation as it is not the same issue.
andrewnicols commentedon Sep 10, 2025
Okay, so I've tracked the creation of the null to
PHP_CodeSniffer/src/Tokenizers/PHP.php
Lines 889 to 906 in 6b82a86
The comment above the change is:
The code being parsed at that point is this:
Later on, when we get to the
?on the final line it's looking for the next non-empty line:PHP_CodeSniffer/src/Tokenizers/PHP.php
Lines 2186 to 2191 in 6b82a86
There is a break there but we never get to it because it's linting invalid code at the end of the file. There is no next non-empty line.
So instead it works backwards to look for a nullable type or ternary:
PHP_CodeSniffer/src/Tokenizers/PHP.php
Lines 2255 to 2259 in 6b82a86
But none of the previous tokens before the
?meet any of the break conditions:if ($tokenType === ':' || $tokenType === ',' || $tokenType === T_ATTRIBUTE_END) {PHP_CodeSniffer/src/Tokenizers/PHP.php
Lines 2288 to 2294 in 6b82a86
PHP_CodeSniffer/src/Tokenizers/PHP.php
Line 2302 in 6b82a86
So the combination of a comment on the line before, and the invalid code, and none of the other lines matching a break, mean that we hit the
nulltoken, which we expect to skip.I'm not sure what the best solution is in this case. You know the code far better than me and the intent. I'm about to jump on a longhaul flight for the next 19 hours.
It feels wrong to add a specific check here for the value being null, but that may be the best solution in this case.
PHP 8.5 | Tokenizer/PHP: fix "Using null as an array offset" deprecation
jrfnl commentedon Sep 10, 2025
@andrewnicols Thank you for doing that analysis. I've followed your steps and agree with your conclusion about the cause.
Makes me wonder if there are other places in the Tokenizer which needs additional defensive coding for
$tokens[$stackPtr]potentially beingnull, but that's for another ticket.I concur that it feels wrong to explicitly check for
nullhere and I believe it can be avoided.This is about determining whether a "?" character is a
T_NULLABLEtoken or aT_INLINE_THEN.I think 👆🏻 this is the crucial part.
We first loop forward and if we didn't reach a conclusion, we loop backwards, but if the "?" is the last non-empty token in the file, I think we can already draw a conclusion after that first forward-loop, namely that we can never determine what the
?represents for definite, so we should short-circuit to theT_INLINE_THENconclusion (= maintain existing behaviour).So, I would suggest a check between the first (forward) loop and the second (backward) loop to see if the first loop reached the end of the file
($i === $numTokens)and if so, set the token toT_INLINE_THENandcontinueto prevent it from even entering the second loop.How does that sound to you ? I've locally tested a fix like described above and it seems to hold up fine.
If you like I could update your PR with the code I have locally (and add an extra test), though I'm also perfectly happy to let you fix it up further. Just let me know what you'd prefer.
jrfnl commentedon Sep 10, 2025
Hmm... just thinking... alternatively - the loop walking backwards could/should look at the
$finalTokensstack instead of the$tokens...I think the above is probably the better fix for this particular issue, but 👆🏻 this may not be a bad idea anyway....
jrfnl commentedon Sep 10, 2025
I've opened up a separate ticket as a reminder for this task: #1231
jrfnl commentedon Sep 15, 2025
@andrewnicols Just checking - had a chance to look over my above reply yet (the first one) ?
andrewnicols commentedon Sep 17, 2025
Hi @jrfnl,
Sorry - not had a chance to look at this and I doubt I will in the next week. Family emergencies. Sorry for the delay.
jrfnl commentedon Sep 17, 2025
@andrewnicols No worries, there's no rush as the deprecation notice is silenced in the mean time, so take your time.
jrfnl commentedon Oct 16, 2025
@andrewnicols Just checking in - had a chance to look at this ? Would you like me to push my alternative solution to your branch so you could have a look at it ?