Skip to content

Stdlib: nested substr(sprintf()) — inner string lost, wrong slice (ext/standard/string.c) #10673

Closed

Description

@PurHur

Problem

When substr() takes a nested sprintf() call as its first argument, the inner formatted string is not passed correctly. The outer substr sees a corrupted subject and returns the wrong tail.

Breaks common file-mode formatting: substr(sprintf('%o', fileperms($f)), -4) should yield 0644, not -4.

./script/docker-exec.sh -- bash -lc 'php test/repro/maintainer_gap_substr_sprintf_nested.php; php bin/vm.php test/repro/maintainer_gap_substr_sprintf_nested.php'
Runtime substr(sprintf('%o', 33188), -4)
Zend 0644
VM -4

Isolated calls work on both runtimes:

sprintf('%o', 33188); // 100644
substr('100644', -4); // 0644

Related family: #9547 (nested array-return builtins → null). This repro is string-return nesting (sprintf → substr).

php-src reference

  • ext/standard/string.c — php_substr, php_sprintf
  • Zend/zend_execute.c — nested call temporaries

PHP implementation target

Done when (php-src-strict)

  • substr(sprintf('%o', $mode), -4) matches Zend for typical fileperms() values
  • Other nested string builtins (trim(str_replace(...)), etc.) spot-checked
  • Targeted compliance guard + test/repro/maintainer_gap_substr_sprintf_nested.php

Activity

  1. added
    enhancementNew feature or request
    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 Jun 23, 2026
  2. PurHur commented on Jun 23, 2026

    @PurHur
    OwnerAuthor

    claim: worker-lane-c — starting this run (VM nested builtin call bridge; not ext/standard-only scope)

  3. PurHur commented on Jun 23, 2026

    @PurHur
    OwnerAuthor

    Merged via #10685.

    Root cause: php-cfg lowers substr(sprintf(...), -N) as adjacent producers [sprintf, UnaryMinus, substr]. isSiblingMultiArgFuncCallProducer rejected sprintf when a unary prelude sat between it and substr, so the inner sprintf return never wired to substr arg 0.

    Fix: allow unary-prelude siblings and map distance-2 FuncCall producers to consumer arg 0 in lib/Compiler.php.

    Verification (green):

    ./script/docker-exec.sh -- bash -lc 'php bin/vm.php test/repro/maintainer_gap_substr_sprintf_nested.php'
    # 0644
    ./script/docker-exec.sh -- bash -lc 'php vendor/bin/phpunit --filter nested_substr_sprintf'
    # OK
  4. PurHur commented on Jul 4, 2026

    @PurHur
    OwnerAuthor

    claim: cursor-agent-lane-b — regression re-open: substr(sprintf(...), -N) misbind on master (InlineCallArgProducerSlotTest red)

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 machineenhancementNew feature or requestimplementation-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