Fix three defects in Psych::Parser#parse - #817

Merged
hsbt merged 4 commits into
masterfrom
claude/kind-burnell-81c502
Aug 21, 2026
Merged

Fix three defects in Psych::Parser#parse#817
hsbt merged 4 commits into
masterfrom
claude/kind-burnell-81c502

Conversation

@hsbt

@hsbthsbt commented Aug 20, 2026

Copy link
Copy Markdown
Member

An exception raised in Psych::Handler#event_location never reaches the caller. Every branch of the event switch overwrites the rb_protect state, so only the second result is tested.

Calling Psych::Parser#parse from inside a handler callback reinitialises the parser the outer loop is still driving. The outer loop then reads the inner document's freed buffer, or resumes with no input and trips libyaml's read_handler assertion, aborting the process. parse now raises Psych::Exception when the parser is already parsing, and rb_ensure clears the flag so the parser stays reusable.

The loop also spun forever on an event with no type, which libyaml keeps returning once stream_end_produced is set.

None of this is reachable through Psych.load and friends, which build a fresh parser each time. The libfyaml backend shared all three and is fixed alongside.

hsbtand others added 3 commits August 19, 2026 10:59
Every case of the event switch overwrote the rb_protect state, so only
the second result was ever tested and the state from the event_location
call was dropped. The parse then returned normally with the exception
silently discarded.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Once libyaml has set stream_end_produced, every further
yaml_parser_parse succeeds with a zeroed event, so YAML_STREAM_END_EVENT
can no longer be reached and the loop calls handler#empty forever at
full CPU. A parser left in that state should terminate instead.
No test covers this directly because the loop only reaches it through a
reentrant parse, which the parser now rejects outright.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The parser calls back into Ruby for every event, so a handler can call
#parse again on the same object. That reinitialised the parser and
re-pointed it at new input while the outer loop was still driving it,
which left the outer loop reading the inner document's freed buffer, or
resuming on a parser with no input at all and tripping libyaml's
read_handler assertion.
Psych::Exception is used so the caller can rescue it, and the in-use
flag is cleared with rb_ensure so a parser stays reusable afterwards.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CopilotAI lite review requested due to automatic review settings August 20, 2026 03:12

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens Psych::Parser#parse against three low-level correctness defects in both the libyaml and libfyaml backends: (1) exceptions raised from Handler#event_location not reliably propagating, (2) unsafe reentrant parse calls from within callbacks corrupting parser state, and (3) a potential infinite loop when the backend returns an event with no type.

Changes:

  • Ensure exceptions from event_location are immediately surfaced (and associated event memory is released before raising).
  • Add a per-parser “in use” guard to reject reentrant parse calls, and clear it via rb_ensure to keep parsers reusable.
  • Stop the parse loop when the backend returns a “no event” type to avoid spinning indefinitely.
  • Add tests for exception propagation and reentrancy behavior.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

FileDescription
test/psych/test_parser.rbAdds regression tests covering event_location exception propagation and reentrant parse rejection/reuse.
ext/psych/psych_parser.cWraps yaml_parser_t in a struct with an in-use flag; fixes exception propagation and loop termination edge cases.
ext/psych/psych_parser_fy.cMirrors the same correctness fixes for the libfyaml backend (event_location propagation, non-reentrancy, loop termination).

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines 91 to 98
static VALUE allocate(VALUE klass)
{
yaml_parser_t * parser;
VALUE obj = TypedData_Make_Struct(klass, yaml_parser_t, &psych_parser_type, parser);
psych_parser_t * parser;
VALUE obj = TypedData_Make_Struct(klass, psych_parser_t, &psych_parser_type, parser);

yaml_parser_initialize(parser);
yaml_parser_initialize(&parser->yaml_parser);

return obj;
Comment on lines 39 to 49
typedef struct {
struct fy_parser *fyp;
size_t mark_line;
size_t mark_column;
size_t mark_index;
/* The parser calls back into Ruby for every event, so a handler can call
* Psych::Parser#parse again on the same object. parse() destroys and
* recreates fyp, which would pull the parser out from under the loop still
* driving it, so keep a flag to reject a reentrant call. */
int parsing;
} psych_fy_parser_t;
@hsbt
hsbt merged commit 182f803 into masterAug 21, 2026
234 of 235 checks passed
@hsbt
hsbt deleted the claude/kind-burnell-81c502 branch August 21, 2026 01:05
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@hsbt
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Fix three defects in Psych::Parser#parse - #817

Merged
hsbt merged 4 commits into
masterfrom
claude/kind-burnell-81c502
Aug 21, 2026
Merged

Fix three defects in Psych::Parser#parse#817
hsbt merged 4 commits into
masterfrom
claude/kind-burnell-81c502

Conversation

@hsbt

@hsbthsbt commented Aug 20, 2026

Copy link
Copy Markdown
Member

An exception raised in Psych::Handler#event_location never reaches the caller. Every branch of the event switch overwrites the rb_protect state, so only the second result is tested.

Calling Psych::Parser#parse from inside a handler callback reinitialises the parser the outer loop is still driving. The outer loop then reads the inner document's freed buffer, or resumes with no input and trips libyaml's read_handler assertion, aborting the process. parse now raises Psych::Exception when the parser is already parsing, and rb_ensure clears the flag so the parser stays reusable.

The loop also spun forever on an event with no type, which libyaml keeps returning once stream_end_produced is set.

None of this is reachable through Psych.load and friends, which build a fresh parser each time. The libfyaml backend shared all three and is fixed alongside.

hsbtand others added 3 commits August 19, 2026 10:59
Every case of the event switch overwrote the rb_protect state, so only
the second result was ever tested and the state from the event_location
call was dropped. The parse then returned normally with the exception
silently discarded.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Once libyaml has set stream_end_produced, every further
yaml_parser_parse succeeds with a zeroed event, so YAML_STREAM_END_EVENT
can no longer be reached and the loop calls handler#empty forever at
full CPU. A parser left in that state should terminate instead.
No test covers this directly because the loop only reaches it through a
reentrant parse, which the parser now rejects outright.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The parser calls back into Ruby for every event, so a handler can call
#parse again on the same object. That reinitialised the parser and
re-pointed it at new input while the outer loop was still driving it,
which left the outer loop reading the inner document's freed buffer, or
resuming on a parser with no input at all and tripping libyaml's
read_handler assertion.
Psych::Exception is used so the caller can rescue it, and the in-use
flag is cleared with rb_ensure so a parser stays reusable afterwards.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CopilotAI lite review requested due to automatic review settings August 20, 2026 03:12

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens Psych::Parser#parse against three low-level correctness defects in both the libyaml and libfyaml backends: (1) exceptions raised from Handler#event_location not reliably propagating, (2) unsafe reentrant parse calls from within callbacks corrupting parser state, and (3) a potential infinite loop when the backend returns an event with no type.

Changes:

  • Ensure exceptions from event_location are immediately surfaced (and associated event memory is released before raising).
  • Add a per-parser “in use” guard to reject reentrant parse calls, and clear it via rb_ensure to keep parsers reusable.
  • Stop the parse loop when the backend returns a “no event” type to avoid spinning indefinitely.
  • Add tests for exception propagation and reentrancy behavior.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

FileDescription
test/psych/test_parser.rbAdds regression tests covering event_location exception propagation and reentrant parse rejection/reuse.
ext/psych/psych_parser.cWraps yaml_parser_t in a struct with an in-use flag; fixes exception propagation and loop termination edge cases.
ext/psych/psych_parser_fy.cMirrors the same correctness fixes for the libfyaml backend (event_location propagation, non-reentrancy, loop termination).

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines 91 to 98
static VALUE allocate(VALUE klass)
{
yaml_parser_t * parser;
VALUE obj = TypedData_Make_Struct(klass, yaml_parser_t, &psych_parser_type, parser);
psych_parser_t * parser;
VALUE obj = TypedData_Make_Struct(klass, psych_parser_t, &psych_parser_type, parser);

yaml_parser_initialize(parser);
yaml_parser_initialize(&parser->yaml_parser);

return obj;
Comment on lines 39 to 49
typedef struct {
struct fy_parser *fyp;
size_t mark_line;
size_t mark_column;
size_t mark_index;
/* The parser calls back into Ruby for every event, so a handler can call
* Psych::Parser#parse again on the same object. parse() destroys and
* recreates fyp, which would pull the parser out from under the loop still
* driving it, so keep a flag to reject a reentrant call. */
int parsing;
} psych_fy_parser_t;
@hsbt
hsbt merged commit 182f803 into masterAug 21, 2026
234 of 235 checks passed
@hsbt
hsbt deleted the claude/kind-burnell-81c502 branch August 21, 2026 01:05
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@hsbt
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix three defects in Psych::Parser#parse - #817

Merged
hsbt merged 4 commits into
masterfrom
claude/kind-burnell-81c502
Aug 21, 2026
Merged

Fix three defects in Psych::Parser#parse#817
hsbt merged 4 commits into
masterfrom
claude/kind-burnell-81c502

Conversation

@hsbt

@hsbthsbt commented Aug 20, 2026

Copy link
Copy Markdown
Member

An exception raised in Psych::Handler#event_location never reaches the caller. Every branch of the event switch overwrites the rb_protect state, so only the second result is tested.

Calling Psych::Parser#parse from inside a handler callback reinitialises the parser the outer loop is still driving. The outer loop then reads the inner document's freed buffer, or resumes with no input and trips libyaml's read_handler assertion, aborting the process. parse now raises Psych::Exception when the parser is already parsing, and rb_ensure clears the flag so the parser stays reusable.

The loop also spun forever on an event with no type, which libyaml keeps returning once stream_end_produced is set.

None of this is reachable through Psych.load and friends, which build a fresh parser each time. The libfyaml backend shared all three and is fixed alongside.

hsbtand others added 3 commits August 19, 2026 10:59
Every case of the event switch overwrote the rb_protect state, so only
the second result was ever tested and the state from the event_location
call was dropped. The parse then returned normally with the exception
silently discarded.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Once libyaml has set stream_end_produced, every further
yaml_parser_parse succeeds with a zeroed event, so YAML_STREAM_END_EVENT
can no longer be reached and the loop calls handler#empty forever at
full CPU. A parser left in that state should terminate instead.
No test covers this directly because the loop only reaches it through a
reentrant parse, which the parser now rejects outright.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The parser calls back into Ruby for every event, so a handler can call
#parse again on the same object. That reinitialised the parser and
re-pointed it at new input while the outer loop was still driving it,
which left the outer loop reading the inner document's freed buffer, or
resuming on a parser with no input at all and tripping libyaml's
read_handler assertion.
Psych::Exception is used so the caller can rescue it, and the in-use
flag is cleared with rb_ensure so a parser stays reusable afterwards.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CopilotAI lite review requested due to automatic review settings August 20, 2026 03:12

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens Psych::Parser#parse against three low-level correctness defects in both the libyaml and libfyaml backends: (1) exceptions raised from Handler#event_location not reliably propagating, (2) unsafe reentrant parse calls from within callbacks corrupting parser state, and (3) a potential infinite loop when the backend returns an event with no type.

Changes:

  • Ensure exceptions from event_location are immediately surfaced (and associated event memory is released before raising).
  • Add a per-parser “in use” guard to reject reentrant parse calls, and clear it via rb_ensure to keep parsers reusable.
  • Stop the parse loop when the backend returns a “no event” type to avoid spinning indefinitely.
  • Add tests for exception propagation and reentrancy behavior.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

FileDescription
test/psych/test_parser.rbAdds regression tests covering event_location exception propagation and reentrant parse rejection/reuse.
ext/psych/psych_parser.cWraps yaml_parser_t in a struct with an in-use flag; fixes exception propagation and loop termination edge cases.
ext/psych/psych_parser_fy.cMirrors the same correctness fixes for the libfyaml backend (event_location propagation, non-reentrancy, loop termination).

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines 91 to 98
static VALUE allocate(VALUE klass)
{
yaml_parser_t * parser;
VALUE obj = TypedData_Make_Struct(klass, yaml_parser_t, &psych_parser_type, parser);
psych_parser_t * parser;
VALUE obj = TypedData_Make_Struct(klass, psych_parser_t, &psych_parser_type, parser);

yaml_parser_initialize(parser);
yaml_parser_initialize(&parser->yaml_parser);

return obj;
Comment on lines 39 to 49
typedef struct {
struct fy_parser *fyp;
size_t mark_line;
size_t mark_column;
size_t mark_index;
/* The parser calls back into Ruby for every event, so a handler can call
* Psych::Parser#parse again on the same object. parse() destroys and
* recreates fyp, which would pull the parser out from under the loop still
* driving it, so keep a flag to reject a reentrant call. */
int parsing;
} psych_fy_parser_t;
@hsbt
hsbt merged commit 182f803 into masterAug 21, 2026
234 of 235 checks passed
@hsbt
hsbt deleted the claude/kind-burnell-81c502 branch August 21, 2026 01:05
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@hsbt
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix three defects in Psych::Parser#parse - #817

Merged
hsbt merged 4 commits into
masterfrom
claude/kind-burnell-81c502
Aug 21, 2026
Merged

Fix three defects in Psych::Parser#parse#817
hsbt merged 4 commits into
masterfrom
claude/kind-burnell-81c502

Conversation

@hsbt

@hsbthsbt commented Aug 20, 2026

Copy link
Copy Markdown
Member

An exception raised in Psych::Handler#event_location never reaches the caller. Every branch of the event switch overwrites the rb_protect state, so only the second result is tested.

Calling Psych::Parser#parse from inside a handler callback reinitialises the parser the outer loop is still driving. The outer loop then reads the inner document's freed buffer, or resumes with no input and trips libyaml's read_handler assertion, aborting the process. parse now raises Psych::Exception when the parser is already parsing, and rb_ensure clears the flag so the parser stays reusable.

The loop also spun forever on an event with no type, which libyaml keeps returning once stream_end_produced is set.

None of this is reachable through Psych.load and friends, which build a fresh parser each time. The libfyaml backend shared all three and is fixed alongside.

hsbtand others added 3 commits August 19, 2026 10:59
Every case of the event switch overwrote the rb_protect state, so only
the second result was ever tested and the state from the event_location
call was dropped. The parse then returned normally with the exception
silently discarded.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Once libyaml has set stream_end_produced, every further
yaml_parser_parse succeeds with a zeroed event, so YAML_STREAM_END_EVENT
can no longer be reached and the loop calls handler#empty forever at
full CPU. A parser left in that state should terminate instead.
No test covers this directly because the loop only reaches it through a
reentrant parse, which the parser now rejects outright.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The parser calls back into Ruby for every event, so a handler can call
#parse again on the same object. That reinitialised the parser and
re-pointed it at new input while the outer loop was still driving it,
which left the outer loop reading the inner document's freed buffer, or
resuming on a parser with no input at all and tripping libyaml's
read_handler assertion.
Psych::Exception is used so the caller can rescue it, and the in-use
flag is cleared with rb_ensure so a parser stays reusable afterwards.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CopilotAI lite review requested due to automatic review settings August 20, 2026 03:12

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens Psych::Parser#parse against three low-level correctness defects in both the libyaml and libfyaml backends: (1) exceptions raised from Handler#event_location not reliably propagating, (2) unsafe reentrant parse calls from within callbacks corrupting parser state, and (3) a potential infinite loop when the backend returns an event with no type.

Changes:

  • Ensure exceptions from event_location are immediately surfaced (and associated event memory is released before raising).
  • Add a per-parser “in use” guard to reject reentrant parse calls, and clear it via rb_ensure to keep parsers reusable.
  • Stop the parse loop when the backend returns a “no event” type to avoid spinning indefinitely.
  • Add tests for exception propagation and reentrancy behavior.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

FileDescription
test/psych/test_parser.rbAdds regression tests covering event_location exception propagation and reentrant parse rejection/reuse.
ext/psych/psych_parser.cWraps yaml_parser_t in a struct with an in-use flag; fixes exception propagation and loop termination edge cases.
ext/psych/psych_parser_fy.cMirrors the same correctness fixes for the libfyaml backend (event_location propagation, non-reentrancy, loop termination).

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines 91 to 98
static VALUE allocate(VALUE klass)
{
yaml_parser_t * parser;
VALUE obj = TypedData_Make_Struct(klass, yaml_parser_t, &psych_parser_type, parser);
psych_parser_t * parser;
VALUE obj = TypedData_Make_Struct(klass, psych_parser_t, &psych_parser_type, parser);

yaml_parser_initialize(parser);
yaml_parser_initialize(&parser->yaml_parser);

return obj;
Comment on lines 39 to 49
typedef struct {
struct fy_parser *fyp;
size_t mark_line;
size_t mark_column;
size_t mark_index;
/* The parser calls back into Ruby for every event, so a handler can call
* Psych::Parser#parse again on the same object. parse() destroys and
* recreates fyp, which would pull the parser out from under the loop still
* driving it, so keep a flag to reject a reentrant call. */
int parsing;
} psych_fy_parser_t;
@hsbt
hsbt merged commit 182f803 into masterAug 21, 2026
234 of 235 checks passed
@hsbt
hsbt deleted the claude/kind-burnell-81c502 branch August 21, 2026 01:05
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@hsbt
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Fix three defects in Psych::Parser#parse - #817

Merged
hsbt merged 4 commits into
masterfrom
claude/kind-burnell-81c502
Aug 21, 2026
Merged

Fix three defects in Psych::Parser#parse#817
hsbt merged 4 commits into
masterfrom
claude/kind-burnell-81c502

Conversation

@hsbt

@hsbthsbt commented Aug 20, 2026

Copy link
Copy Markdown
Member

An exception raised in Psych::Handler#event_location never reaches the caller. Every branch of the event switch overwrites the rb_protect state, so only the second result is tested.

Calling Psych::Parser#parse from inside a handler callback reinitialises the parser the outer loop is still driving. The outer loop then reads the inner document's freed buffer, or resumes with no input and trips libyaml's read_handler assertion, aborting the process. parse now raises Psych::Exception when the parser is already parsing, and rb_ensure clears the flag so the parser stays reusable.

The loop also spun forever on an event with no type, which libyaml keeps returning once stream_end_produced is set.

None of this is reachable through Psych.load and friends, which build a fresh parser each time. The libfyaml backend shared all three and is fixed alongside.

hsbtand others added 3 commits August 19, 2026 10:59
Every case of the event switch overwrote the rb_protect state, so only
the second result was ever tested and the state from the event_location
call was dropped. The parse then returned normally with the exception
silently discarded.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Once libyaml has set stream_end_produced, every further
yaml_parser_parse succeeds with a zeroed event, so YAML_STREAM_END_EVENT
can no longer be reached and the loop calls handler#empty forever at
full CPU. A parser left in that state should terminate instead.
No test covers this directly because the loop only reaches it through a
reentrant parse, which the parser now rejects outright.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The parser calls back into Ruby for every event, so a handler can call
#parse again on the same object. That reinitialised the parser and
re-pointed it at new input while the outer loop was still driving it,
which left the outer loop reading the inner document's freed buffer, or
resuming on a parser with no input at all and tripping libyaml's
read_handler assertion.
Psych::Exception is used so the caller can rescue it, and the in-use
flag is cleared with rb_ensure so a parser stays reusable afterwards.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CopilotAI lite review requested due to automatic review settings August 20, 2026 03:12

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens Psych::Parser#parse against three low-level correctness defects in both the libyaml and libfyaml backends: (1) exceptions raised from Handler#event_location not reliably propagating, (2) unsafe reentrant parse calls from within callbacks corrupting parser state, and (3) a potential infinite loop when the backend returns an event with no type.

Changes:

  • Ensure exceptions from event_location are immediately surfaced (and associated event memory is released before raising).
  • Add a per-parser “in use” guard to reject reentrant parse calls, and clear it via rb_ensure to keep parsers reusable.
  • Stop the parse loop when the backend returns a “no event” type to avoid spinning indefinitely.
  • Add tests for exception propagation and reentrancy behavior.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

FileDescription
test/psych/test_parser.rbAdds regression tests covering event_location exception propagation and reentrant parse rejection/reuse.
ext/psych/psych_parser.cWraps yaml_parser_t in a struct with an in-use flag; fixes exception propagation and loop termination edge cases.
ext/psych/psych_parser_fy.cMirrors the same correctness fixes for the libfyaml backend (event_location propagation, non-reentrancy, loop termination).

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines 91 to 98
static VALUE allocate(VALUE klass)
{
yaml_parser_t * parser;
VALUE obj = TypedData_Make_Struct(klass, yaml_parser_t, &psych_parser_type, parser);
psych_parser_t * parser;
VALUE obj = TypedData_Make_Struct(klass, psych_parser_t, &psych_parser_type, parser);

yaml_parser_initialize(parser);
yaml_parser_initialize(&parser->yaml_parser);

return obj;
Comment on lines 39 to 49
typedef struct {
struct fy_parser *fyp;
size_t mark_line;
size_t mark_column;
size_t mark_index;
/* The parser calls back into Ruby for every event, so a handler can call
* Psych::Parser#parse again on the same object. parse() destroys and
* recreates fyp, which would pull the parser out from under the loop still
* driving it, so keep a flag to reject a reentrant call. */
int parsing;
} psych_fy_parser_t;
@hsbt
hsbt merged commit 182f803 into masterAug 21, 2026
234 of 235 checks passed
@hsbt
hsbt deleted the claude/kind-burnell-81c502 branch August 21, 2026 01:05
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@hsbt
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix three defects in Psych::Parser#parse - #817

Merged
hsbt merged 4 commits into
masterfrom
claude/kind-burnell-81c502
Aug 21, 2026
Merged

Fix three defects in Psych::Parser#parse#817
hsbt merged 4 commits into
masterfrom
claude/kind-burnell-81c502

Conversation

@hsbt

@hsbthsbt commented Aug 20, 2026

Copy link
Copy Markdown
Member

An exception raised in Psych::Handler#event_location never reaches the caller. Every branch of the event switch overwrites the rb_protect state, so only the second result is tested.

Calling Psych::Parser#parse from inside a handler callback reinitialises the parser the outer loop is still driving. The outer loop then reads the inner document's freed buffer, or resumes with no input and trips libyaml's read_handler assertion, aborting the process. parse now raises Psych::Exception when the parser is already parsing, and rb_ensure clears the flag so the parser stays reusable.

The loop also spun forever on an event with no type, which libyaml keeps returning once stream_end_produced is set.

None of this is reachable through Psych.load and friends, which build a fresh parser each time. The libfyaml backend shared all three and is fixed alongside.

hsbtand others added 3 commits August 19, 2026 10:59
Every case of the event switch overwrote the rb_protect state, so only
the second result was ever tested and the state from the event_location
call was dropped. The parse then returned normally with the exception
silently discarded.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Once libyaml has set stream_end_produced, every further
yaml_parser_parse succeeds with a zeroed event, so YAML_STREAM_END_EVENT
can no longer be reached and the loop calls handler#empty forever at
full CPU. A parser left in that state should terminate instead.
No test covers this directly because the loop only reaches it through a
reentrant parse, which the parser now rejects outright.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The parser calls back into Ruby for every event, so a handler can call
#parse again on the same object. That reinitialised the parser and
re-pointed it at new input while the outer loop was still driving it,
which left the outer loop reading the inner document's freed buffer, or
resuming on a parser with no input at all and tripping libyaml's
read_handler assertion.
Psych::Exception is used so the caller can rescue it, and the in-use
flag is cleared with rb_ensure so a parser stays reusable afterwards.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CopilotAI lite review requested due to automatic review settings August 20, 2026 03:12

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens Psych::Parser#parse against three low-level correctness defects in both the libyaml and libfyaml backends: (1) exceptions raised from Handler#event_location not reliably propagating, (2) unsafe reentrant parse calls from within callbacks corrupting parser state, and (3) a potential infinite loop when the backend returns an event with no type.

Changes:

  • Ensure exceptions from event_location are immediately surfaced (and associated event memory is released before raising).
  • Add a per-parser “in use” guard to reject reentrant parse calls, and clear it via rb_ensure to keep parsers reusable.
  • Stop the parse loop when the backend returns a “no event” type to avoid spinning indefinitely.
  • Add tests for exception propagation and reentrancy behavior.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

FileDescription
test/psych/test_parser.rbAdds regression tests covering event_location exception propagation and reentrant parse rejection/reuse.
ext/psych/psych_parser.cWraps yaml_parser_t in a struct with an in-use flag; fixes exception propagation and loop termination edge cases.
ext/psych/psych_parser_fy.cMirrors the same correctness fixes for the libfyaml backend (event_location propagation, non-reentrancy, loop termination).

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines 91 to 98
static VALUE allocate(VALUE klass)
{
yaml_parser_t * parser;
VALUE obj = TypedData_Make_Struct(klass, yaml_parser_t, &psych_parser_type, parser);
psych_parser_t * parser;
VALUE obj = TypedData_Make_Struct(klass, psych_parser_t, &psych_parser_type, parser);

yaml_parser_initialize(parser);
yaml_parser_initialize(&parser->yaml_parser);

return obj;
Comment on lines 39 to 49
typedef struct {
struct fy_parser *fyp;
size_t mark_line;
size_t mark_column;
size_t mark_index;
/* The parser calls back into Ruby for every event, so a handler can call
* Psych::Parser#parse again on the same object. parse() destroys and
* recreates fyp, which would pull the parser out from under the loop still
* driving it, so keep a flag to reject a reentrant call. */
int parsing;
} psych_fy_parser_t;
@hsbt
hsbt merged commit 182f803 into masterAug 21, 2026
234 of 235 checks passed
@hsbt
hsbt deleted the claude/kind-burnell-81c502 branch August 21, 2026 01:05
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@hsbt
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix three defects in Psych::Parser#parse - #817

Merged
hsbt merged 4 commits into
masterfrom
claude/kind-burnell-81c502
Aug 21, 2026
Merged

Fix three defects in Psych::Parser#parse#817
hsbt merged 4 commits into
masterfrom
claude/kind-burnell-81c502

Conversation

@hsbt

@hsbthsbt commented Aug 20, 2026

Copy link
Copy Markdown
Member

An exception raised in Psych::Handler#event_location never reaches the caller. Every branch of the event switch overwrites the rb_protect state, so only the second result is tested.

Calling Psych::Parser#parse from inside a handler callback reinitialises the parser the outer loop is still driving. The outer loop then reads the inner document's freed buffer, or resumes with no input and trips libyaml's read_handler assertion, aborting the process. parse now raises Psych::Exception when the parser is already parsing, and rb_ensure clears the flag so the parser stays reusable.

The loop also spun forever on an event with no type, which libyaml keeps returning once stream_end_produced is set.

None of this is reachable through Psych.load and friends, which build a fresh parser each time. The libfyaml backend shared all three and is fixed alongside.

hsbtand others added 3 commits August 19, 2026 10:59
Every case of the event switch overwrote the rb_protect state, so only
the second result was ever tested and the state from the event_location
call was dropped. The parse then returned normally with the exception
silently discarded.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Once libyaml has set stream_end_produced, every further
yaml_parser_parse succeeds with a zeroed event, so YAML_STREAM_END_EVENT
can no longer be reached and the loop calls handler#empty forever at
full CPU. A parser left in that state should terminate instead.
No test covers this directly because the loop only reaches it through a
reentrant parse, which the parser now rejects outright.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The parser calls back into Ruby for every event, so a handler can call
#parse again on the same object. That reinitialised the parser and
re-pointed it at new input while the outer loop was still driving it,
which left the outer loop reading the inner document's freed buffer, or
resuming on a parser with no input at all and tripping libyaml's
read_handler assertion.
Psych::Exception is used so the caller can rescue it, and the in-use
flag is cleared with rb_ensure so a parser stays reusable afterwards.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CopilotAI lite review requested due to automatic review settings August 20, 2026 03:12

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens Psych::Parser#parse against three low-level correctness defects in both the libyaml and libfyaml backends: (1) exceptions raised from Handler#event_location not reliably propagating, (2) unsafe reentrant parse calls from within callbacks corrupting parser state, and (3) a potential infinite loop when the backend returns an event with no type.

Changes:

  • Ensure exceptions from event_location are immediately surfaced (and associated event memory is released before raising).
  • Add a per-parser “in use” guard to reject reentrant parse calls, and clear it via rb_ensure to keep parsers reusable.
  • Stop the parse loop when the backend returns a “no event” type to avoid spinning indefinitely.
  • Add tests for exception propagation and reentrancy behavior.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

FileDescription
test/psych/test_parser.rbAdds regression tests covering event_location exception propagation and reentrant parse rejection/reuse.
ext/psych/psych_parser.cWraps yaml_parser_t in a struct with an in-use flag; fixes exception propagation and loop termination edge cases.
ext/psych/psych_parser_fy.cMirrors the same correctness fixes for the libfyaml backend (event_location propagation, non-reentrancy, loop termination).

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines 91 to 98
static VALUE allocate(VALUE klass)
{
yaml_parser_t * parser;
VALUE obj = TypedData_Make_Struct(klass, yaml_parser_t, &psych_parser_type, parser);
psych_parser_t * parser;
VALUE obj = TypedData_Make_Struct(klass, psych_parser_t, &psych_parser_type, parser);

yaml_parser_initialize(parser);
yaml_parser_initialize(&parser->yaml_parser);

return obj;
Comment on lines 39 to 49
typedef struct {
struct fy_parser *fyp;
size_t mark_line;
size_t mark_column;
size_t mark_index;
/* The parser calls back into Ruby for every event, so a handler can call
* Psych::Parser#parse again on the same object. parse() destroys and
* recreates fyp, which would pull the parser out from under the loop still
* driving it, so keep a flag to reject a reentrant call. */
int parsing;
} psych_fy_parser_t;
@hsbt
hsbt merged commit 182f803 into masterAug 21, 2026
234 of 235 checks passed
@hsbt
hsbt deleted the claude/kind-burnell-81c502 branch August 21, 2026 01:05
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@hsbt
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Fix three defects in Psych::Parser#parse - #817

Merged
hsbt merged 4 commits into
masterfrom
claude/kind-burnell-81c502
Aug 21, 2026
Merged

Fix three defects in Psych::Parser#parse#817
hsbt merged 4 commits into
masterfrom
claude/kind-burnell-81c502

Conversation

@hsbt

@hsbthsbt commented Aug 20, 2026

Copy link
Copy Markdown
Member

An exception raised in Psych::Handler#event_location never reaches the caller. Every branch of the event switch overwrites the rb_protect state, so only the second result is tested.

Calling Psych::Parser#parse from inside a handler callback reinitialises the parser the outer loop is still driving. The outer loop then reads the inner document's freed buffer, or resumes with no input and trips libyaml's read_handler assertion, aborting the process. parse now raises Psych::Exception when the parser is already parsing, and rb_ensure clears the flag so the parser stays reusable.

The loop also spun forever on an event with no type, which libyaml keeps returning once stream_end_produced is set.

None of this is reachable through Psych.load and friends, which build a fresh parser each time. The libfyaml backend shared all three and is fixed alongside.

hsbtand others added 3 commits August 19, 2026 10:59
Every case of the event switch overwrote the rb_protect state, so only
the second result was ever tested and the state from the event_location
call was dropped. The parse then returned normally with the exception
silently discarded.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Once libyaml has set stream_end_produced, every further
yaml_parser_parse succeeds with a zeroed event, so YAML_STREAM_END_EVENT
can no longer be reached and the loop calls handler#empty forever at
full CPU. A parser left in that state should terminate instead.
No test covers this directly because the loop only reaches it through a
reentrant parse, which the parser now rejects outright.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The parser calls back into Ruby for every event, so a handler can call
#parse again on the same object. That reinitialised the parser and
re-pointed it at new input while the outer loop was still driving it,
which left the outer loop reading the inner document's freed buffer, or
resuming on a parser with no input at all and tripping libyaml's
read_handler assertion.
Psych::Exception is used so the caller can rescue it, and the in-use
flag is cleared with rb_ensure so a parser stays reusable afterwards.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CopilotAI lite review requested due to automatic review settings August 20, 2026 03:12

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens Psych::Parser#parse against three low-level correctness defects in both the libyaml and libfyaml backends: (1) exceptions raised from Handler#event_location not reliably propagating, (2) unsafe reentrant parse calls from within callbacks corrupting parser state, and (3) a potential infinite loop when the backend returns an event with no type.

Changes:

  • Ensure exceptions from event_location are immediately surfaced (and associated event memory is released before raising).
  • Add a per-parser “in use” guard to reject reentrant parse calls, and clear it via rb_ensure to keep parsers reusable.
  • Stop the parse loop when the backend returns a “no event” type to avoid spinning indefinitely.
  • Add tests for exception propagation and reentrancy behavior.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

FileDescription
test/psych/test_parser.rbAdds regression tests covering event_location exception propagation and reentrant parse rejection/reuse.
ext/psych/psych_parser.cWraps yaml_parser_t in a struct with an in-use flag; fixes exception propagation and loop termination edge cases.
ext/psych/psych_parser_fy.cMirrors the same correctness fixes for the libfyaml backend (event_location propagation, non-reentrancy, loop termination).

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines 91 to 98
static VALUE allocate(VALUE klass)
{
yaml_parser_t * parser;
VALUE obj = TypedData_Make_Struct(klass, yaml_parser_t, &psych_parser_type, parser);
psych_parser_t * parser;
VALUE obj = TypedData_Make_Struct(klass, psych_parser_t, &psych_parser_type, parser);

yaml_parser_initialize(parser);
yaml_parser_initialize(&parser->yaml_parser);

return obj;
Comment on lines 39 to 49
typedef struct {
struct fy_parser *fyp;
size_t mark_line;
size_t mark_column;
size_t mark_index;
/* The parser calls back into Ruby for every event, so a handler can call
* Psych::Parser#parse again on the same object. parse() destroys and
* recreates fyp, which would pull the parser out from under the loop still
* driving it, so keep a flag to reject a reentrant call. */
int parsing;
} psych_fy_parser_t;
@hsbt
hsbt merged commit 182f803 into masterAug 21, 2026
234 of 235 checks passed
@hsbt
hsbt deleted the claude/kind-burnell-81c502 branch August 21, 2026 01:05
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@hsbt