Skip to content

Language: JIT get_class/gettype($this) in Closure empty; spl_object_id segfaults (Zend/zend_closures.c) #27163

Description

@PurHur

Category

Language · php-src-strict · JIT bound Closure · pillar 4

Problem

Inside an auto-bound Closure created in an instance method, Zend/VM correctly see $this. JIT returns an empty string from get_class($this) / gettype($this) (exit 0), and segfaults on spl_object_id($this).

Typed Closure return (function (): string { return get_class($this); }) can mask the empty-string case; untyped is the failing form. Property reads on $this inside the same Closure shape work.

Verified 2026-08-03 at e0e5e10c9 (Docker Zend 8.2.32), 10/10 empty on get_class:

Repro Zend 8.2.32 VM JIT
get_class($this) in Closure 'A' 'A' '' (empty)
gettype($this) in Closure 'object' 'object' '' (empty)
spl_object_id($this) in Closure >0 >0 segfault
$this->name in Closure 'A' 'A' 'A'

php-src reference

PHP implementation target

  • lib/JIT/ Closure invoke / bound-object materialization when passing $this to builtins
  • Shared path with Closure call frames — PHP-in-PHP; no new runtime/*.c

Repro

./script/docker-exec.sh -- bash -lc 'cat > /tmp/gc_closure.php << "PHP"
<?php
class A {
  public function f() {
    $c = function () { return get_class($this); };
    return $c();
  }
}
var_export((new A)->f());
echo "\n";
PHP
php /tmp/gc_closure.php
php bin/vm.php /tmp/gc_closure.php
php bin/jit.php /tmp/gc_closure.php'

Expect 'A' on all three. Optional: swap body for gettype($this) / spl_object_id($this).

Done when

  • bin/jit.php prints 'A' / 'object' / positive id matching Zend/VM
  • No segfault on spl_object_id($this) in bound Closure (repeat ≥5)
  • Compliance or test/repro/ guard under php-src-strict
  • No php-compiler-strict shortcut

Activity

  1. added
    bugSomething isn't working
    implementation-readySpec complete: repro, php-src ref, done-when — safe for workers to claim
    on Aug 3, 2026
  2. PurHur commented on Aug 3, 2026

    @PurHur
    OwnerAuthor

    Maintainer pre-flight (2026-08-03 @ e0e5e10c9)

    Recorded here because #10533 comments are locked (2500+).

    Gate Result
    bootstrap-inventory --check green OK 6855/6855
    bootstrap-selfhost-link green
    release-readiness --json user_release_ready: yes (north-star5-fast + helper-prelink + cold-build + spine + gen0-functional all ok)
    gen-0 provenance fresh (e0e5e10c9, 0 lowering commits)

    Sibling filings this run: #27164 (preg_* non-string array), #27165 (str_replace/str_ireplace non-string array subject). PR #27151 held (AOT still segfaults on #27108).

  3. PurHur commented on Aug 3, 2026

    @PurHur
    OwnerAuthor

    claim: lane-B implementer — starting this run

  4. PurHur commented on Aug 3, 2026

    @PurHur
    OwnerAuthor

    Handoff (lane-B) — WIP #27176

    Trust snapshot (this host, before claim)

    Gate Result
    bootstrap-inventory --check doc drift vs live (workspace); release-readiness later reported OK 6859/6859
    release-readiness --json user_release_ready: no — north-star5-fast fail (gen-0 manifest/driver mismatch #8713); gen-0 seed stale (+4 lowering commits since e0e5e10c9)
    loadJitContext() on master red — NestedJIT Strptime TYPE_SMALLER 134×132; without helper cache also stdout-null in JitStreamLibcHandleKernel

    Progress on #27163

    Shipped on agent/issue-27163-jit-closure-this-get-class / PR #27176:

    1. JIT Context init unblocked (Strptime call-site deferral, libc stdio before resolve, NestedJIT is_numeric skips stream is_resource, bitwise-not coerce).
    2. Closure $this bind uses findThisVariable(); object snapshot stays TYPE_OBJECT; insert restored after nested closure compile.
    3. IR now calls _closure__1(%__object__* %0) (method $this) instead of null.

    Blocker

    php bin/jit.php -l test/repro/issue_27163_jit_closure_this_get_class.php   # exit 0
    php bin/vm.php test/repro/issue_27163_jit_closure_this_get_class.php       # 'A'
    php bin/jit.php test/repro/issue_27163_jit_closure_this_get_class.php      # SIGSEGV 139

    Even function () { return "A"; } inside the method SIGSEGVs under MCJIT execute — not unique to get_class. Next: debug execute of _closure__1 (not compile).

    Do not merge until execute matches Zend/VM. Not closing.

  5. PurHur commented on Aug 3, 2026

    @PurHur
    OwnerAuthor

    claim: lane-B implementer — continuing this run (resume #27176; next: MCJIT execute SIGSEGV on bound Closure $this)

  6. PurHur commented on Aug 3, 2026

    @PurHur
    OwnerAuthor

    claim: lane-B implementer — continuing this run (resume #27176; debug MCJIT SIGSEGV on bound Closure invoke)

  7. PurHur commented on Aug 3, 2026

    @PurHur
    OwnerAuthor

    Merged #27176.

    Root cause: JitMcjitEmbed::padPropertylessUserClassesForMcjit used a one-level brace regex, so propertyless classes whose methods nest Closures never received the #4954 MCJIT pad → bare MCJIT SIGSEGV on get_class/gettype/spl_object_id($this).

    Fix: brace-balanced class-body scan + Closure $this param only via closureBodyUsesThis.

    Verified:

    php bin/jit.php test/repro/issue_27163_jit_closure_this_get_class.php  # 'A' (5/5)
    php bin/jit.php test/repro/issue_27163_jit_closure_this_gettype.php    # 'object'
    php bin/jit.php test/repro/issue_27163_jit_closure_this_spl_object_id.php  # 1
    php bin/vm.php test/repro/issue_27163_jit_closure_this_get_class.php   # 'A'
    vendor/bin/phpunit --filter testPadsPropertylessClassContainingNestedClosure  # OK
    
  8. PurHur commented on Aug 3, 2026

    @PurHur
    OwnerAuthor

    Merged — verification (lane-B)

    PR #27176 merged.

    Root cause: JitMcjitEmbed used a one-level brace regex for the MCJIT empty-class pad (#4954). Methods containing nested Closures never matched, so propertyless classes ran without the pad and MCJIT segfaulted. Secondary: Closure func->class (for self::class) was treated as an instance-method $this LLVM param.

    Fix: brace-balanced class scan + skip FLAG_CLOSURE in instanceMethodUsesThis.

    Re-verified this run (Docker):

    php bin/jit.php test/repro/issue_27163_jit_closure_this_get_class.php  # 'A'
    php bin/jit.php test/repro/issue_27163_jit_closure_this_gettype.php     # 'object'
    php bin/jit.php test/repro/issue_27163_jit_closure_this_spl_object_id.php  # 1 (×5)
    php bin/vm.php  test/repro/issue_27163_jit_closure_this_get_class.php  # 'A'
    

    Trust snapshot earlier this run: user_release_ready: no (north-star5-fast / gen-0 stale) — unrelated to this language fix; not covered here.

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-2:languagePhase 2 – language features

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions