From 8bd769cbf14b44f84582aaff8b4ae1b4adee8525 Mon Sep 17 00:00:00 2001 From: Phill MV Date: Tue, 31 Jan 2023 11:25:28 -0500 Subject: [PATCH 1/9] Update cmark-upstream to https://github.com/github/cmark-gfm/commit/c32ef78bae851cb83b7ad52d0fbff880acdcd44a --- ext/commonmarker/arena.c | 2 +- ext/commonmarker/autolink.c | 5 +++ ext/commonmarker/cmark-gfm-core-extensions.h | 22 +++++++------- ext/commonmarker/cmark-gfm.h | 9 ++++-- ext/commonmarker/cmark-upstream | 2 +- ext/commonmarker/cmark.c | 6 ++-- ext/commonmarker/html.c | 18 +++++++++-- ext/commonmarker/node.c | 32 +++++++++----------- ext/commonmarker/node.h | 32 ++++++++++++-------- ext/commonmarker/table.c | 2 +- 10 files changed, 78 insertions(+), 52 deletions(-) diff --git a/ext/commonmarker/arena.c b/ext/commonmarker/arena.c index 6373c0d0..da1a70e9 100644 --- a/ext/commonmarker/arena.c +++ b/ext/commonmarker/arena.c @@ -99,6 +99,6 @@ static void arena_free(void *ptr) { cmark_mem CMARK_ARENA_MEM_ALLOCATOR = {arena_calloc, arena_realloc, arena_free}; -cmark_mem *cmark_get_arena_mem_allocator() { +cmark_mem *cmark_get_arena_mem_allocator(void) { return &CMARK_ARENA_MEM_ALLOCATOR; } diff --git a/ext/commonmarker/autolink.c b/ext/commonmarker/autolink.c index f3b75a7e..491d96c3 100644 --- a/ext/commonmarker/autolink.c +++ b/ext/commonmarker/autolink.c @@ -267,6 +267,11 @@ static cmark_node *url_match(cmark_parser *parser, cmark_node *parent, cmark_node *text = cmark_node_new_with_mem(CMARK_NODE_TEXT, parser->mem); text->as.literal = url; cmark_node_append_child(node, text); + + node->start_line = text->start_line = node->end_line = text->end_line = cmark_inline_parser_get_line(inline_parser); + + node->start_column = text->start_column = max_rewind - rewind; + node->end_column = text->end_column = cmark_inline_parser_get_column(inline_parser) - 1; return node; } diff --git a/ext/commonmarker/cmark-gfm-core-extensions.h b/ext/commonmarker/cmark-gfm-core-extensions.h index 0645915f..bc29ffd4 100644 --- a/ext/commonmarker/cmark-gfm-core-extensions.h +++ b/ext/commonmarker/cmark-gfm-core-extensions.h @@ -6,45 +6,45 @@ extern "C" { #endif #include "cmark-gfm-extension_api.h" -#include "cmark-gfm-extensions_export.h" -#include "config.h" // for bool +#include "cmark-gfm_export.h" +#include #include -CMARK_GFM_EXTENSIONS_EXPORT +CMARK_GFM_EXPORT void cmark_gfm_core_extensions_ensure_registered(void); -CMARK_GFM_EXTENSIONS_EXPORT +CMARK_GFM_EXPORT uint16_t cmark_gfm_extensions_get_table_columns(cmark_node *node); /** Sets the number of columns for the table, returning 1 on success and 0 on error. */ -CMARK_GFM_EXTENSIONS_EXPORT +CMARK_GFM_EXPORT int cmark_gfm_extensions_set_table_columns(cmark_node *node, uint16_t n_columns); -CMARK_GFM_EXTENSIONS_EXPORT +CMARK_GFM_EXPORT uint8_t *cmark_gfm_extensions_get_table_alignments(cmark_node *node); /** Sets the alignments for the table, returning 1 on success and 0 on error. */ -CMARK_GFM_EXTENSIONS_EXPORT +CMARK_GFM_EXPORT int cmark_gfm_extensions_set_table_alignments(cmark_node *node, uint16_t ncols, uint8_t *alignments); -CMARK_GFM_EXTENSIONS_EXPORT +CMARK_GFM_EXPORT int cmark_gfm_extensions_get_table_row_is_header(cmark_node *node); /** Sets whether the node is a table header row, returning 1 on success and 0 on error. */ -CMARK_GFM_EXTENSIONS_EXPORT +CMARK_GFM_EXPORT int cmark_gfm_extensions_set_table_row_is_header(cmark_node *node, int is_header); -CMARK_GFM_EXTENSIONS_EXPORT +CMARK_GFM_EXPORT bool cmark_gfm_extensions_get_tasklist_item_checked(cmark_node *node); /* For backwards compatibility */ #define cmark_gfm_extensions_tasklist_is_checked cmark_gfm_extensions_get_tasklist_item_checked /** Sets whether a tasklist item is "checked" (completed), returning 1 on success and 0 on error. */ -CMARK_GFM_EXTENSIONS_EXPORT +CMARK_GFM_EXPORT int cmark_gfm_extensions_set_tasklist_item_checked(cmark_node *node, bool is_checked); #ifdef __cplusplus diff --git a/ext/commonmarker/cmark-gfm.h b/ext/commonmarker/cmark-gfm.h index 6fb28693..6b362a86 100644 --- a/ext/commonmarker/cmark-gfm.h +++ b/ext/commonmarker/cmark-gfm.h @@ -111,13 +111,13 @@ typedef struct cmark_mem { * realloc and free. */ CMARK_GFM_EXPORT -cmark_mem *cmark_get_default_mem_allocator(); +cmark_mem *cmark_get_default_mem_allocator(void); /** An arena allocator; uses system calloc to allocate large * slabs of memory. Memory in these slabs is not reused at all. */ CMARK_GFM_EXPORT -cmark_mem *cmark_get_arena_mem_allocator(); +cmark_mem *cmark_get_arena_mem_allocator(void); /** Resets the arena allocator, quickly returning all used memory * to the operating system. @@ -225,6 +225,11 @@ CMARK_GFM_EXPORT cmark_node *cmark_node_first_child(cmark_node *node); */ CMARK_GFM_EXPORT cmark_node *cmark_node_last_child(cmark_node *node); +/** Returns the footnote reference of 'node', or NULL if 'node' doesn't have a + * footnote reference. + */ +CMARK_GFM_EXPORT cmark_node *cmark_node_parent_footnote_def(cmark_node *node); + /** * ## Iterator * diff --git a/ext/commonmarker/cmark-upstream b/ext/commonmarker/cmark-upstream index 57d5e093..c32ef78b 160000 --- a/ext/commonmarker/cmark-upstream +++ b/ext/commonmarker/cmark-upstream @@ -1 +1 @@ -Subproject commit 57d5e093ef801f54bf4174c900f7a863599bb47d +Subproject commit c32ef78bae851cb83b7ad52d0fbff880acdcd44a diff --git a/ext/commonmarker/cmark.c b/ext/commonmarker/cmark.c index b3fad4b0..68c40c47 100644 --- a/ext/commonmarker/cmark.c +++ b/ext/commonmarker/cmark.c @@ -10,9 +10,9 @@ cmark_node_type CMARK_NODE_LAST_BLOCK = CMARK_NODE_FOOTNOTE_DEFINITION; cmark_node_type CMARK_NODE_LAST_INLINE = CMARK_NODE_FOOTNOTE_REFERENCE; -int cmark_version() { return CMARK_GFM_VERSION; } +int cmark_version(void) { return CMARK_GFM_VERSION; } -const char *cmark_version_string() { return CMARK_GFM_VERSION_STRING; } +const char *cmark_version_string(void) { return CMARK_GFM_VERSION_STRING; } static void *xcalloc(size_t nmem, size_t size) { void *ptr = calloc(nmem, size); @@ -38,7 +38,7 @@ static void xfree(void *ptr) { cmark_mem CMARK_DEFAULT_MEM_ALLOCATOR = {xcalloc, xrealloc, xfree}; -cmark_mem *cmark_get_default_mem_allocator() { +cmark_mem *cmark_get_default_mem_allocator(void) { return &CMARK_DEFAULT_MEM_ALLOCATOR; } diff --git a/ext/commonmarker/html.c b/ext/commonmarker/html.c index 12d3c3e9..529e0ea3 100644 --- a/ext/commonmarker/html.c +++ b/ext/commonmarker/html.c @@ -63,10 +63,16 @@ static bool S_put_footnote_backref(cmark_html_renderer *renderer, cmark_strbuf * if (renderer->written_footnote_ix >= renderer->footnote_ix) return false; renderer->written_footnote_ix = renderer->footnote_ix; + char m[32]; + snprintf(m, sizeof(m), "%d", renderer->written_footnote_ix); cmark_strbuf_puts(html, "as.literal.data, node->as.literal.len); - cmark_strbuf_puts(html, "\" class=\"footnote-backref\" data-footnote-backref aria-label=\"Back to content\">↩"); + cmark_strbuf_puts(html, "\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\""); + cmark_strbuf_puts(html, m); + cmark_strbuf_puts(html, "\" aria-label=\"Back to reference "); + cmark_strbuf_puts(html, m); + cmark_strbuf_puts(html, "\">↩"); if (node->footnote.def_count > 1) { @@ -78,7 +84,15 @@ static bool S_put_footnote_backref(cmark_html_renderer *renderer, cmark_strbuf * houdini_escape_href(html, node->as.literal.data, node->as.literal.len); cmark_strbuf_puts(html, "-"); cmark_strbuf_puts(html, n); - cmark_strbuf_puts(html, "\" class=\"footnote-backref\" data-footnote-backref aria-label=\"Back to content\">↩"); + cmark_strbuf_puts(html, "\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\""); + cmark_strbuf_puts(html, m); + cmark_strbuf_puts(html, "-"); + cmark_strbuf_puts(html, n); + cmark_strbuf_puts(html, "\" aria-label=\"Back to reference "); + cmark_strbuf_puts(html, m); + cmark_strbuf_puts(html, "-"); + cmark_strbuf_puts(html, n); + cmark_strbuf_puts(html, "\">↩"); cmark_strbuf_puts(html, n); cmark_strbuf_puts(html, ""); } diff --git a/ext/commonmarker/node.c b/ext/commonmarker/node.c index 80c434e3..56daf0aa 100644 --- a/ext/commonmarker/node.c +++ b/ext/commonmarker/node.c @@ -9,12 +9,8 @@ static void S_node_unlink(cmark_node *node); #define NODE_MEM(node) cmark_node_mem(node) -cmark_node__internal_flags CMARK_NODE__OPEN; -cmark_node__internal_flags CMARK_NODE__LAST_LINE_BLANK; -cmark_node__internal_flags CMARK_NODE__LAST_LINE_CHECKED; - -void cmark_register_node_flag(cmark_node__internal_flags *flags) { - static uint8_t shift = 0; +void cmark_register_node_flag(cmark_node_internal_flags *flags) { + static cmark_node_internal_flags nextflag = CMARK_NODE__REGISTER_FIRST; // flags should be a pointer to a global variable and this function // should only be called once to initialize its value. @@ -24,24 +20,16 @@ void cmark_register_node_flag(cmark_node__internal_flags *flags) { } // Check that we haven't run out of bits. - if (shift >= 8 * sizeof(cmark_node__internal_flags)) { + if (nextflag == 0) { fprintf(stderr, "too many flags in cmark_register_node_flag\n"); abort(); } - *flags = (cmark_node__internal_flags)1 << shift; - shift++; + *flags = nextflag; + nextflag <<= 1; } -void cmark_init_standard_node_flags() { - static int initialized = 0; - if (!initialized) { - initialized = 1; - cmark_register_node_flag(&CMARK_NODE__OPEN); - cmark_register_node_flag(&CMARK_NODE__LAST_LINE_BLANK); - cmark_register_node_flag(&CMARK_NODE__LAST_LINE_CHECKED); - } -} +void cmark_init_standard_node_flags() {} bool cmark_node_can_contain_type(cmark_node *node, cmark_node_type child_type) { if (child_type == CMARK_NODE_DOCUMENT) { @@ -335,6 +323,14 @@ cmark_node *cmark_node_last_child(cmark_node *node) { } } +cmark_node *cmark_node_parent_footnote_def(cmark_node *node) { + if (node == NULL) { + return NULL; + } else { + return node->parent_footnote_def; + } +} + void *cmark_node_get_user_data(cmark_node *node) { if (node == NULL) { return NULL; diff --git a/ext/commonmarker/node.h b/ext/commonmarker/node.h index 281258f7..e025e25d 100644 --- a/ext/commonmarker/node.h +++ b/ext/commonmarker/node.h @@ -48,7 +48,17 @@ typedef struct { cmark_chunk on_exit; } cmark_custom; -typedef uint16_t cmark_node__internal_flags; +enum cmark_node__internal_flags { + CMARK_NODE__OPEN = (1 << 0), + CMARK_NODE__LAST_LINE_BLANK = (1 << 1), + CMARK_NODE__LAST_LINE_CHECKED = (1 << 2), + + // Extensions can register custom flags by calling `cmark_register_node_flag`. + // This is the starting value for the custom flags. + CMARK_NODE__REGISTER_FIRST = (1 << 3), +}; + +typedef uint16_t cmark_node_internal_flags; struct cmark_node { cmark_strbuf content; @@ -68,7 +78,7 @@ struct cmark_node { int end_column; int internal_offset; uint16_t type; - cmark_node__internal_flags flags; + cmark_node_internal_flags flags; cmark_syntax_extension *extension; @@ -98,19 +108,15 @@ struct cmark_node { * which will store the flag value. */ CMARK_GFM_EXPORT -void cmark_register_node_flag(cmark_node__internal_flags *flags); - -/** - * Standard node flags. (Initialized using `cmark_init_standard_node_flags`.) - */ -extern cmark_node__internal_flags CMARK_NODE__OPEN; -extern cmark_node__internal_flags CMARK_NODE__LAST_LINE_BLANK; -extern cmark_node__internal_flags CMARK_NODE__LAST_LINE_CHECKED; +void cmark_register_node_flag(cmark_node_internal_flags *flags); /** - * Uses `cmark_register_node_flag` to initialize the standard node flags. - * This function should be called at program startup time. Calling it - * multiple times has no additional effect. + * DEPRECATED. + * + * This function was added in cmark-gfm version 0.29.0.gfm.7, and was + * required to be called at program start time, which caused + * backwards-compatibility issues in applications that use cmark-gfm as a + * library. It is now a no-op. */ CMARK_GFM_EXPORT void cmark_init_standard_node_flags(); diff --git a/ext/commonmarker/table.c b/ext/commonmarker/table.c index 7f281322..e53ea315 100644 --- a/ext/commonmarker/table.c +++ b/ext/commonmarker/table.c @@ -12,7 +12,7 @@ #include "cmark-gfm-core-extensions.h" // Custom node flag, initialized in `create_table_extension`. -static cmark_node__internal_flags CMARK_NODE__TABLE_VISITED; +static cmark_node_internal_flags CMARK_NODE__TABLE_VISITED; cmark_node_type CMARK_NODE_TABLE, CMARK_NODE_TABLE_ROW, CMARK_NODE_TABLE_CELL; From 85c205798f7e7078a2abfdb9c1723616facffabf Mon Sep 17 00:00:00 2001 From: Phill MV Date: Tue, 31 Jan 2023 11:33:56 -0500 Subject: [PATCH 2/9] Added aria-label changes to test-footnotes.rb --- test/test_footnotes.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_footnotes.rb b/test/test_footnotes.rb index 7cdfd93a..0e00a913 100644 --- a/test/test_footnotes.rb +++ b/test/test_footnotes.rb @@ -13,7 +13,7 @@ def test_to_html
  1. -

    Hey!

    +

    Hey!

@@ -50,7 +50,7 @@ def test_render_html
  1. -

    This is a footnote

    +

    This is a footnote

From 5249f70a97f426d487bbead52f7d0ce03ab658af Mon Sep 17 00:00:00 2001 From: Phill MV Date: Tue, 31 Jan 2023 11:38:58 -0500 Subject: [PATCH 3/9] :gem: release 0.23.8 --- lib/commonmarker/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/commonmarker/version.rb b/lib/commonmarker/version.rb index 7d03a7ea..6405511f 100644 --- a/lib/commonmarker/version.rb +++ b/lib/commonmarker/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module CommonMarker - VERSION = "0.23.7" + VERSION = "0.23.8" end From d6fe4c8be4febe86f68c68884b08d21a66a2aff4 Mon Sep 17 00:00:00 2001 From: Phill MV Date: Fri, 31 Mar 2023 13:58:33 -0400 Subject: [PATCH 4/9] Update cmark-upstream to https://github.com/github/cmark-gfm/commit/dcf6b3862d2885b7d3dcdfc5b30c6a73526be32c --- ext/commonmarker/blocks.c | 119 ++++++++++++++++++++++++--- ext/commonmarker/cmark-gfm.h | 10 +++ ext/commonmarker/cmark-gfm_version.h | 4 +- ext/commonmarker/cmark-upstream | 2 +- ext/commonmarker/commonmark.c | 52 +++++------- ext/commonmarker/html.c | 10 ++- ext/commonmarker/latex.c | 10 ++- ext/commonmarker/man.c | 19 ++--- ext/commonmarker/node.c | 30 ++++--- ext/commonmarker/node.h | 22 ++++- ext/commonmarker/parser.h | 39 +++++++++ ext/commonmarker/plaintext.c | 42 +++------- ext/commonmarker/render.c | 17 ++-- ext/commonmarker/render.h | 1 + ext/commonmarker/syntax_extension.c | 5 +- ext/commonmarker/table.c | 6 ++ ext/commonmarker/xml.c | 3 +- 17 files changed, 271 insertions(+), 120 deletions(-) diff --git a/ext/commonmarker/blocks.c b/ext/commonmarker/blocks.c index 94045aba..b5a02b7d 100644 --- a/ext/commonmarker/blocks.c +++ b/ext/commonmarker/blocks.c @@ -70,6 +70,22 @@ static void S_parser_feed(cmark_parser *parser, const unsigned char *buffer, static void S_process_line(cmark_parser *parser, const unsigned char *buffer, bufsize_t bytes); +static void subtract_open_block_counts(cmark_parser *parser, cmark_node *node) { + do { + decr_open_block_count(parser, S_type(node)); + node->flags &= ~CMARK_NODE__OPEN_BLOCK; + node = node->last_child; + } while (node); +} + +static void add_open_block_counts(cmark_parser *parser, cmark_node *node) { + do { + incr_open_block_count(parser, S_type(node)); + node->flags |= CMARK_NODE__OPEN_BLOCK; + node = node->last_child; + } while (node); +} + static cmark_node *make_block(cmark_mem *mem, cmark_node_type tag, int start_line, int start_column) { cmark_node *e; @@ -129,6 +145,7 @@ static void cmark_parser_reset(cmark_parser *parser) { parser->refmap = cmark_reference_map_new(parser->mem); parser->root = document; parser->current = document; + add_open_block_counts(parser, document); parser->syntax_extensions = saved_exts; parser->inline_syntax_extensions = saved_inline_exts; @@ -242,15 +259,18 @@ static void remove_trailing_blank_lines(cmark_strbuf *ln) { // Check to see if a node ends with a blank line, descending // if needed into lists and sublists. static bool S_ends_with_blank_line(cmark_node *node) { - if (S_last_line_checked(node)) { - return(S_last_line_blank(node)); - } else if ((S_type(node) == CMARK_NODE_LIST || - S_type(node) == CMARK_NODE_ITEM) && node->last_child) { - S_set_last_line_checked(node); - return(S_ends_with_blank_line(node->last_child)); - } else { - S_set_last_line_checked(node); - return (S_last_line_blank(node)); + while (true) { + if (S_last_line_checked(node)) { + return(S_last_line_blank(node)); + } else if ((S_type(node) == CMARK_NODE_LIST || + S_type(node) == CMARK_NODE_ITEM) && node->last_child) { + S_set_last_line_checked(node); + node = node->last_child; + continue; + } else { + S_set_last_line_checked(node); + return (S_last_line_blank(node)); + } } } @@ -310,6 +330,12 @@ static cmark_node *finalize(cmark_parser *parser, cmark_node *b) { has_content = resolve_reference_link_definitions(parser, b); if (!has_content) { // remove blank node (former reference def) + if (b->flags & CMARK_NODE__OPEN_BLOCK) { + decr_open_block_count(parser, S_type(b)); + if (b->prev) { + add_open_block_counts(parser, b->prev); + } + } cmark_node_free(b); } break; @@ -382,6 +408,17 @@ static cmark_node *finalize(cmark_parser *parser, cmark_node *b) { return parent; } +// Recalculates the number of open blocks. Returns true if it matches what's currently stored +// in parser. (Used to check that the counts in parser, which are updated incrementally, are +// correct.) +bool check_open_block_counts(cmark_parser *parser) { + cmark_parser tmp_parser = {0}; // Only used for its open_block_counts and total_open_blocks fields. + add_open_block_counts(&tmp_parser, parser->root); + return + tmp_parser.total_open_blocks == parser->total_open_blocks && + memcmp(tmp_parser.open_block_counts, parser->open_block_counts, sizeof(parser->open_block_counts)) == 0; +} + // Add a node as child of another. Return pointer to child. static cmark_node *add_child(cmark_parser *parser, cmark_node *parent, cmark_node_type block_type, int start_column) { @@ -400,11 +437,14 @@ static cmark_node *add_child(cmark_parser *parser, cmark_node *parent, if (parent->last_child) { parent->last_child->next = child; child->prev = parent->last_child; + subtract_open_block_counts(parser, parent->last_child); } else { parent->first_child = child; child->prev = NULL; } parent->last_child = child; + add_open_block_counts(parser, child); + return child; } @@ -1047,8 +1087,14 @@ static cmark_node *check_open_blocks(cmark_parser *parser, cmark_chunk *input, *all_matched = false; cmark_node *container = parser->root; cmark_node_type cont_type; + cmark_parser tmp_parser; // Only used for its open_block_counts and total_open_blocks fields. + memcpy(tmp_parser.open_block_counts, parser->open_block_counts, sizeof(parser->open_block_counts)); + tmp_parser.total_open_blocks = parser->total_open_blocks; + + assert(check_open_block_counts(parser)); while (S_last_child_is_open(container)) { + decr_open_block_count(&tmp_parser, S_type(container)); container = container->last_child; cont_type = S_type(container); @@ -1060,6 +1106,53 @@ static cmark_node *check_open_blocks(cmark_parser *parser, cmark_chunk *input, continue; } + // This block of code is a workaround for the quadratic performance + // issue described here (issue 2): + // + // https://github.com/github/cmark-gfm/security/advisories/GHSA-66g8-4hjf-77xh + // + // If the current line is empty then we might be able to skip directly + // to the end of the list of open blocks. To determine whether this is + // possible, we have been maintaining a count of the number of + // different types of open blocks. The main criterium is that every + // remaining block, except the last element of the list, is a LIST or + // ITEM. The code below checks the conditions, and if they're ok, skips + // forward to parser->current. + if (parser->blank && parser->indent == 0) { // Current line is empty + // Make sure that parser->current doesn't point to a closed block. + if (parser->current->flags & CMARK_NODE__OPEN_BLOCK) { + if (parser->current->flags & CMARK_NODE__OPEN) { + const size_t n_list = read_open_block_count(&tmp_parser, CMARK_NODE_LIST); + const size_t n_item = read_open_block_count(&tmp_parser, CMARK_NODE_ITEM); + // At most one block can be something other than a LIST or ITEM. + if (n_list + n_item + 1 >= tmp_parser.total_open_blocks) { + // Check that parser->current is suitable for jumping to. + switch (S_type(parser->current)) { + case CMARK_NODE_LIST: + case CMARK_NODE_ITEM: + if (n_list + n_item != tmp_parser.total_open_blocks) { + if (parser->current->last_child == NULL) { + // There's another node type somewhere in the middle of + // the list, so don't attempt the optimization. + break; + } + } + // fall through + case CMARK_NODE_CODE_BLOCK: + case CMARK_NODE_PARAGRAPH: + case CMARK_NODE_HTML_BLOCK: + // Jump to parser->current + container = parser->current; + cont_type = S_type(container); + break; + default: + break; + } + } + } + } + } + switch (cont_type) { case CMARK_NODE_BLOCK_QUOTE: if (!parse_block_quote_prefix(parser, input)) @@ -1193,8 +1286,9 @@ static void open_new_blocks(cmark_parser *parser, cmark_node **container, has_content = resolve_reference_link_definitions(parser, *container); if (has_content) { - - (*container)->type = (uint16_t)CMARK_NODE_HEADING; + cmark_node_set_type(*container, CMARK_NODE_HEADING); + decr_open_block_count(parser, CMARK_NODE_PARAGRAPH); + incr_open_block_count(parser, CMARK_NODE_HEADING); (*container)->as.heading.level = lev; (*container)->as.heading.setext = true; S_advance_offset(parser, input, input->len - 1 - parser->offset, false); @@ -1349,7 +1443,7 @@ static void add_text_to_container(cmark_parser *parser, cmark_node *container, S_set_last_line_blank(container, last_line_blank); tmp = container; - while (tmp->parent) { + while (tmp->parent && S_last_line_blank(tmp->parent)) { S_set_last_line_blank(tmp->parent, false); tmp = tmp->parent; } @@ -1478,6 +1572,7 @@ static void S_process_line(cmark_parser *parser, const unsigned char *buffer, parser->line_number++; + assert(parser->current->next == NULL); last_matched_container = check_open_blocks(parser, &input, &all_matched); if (!last_matched_container) diff --git a/ext/commonmarker/cmark-gfm.h b/ext/commonmarker/cmark-gfm.h index 6b362a86..902c392d 100644 --- a/ext/commonmarker/cmark-gfm.h +++ b/ext/commonmarker/cmark-gfm.h @@ -37,6 +37,16 @@ char *cmark_markdown_to_html(const char *text, size_t len, int options); #define CMARK_NODE_TYPE_MASK (0xc000) #define CMARK_NODE_VALUE_MASK (0x3fff) +/** + * This is the maximum number of block types (CMARK_NODE_DOCUMENT, + * CMARK_NODE_HEADING, ...). It needs to be bigger than the number of + * hardcoded block types (below) to allow for extensions (see + * cmark_syntax_extension_add_node). But it also determines the size of the + * open_block_counts array in the cmark_parser struct, so we don't want it + * to be excessively large. + */ +#define CMARK_NODE_TYPE_BLOCK_LIMIT 0x20 + typedef enum { /* Error status */ CMARK_NODE_NONE = 0x0000, diff --git a/ext/commonmarker/cmark-gfm_version.h b/ext/commonmarker/cmark-gfm_version.h index e2bbfbbb..736f2e0e 100644 --- a/ext/commonmarker/cmark-gfm_version.h +++ b/ext/commonmarker/cmark-gfm_version.h @@ -1,7 +1,7 @@ #ifndef CMARK_GFM_VERSION_H #define CMARK_GFM_VERSION_H -#define CMARK_GFM_VERSION ((0 << 24) | (29 << 16) | (0 << 8) | 6) -#define CMARK_GFM_VERSION_STRING "0.29.0.gfm.6" +#define CMARK_GFM_VERSION ((0 << 24) | (29 << 16) | (0 << 8) | 10) +#define CMARK_GFM_VERSION_STRING "0.29.0.gfm.10" #endif diff --git a/ext/commonmarker/cmark-upstream b/ext/commonmarker/cmark-upstream index c32ef78b..dcf6b386 160000 --- a/ext/commonmarker/cmark-upstream +++ b/ext/commonmarker/cmark-upstream @@ -1 +1 @@ -Subproject commit c32ef78bae851cb83b7ad52d0fbff880acdcd44a +Subproject commit dcf6b3862d2885b7d3dcdfc5b30c6a73526be32c diff --git a/ext/commonmarker/commonmark.c b/ext/commonmarker/commonmark.c index 2e071944..f2210cdf 100644 --- a/ext/commonmarker/commonmark.c +++ b/ext/commonmarker/commonmark.c @@ -153,23 +153,8 @@ static bool is_autolink(cmark_node *node) { link_text->as.literal.len) == 0); } -// if node is a block node, returns node. -// otherwise returns first block-level node that is an ancestor of node. -// if there is no block-level ancestor, returns NULL. -static cmark_node *get_containing_block(cmark_node *node) { - while (node) { - if (CMARK_NODE_BLOCK_P(node)) { - return node; - } else { - node = node->parent; - } - } - return NULL; -} - static int S_render_node(cmark_renderer *renderer, cmark_node *node, cmark_event_type ev_type, int options) { - cmark_node *tmp; int list_number; cmark_delim_type list_delim; int numticks; @@ -189,14 +174,17 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, // Don't adjust tight list status til we've started the list. // Otherwise we loose the blank line between a paragraph and // a following list. - if (!(node->type == CMARK_NODE_ITEM && node->prev == NULL && entering)) { - tmp = get_containing_block(node); - renderer->in_tight_list_item = - tmp && // tmp might be NULL if there is no containing block - ((tmp->type == CMARK_NODE_ITEM && - cmark_node_get_list_tight(tmp->parent)) || - (tmp && tmp->parent && tmp->parent->type == CMARK_NODE_ITEM && - cmark_node_get_list_tight(tmp->parent->parent))); + if (entering) { + if (node->parent && node->parent->type == CMARK_NODE_ITEM) { + renderer->in_tight_list_item = node->parent->parent->as.list.tight; + } + } else { + if (node->type == CMARK_NODE_LIST) { + renderer->in_tight_list_item = + node->parent && + node->parent->type == CMARK_NODE_ITEM && + node->parent->parent->as.list.tight; + } } if (node->extension && node->extension->commonmark_render_func) { @@ -228,19 +216,15 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, LIT(""); BLANKLINE(); } + renderer->list_number = cmark_node_get_list_start(node); break; case CMARK_NODE_ITEM: if (cmark_node_get_list_type(node->parent) == CMARK_BULLET_LIST) { marker_width = 4; } else { - list_number = cmark_node_get_list_start(node->parent); + list_number = renderer->list_number++; list_delim = cmark_node_get_list_delim(node->parent); - tmp = node; - while (tmp->prev) { - tmp = tmp->prev; - list_number += 1; - } // we ensure a width of at least 4 so // we get nice transition from single digits // to double @@ -405,10 +389,12 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, break; case CMARK_NODE_STRONG: - if (entering) { - LIT("**"); - } else { - LIT("**"); + if (node->parent == NULL || node->parent->type != CMARK_NODE_STRONG) { + if (entering) { + LIT("**"); + } else { + LIT("**"); + } } break; diff --git a/ext/commonmarker/html.c b/ext/commonmarker/html.c index 529e0ea3..22513c93 100644 --- a/ext/commonmarker/html.c +++ b/ext/commonmarker/html.c @@ -364,10 +364,12 @@ static int S_render_node(cmark_html_renderer *renderer, cmark_node *node, break; case CMARK_NODE_STRONG: - if (entering) { - cmark_strbuf_puts(html, ""); - } else { - cmark_strbuf_puts(html, ""); + if (node->parent == NULL || node->parent->type != CMARK_NODE_STRONG) { + if (entering) { + cmark_strbuf_puts(html, ""); + } else { + cmark_strbuf_puts(html, ""); + } } break; diff --git a/ext/commonmarker/latex.c b/ext/commonmarker/latex.c index 8be15b0d..1a6367a4 100644 --- a/ext/commonmarker/latex.c +++ b/ext/commonmarker/latex.c @@ -385,10 +385,12 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, break; case CMARK_NODE_STRONG: - if (entering) { - LIT("\\textbf{"); - } else { - LIT("}"); + if (node->parent == NULL || node->parent->type != CMARK_NODE_STRONG) { + if (entering) { + LIT("\\textbf{"); + } else { + LIT("}"); + } } break; diff --git a/ext/commonmarker/man.c b/ext/commonmarker/man.c index 441a96e4..e40e46ce 100644 --- a/ext/commonmarker/man.c +++ b/ext/commonmarker/man.c @@ -74,7 +74,6 @@ static void S_outc(cmark_renderer *renderer, cmark_node *node, static int S_render_node(cmark_renderer *renderer, cmark_node *node, cmark_event_type ev_type, int options) { - cmark_node *tmp; int list_number; bool entering = (ev_type == CMARK_EVENT_ENTER); bool allow_wrap = renderer->width > 0 && !(CMARK_OPT_NOBREAKS & options); @@ -114,6 +113,7 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, break; case CMARK_NODE_LIST: + renderer->list_number = cmark_node_get_list_start(node); break; case CMARK_NODE_ITEM: @@ -123,12 +123,7 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, if (cmark_node_get_list_type(node->parent) == CMARK_BULLET_LIST) { LIT("\\[bu] 2"); } else { - list_number = cmark_node_get_list_start(node->parent); - tmp = node; - while (tmp->prev) { - tmp = tmp->prev; - list_number += 1; - } + list_number = renderer->list_number++; char list_number_s[LIST_NUMBER_SIZE]; snprintf(list_number_s, LIST_NUMBER_SIZE, "\"%d.\" 4", list_number); LIT(list_number_s); @@ -225,10 +220,12 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, break; case CMARK_NODE_STRONG: - if (entering) { - LIT("\\f[B]"); - } else { - LIT("\\f[]"); + if (node->parent == NULL || node->parent->type != CMARK_NODE_STRONG) { + if (entering) { + LIT("\\f[B]"); + } else { + LIT("\\f[]"); + } } break; diff --git a/ext/commonmarker/node.c b/ext/commonmarker/node.c index 56daf0aa..4866786d 100644 --- a/ext/commonmarker/node.c +++ b/ext/commonmarker/node.c @@ -5,6 +5,16 @@ #include "node.h" #include "syntax_extension.h" +/** + * Expensive safety checks are off by default, but can be enabled + * by calling cmark_enable_safety_checks(). + */ +static bool enable_safety_checks = false; + +void cmark_enable_safety_checks(bool enable) { + enable_safety_checks = enable; +} + static void S_node_unlink(cmark_node *node); #define NODE_MEM(node) cmark_node_mem(node) @@ -70,8 +80,6 @@ bool cmark_node_can_contain_type(cmark_node *node, cmark_node_type child_type) { } static bool S_can_contain(cmark_node *node, cmark_node *child) { - cmark_node *cur; - if (node == NULL || child == NULL) { return false; } @@ -79,14 +87,16 @@ static bool S_can_contain(cmark_node *node, cmark_node *child) { return 0; } - // Verify that child is not an ancestor of node or equal to node. - cur = node; - do { - if (cur == child) { - return false; - } - cur = cur->parent; - } while (cur != NULL); + if (enable_safety_checks) { + // Verify that child is not an ancestor of node or equal to node. + cmark_node *cur = node; + do { + if (cur == child) { + return false; + } + cur = cur->parent; + } while (cur != NULL); + } return cmark_node_can_contain_type(node, (cmark_node_type) child->type); } diff --git a/ext/commonmarker/node.h b/ext/commonmarker/node.h index e025e25d..ffebcb60 100644 --- a/ext/commonmarker/node.h +++ b/ext/commonmarker/node.h @@ -50,12 +50,13 @@ typedef struct { enum cmark_node__internal_flags { CMARK_NODE__OPEN = (1 << 0), - CMARK_NODE__LAST_LINE_BLANK = (1 << 1), - CMARK_NODE__LAST_LINE_CHECKED = (1 << 2), + CMARK_NODE__OPEN_BLOCK = (1 << 1), + CMARK_NODE__LAST_LINE_BLANK = (1 << 2), + CMARK_NODE__LAST_LINE_CHECKED = (1 << 3), // Extensions can register custom flags by calling `cmark_register_node_flag`. // This is the starting value for the custom flags. - CMARK_NODE__REGISTER_FIRST = (1 << 3), + CMARK_NODE__REGISTER_FIRST = (1 << 4), }; typedef uint16_t cmark_node_internal_flags; @@ -82,6 +83,14 @@ struct cmark_node { cmark_syntax_extension *extension; + /** + * Used during cmark_render() to cache the most recent non-NULL + * extension, if you go up the parent chain like this: + * + * node->parent->...parent->extension + */ + cmark_syntax_extension *ancestor_extension; + union { int ref_ix; int def_count; @@ -144,6 +153,13 @@ static CMARK_INLINE bool CMARK_NODE_INLINE_P(cmark_node *node) { CMARK_GFM_EXPORT bool cmark_node_can_contain_type(cmark_node *node, cmark_node_type child_type); +/** + * Enable (or disable) extra safety checks. These extra checks cause + * extra performance overhead (in some cases quadratic), so they are only + * intended to be used during testing. + */ +CMARK_GFM_EXPORT void cmark_enable_safety_checks(bool enable); + #ifdef __cplusplus } #endif diff --git a/ext/commonmarker/parser.h b/ext/commonmarker/parser.h index 436c53f5..05403fe3 100644 --- a/ext/commonmarker/parser.h +++ b/ext/commonmarker/parser.h @@ -50,8 +50,47 @@ struct cmark_parser { cmark_llist *syntax_extensions; cmark_llist *inline_syntax_extensions; cmark_ispunct_func backslash_ispunct; + + /** + * The "open" blocks are the blocks visited by the loop in + * check_open_blocks (blocks.c). I.e. the blocks in this list: + * + * parser->root->last_child->...->last_child + * + * open_block_counts is used to keep track of how many of each type of + * node are currently in the open blocks list. Knowing these counts can + * sometimes help to end the loop in check_open_blocks early, improving + * efficiency. + * + * The count is stored at this offset: type - CMARK_NODE_TYPE_BLOCK - 1 + * For example, CMARK_NODE_LIST (0x8003) is stored at offset 2. + */ + size_t open_block_counts[CMARK_NODE_TYPE_BLOCK_LIMIT]; + size_t total_open_blocks; }; +static CMARK_INLINE void incr_open_block_count(cmark_parser *parser, cmark_node_type type) { + assert(type > CMARK_NODE_TYPE_BLOCK); + assert(type <= CMARK_NODE_TYPE_BLOCK + CMARK_NODE_TYPE_BLOCK_LIMIT); + parser->open_block_counts[type - CMARK_NODE_TYPE_BLOCK - 1]++; + parser->total_open_blocks++; +} + +static CMARK_INLINE void decr_open_block_count(cmark_parser *parser, cmark_node_type type) { + assert(type > CMARK_NODE_TYPE_BLOCK); + assert(type <= CMARK_NODE_TYPE_BLOCK + CMARK_NODE_TYPE_BLOCK_LIMIT); + assert(parser->open_block_counts[type - CMARK_NODE_TYPE_BLOCK - 1] > 0); + parser->open_block_counts[type - CMARK_NODE_TYPE_BLOCK - 1]--; + assert(parser->total_open_blocks > 0); + parser->total_open_blocks--; +} + +static CMARK_INLINE size_t read_open_block_count(cmark_parser *parser, cmark_node_type type) { + assert(type > CMARK_NODE_TYPE_BLOCK); + assert(type <= CMARK_NODE_TYPE_BLOCK + CMARK_NODE_TYPE_BLOCK_LIMIT); + return parser->open_block_counts[type - CMARK_NODE_TYPE_BLOCK - 1]; +} + #ifdef __cplusplus } #endif diff --git a/ext/commonmarker/plaintext.c b/ext/commonmarker/plaintext.c index b25e4a39..a4047621 100644 --- a/ext/commonmarker/plaintext.c +++ b/ext/commonmarker/plaintext.c @@ -16,23 +16,8 @@ static CMARK_INLINE void outc(cmark_renderer *renderer, cmark_node *node, cmark_render_code_point(renderer, c); } -// if node is a block node, returns node. -// otherwise returns first block-level node that is an ancestor of node. -// if there is no block-level ancestor, returns NULL. -static cmark_node *get_containing_block(cmark_node *node) { - while (node) { - if (CMARK_NODE_BLOCK_P(node)) { - return node; - } else { - node = node->parent; - } - } - return NULL; -} - static int S_render_node(cmark_renderer *renderer, cmark_node *node, cmark_event_type ev_type, int options) { - cmark_node *tmp; int list_number; cmark_delim_type list_delim; int i; @@ -46,14 +31,17 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, // Don't adjust tight list status til we've started the list. // Otherwise we loose the blank line between a paragraph and // a following list. - if (!(node->type == CMARK_NODE_ITEM && node->prev == NULL && entering)) { - tmp = get_containing_block(node); - renderer->in_tight_list_item = - tmp && // tmp might be NULL if there is no containing block - ((tmp->type == CMARK_NODE_ITEM && - cmark_node_get_list_tight(tmp->parent)) || - (tmp && tmp->parent && tmp->parent->type == CMARK_NODE_ITEM && - cmark_node_get_list_tight(tmp->parent->parent))); + if (entering) { + if (node->parent && node->parent->type == CMARK_NODE_ITEM) { + renderer->in_tight_list_item = node->parent->parent->as.list.tight; + } + } else { + if (node->type == CMARK_NODE_LIST) { + renderer->in_tight_list_item = + node->parent && + node->parent->type == CMARK_NODE_ITEM && + node->parent->parent->as.list.tight; + } } if (node->extension && node->extension->plaintext_render_func) { @@ -73,19 +61,15 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, node->next->type == CMARK_NODE_LIST)) { CR(); } + renderer->list_number = cmark_node_get_list_start(node); break; case CMARK_NODE_ITEM: if (cmark_node_get_list_type(node->parent) == CMARK_BULLET_LIST) { marker_width = 4; } else { - list_number = cmark_node_get_list_start(node->parent); + list_number = renderer->list_number++; list_delim = cmark_node_get_list_delim(node->parent); - tmp = node; - while (tmp->prev) { - tmp = tmp->prev; - list_number += 1; - } // we ensure a width of at least 4 so // we get nice transition from single digits // to double diff --git a/ext/commonmarker/render.c b/ext/commonmarker/render.c index 02e9e838..d7a83ebf 100644 --- a/ext/commonmarker/render.c +++ b/ext/commonmarker/render.c @@ -31,13 +31,7 @@ static void S_out(cmark_renderer *renderer, cmark_node *node, cmark_chunk remainder = cmark_chunk_literal(""); int k = renderer->buffer->size - 1; - cmark_syntax_extension *ext = NULL; - cmark_node *n = node; - while (n && !ext) { - ext = n->extension; - if (!ext) - n = n->parent; - } + cmark_syntax_extension *ext = node->ancestor_extension; if (ext && !ext->commonmark_escape_func) ext = NULL; @@ -177,11 +171,16 @@ char *cmark_render(cmark_mem *mem, cmark_node *root, int options, int width, cmark_renderer renderer = {mem, &buf, &pref, 0, width, 0, 0, true, true, false, - false, outc, S_cr, S_blankline, S_out, - 0}; + false, 0, outc, S_cr, S_blankline, + S_out, 0}; while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) { cur = cmark_iter_get_node(iter); + if (cur->extension) { + cur->ancestor_extension = cur->extension; + } else if (cur->parent) { + cur->ancestor_extension = cur->parent->ancestor_extension; + } if (!render_node(&renderer, cur, ev_type, options)) { // a false value causes us to skip processing // the node's contents. this is used for diff --git a/ext/commonmarker/render.h b/ext/commonmarker/render.h index 4a68d1e0..aa5162f9 100644 --- a/ext/commonmarker/render.h +++ b/ext/commonmarker/render.h @@ -23,6 +23,7 @@ struct cmark_renderer { bool begin_content; bool no_linebreaks; bool in_tight_list_item; + int list_number; void (*outc)(struct cmark_renderer *, cmark_node *, cmark_escaping, int32_t, unsigned char); void (*cr)(struct cmark_renderer *); void (*blankline)(struct cmark_renderer *); diff --git a/ext/commonmarker/syntax_extension.c b/ext/commonmarker/syntax_extension.c index d24fe43e..a2fb3b04 100644 --- a/ext/commonmarker/syntax_extension.c +++ b/ext/commonmarker/syntax_extension.c @@ -29,7 +29,10 @@ cmark_syntax_extension *cmark_syntax_extension_new(const char *name) { cmark_node_type cmark_syntax_extension_add_node(int is_inline) { cmark_node_type *ref = !is_inline ? &CMARK_NODE_LAST_BLOCK : &CMARK_NODE_LAST_INLINE; - if ((*ref & CMARK_NODE_VALUE_MASK) == CMARK_NODE_VALUE_MASK) { + if ((*ref & CMARK_NODE_VALUE_MASK) >= CMARK_NODE_TYPE_BLOCK_LIMIT) { + // This assertion will fail if you try to register more extensions than + // are currently allowed by CMARK_NODE_TYPE_BLOCK_MAXNUM. Try increasing + // the limit. assert(false); return (cmark_node_type) 0; } diff --git a/ext/commonmarker/table.c b/ext/commonmarker/table.c index e53ea315..6e75e38d 100644 --- a/ext/commonmarker/table.c +++ b/ext/commonmarker/table.c @@ -311,12 +311,18 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self, } } + assert(cmark_node_get_type(parent_container) == CMARK_NODE_PARAGRAPH); if (!cmark_node_set_type(parent_container, CMARK_NODE_TABLE)) { free_table_row(parser->mem, header_row); free_table_row(parser->mem, marker_row); return parent_container; } + // Update the node counts after parent_container changed type. + assert(parent_container->next == NULL); + decr_open_block_count(parser, CMARK_NODE_PARAGRAPH); + incr_open_block_count(parser, CMARK_NODE_TABLE); + if (header_row->paragraph_offset) { try_inserting_table_header_paragraph(parser, parent_container, (unsigned char *)parent_string, header_row->paragraph_offset); diff --git a/ext/commonmarker/xml.c b/ext/commonmarker/xml.c index 2975bf96..5753e5ab 100644 --- a/ext/commonmarker/xml.c +++ b/ext/commonmarker/xml.c @@ -11,6 +11,7 @@ #include "syntax_extension.h" #define BUFFER_SIZE 100 +#define MAX_INDENT 40 // Functions to convert cmark_nodes to XML strings. @@ -26,7 +27,7 @@ struct render_state { static CMARK_INLINE void indent(struct render_state *state) { int i; - for (i = 0; i < state->indent; i++) { + for (i = 0; i < state->indent && i < MAX_INDENT; i++) { cmark_strbuf_putc(state->xml, ' '); } } From f303e6bae7d83c76f3a929fa9e59ae2e3ac27081 Mon Sep 17 00:00:00 2001 From: Phill MV Date: Fri, 31 Mar 2023 14:01:10 -0400 Subject: [PATCH 5/9] :gem: release 0.23.9 --- lib/commonmarker/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/commonmarker/version.rb b/lib/commonmarker/version.rb index 6405511f..77032fd8 100644 --- a/lib/commonmarker/version.rb +++ b/lib/commonmarker/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module CommonMarker - VERSION = "0.23.8" + VERSION = "0.23.9" end From bbb49db7223d71ad9366309c358c7c09a4e1c5a8 Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Sat, 1 Apr 2023 17:27:11 +1100 Subject: [PATCH 6/9] HtmlRenderer: don't nest --- lib/commonmarker/renderer/html_renderer.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/commonmarker/renderer/html_renderer.rb b/lib/commonmarker/renderer/html_renderer.rb index df10042d..2d8375ab 100644 --- a/lib/commonmarker/renderer/html_renderer.rb +++ b/lib/commonmarker/renderer/html_renderer.rb @@ -129,8 +129,12 @@ def emph(_) out("", :children, "") end - def strong(_) - out("", :children, "") + def strong(node) + if node.parent&.type == :strong + out(:children) + else + out("", :children, "") + end end def link(node) From 2eb8ca8f2f4463448806663d72218cdcc0a9f38c Mon Sep 17 00:00:00 2001 From: Bas Alberts Date: Thu, 6 Apr 2023 14:42:40 -0400 Subject: [PATCH 7/9] Update cmark-upstream to https://github.com/github/cmark-gfm/commit/c8dcdc71ce1e2028d2d4d77c09c156512ffb3fa2 --- ext/commonmarker/blocks.c | 131 +++++----------------------- ext/commonmarker/cmark-gfm.h | 21 ++--- ext/commonmarker/cmark-upstream | 2 +- ext/commonmarker/commonmark.c | 3 +- ext/commonmarker/man.c | 3 +- ext/commonmarker/node.c | 27 +++++- ext/commonmarker/node.h | 9 +- ext/commonmarker/parser.h | 39 --------- ext/commonmarker/plaintext.c | 3 +- ext/commonmarker/render.c | 13 ++- ext/commonmarker/render.h | 1 - ext/commonmarker/syntax_extension.c | 5 +- ext/commonmarker/table.c | 6 -- 13 files changed, 80 insertions(+), 183 deletions(-) diff --git a/ext/commonmarker/blocks.c b/ext/commonmarker/blocks.c index b5a02b7d..03a58748 100644 --- a/ext/commonmarker/blocks.c +++ b/ext/commonmarker/blocks.c @@ -27,6 +27,14 @@ #define CODE_INDENT 4 #define TAB_STOP 4 +/** + * Very deeply nested lists can cause quadratic performance issues. + * This constant is used in open_new_blocks() to limit the nesting + * depth. It is unlikely that a non-contrived markdown document will + * be nested this deeply. + */ +#define MAX_LIST_DEPTH 100 + #ifndef MIN #define MIN(x, y) ((x < y) ? x : y) #endif @@ -70,22 +78,6 @@ static void S_parser_feed(cmark_parser *parser, const unsigned char *buffer, static void S_process_line(cmark_parser *parser, const unsigned char *buffer, bufsize_t bytes); -static void subtract_open_block_counts(cmark_parser *parser, cmark_node *node) { - do { - decr_open_block_count(parser, S_type(node)); - node->flags &= ~CMARK_NODE__OPEN_BLOCK; - node = node->last_child; - } while (node); -} - -static void add_open_block_counts(cmark_parser *parser, cmark_node *node) { - do { - incr_open_block_count(parser, S_type(node)); - node->flags |= CMARK_NODE__OPEN_BLOCK; - node = node->last_child; - } while (node); -} - static cmark_node *make_block(cmark_mem *mem, cmark_node_type tag, int start_line, int start_column) { cmark_node *e; @@ -145,7 +137,6 @@ static void cmark_parser_reset(cmark_parser *parser) { parser->refmap = cmark_reference_map_new(parser->mem); parser->root = document; parser->current = document; - add_open_block_counts(parser, document); parser->syntax_extensions = saved_exts; parser->inline_syntax_extensions = saved_inline_exts; @@ -259,18 +250,15 @@ static void remove_trailing_blank_lines(cmark_strbuf *ln) { // Check to see if a node ends with a blank line, descending // if needed into lists and sublists. static bool S_ends_with_blank_line(cmark_node *node) { - while (true) { - if (S_last_line_checked(node)) { - return(S_last_line_blank(node)); - } else if ((S_type(node) == CMARK_NODE_LIST || - S_type(node) == CMARK_NODE_ITEM) && node->last_child) { - S_set_last_line_checked(node); - node = node->last_child; - continue; - } else { - S_set_last_line_checked(node); - return (S_last_line_blank(node)); - } + if (S_last_line_checked(node)) { + return(S_last_line_blank(node)); + } else if ((S_type(node) == CMARK_NODE_LIST || + S_type(node) == CMARK_NODE_ITEM) && node->last_child) { + S_set_last_line_checked(node); + return(S_ends_with_blank_line(node->last_child)); + } else { + S_set_last_line_checked(node); + return (S_last_line_blank(node)); } } @@ -330,12 +318,6 @@ static cmark_node *finalize(cmark_parser *parser, cmark_node *b) { has_content = resolve_reference_link_definitions(parser, b); if (!has_content) { // remove blank node (former reference def) - if (b->flags & CMARK_NODE__OPEN_BLOCK) { - decr_open_block_count(parser, S_type(b)); - if (b->prev) { - add_open_block_counts(parser, b->prev); - } - } cmark_node_free(b); } break; @@ -408,17 +390,6 @@ static cmark_node *finalize(cmark_parser *parser, cmark_node *b) { return parent; } -// Recalculates the number of open blocks. Returns true if it matches what's currently stored -// in parser. (Used to check that the counts in parser, which are updated incrementally, are -// correct.) -bool check_open_block_counts(cmark_parser *parser) { - cmark_parser tmp_parser = {0}; // Only used for its open_block_counts and total_open_blocks fields. - add_open_block_counts(&tmp_parser, parser->root); - return - tmp_parser.total_open_blocks == parser->total_open_blocks && - memcmp(tmp_parser.open_block_counts, parser->open_block_counts, sizeof(parser->open_block_counts)) == 0; -} - // Add a node as child of another. Return pointer to child. static cmark_node *add_child(cmark_parser *parser, cmark_node *parent, cmark_node_type block_type, int start_column) { @@ -437,14 +408,11 @@ static cmark_node *add_child(cmark_parser *parser, cmark_node *parent, if (parent->last_child) { parent->last_child->next = child; child->prev = parent->last_child; - subtract_open_block_counts(parser, parent->last_child); } else { parent->first_child = child; child->prev = NULL; } parent->last_child = child; - add_open_block_counts(parser, child); - return child; } @@ -1087,14 +1055,8 @@ static cmark_node *check_open_blocks(cmark_parser *parser, cmark_chunk *input, *all_matched = false; cmark_node *container = parser->root; cmark_node_type cont_type; - cmark_parser tmp_parser; // Only used for its open_block_counts and total_open_blocks fields. - memcpy(tmp_parser.open_block_counts, parser->open_block_counts, sizeof(parser->open_block_counts)); - tmp_parser.total_open_blocks = parser->total_open_blocks; - - assert(check_open_block_counts(parser)); while (S_last_child_is_open(container)) { - decr_open_block_count(&tmp_parser, S_type(container)); container = container->last_child; cont_type = S_type(container); @@ -1106,53 +1068,6 @@ static cmark_node *check_open_blocks(cmark_parser *parser, cmark_chunk *input, continue; } - // This block of code is a workaround for the quadratic performance - // issue described here (issue 2): - // - // https://github.com/github/cmark-gfm/security/advisories/GHSA-66g8-4hjf-77xh - // - // If the current line is empty then we might be able to skip directly - // to the end of the list of open blocks. To determine whether this is - // possible, we have been maintaining a count of the number of - // different types of open blocks. The main criterium is that every - // remaining block, except the last element of the list, is a LIST or - // ITEM. The code below checks the conditions, and if they're ok, skips - // forward to parser->current. - if (parser->blank && parser->indent == 0) { // Current line is empty - // Make sure that parser->current doesn't point to a closed block. - if (parser->current->flags & CMARK_NODE__OPEN_BLOCK) { - if (parser->current->flags & CMARK_NODE__OPEN) { - const size_t n_list = read_open_block_count(&tmp_parser, CMARK_NODE_LIST); - const size_t n_item = read_open_block_count(&tmp_parser, CMARK_NODE_ITEM); - // At most one block can be something other than a LIST or ITEM. - if (n_list + n_item + 1 >= tmp_parser.total_open_blocks) { - // Check that parser->current is suitable for jumping to. - switch (S_type(parser->current)) { - case CMARK_NODE_LIST: - case CMARK_NODE_ITEM: - if (n_list + n_item != tmp_parser.total_open_blocks) { - if (parser->current->last_child == NULL) { - // There's another node type somewhere in the middle of - // the list, so don't attempt the optimization. - break; - } - } - // fall through - case CMARK_NODE_CODE_BLOCK: - case CMARK_NODE_PARAGRAPH: - case CMARK_NODE_HTML_BLOCK: - // Jump to parser->current - container = parser->current; - cont_type = S_type(container); - break; - default: - break; - } - } - } - } - } - switch (cont_type) { case CMARK_NODE_BLOCK_QUOTE: if (!parse_block_quote_prefix(parser, input)) @@ -1212,10 +1127,11 @@ static void open_new_blocks(cmark_parser *parser, cmark_node **container, bool has_content; int save_offset; int save_column; + size_t depth = 0; while (cont_type != CMARK_NODE_CODE_BLOCK && cont_type != CMARK_NODE_HTML_BLOCK) { - + depth++; S_find_first_nonspace(parser, input); indented = parser->indent >= CODE_INDENT; @@ -1286,9 +1202,8 @@ static void open_new_blocks(cmark_parser *parser, cmark_node **container, has_content = resolve_reference_link_definitions(parser, *container); if (has_content) { - cmark_node_set_type(*container, CMARK_NODE_HEADING); - decr_open_block_count(parser, CMARK_NODE_PARAGRAPH); - incr_open_block_count(parser, CMARK_NODE_HEADING); + + (*container)->type = (uint16_t)CMARK_NODE_HEADING; (*container)->as.heading.level = lev; (*container)->as.heading.setext = true; S_advance_offset(parser, input, input->len - 1 - parser->offset, false); @@ -1318,6 +1233,7 @@ static void open_new_blocks(cmark_parser *parser, cmark_node **container, (*container)->internal_offset = matched; } else if ((!indented || cont_type == CMARK_NODE_LIST) && parser->indent < 4 && + depth < MAX_LIST_DEPTH && (matched = parse_list_marker( parser->mem, input, parser->first_nonspace, (*container)->type == CMARK_NODE_PARAGRAPH, &data))) { @@ -1443,7 +1359,7 @@ static void add_text_to_container(cmark_parser *parser, cmark_node *container, S_set_last_line_blank(container, last_line_blank); tmp = container; - while (tmp->parent && S_last_line_blank(tmp->parent)) { + while (tmp->parent) { S_set_last_line_blank(tmp->parent, false); tmp = tmp->parent; } @@ -1572,7 +1488,6 @@ static void S_process_line(cmark_parser *parser, const unsigned char *buffer, parser->line_number++; - assert(parser->current->next == NULL); last_matched_container = check_open_blocks(parser, &input, &all_matched); if (!last_matched_container) diff --git a/ext/commonmarker/cmark-gfm.h b/ext/commonmarker/cmark-gfm.h index 902c392d..0544057a 100644 --- a/ext/commonmarker/cmark-gfm.h +++ b/ext/commonmarker/cmark-gfm.h @@ -37,16 +37,6 @@ char *cmark_markdown_to_html(const char *text, size_t len, int options); #define CMARK_NODE_TYPE_MASK (0xc000) #define CMARK_NODE_VALUE_MASK (0x3fff) -/** - * This is the maximum number of block types (CMARK_NODE_DOCUMENT, - * CMARK_NODE_HEADING, ...). It needs to be bigger than the number of - * hardcoded block types (below) to allow for extensions (see - * cmark_syntax_extension_add_node). But it also determines the size of the - * open_block_counts array in the cmark_parser struct, so we don't want it - * to be excessively large. - */ -#define CMARK_NODE_TYPE_BLOCK_LIMIT 0x20 - typedef enum { /* Error status */ CMARK_NODE_NONE = 0x0000, @@ -423,6 +413,17 @@ CMARK_GFM_EXPORT int cmark_node_get_list_tight(cmark_node *node); */ CMARK_GFM_EXPORT int cmark_node_set_list_tight(cmark_node *node, int tight); +/** + * Returns item index of 'node'. This is only used when rendering output + * formats such as commonmark, which need to output the index. It is not + * required for formats such as html or latex. + */ +CMARK_GFM_EXPORT int cmark_node_get_item_index(cmark_node *node); + +/** Sets item index of 'node'. Returns 1 on success, 0 on failure. + */ +CMARK_GFM_EXPORT int cmark_node_set_item_index(cmark_node *node, int idx); + /** Returns the info string from a fenced code block. */ CMARK_GFM_EXPORT const char *cmark_node_get_fence_info(cmark_node *node); diff --git a/ext/commonmarker/cmark-upstream b/ext/commonmarker/cmark-upstream index dcf6b386..c8dcdc71 160000 --- a/ext/commonmarker/cmark-upstream +++ b/ext/commonmarker/cmark-upstream @@ -1 +1 @@ -Subproject commit dcf6b3862d2885b7d3dcdfc5b30c6a73526be32c +Subproject commit c8dcdc71ce1e2028d2d4d77c09c156512ffb3fa2 diff --git a/ext/commonmarker/commonmark.c b/ext/commonmarker/commonmark.c index f2210cdf..4815bfc3 100644 --- a/ext/commonmarker/commonmark.c +++ b/ext/commonmarker/commonmark.c @@ -216,14 +216,13 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, LIT(""); BLANKLINE(); } - renderer->list_number = cmark_node_get_list_start(node); break; case CMARK_NODE_ITEM: if (cmark_node_get_list_type(node->parent) == CMARK_BULLET_LIST) { marker_width = 4; } else { - list_number = renderer->list_number++; + list_number = cmark_node_get_item_index(node); list_delim = cmark_node_get_list_delim(node->parent); // we ensure a width of at least 4 so // we get nice transition from single digits diff --git a/ext/commonmarker/man.c b/ext/commonmarker/man.c index e40e46ce..634fd9d0 100644 --- a/ext/commonmarker/man.c +++ b/ext/commonmarker/man.c @@ -113,7 +113,6 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, break; case CMARK_NODE_LIST: - renderer->list_number = cmark_node_get_list_start(node); break; case CMARK_NODE_ITEM: @@ -123,7 +122,7 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, if (cmark_node_get_list_type(node->parent) == CMARK_BULLET_LIST) { LIT("\\[bu] 2"); } else { - list_number = renderer->list_number++; + list_number = cmark_node_get_item_index(node); char list_number_s[LIST_NUMBER_SIZE]; snprintf(list_number_s, LIST_NUMBER_SIZE, "\"%d.\" 4", list_number); LIT(list_number_s); diff --git a/ext/commonmarker/node.c b/ext/commonmarker/node.c index 4866786d..67f657d8 100644 --- a/ext/commonmarker/node.c +++ b/ext/commonmarker/node.c @@ -39,7 +39,7 @@ void cmark_register_node_flag(cmark_node_internal_flags *flags) { nextflag <<= 1; } -void cmark_init_standard_node_flags() {} +void cmark_init_standard_node_flags(void) {} bool cmark_node_can_contain_type(cmark_node *node, cmark_node_type child_type) { if (child_type == CMARK_NODE_DOCUMENT) { @@ -564,6 +564,31 @@ int cmark_node_set_list_tight(cmark_node *node, int tight) { } } +int cmark_node_get_item_index(cmark_node *node) { + if (node == NULL) { + return 0; + } + + if (node->type == CMARK_NODE_ITEM) { + return node->as.list.start; + } else { + return 0; + } +} + +int cmark_node_set_item_index(cmark_node *node, int idx) { + if (node == NULL || idx < 0) { + return 0; + } + + if (node->type == CMARK_NODE_ITEM) { + node->as.list.start = idx; + return 1; + } else { + return 0; + } +} + const char *cmark_node_get_fence_info(cmark_node *node) { if (node == NULL) { return NULL; diff --git a/ext/commonmarker/node.h b/ext/commonmarker/node.h index ffebcb60..38ac4a6f 100644 --- a/ext/commonmarker/node.h +++ b/ext/commonmarker/node.h @@ -50,13 +50,12 @@ typedef struct { enum cmark_node__internal_flags { CMARK_NODE__OPEN = (1 << 0), - CMARK_NODE__OPEN_BLOCK = (1 << 1), - CMARK_NODE__LAST_LINE_BLANK = (1 << 2), - CMARK_NODE__LAST_LINE_CHECKED = (1 << 3), + CMARK_NODE__LAST_LINE_BLANK = (1 << 1), + CMARK_NODE__LAST_LINE_CHECKED = (1 << 2), // Extensions can register custom flags by calling `cmark_register_node_flag`. // This is the starting value for the custom flags. - CMARK_NODE__REGISTER_FIRST = (1 << 4), + CMARK_NODE__REGISTER_FIRST = (1 << 3), }; typedef uint16_t cmark_node_internal_flags; @@ -128,7 +127,7 @@ void cmark_register_node_flag(cmark_node_internal_flags *flags); * library. It is now a no-op. */ CMARK_GFM_EXPORT -void cmark_init_standard_node_flags(); +void cmark_init_standard_node_flags(void); static CMARK_INLINE cmark_mem *cmark_node_mem(cmark_node *node) { return node->content.mem; diff --git a/ext/commonmarker/parser.h b/ext/commonmarker/parser.h index 05403fe3..436c53f5 100644 --- a/ext/commonmarker/parser.h +++ b/ext/commonmarker/parser.h @@ -50,47 +50,8 @@ struct cmark_parser { cmark_llist *syntax_extensions; cmark_llist *inline_syntax_extensions; cmark_ispunct_func backslash_ispunct; - - /** - * The "open" blocks are the blocks visited by the loop in - * check_open_blocks (blocks.c). I.e. the blocks in this list: - * - * parser->root->last_child->...->last_child - * - * open_block_counts is used to keep track of how many of each type of - * node are currently in the open blocks list. Knowing these counts can - * sometimes help to end the loop in check_open_blocks early, improving - * efficiency. - * - * The count is stored at this offset: type - CMARK_NODE_TYPE_BLOCK - 1 - * For example, CMARK_NODE_LIST (0x8003) is stored at offset 2. - */ - size_t open_block_counts[CMARK_NODE_TYPE_BLOCK_LIMIT]; - size_t total_open_blocks; }; -static CMARK_INLINE void incr_open_block_count(cmark_parser *parser, cmark_node_type type) { - assert(type > CMARK_NODE_TYPE_BLOCK); - assert(type <= CMARK_NODE_TYPE_BLOCK + CMARK_NODE_TYPE_BLOCK_LIMIT); - parser->open_block_counts[type - CMARK_NODE_TYPE_BLOCK - 1]++; - parser->total_open_blocks++; -} - -static CMARK_INLINE void decr_open_block_count(cmark_parser *parser, cmark_node_type type) { - assert(type > CMARK_NODE_TYPE_BLOCK); - assert(type <= CMARK_NODE_TYPE_BLOCK + CMARK_NODE_TYPE_BLOCK_LIMIT); - assert(parser->open_block_counts[type - CMARK_NODE_TYPE_BLOCK - 1] > 0); - parser->open_block_counts[type - CMARK_NODE_TYPE_BLOCK - 1]--; - assert(parser->total_open_blocks > 0); - parser->total_open_blocks--; -} - -static CMARK_INLINE size_t read_open_block_count(cmark_parser *parser, cmark_node_type type) { - assert(type > CMARK_NODE_TYPE_BLOCK); - assert(type <= CMARK_NODE_TYPE_BLOCK + CMARK_NODE_TYPE_BLOCK_LIMIT); - return parser->open_block_counts[type - CMARK_NODE_TYPE_BLOCK - 1]; -} - #ifdef __cplusplus } #endif diff --git a/ext/commonmarker/plaintext.c b/ext/commonmarker/plaintext.c index a4047621..0c7d257b 100644 --- a/ext/commonmarker/plaintext.c +++ b/ext/commonmarker/plaintext.c @@ -61,14 +61,13 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, node->next->type == CMARK_NODE_LIST)) { CR(); } - renderer->list_number = cmark_node_get_list_start(node); break; case CMARK_NODE_ITEM: if (cmark_node_get_list_type(node->parent) == CMARK_BULLET_LIST) { marker_width = 4; } else { - list_number = renderer->list_number++; + list_number = cmark_node_get_item_index(node); list_delim = cmark_node_get_list_delim(node->parent); // we ensure a width of at least 4 so // we get nice transition from single digits diff --git a/ext/commonmarker/render.c b/ext/commonmarker/render.c index d7a83ebf..1a0d2ae8 100644 --- a/ext/commonmarker/render.c +++ b/ext/commonmarker/render.c @@ -171,8 +171,8 @@ char *cmark_render(cmark_mem *mem, cmark_node *root, int options, int width, cmark_renderer renderer = {mem, &buf, &pref, 0, width, 0, 0, true, true, false, - false, 0, outc, S_cr, S_blankline, - S_out, 0}; + false, outc, S_cr, S_blankline, S_out, + 0}; while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) { cur = cmark_iter_get_node(iter); @@ -181,6 +181,15 @@ char *cmark_render(cmark_mem *mem, cmark_node *root, int options, int width, } else if (cur->parent) { cur->ancestor_extension = cur->parent->ancestor_extension; } + if (cur->type == CMARK_NODE_ITEM) { + // Calculate the list item's index, for the benefit of output formats + // like commonmark and plaintext. + if (cur->prev) { + cmark_node_set_item_index(cur, 1 + cmark_node_get_item_index(cur->prev)); + } else { + cmark_node_set_item_index(cur, cmark_node_get_list_start(cur->parent)); + } + } if (!render_node(&renderer, cur, ev_type, options)) { // a false value causes us to skip processing // the node's contents. this is used for diff --git a/ext/commonmarker/render.h b/ext/commonmarker/render.h index aa5162f9..4a68d1e0 100644 --- a/ext/commonmarker/render.h +++ b/ext/commonmarker/render.h @@ -23,7 +23,6 @@ struct cmark_renderer { bool begin_content; bool no_linebreaks; bool in_tight_list_item; - int list_number; void (*outc)(struct cmark_renderer *, cmark_node *, cmark_escaping, int32_t, unsigned char); void (*cr)(struct cmark_renderer *); void (*blankline)(struct cmark_renderer *); diff --git a/ext/commonmarker/syntax_extension.c b/ext/commonmarker/syntax_extension.c index a2fb3b04..d24fe43e 100644 --- a/ext/commonmarker/syntax_extension.c +++ b/ext/commonmarker/syntax_extension.c @@ -29,10 +29,7 @@ cmark_syntax_extension *cmark_syntax_extension_new(const char *name) { cmark_node_type cmark_syntax_extension_add_node(int is_inline) { cmark_node_type *ref = !is_inline ? &CMARK_NODE_LAST_BLOCK : &CMARK_NODE_LAST_INLINE; - if ((*ref & CMARK_NODE_VALUE_MASK) >= CMARK_NODE_TYPE_BLOCK_LIMIT) { - // This assertion will fail if you try to register more extensions than - // are currently allowed by CMARK_NODE_TYPE_BLOCK_MAXNUM. Try increasing - // the limit. + if ((*ref & CMARK_NODE_VALUE_MASK) == CMARK_NODE_VALUE_MASK) { assert(false); return (cmark_node_type) 0; } diff --git a/ext/commonmarker/table.c b/ext/commonmarker/table.c index 6e75e38d..e53ea315 100644 --- a/ext/commonmarker/table.c +++ b/ext/commonmarker/table.c @@ -311,18 +311,12 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self, } } - assert(cmark_node_get_type(parent_container) == CMARK_NODE_PARAGRAPH); if (!cmark_node_set_type(parent_container, CMARK_NODE_TABLE)) { free_table_row(parser->mem, header_row); free_table_row(parser->mem, marker_row); return parent_container; } - // Update the node counts after parent_container changed type. - assert(parent_container->next == NULL); - decr_open_block_count(parser, CMARK_NODE_PARAGRAPH); - incr_open_block_count(parser, CMARK_NODE_TABLE); - if (header_row->paragraph_offset) { try_inserting_table_header_paragraph(parser, parent_container, (unsigned char *)parent_string, header_row->paragraph_offset); From 4e4588f2e0d34a21eeb79ec53459d248fdf56b39 Mon Sep 17 00:00:00 2001 From: Bas Alberts Date: Thu, 6 Apr 2023 14:42:56 -0400 Subject: [PATCH 8/9] Update Makefile for export header consolidation cmark-gfm removed the redundant extensions header in https://github.com/github/cmark-gfm/pull/289 --- Makefile | 3 --- 1 file changed, 3 deletions(-) diff --git a/Makefile b/Makefile index b92c1c11..9e682c56 100644 --- a/Makefile +++ b/Makefile @@ -21,6 +21,3 @@ ext/commonmarker/cmark-gfm_export.h: ext/commonmarker/cmark-upstream/build/src/c ext/commonmarker/cmark-gfm_version.h: ext/commonmarker/cmark-upstream/build/src/cmark-gfm_version.h cp $< $@ - -ext/commonmarker/cmark-gfm-extensions_export.h: ext/commonmarker/cmark-upstream/build/extensions/cmark-gfm-extensions_export.h - cp $< $@ From d793fbf45106f17c1eb721293bcc8d098bd999dc Mon Sep 17 00:00:00 2001 From: Bas Alberts Date: Thu, 6 Apr 2023 15:29:38 -0400 Subject: [PATCH 9/9] Update cmark-upstream to https://github.com/github/cmark-gfm/commit/1e230827a584ebc9938c3eadc5059c55ef3c9abf --- ext/commonmarker/cmark-gfm_version.h | 4 ++-- ext/commonmarker/cmark-upstream | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/commonmarker/cmark-gfm_version.h b/ext/commonmarker/cmark-gfm_version.h index 736f2e0e..19e67d81 100644 --- a/ext/commonmarker/cmark-gfm_version.h +++ b/ext/commonmarker/cmark-gfm_version.h @@ -1,7 +1,7 @@ #ifndef CMARK_GFM_VERSION_H #define CMARK_GFM_VERSION_H -#define CMARK_GFM_VERSION ((0 << 24) | (29 << 16) | (0 << 8) | 10) -#define CMARK_GFM_VERSION_STRING "0.29.0.gfm.10" +#define CMARK_GFM_VERSION ((0 << 24) | (29 << 16) | (0 << 8) | 11) +#define CMARK_GFM_VERSION_STRING "0.29.0.gfm.11" #endif diff --git a/ext/commonmarker/cmark-upstream b/ext/commonmarker/cmark-upstream index c8dcdc71..1e230827 160000 --- a/ext/commonmarker/cmark-upstream +++ b/ext/commonmarker/cmark-upstream @@ -1 +1 @@ -Subproject commit c8dcdc71ce1e2028d2d4d77c09c156512ffb3fa2 +Subproject commit 1e230827a584ebc9938c3eadc5059c55ef3c9abf