From 806c153979ca940485cc3a9238dca7a3e7633415 Mon Sep 17 00:00:00 2001 From: Spencer Brower <6729162+sbrow@users.noreply.github.com> Date: Sun, 2 Aug 2026 16:20:54 -0400 Subject: [PATCH] perf: Fixed leaks in commonmark code. --- TODOS.md | 3 ++- content.odin | 9 +++++---- markdown/footnotes.odin | 6 ++++-- markdown/markdown.odin | 14 ++++++++++++-- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/TODOS.md b/TODOS.md index 70366f5..b2c09b9 100644 --- a/TODOS.md +++ b/TODOS.md @@ -194,7 +194,8 @@ main :: proc () { - [x] basic poll loop - [ ] filesystem poll loop - [ ] event based -- [ ] Free cmark HTML output (`body_html`) — cmark allocates via C malloc, not the arena, so it leaks per iteration in watch mode +- [x] Free cmark HTML output (`body_html`) — cmark allocates via C malloc, not the arena, so it leaks per iteration in watch mode +- [ ] manually pass `site_allocator` to `load_page` - [ ] Mount content in VFS - [ ] commands - [ ] `build` alias of default diff --git a/content.odin b/content.odin index 534ead9..078b39f 100644 --- a/content.odin +++ b/content.odin @@ -3,9 +3,9 @@ package main import md "markdown" import ts "treesitter" +import "core:encoding/json" import "core:fmt" import "core:log" -import "core:encoding/json" import "core:os" import "core:strings" import "core:time" @@ -24,7 +24,7 @@ Page :: struct { weight: Maybe(int), lastmod: string, menus: map[string]Menu_Entry, - params: json.Value, + params: json.Value, content: string, og: Open_Graph, draft: bool, @@ -275,9 +275,9 @@ load_page :: proc( page.og = fm.og if strings.has_suffix(file_path, ".html") { - page.content = strings.clone(body) + page.content = strings.clone(body, context.allocator) } else { - page.content = md.process(body, ext, file_path) + page.content = md.process(body, ext, file_path, context.allocator) } ok = true @@ -295,3 +295,4 @@ strip_extension :: proc(name: string) -> string { } return name[:dot] } + diff --git a/markdown/footnotes.odin b/markdown/footnotes.odin index abbe190..b897259 100644 --- a/markdown/footnotes.odin +++ b/markdown/footnotes.odin @@ -171,8 +171,9 @@ inject_notes :: proc(html: string, sn_defs, mn_defs: map[string]string) -> strin } // Render definition through cmark for markdown support - def_html := cm.markdown_to_html_from_string(def_text, {.Unsafe}) - def_html = strip_p_tags(def_html) + raw_html := cm.markdown_to_html_from_string(def_text, {.Unsafe}) + defer cm.free_string(raw_html) + def_html := strip_p_tags(raw_html) note: string defer delete(note) @@ -210,3 +211,4 @@ strip_p_tags :: proc(html: string) -> string { } return s } + diff --git a/markdown/markdown.odin b/markdown/markdown.odin index 1751dc5..af6e5be 100644 --- a/markdown/markdown.odin +++ b/markdown/markdown.odin @@ -16,14 +16,23 @@ Extension :: enum { DEFAULT_EXTENSIONS :: bit_set[Extension]{.Emoji, .Sidenotes, .Alerts, .HeadingIDs} -process :: proc(body: string, ext: bit_set[Extension], file_path: string) -> string { +// Caller is responsible for freeing string +process :: proc( + body: string, + ext: bit_set[Extension], + file_path: string, + allocator := context.allocator, +) -> string { side_notes := make(map[string]string) margin_notes := make(map[string]string) clean_body := body if .Sidenotes in ext { clean_body, side_notes, margin_notes = strip_definitions(body) } - html := cm.markdown_to_html_from_string(clean_body, {.Unsafe}) + original_html := cm.markdown_to_html_from_string(clean_body, {.Unsafe}) + html := strings.clone(original_html, allocator) + cm.free_string(original_html) + if .Emoji in ext { html = expand_emoji(html) } @@ -88,3 +97,4 @@ apply_extension_config :: proc(ext: ^bit_set[Extension], config: json.Object) { } } } +