[php-src] Issue #21359: JIT Bug: EXC_BAD_ACCESS on ARM64 with property hooks + Fibers

[email protected] (azjezz)
Newsgroups php.bugs
Message-ID <[email protected]>
Issue: https://github.com/php/php-src/issues/21359
Author: azjezz

### Description

The following code:

```php
<?php
/**
 * JIT bus error reproduction — PHP 8.5.2, arm64, opcache.jit=tracing
 *
 * Run:
 *   php -dopcache.enable_cli=1 -dopcache.jit=tracing -dopcache.jit_buffer_size=64M jit-repro.php
 *
 * Requires: azjezz/psl 5.1.0
 *
 * Crashes ~40% of the time on arm64 with JIT tracing mode.
 * The JIT miscompiles ZEND_FETCH_OBJ_FUNC_ARG on a property hook,
 * returning `call_leave_op` (a data-segment pointer in __DATA_CONST)
 * as the next opcode handler. ARM64 W^X policy prevents executing
 * data pages → EXC_BAD_ACCESS (code=2).
 */
declare(strict_types=1);

require __DIR__ . '/../../vendor/autoload.php';

use Psl\Async;
use Psl\IO;
use Psl\TLS;

enum Kind: int { case A = 1; }

final class Rec {
    public Kind $kind { get => Kind::A; }
    public function __construct(public readonly string $name) {}
}

const Q = "\x00\x01\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07example\x03com\x00\x00\x01\x00\x01";

function dnsq(string $srv, string $name): array {
    $c = TLS\connect($srv, 853);
    $c->writeAll(pack('n', strlen(Q)) . Q);
    $c->readAll(unpack('n', $c->readAll(2))[1]);
    $c->close();
    return [new Rec($name)];
}

function race(string $n): array {
    return Async\first(array_map(fn($srv) => Async\run(fn() => dnsq($srv, $n)), ['1.1.1.1', '8.8.8.8']));
}

$tasks = [];
foreach (['a.com', 'b.com', 'c.com'] as $d) {
    $tasks[$d] = Async\run(static fn() => Async\concurrently([
        0 => fn() => race($d), 1 => fn() => race($d), 2 => fn() => race($d),
    ]));
}
foreach (Async\all($tasks) as $responses) {
    foreach ($responses[0] as $r) { IO\write_line('[%s]: %s', $r->name, $r->kind->name); }
    foreach ($responses[1] as $r) { IO\write_line('[%s]: %s', $r->name, $r->kind->name); }
    foreach ($responses[2] as $r) { IO\write_line('[%s]: %s', $r->name, $r->kind->name); }
}
echo "OK\n";
```

Resulted in this output:
```
[a.com]: A
[a.com]: A
[a.com]: A
[b.com]: A
[b.com]: A
[b.com]: A
[c.com]: A
[1]    8610 bus error  php -dopcache.enable_cli=1 -dopcache.jit=tracing -dopcache.jit_buffer_size=64
```

But I expected this output instead:
```
[a.com]: A
[a.com]: A
[a.com]: A
[b.com]: A
[b.com]: A
[b.com]: A
[c.com]: A
[c.com]: A
[c.com]: A
OK
```

---

**Crash**: `EXC_BAD_ACCESS` (code=2, address=0x101409048) at `call_leave_op`
**Crash site**: Line 36 - `IO\write_line('[%s]: %s', $r->name, $r->kind->name)` where `$r->kind` is a property hook (`public Kind $kind { get => Kind::A; }`)

Stack trace:
```
➜  leaf git:(main) ✗ lldb php examples/dns/jit-repro.php
(lldb) target create "php"
Current executable set to '/Users/azjezz/.phpbrew/php/8.5.2/bin/php' (arm64).
(lldb) settings set -- target.run-args  "examples/dns/jit-repro.php"
(lldb) run
Process 14499 launched: '/Users/azjezz/.phpbrew/php/8.5.2/bin/php' (arm64)
[a.com]: A
[a.com]: A
[a.com]: A
[b.com]: A
[b.com]: A
[b.com]: A
[c.com]: A
[c.com]: A
[c.com]: A
OK
Process 14499 exited with status = 0 (0x00000000)
(lldb) run
Process 14550 launched: '/Users/azjezz/.phpbrew/php/8.5.2/bin/php' (arm64)
[a.com]: A
[a.com]: A
[a.com]: A
[b.com]: A
[b.com]: A
[b.com]: A
[c.com]: A
[c.com]: A
Process 14550 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=2, address=0x101409048)
    frame #0: 0x0000000101409048 php`call_leave_op
php`call_leave_op:
->  0x101409048 <+0>:  <unknown>
    0x10140904c <+4>:  udf    #0x1
    0x101409050 <+8>:  udf    #0x0
    0x101409054 <+12>: udf    #0x0
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=2, address=0x101409048)
  * frame #0: 0x0000000101409048 php`call_leave_op
    frame #1: 0x000000010061cf44 php`execute_ex(ex=<unavailable>) at zend_vm_execute.h:116172:12 [opt]
    frame #2: 0x000000010062f32c php`zend_execute.cold.1 at zend_vm_execute.h:121884:2 [opt]
    frame #3: 0x00000001004eab18 php`zend_execute(op_array=<unavailable>, return_value=<unavailable>) at zend_vm_execute.h:121867:48 [opt]
    frame #4: 0x00000001005c5830 php`zend_execute_script(type=8, retval=0x0000000000000000, file_handle=0x000000016fdfe140) at zend.c:1977:3 [opt]
    frame #5: 0x0000000100437d50 php`php_execute_script_ex(primary_file=0x000000016fdfe140, retval=0x0000000000000000) at main.c:2641:13 [opt]
    frame #6: 0x0000000100437eac php`php_execute_script(primary_file=<unavailable>) at main.c:2681:9 [opt] [artificial]
    frame #7: 0x00000001005c7e0c php`do_cli(argc=<unavailable>, argv=<unavailable>) at php_cli.c:951:5 [opt]
    frame #8: 0x00000001005c6af8 php`main(argc=<unavailable>, argv=0x0000000102f9c9b0) at php_cli.c:1362:18 [opt]
    frame #9: 0x000000019b551d54 dyld`start + 7184
(lldb)
```

### PHP Version

```plain
PHP 8.5.2 (cli) (built: Feb 11 2026 07:55:48) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.5.2, Copyright (c) Zend Technologies
    with Zend OPcache v8.5.2, Copyright (c), by Zend Technologies
```

### Operating System

MacOS / ARM64
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.