Hi folks!
For the little story, I rebuilt my website writing my own custom static site generator using Odin.
It was previously using Pelican, a Python-based static site generator, which works fine, but it’s annoying slow for the “watch for changes & serve” loop and every now and then I would have dependencies issues.
Now I’ve got my own static site generator written in Odin, with a very hacky template engine – but it works, and damn it’s fast!
I am hitting a weird issue using the CMark-GFM library, which I’ve installed on my Ubuntu box with sudo apt install libcmark-gfm-dev libcmark-gfm-extensions-dev
and I came up with bindings adapting the ones that come vendored in Odin for original CMark.
Anyway, for some reason, my code segfaults if I runtime.free
the output of the render_html function.
Here is a self-contained example that reproduces the issue:
package cmark_gfm_example
import "base:runtime"
import "core:c"
import "core:log"
import "core:fmt"
import "core:strings"
foreign import lib "system:cmark-gfm"
foreign import lib_ext "system:cmark-gfm-extensions"
@(default_calling_convention = "c", link_prefix = "cmark_")
foreign lib {
parser_new :: proc(options: c.int) -> rawptr ---
parser_feed :: proc(parser: rawptr, text: cstring, length: c.size_t) ---
parser_finish :: proc(parser: rawptr) -> rawptr ---
render_html :: proc(document: rawptr, options: c.int, extensions: rawptr) -> cstring ---
node_free :: proc(node: rawptr) ---
parser_free :: proc(parser: rawptr) ---
}
@(default_calling_convention = "c", link_prefix = "cmark_")
foreign lib_ext {
// Extension management
gfm_core_extensions_ensure_registered :: proc() ---
find_syntax_extension :: proc(name: cstring) -> rawptr ---
parser_attach_syntax_extension :: proc(parser: rawptr, extension: rawptr) -> bool ---
}
@(private = "file")
enable_extension :: proc(parser: rawptr, ext_name: cstring) -> bool {
if table_ext := find_syntax_extension(ext_name); table_ext != nil {
parser_attach_syntax_extension(parser, table_ext) or_return
}
return true
}
markdown_to_html_from_string_extended :: proc(text: string) -> (html: string) {
options: i32 = 0 // int value of CMARK_OPT_DEFAULT
gfm_core_extensions_ensure_registered()
parser := parser_new(options)
defer parser_free(parser)
enable_extension(parser, "table")
enable_extension(parser, "strikethrough")
// Parse the markdown
parser_feed(parser, strings.unsafe_string_to_cstring(text), len(text))
document := parser_finish(parser)
defer node_free(document)
// Render to HTML
html_cstr := render_html(document, options, c.NULL)
// it's segfaulting here -- but why??
defer runtime.free(rawptr(html_cstr))
return strings.clone_from_cstring(html_cstr)
}
main :: proc() {
context.logger = log.create_console_logger()
text: string = "This is a ~~strikethrough~~ example.\n\n| Header | Header |\n| ------ | ------ |\n| Cell | Cell |"
fmt.printf("Rendered markdown:\n%s", markdown_to_html_from_string_extended(text))
}
If I uncomment the line with runtime.free
, the code works fine.
And the equivalent C example works fine as well:
// Compile with:
// gcc example_c.c -o example_c -lcmark-gfm -lcmark-gfm-extensions
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <cmark-gfm.h>
#include <cmark-gfm-core-extensions.h>
// This is a function that will make enabling extensions easier later on.
void enable_markdown_extension(cmark_parser *parser, char *extName) {
cmark_syntax_extension *ext = cmark_find_syntax_extension(extName);
if ( ext )
cmark_parser_attach_syntax_extension(parser, ext);
}
// A function to convert HTML to markdown
char *markdown_to_html(char *markdown_string)
{
int options = CMARK_OPT_DEFAULT; // You can also use CMARK_OPT_STRIKETHROUGH_DOUBLE_TILDE to enforce double tilde.
cmark_gfm_core_extensions_ensure_registered();
// Modified version of cmark_parse_document in blocks.c
cmark_parser *parser = cmark_parser_new(options);
/// Add extensions here
enable_markdown_extension(parser, "strikethrough");
enable_markdown_extension(parser, "table");
cmark_node *doc;
cmark_parser_feed(parser, markdown_string, strlen(markdown_string));
doc = cmark_parser_finish(parser);
cmark_parser_free(parser);
// Render
char *html = cmark_render_html(doc, options, NULL);
cmark_node_free(doc);
return html;
}
int main() {
char *markdown = "This is a ~~strikethrough~~ example.\n\n| Header | Header |\n| ------ | ------ |\n| Cell | Cell |";
char *html = markdown_to_html(markdown);
if (html) {
printf("Converted HTML:\n%s\n", html);
free(html); // Free the allocated memory for HTML
} else {
printf("Conversion failed.\n");
}
return 0;
}
Any ideas on what might be going on here?
Thanks!