Skip to content

Nullsafe operator ?-> incorrectly short-circuits subsequent array access [] (Precedence Violation) #20684

@azjezz

Description

@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.

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

All supported versions

Operating System

N/A

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions