Skip to content

Commit 75008f1

Browse files
nwellnhofphilipturnbull
authored andcommitted
Fix quadratic behavior when parsing emphasis
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.
1 parent 9d57d8a commit 75008f1

File tree

2 files changed

+15
-12
lines changed

2 files changed

+15
-12
lines changed

src/cmark-gfm-extension_api.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ typedef struct delimiter {
114114
struct delimiter *previous;
115115
struct delimiter *next;
116116
cmark_node *inl_text;
117+
bufsize_t position;
117118
bufsize_t length;
118119
unsigned char delim_char;
119120
int can_open;

src/inlines.c

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ static const char *RIGHTSINGLEQUOTE = "\xE2\x80\x99";
3535

3636
typedef struct bracket {
3737
struct bracket *previous;
38-
struct delimiter *previous_delimiter;
3938
cmark_node *inl_text;
4039
bufsize_t position;
4140
bool image;
@@ -505,6 +504,7 @@ static void push_delimiter(subject *subj, unsigned char c, bool can_open,
505504
delim->can_open = can_open;
506505
delim->can_close = can_close;
507506
delim->inl_text = inl_text;
507+
delim->position = subj->pos;
508508
delim->length = inl_text->as.literal.len;
509509
delim->previous = subj->last_delim;
510510
delim->next = NULL;
@@ -525,7 +525,6 @@ static void push_bracket(subject *subj, bool image, cmark_node *inl_text) {
525525
b->active = true;
526526
b->inl_text = inl_text;
527527
b->previous = subj->last_bracket;
528-
b->previous_delimiter = subj->last_delim;
529528
b->position = subj->pos;
530529
b->bracket_after = false;
531530
if (image) {
@@ -640,12 +639,12 @@ static cmark_syntax_extension *get_extension_for_special_char(cmark_parser *pars
640639
return NULL;
641640
}
642641

643-
static void process_emphasis(cmark_parser *parser, subject *subj, delimiter *stack_bottom) {
642+
static void process_emphasis(cmark_parser *parser, subject *subj, bufsize_t stack_bottom) {
644643
delimiter *closer = subj->last_delim;
645644
delimiter *opener;
646645
delimiter *old_closer;
647646
bool opener_found;
648-
delimiter *openers_bottom[3][128];
647+
bufsize_t openers_bottom[3][128];
649648
int i;
650649

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

660659
// move back to first relevant delim.
661-
while (closer != NULL && closer->previous != stack_bottom) {
660+
while (closer != NULL &&
661+
closer->previous != NULL &&
662+
closer->previous->position >= stack_bottom) {
662663
closer = closer->previous;
663664
}
664665

@@ -669,8 +670,8 @@ static void process_emphasis(cmark_parser *parser, subject *subj, delimiter *sta
669670
// Now look backwards for first matching opener:
670671
opener = closer->previous;
671672
opener_found = false;
672-
while (opener != NULL && opener != stack_bottom &&
673-
opener != openers_bottom[closer->length % 3][closer->delim_char]) {
673+
while (opener != NULL && opener->position >= stack_bottom &&
674+
opener->position >= openers_bottom[closer->length % 3][closer->delim_char]) {
674675
if (opener->can_open && opener->delim_char == closer->delim_char) {
675676
// interior closer of size 2 can't match opener of size 1
676677
// or of size 1 can't match 2
@@ -716,7 +717,7 @@ static void process_emphasis(cmark_parser *parser, subject *subj, delimiter *sta
716717
if (!opener_found) {
717718
// set lower bound for future searches for openers
718719
openers_bottom[old_closer->length % 3][old_closer->delim_char] =
719-
old_closer->previous;
720+
old_closer->position;
720721
if (!old_closer->can_open) {
721722
// we can remove a closer that can't be an
722723
// opener, once we've seen there's no
@@ -729,7 +730,8 @@ static void process_emphasis(cmark_parser *parser, subject *subj, delimiter *sta
729730
}
730731
}
731732
// free all delimiters in list until stack_bottom:
732-
while (subj->last_delim != NULL && subj->last_delim != stack_bottom) {
733+
while (subj->last_delim != NULL &&
734+
subj->last_delim->position >= stack_bottom) {
733735
remove_delimiter(subj, subj->last_delim);
734736
}
735737
}
@@ -1193,7 +1195,7 @@ static cmark_node *handle_close_bracket(cmark_parser *parser, subject *subj) {
11931195
// being replacing the opening '[' text node with a `^footnote-ref]` node.
11941196
cmark_node_insert_before(opener->inl_text, fnref);
11951197

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

1248-
process_emphasis(parser, subj, opener->previous_delimiter);
1250+
process_emphasis(parser, subj, opener->position);
12491251
pop_bracket(subj);
12501252

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

1473-
process_emphasis(parser, &subj, NULL);
1475+
process_emphasis(parser, &subj, 0);
14741476
// free bracket and delim stack
14751477
while (subj.last_delim) {
14761478
remove_delimiter(&subj, subj.last_delim);

0 commit comments

Comments
 (0)