@@ -1103,17 +1103,41 @@ static cmark_node *check_open_blocks(cmark_parser *parser, cmark_chunk *input,
1103
1103
continue ;
1104
1104
}
1105
1105
1106
- if (parser -> blank ) {
1107
- const size_t n_list = read_open_block_count (& tmp_parser , CMARK_NODE_LIST );
1108
- const size_t n_item = read_open_block_count (& tmp_parser , CMARK_NODE_ITEM );
1109
- const size_t n_para = read_open_block_count (& tmp_parser , CMARK_NODE_PARAGRAPH );
1110
- if (n_list + n_item + n_para == tmp_parser .total_open_blocks ) {
1111
- if (parser -> current -> flags & CMARK_NODE__OPEN_BLOCK ) {
1112
- if (parser -> current -> flags & CMARK_NODE__OPEN ) {
1106
+ // This block of code is a workaround for the quadratic performance
1107
+ // issue described here (issue 2):
1108
+ //
1109
+ // https://github.com/github/cmark-gfm/security/advisories/GHSA-66g8-4hjf-77xh
1110
+ //
1111
+ // If the current line is empty then we might be able to skip directly
1112
+ // to the end of the list of open blocks. To determine whether this is
1113
+ // possible, we have been maintaining a count of the number of
1114
+ // different types of open blocks. The main criterium is that every
1115
+ // remaining block, except the last element of the list, is a LIST or
1116
+ // ITEM. The code below checks the conditions, and if they're ok, skips
1117
+ // forward to parser->current.
1118
+ if (parser -> blank && parser -> indent == 0 ) { // Current line is empty
1119
+ // Make sure that parser->current doesn't point to a closed block.
1120
+ if (parser -> current -> flags & CMARK_NODE__OPEN_BLOCK ) {
1121
+ if (parser -> current -> flags & CMARK_NODE__OPEN ) {
1122
+ const size_t n_list = read_open_block_count (& tmp_parser , CMARK_NODE_LIST );
1123
+ const size_t n_item = read_open_block_count (& tmp_parser , CMARK_NODE_ITEM );
1124
+ // At most one block can be something other than a LIST or ITEM.
1125
+ if (n_list + n_item + 1 >= tmp_parser .total_open_blocks ) {
1126
+ // Check that parser->current is suitable for jumping to.
1113
1127
switch (S_type (parser -> current )) {
1114
- case CMARK_NODE_PARAGRAPH :
1115
1128
case CMARK_NODE_LIST :
1116
1129
case CMARK_NODE_ITEM :
1130
+ if (n_list + n_item != tmp_parser .total_open_blocks ) {
1131
+ if (parser -> current -> last_child == NULL ) {
1132
+ // There's another node type somewhere in the middle of
1133
+ // the list, so don't attempt the optimization.
1134
+ break ;
1135
+ }
1136
+ }
1137
+ // fall through
1138
+ case CMARK_NODE_CODE_BLOCK :
1139
+ case CMARK_NODE_PARAGRAPH :
1140
+ // Jump to parser->current
1117
1141
container = parser -> current ;
1118
1142
cont_type = S_type (container );
1119
1143
break ;
0 commit comments