Fix unguarded .content[1].t access in Lua filters#14587
Merged
Conversation
`divEl.content[1].t` crashes when a div with `latex` attribute has
no content blocks. Add `#divEl.content > 0` guard so empty divs fall
through to the RawBlock branch, producing valid \begin{env}\end{env}.
quarto.utils.match() not used here — the first-and-last element check
with content mutation doesn't map to its path-traversal pattern.
`#fig.content ~= 1 and fig.content[1].t ~= "Plain"` uses `and` but the intent is "bail unless content is exactly one Plain block." With `and`, empty content crashes (indexes nil) and single non-Plain doesn't bail. Change to `or` — short-circuits on empty, bails on non-Plain. Test uses a pre-ast Lua filter to strip Figure content, confirming the crash triggers at the normalize stage.
HTML path: replace manual `figure.content[1].t == "Plain"` then
`plain.content[1].t == "Link"` chain with single
`quarto.utils.match("[1]/Plain/[1]/Link")(figure)` call.
match() returns false on nil/empty content — no crash possible.
Typst path: add `#figure.content == 0` guard before accessing
figure.content[1] (match() doesn't fit here — the value is passed
to make_typst_figure, not used as a type check).
Test uses pre-ast Lua filter to empty non-crossref Figure content.
Replace manual `content[1].content[1].t == "Image"` with
`quarto.utils.match("[1]/Para/[1]/Image")(content)` which returns
false on nil/empty content — no crash when Para has no inlines.
Test uses pre-ast Lua filter to create a fig-div with an empty Para
and external fig-cap attribute, confirming the crash at line 309.
The Div handler in crossref_theorems() that rendered proof-type divs has been dead code since e644af3 (Dec 2023, "crossreferenceable remarks"). That commit introduced customnodes/proof.lua with a Proof custom node and renderer. The normalize stage (parseblockreftargets in astpipeline.lua) converts all proof-type divs to Proof custom nodes before crossref_theorems() runs — the Div handler never receives them. The theoremType branch already acknowledged this with internal_error(). Remove the entire Div handler; proof rendering is handled by proof.lua. New regression tests verify proof rendering (PDF and HTML) still works with content, named proofs, empty proofs, and remarks.
quarto.utils.match() returns false (not nil) on no-match. The == nil
check let the boolean false pass through to pandoc.Para({link}),
crashing with "Inline-ish expected, got boolean" on figures without
linked images (e.g. alt-text feature-format-matrix tests).
Collaborator
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
- empty-latex-div: use distinct env (flushright) for empty div so regex
uniquely verifies it instead of being satisfied by the non-empty case
- proof-rendering: assert named proof title \begin{proof}[Of the main theorem]
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Lua filters access
.content[1].tin ~35 callsites acrosssrc/resources/filters/. About half have proper nil/empty guards; the rest crash when content is empty or structurally unexpected. Same class of bug as #14585 (empty blockquote crash).Changes
Four filter files fixed, one dead code path removed:
latexdiv.lua—quarto.utils.match("[1]/Para")guards the first-element check; empty divs withlatexattribute fall through to the RawBlock branchparsefiguredivs.lua:489—and→orlogic bug in Figure bail-out condition; withand, empty content crashes and single non-Plain doesn't bailpandoc3_figure.lua— HTML path migrated toquarto.utils.match("[1]/Plain/[1]/Link")withnot linkguard (match()returnsfalse, notnil, on no-match); Typst path gets empty-content early returnparsefiguredivs.lua:309— migrated toquarto.utils.match("[1]/Para/[1]/Image")for the nested type checkcrossref/theorems.lua— removed 90 lines of dead proof-rendering code in theDivhandler. Sincee644af3a4(Dec 2023),parseblockreftargetsconverts proof divs toProofcustom nodes beforecrossref_theorems()runs; theDivhandler was never reachedTest Plan
empty-latex-div.qmd— empty div withlatexattribute renders to PDFempty-figure-content.qmd— Figure with content stripped by pre-ast filter renders to PDFempty-pandoc3-figure.qmd— non-crossref Figure with empty content renders to HTMLempty-para-inlines.qmd— fig-div with empty Para inlines renders to HTMLproof-rendering.qmd/proof-rendering-html.qmd— proof, remark, named proof, empty proof still render correctly after dead code removalimage/alt-texttests pass (regression frommatch()return value)