Skip to content

test(count): lock the null-container sentinel path after the #585 normalization - #616

Merged
nahime0 merged 1 commit into
illegalstudio:mainfrom
mirchaemanuel:fix/602-count-sentinel-typeerror
Jul 27, 2026
Merged

test(count): lock the null-container sentinel path after the #585 normalization#616
nahime0 merged 1 commit into
illegalstudio:mainfrom
mirchaemanuel:fix/602-count-sentinel-typeerror

Conversation

@mirchaemanuel

@mirchaemanuel mirchaemanuel commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Summary

count() on a boxed Mixed receiver whose payload was the null-container sentinel used to
segfault (exit 139). A missed array read forwarded through a ternary merge
($argc == 1 ? $rows[5] : ["a", "b"]) was the concrete repro from issue #602.

That crash is already gone on main: the null-container normalization from issue #585
(PR #591) turns the sentinel into a real null before count() ever sees it, and
__rt_mixed_count already guards tag-4/5 payloads with emit_branch_if_null_container.
There is no dedicated runtime change left for this PR to land — an earlier guard here became
unreachable after rebasing onto that normalization.

This PR is tests only. It locks the post-#585 behavior so the #602 crash cannot return
silently, and it records the remaining PHP-parity gap so #617 cannot be changed by accident.

Observed behavior (locked here)

<?php
$rows = [[1, 2]];
$r = $argc == 1 ? $rows[5] : ["a", "b"];
echo count($r), "\n";

Run with no arguments (missed-read arm):

The quiet 0 for null receivers also preserves the off-web BUG-0 convention
(count($_SERVER) === 0 before assignment). Closing the PHP TypeError gap without breaking
that convention is tracked separately in #617. After normalization, sentinel and real null
are no longer distinguishable at count(), so #617 is an all-or-nothing decision.

Tests

New regression file tests/codegen/regressions/mixed_count_sentinel.rs:

  • uncaught sentinel count() does not crash; prints 0 and the undefined-key warning
  • catch (TypeError) does not fire (explicit count() on mixed null/scalar receivers returns 0 instead of PHP's TypeError #617 divergence lock)
  • the same path is heap-clean under heap debug
  • a real null cell (json_decode("null"), tag 8) counts as 0 the same way
  • the same ternary merge delivering a real array counts correctly
  • an empty container in a mixed cell still counts as 0 without a warning

Non-goals

Fixes #602

@mirchaemanuel

Copy link
Copy Markdown
Contributor Author

The remaining count() parity gap mentioned in the description (non-null scalars in a mixed cell silently counting as 0, where PHP raises TypeError with the scalar type in the message) is now tracked as #617 — the machinery from this PR makes it a small follow-up, but it changes a test-locked behavior so it's kept separate.

@github-actions github-actions Bot added area:codegen Touches target-aware assembly or backend lowering. area:runtime Touches runtime helpers, GC, ownership, or bridge runtimes. size:s Small pull request. type:fix Corrects broken or incompatible behavior. labels Jul 23, 2026
@mirchaemanuel

Copy link
Copy Markdown
Contributor Author

CI caught a real interaction here: extending the TypeError to plain null receivers breaks the deliberate BUG-0 CLI convention that off-web superglobals count as 0 (bug0_cli_read_of_server_superglobal_before_assignment_does_not_crash). Correcting to raise the TypeError for the sentinel forms only (the actual #602 crash) and restoring the legacy 0 for plain null / boxed-null receivers — that residual PHP-parity gap moves to #617's scope. The docs-sync failure is just line-number drift from the builtins.rs edit; regenerating. Fix incoming on this branch.

@mirchaemanuel

Copy link
Copy Markdown
Contributor Author

Correction pushed (f16c4b668 + docs sync ac606f34c). Final routing: only values carrying the in-band NULL_SENTINEL (as the cell or as a tag-4/5 payload) raise the TypeError — i.e. exactly the forms that segfaulted; a plain null cell (off-web superglobals), a null tag-4/5 payload, and a boxed real null (tag 8) all keep their legacy 0, so the BUG-0 CLI convention (count($_SERVER) === 0) is preserved and bug0_cli_read_of_server_superglobal_before_assignment_does_not_crash passes again. PHP parity for real-null/scalar receivers stays tracked in #617. Docs desync was the lower_empty embedded line shifting 1077→1099 from the new guard; regenerated with the CI sequence, idempotent.

@github-actions github-actions Bot added the area:tooling-ci Touches CI, development tooling, Docker, or repository scripts. label Jul 23, 2026
…tudio#585 normalization

count() on a Mixed receiver carrying the null-container sentinel used to
dereference it and segfault (issue illegalstudio#602). The normalization added for issue
illegalstudio#585 turns that sentinel into a real null before count() sees it, so the crash
is gone on main without a dedicated guard.

These tests lock the resulting behavior: the sentinel path no longer crashes,
reports the missed read and counts as 0, and stays heap-clean; a real null cell
counts the same way; a populated merge and an empty container are unaffected.
One test records that no TypeError is raised where PHP raises one, so the
divergence tracked in issue illegalstudio#617 cannot be changed silently.

Claude-Session: https://claude.ai/code/session_01124pGFSEbzYQcGbNykWN7V
@mirchaemanuel
mirchaemanuel force-pushed the fix/602-count-sentinel-typeerror branch from ac606f3 to 6d88381 Compare July 25, 2026 21:08
@mirchaemanuel mirchaemanuel changed the title fix: raise count() TypeError on a null-container Mixed receiver instead of segfaulting test(count): lock the null-container sentinel path after the #585 normalization Jul 25, 2026
@mirchaemanuel

Copy link
Copy Markdown
Contributor Author

This PR no longer needs to change any runtime code — your #585 normalization already fixed #602. I've rewritten it as tests only and retitled it accordingly.

Rebasing it onto today's main conflicted in mixed_count.rs against bd354a5ee, and the conflict turned out to be semantic rather than textual: both changes decide how count() treats a null container. Resolving it textually produced a build where the sentinel branch was simply unreachable — the PR's own tests failed, returning 0 instead of the TypeError.

So I checked whether the original crash still exists. On afee4cb47 with no patch of mine:

$rows = [[1, 2]];
$r = $argc == 1 ? $rows[5] : ["a", "b"];
echo count($r), "\n";
Warning: Undefined array key 5
0                                  ← exit 0, was SIGSEGV 139

Normalization turns the sentinel into a real null before count() ever sees it, so there's nothing left to guard. The runtime change here had become dead code, and keeping it would have implied a fix that isn't mine to claim.

What remains is the PHP-parity gap: php raises count(): Argument #1 ($value) must be of type Countable|array, null given where we return 0. That was always out of scope here and is tracked in #617 — and it's now the only thing left, since sentinel and real null are no longer distinguishable at that point. Which also means the narrow version this PR originally shipped (TypeError for sentinel forms only, legacy 0 everywhere else, preserving count($_SERVER) == 0 off-web) can't be expressed any more. Worth knowing before #617 gets picked up: it's now an all-or-nothing decision against the off-web convention.

The six tests lock what main actually guarantees — no crash, warning plus 0, heap-clean on that path, real-null cell counting the same way, populated merge and empty container unaffected — and one of them records that no TypeError is raised where PHP raises one, so #617's divergence can't be changed silently.

Verified on 6d88381e9 (macOS aarch64): test target compiles clean, mixed_count_sentinel 6 passed / 0 failed. Diff is two test files, nothing else — no CHANGELOG entry, since nothing user-facing changes.

@github-actions github-actions Bot added area:triage No primary component could be inferred from changed paths. type:test Changes tests or test infrastructure only. and removed type:fix Corrects broken or incompatible behavior. area:codegen Touches target-aware assembly or backend lowering. area:runtime Touches runtime helpers, GC, ownership, or bridge runtimes. area:tooling-ci Touches CI, development tooling, Docker, or repository scripts. labels Jul 25, 2026
@nahime0
nahime0 merged commit dceffa9 into illegalstudio:main Jul 27, 2026
114 of 115 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:triage No primary component could be inferred from changed paths. size:s Small pull request. type:test Changes tests or test infrastructure only.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

count() on a null-container sentinel mixed cell segfaults instead of raising PHP's TypeError

2 participants