Skip to content

VM captures root node when a captured ref returns via non-greedy optional skip #381

@zharinov

Description

@zharinov

Summary

  • A captured ref can fabricate a node when the callee returns without matching anything.
  • Repro via CLI: on source returns the root node as .
  • Suspected root cause: unconditionally writes even when the callee matched nothing.

Repro

Run:

{"x":{"kind":"program","text":"foo","span":[0, 3]}}

Observed output:

Trace also shows the callee taking the epsilon skip path and then the caller executing an epsilon capture:

_ObjWrap:
00 ε [Obj] 02
⬥ Obj
02 Trampoline 03
▶ (Q)

Q:

08 (A) 06 : 09
▶ (A)

A:

06 ε 12, 16

12 ◀ (A)

Q:
09 ε [Node Set(M0)] 11
⬥ Node
⬥ Set "x"
11 ◀ (Q)

_ObjWrap:

03 ε [EndObj] 05
⬥ EndObj
05 ◀ _ObjWrap ◼

{
"x": {
"kind": "program",
"text": "foo",
"span": [0, 3]
}
}

And the lowered bytecode contains:

Why this looks wrong

is a non-greedy optional, so is allowed to return without matching an .
In that case, should not fabricate a node that never matched.

Regardless of whether the intended behavior is null / absent / no match, returning the root node is incorrect.

Suspected root cause

In , does:

  • set
  • then restore tree depth

That seems correct when the callee actually matched something, but incorrect when the callee returned through a zero-match path (like a skipped optional).

A fix likely needs to distinguish:

  • return after a real match
  • return after a zero-width / skipped path

and avoid manufacturing in the latter case.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions