mirror of
https://github.com/sbrow/thor.git
synced 2026-10-25 10:11:55 -04:00
fix: fixed use-after-free in highlight.odin.
This commit is contained in:
@@ -90,9 +90,12 @@ write_escaped :: proc(b: ^strings.Builder, s: string) {
|
||||
}
|
||||
}
|
||||
|
||||
// unescape_html returns a caller-owned string (free it with delete). When the
|
||||
// input has no entities it returns a clone; otherwise it returns the builder's
|
||||
// buffer. It must NOT destroy the builder — to_string aliases that buffer, so the
|
||||
// returned string owns it.
|
||||
unescape_html :: proc(s: string) -> string {
|
||||
sb := strings.builder_make()
|
||||
defer strings.builder_destroy(&sb)
|
||||
|
||||
start := 0
|
||||
for i in 0 ..< len(s) {
|
||||
@@ -119,7 +122,7 @@ unescape_html :: proc(s: string) -> string {
|
||||
strings.write_string(&sb, replacement)
|
||||
start = i + semi + 1
|
||||
}
|
||||
if start == 0 do return s
|
||||
if start == 0 do return strings.clone(s)
|
||||
if start < len(s) do strings.write_string(&sb, s[start:])
|
||||
return strings.to_string(sb)
|
||||
}
|
||||
@@ -136,6 +139,7 @@ highlight_block :: proc(code: string, lang: string, file_path: string) -> string
|
||||
defer ts.parser_delete(parser)
|
||||
|
||||
raw_code := unescape_html(code)
|
||||
defer delete(raw_code)
|
||||
raw_c := strings.clone_to_cstring(raw_code)
|
||||
defer delete(raw_c)
|
||||
|
||||
|
||||
@@ -81,3 +81,23 @@ test_highlight_code_wraps_css_block :: proc(t: ^testing.T) {
|
||||
"block body should be highlighted",
|
||||
)
|
||||
}
|
||||
|
||||
// unescape_html must return a caller-owned allocation the caller frees exactly
|
||||
// once. The buggy version frees its builder internally (defer builder_destroy)
|
||||
// and returns the freed alias, so the caller's delete becomes a double-free that
|
||||
// the test runner's tracking allocator flags.
|
||||
@(test)
|
||||
test_unescape_html_entities_owned :: proc(t: ^testing.T) {
|
||||
got := unescape_html("<a>")
|
||||
defer delete(got)
|
||||
testing.expect_value(t, got, "<a>")
|
||||
}
|
||||
|
||||
// Same contract on the no-entity path: the buggy version returns the borrowed
|
||||
// input, so freeing it is a bad free (pointer not from the allocator).
|
||||
@(test)
|
||||
test_unescape_html_passthrough_owned :: proc(t: ^testing.T) {
|
||||
got := unescape_html("plain text")
|
||||
defer delete(got)
|
||||
testing.expect_value(t, got, "plain text")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user