[php-src] Issue #20684: Nullsafe operator ?-> incorrectly short-circuits subsequent array access [] (Precedence Violation)
[email protected] (azjezz)
| Newsgroups | php.bugs |
|---|---|
| Message-ID | <pfE8VJHRe12K2uby5qV0g5qFMGpGkbtlOU29wuzPIzQ@main.internal.php.net> |
Issue: https://github.com/php/php-src/issues/20684
Author: azjezz
### Description
The nullsafe operator (`?->`) short-circuits execution of subsequent array access operations (`[]`) if the base object is `null`. This behavior violates operator precedence rules.
The array subscript `[]` applies to the result of the method call/property access.
Expression: `$nullable?->meth()[0]`
- Since `$nullable` is `null`, `$nullable?->meth()` evaluates to `null`.
- The remaining expression is effectively `(null)[0]`.
- This should trigger a Warning, but currently returns `null` silently.
The compiler generates a `JMP_NULL` that skips the `FETCH_DIM_R` opcode entirely. This is incorrect because the array access is outside the scope of the nullsafe interaction. The issue is even more evident when parentheses are used to strictly contain the nullsafe expression, yet the compiler still jumps over the array access.
Test:
```
<?php
function example(): void {
$nullable = null;
// Case 1: Standard Chain
// Precedence dictates this is ($nullable?->meth())[0]
// Expected: Warning (accessing offset 0 on null)
// Actual: Silent null
$nullable?->meth()[0];
// Case 2: Parenthesized
// The grouping explicitly separates the operations.
// Expected: Warning
// Actual: Silent null
($nullable?->meth())[0];
// Case 3: Control
// Demonstrates that accessing an offset on null throws a warning.
(null)[0];
}
example();
```
Expected Result:
```
Warning: Trying to access array offset on value of type null in %s on line %d
Warning: Trying to access array offset on value of type null in %s on line %d
Warning: Trying to access array offset on value of type null in %s on line %d
```
Actual Result:
```
Warning: Trying to access array offset on value of type null in %s on line %d
```
(Only the control case emits the warning)
The issue is confirmed by the generated opcodes. The `JMP_NULL` instruction (generated by `?->`) targets the instruction after the `FETCH_DIM_R` (array access).
In the dump below (for `($nullable?->meth())[0]`), `JMP_NULL` at line #6 jumps to ~5. The `FETCH_DIM_R` at #9 writes to ~5. The jump skips the fetch, improperly treating the array access as part of the nullsafe short-circuit.
```opcode
filename: /in/n6rgk
function name: example
line #* E I O op fetch ext return operands
-------------------------------------------------------------------------------------
6 1 JMP_NULL ~3 !0
2 INIT_METHOD_CALL !0, 'meth'
3 DO_FCALL 0 $2
4 FETCH_DIM_R ~3 $2, 0
5 FREE ~3
7 6 JMP_NULL ~5 !0 <-- Jumps to result of FETCH_DIM_R
7 INIT_METHOD_CALL !0, 'meth'
8 DO_FCALL 0 $4
9 FETCH_DIM_R ~5 $4, 0
10 FREE ~5
```
### PHP Version
```plain
All supported versions
```
### Operating System
N/A