Skip to content

Regression: next()/prev() on object leave current() NULL — Zend returns element (ext/standard/array.c) #25097

Closed

Description

@PurHur

Category

stdlib · php-src-strict · array pointer builtins

Problem

next() / prev() / end() on an object still advance key() (php-src accepts object HT), but current() (and often the return value of prev()) is NULL on VM/JIT while Zend returns the element at the new position.

Probed 2026-07-29 on Zend 8.2.32 vs bin/vm.php / bin/jit.php.

Repro Zend 8.2+ VM / JIT
$o=(object)['a'=>1,'b'=>2]; next($o); current($o) 2 (key b) NULL (key still b)
$o=(object)['a'=>1,'b'=>2,'c'=>3]; end($o); prev($o) ret=2, current=2, key=b ret=NULL/2 (host variance), current=NULL, key=b
second prev($o) ret=1, current=1, key=a ret=NULL, current=NULL, key=a

Note: Zend emits deprecation for calling these on objects; php-src-strict still requires the element values to match until removed.

php-src reference

PHP implementation target

  • ext/standard/prev.php, next.php, current.php + ext/standard/JitArrayPointer.php / ArrayPointerJitHelper.php / ArrayPointerRuntime — after moving the object HT pointer, current() must load the value at key() (same as arrays)
  • Prefer PHP-in-PHP fix; no new C in runtime/

Repro

./script/docker-exec.sh -- bash -lc 'cat > /tmp/prev_obj.php <<'"'"'PHP'"'"'
<?php
$o = (object)["a" => 1, "b" => 2, "c" => 3];
end($o);
$r = prev($o);
echo "prev_ret=" . var_export($r, true)
   . " current=" . var_export(current($o), true)
   . " key=" . var_export(key($o), true) . "\n";
$o2 = (object)["a" => 1, "b" => 2];
next($o2);
echo "next_current=" . var_export(current($o2), true) . " key=" . var_export(key($o2), true) . "\n";
PHP
php /tmp/prev_obj.php 2>/dev/null
php bin/vm.php /tmp/prev_obj.php
php bin/jit.php /tmp/prev_obj.php'

Done when

  • After next/prev/end on stdClass, current() and the builtin return value match Zend for public props
  • Compliance .phpt under test/compliance/cases/stdlib/
  • php-src-strict; no php-compiler-strict shortcut

Activity

  1. added
    bugSomething isn't working
    phase-4:stdlibPhase 4 – stdlib for web apps
    area:vmVirtual machine
    implementation-readySpec complete: repro, php-src ref, done-when — safe for workers to claim
    on Jul 30, 2026
  2. PurHur commented on Jul 30, 2026

    @PurHur
    OwnerAuthor

    claim: PHP Compiler STD Lib Advanced — batch (issues #25063, #25097, #23785)

    Hard stdlib cluster: highlight_string/file/show_source Zend HTML wire (#25063), next/prev/current on object HT (#25097), highlight Reflection names (#23785). Shipping sequential PRs; starting with #25063.

  3. PurHur commented on Jul 30, 2026

    @PurHur
    OwnerAuthor

    Continuing claimed batch after #25063 / #25128: implementing #25097 next — next/prev/current on object HT.

  4. PurHur commented on Jul 30, 2026

    @PurHur
    OwnerAuthor

    Handoff — blocked on CFG arg wiring (not ObjectEntry)

    Claim: continuing advanced-stdlib batch after #25063 / #25128.

    Stable repro (issue shape)

    $o2 = (object)["a" => 1, "b" => 2];
    next($o2);
    echo "next_current=" . var_export(current($o2), true) . " key=" . var_export(key($o2), true) . "\n";
    Runtime Result
    Zend 8.2 next_current=2 key='b'
    VM/JIT next_current=NULL key='b' (key OK, current NULL)

    Same shape with a declared class (class C { public $a=1; public $b=2; }) is green. Arrays are green. $c = current($o2) then echo is green. ObjectEntry pointer ops are fine when current() is not nested under var_export(..., true) in a concat.

    Root cause (CFG)

    php bin/print.php shows php-cfg hoist:

    FuncCall current → result Var#8
    ConstFetch true → Var#9
    FuncCall var_export args[0]=Var#10 args[1]=Var#11   // dead temps, not #8/#9
    

    slotForHoistedArrayPointerBuiltinCallArg / siblingFuncCallFeedsVarExportConsumer (#13829 / #13828) already cover array var_export(current($a), true) (see InlineCallArgProducerSlotTest). Dynamic-property stdClass / (object)[...] after stmt-level next/prev still leaves arg #0 on a dead temp → var_export sees NULL while key() often still prints the advanced key.

    Related existing coverage: test/repro/maintainer_gap_array_pointer_object.php (declared props, separate echoes — green), unit tests around #13828/#13829 (arrays only).

    Next step

    1. Extend InlineCallArgProducerSlotTest with the cast-stdClass concat repro above (expect 2).
    2. Fix producer→var_export ARG_SEND rewiring for current/key/pos when the operand is a Cast_Object / dynamic-prop object (same family as Regression: key() string-concat nested operand — returns true not key (ext/standard/array.c) #13829, not a new runtime/*.c path).
    3. Re-run issue repro VM+JIT; add test/compliance/cases/stdlib/array_pointer_object_current_after_next.phpt.

    Not starting #23785 in this run until #25097 is green (batch rule).

  5. PurHur commented on Jul 30, 2026

    @PurHur
    OwnerAuthor

    claim: PHP Compiler STD Lib Advanced — continuing batch (#25063 done, #25097 next, then #23785)

    Root cause found: releaseOutgoingCallArgTemps called ObjectLifetime::releaseDirectObject on by-ref SEND entries (slot null). For next($o2) that releases the CV's sole object ref → destroyForGc nulls stdClass properties → current() returns NULL while key() still shows the advanced pointer. Extra live refs (longer scripts) masked it (refCount 2→1). Fix: only releaseDirectObject when the entry has a by-value snapshot slot.

  6. PurHur commented on Jul 30, 2026

    @PurHur
    OwnerAuthor

    Merged via #25149

    Root cause: releaseOutgoingCallArgTemps called ObjectLifetime::releaseDirectObject on by-ref SEND entries (no snapshot). next($stdClass) dropped the CV’s sole object ref → destroyForGc nulled properties → current() NULL while key() advanced.

    Verification

    • Zend/VM/JIT issue repro: prev_ret=2 current=2 key='b' / next_current=2 key='b'
    • phpunit --filter testVarExportCurrentAfterNextOnCastStdClass OK
    • phpunit --filter array_pointer_object_current_after_next OK (VM+JIT)
    • AOT build smoke tier 1 OK during ci-fast
    • Not covered: full ci-fast.sh end-to-end (master already red on duplicate spine SoapHttpTransport require + related footnote heal); hygiene commits on the PR also allowlisted the intentional max_memory_limit unlimited probe and synced init-sessionsweb flash clear
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:vmVirtual machinebugSomething isn't workingimplementation-readySpec complete: repro, php-src ref, done-when — safe for workers to claimphase-4:stdlibPhase 4 – stdlib for web appsstdlib

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions