From 01a2afa7c55e64c71a7d60083962da046ce52027 Mon Sep 17 00:00:00 2001 From: V1rus <34763711+thaning0@users.noreply.github.com> Date: Fri, 19 Jun 2026 01:04:28 +0800 Subject: [PATCH] Fix markdown rendering hang on unmatched backticks (#1585) * fix: prevent infinite loop on unmatched backtick runs in getInlineCodeRanges * test: cover unmatched inline backtick runs --------- Co-authored-by: Mohamed Boudra --- packages/app/src/components/markdown/html-ish.test.ts | 6 ++++++ packages/app/src/components/markdown/html-ish.ts | 7 ++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/app/src/components/markdown/html-ish.test.ts b/packages/app/src/components/markdown/html-ish.test.ts index ac6f2ad8b..c47a718fe 100644 --- a/packages/app/src/components/markdown/html-ish.test.ts +++ b/packages/app/src/components/markdown/html-ish.test.ts @@ -256,6 +256,12 @@ describe("splitHtmlishMarkdown", () => { expect(splitHtmlishMarkdown(source)).toEqual([{ kind: "markdown", text: source }]); }); + it("terminates when an unmatched backtick follows a closed inline code span", () => { + const source = "Use `
ExampleBody
` before `dangling"; + + expect(splitHtmlishMarkdown(source)).toEqual([{ kind: "markdown", text: source }]); + }); + it("still parses normal details outside code", () => { expect(splitHtmlishMarkdown("`code`\n
RealBody
")).toEqual([ { kind: "markdown", text: "`code`\n" }, diff --git a/packages/app/src/components/markdown/html-ish.ts b/packages/app/src/components/markdown/html-ish.ts index b68e734b5..7a92f0d20 100644 --- a/packages/app/src/components/markdown/html-ish.ts +++ b/packages/app/src/components/markdown/html-ish.ts @@ -651,8 +651,13 @@ function getInlineCodeRanges( } const marker = open[0]; - const close = findClosingBacktickRun(source, BACKTICK_RUN_RE.lastIndex, marker, fencedRanges); + const afterOpen = BACKTICK_RUN_RE.lastIndex; + const close = findClosingBacktickRun(source, afterOpen, marker, fencedRanges); if (!close) { + // Unmatched backtick run — skip past it so the loop doesn't restart from 0. + // findClosingBacktickRun exhausts the global regex, which resets lastIndex + // to 0 when exec() returns null (ECMAScript §22.2.7.2). + BACKTICK_RUN_RE.lastIndex = afterOpen; continue; }