Repository: pulldown-cmark/pulldown-cmark (rust)

Issue:
Panic in definition list support
When I upgraded pulldown-cmark to 0.12.1 in my little frontend and tried to enable definition list support, I got a panic at `src/firstpass.rs:1507:38` from the markdown file that acts as a mix of syntax reference and test suite.

I narrowed it down to this chunk of [Prettier](https://prettier.io/)-autoformatted text:

```markdown
**Example:**

> The Konami Code originally appeared in
> [Gradius (1986)](https://www.mobygames.com/game/4512/gradius/) on the NES,
> where it took the form
> <span style="white-space: nowrap">:dpad-up::dpad-up::dpad-down::dpad-down::dpad-left::dpad-right::dpad-left::dpad-right::btn-b-nes::btn-a-nes:.</span>
> Rather than being part of the code, the addition of :btn-start-nes: and/or
> :btn-select-nes: is actually part of selecting the number of players and
> starting a new game _after_ the code has been entered. (Naturally, that makes
> easter eggs difficult on Sony consoles, which have
> :btn-triangle:,:btn-circle:,:btn-cross:, and :btn-square: but no :btn-a: or
> :btn-b:.)
```

...then to these two lines:

```markdown
> Rather than being part of the code, the addition of :btn-start-nes: and/or
> :btn-select-nes: is actually part of selecting the number of players and
```

...and finally to to this test case:

```rust
#[test]
fn pulldown_cmark_0_12_1_deflist_panic() {
    let markdown = "> foo\n> :word-wrapped-shortcode:";
    let mut options = pulldown_cmark::Options::empty();
    options.insert(pulldown_cmark::Options::ENABLE_DEFINITION_LIST);

    Parser::new_ext(markdown, options);
}
```

Here's the traceback:

```
---- markdown::test::pulldown_cmark_0_12_1_deflist_panic stdout ----
thread 'markdown::test::pulldown_cmark_0_12_1_deflist_panic' panicked at /home/ssokolow/.cargo/registry/src/index.crates.io-6f17d22bba15001f/pulldown-cmark-0.12.1/src/firstpass.rs:1507:38:
called `Option::unwrap()` on a `None` value
stack backtrace:
   0: rust_begin_unwind
             at /rustc/3f5fd8dd41153bc5fdca9427e9e05be2c767ba23/library/std/src/panicking.rs:652:5
   1: core::panicking::panic_fmt
             at /rustc/3f5fd8dd41153bc5fdca9427e9e05be2c767ba23/library/core/src/panicking.rs:72:14
   2: core::panicking::panic
             at /rustc/3f5fd8dd41153bc5fdca9427e9e05be2c767ba23/library/core/src/panicking.rs:146:5
   3: core::option::unwrap_failed
             at /rustc/3f5fd8dd41153bc5fdca9427e9e05be2c767ba23/library/core/src/option.rs:1985:5
   4: core::option::Option<T>::unwrap
             at /rustc/3f5fd8dd41153bc5fdca9427e9e05be2c767ba23/library/core/src/option.rs:935:21
   5: pulldown_cmark::firstpass::FirstPass::pop
             at /home/ssokolow/.cargo/registry/src/index.crates.io-6f17d22bba15001f/pulldown-cmark-0.12.1/src/firstpass.rs:1507:38
   6: pulldown_cmark::firstpass::FirstPass::run
             at /home/ssokolow/.cargo/registry/src/index.crates.io-6f17d22bba15001f/pulldown-cmark-0.12.1/src/firstpass.rs:79:13
   7: pulldown_cmark::firstpass::run_first_pass
             at /home/ssokolow/.cargo/registry/src/index.crates.io-6f17d22bba15001f/pulldown-cmark-0.12.1/src/firstpass.rs:41:5
   8: pulldown_cmark::parse::Parser<F>::new_with_broken_link_callback
             at /home/ssokolow/.cargo/registry/src/index.crates.io-6f17d22bba15001f/pulldown-cmark-0.12.1/src/parse.rs:247:34
   9: pulldown_cmark::parse::Parser::new_ext
             at /home/ssokolow/.cargo/registry/src/index.crates.io-6f17d22bba15001f/pulldown-cmark-0.12.1/src/parse.rs:232:9
  10: render_wishlist::markdown::test::pulldown_cmark_0_12_1_deflist_panic
             at ./src/markdown.rs:563:9
  11: render_wishlist::markdown::test::pulldown_cmark_0_12_1_deflist_panic::{{closure}}
             at ./src/markdown.rs:558:45
  12: core::ops::function::FnOnce::call_once
             at /rustc/3f5fd8dd41153bc5fdca9427e9e05be2c767ba23/library/core/src/ops/function.rs:250:5
  13: core::ops::function::FnOnce::call_once
             at /rustc/3f5fd8dd41153bc5fdca9427e9e05be2c767ba23/library/core/src/ops/function.rs:250:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
```

(Yeah. `render_wishlist` is the original purpose of the project and I haven't renamed it for its broader use yet.)

Basically, it looks like there's some kind of "Once you start something that might be definition list markup, you'd better jolly well finish it as valid definition list markup or we're gonna crash" bug... but, funny enough, so far, I've only managed to get it to trigger when the potential DL markup is inside a blockquote.

Pull request:
Account for definition list fixups while popping containers
Fixes #949


Patch:
diff --git a/pulldown-cmark/specs/regression.txt b/pulldown-cmark/specs/regression.txt
index 7b6cb7f..cbfde9c 100644
--- a/pulldown-cmark/specs/regression.txt
+++ b/pulldown-cmark/specs/regression.txt
@@ -2644,3 +2644,54 @@ ISSUE #655
 .
 <p><code> </code></p>
 ````````````````````````````````
+
+ISSUE #949
+
+```````````````````````````````` example
+* def this
+  : def text def text
+.
+<ul>
+<li>
+<dl>
+<dt>def this</dt>
+<dd>def text def text</dd>
+</dl>
+</li>
+</ul>
+````````````````````````````````
+
+```````````````````````````````` example
+* def this
+
+  : def text def text
+.
+<ul>
+<li>
+<dl>
+<dt>def this</dt>
+<dd><p>def text def text</p></dd>
+</dl>
+</li>
+</ul>
+````````````````````````````````
+
+```````````````````````````````` example
+**A:**
+
+> B C
+> I J :x: K
+> :x: L M
+> N O _P_ Q R. (S
+> T U, V W
+> :x:,:x:,:x:, and :x: but no :x: or
+> :x:.)
+.
+<p><strong>A:</strong></p><blockquote>
+<dl><dt>B C
+I J :x: K</dt><dd>x: L M
+N O <em>P</em> Q R. (S
+T U, V W</dd>
+<dd>x:,:x:,:x:, and :x: but no :x: or</dd>
+<dd>x:.)</dd></dl></blockquote>
+````````````````````````````````
diff --git a/pulldown-cmark/src/firstpass.rs b/pulldown-cmark/src/firstpass.rs
index cd4fbf8..909c812 100644
--- a/pulldown-cmark/src/firstpass.rs
+++ b/pulldown-cmark/src/firstpass.rs
@@ -75,7 +75,7 @@ impl<'a, 'b> FirstPass<'a, 'b> {
         while ix < self.text.len() {
             ix = self.parse_block(ix);
         }
-        for _ in 0..self.tree.spine_len() {
+        while self.tree.spine_len() > 0 {
             self.pop(ix);
         }
         (self.tree, self.allocs)


Test patch:
diff --git a/pulldown-cmark/tests/suite/regression.rs b/pulldown-cmark/tests/suite/regression.rs
index 14f2833..d58030d 100644
--- a/pulldown-cmark/tests/suite/regression.rs
+++ b/pulldown-cmark/tests/suite/regression.rs
@@ -3153,3 +3153,64 @@ fn regression_test_200() {
 
     test_markdown_html(original, expected, false, false, false);
 }
+
+#[test]
+fn regression_test_201() {
+    let original = r##"* def this
+  : def text def text
+"##;
+    let expected = r##"<ul>
+<li>
+<dl>
+<dt>def this</dt>
+<dd>def text def text</dd>
+</dl>
+</li>
+</ul>
+"##;
+
+    test_markdown_html(original, expected, false, false, false);
+}
+
+#[test]
+fn regression_test_202() {
+    let original = r##"* def this
+
+  : def text def text
+"##;
+    let expected = r##"<ul>
+<li>
+<dl>
+<dt>def this</dt>
+<dd><p>def text def text</p></dd>
+</dl>
+</li>
+</ul>
+"##;
+
+    test_markdown_html(original, expected, false, false, false);
+}
+
+#[test]
+fn regression_test_203() {
+    let original = r##"**A:**
+
+> B C
+> I J :x: K
+> :x: L M
+> N O _P_ Q R. (S
+> T U, V W
+> :x:,:x:,:x:, and :x: but no :x: or
+> :x:.)
+"##;
+    let expected = r##"<p><strong>A:</strong></p><blockquote>
+<dl><dt>B C
+I J :x: K</dt><dd>x: L M
+N O <em>P</em> Q R. (S
+T U, V W</dd>
+<dd>x:,:x:,:x:, and :x: but no :x: or</dd>
+<dd>x:.)</dd></dl></blockquote>
+"##;
+
+    test_markdown_html(original, expected, false, false, false);
+}