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 committed Jul 13, 2021
1 parent 5cb26e0 commit b928047
Showing 1 changed file with 16 additions and 12 deletions.
28 changes: 16 additions & 12 deletions src/inlines.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ typedef struct delimiter {
struct delimiter *previous;
struct delimiter *next;
cmark_node *inl_text;
bufsize_t position;
bufsize_t length;
unsigned char delim_char;
bool can_open;
Expand All @@ -41,7 +42,6 @@ typedef struct delimiter {

typedef struct bracket {
struct bracket *previous;
struct delimiter *previous_delimiter;
cmark_node *inl_text;
bufsize_t position;
bool image;
Expand Down Expand Up @@ -529,6 +529,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 - 1;
delim->length = inl_text->len;
delim->previous = subj->last_delim;
delim->next = NULL;
Expand All @@ -547,7 +548,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;
subj->last_bracket = b;
Expand Down Expand Up @@ -639,18 +639,20 @@ static cmark_node *handle_period(subject *subj, bool smart) {
}
}

static void process_emphasis(subject *subj, delimiter *stack_bottom) {
static void process_emphasis(subject *subj, bufsize_t stack_bottom) {
delimiter *closer = subj->last_delim;
delimiter *opener;
delimiter *old_closer;
bool opener_found;
int openers_bottom_index = 0;
delimiter *openers_bottom[9] = {stack_bottom, stack_bottom, stack_bottom,
stack_bottom, stack_bottom, stack_bottom,
stack_bottom, stack_bottom, stack_bottom};
bufsize_t openers_bottom[9] = {stack_bottom, stack_bottom, stack_bottom,
stack_bottom, stack_bottom, stack_bottom,
stack_bottom, stack_bottom, stack_bottom};

// 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 Down Expand Up @@ -678,7 +680,8 @@ static void process_emphasis(subject *subj, delimiter *stack_bottom) {
// Now look backwards for first matching opener:
opener = closer->previous;
opener_found = false;
while (opener != NULL && opener != openers_bottom[openers_bottom_index]) {
while (opener != NULL &&
opener->position >= openers_bottom[openers_bottom_index]) {
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 @@ -717,7 +720,7 @@ static void process_emphasis(subject *subj, delimiter *stack_bottom) {
}
if (!opener_found) {
// set lower bound for future searches for openers
openers_bottom[openers_bottom_index] = old_closer->previous;
openers_bottom[openers_bottom_index] = 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 @@ -730,7 +733,8 @@ static void process_emphasis(subject *subj, delimiter *stack_bottom) {
}
}
// 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 @@ -1205,7 +1209,7 @@ static cmark_node *handle_close_bracket(subject *subj) {
// Free the bracket [:
cmark_node_free(opener->inl_text);

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

// Now, if we have a link, we also want to deactivate earlier link
Expand Down Expand Up @@ -1383,7 +1387,7 @@ void cmark_parse_inlines(cmark_mem *mem, cmark_node *parent,
while (!is_eof(&subj) && parse_inline(&subj, parent, options))
;

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

0 comments on commit b928047

Please sign in to comment.