Skip to content

Commit

Permalink
Update cmark-upstream to github/cmark-gfm@dcf6b38
Browse files Browse the repository at this point in the history
  • Loading branch information
phillmv committed Mar 31, 2023
1 parent 94c0af9 commit d6fe4c8
Show file tree
Hide file tree
Showing 17 changed files with 271 additions and 120 deletions.
119 changes: 107 additions & 12 deletions ext/commonmarker/blocks.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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));
}
}
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
}

Expand Down Expand Up @@ -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);

Expand All @@ -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))
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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)
Expand Down
10 changes: 10 additions & 0 deletions ext/commonmarker/cmark-gfm.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions ext/commonmarker/cmark-gfm_version.h
Original file line number Diff line number Diff line change
@@ -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
52 changes: 19 additions & 33 deletions ext/commonmarker/commonmark.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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) {
Expand Down Expand Up @@ -228,19 +216,15 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node,
LIT("<!-- end list -->");
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
Expand Down Expand Up @@ -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;

Expand Down
10 changes: 6 additions & 4 deletions ext/commonmarker/html.c
Original file line number Diff line number Diff line change
Expand Up @@ -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, "<strong>");
} else {
cmark_strbuf_puts(html, "</strong>");
if (node->parent == NULL || node->parent->type != CMARK_NODE_STRONG) {
if (entering) {
cmark_strbuf_puts(html, "<strong>");
} else {
cmark_strbuf_puts(html, "</strong>");
}
}
break;

Expand Down
10 changes: 6 additions & 4 deletions ext/commonmarker/latex.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Loading

0 comments on commit d6fe4c8

Please sign in to comment.