[php-src] PR #25298: JIT: Fix FETCH_DIM_IS ignoring leading-numeric string offsets
| From: | jorgsowa | Date: | Sat, 07 Nov 2026 22:44:09 +0000 |
| Subject: | [php-src] PR #25298: JIT: Fix FETCH_DIM_IS ignoring leading-numeric string offsets | ||
| Groups: | php.git-pulls | ||
| Request: | Send a blank email to git-pulls+get-40416@lists.php.net to get a copy of this message | ||
Pull Request: https://github.com/php/php-src/pull/25298
Author: jorgsowa
For a leading-numeric string offset such as "1x", the VM warns Illegal string offset
"1x" and uses the leading number, also under ??:
```
function f(string $s) { return $s["1x"] ?? "default"; }
var_dump(f("abc"));
// VM: Warning: Illegal string offset "1x" + string(1) "b"
// JIT: string(7) "default", no warning
```
This is the behaviour specified by the Saner Numeric Strings RFC and pinned in
Zend/tests/offsets/string_container_offset_behaviour.phpt. The RFC change (b2248789ed2) updated the
JIT's R helper but not zend_jit_fetch_dim_str_is_helper, which kept a strict parse and silently
returned null. The IS helper now parses the offset like the VM, warns on trailing data, and holds a
reference to the string in case the error handler releases it.
zend_may_throw_ex() did not count this warning for FETCH_DIM_IS either. With ?? this
has no effect, because COALESCE always consumes the result. The inner fetch of a nested
isset($s["1x"][0]) / empty() is different:
- DCE removed it when the result was unused, losing the warning even without JIT.
- Function JIT skipped saving the call chain around it, leaking already-sent arguments when the
error handler throws.
| Code | VM (no opcache) | opcache, JIT off | opcache, function JIT |
|---|---|---|---|
| $s["1x"] ?? "d" | warning, "b" | warning,
"b" | before: "d", no warning<br>after: warning,
"b" |
| $x = isset($s["1x"][0]); (result used) | warning, true |
warning, true | before: false, no warning<br>after: warning,
true |
| isset($s["1x"][0]); (result unused) | warning | before: no
warning<br>after: warning | before: no warning<br>after: warning |
| consume($t, isset($s["1x"][0])) with a throwing error handler | exception |
exception | before: exception plus a leaked $t<br>after: exception |