Skip to content

AOT: str_replace() prints "Array" instead of the replaced string #23912

Closed

Description

@PurHur

Summary

str_replace() returns something that stringifies to Array in an AOT binary, instead of the
replaced string. Silent wrong output — runs to completion, exit code 0.

VM is unaffected.

Reproducer

<?php $s = "hello world"; echo str_replace("o", "0", $s), "\n";
zend: hell0 w0rld
aot : Array

All three arguments are scalars — string $search, string $replace, string $subject — so the
array-returning overload (array $search) is not in play. Zend returns a string here.

Printing Array specifically suggests the result is coming back as a PHP array rather than a
string, i.e. the return value's type is wrong rather than its contents.

Blast radius

Explains two standing AOT differential failures:

  • c10_builtin.php — str_replace($r['a'],$r['b'],'xy!'), whose first line currently prints empty
  • e15_str_fns.php — its str_replace("o","0",$s) line

e15 also hits #23913 (str_pad segfault), so it will need both fixed before it passes.

Related but distinct

Several closed issues cover str_replace() argument handling — null coercion (#20173, #19755,
#21189), and string $search + array $replace returning the first element (#22827). This is a
different failure: three plain string arguments, wrong return type, AOT only.

Environment: php-compiler:22.04-dev, PHP 8.2.32, LLVM 9, php bin/compile.php -o <bin> <file>,
master 055356698. Reproducer at build/micro/strfn/p5_strreplace.php.

Activity

  1. PurHur commented on Jul 27, 2026

    @PurHur
    OwnerAuthor

    Correction: the str_pad segfault referenced above as "#23913" is actually #23911. e15_str_fns.php needs both #23911 and this one fixed before it passes.

  2. PurHur commented on Jul 27, 2026

    @PurHur
    OwnerAuthor

    claim: Lane C implementer — starting this run (after closing #23897 as already-fixed on master)

  3. added
    bugSomething isn't working
    phase-3:aotPhase 3 – AOT deployment
    implementation-readySpec complete: repro, php-src ref, done-when — safe for workers to claim
    on Jul 27, 2026
  4. PurHur commented on Jul 27, 2026

    @PurHur
    OwnerAuthor

    claim: Lane C implementer — continuing in-flight branch agent/issue-23912-aot-str-replace (verify + merge this run)

  5. PurHur commented on Jul 27, 2026

    @PurHur
    OwnerAuthor

    claim: Lane C implementer — continuing agent/issue-23912-aot-str-replace (verify + merge this run)

  6. PurHur commented on Jul 27, 2026

    @PurHur
    OwnerAuthor

    Merged via #23940

    Verification (./script/docker-exec.sh --bind):

    • VM + AOT repro test/repro/issue_23912_aot_str_replace.php → hell0 w0rld
    • Extra AOT: literal/variable subject, ll/l/empty search
    • vendor/bin/phpunit --filter StrReplaceRuntimeShrinkTest → OK

    Fix: NestedJIT helper walks the original subject (no suffix reassign / int tally); scalars route through StringStrReplace.

  7. PurHur commented on Aug 19, 2026

    @PurHur
    OwnerAuthor

    claim: implementer-worker-lane-b — starting this run (lane overflow: north-star queue closed; aot_str_replace_scalar_subject regressed — empty output)

  8. PurHur commented on Aug 19, 2026

    @PurHur
    OwnerAuthor

    status: repro still red after investigation this run (lane overflow — north-star queue closed).

    Verified on master @ c3cf23f

    • ./script/aot-smoke.sh → 8/8 green
    • ./script/phpunit.sh --filter aot_str_replace_scalar_subject → FAIL (expected hell0 w0rld\nhell0 w0rld\n, actual empty stdout)
    • ./script/differential-sweep.sh --aot --dir …/c10_builtin.php → DIFF (zend zw!\nzw, aot ~zw — str_replace line missing + max wrong)
    • ./script/differential-sweep.sh --aot --dir …/c11_strcmp.php (str_pad/substr) → SIGSEGV on str_pad path isolated

    VM/JIT: StrReplaceRuntimeShrinkTest green; php bin/vm.php prints correct output.

    AOT isolate

    ./script/docker-exec.sh -- bash -lc 'tmp=$(mktemp -d); echo "<?php var_dump(str_replace(\"o\",\"0\",\"hello world\"));" > "$tmp/t.php"; php bin/compile.php -o "$tmp/t.bin" "$tmp/t.php" && "$tmp/t.bin"'
    # zend: string(11) "hell0 w0rld"
    # aot:  string(0) ""

    At 8893fab the same probe SIGSEGV (rc=139); current master returns empty string (rc=0) — regression changed shape but never matched Zend.

    Hypothesis (not yet proven)

    HelperRuntimeCache::USER_SCRIPT_INLINE_ONLY_LOGICALS lists StrReplaceJitHelper::replaceArgv, but JitVmHelperLink::runNestedHelperCompile clears PHP_COMPILER_AOT_USER_SCRIPT (#15407), so tryProvide() may bind stale unit.o for replaceArgv during the NestedJIT window (#23912 comment). Partial attempts this run (inline-only guard during NestedJitCompileScope, skipHelperRuntimeCache on bridges, defer Context pre-init) did not fix the repro.

    Next step

    1. Confirm whether phpc_str_replace bridge calls NestedJIT-compiled replaceArgv or a stale extern (module verify / symbol trace on user AOT TU).
    2. Add StrPadJitHelper::padArgv to inline-only list (peer AOT: str_pad() segfaults — three-argument literal call is enough #23911) once replaceArgv path is understood — str_pad-only probe SIGSEGV rc=139.
    3. Re-run ./script/differential-sweep.sh --aot batch after fix; regenerate baseline if c10/c11 green.

    Did not merge — issue repro still fails.

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:compilerCompiler / CFG / JITbugSomething isn't workingimplementation-readySpec complete: repro, php-src ref, done-when — safe for workers to claimphase-3:aotPhase 3 – AOT deployment

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions