Skip to content

Commit

Permalink
Fix quadratic behavior when parsing emphasis
Browse files Browse the repository at this point in the history
Delimiters can be deleted, so store delimiter positions instead of
pointers in `openers_bottom`. Besides causing undefined behavior when
reading a dangling pointer, this could also result in quadratic
behavior when parsing emphasis.

Fixes commonmark#389.
  • Loading branch information
nwellnhof authored and philipturnbull committed Oct 4, 2022
1 parent 9d57d8a commit 75008f1
Show file tree
Hide file tree
Showing 2 changed files with 15 additions and 12 deletions.
1 change: 1 addition & 0 deletions src/cmark-gfm-extension_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ typedef struct delimiter {
struct delimiter *previous;
struct delimiter *next;
cmark_node *inl_text;
bufsize_t position;
bufsize_t length;
unsigned char delim_char;
int can_open;
Expand Down
26 changes: 14 additions & 12 deletions src/inlines.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ static const char *RIGHTSINGLEQUOTE = "\xE2\x80\x99";

typedef struct bracket {
struct bracket *previous;
struct delimiter *previous_delimiter;
cmark_node *inl_text;
bufsize_t position;
bool image;
Expand Down Expand Up @@ -505,6 +504,7 @@ static void push_delimiter(subject *subj, unsigned char c, bool can_open,
delim->can_open = can_open;
delim->can_close = can_close;
delim->inl_text = inl_text;
delim->position = subj->pos;
delim->length = inl_text->as.literal.len;
delim->previous = subj->last_delim;
delim->next = NULL;
Expand All @@ -525,7 +525,6 @@ static void push_bracket(subject *subj, bool image, cmark_node *inl_text) {
b->active = true;
b->inl_text = inl_text;
b->previous = subj->last_bracket;
b->previous_delimiter = subj->last_delim;
b->position = subj->pos;
b->bracket_after = false;
if (image) {
Expand Down Expand Up @@ -640,12 +639,12 @@ static cmark_syntax_extension *get_extension_for_special_char(cmark_parser *pars
return NULL;
}

static void process_emphasis(cmark_parser *parser, subject *subj, delimiter *stack_bottom) {
static void process_emphasis(cmark_parser *parser, subject *subj, bufsize_t stack_bottom) {
delimiter *closer = subj->last_delim;
delimiter *opener;
delimiter *old_closer;
bool opener_found;
delimiter *openers_bottom[3][128];
bufsize_t openers_bottom[3][128];
int i;

// initialize openers_bottom:
Expand All @@ -658,7 +657,9 @@ static void process_emphasis(cmark_parser *parser, subject *subj, delimiter *sta
}

// move back to first relevant delim.
while (closer != NULL && closer->previous != stack_bottom) {
while (closer != NULL &&
closer->previous != NULL &&
closer->previous->position >= stack_bottom) {
closer = closer->previous;
}

Expand All @@ -669,8 +670,8 @@ static void process_emphasis(cmark_parser *parser, subject *subj, delimiter *sta
// Now look backwards for first matching opener:
opener = closer->previous;
opener_found = false;
while (opener != NULL && opener != stack_bottom &&
opener != openers_bottom[closer->length % 3][closer->delim_char]) {
while (opener != NULL && opener->position >= stack_bottom &&
opener->position >= openers_bottom[closer->length % 3][closer->delim_char]) {
if (opener->can_open && opener->delim_char == closer->delim_char) {
// interior closer of size 2 can't match opener of size 1
// or of size 1 can't match 2
Expand Down Expand Up @@ -716,7 +717,7 @@ static void process_emphasis(cmark_parser *parser, subject *subj, delimiter *sta
if (!opener_found) {
// set lower bound for future searches for openers
openers_bottom[old_closer->length % 3][old_closer->delim_char] =
old_closer->previous;
old_closer->position;
if (!old_closer->can_open) {
// we can remove a closer that can't be an
// opener, once we've seen there's no
Expand All @@ -729,7 +730,8 @@ static void process_emphasis(cmark_parser *parser, subject *subj, delimiter *sta
}
}
// free all delimiters in list until stack_bottom:
while (subj->last_delim != NULL && subj->last_delim != stack_bottom) {
while (subj->last_delim != NULL &&
subj->last_delim->position >= stack_bottom) {
remove_delimiter(subj, subj->last_delim);
}
}
Expand Down Expand Up @@ -1193,7 +1195,7 @@ static cmark_node *handle_close_bracket(cmark_parser *parser, subject *subj) {
// being replacing the opening '[' text node with a `^footnote-ref]` node.
cmark_node_insert_before(opener->inl_text, fnref);

process_emphasis(parser, subj, opener->previous_delimiter);
process_emphasis(parser, subj, opener->position);
// sometimes, the footnote reference text gets parsed into multiple nodes
// i.e. '[^example]' parsed into '[', '^exam', 'ple]'.
// this happens for ex with the autolink extension. when the autolinker
Expand Down Expand Up @@ -1245,7 +1247,7 @@ static cmark_node *handle_close_bracket(cmark_parser *parser, subject *subj) {
// Free the bracket [:
cmark_node_free(opener->inl_text);

process_emphasis(parser, subj, opener->previous_delimiter);
process_emphasis(parser, subj, opener->position);
pop_bracket(subj);

// Now, if we have a link, we also want to deactivate earlier link
Expand Down Expand Up @@ -1470,7 +1472,7 @@ void cmark_parse_inlines(cmark_parser *parser,
while (!is_eof(&subj) && parse_inline(parser, &subj, parent, options))
;

process_emphasis(parser, &subj, NULL);
process_emphasis(parser, &subj, 0);
// free bracket and delim stack
while (subj.last_delim) {
remove_delimiter(&subj, subj.last_delim);
Expand Down

0 comments on commit 75008f1

Please sign in to comment.