From d80fe0965164c00a36e2755f811f173a31d59891 Mon Sep 17 00:00:00 2001
From: Liam
Date: Sun, 3 Nov 2024 09:43:38 -0500
Subject: [PATCH] Unwrap Mutex from broken_link_callback
---
fuzz/fuzz_targets/all_options.rs | 4 +-
src/cm.rs | 10 ++--
src/html.rs | 12 ++---
src/lib.rs | 4 +-
src/parser/inlines.rs | 10 ++--
src/parser/mod.rs | 78 ++++++++++++++++----------------
src/parser/table.rs | 8 ++--
src/tests/api.rs | 28 ++++++------
src/tests/options.rs | 22 +++------
src/xml.rs | 8 ++--
10 files changed, 86 insertions(+), 98 deletions(-)
diff --git a/fuzz/fuzz_targets/all_options.rs b/fuzz/fuzz_targets/all_options.rs
index 2af39c81..0684a197 100644
--- a/fuzz/fuzz_targets/all_options.rs
+++ b/fuzz/fuzz_targets/all_options.rs
@@ -35,13 +35,13 @@ fuzz_target!(|s: &str| {
parse.default_info_string = Some("rust".to_string());
parse.relaxed_tasklist_matching = true;
parse.relaxed_autolinks = true;
- let mut cb = |link_ref: BrokenLinkReference| {
+ let cb = |link_ref: BrokenLinkReference| {
Some(ResolvedReference {
url: link_ref.normalized.to_string(),
title: link_ref.original.to_string(),
})
};
- parse.broken_link_callback = Some(Arc::new(Mutex::new(&mut cb)));
+ parse.broken_link_callback = Some(Arc::new(cb));
let mut render = RenderOptions::default();
render.hardbreaks = true;
diff --git a/src/cm.rs b/src/cm.rs
index 9d46d387..10cba543 100644
--- a/src/cm.rs
+++ b/src/cm.rs
@@ -47,9 +47,9 @@ pub fn format_document_with_plugins<'a>(
Ok(())
}
-struct CommonMarkFormatter<'a, 'o, 'c> {
+struct CommonMarkFormatter<'a, 'o> {
node: &'a AstNode<'a>,
- options: &'o Options<'c>,
+ options: &'o Options,
v: Vec,
prefix: Vec,
column: usize,
@@ -72,7 +72,7 @@ enum Escaping {
Title,
}
-impl<'a, 'o, 'c> Write for CommonMarkFormatter<'a, 'o, 'c> {
+impl<'a, 'o> Write for CommonMarkFormatter<'a, 'o> {
fn write(&mut self, buf: &[u8]) -> std::io::Result {
self.output(buf, false, Escaping::Literal);
Ok(buf.len())
@@ -83,8 +83,8 @@ impl<'a, 'o, 'c> Write for CommonMarkFormatter<'a, 'o, 'c> {
}
}
-impl<'a, 'o, 'c> CommonMarkFormatter<'a, 'o, 'c> {
- fn new(node: &'a AstNode<'a>, options: &'o Options<'c>) -> Self {
+impl<'a, 'o> CommonMarkFormatter<'a, 'o> {
+ fn new(node: &'a AstNode<'a>, options: &'o Options) -> Self {
CommonMarkFormatter {
node,
options,
diff --git a/src/html.rs b/src/html.rs
index b7c1edf5..c82abe6f 100644
--- a/src/html.rs
+++ b/src/html.rs
@@ -126,9 +126,9 @@ impl Anchorizer {
}
}
-struct HtmlFormatter<'o, 'c> {
+struct HtmlFormatter<'o> {
output: &'o mut WriteWithLast<'o>,
- options: &'o Options<'c>,
+ options: &'o Options,
anchorizer: Anchorizer,
footnote_ix: u32,
written_footnote_ix: u32,
@@ -361,12 +361,8 @@ where
Ok(())
}
-impl<'o, 'c: 'o> HtmlFormatter<'o, 'c> {
- fn new(
- options: &'o Options<'c>,
- output: &'o mut WriteWithLast<'o>,
- plugins: &'o Plugins,
- ) -> Self {
+impl<'o> HtmlFormatter<'o> {
+ fn new(options: &'o Options, output: &'o mut WriteWithLast<'o>, plugins: &'o Plugins) -> Self {
HtmlFormatter {
options,
output,
diff --git a/src/lib.rs b/src/lib.rs
index 16f0abe6..3abdb656 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -101,9 +101,9 @@ pub use xml::format_document_with_plugins as format_xml_with_plugins;
/// Legacy naming of [`ExtensionOptions`]
pub type ComrakExtensionOptions = ExtensionOptions;
/// Legacy naming of [`Options`]
-pub type ComrakOptions<'c> = Options<'c>;
+pub type ComrakOptions = Options;
/// Legacy naming of [`ParseOptions`]
-pub type ComrakParseOptions<'c> = ParseOptions<'c>;
+pub type ComrakParseOptions = ParseOptions;
/// Legacy naming of [`Plugins`]
pub type ComrakPlugins<'a> = Plugins<'a>;
/// Legacy naming of [`RenderOptions`]
diff --git a/src/parser/inlines.rs b/src/parser/inlines.rs
index acea0ea0..5601cfb7 100644
--- a/src/parser/inlines.rs
+++ b/src/parser/inlines.rs
@@ -25,9 +25,9 @@ const MAXBACKTICKS: usize = 80;
const MAX_LINK_LABEL_LENGTH: usize = 1000;
const MAX_MATH_DOLLARS: usize = 2;
-pub struct Subject<'a: 'd, 'r, 'o, 'c, 'd, 'i> {
+pub struct Subject<'a: 'd, 'r, 'o, 'd, 'i> {
pub arena: &'a Arena>,
- options: &'o Options<'c>,
+ options: &'o Options,
pub input: &'i [u8],
line: usize,
pub pos: usize,
@@ -110,10 +110,10 @@ struct WikilinkComponents<'i> {
link_label: Option<(&'i [u8], usize, usize)>,
}
-impl<'a, 'r, 'o, 'c, 'd, 'i> Subject<'a, 'r, 'o, 'c, 'd, 'i> {
+impl<'a, 'r, 'o, 'd, 'i> Subject<'a, 'r, 'o, 'd, 'i> {
pub fn new(
arena: &'a Arena>,
- options: &'o Options<'c>,
+ options: &'o Options,
input: &'i [u8],
line: usize,
refmap: &'r mut RefMap,
@@ -1548,7 +1548,7 @@ impl<'a, 'r, 'o, 'c, 'd, 'i> Subject<'a, 'r, 'o, 'c, 'd, 'i> {
// Attempt to use the provided broken link callback if a reference cannot be resolved
if reff.is_none() {
if let Some(callback) = &self.options.parse.broken_link_callback {
- reff = callback.lock().unwrap()(BrokenLinkReference {
+ reff = callback.resolve(BrokenLinkReference {
normalized: &lab,
original: &unfolded_lab,
});
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 280e10df..5b13fc42 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -26,7 +26,7 @@ use std::fmt::{self, Debug, Formatter};
use std::mem;
use std::panic::RefUnwindSafe;
use std::str;
-use std::sync::{Arc, Mutex};
+use std::sync::Arc;
use typed_arena::Arena;
use crate::adapters::HeadingAdapter;
@@ -80,16 +80,16 @@ pub fn parse_document<'a>(
/// [`ParseOptions::broken_link_callback`].
#[deprecated(
since = "0.25.0",
- note = "The broken link callback has been moved into ParseOptions<'c>."
+ note = "The broken link callback has been moved into ParseOptions."
)]
-pub fn parse_document_with_broken_link_callback<'a, 'c>(
+pub fn parse_document_with_broken_link_callback<'a>(
arena: &'a Arena>,
buffer: &str,
- options: &Options<'c>,
- callback: Option>,
+ options: &Options,
+ callback: Arc,
) -> &'a AstNode<'a> {
let mut options_with_callback = options.clone();
- options_with_callback.parse.broken_link_callback = callback.map(|cb| Arc::new(Mutex::new(cb)));
+ options_with_callback.parse.broken_link_callback = Some(callback);
parse_document(arena, buffer, &options_with_callback)
}
@@ -100,8 +100,26 @@ pub fn parse_document_with_broken_link_callback<'a, 'c>(
/// [`BrokenLinkReference`] argument. If a [`ResolvedReference`] is returned, it
/// is used as the link; otherwise, no link is made and the reference text is
/// preserved in its entirety.
-pub type BrokenLinkCallback<'c> =
- &'c mut dyn FnMut(BrokenLinkReference) -> Option;
+pub trait BrokenLinkCallback: RefUnwindSafe + Send + Sync {
+ /// Potentially resolve a single broken link reference.
+ fn resolve(&self, broken_link_reference: BrokenLinkReference) -> Option;
+}
+
+impl Debug for dyn BrokenLinkCallback {
+ fn fmt(&self, formatter: &mut Formatter<'_>) -> Result<(), fmt::Error> {
+ formatter.write_str("")
+ }
+}
+
+impl BrokenLinkCallback for F
+where
+ F: Fn(BrokenLinkReference) -> Option,
+ F: RefUnwindSafe + Send + Sync,
+{
+ fn resolve(&self, broken_link_reference: BrokenLinkReference) -> Option {
+ self(broken_link_reference)
+ }
+}
/// Struct to the broken link callback, containing details on the link reference
/// which failed to find a match.
@@ -116,7 +134,7 @@ pub struct BrokenLinkReference<'l> {
pub original: &'l str,
}
-pub struct Parser<'a, 'o, 'c> {
+pub struct Parser<'a, 'o> {
arena: &'a Arena>,
refmap: RefMap,
root: &'a AstNode<'a>,
@@ -135,19 +153,18 @@ pub struct Parser<'a, 'o, 'c> {
last_line_length: usize,
last_buffer_ended_with_cr: bool,
total_size: usize,
- options: &'o Options<'c>,
+ options: &'o Options,
}
#[derive(Default, Debug, Clone)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
-/// Umbrella options struct. `'c` represents the lifetime of any callback
-/// closure options may take.
-pub struct Options<'c> {
+/// Umbrella options struct.
+pub struct Options {
/// Enable CommonMark extensions.
pub extension: ExtensionOptions,
/// Configure parse-time options.
- pub parse: ParseOptions<'c>,
+ pub parse: ParseOptions,
/// Configure render-time options.
pub render: RenderOptions,
@@ -581,10 +598,10 @@ pub struct ExtensionOptions {
}
#[non_exhaustive]
-#[derive(Default, Clone, Builder)]
+#[derive(Default, Clone, Debug, Builder)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
/// Options for parser functions.
-pub struct ParseOptions<'c> {
+pub struct ParseOptions {
/// Punctuation (quotes, full-stops and hyphens) are converted into 'smart' punctuation.
///
/// ```
@@ -643,18 +660,18 @@ pub struct ParseOptions<'c> {
/// used as the link destination and title if not [`None`].
///
/// ```
- /// # use std::{str, sync::{Arc, Mutex}};
+ /// # use std::{str, sync::Arc};
/// # use comrak::{markdown_to_html, BrokenLinkReference, Options, ResolvedReference};
- /// let mut cb = |link_ref: BrokenLinkReference| match link_ref.normalized {
+ /// let cb = |link_ref: BrokenLinkReference| match link_ref.normalized {
/// "foo" => Some(ResolvedReference {
/// url: "https://www.rust-lang.org/".to_string(),
/// title: "The Rust Language".to_string(),
/// }),
/// _ => None,
/// };
- ///
+ ///
/// let mut options = Options::default();
- /// options.parse.broken_link_callback = Some(Arc::new(Mutex::new(&mut cb)));
+ /// options.parse.broken_link_callback = Some(Arc::new(cb));
///
/// let output = markdown_to_html(
/// "# Cool input!\nWow look at this cool [link][foo]. A [broken link] renders as text.",
@@ -666,22 +683,7 @@ pub struct ParseOptions<'c> {
/// link. \
/// A [broken link] renders as text.
\n");
#[cfg_attr(feature = "arbitrary", arbitrary(default))]
- pub broken_link_callback: Option>>>,
-}
-
-impl<'c> fmt::Debug for ParseOptions<'c> {
- fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
- let mut struct_fmt = f.debug_struct("ParseOptions");
- struct_fmt.field("smart", &self.smart);
- struct_fmt.field("default_info_string", &self.default_info_string);
- struct_fmt.field("relaxed_tasklist_matching", &self.relaxed_tasklist_matching);
- struct_fmt.field("relaxed_autolinks", &self.relaxed_autolinks);
- struct_fmt.field(
- "broken_link_callback.is_some()",
- &self.broken_link_callback.is_some(),
- );
- struct_fmt.finish()
- }
+ pub broken_link_callback: Option>,
}
#[non_exhaustive]
@@ -1088,8 +1090,8 @@ struct FootnoteDefinition<'a> {
total_references: u32,
}
-impl<'a, 'o, 'c: 'o> Parser<'a, 'o, 'c> {
- fn new(arena: &'a Arena>, root: &'a AstNode<'a>, options: &'o Options<'c>) -> Self {
+impl<'a, 'o> Parser<'a, 'o> {
+ fn new(arena: &'a Arena>, root: &'a AstNode<'a>, options: &'o Options) -> Self {
Parser {
arena,
refmap: RefMap::new(),
diff --git a/src/parser/table.rs b/src/parser/table.rs
index 4d6f8610..50127085 100644
--- a/src/parser/table.rs
+++ b/src/parser/table.rs
@@ -13,7 +13,7 @@ use super::inlines::count_newlines;
const MAX_AUTOCOMPLETED_CELLS: usize = 500_000;
pub fn try_opening_block<'a>(
- parser: &mut Parser<'a, '_, '_>,
+ parser: &mut Parser<'a, '_>,
container: &'a AstNode<'a>,
line: &[u8],
) -> Option<(&'a AstNode<'a>, bool, bool)> {
@@ -30,7 +30,7 @@ pub fn try_opening_block<'a>(
}
fn try_opening_header<'a>(
- parser: &mut Parser<'a, '_, '_>,
+ parser: &mut Parser<'a, '_>,
container: &'a AstNode<'a>,
line: &[u8],
) -> Option<(&'a AstNode<'a>, bool, bool)> {
@@ -133,7 +133,7 @@ fn try_opening_header<'a>(
}
fn try_opening_row<'a>(
- parser: &mut Parser<'a, '_, '_>,
+ parser: &mut Parser<'a, '_>,
container: &'a AstNode<'a>,
alignments: &[TableAlignment],
line: &[u8],
@@ -280,7 +280,7 @@ fn row(string: &[u8], spoiler: bool) -> Option {
}
fn try_inserting_table_header_paragraph<'a>(
- parser: &mut Parser<'a, '_, '_>,
+ parser: &mut Parser<'a, '_>,
container: &'a AstNode<'a>,
paragraph_offset: usize,
) {
diff --git a/src/tests/api.rs b/src/tests/api.rs
index b06e2a24..07808209 100644
--- a/src/tests/api.rs
+++ b/src/tests/api.rs
@@ -32,14 +32,15 @@ fn exercise_full_api() {
let _: &AstNode = parse_document(&arena, "document", &default_options);
// Ensure the closure can modify its context.
- let mut blr_ctx_0 = 0;
+ let blr_ctx_0 = Arc::new(Mutex::new(0));
+ let blr_ctx_1 = blr_ctx_0.clone();
#[allow(deprecated)]
let _: &AstNode = parse_document_with_broken_link_callback(
&arena,
"document",
&Options::default(),
- Some(&mut |blr: BrokenLinkReference| {
- blr_ctx_0 += 1;
+ Arc::new(move |blr: BrokenLinkReference| {
+ *blr_ctx_1.lock().unwrap() += 1;
let _: &str = blr.normalized;
let _: &str = blr.original;
Some(ResolvedReference {
@@ -80,17 +81,16 @@ fn exercise_full_api() {
.relaxed_tasklist_matching(false)
.relaxed_autolinks(false);
- let mut blr_ctx_1 = 0;
- let _parse =
- parse.broken_link_callback(Arc::new(Mutex::new(&mut |blr: BrokenLinkReference| {
- blr_ctx_1 += 1;
- let _: &str = blr.normalized;
- let _: &str = blr.original;
- Some(ResolvedReference {
- url: String::new(),
- title: String::new(),
- })
- })));
+ let blr_ctx_1 = blr_ctx_0.clone();
+ let _parse = parse.broken_link_callback(Arc::new(move |blr: BrokenLinkReference| {
+ *blr_ctx_1.lock().unwrap() += 1;
+ let _: &str = blr.normalized;
+ let _: &str = blr.original;
+ Some(ResolvedReference {
+ url: String::new(),
+ title: String::new(),
+ })
+ }));
let _render = RenderOptions::builder()
.hardbreaks(false)
diff --git a/src/tests/options.rs b/src/tests/options.rs
index 4f07bef2..d3a0ae33 100644
--- a/src/tests/options.rs
+++ b/src/tests/options.rs
@@ -1,4 +1,4 @@
-use std::sync::{Arc, Mutex};
+use std::sync::Arc;
use super::*;
@@ -67,32 +67,22 @@ fn smart_chars() {
#[test]
fn broken_link_callback() {
- let arena = Arena::new();
-
- let mut cb = |link_ref: BrokenLinkReference| match link_ref.normalized {
+ let cb = |link_ref: BrokenLinkReference| match link_ref.normalized {
"foo" => Some(ResolvedReference {
url: "https://www.rust-lang.org/".to_string(),
title: "The Rust Language".to_string(),
}),
_ => None,
};
- let options = Options {
- parse: ParseOptions::builder()
- .broken_link_callback(Arc::new(Mutex::new(&mut cb)))
- .build(),
- ..Default::default()
- };
+ let mut options = Options::default();
+ options.parse.broken_link_callback = Some(Arc::new(cb));
- let root = parse_document(
- &arena,
+ let output = markdown_to_html(
"# Cool input!\nWow look at this cool [link][foo]. A [broken link] renders as text.",
&options,
);
- let mut output = Vec::new();
- format_html(root, &Options::default(), &mut output).unwrap();
- let output_str = std::str::from_utf8(&output).unwrap();
assert_eq!(
- output_str,
+ output,
"Cool input!
\nWow look at this cool \
link. \
A [broken link] renders as text.
\n"
diff --git a/src/xml.rs b/src/xml.rs
index 1afa5cf8..19721d2e 100644
--- a/src/xml.rs
+++ b/src/xml.rs
@@ -30,15 +30,15 @@ pub fn format_document_with_plugins<'a>(
XmlFormatter::new(options, output, plugins).format(root, false)
}
-struct XmlFormatter<'o, 'c> {
+struct XmlFormatter<'o> {
output: &'o mut dyn Write,
- options: &'o Options<'c>,
+ options: &'o Options,
_plugins: &'o Plugins<'o>,
indent: u32,
}
-impl<'o, 'c> XmlFormatter<'o, 'c> {
- fn new(options: &'o Options<'c>, output: &'o mut dyn Write, plugins: &'o Plugins) -> Self {
+impl<'o> XmlFormatter<'o> {
+ fn new(options: &'o Options, output: &'o mut dyn Write, plugins: &'o Plugins) -> Self {
XmlFormatter {
options,
output,