<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<!DOCTYPE bugzilla SYSTEM "https://bugs.webkit.org/page.cgi?id=bugzilla.dtd">

<bugzilla version="5.0.4.1"
          urlbase="https://bugs.webkit.org/"
          
          maintainer="admin@webkit.org"
>

    <bug>
          <bug_id>312688</bug_id>
          
          <creation_ts>2026-04-18 12:24:56 -0700</creation_ts>
          <short_desc>YARR Interpreter Omits Named Group from `indices.groups` via Unconditional Tracking-Slot Reset on Backtrack</short_desc>
          <delta_ts>2026-05-22 14:30:36 -0700</delta_ts>
          <reporter_accessible>1</reporter_accessible>
          <cclist_accessible>1</cclist_accessible>
          <classification_id>1</classification_id>
          <classification>Unclassified</classification>
          <product>WebKit</product>
          <component>JavaScriptCore</component>
          <version>WebKit Nightly Build</version>
          <rep_platform>PC</rep_platform>
          <op_sys>Linux</op_sys>
          <bug_status>RESOLVED</bug_status>
          <resolution>FIXED</resolution>
          
          
          <bug_file_loc></bug_file_loc>
          <status_whiteboard></status_whiteboard>
          <keywords>InRadar</keywords>
          <priority>P2</priority>
          <bug_severity>Minor</bug_severity>
          <target_milestone>---</target_milestone>
          
          
          <everconfirmed>1</everconfirmed>
          <reporter>parkjuny</reporter>
          <assigned_to name="Nobody">webkit-unassigned</assigned_to>
          <cc>webkit-bug-importer</cc>
          

      

      

      

          <comment_sort_order>oldest_to_newest</comment_sort_order>  
          <long_desc isprivate="0" >
    <commentid>2201875</commentid>
    <comment_count>0</comment_count>
    <who name="">parkjuny</who>
    <bug_when>2026-04-18 12:24:56 -0700</bug_when>
    <thetext>## Summary

The YARR interpreter resets the duplicate named-group tracking slot to zero on every backtrack. `RegExpMatchesArray.h` gates the `indicesGroups` property write on that slot being non-zero, so the interpreter silently omits the named-group property from `indices.groups`. The JIT retains a stale (non-zero) slot after the same backtrack and accidentally produces the correct result. The property should always be present.

## Bug

### Summary

For a duplicate named capture group that partially matches and then backtracks, `RegExpMatchesArray.h:228` writes the `&quot;x&quot;` property into `indicesGroups` only when `captureIndex &gt; 0`. The interpreter zeros the tracking slot on backtrack, producing `captureIndex = 0`, so the property is never written and `&quot;x&quot; in m.indices.groups` returns `false`. The JIT does not zero the slot on backtrack, so `captureIndex` remains non-zero and the property is written with value `undefined` — matching the correct behavior. The JIT output is the correct answer; the interpreter output is wrong.

### Detail

**Root-cause site — `RegExpMatchesArray.h:228`:**

```cpp
groups-&gt;putDirect(vm, Identifier::fromString(vm, groupName), value);         // always written — correct

if (createIndices &amp;&amp; captureIndex &gt; 0)                                        // BUG: skips write when slot is 0
    indicesGroups-&gt;putDirect(vm, Identifier::fromString(vm, groupName), indicesArray-&gt;getIndexQuickly(captureIndex));
```

`groups` always receives the property (even when `captureIndex == 0`, value is `jsUndefined()`). `indicesGroups` should mirror `groups`, but the `captureIndex &gt; 0` guard prevents it.

**Interpreter backtrack — `YarrInterpreter.cpp:173-176`, `1134-1137` — restores slot to 0:**

For a `{N}` group, the interpreter uses `matchParentheses`/`backtrackParentheses` with a `ParenthesesDisjunctionContext` save/restore mechanism. Each context saves the current tracking slot value on allocation and zeros it:

```cpp
// YarrInterpreter.cpp:173-176 — ParenthesesDisjunctionContext constructor
for (unsigned duplicateNamedGroupId : m_duplicateNamedGroups) {
    subpatternAndGroupIdBackup[...] = output[pattern-&gt;offsetForDuplicateNamedGroupId(duplicateNamedGroupId)];  // saves current value
    output[pattern-&gt;offsetForDuplicateNamedGroupId(duplicateNamedGroupId)] = 0;
}
```

On backtrack, `resetMatches` → `restoreOutput` puts back the saved value:

```cpp
// YarrInterpreter.cpp:1134-1138
void resetMatches(ByteTerm&amp; term, ParenthesesDisjunctionContext* context)
{
    unsigned firstSubpatternId = term.subpatternId();
    context-&gt;restoreOutput(output, firstSubpatternId);
}
```

Because `recordParenthesesMatch` — which writes the subpatternId to the slot — is called only after all N iterations succeed (`YarrInterpreter.cpp:1450`), the per-iteration contexts always save a slot value of 0. Backtracking therefore restores the slot to 0. After this, `subpatternIdForGroupName` returns 0 → `captureIndex = 0` → the `indicesGroups` write is skipped.

**JIT backtrack — `YarrJIT.cpp:4885-4888` — does not reset slot:**

```cpp
if (shouldRecordSubpatterns() &amp;&amp; term-&gt;containsAnyCaptures()) {
    for (unsigned subpattern = term-&gt;parentheses.subpatternId; subpattern &lt;= term-&gt;parentheses.lastSubpatternId; subpattern++)
        clearSubpattern(subpattern);  // clears capture start/end; tracking slot left stale
}
```

The stale slot keeps `captureIndex &gt; 0`, so the `indicesGroups` write proceeds and the property is present with value `undefined`.

**`subpatternIdForGroupName` — `RegExp.h:119-130` — reads the tracking slot:**

```cpp
return ovector[offsetVectorBaseForNamedCaptures() + it-&gt;value[0] - 1];  // tracking slot: 0 or subpatternId
```

### Trigger Conditions

1. Regex has the **`/d` flag**.
2. Pattern contains **duplicate named capture groups** across alternatives.
3. At least one duplicate group is **quantified `{N}` with N ≥ 2** (required for the JIT/interpreter discrepancy; with `{1}` both engines return `false`).
4. That group **partially matches then fails** (FixedCount path is entered and backtracked).
5. The overall match succeeds via an **alternative that does not define the duplicate group**.

## Version

### Reproduced Version

- `main` branch latest commit (2026/04/19): `a4390137a4039d12b4a0843e4f2b37e9ce2b6e6c`

## Reproduction Case

### Release Build

```bash
jsc poc.js                        # JIT (default): true  ← correct
jsc --useRegExpJIT=false poc.js   # Interpreter:   false ← wrong
```

Debug build produces identical output; no assertion fires as the stale slot is not validated by any ASSERT.

### PoC Code

```js
let m = /(?&lt;x&gt;a){2}z|(?&lt;x&gt;b){2}y|c/d.exec(&quot;aac&quot;);
print(&quot;x&quot; in m.indices.groups);
```

## Suggested Patch

### File: `Source/JavaScriptCore/runtime/RegExpMatchesArray.h`

#### Diff

```diff
--- a/Source/JavaScriptCore/runtime/RegExpMatchesArray.h
+++ b/Source/JavaScriptCore/runtime/RegExpMatchesArray.h
@@ -225,8 +225,10 @@ ALWAYS_INLINE JSArray* createRegExpMatchesArray(
                     value = jsUndefined();
                 groups-&gt;putDirect(vm, Identifier::fromString(vm, groupName), value);
 
-                if (createIndices &amp;&amp; captureIndex &gt; 0)
-                    indicesGroups-&gt;putDirect(vm, Identifier::fromString(vm, groupName), indicesArray-&gt;getIndexQuickly(captureIndex));
+                if (createIndices) {
+                    JSValue indicesValue = captureIndex &gt; 0 ? indicesArray-&gt;getIndexQuickly(captureIndex) : jsUndefined();
+                    indicesGroups-&gt;putDirect(vm, Identifier::fromString(vm, groupName), indicesValue);
+                }
             }
         }
     }
```

This mirrors how `groups` is built (lines 221–226) and ensures `indicesGroups` always contains a property for every named capture group, with value `undefined` when the group did not participate.

Note: `YarrJIT.cpp:4885-4888` should separately add `storeDuplicateNamedGroupSubpatternId(duplicateNamedGroupId, 0)` in the FixedCount backtrack loop (matching the `ParenthesesSubpatternOnceBegin` backtrack at lines 4772–4778) to eliminate the stale slot — but that is an independent state-hygiene fix and does not affect observable behavior once `RegExpMatchesArray.h` is corrected.

### Credit Information

Reporter credit: Junyoung Park (@candymate) of KAIST Hacking Lab</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>2202034</commentid>
    <comment_count>1</comment_count>
    <who name="Radar WebKit Bug Importer">webkit-bug-importer</who>
    <bug_when>2026-04-19 13:22:15 -0700</bug_when>
    <thetext>&lt;rdar://problem/175122294&gt;</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>2206319</commentid>
    <comment_count>2</comment_count>
    <who name="Kai Tamkun">k_tamkun</who>
    <bug_when>2026-04-30 10:52:52 -0700</bug_when>
    <thetext>Pull request: https://github.com/WebKit/WebKit/pull/63982</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>2213381</commentid>
    <comment_count>3</comment_count>
    <who name="EWS">ews-feeder</who>
    <bug_when>2026-05-22 14:30:35 -0700</bug_when>
    <thetext>Committed 313762@main (e1cdfab158f3): &lt;https://commits.webkit.org/313762@main&gt;

Reviewed commits have been landed. Closing PR #63982 and removing active labels.</thetext>
  </long_desc>
      
      

    </bug>

</bugzilla>