From d5b1faca6f8b145c9e3c9841eef6fefc505ee739 Mon Sep 17 00:00:00 2001 From: Benny Wong Date: Wed, 8 Jul 2020 01:14:04 -0400 Subject: [PATCH 01/47] [Autolink] Store positioning info for url_match Currently for the autolink extensioon, only `www_match` correctly returns correct positioning when calling `cmark_node_get_start_line`/`cmark_node_get_start_column`/`cmark_node_get_end_line`/`cmark_node_get_end_column`. This PR adds positioning support for the `url_match` function. --- extensions/autolink.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/extensions/autolink.c b/extensions/autolink.c index 41564ee4c..24b413a68 100644 --- a/extensions/autolink.c +++ b/extensions/autolink.c @@ -245,6 +245,11 @@ static cmark_node *url_match(cmark_parser *parser, cmark_node *parent, cmark_node *text = cmark_node_new_with_mem(CMARK_NODE_TEXT, parser->mem); text->as.literal = url; cmark_node_append_child(node, text); + + node->start_line = text->start_line = node->end_line = text->end_line = cmark_inline_parser_get_line(inline_parser); + + node->start_column = text->start_column = max_rewind - rewind; + node->end_column = text->end_column = cmark_inline_parser_get_column(inline_parser) - 1; return node; } From 4517fef666cc106d44fc101d2f81b35354022928 Mon Sep 17 00:00:00 2001 From: Ryan Mulligan Date: Thu, 25 Nov 2021 13:56:53 -0800 Subject: [PATCH 02/47] cmark-gfm-core-extensions: use stdbool.h instead of private header fixes #244 --- extensions/cmark-gfm-core-extensions.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/cmark-gfm-core-extensions.h b/extensions/cmark-gfm-core-extensions.h index 0645915f9..3a32f76b0 100644 --- a/extensions/cmark-gfm-core-extensions.h +++ b/extensions/cmark-gfm-core-extensions.h @@ -7,7 +7,7 @@ extern "C" { #include "cmark-gfm-extension_api.h" #include "cmark-gfm-extensions_export.h" -#include "config.h" // for bool +#include #include CMARK_GFM_EXTENSIONS_EXPORT From 73bb24f699ff358528573f069ecc5ccd11212ba1 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 17 Jan 2022 21:20:47 +1300 Subject: [PATCH 03/47] Expose `cmark_node_parent_footnote_def`. --- src/cmark-gfm.h | 5 +++++ src/node.c | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/src/cmark-gfm.h b/src/cmark-gfm.h index 6fb28693c..4113f71d4 100644 --- a/src/cmark-gfm.h +++ b/src/cmark-gfm.h @@ -225,6 +225,11 @@ CMARK_GFM_EXPORT cmark_node *cmark_node_first_child(cmark_node *node); */ CMARK_GFM_EXPORT cmark_node *cmark_node_last_child(cmark_node *node); +/** Returns the footnote reference of 'node', or NULL if 'node' doesn't have a + * footnote reference. + */ +CMARK_GFM_EXPORT cmark_node *cmark_node_parent_footnote_def(cmark_node *node); + /** * ## Iterator * diff --git a/src/node.c b/src/node.c index 0118d6511..5fce332cb 100644 --- a/src/node.c +++ b/src/node.c @@ -301,6 +301,14 @@ cmark_node *cmark_node_last_child(cmark_node *node) { } } +cmark_node *cmark_node_parent_footnote_def(cmark_node *node) { + if (node == NULL) { + return NULL; + } else { + return node->parent_footnote_def; + } +} + void *cmark_node_get_user_data(cmark_node *node) { if (node == NULL) { return NULL; From f13693d7af2e857771175360ca30f9d8c774be3a Mon Sep 17 00:00:00 2001 From: Keith Packard Date: Mon, 17 Jan 2022 16:48:22 -0800 Subject: [PATCH 04/47] man: Switch --safe option for --unsafe in man page The old --safe option is now the default, to get the previous default behavior, use the --unsafe flag. Signed-off-by: Keith Packard --- man/man1/cmark-gfm.1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/man/man1/cmark-gfm.1 b/man/man1/cmark-gfm.1 index 5c68d79c3..4fca62732 100644 --- a/man/man1/cmark-gfm.1 +++ b/man/man1/cmark-gfm.1 @@ -58,10 +58,10 @@ be rendered as curly quotes, depending on their position. \f[C]\-\-\-\f[] will be rendered as an em-dash. \f[C]...\f[] will be rendered as ellipses. .TP 12n -.B \-\-safe -Do not render raw HTML or potentially dangerous URLs. -(Raw HTML is replaced by a placeholder comment; potentially -dangerous URLs are replaced by empty strings.) Dangerous +.B \-\-unsafe +Render raw HTML and potentially dangerous URLs. +(Raw HTML is not replaced by a placeholder comment; potentially +dangerous URLs are not replaced by empty strings.) Dangerous URLs are those that begin with `javascript:`, `vbscript:`, `file:`, or `data:` (except for `image/png`, `image/gif`, `image/jpeg`, or `image/webp` mime types). From ef5a49447d8b0fc949617ca0e878d7fed32aea5b Mon Sep 17 00:00:00 2001 From: Jeroen Ooms Date: Fri, 14 Oct 2022 13:41:46 +0200 Subject: [PATCH 05/47] Fix -Wstrict-prototypes warnings --- src/arena.c | 2 +- src/cmark-gfm.h | 4 ++-- src/cmark.c | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/arena.c b/src/arena.c index 83a15255f..13982c3fa 100644 --- a/src/arena.c +++ b/src/arena.c @@ -98,6 +98,6 @@ static void arena_free(void *ptr) { cmark_mem CMARK_ARENA_MEM_ALLOCATOR = {arena_calloc, arena_realloc, arena_free}; -cmark_mem *cmark_get_arena_mem_allocator() { +cmark_mem *cmark_get_arena_mem_allocator(void) { return &CMARK_ARENA_MEM_ALLOCATOR; } diff --git a/src/cmark-gfm.h b/src/cmark-gfm.h index 6fb28693c..f7f7b8038 100644 --- a/src/cmark-gfm.h +++ b/src/cmark-gfm.h @@ -111,13 +111,13 @@ typedef struct cmark_mem { * realloc and free. */ CMARK_GFM_EXPORT -cmark_mem *cmark_get_default_mem_allocator(); +cmark_mem *cmark_get_default_mem_allocator(void); /** An arena allocator; uses system calloc to allocate large * slabs of memory. Memory in these slabs is not reused at all. */ CMARK_GFM_EXPORT -cmark_mem *cmark_get_arena_mem_allocator(); +cmark_mem *cmark_get_arena_mem_allocator(void); /** Resets the arena allocator, quickly returning all used memory * to the operating system. diff --git a/src/cmark.c b/src/cmark.c index b3fad4b08..68c40c477 100644 --- a/src/cmark.c +++ b/src/cmark.c @@ -10,9 +10,9 @@ cmark_node_type CMARK_NODE_LAST_BLOCK = CMARK_NODE_FOOTNOTE_DEFINITION; cmark_node_type CMARK_NODE_LAST_INLINE = CMARK_NODE_FOOTNOTE_REFERENCE; -int cmark_version() { return CMARK_GFM_VERSION; } +int cmark_version(void) { return CMARK_GFM_VERSION; } -const char *cmark_version_string() { return CMARK_GFM_VERSION_STRING; } +const char *cmark_version_string(void) { return CMARK_GFM_VERSION_STRING; } static void *xcalloc(size_t nmem, size_t size) { void *ptr = calloc(nmem, size); @@ -38,7 +38,7 @@ static void xfree(void *ptr) { cmark_mem CMARK_DEFAULT_MEM_ALLOCATOR = {xcalloc, xrealloc, xfree}; -cmark_mem *cmark_get_default_mem_allocator() { +cmark_mem *cmark_get_default_mem_allocator(void) { return &CMARK_DEFAULT_MEM_ALLOCATOR; } From 855bf908d6f2ba768345c5c2f41df39c34b4dae3 Mon Sep 17 00:00:00 2001 From: Eli Schwartz Date: Tue, 25 Oct 2022 21:56:59 -0400 Subject: [PATCH 06/47] get rid of deprecated function add_compiler_export_flags() is deprecated since CMake 3.0, and was removed a long time ago in cmark via commit abf3a7a47d45ddad2a1bd69b176a4c471f65231d -- but preserved in cmark-gfm's modifications. --- extensions/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/extensions/CMakeLists.txt b/extensions/CMakeLists.txt index 0c007c706..61503ba78 100644 --- a/extensions/CMakeLists.txt +++ b/extensions/CMakeLists.txt @@ -24,7 +24,6 @@ include_directories(. ${CMAKE_CURRENT_BINARY_DIR}) set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE} -pg") set(CMAKE_LINKER_PROFILE "${CMAKE_LINKER_FLAGS_RELEASE} -pg") -add_compiler_export_flags() if (CMARK_SHARED) add_library(${LIBRARY} SHARED ${LIBRARY_SOURCES}) From e08c5523b662f022a37bae28494a8c17b5c5097b Mon Sep 17 00:00:00 2001 From: Eli Schwartz Date: Wed, 26 Oct 2022 00:29:45 -0400 Subject: [PATCH 07/47] extensions: avoid useless duplication of headers The export header is identical between the main library and the extensions. It is generated from the same template, the libraries share the same properties, and the only difference is that they use different symbol names. Let them share a header. --- extensions/CMakeLists.txt | 13 ++----------- extensions/cmark-gfm-core-extensions.h | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/extensions/CMakeLists.txt b/extensions/CMakeLists.txt index 61503ba78..2458d4243 100644 --- a/extensions/CMakeLists.txt +++ b/extensions/CMakeLists.txt @@ -18,8 +18,6 @@ include_directories( ${PROJECT_BINARY_DIR}/src ) -include (GenerateExportHeader) - include_directories(. ${CMAKE_CURRENT_BINARY_DIR}) set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE} -pg") @@ -30,6 +28,7 @@ if (CMARK_SHARED) set_target_properties(${LIBRARY} PROPERTIES OUTPUT_NAME "cmark-gfm-extensions" + DEFINE_SYMBOL "cmark-gfm" SOVERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}.gfm.${PROJECT_VERSION_GFM} VERSION ${PROJECT_VERSION}) @@ -39,9 +38,6 @@ if (CMARK_SHARED) # Avoid name clash between PROGRAM and LIBRARY pdb files. set_target_properties(${LIBRARY} PROPERTIES PDB_NAME cmark-gfm-extensions_dll) - generate_export_header(${LIBRARY} - BASE_NAME cmark-gfm-extensions) - list(APPEND CMARK_INSTALL ${LIBRARY}) target_link_libraries(${LIBRARY} libcmark-gfm) @@ -52,6 +48,7 @@ if (CMARK_STATIC) set_target_properties(${STATICLIBRARY} PROPERTIES COMPILE_FLAGS "-DCMARK_GFM_STATIC_DEFINE -DCMARK_GFM_EXTENSIONS_STATIC_DEFINE" + DEFINE_SYMBOL "cmark-gfm" POSITION_INDEPENDENT_CODE ON) if (MSVC) @@ -64,11 +61,6 @@ if (CMARK_STATIC) VERSION ${PROJECT_VERSION}) endif(MSVC) - if (NOT CMARK_SHARED) - generate_export_header(${STATICLIBRARY} - BASE_NAME cmark-gfm-extensions) - endif() - list(APPEND CMARK_INSTALL ${STATICLIBRARY}) endif() @@ -85,7 +77,6 @@ install(TARGETS ${CMARK_INSTALL} if (CMARK_SHARED OR CMARK_STATIC) install(FILES cmark-gfm-core-extensions.h - ${CMAKE_CURRENT_BINARY_DIR}/cmark-gfm-extensions_export.h DESTINATION include ) diff --git a/extensions/cmark-gfm-core-extensions.h b/extensions/cmark-gfm-core-extensions.h index 0645915f9..97c6728be 100644 --- a/extensions/cmark-gfm-core-extensions.h +++ b/extensions/cmark-gfm-core-extensions.h @@ -6,45 +6,45 @@ extern "C" { #endif #include "cmark-gfm-extension_api.h" -#include "cmark-gfm-extensions_export.h" +#include "cmark-gfm_export.h" #include "config.h" // for bool #include -CMARK_GFM_EXTENSIONS_EXPORT +CMARK_GFM_EXPORT void cmark_gfm_core_extensions_ensure_registered(void); -CMARK_GFM_EXTENSIONS_EXPORT +CMARK_GFM_EXPORT uint16_t cmark_gfm_extensions_get_table_columns(cmark_node *node); /** Sets the number of columns for the table, returning 1 on success and 0 on error. */ -CMARK_GFM_EXTENSIONS_EXPORT +CMARK_GFM_EXPORT int cmark_gfm_extensions_set_table_columns(cmark_node *node, uint16_t n_columns); -CMARK_GFM_EXTENSIONS_EXPORT +CMARK_GFM_EXPORT uint8_t *cmark_gfm_extensions_get_table_alignments(cmark_node *node); /** Sets the alignments for the table, returning 1 on success and 0 on error. */ -CMARK_GFM_EXTENSIONS_EXPORT +CMARK_GFM_EXPORT int cmark_gfm_extensions_set_table_alignments(cmark_node *node, uint16_t ncols, uint8_t *alignments); -CMARK_GFM_EXTENSIONS_EXPORT +CMARK_GFM_EXPORT int cmark_gfm_extensions_get_table_row_is_header(cmark_node *node); /** Sets whether the node is a table header row, returning 1 on success and 0 on error. */ -CMARK_GFM_EXTENSIONS_EXPORT +CMARK_GFM_EXPORT int cmark_gfm_extensions_set_table_row_is_header(cmark_node *node, int is_header); -CMARK_GFM_EXTENSIONS_EXPORT +CMARK_GFM_EXPORT bool cmark_gfm_extensions_get_tasklist_item_checked(cmark_node *node); /* For backwards compatibility */ #define cmark_gfm_extensions_tasklist_is_checked cmark_gfm_extensions_get_tasklist_item_checked /** Sets whether a tasklist item is "checked" (completed), returning 1 on success and 0 on error. */ -CMARK_GFM_EXTENSIONS_EXPORT +CMARK_GFM_EXPORT int cmark_gfm_extensions_set_tasklist_item_checked(cmark_node *node, bool is_checked); #ifdef __cplusplus From c262f158652573cac694694714f48904081eaaef Mon Sep 17 00:00:00 2001 From: Clay Miller Date: Wed, 25 Jan 2023 20:46:37 +0000 Subject: [PATCH 08/47] fix: DLinks with unique targets should have unique labels --- src/html.c | 10 ++++++++-- test/extensions.txt | 12 ++++++------ test/regression.txt | 14 +++++++------- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/html.c b/src/html.c index 12d3c3e9c..f9e6c17b6 100644 --- a/src/html.c +++ b/src/html.c @@ -66,7 +66,9 @@ static bool S_put_footnote_backref(cmark_html_renderer *renderer, cmark_strbuf * cmark_strbuf_puts(html, "as.literal.data, node->as.literal.len); - cmark_strbuf_puts(html, "\" class=\"footnote-backref\" data-footnote-backref aria-label=\"Back to content\">↩"); + cmark_strbuf_puts(html, "\" class=\"footnote-backref\" data-footnote-backref aria-label=\"Back to reference "); + houdini_escape_href(html, node->as.literal.data, node->as.literal.len); + cmark_strbuf_puts(html, "\">↩"); if (node->footnote.def_count > 1) { @@ -78,7 +80,11 @@ static bool S_put_footnote_backref(cmark_html_renderer *renderer, cmark_strbuf * houdini_escape_href(html, node->as.literal.data, node->as.literal.len); cmark_strbuf_puts(html, "-"); cmark_strbuf_puts(html, n); - cmark_strbuf_puts(html, "\" class=\"footnote-backref\" data-footnote-backref aria-label=\"Back to content\">↩"); + cmark_strbuf_puts(html, "\" class=\"footnote-backref\" data-footnote-backref aria-label=\"Back to reference "); + houdini_escape_href(html, node->as.literal.data, node->as.literal.len); + cmark_strbuf_puts(html, "-"); + cmark_strbuf_puts(html, n); + cmark_strbuf_puts(html, "\">↩"); cmark_strbuf_puts(html, n); cmark_strbuf_puts(html, ""); } diff --git a/test/extensions.txt b/test/extensions.txt index a1a150faa..580242ffc 100644 --- a/test/extensions.txt +++ b/test/extensions.txt @@ -737,7 +737,7 @@ Hi!
  1. -

    Some bolded footnote definition.

    +

    Some bolded footnote definition.

  2. @@ -745,15 +745,15 @@ Hi!
    as well as code blocks
     
    -

    or, naturally, simple paragraphs.

    +

    or, naturally, simple paragraphs.

  3. -

    no code block here (spaces are stripped away)

    +

    no code block here (spaces are stripped away)

  4. this is now a code block (8 spaces indentation)
     
    - +
@@ -773,7 +773,7 @@ This footnote is referenced[^a-footnote] multiple times, in lots of different pl
  1. -

    This footnote definition should have three backrefs. 2 3

    +

    This footnote definition should have three backrefs. 2 3

@@ -790,7 +790,7 @@ Hello[^">]
  1. -

    pwned

    +

    pwned

diff --git a/test/regression.txt b/test/regression.txt index 844da60a2..55a1c71a5 100644 --- a/test/regression.txt +++ b/test/regression.txt @@ -194,7 +194,7 @@ A footnote in a paragraph[^1]
  1. -

    a footnote 2

    +

    a footnote 2

@@ -284,10 +284,10 @@ This is some text. It has a citation.[^citation]
  1. -

    This is a long winded parapgraph that also has another citation.2

    +

    This is a long winded parapgraph that also has another citation.2

  2. -

    My second citation.

    +

    My second citation.

@@ -306,10 +306,10 @@ This is some text. It has two footnotes references, side-by-side without any spa
  1. -

    Hello.

    +

    Hello.

  2. -

    Goodbye.

    +

    Goodbye.

@@ -331,10 +331,10 @@ It has another footnote that contains many different characters (the autolinker
  1. -

    this renders properly.

    +

    this renders properly.

  2. -

    so does this.

    +

    so does this.

From 1f30fbb2dadf13dc58894902e3b9fb0ade7e4071 Mon Sep 17 00:00:00 2001 From: Clay Miller Date: Wed, 25 Jan 2023 21:29:48 +0000 Subject: [PATCH 09/47] fix: Use 'written_footnote_ix' (index) in place of named tag --- src/html.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/html.c b/src/html.c index f9e6c17b6..5b40aa07b 100644 --- a/src/html.c +++ b/src/html.c @@ -63,11 +63,13 @@ static bool S_put_footnote_backref(cmark_html_renderer *renderer, cmark_strbuf * if (renderer->written_footnote_ix >= renderer->footnote_ix) return false; renderer->written_footnote_ix = renderer->footnote_ix; - + char m[32]; + snprintf(m, sizeof(m), "%d", renderer->written_footnote_ix); + cmark_strbuf_puts(html, "as.literal.data, node->as.literal.len); cmark_strbuf_puts(html, "\" class=\"footnote-backref\" data-footnote-backref aria-label=\"Back to reference "); - houdini_escape_href(html, node->as.literal.data, node->as.literal.len); + cmark_strbuf_puts(html, m); cmark_strbuf_puts(html, "\">↩"); if (node->footnote.def_count > 1) From 4cc55410ce1f417d96e14b332344d14e0c82bab2 Mon Sep 17 00:00:00 2001 From: Clay Miller Date: Wed, 25 Jan 2023 21:33:23 +0000 Subject: [PATCH 10/47] fix: Use 'written_footnote_ix' (index) in place of named tag in subsequent references --- src/html.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/html.c b/src/html.c index 5b40aa07b..7836f0052 100644 --- a/src/html.c +++ b/src/html.c @@ -65,7 +65,7 @@ static bool S_put_footnote_backref(cmark_html_renderer *renderer, cmark_strbuf * renderer->written_footnote_ix = renderer->footnote_ix; char m[32]; snprintf(m, sizeof(m), "%d", renderer->written_footnote_ix); - + cmark_strbuf_puts(html, "as.literal.data, node->as.literal.len); cmark_strbuf_puts(html, "\" class=\"footnote-backref\" data-footnote-backref aria-label=\"Back to reference "); @@ -83,7 +83,7 @@ static bool S_put_footnote_backref(cmark_html_renderer *renderer, cmark_strbuf * cmark_strbuf_puts(html, "-"); cmark_strbuf_puts(html, n); cmark_strbuf_puts(html, "\" class=\"footnote-backref\" data-footnote-backref aria-label=\"Back to reference "); - houdini_escape_href(html, node->as.literal.data, node->as.literal.len); + cmark_strbuf_puts(html, m); cmark_strbuf_puts(html, "-"); cmark_strbuf_puts(html, n); cmark_strbuf_puts(html, "\">↩"); From 388ddecc6f0bb220eb1863e00339061cf6b713b0 Mon Sep 17 00:00:00 2001 From: Clay Miller Date: Thu, 26 Jan 2023 12:15:39 -0500 Subject: [PATCH 11/47] feat: Output index in a data attribute --- src/html.c | 10 ++++++++-- test/extensions.txt | 12 ++++++------ test/regression.txt | 14 +++++++------- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/html.c b/src/html.c index 7836f0052..529e0ea31 100644 --- a/src/html.c +++ b/src/html.c @@ -68,7 +68,9 @@ static bool S_put_footnote_backref(cmark_html_renderer *renderer, cmark_strbuf * cmark_strbuf_puts(html, "as.literal.data, node->as.literal.len); - cmark_strbuf_puts(html, "\" class=\"footnote-backref\" data-footnote-backref aria-label=\"Back to reference "); + cmark_strbuf_puts(html, "\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\""); + cmark_strbuf_puts(html, m); + cmark_strbuf_puts(html, "\" aria-label=\"Back to reference "); cmark_strbuf_puts(html, m); cmark_strbuf_puts(html, "\">↩"); @@ -82,7 +84,11 @@ static bool S_put_footnote_backref(cmark_html_renderer *renderer, cmark_strbuf * houdini_escape_href(html, node->as.literal.data, node->as.literal.len); cmark_strbuf_puts(html, "-"); cmark_strbuf_puts(html, n); - cmark_strbuf_puts(html, "\" class=\"footnote-backref\" data-footnote-backref aria-label=\"Back to reference "); + cmark_strbuf_puts(html, "\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\""); + cmark_strbuf_puts(html, m); + cmark_strbuf_puts(html, "-"); + cmark_strbuf_puts(html, n); + cmark_strbuf_puts(html, "\" aria-label=\"Back to reference "); cmark_strbuf_puts(html, m); cmark_strbuf_puts(html, "-"); cmark_strbuf_puts(html, n); diff --git a/test/extensions.txt b/test/extensions.txt index 580242ffc..fe98fe548 100644 --- a/test/extensions.txt +++ b/test/extensions.txt @@ -737,7 +737,7 @@ Hi!
  1. -

    Some bolded footnote definition.

    +

    Some bolded footnote definition.

  2. @@ -745,15 +745,15 @@ Hi!
    as well as code blocks
     
    -

    or, naturally, simple paragraphs.

    +

    or, naturally, simple paragraphs.

  3. -

    no code block here (spaces are stripped away)

    +

    no code block here (spaces are stripped away)

  4. this is now a code block (8 spaces indentation)
     
    - +
@@ -773,7 +773,7 @@ This footnote is referenced[^a-footnote] multiple times, in lots of different pl
  1. -

    This footnote definition should have three backrefs. 2 3

    +

    This footnote definition should have three backrefs. 2 3

@@ -790,7 +790,7 @@ Hello[^">]
  1. -

    pwned

    +

    pwned

diff --git a/test/regression.txt b/test/regression.txt index 55a1c71a5..1334a674a 100644 --- a/test/regression.txt +++ b/test/regression.txt @@ -194,7 +194,7 @@ A footnote in a paragraph[^1]
  1. -

    a footnote 2

    +

    a footnote 2

@@ -284,10 +284,10 @@ This is some text. It has a citation.[^citation]
  1. -

    This is a long winded parapgraph that also has another citation.2

    +

    This is a long winded parapgraph that also has another citation.2

  2. -

    My second citation.

    +

    My second citation.

@@ -306,10 +306,10 @@ This is some text. It has two footnotes references, side-by-side without any spa
  1. -

    Hello.

    +

    Hello.

  2. -

    Goodbye.

    +

    Goodbye.

@@ -331,10 +331,10 @@ It has another footnote that contains many different characters (the autolinker
  1. -

    this renders properly.

    +

    this renders properly.

  2. -

    so does this.

    +

    so does this.

From ef87602145f8b1d53e07ab50df7f087e91461e50 Mon Sep 17 00:00:00 2001 From: Phill MV Date: Tue, 31 Jan 2023 11:02:58 -0500 Subject: [PATCH 12/47] Update src/cmark-gfm.h comment Co-authored-by: Waldir Pimenta --- src/cmark-gfm.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmark-gfm.h b/src/cmark-gfm.h index 4113f71d4..07648f8c7 100644 --- a/src/cmark-gfm.h +++ b/src/cmark-gfm.h @@ -226,7 +226,7 @@ CMARK_GFM_EXPORT cmark_node *cmark_node_first_child(cmark_node *node); CMARK_GFM_EXPORT cmark_node *cmark_node_last_child(cmark_node *node); /** Returns the footnote reference of 'node', or NULL if 'node' doesn't have a - * footnote reference. + * footnote reference. */ CMARK_GFM_EXPORT cmark_node *cmark_node_parent_footnote_def(cmark_node *node); From 730b0095e8904d7097a2d9195592fd6aa4ce38ab Mon Sep 17 00:00:00 2001 From: huven Date: Sun, 12 Feb 2023 19:11:33 +0100 Subject: [PATCH 13/47] Fix strict prototype clang warning cmark-gfm/src/node.h:122:36: warning: this function declaration is not a prototype [-Wstrict-prototypes] void cmark_init_standard_node_flags(); ^ void 1 warning generated. --- src/node.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node.h b/src/node.h index e025e25d6..ccd82d8d2 100644 --- a/src/node.h +++ b/src/node.h @@ -119,7 +119,7 @@ void cmark_register_node_flag(cmark_node_internal_flags *flags); * library. It is now a no-op. */ CMARK_GFM_EXPORT -void cmark_init_standard_node_flags(); +void cmark_init_standard_node_flags(void); static CMARK_INLINE cmark_mem *cmark_node_mem(cmark_node *node) { return node->content.mem; From 9e0855d7701874f7133cc1514cf62ddd3594bd7c Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Wed, 22 Feb 2023 12:59:22 +0000 Subject: [PATCH 14/47] Fix GHSL-2023-031: prevent quadratic performance by not allowing very deeply nested lists. --- src/blocks.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/blocks.c b/src/blocks.c index 94045aba2..03a58748b 100644 --- a/src/blocks.c +++ b/src/blocks.c @@ -27,6 +27,14 @@ #define CODE_INDENT 4 #define TAB_STOP 4 +/** + * Very deeply nested lists can cause quadratic performance issues. + * This constant is used in open_new_blocks() to limit the nesting + * depth. It is unlikely that a non-contrived markdown document will + * be nested this deeply. + */ +#define MAX_LIST_DEPTH 100 + #ifndef MIN #define MIN(x, y) ((x < y) ? x : y) #endif @@ -1119,10 +1127,11 @@ static void open_new_blocks(cmark_parser *parser, cmark_node **container, bool has_content; int save_offset; int save_column; + size_t depth = 0; while (cont_type != CMARK_NODE_CODE_BLOCK && cont_type != CMARK_NODE_HTML_BLOCK) { - + depth++; S_find_first_nonspace(parser, input); indented = parser->indent >= CODE_INDENT; @@ -1224,6 +1233,7 @@ static void open_new_blocks(cmark_parser *parser, cmark_node **container, (*container)->internal_offset = matched; } else if ((!indented || cont_type == CMARK_NODE_LIST) && parser->indent < 4 && + depth < MAX_LIST_DEPTH && (matched = parse_list_marker( parser->mem, input, parser->first_nonspace, (*container)->type == CMARK_NODE_PARAGRAPH, &data))) { From 5c75d23a02c52ee8b226ce53e3fb5339e4593757 Mon Sep 17 00:00:00 2001 From: Joshua Katz Date: Sat, 4 Mar 2023 12:22:11 -0500 Subject: [PATCH 15/47] Ignore nested STRONGs during rendering STRONG, in most rendering engines, becomes bold. Bold cannot be applied to text two times in most languages. This caps the number of times we attempt to bold text when rendering. Running `python3 -c 'pad = "_" * 100000; print(pad + "." + pad, end="")' | time ./build/src/cmark-gfm --to $LANG` Before: ``` ./build/src/cmark-gfm --to plaintext > /dev/null 12.29s user 0.00s system 99% cpu 12.321 total ./build/src/cmark-gfm --to commonmark > /dev/null 25.97s user 0.01s system 99% cpu 26.026 total ./build/src/cmark-gfm --to html > /dev/null 0.01s user 0.00s system 43% cpu 0.033 total ./build/src/cmark-gfm --to man > /dev/null 12.91s user 0.00s system 99% cpu 12.938 total ./build/src/cmark-gfm --to latex > /dev/null 13.13s user 0.01s system 99% cpu 13.159 total ``` After: ``` ./build/src/cmark-gfm --to plaintext > /dev/null 0.01s user 0.01s system 39% cpu 0.030 total ./build/src/cmark-gfm --to commonmark > /dev/null 0.01s user 0.00s system 41% cpu 0.031 total ./build/src/cmark-gfm --to html > /dev/null 0.01s user 0.00s system 38% cpu 0.030 total ./build/src/cmark-gfm --to man > /dev/null 0.01s user 0.01s system 40% cpu 0.030 total ./build/src/cmark-gfm --to latex > /dev/null 0.01s user 0.00s system 39% cpu 0.033 total ``` --- src/commonmark.c | 29 +++++++++++++++++------------ src/html.c | 10 ++++++---- src/latex.c | 10 ++++++---- src/man.c | 10 ++++++---- src/plaintext.c | 19 +++++++++++-------- 5 files changed, 46 insertions(+), 32 deletions(-) diff --git a/src/commonmark.c b/src/commonmark.c index 2e0719443..8d0644d57 100644 --- a/src/commonmark.c +++ b/src/commonmark.c @@ -189,14 +189,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) { @@ -405,10 +408,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; diff --git a/src/html.c b/src/html.c index 529e0ea31..22513c939 100644 --- a/src/html.c +++ b/src/html.c @@ -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, ""); - } else { - cmark_strbuf_puts(html, ""); + if (node->parent == NULL || node->parent->type != CMARK_NODE_STRONG) { + if (entering) { + cmark_strbuf_puts(html, ""); + } else { + cmark_strbuf_puts(html, ""); + } } break; diff --git a/src/latex.c b/src/latex.c index 8be15b0d5..1a6367a4e 100644 --- a/src/latex.c +++ b/src/latex.c @@ -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; diff --git a/src/man.c b/src/man.c index 441a96e49..da0706509 100644 --- a/src/man.c +++ b/src/man.c @@ -225,10 +225,12 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, break; case CMARK_NODE_STRONG: - if (entering) { - LIT("\\f[B]"); - } else { - LIT("\\f[]"); + if (node->parent == NULL || node->parent->type != CMARK_NODE_STRONG) { + if (entering) { + LIT("\\f[B]"); + } else { + LIT("\\f[]"); + } } break; diff --git a/src/plaintext.c b/src/plaintext.c index b25e4a396..d9c6a060b 100644 --- a/src/plaintext.c +++ b/src/plaintext.c @@ -46,14 +46,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->plaintext_render_func) { From c72487d7a2198aa849229bd02e59d6f26f71899b Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Sun, 5 Mar 2023 15:31:28 +0000 Subject: [PATCH 16/47] Keep a count of the number of open blocks. --- extensions/table.c | 6 ++++++ src/blocks.c | 43 ++++++++++++++++++++++++++++++++++++++++-- src/cmark-gfm.h | 10 ++++++++++ src/node.h | 7 ++++--- src/parser.h | 29 ++++++++++++++++++++++++++++ src/syntax_extension.c | 5 ++++- 6 files changed, 94 insertions(+), 6 deletions(-) diff --git a/extensions/table.c b/extensions/table.c index e53ea3154..6e75e38d9 100644 --- a/extensions/table.c +++ b/extensions/table.c @@ -311,12 +311,18 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self, } } + assert(cmark_node_get_type(parent_container) == CMARK_NODE_PARAGRAPH); if (!cmark_node_set_type(parent_container, CMARK_NODE_TABLE)) { free_table_row(parser->mem, header_row); free_table_row(parser->mem, marker_row); return parent_container; } + // Update the node counts after parent_container changed type. + assert(parent_container->next == NULL); + decr_open_block_count(parser, CMARK_NODE_PARAGRAPH); + incr_open_block_count(parser, CMARK_NODE_TABLE); + if (header_row->paragraph_offset) { try_inserting_table_header_paragraph(parser, parent_container, (unsigned char *)parent_string, header_row->paragraph_offset); diff --git a/src/blocks.c b/src/blocks.c index 94045aba2..63741594e 100644 --- a/src/blocks.c +++ b/src/blocks.c @@ -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; @@ -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; @@ -310,6 +327,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; @@ -382,6 +405,15 @@ 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 field. + add_open_block_counts(&tmp_parser, parser->root); + return 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) { @@ -400,11 +432,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; } @@ -1048,6 +1083,8 @@ static cmark_node *check_open_blocks(cmark_parser *parser, cmark_chunk *input, cmark_node *container = parser->root; cmark_node_type cont_type; + assert(check_open_block_counts(parser)); + while (S_last_child_is_open(container)) { container = container->last_child; cont_type = S_type(container); @@ -1193,8 +1230,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); @@ -1478,6 +1516,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) diff --git a/src/cmark-gfm.h b/src/cmark-gfm.h index 6b362a865..902c392de 100644 --- a/src/cmark-gfm.h +++ b/src/cmark-gfm.h @@ -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, diff --git a/src/node.h b/src/node.h index e025e25d6..827749ff8 100644 --- a/src/node.h +++ b/src/node.h @@ -50,12 +50,13 @@ typedef struct { enum cmark_node__internal_flags { CMARK_NODE__OPEN = (1 << 0), - CMARK_NODE__LAST_LINE_BLANK = (1 << 1), - CMARK_NODE__LAST_LINE_CHECKED = (1 << 2), + CMARK_NODE__OPEN_BLOCK = (1 << 1), + CMARK_NODE__LAST_LINE_BLANK = (1 << 2), + CMARK_NODE__LAST_LINE_CHECKED = (1 << 3), // Extensions can register custom flags by calling `cmark_register_node_flag`. // This is the starting value for the custom flags. - CMARK_NODE__REGISTER_FIRST = (1 << 3), + CMARK_NODE__REGISTER_FIRST = (1 << 4), }; typedef uint16_t cmark_node_internal_flags; diff --git a/src/parser.h b/src/parser.h index 436c53f5b..5020aa20b 100644 --- a/src/parser.h +++ b/src/parser.h @@ -50,8 +50,37 @@ struct cmark_parser { cmark_llist *syntax_extensions; cmark_llist *inline_syntax_extensions; cmark_ispunct_func backslash_ispunct; + + /** + * The "open" blocks are the blocks visited by the loop in + * check_open_blocks (blocks.c). I.e. the blocks in this list: + * + * parser->root->last_child->...->last_child + * + * open_block_counts is used to keep track of how many of each type of + * node are currently in the open blocks list. Knowing these counts can + * sometimes help to end the loop in check_open_blocks early, improving + * efficiency. + * + * The count is stored at this offset: type - CMARK_NODE_TYPE_BLOCK - 1 + * For example, CMARK_NODE_LIST (0x8003) is stored at offset 2. + */ + size_t open_block_counts[CMARK_NODE_TYPE_BLOCK_LIMIT]; }; +static CMARK_INLINE void incr_open_block_count(cmark_parser *parser, cmark_node_type type) { + assert(type > CMARK_NODE_TYPE_BLOCK); + assert(type <= CMARK_NODE_TYPE_BLOCK + CMARK_NODE_TYPE_BLOCK_LIMIT); + parser->open_block_counts[type - CMARK_NODE_TYPE_BLOCK - 1]++; +} + +static CMARK_INLINE void decr_open_block_count(cmark_parser *parser, cmark_node_type type) { + assert(type > CMARK_NODE_TYPE_BLOCK); + assert(type <= CMARK_NODE_TYPE_BLOCK + CMARK_NODE_TYPE_BLOCK_LIMIT); + assert(parser->open_block_counts[type - CMARK_NODE_TYPE_BLOCK - 1] > 0); + parser->open_block_counts[type - CMARK_NODE_TYPE_BLOCK - 1]--; +} + #ifdef __cplusplus } #endif diff --git a/src/syntax_extension.c b/src/syntax_extension.c index d24fe43e6..a2fb3b04d 100644 --- a/src/syntax_extension.c +++ b/src/syntax_extension.c @@ -29,7 +29,10 @@ cmark_syntax_extension *cmark_syntax_extension_new(const char *name) { cmark_node_type cmark_syntax_extension_add_node(int is_inline) { cmark_node_type *ref = !is_inline ? &CMARK_NODE_LAST_BLOCK : &CMARK_NODE_LAST_INLINE; - if ((*ref & CMARK_NODE_VALUE_MASK) == CMARK_NODE_VALUE_MASK) { + if ((*ref & CMARK_NODE_VALUE_MASK) >= CMARK_NODE_TYPE_BLOCK_LIMIT) { + // This assertion will fail if you try to register more extensions than + // are currently allowed by CMARK_NODE_TYPE_BLOCK_MAXNUM. Try increasing + // the limit. assert(false); return (cmark_node_type) 0; } From aa73711d67f0230b3cd5cc0b2c17a672d82f59dd Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Mon, 6 Mar 2023 22:15:58 +0000 Subject: [PATCH 17/47] Early-terminate the loop in check_open_blocks when the current line is blank. --- src/blocks.c | 32 +++++++++++++++++++++++++++++--- src/parser.h | 10 ++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/blocks.c b/src/blocks.c index 63741594e..2c70b21c9 100644 --- a/src/blocks.c +++ b/src/blocks.c @@ -409,9 +409,11 @@ static cmark_node *finalize(cmark_parser *parser, cmark_node *b) { // 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 field. + 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 memcmp(tmp_parser.open_block_counts, parser->open_block_counts, sizeof(parser->open_block_counts)) == 0; + 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. @@ -1082,10 +1084,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); @@ -1097,6 +1103,26 @@ static cmark_node *check_open_blocks(cmark_parser *parser, cmark_chunk *input, continue; } + if (parser->blank) { + 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); + const size_t n_para = read_open_block_count(&tmp_parser, CMARK_NODE_PARAGRAPH); + if (n_list + n_item + n_para == tmp_parser.total_open_blocks) { + if (parser->current->flags & CMARK_NODE__OPEN_BLOCK) { + if (S_type(parser->current) == CMARK_NODE_PARAGRAPH) { + container = parser->current; + goto done; + } + if (S_type(parser->current) == CMARK_NODE_ITEM) { + if (parser->current->flags & CMARK_NODE__OPEN) { + container = parser->current; + cont_type = S_type(container); + } + } + } + } + } + switch (cont_type) { case CMARK_NODE_BLOCK_QUOTE: if (!parse_block_quote_prefix(parser, input)) @@ -1387,7 +1413,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; } diff --git a/src/parser.h b/src/parser.h index 5020aa20b..05403fe3d 100644 --- a/src/parser.h +++ b/src/parser.h @@ -66,12 +66,14 @@ struct cmark_parser { * For example, CMARK_NODE_LIST (0x8003) is stored at offset 2. */ size_t open_block_counts[CMARK_NODE_TYPE_BLOCK_LIMIT]; + size_t total_open_blocks; }; static CMARK_INLINE void incr_open_block_count(cmark_parser *parser, cmark_node_type type) { assert(type > CMARK_NODE_TYPE_BLOCK); assert(type <= CMARK_NODE_TYPE_BLOCK + CMARK_NODE_TYPE_BLOCK_LIMIT); parser->open_block_counts[type - CMARK_NODE_TYPE_BLOCK - 1]++; + parser->total_open_blocks++; } static CMARK_INLINE void decr_open_block_count(cmark_parser *parser, cmark_node_type type) { @@ -79,6 +81,14 @@ static CMARK_INLINE void decr_open_block_count(cmark_parser *parser, cmark_node_ assert(type <= CMARK_NODE_TYPE_BLOCK + CMARK_NODE_TYPE_BLOCK_LIMIT); assert(parser->open_block_counts[type - CMARK_NODE_TYPE_BLOCK - 1] > 0); parser->open_block_counts[type - CMARK_NODE_TYPE_BLOCK - 1]--; + assert(parser->total_open_blocks > 0); + parser->total_open_blocks--; +} + +static CMARK_INLINE size_t read_open_block_count(cmark_parser *parser, cmark_node_type type) { + assert(type > CMARK_NODE_TYPE_BLOCK); + assert(type <= CMARK_NODE_TYPE_BLOCK + CMARK_NODE_TYPE_BLOCK_LIMIT); + return parser->open_block_counts[type - CMARK_NODE_TYPE_BLOCK - 1]; } #ifdef __cplusplus From 27ba3a4b24c837bc154c56254fcd63798489d953 Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Wed, 8 Mar 2023 11:34:48 +0000 Subject: [PATCH 18/47] Also handle CMARK_NODE_LIST --- src/blocks.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/blocks.c b/src/blocks.c index 2c70b21c9..4091bb71c 100644 --- a/src/blocks.c +++ b/src/blocks.c @@ -1109,14 +1109,16 @@ static cmark_node *check_open_blocks(cmark_parser *parser, cmark_chunk *input, const size_t n_para = read_open_block_count(&tmp_parser, CMARK_NODE_PARAGRAPH); if (n_list + n_item + n_para == tmp_parser.total_open_blocks) { if (parser->current->flags & CMARK_NODE__OPEN_BLOCK) { - if (S_type(parser->current) == CMARK_NODE_PARAGRAPH) { - container = parser->current; - goto done; - } - if (S_type(parser->current) == CMARK_NODE_ITEM) { - if (parser->current->flags & CMARK_NODE__OPEN) { + if (parser->current->flags & CMARK_NODE__OPEN) { + switch (S_type(parser->current)) { + case CMARK_NODE_PARAGRAPH: + case CMARK_NODE_LIST: + case CMARK_NODE_ITEM: container = parser->current; cont_type = S_type(container); + break; + default: + break; } } } From a97a478930ff164a2736d5011e2deafede214907 Mon Sep 17 00:00:00 2001 From: Vas Sudanagunta Date: Thu, 9 Mar 2023 11:06:24 -0500 Subject: [PATCH 19/47] fix test/regression.txt regression fixes #312 --- test/regression.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/regression.txt b/test/regression.txt index 1334a674a..0ec493d3d 100644 --- a/test/regression.txt +++ b/test/regression.txt @@ -4,8 +4,7 @@ Issue #113: EOL character weirdness on Windows (Important: first line ends with CR + CR + LF) ```````````````````````````````` example -line1 - +line1 line2 .

line1

From e27a52f81e85fed8721e9f64f857694f5f10d7f7 Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Fri, 10 Mar 2023 11:36:21 +0000 Subject: [PATCH 20/47] Also handle CMARK_NODE_CODE_BLOCK --- src/blocks.c | 40 ++++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/src/blocks.c b/src/blocks.c index 4091bb71c..d3b176a51 100644 --- a/src/blocks.c +++ b/src/blocks.c @@ -1103,17 +1103,41 @@ static cmark_node *check_open_blocks(cmark_parser *parser, cmark_chunk *input, continue; } - if (parser->blank) { - 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); - const size_t n_para = read_open_block_count(&tmp_parser, CMARK_NODE_PARAGRAPH); - if (n_list + n_item + n_para == tmp_parser.total_open_blocks) { - if (parser->current->flags & CMARK_NODE__OPEN_BLOCK) { - if (parser->current->flags & CMARK_NODE__OPEN) { + // 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_PARAGRAPH: 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: + // Jump to parser->current container = parser->current; cont_type = S_type(container); break; From b44f4795ed00fd275038c3dea09761349f5733bd Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Tue, 14 Mar 2023 15:09:03 +0000 Subject: [PATCH 21/47] Also handle CMARK_NODE_HTML_BLOCK --- src/blocks.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/blocks.c b/src/blocks.c index d3b176a51..b5a02b7db 100644 --- a/src/blocks.c +++ b/src/blocks.c @@ -259,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)); + } } } @@ -1137,6 +1140,7 @@ static cmark_node *check_open_blocks(cmark_parser *parser, cmark_chunk *input, // 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); From e840cad9178143d9c4dc729ef93def68709d2712 Mon Sep 17 00:00:00 2001 From: Richard Huveneers Date: Sun, 19 Mar 2023 09:50:05 +0100 Subject: [PATCH 22/47] Copied prototype change to implementation --- src/node.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node.c b/src/node.c index 56daf0aa6..5cab11fc9 100644 --- a/src/node.c +++ b/src/node.c @@ -29,7 +29,7 @@ void cmark_register_node_flag(cmark_node_internal_flags *flags) { nextflag <<= 1; } -void cmark_init_standard_node_flags() {} +void cmark_init_standard_node_flags(void) {} bool cmark_node_can_contain_type(cmark_node *node, cmark_node_type child_type) { if (child_type == CMARK_NODE_DOCUMENT) { From 828322d1ee4facdab56f0d3edccb13e9af90dcd2 Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Sat, 25 Mar 2023 14:24:25 +0000 Subject: [PATCH 23/47] Update expected output --- test/spec.txt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/spec.txt b/test/spec.txt index eaab9032d..d42f3369e 100644 --- a/test/spec.txt +++ b/test/spec.txt @@ -6926,7 +6926,7 @@ foo__bar__ ```````````````````````````````` example __foo, __bar__, baz__ . -

foo, bar, baz

+

foo, bar, baz

```````````````````````````````` @@ -7197,7 +7197,7 @@ foo***bar***baz ```````````````````````````````` example foo******bar*********baz . -

foobar***baz

+

foobar***baz

```````````````````````````````` @@ -7268,21 +7268,21 @@ __foo _bar_ baz__ ```````````````````````````````` example __foo __bar__ baz__ . -

foo bar baz

+

foo bar baz

```````````````````````````````` ```````````````````````````````` example ____foo__ bar__ . -

foo bar

+

foo bar

```````````````````````````````` ```````````````````````````````` example **foo **bar**** . -

foo bar

+

foo bar

```````````````````````````````` @@ -7567,14 +7567,14 @@ switching delimiters: ```````````````````````````````` example ****foo**** . -

foo

+

foo

```````````````````````````````` ```````````````````````````````` example ____foo____ . -

foo

+

foo

```````````````````````````````` @@ -7585,7 +7585,7 @@ delimiters: ```````````````````````````````` example ******foo****** . -

foo

+

foo

```````````````````````````````` @@ -7601,7 +7601,7 @@ Rule 14: ```````````````````````````````` example _____foo_____ . -

foo

+

foo

```````````````````````````````` From f7e31f8d7af9e9e7b76a68055a9d9b4a25e26286 Mon Sep 17 00:00:00 2001 From: John MacFarlane Date: Sun, 23 Aug 2020 10:58:08 -0700 Subject: [PATCH 24/47] Add MAX_INDENT for xml. Otherwise we can get quadratic increase in size with deeply nested structures. See #355. --- src/xml.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/xml.c b/src/xml.c index 2975bf96c..5753e5ab9 100644 --- a/src/xml.c +++ b/src/xml.c @@ -11,6 +11,7 @@ #include "syntax_extension.h" #define BUFFER_SIZE 100 +#define MAX_INDENT 40 // Functions to convert cmark_nodes to XML strings. @@ -26,7 +27,7 @@ struct render_state { static CMARK_INLINE void indent(struct render_state *state) { int i; - for (i = 0; i < state->indent; i++) { + for (i = 0; i < state->indent && i < MAX_INDENT; i++) { cmark_strbuf_putc(state->xml, ' '); } } From 763587e8775350b8cb4a2aa0f4cec3685aa96e8b Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Tue, 28 Mar 2023 11:07:06 +0100 Subject: [PATCH 25/47] Fix quadratic performance issue in list numbering. --- src/commonmark.c | 8 ++------ src/man.c | 8 ++------ src/plaintext.c | 8 ++------ src/render.c | 4 ++-- src/render.h | 1 + 5 files changed, 9 insertions(+), 20 deletions(-) diff --git a/src/commonmark.c b/src/commonmark.c index 8d0644d57..843cf710f 100644 --- a/src/commonmark.c +++ b/src/commonmark.c @@ -231,19 +231,15 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, LIT(""); 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 diff --git a/src/man.c b/src/man.c index da0706509..c6383bf86 100644 --- a/src/man.c +++ b/src/man.c @@ -114,6 +114,7 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, break; case CMARK_NODE_LIST: + renderer->list_number = cmark_node_get_list_start(node); break; case CMARK_NODE_ITEM: @@ -123,12 +124,7 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, if (cmark_node_get_list_type(node->parent) == CMARK_BULLET_LIST) { LIT("\\[bu] 2"); } else { - list_number = cmark_node_get_list_start(node->parent); - tmp = node; - while (tmp->prev) { - tmp = tmp->prev; - list_number += 1; - } + list_number = renderer->list_number++; char list_number_s[LIST_NUMBER_SIZE]; snprintf(list_number_s, LIST_NUMBER_SIZE, "\"%d.\" 4", list_number); LIT(list_number_s); diff --git a/src/plaintext.c b/src/plaintext.c index d9c6a060b..e708165c7 100644 --- a/src/plaintext.c +++ b/src/plaintext.c @@ -76,19 +76,15 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, node->next->type == CMARK_NODE_LIST)) { CR(); } + 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 diff --git a/src/render.c b/src/render.c index 02e9e838b..c6dd23473 100644 --- a/src/render.c +++ b/src/render.c @@ -177,8 +177,8 @@ char *cmark_render(cmark_mem *mem, cmark_node *root, int options, int width, cmark_renderer renderer = {mem, &buf, &pref, 0, width, 0, 0, true, true, false, - false, outc, S_cr, S_blankline, S_out, - 0}; + false, 0, outc, S_cr, S_blankline, + S_out, 0}; while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) { cur = cmark_iter_get_node(iter); diff --git a/src/render.h b/src/render.h index 4a68d1e07..aa5162f94 100644 --- a/src/render.h +++ b/src/render.h @@ -23,6 +23,7 @@ struct cmark_renderer { bool begin_content; bool no_linebreaks; bool in_tight_list_item; + int list_number; void (*outc)(struct cmark_renderer *, cmark_node *, cmark_escaping, int32_t, unsigned char); void (*cr)(struct cmark_renderer *); void (*blankline)(struct cmark_renderer *); From 78e1cc07692c1cd7d12c3e64da8047e82e84b6cb Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Tue, 28 Mar 2023 14:07:09 +0100 Subject: [PATCH 26/47] Add ancestor_extension field. --- src/node.h | 8 ++++++++ src/render.c | 13 ++++++------- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/node.h b/src/node.h index e025e25d6..fa47e4468 100644 --- a/src/node.h +++ b/src/node.h @@ -82,6 +82,14 @@ struct cmark_node { cmark_syntax_extension *extension; + /** + * Used during cmark_render() to cache the most recent non-NULL + * extension, if you go up the parent chain like this: + * + * node->parent->...parent->extension + */ + cmark_syntax_extension *ancestor_extension; + union { int ref_ix; int def_count; diff --git a/src/render.c b/src/render.c index c6dd23473..d7a83ebfb 100644 --- a/src/render.c +++ b/src/render.c @@ -31,13 +31,7 @@ static void S_out(cmark_renderer *renderer, cmark_node *node, cmark_chunk remainder = cmark_chunk_literal(""); int k = renderer->buffer->size - 1; - cmark_syntax_extension *ext = NULL; - cmark_node *n = node; - while (n && !ext) { - ext = n->extension; - if (!ext) - n = n->parent; - } + cmark_syntax_extension *ext = node->ancestor_extension; if (ext && !ext->commonmark_escape_func) ext = NULL; @@ -182,6 +176,11 @@ char *cmark_render(cmark_mem *mem, cmark_node *root, int options, int width, while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) { cur = cmark_iter_get_node(iter); + if (cur->extension) { + cur->ancestor_extension = cur->extension; + } else if (cur->parent) { + cur->ancestor_extension = cur->parent->ancestor_extension; + } if (!render_node(&renderer, cur, ev_type, options)) { // a false value causes us to skip processing // the node's contents. this is used for From bd4f96e7fd06ce8cb61e6f4ba41972d2a8b91d24 Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Wed, 29 Mar 2023 21:30:22 +0100 Subject: [PATCH 27/47] Remove dead code --- src/commonmark.c | 15 --------------- src/man.c | 1 - src/plaintext.c | 15 --------------- 3 files changed, 31 deletions(-) diff --git a/src/commonmark.c b/src/commonmark.c index 843cf710f..f2210cdfb 100644 --- a/src/commonmark.c +++ b/src/commonmark.c @@ -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; diff --git a/src/man.c b/src/man.c index c6383bf86..e40e46ce2 100644 --- a/src/man.c +++ b/src/man.c @@ -74,7 +74,6 @@ static void S_outc(cmark_renderer *renderer, cmark_node *node, static int S_render_node(cmark_renderer *renderer, cmark_node *node, cmark_event_type ev_type, int options) { - cmark_node *tmp; int list_number; bool entering = (ev_type == CMARK_EVENT_ENTER); bool allow_wrap = renderer->width > 0 && !(CMARK_OPT_NOBREAKS & options); diff --git a/src/plaintext.c b/src/plaintext.c index e708165c7..a40476210 100644 --- a/src/plaintext.c +++ b/src/plaintext.c @@ -16,23 +16,8 @@ static CMARK_INLINE void outc(cmark_renderer *renderer, cmark_node *node, cmark_render_code_point(renderer, c); } -// 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 i; From 178943f4b00990532e7371fdffe097ca6284b1f0 Mon Sep 17 00:00:00 2001 From: Bas Alberts Date: Fri, 31 Mar 2023 11:09:08 -0400 Subject: [PATCH 28/47] 0.29.0.gfm.10 changelog --- changelog.txt | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/changelog.txt b/changelog.txt index 4cc5c58ec..28ba62915 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,25 @@ +[0.29.0.gfm.10] + + * Fixed polynomial time complexity issue per + https://github.com/github/cmark-gfm/security/advisories/GHSA-r8vr-c48j-fcc5 + * Fixed polynomial time complexity issues per + https://github.com/github/cmark-gfm/security/advisories/GHSA-66g8-4hjf-77xh + + Note: these changes remove redundant bold tag nesting which may result + in existing rendering tests failing, e.g. rendering "____bold____" to html + will no longer yield "

bold

". + +[0.29.0.gfm.9] + + * Cleanup: Use of a private header was cleaned up (#248) + * Cleanup: Man page was update (#255) + * Cleanup: Warnings for -Wstrict-prototypes were cleaned up (#285) + * Cleanup: We avoid header duplication (#289) + + * We now store positioning info for url_match (#201) + * We now expose cmark_parent_footnote_def for non-C renderers (#254) + * Footnote aria-label text now reference the specific footnote backref, and we include a data-footnote-backref-idx attribute so the label can be internationalized in a downstream filter (#307) + [0.29.0.gfm.8] * We restored backwards compatibility by deprecating the `cmark_init_standard_node_flags()` requirement, which is now a noop (#305) From 05e3ddb6689986b3f1951871eaf0b03bf45e967e Mon Sep 17 00:00:00 2001 From: Bas Alberts Date: Fri, 31 Mar 2023 11:09:46 -0400 Subject: [PATCH 29/47] bump version string --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 96559be59..76b291142 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ project(cmark-gfm) set(PROJECT_VERSION_MAJOR 0) set(PROJECT_VERSION_MINOR 29) set(PROJECT_VERSION_PATCH 0) -set(PROJECT_VERSION_GFM 6) +set(PROJECT_VERSION_GFM 10) set(PROJECT_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}.gfm.${PROJECT_VERSION_GFM}) include("FindAsan.cmake") From 6e4493d628e71e8e6b25918bbd69733f37f8b03a Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Wed, 22 Feb 2023 13:01:20 +0000 Subject: [PATCH 30/47] Fix GHSA-66g8-4hjf-77xh: don't run expense safety check which causes quadratic performance. --- api_test/main.c | 1 + src/node.c | 30 ++++++++++++++++++++---------- src/node.h | 7 +++++++ 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/api_test/main.c b/api_test/main.c index 62006eaa9..f9f83cdaa 100644 --- a/api_test/main.c +++ b/api_test/main.c @@ -1133,6 +1133,7 @@ int main() { int retval; test_batch_runner *runner = test_batch_runner_new(); + cmark_enable_safety_checks(true); version(runner); constructor(runner); accessors(runner); diff --git a/src/node.c b/src/node.c index 56daf0aa6..4866786da 100644 --- a/src/node.c +++ b/src/node.c @@ -5,6 +5,16 @@ #include "node.h" #include "syntax_extension.h" +/** + * Expensive safety checks are off by default, but can be enabled + * by calling cmark_enable_safety_checks(). + */ +static bool enable_safety_checks = false; + +void cmark_enable_safety_checks(bool enable) { + enable_safety_checks = enable; +} + static void S_node_unlink(cmark_node *node); #define NODE_MEM(node) cmark_node_mem(node) @@ -70,8 +80,6 @@ bool cmark_node_can_contain_type(cmark_node *node, cmark_node_type child_type) { } static bool S_can_contain(cmark_node *node, cmark_node *child) { - cmark_node *cur; - if (node == NULL || child == NULL) { return false; } @@ -79,14 +87,16 @@ static bool S_can_contain(cmark_node *node, cmark_node *child) { return 0; } - // Verify that child is not an ancestor of node or equal to node. - cur = node; - do { - if (cur == child) { - return false; - } - cur = cur->parent; - } while (cur != NULL); + if (enable_safety_checks) { + // Verify that child is not an ancestor of node or equal to node. + cmark_node *cur = node; + do { + if (cur == child) { + return false; + } + cur = cur->parent; + } while (cur != NULL); + } return cmark_node_can_contain_type(node, (cmark_node_type) child->type); } diff --git a/src/node.h b/src/node.h index 827749ff8..36c5c2cb0 100644 --- a/src/node.h +++ b/src/node.h @@ -145,6 +145,13 @@ static CMARK_INLINE bool CMARK_NODE_INLINE_P(cmark_node *node) { CMARK_GFM_EXPORT bool cmark_node_can_contain_type(cmark_node *node, cmark_node_type child_type); +/** + * Enable (or disable) extra safety checks. These extra checks cause + * extra performance overhead (in some cases quadratic), so they are only + * intended to be used during testing. + */ +CMARK_GFM_EXPORT void cmark_enable_safety_checks(bool enable); + #ifdef __cplusplus } #endif From bdabba476636510505cf760b94ae941bdb7119b3 Mon Sep 17 00:00:00 2001 From: Phill MV Date: Fri, 31 Mar 2023 13:50:13 -0400 Subject: [PATCH 31/47] Update changelog.txt --- changelog.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 28ba62915..9601ab4ee 100644 --- a/changelog.txt +++ b/changelog.txt @@ -12,7 +12,7 @@ [0.29.0.gfm.9] * Cleanup: Use of a private header was cleaned up (#248) - * Cleanup: Man page was update (#255) + * Cleanup: Man page was updated (#255) * Cleanup: Warnings for -Wstrict-prototypes were cleaned up (#285) * Cleanup: We avoid header duplication (#289) From d5b0cfb6a0a5276537e049c960e632e69d552b42 Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Fri, 31 Mar 2023 21:25:57 +0100 Subject: [PATCH 32/47] Fuzz target for bracketed patterns, such as [[[[x]]]]. --- fuzz/CMakeLists.txt | 1 + fuzz/fuzz_quadratic_brackets.c | 108 +++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 fuzz/fuzz_quadratic_brackets.c diff --git a/fuzz/CMakeLists.txt b/fuzz/CMakeLists.txt index 3cae79421..a9ed57aa5 100644 --- a/fuzz/CMakeLists.txt +++ b/fuzz/CMakeLists.txt @@ -19,3 +19,4 @@ macro(fuzzer name) endmacro() fuzzer(fuzz_quadratic) +fuzzer(fuzz_quadratic_brackets) diff --git a/fuzz/fuzz_quadratic_brackets.c b/fuzz/fuzz_quadratic_brackets.c new file mode 100644 index 000000000..d1719e19e --- /dev/null +++ b/fuzz/fuzz_quadratic_brackets.c @@ -0,0 +1,108 @@ +#include +#include +#include +#include "cmark-gfm.h" +#include "cmark-gfm-core-extensions.h" +#include +#include +#include +#include + +const char *extension_names[] = { + "autolink", + "strikethrough", + "table", + "tagfilter", + NULL, +}; + +int LLVMFuzzerInitialize(int *argc, char ***argv) { + cmark_gfm_core_extensions_ensure_registered(); + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + struct __attribute__((packed)) { + int options; + int width; + uint8_t startlen; + uint8_t openlen; + uint8_t middlelen; + uint8_t closelen; + } fuzz_config; + + if (size >= sizeof(fuzz_config)) { + /* The beginning of `data` is treated as fuzzer configuration */ + memcpy(&fuzz_config, data, sizeof(fuzz_config)); + + /* Test options that are used by GitHub. */ + fuzz_config.options = CMARK_OPT_UNSAFE | CMARK_OPT_FOOTNOTES | CMARK_OPT_GITHUB_PRE_LANG | CMARK_OPT_HARDBREAKS; + fuzz_config.openlen = fuzz_config.openlen & 0x7; + fuzz_config.middlelen = fuzz_config.middlelen & 0x7; + fuzz_config.closelen = fuzz_config.closelen & 0x7; + + /* Remainder of input is the markdown */ + const char *markdown0 = (const char *)(data + sizeof(fuzz_config)); + const size_t markdown_size0 = size - sizeof(fuzz_config); + char markdown[0x80000]; + if (markdown_size0 <= sizeof(markdown)) { + size_t markdown_size = 0; + const size_t componentslen = fuzz_config.startlen + fuzz_config.openlen + fuzz_config.middlelen + fuzz_config.closelen; + if (componentslen <= markdown_size0) { + size_t offset = 0; + const size_t endlen = markdown_size0 - componentslen; + memcpy(&markdown[markdown_size], &markdown0[offset], fuzz_config.startlen); + markdown_size += fuzz_config.startlen; + offset += fuzz_config.startlen; + + if (0 < fuzz_config.openlen) { + while (markdown_size + fuzz_config.openlen <= sizeof(markdown)/2) { + memcpy(&markdown[markdown_size], &markdown0[offset], + fuzz_config.openlen); + markdown_size += fuzz_config.openlen; + } + offset += fuzz_config.openlen; + } + memcpy(&markdown[markdown_size], &markdown0[offset], + fuzz_config.middlelen); + markdown_size += fuzz_config.middlelen; + offset += fuzz_config.middlelen; + if (0 < fuzz_config.closelen) { + while (markdown_size + fuzz_config.closelen + endlen <= sizeof(markdown)) { + memcpy(&markdown[markdown_size], &markdown0[offset], + fuzz_config.closelen); + markdown_size += fuzz_config.closelen; + } + offset += fuzz_config.closelen; + } + memcpy(&markdown[markdown_size], &markdown0[offset], + endlen); + markdown_size += endlen; + } else { + markdown_size = markdown_size0; + memcpy(markdown, markdown0, markdown_size); + } + + cmark_parser *parser = cmark_parser_new(fuzz_config.options); + + for (const char **it = extension_names; *it; ++it) { + const char *extension_name = *it; + cmark_syntax_extension *syntax_extension = cmark_find_syntax_extension(extension_name); + if (!syntax_extension) { + fprintf(stderr, "%s is not a valid syntax extension\n", extension_name); + abort(); + } + cmark_parser_attach_syntax_extension(parser, syntax_extension); + } + + cmark_parser_feed(parser, markdown, markdown_size); + cmark_node *doc = cmark_parser_finish(parser); + + free(cmark_render_html(doc, fuzz_config.options, NULL)); + + cmark_node_free(doc); + cmark_parser_free(parser); + } + } + return 0; +} From 6ab3e99a9a347279fe74cabb3181da5cebd64df1 Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Sun, 2 Apr 2023 12:51:13 +0100 Subject: [PATCH 33/47] Partially revert "Remove dead code" This partially reverts commit bd4f96e7fd06ce8cb61e6f4ba41972d2a8b91d24. --- src/commonmark.c | 1 + src/man.c | 1 + src/plaintext.c | 1 + 3 files changed, 3 insertions(+) diff --git a/src/commonmark.c b/src/commonmark.c index f2210cdfb..6101064e0 100644 --- a/src/commonmark.c +++ b/src/commonmark.c @@ -155,6 +155,7 @@ static bool is_autolink(cmark_node *node) { 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; diff --git a/src/man.c b/src/man.c index e40e46ce2..c6383bf86 100644 --- a/src/man.c +++ b/src/man.c @@ -74,6 +74,7 @@ static void S_outc(cmark_renderer *renderer, cmark_node *node, static int S_render_node(cmark_renderer *renderer, cmark_node *node, cmark_event_type ev_type, int options) { + cmark_node *tmp; int list_number; bool entering = (ev_type == CMARK_EVENT_ENTER); bool allow_wrap = renderer->width > 0 && !(CMARK_OPT_NOBREAKS & options); diff --git a/src/plaintext.c b/src/plaintext.c index a40476210..446537acc 100644 --- a/src/plaintext.c +++ b/src/plaintext.c @@ -18,6 +18,7 @@ static CMARK_INLINE void outc(cmark_renderer *renderer, cmark_node *node, 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 i; From 36f81124db45f813c3bcc779e6ad6b5fb9c8b299 Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Sun, 2 Apr 2023 12:51:39 +0100 Subject: [PATCH 34/47] Revert "Fix quadratic performance issue in list numbering." This reverts commit 763587e8775350b8cb4a2aa0f4cec3685aa96e8b. --- src/commonmark.c | 8 ++++++-- src/man.c | 8 ++++++-- src/plaintext.c | 8 ++++++-- src/render.c | 4 ++-- src/render.h | 1 - 5 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/commonmark.c b/src/commonmark.c index 6101064e0..d2d2ef5dc 100644 --- a/src/commonmark.c +++ b/src/commonmark.c @@ -217,15 +217,19 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, LIT(""); 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 = renderer->list_number++; + list_number = cmark_node_get_list_start(node->parent); 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 diff --git a/src/man.c b/src/man.c index c6383bf86..da0706509 100644 --- a/src/man.c +++ b/src/man.c @@ -114,7 +114,6 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, break; case CMARK_NODE_LIST: - renderer->list_number = cmark_node_get_list_start(node); break; case CMARK_NODE_ITEM: @@ -124,7 +123,12 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, if (cmark_node_get_list_type(node->parent) == CMARK_BULLET_LIST) { LIT("\\[bu] 2"); } else { - list_number = renderer->list_number++; + list_number = cmark_node_get_list_start(node->parent); + tmp = node; + while (tmp->prev) { + tmp = tmp->prev; + list_number += 1; + } char list_number_s[LIST_NUMBER_SIZE]; snprintf(list_number_s, LIST_NUMBER_SIZE, "\"%d.\" 4", list_number); LIT(list_number_s); diff --git a/src/plaintext.c b/src/plaintext.c index 446537acc..a145ca723 100644 --- a/src/plaintext.c +++ b/src/plaintext.c @@ -62,15 +62,19 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, node->next->type == CMARK_NODE_LIST)) { CR(); } - 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 = renderer->list_number++; + list_number = cmark_node_get_list_start(node->parent); 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 diff --git a/src/render.c b/src/render.c index d7a83ebfb..4734f71be 100644 --- a/src/render.c +++ b/src/render.c @@ -171,8 +171,8 @@ char *cmark_render(cmark_mem *mem, cmark_node *root, int options, int width, cmark_renderer renderer = {mem, &buf, &pref, 0, width, 0, 0, true, true, false, - false, 0, outc, S_cr, S_blankline, - S_out, 0}; + false, outc, S_cr, S_blankline, S_out, + 0}; while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) { cur = cmark_iter_get_node(iter); diff --git a/src/render.h b/src/render.h index aa5162f94..4a68d1e07 100644 --- a/src/render.h +++ b/src/render.h @@ -23,7 +23,6 @@ struct cmark_renderer { bool begin_content; bool no_linebreaks; bool in_tight_list_item; - int list_number; void (*outc)(struct cmark_renderer *, cmark_node *, cmark_escaping, int32_t, unsigned char); void (*cr)(struct cmark_renderer *); void (*blankline)(struct cmark_renderer *); From f040422cd787ea3fd22cb53d14e22952fb31055d Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Sun, 2 Apr 2023 15:51:23 +0100 Subject: [PATCH 35/47] Store list item index on the node during rendering, to avoid quadratic performance. --- src/cmark-gfm.h | 11 +++++++++++ src/commonmark.c | 8 +------- src/man.c | 8 +------- src/node.c | 25 +++++++++++++++++++++++++ src/plaintext.c | 8 +------- src/render.c | 9 +++++++++ 6 files changed, 48 insertions(+), 21 deletions(-) diff --git a/src/cmark-gfm.h b/src/cmark-gfm.h index 902c392de..663a77cef 100644 --- a/src/cmark-gfm.h +++ b/src/cmark-gfm.h @@ -423,6 +423,17 @@ CMARK_GFM_EXPORT int cmark_node_get_list_tight(cmark_node *node); */ CMARK_GFM_EXPORT int cmark_node_set_list_tight(cmark_node *node, int tight); +/** + * Returns item index of 'node'. This is only used when rendering output + * formats such as commonmark, which need to output the index. It is not + * required for formats such as html or latex. + */ +CMARK_GFM_EXPORT int cmark_node_get_item_index(cmark_node *node); + +/** Sets item index of 'node'. Returns 1 on success, 0 on failure. + */ +CMARK_GFM_EXPORT int cmark_node_set_item_index(cmark_node *node, int idx); + /** Returns the info string from a fenced code block. */ CMARK_GFM_EXPORT const char *cmark_node_get_fence_info(cmark_node *node); diff --git a/src/commonmark.c b/src/commonmark.c index d2d2ef5dc..4815bfc3c 100644 --- a/src/commonmark.c +++ b/src/commonmark.c @@ -155,7 +155,6 @@ static bool is_autolink(cmark_node *node) { 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; @@ -223,13 +222,8 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, 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 = cmark_node_get_item_index(node); 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 diff --git a/src/man.c b/src/man.c index da0706509..634fd9d0f 100644 --- a/src/man.c +++ b/src/man.c @@ -74,7 +74,6 @@ static void S_outc(cmark_renderer *renderer, cmark_node *node, static int S_render_node(cmark_renderer *renderer, cmark_node *node, cmark_event_type ev_type, int options) { - cmark_node *tmp; int list_number; bool entering = (ev_type == CMARK_EVENT_ENTER); bool allow_wrap = renderer->width > 0 && !(CMARK_OPT_NOBREAKS & options); @@ -123,12 +122,7 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, if (cmark_node_get_list_type(node->parent) == CMARK_BULLET_LIST) { LIT("\\[bu] 2"); } else { - list_number = cmark_node_get_list_start(node->parent); - tmp = node; - while (tmp->prev) { - tmp = tmp->prev; - list_number += 1; - } + list_number = cmark_node_get_item_index(node); char list_number_s[LIST_NUMBER_SIZE]; snprintf(list_number_s, LIST_NUMBER_SIZE, "\"%d.\" 4", list_number); LIT(list_number_s); diff --git a/src/node.c b/src/node.c index 4866786da..7526113a4 100644 --- a/src/node.c +++ b/src/node.c @@ -564,6 +564,31 @@ int cmark_node_set_list_tight(cmark_node *node, int tight) { } } +int cmark_node_get_item_index(cmark_node *node) { + if (node == NULL) { + return 0; + } + + if (node->type == CMARK_NODE_ITEM) { + return node->as.list.start; + } else { + return 0; + } +} + +int cmark_node_set_item_index(cmark_node *node, int idx) { + if (node == NULL || idx < 0) { + return 0; + } + + if (node->type == CMARK_NODE_ITEM) { + node->as.list.start = idx; + return 1; + } else { + return 0; + } +} + const char *cmark_node_get_fence_info(cmark_node *node) { if (node == NULL) { return NULL; diff --git a/src/plaintext.c b/src/plaintext.c index a145ca723..0c7d257b3 100644 --- a/src/plaintext.c +++ b/src/plaintext.c @@ -18,7 +18,6 @@ static CMARK_INLINE void outc(cmark_renderer *renderer, cmark_node *node, 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 i; @@ -68,13 +67,8 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, 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 = cmark_node_get_item_index(node); 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 diff --git a/src/render.c b/src/render.c index 4734f71be..1a0d2ae8d 100644 --- a/src/render.c +++ b/src/render.c @@ -181,6 +181,15 @@ char *cmark_render(cmark_mem *mem, cmark_node *root, int options, int width, } else if (cur->parent) { cur->ancestor_extension = cur->parent->ancestor_extension; } + if (cur->type == CMARK_NODE_ITEM) { + // Calculate the list item's index, for the benefit of output formats + // like commonmark and plaintext. + if (cur->prev) { + cmark_node_set_item_index(cur, 1 + cmark_node_get_item_index(cur->prev)); + } else { + cmark_node_set_item_index(cur, cmark_node_get_list_start(cur->parent)); + } + } if (!render_node(&renderer, cur, ev_type, options)) { // a false value causes us to skip processing // the node's contents. this is used for From dc01f7167d4a3e7fced0281e4962d75d7342609f Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Mon, 3 Apr 2023 14:17:15 +0100 Subject: [PATCH 36/47] Revert "Also handle CMARK_NODE_HTML_BLOCK" This reverts commit b44f4795ed00fd275038c3dea09761349f5733bd. --- src/blocks.c | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/blocks.c b/src/blocks.c index b5a02b7db..d3b176a51 100644 --- a/src/blocks.c +++ b/src/blocks.c @@ -259,18 +259,15 @@ 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) { - 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)); - } + 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)); } } @@ -1140,7 +1137,6 @@ static cmark_node *check_open_blocks(cmark_parser *parser, cmark_chunk *input, // 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); From 66a36c4386e1d2955061d67c3026872665ae78ff Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Mon, 3 Apr 2023 14:17:19 +0100 Subject: [PATCH 37/47] Revert "Also handle CMARK_NODE_CODE_BLOCK" This reverts commit e27a52f81e85fed8721e9f64f857694f5f10d7f7. --- src/blocks.c | 40 ++++++++-------------------------------- 1 file changed, 8 insertions(+), 32 deletions(-) diff --git a/src/blocks.c b/src/blocks.c index d3b176a51..4091bb71c 100644 --- a/src/blocks.c +++ b/src/blocks.c @@ -1103,41 +1103,17 @@ 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. + if (parser->blank) { + 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); + const size_t n_para = read_open_block_count(&tmp_parser, CMARK_NODE_PARAGRAPH); + if (n_list + n_item + n_para == tmp_parser.total_open_blocks) { + if (parser->current->flags & CMARK_NODE__OPEN_BLOCK) { + if (parser->current->flags & CMARK_NODE__OPEN) { switch (S_type(parser->current)) { + case CMARK_NODE_PARAGRAPH: 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: - // Jump to parser->current container = parser->current; cont_type = S_type(container); break; From 7f567e2292d6d4389fe9a44fa2d772391a09788d Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Mon, 3 Apr 2023 14:17:22 +0100 Subject: [PATCH 38/47] Revert "Also handle CMARK_NODE_LIST" This reverts commit 27ba3a4b24c837bc154c56254fcd63798489d953. --- src/blocks.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/blocks.c b/src/blocks.c index 4091bb71c..2c70b21c9 100644 --- a/src/blocks.c +++ b/src/blocks.c @@ -1109,16 +1109,14 @@ static cmark_node *check_open_blocks(cmark_parser *parser, cmark_chunk *input, const size_t n_para = read_open_block_count(&tmp_parser, CMARK_NODE_PARAGRAPH); if (n_list + n_item + n_para == tmp_parser.total_open_blocks) { if (parser->current->flags & CMARK_NODE__OPEN_BLOCK) { - if (parser->current->flags & CMARK_NODE__OPEN) { - switch (S_type(parser->current)) { - case CMARK_NODE_PARAGRAPH: - case CMARK_NODE_LIST: - case CMARK_NODE_ITEM: + if (S_type(parser->current) == CMARK_NODE_PARAGRAPH) { + container = parser->current; + goto done; + } + if (S_type(parser->current) == CMARK_NODE_ITEM) { + if (parser->current->flags & CMARK_NODE__OPEN) { container = parser->current; cont_type = S_type(container); - break; - default: - break; } } } From 0656bf98efeffb6c2c7146fca629357a724af625 Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Mon, 3 Apr 2023 14:17:25 +0100 Subject: [PATCH 39/47] Revert "Early-terminate the loop in check_open_blocks when the current line is blank." This reverts commit aa73711d67f0230b3cd5cc0b2c17a672d82f59dd. --- src/blocks.c | 32 +++----------------------------- src/parser.h | 10 ---------- 2 files changed, 3 insertions(+), 39 deletions(-) diff --git a/src/blocks.c b/src/blocks.c index 2c70b21c9..63741594e 100644 --- a/src/blocks.c +++ b/src/blocks.c @@ -409,11 +409,9 @@ static cmark_node *finalize(cmark_parser *parser, cmark_node *b) { // 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. + cmark_parser tmp_parser = {0}; // Only used for its open_block_counts field. 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; + return 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. @@ -1084,14 +1082,10 @@ 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); @@ -1103,26 +1097,6 @@ static cmark_node *check_open_blocks(cmark_parser *parser, cmark_chunk *input, continue; } - if (parser->blank) { - 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); - const size_t n_para = read_open_block_count(&tmp_parser, CMARK_NODE_PARAGRAPH); - if (n_list + n_item + n_para == tmp_parser.total_open_blocks) { - if (parser->current->flags & CMARK_NODE__OPEN_BLOCK) { - if (S_type(parser->current) == CMARK_NODE_PARAGRAPH) { - container = parser->current; - goto done; - } - if (S_type(parser->current) == CMARK_NODE_ITEM) { - if (parser->current->flags & CMARK_NODE__OPEN) { - container = parser->current; - cont_type = S_type(container); - } - } - } - } - } - switch (cont_type) { case CMARK_NODE_BLOCK_QUOTE: if (!parse_block_quote_prefix(parser, input)) @@ -1413,7 +1387,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 && S_last_line_blank(tmp->parent)) { + while (tmp->parent) { S_set_last_line_blank(tmp->parent, false); tmp = tmp->parent; } diff --git a/src/parser.h b/src/parser.h index 05403fe3d..5020aa20b 100644 --- a/src/parser.h +++ b/src/parser.h @@ -66,14 +66,12 @@ struct cmark_parser { * For example, CMARK_NODE_LIST (0x8003) is stored at offset 2. */ size_t open_block_counts[CMARK_NODE_TYPE_BLOCK_LIMIT]; - size_t total_open_blocks; }; static CMARK_INLINE void incr_open_block_count(cmark_parser *parser, cmark_node_type type) { assert(type > CMARK_NODE_TYPE_BLOCK); assert(type <= CMARK_NODE_TYPE_BLOCK + CMARK_NODE_TYPE_BLOCK_LIMIT); parser->open_block_counts[type - CMARK_NODE_TYPE_BLOCK - 1]++; - parser->total_open_blocks++; } static CMARK_INLINE void decr_open_block_count(cmark_parser *parser, cmark_node_type type) { @@ -81,14 +79,6 @@ static CMARK_INLINE void decr_open_block_count(cmark_parser *parser, cmark_node_ assert(type <= CMARK_NODE_TYPE_BLOCK + CMARK_NODE_TYPE_BLOCK_LIMIT); assert(parser->open_block_counts[type - CMARK_NODE_TYPE_BLOCK - 1] > 0); parser->open_block_counts[type - CMARK_NODE_TYPE_BLOCK - 1]--; - assert(parser->total_open_blocks > 0); - parser->total_open_blocks--; -} - -static CMARK_INLINE size_t read_open_block_count(cmark_parser *parser, cmark_node_type type) { - assert(type > CMARK_NODE_TYPE_BLOCK); - assert(type <= CMARK_NODE_TYPE_BLOCK + CMARK_NODE_TYPE_BLOCK_LIMIT); - return parser->open_block_counts[type - CMARK_NODE_TYPE_BLOCK - 1]; } #ifdef __cplusplus From 1eda738d8e121211574f6c4cd113bfa6807b8ca0 Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Mon, 3 Apr 2023 14:17:29 +0100 Subject: [PATCH 40/47] Revert "Keep a count of the number of open blocks." This reverts commit c72487d7a2198aa849229bd02e59d6f26f71899b. --- extensions/table.c | 6 ------ src/blocks.c | 43 ++---------------------------------------- src/cmark-gfm.h | 10 ---------- src/node.h | 7 +++---- src/parser.h | 29 ---------------------------- src/syntax_extension.c | 5 +---- 6 files changed, 6 insertions(+), 94 deletions(-) diff --git a/extensions/table.c b/extensions/table.c index 6e75e38d9..e53ea3154 100644 --- a/extensions/table.c +++ b/extensions/table.c @@ -311,18 +311,12 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self, } } - assert(cmark_node_get_type(parent_container) == CMARK_NODE_PARAGRAPH); if (!cmark_node_set_type(parent_container, CMARK_NODE_TABLE)) { free_table_row(parser->mem, header_row); free_table_row(parser->mem, marker_row); return parent_container; } - // Update the node counts after parent_container changed type. - assert(parent_container->next == NULL); - decr_open_block_count(parser, CMARK_NODE_PARAGRAPH); - incr_open_block_count(parser, CMARK_NODE_TABLE); - if (header_row->paragraph_offset) { try_inserting_table_header_paragraph(parser, parent_container, (unsigned char *)parent_string, header_row->paragraph_offset); diff --git a/src/blocks.c b/src/blocks.c index 63741594e..94045aba2 100644 --- a/src/blocks.c +++ b/src/blocks.c @@ -70,22 +70,6 @@ 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; @@ -145,7 +129,6 @@ 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; @@ -327,12 +310,6 @@ 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; @@ -405,15 +382,6 @@ 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 field. - add_open_block_counts(&tmp_parser, parser->root); - return 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) { @@ -432,14 +400,11 @@ 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; } @@ -1083,8 +1048,6 @@ static cmark_node *check_open_blocks(cmark_parser *parser, cmark_chunk *input, cmark_node *container = parser->root; cmark_node_type cont_type; - assert(check_open_block_counts(parser)); - while (S_last_child_is_open(container)) { container = container->last_child; cont_type = S_type(container); @@ -1230,9 +1193,8 @@ static void open_new_blocks(cmark_parser *parser, cmark_node **container, has_content = resolve_reference_link_definitions(parser, *container); if (has_content) { - 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)->type = (uint16_t)CMARK_NODE_HEADING; (*container)->as.heading.level = lev; (*container)->as.heading.setext = true; S_advance_offset(parser, input, input->len - 1 - parser->offset, false); @@ -1516,7 +1478,6 @@ 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) diff --git a/src/cmark-gfm.h b/src/cmark-gfm.h index 902c392de..6b362a865 100644 --- a/src/cmark-gfm.h +++ b/src/cmark-gfm.h @@ -37,16 +37,6 @@ 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, diff --git a/src/node.h b/src/node.h index ffebcb60e..6630afe40 100644 --- a/src/node.h +++ b/src/node.h @@ -50,13 +50,12 @@ typedef struct { enum cmark_node__internal_flags { CMARK_NODE__OPEN = (1 << 0), - CMARK_NODE__OPEN_BLOCK = (1 << 1), - CMARK_NODE__LAST_LINE_BLANK = (1 << 2), - CMARK_NODE__LAST_LINE_CHECKED = (1 << 3), + CMARK_NODE__LAST_LINE_BLANK = (1 << 1), + CMARK_NODE__LAST_LINE_CHECKED = (1 << 2), // Extensions can register custom flags by calling `cmark_register_node_flag`. // This is the starting value for the custom flags. - CMARK_NODE__REGISTER_FIRST = (1 << 4), + CMARK_NODE__REGISTER_FIRST = (1 << 3), }; typedef uint16_t cmark_node_internal_flags; diff --git a/src/parser.h b/src/parser.h index 5020aa20b..436c53f5b 100644 --- a/src/parser.h +++ b/src/parser.h @@ -50,37 +50,8 @@ struct cmark_parser { cmark_llist *syntax_extensions; cmark_llist *inline_syntax_extensions; cmark_ispunct_func backslash_ispunct; - - /** - * The "open" blocks are the blocks visited by the loop in - * check_open_blocks (blocks.c). I.e. the blocks in this list: - * - * parser->root->last_child->...->last_child - * - * open_block_counts is used to keep track of how many of each type of - * node are currently in the open blocks list. Knowing these counts can - * sometimes help to end the loop in check_open_blocks early, improving - * efficiency. - * - * The count is stored at this offset: type - CMARK_NODE_TYPE_BLOCK - 1 - * For example, CMARK_NODE_LIST (0x8003) is stored at offset 2. - */ - size_t open_block_counts[CMARK_NODE_TYPE_BLOCK_LIMIT]; }; -static CMARK_INLINE void incr_open_block_count(cmark_parser *parser, cmark_node_type type) { - assert(type > CMARK_NODE_TYPE_BLOCK); - assert(type <= CMARK_NODE_TYPE_BLOCK + CMARK_NODE_TYPE_BLOCK_LIMIT); - parser->open_block_counts[type - CMARK_NODE_TYPE_BLOCK - 1]++; -} - -static CMARK_INLINE void decr_open_block_count(cmark_parser *parser, cmark_node_type type) { - assert(type > CMARK_NODE_TYPE_BLOCK); - assert(type <= CMARK_NODE_TYPE_BLOCK + CMARK_NODE_TYPE_BLOCK_LIMIT); - assert(parser->open_block_counts[type - CMARK_NODE_TYPE_BLOCK - 1] > 0); - parser->open_block_counts[type - CMARK_NODE_TYPE_BLOCK - 1]--; -} - #ifdef __cplusplus } #endif diff --git a/src/syntax_extension.c b/src/syntax_extension.c index a2fb3b04d..d24fe43e6 100644 --- a/src/syntax_extension.c +++ b/src/syntax_extension.c @@ -29,10 +29,7 @@ cmark_syntax_extension *cmark_syntax_extension_new(const char *name) { cmark_node_type cmark_syntax_extension_add_node(int is_inline) { cmark_node_type *ref = !is_inline ? &CMARK_NODE_LAST_BLOCK : &CMARK_NODE_LAST_INLINE; - if ((*ref & CMARK_NODE_VALUE_MASK) >= CMARK_NODE_TYPE_BLOCK_LIMIT) { - // This assertion will fail if you try to register more extensions than - // are currently allowed by CMARK_NODE_TYPE_BLOCK_MAXNUM. Try increasing - // the limit. + if ((*ref & CMARK_NODE_VALUE_MASK) == CMARK_NODE_VALUE_MASK) { assert(false); return (cmark_node_type) 0; } From 9f8945c965d9b4edfba661793f9e59217029fc7d Mon Sep 17 00:00:00 2001 From: Bas Alberts Date: Mon, 3 Apr 2023 12:29:46 -0400 Subject: [PATCH 41/47] Bump version and start Changelog for 0.29.0.gfm.11 --- CMakeLists.txt | 2 +- changelog.txt | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 76b291142..eb1478804 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ project(cmark-gfm) set(PROJECT_VERSION_MAJOR 0) set(PROJECT_VERSION_MINOR 29) set(PROJECT_VERSION_PATCH 0) -set(PROJECT_VERSION_GFM 10) +set(PROJECT_VERSION_GFM 11) set(PROJECT_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}.gfm.${PROJECT_VERSION_GFM}) include("FindAsan.cmake") diff --git a/changelog.txt b/changelog.txt index 9601ab4ee..3d6956bf5 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,19 @@ +[0.29.0.gfm.11] + + * Improved fixes for polynomial time complexity issues per + https://github.com/github/cmark-gfm/security/advisories/GHSA-66g8-4hjf-77xh + (#323, #324) + * Added fuzzing target for bracketed patterns (#318) + * Fixed bug in list numbering introduced in + 763587e8775350b8cb4a2aa0f4cec3685aa96e8b (#322) which caused list numbers + to increment by 2 + * Fixed strict prototype clang warning (#310) + * Fixed regression test (#312) + + Note: these changes remove redundant bold tag nesting which may result + in existing rendering tests failing, e.g. rendering "____bold____" to html + will no longer yield "

bold

". + [0.29.0.gfm.10] * Fixed polynomial time complexity issue per From 889867be62d5beafbd4f72b1ed6453ab6c8e587f Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Mon, 3 Apr 2023 13:14:54 -0400 Subject: [PATCH 42/47] Update changelog.txt Co-authored-by: Bas Alberts <13686387+anticomputer@users.noreply.github.com> --- changelog.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/changelog.txt b/changelog.txt index 3d6956bf5..e8e74ae0f 100644 --- a/changelog.txt +++ b/changelog.txt @@ -10,9 +10,6 @@ * Fixed strict prototype clang warning (#310) * Fixed regression test (#312) - Note: these changes remove redundant bold tag nesting which may result - in existing rendering tests failing, e.g. rendering "____bold____" to html - will no longer yield "

bold

". [0.29.0.gfm.10] From f6e3ee1754b2e4b8a6479532e1200b550501c617 Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Wed, 5 Apr 2023 20:28:05 +0100 Subject: [PATCH 43/47] Don't copy if endlen is too big. --- fuzz/fuzz_quadratic_brackets.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/fuzz/fuzz_quadratic_brackets.c b/fuzz/fuzz_quadratic_brackets.c index d1719e19e..5c1646464 100644 --- a/fuzz/fuzz_quadratic_brackets.c +++ b/fuzz/fuzz_quadratic_brackets.c @@ -75,9 +75,11 @@ int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { } offset += fuzz_config.closelen; } - memcpy(&markdown[markdown_size], &markdown0[offset], - endlen); - markdown_size += endlen; + if (markdown_size + endlen <= sizeof(markdown)) { + memcpy(&markdown[markdown_size], &markdown0[offset], + endlen); + markdown_size += endlen; + } } else { markdown_size = markdown_size0; memcpy(markdown, markdown0, markdown_size); From 9d9f6307057ac59b15888b358445bed12f16ad34 Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Thu, 6 Apr 2023 12:21:32 +0100 Subject: [PATCH 44/47] Add other output formats to the quadratic fuzzer. --- fuzz/fuzz_quadratic.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/fuzz/fuzz_quadratic.c b/fuzz/fuzz_quadratic.c index cab1b6330..523c0f444 100644 --- a/fuzz/fuzz_quadratic.c +++ b/fuzz/fuzz_quadratic.c @@ -75,8 +75,13 @@ int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { cmark_parser_feed(parser, markdown, markdown_size); cmark_node *doc = cmark_parser_finish(parser); - + free(cmark_render_html(doc, fuzz_config.options, NULL)); + free(cmark_render_xml(doc, fuzz_config.options)); + free(cmark_render_man(doc, fuzz_config.options, 80)); + free(cmark_render_commonmark(doc, fuzz_config.options, 80)); + free(cmark_render_plaintext(doc, fuzz_config.options, 80)); + free(cmark_render_latex(doc, fuzz_config.options, 80)); cmark_node_free(doc); cmark_parser_free(parser); From 5ddc775d5a88faa3887d539bffff8f608caf9305 Mon Sep 17 00:00:00 2001 From: Bas Alberts <13686387+anticomputer@users.noreply.github.com> Date: Thu, 6 Apr 2023 12:08:23 -0400 Subject: [PATCH 45/47] Update changelog.txt --- changelog.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index e8e74ae0f..93f854e14 100644 --- a/changelog.txt +++ b/changelog.txt @@ -9,7 +9,8 @@ to increment by 2 * Fixed strict prototype clang warning (#310) * Fixed regression test (#312) - + * Added additional output formats to quadratic fuzzer (#327) + * Fixed buffer overflow in fuzzing harness (#326) [0.29.0.gfm.10] From fe3e84688fa22526144b2762de0a6cedd174c63a Mon Sep 17 00:00:00 2001 From: Bas Alberts <13686387+anticomputer@users.noreply.github.com> Date: Thu, 6 Apr 2023 12:41:44 -0400 Subject: [PATCH 46/47] Update changelog.txt --- changelog.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/changelog.txt b/changelog.txt index 93f854e14..848e71029 100644 --- a/changelog.txt +++ b/changelog.txt @@ -12,6 +12,11 @@ * Added additional output formats to quadratic fuzzer (#327) * Fixed buffer overflow in fuzzing harness (#326) + Note: these changes may lead to minor changes in expected output on plaintext + rendering of list items. Notably, blank lines may no longer delineate the start + of a list when rendering to plaintext due to changes in how the tight list status + is calculated. + [0.29.0.gfm.10] * Fixed polynomial time complexity issue per From af81c603c69b0b8d573c1377037943b61c1a5778 Mon Sep 17 00:00:00 2001 From: Victoria Mitchell Date: Thu, 6 Apr 2023 13:38:12 -0600 Subject: [PATCH 47/47] guard global safety check setting behind a lock --- src/node.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/node.c b/src/node.c index 9eb89466e..d93ed307d 100644 --- a/src/node.c +++ b/src/node.c @@ -7,6 +7,7 @@ #include "syntax_extension.h" CMARK_DEFINE_LOCK(nextflag) +CMARK_DEFINE_LOCK(safety) /** * Expensive safety checks are off by default, but can be enabled @@ -15,7 +16,20 @@ CMARK_DEFINE_LOCK(nextflag) static bool enable_safety_checks = false; void cmark_enable_safety_checks(bool enable) { + CMARK_INITIALIZE_AND_LOCK(safety); enable_safety_checks = enable; + CMARK_UNLOCK(safety); +} + +/** + * Check whether safety checks have been enabled in a function to guard access + * behind a lock. + */ +static bool S_safety_checks_enabled() { + CMARK_INITIALIZE_AND_LOCK(safety); + bool safety_enabled = enable_safety_checks; + CMARK_UNLOCK(safety); + return safety_enabled; } static void S_node_unlink(cmark_node *node); @@ -95,7 +109,7 @@ static bool S_can_contain(cmark_node *node, cmark_node *child) { return 0; } - if (enable_safety_checks) { + if (S_safety_checks_enabled()) { // Verify that child is not an ancestor of node or equal to node. cmark_node *cur = node; do {