Skip to content

Null coalescing misses the default for missing statically-string array elements #554

Description

@nahime0

Summary

Null coalescing does not select its default when a missing array access has a statically known Str result:

<?php
$a = [
    ['', 'word0'],
    ['', 'word1'],
];

echo '[' . ($a[7][1] ?? 'dflt') . "]\n";
echo '[' . ($a[1][7] ?? 'dflt') . "]\n";
echo '[' . ($a[1][0] ?? 'dflt') . "]\n";

PHP distinguishes the two missing reads from the real empty string and prints:

[dflt]
[dflt]
[]

On current elephc, the already non-crashing second-index miss $a[1][7] ?? 'dflt' yields '' instead of 'dflt'. PR #533 makes the first-index miss $a[7][1] safe instead of dereferencing the null-container sentinel, but it then exposes the same pre-existing semantic gap: that expression also yields ''.

The coalescing expressions must remain silent, matching PHP.

Root cause

?? lowers its left-hand access silently and then tests the result with IsNull. In src/codegen/lower_inst/predicates.rs::emit_is_null_result(), statically known PhpType::Str values fall through to constant false.

A missed read of a statically-string slot is therefore indistinguishable at the null check from a legitimate empty string. By the time IsNull runs, the missing/null information has been erased.

Current main at the time of filing: 0b26415c9b.

Design scope

A fix must preserve a distinction between:

  • a missing/null string-valued read, for which ?? selects the default; and
  • a real empty string, for which ?? keeps ''.

One possible direction is to give Str an explicit null representation. Another is to preserve missingness explicitly through silent access/coalesce lowering. Either approach needs a broad audit because strings use multiple storage classes and ownership contracts.

At minimum, audit:

  • persistent and literal empty strings;
  • concat/scratch-backed strings;
  • heap-owned strings, including owned zero-length allocations;
  • casts and builtin-produced strings;
  • array/hash/property/string-offset miss paths;
  • local, argument, return, property, and array-element storage;
  • string ABI materialization and release/refcount helpers on every supported target.

The implementation must not reinterpret legitimate empty strings as null, create invalid sentinel pointers for string consumers, or introduce leaks/double releases.

Regression coverage

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions