diff --git a/org/html_writer.go b/org/html_writer.go index ad1bedd..6355fd1 100644 --- a/org/html_writer.go +++ b/org/html_writer.go @@ -19,7 +19,7 @@ type HTMLWriter struct { HighlightCodeBlock func(source, lang string) string htmlEscape bool log *log.Logger - footnotes footnotes + footnotes *footnotes } type footnotes struct { @@ -61,7 +61,7 @@ func NewHTMLWriter() *HTMLWriter { HighlightCodeBlock: func(source, lang string) string { return fmt.Sprintf("
\n
\n%s\n
\n
", html.EscapeString(source)) }, - footnotes: footnotes{ + footnotes: &footnotes{ mapping: map[string]int{}, }, } diff --git a/org/testdata/footnotes_in_headline.html b/org/testdata/footnotes_in_headline.html new file mode 100644 index 0000000..489169a --- /dev/null +++ b/org/testdata/footnotes_in_headline.html @@ -0,0 +1,47 @@ + +

+Title 1 +

+
+
+
+
+1 +
+

+this test file just exists to reproduce a bug with footnotes in headlines - that only happens in very specific circumstances. +The TLDR is: +

+
    +
  • +

    +HTMLWriter.footnotes should be a pointer field. I didn't notice my error as go translated my pointer-method calls on +non-pointer values rather than complaining - i.e. footnotes.add() transparently gets translated to (&footnotes).add() (docs). +

    +
  • +
  • +

    +Headlines have to be htmlified twice - once for the outline and once for the headline itself. To do so we have to copy the writer +

    +
  • +
  • +

    +Copying the writer copies footnotes - which contains a map and a slice. Changes to the map will always be reflected in the original map. +Changes to the slice will only be reflected if the slice doesn't grow. +

    +
  • +
  • +

    +We can thus end up with a footnote being in the mapping but not the slice - and get an index out of range error. +

    +
  • +
+
+
+
+
diff --git a/org/testdata/footnotes_in_headline.org b/org/testdata/footnotes_in_headline.org new file mode 100644 index 0000000..3d9886c --- /dev/null +++ b/org/testdata/footnotes_in_headline.org @@ -0,0 +1,10 @@ +* Title [fn:1] + +[fn:1] this test file just exists to reproduce a bug with footnotes in headlines - that only happens in very specific circumstances. +The TLDR is: +- HTMLWriter.footnotes should be a pointer field. I didn't notice my error as go translated my pointer-method calls on + non-pointer values rather than complaining - i.e. =footnotes.add()= transparently gets translated to =(&footnotes).add()= ([[https://golang.org/ref/spec#Calls][docs]]). +- Headlines have to be htmlified twice - once for the outline and once for the headline itself. To do so we have to copy the writer +- Copying the writer copies footnotes - which contains a map and a slice. Changes to the map will always be reflected in the original map. + Changes to the slice will only be reflected if the slice doesn't grow. +- We can thus end up with a footnote being in the mapping but not the slice - and get an index out of range error. diff --git a/org/testdata/footnotes_in_headline.pretty_org b/org/testdata/footnotes_in_headline.pretty_org new file mode 100644 index 0000000..3d9886c --- /dev/null +++ b/org/testdata/footnotes_in_headline.pretty_org @@ -0,0 +1,10 @@ +* Title [fn:1] + +[fn:1] this test file just exists to reproduce a bug with footnotes in headlines - that only happens in very specific circumstances. +The TLDR is: +- HTMLWriter.footnotes should be a pointer field. I didn't notice my error as go translated my pointer-method calls on + non-pointer values rather than complaining - i.e. =footnotes.add()= transparently gets translated to =(&footnotes).add()= ([[https://golang.org/ref/spec#Calls][docs]]). +- Headlines have to be htmlified twice - once for the outline and once for the headline itself. To do so we have to copy the writer +- Copying the writer copies footnotes - which contains a map and a slice. Changes to the map will always be reflected in the original map. + Changes to the slice will only be reflected if the slice doesn't grow. +- We can thus end up with a footnote being in the mapping but not the slice - and get an index out of range error.