Skip to content

bibtex: fix text.prefix$ hang on special characters - #1405

Open
StevenLi-phoenix wants to merge 3 commits into
tectonic-typesetting:masterfrom
StevenLi-phoenix:fix/1398-text-prefix-hang
Open

StevenLi-phoenix wants to merge 3 commits into
tectonic-typesetting:masterfrom
StevenLi-phoenix:fix/1398-text-prefix-hang

Conversation

@StevenLi-phoenix

Copy link
Copy Markdown

Created by Claude Opus-5-5: the code, tests and this description were written entirely by Claude Opus-5-5.

Fixes #1398.

What was broken

Tectonic hung at "Running BibTeX" for any entry whose alphabetic label needs
a braced special character such as {\"o}. That covers \bibliographystyle{alpha}
and biblatex style=alphabetic with backend=bibtex, whenever the special
character falls within the first three letters of the surname. With alpha.bst
one entry is enough to trigger it, e.g. author = {Kurt G{\"o}del}. biblatex's
label code calls text.prefix$ on a name only in some configurations, which is
probably why a single entry did not reproduce it in the report. Tectonic printed
nothing and never finished.

Root cause

interp_text_prefix (text.prefix$) has an inner loop that skips to the closing
brace of a special character. That loop never advanced idx, so it spun forever.
#1185 fixed the same loop in interp_text_len (text.length$). Both loops also
counted every byte of the special character as a text character. bibtex.web
counts an entire special character as one text character (incr(num_text_chars)
once, after the loop).

Changes

Three commits. The second and third are independent of the hang and can be
split into separate PRs if you prefer.

  1. bibtex: fix text.prefix$ hang on special characters. Advances idx inside
    the loop and counts the special character once. G{\"o}del now gives the
    label G{\"o}d31, as bibtex.web specifies.
  2. bibtex: count a special character once in text.length$. This is the same
    counting fix in interp_text_len. text.length$ on H{\"a}hnle now returns 6
    (it returned 8 before). In alpha.bst the miscount changed labels for a single
    author whose last name starts with a special character: {\.I}lhan was
    labelled {\.I} and is now {\.I}lh. This changes the existing
    control_seq_alpha expectations. The entry order and the warning order in
    the .blg also change, because alpha.bst sorts by label.
  3. bibtex: fix text.prefix$ returning an empty string for fields. I found
    this while testing. PoolCursor::append_substr copied the bytes but did not
    advance the cursor, so text.prefix$ on a non-scratch string (an entry field
    or a literal) returned only the closing braces it adds. In alpha.bst an
    entry with a key field and no author got the label 99 instead of
    {\"O}st99. It has been wrong since the StringPool cursor rewrite
    (2742e7a), and 0.17.0 also returns an empty string for "Smith" #3 text.prefix$
    on a field. The fix advances the cursor, as append_str already does.

Tests

  • New tests/bibtex.rs case test_text_prefix_special (fixture
    tests/bibtex/cites/text_prefix_special.*, using the existing alpha.bst) has
    three entries: G{\"o}del, G{\"{a}}rtner (nested braces inside the special
    character) and an entry labelled from key = "{\"O}sterreich".
    • On unmodified master the test hangs: timeout 70 cargo test --features external-harfbuzz --test bibtex test_text_prefix_special
      exits 124 after printing "has been running for over 60 seconds".
    • With only the idx increment (per-byte counting kept), the labels come out
      as G{\"o}31 and G{\"{a}}50, and the test fails.
    • Before commit 3 the key entry is labelled 99, and the test fails.
  • control_seq_alpha.{bbl,blg} now expect {\.I}lh. The test fails before
    commit 2 and passes after it.
  • New unit test pool::tests::test_write_substr. It fails before commit 3
    (the string is ! instead of Hello!) and passes after it.
  • No TeX Live bibtex was available here, so I wrote the expected .bbl files
    from bibtex.web and cross-checked them with pybtex 0.26.1, which gives the
    same labels: G{\"o}d31, G{\"{a}}r50, {\"O}st99 and {\.I}lh. A run against
    real BibTeX would still be welcome.

Commands run (macOS arm64, Rust 1.97.1):

cargo test --workspace --features external-harfbuzz       # all pass
cargo test --features external-harfbuzz --test bibtex     # 25 passed
cargo test -p tectonic_engine_bibtex                      # 10 passed
cargo fmt --all -- --check                                # clean
cargo clippy -p tectonic_engine_bibtex --all-targets --all-features -- --deny warnings   # clean
cargo clippy --all --all-targets --all-features -- --deny warnings                     # clean

End to end, with the reproducers from the issue:

  • \bibliographystyle{alpha} with a single Kurt G{\"o}del entry: Tectonic
    0.17.0 is still in BibTeX when killed after 120 s. The patched build finishes,
    and doc.bbl has \bibitem[G{\"o}d31]{g31}.
  • biblatex backend=bibtex,style=alphabetic with H{\"a}hnle, M{\"u}ller,
    Lov{\'a}sz and G\"o\"os: Tectonic 0.17.0 is killed after 90 s. The patched
    build finishes with labelalpha values H{\"a}h10, M{\"u}l01, Lov79 and
    G\"22. The last one is unchanged by this PR. The accent in G\"o\"os is not
    braced, so text.prefix$ counts \ and " as ordinary characters, as
    bibtex.web does.
  • A small .bst that writes text.length$ and #3 text.prefix$ of each
    author field gives 5 Smi, 6 H{\"a}h, 7 G{\"{a}}r and 5 {\.I}lh for
    Smith, H{\"a}hnle, G{\"{a}}rtner and {\.I}lhan. 0.17.0 gives lengths
    8, 11 and 7 for the last three, and an empty prefix for Smith.

Not addressed

The issue also asks for a diagnostic or a timeout when BibTeX does not
terminate. This PR fixes the cause of the hang only.

PoolCursor::append writes strings[end] without growing the pool. If a string
ends exactly at the end of the allocated pool, the next append panics with
"index out of bounds" (shown with a unit test that adds a 65536-byte string and
then appends one byte). add.period$ and the closing braces of text.prefix$
go through this path. The code before the StringPool rewrite reserved room
first. I left it out of this PR because it is a separate bug.

When text.prefix$ reached a special character such as {\"o} at brace
level 1, the loop that skips to its closing brace never advanced, so
bibtex spun forever. alpha.bst and biblatex's alphabetic style hit this
for any surname with a braced accent in its first three letters.

Advance through the special character and count it as a single text
character, as bibtex.web does. This is the same loop that tectonic-typesetting#1185 fixed in
text.length$.

Fixes tectonic-typesetting#1398.
text.length$ counted every byte after the backslash of a special
character, so it returned 8 for H{\"a}hnle where BibTeX returns 6.
bibtex.web counts an entire special character as one text character,
and text.prefix$ already does so.

This shows up in alpha.bst labels for a single author whose last name
starts with a special character: the length check that decides whether
to take three letters of the last name saw {\.I} as three characters,
so the label was {\.I} rather than {\.I}lh. Update the control_seq_alpha
expectations to the BibTeX result, which also changes the sort order.
PoolCursor::append_substr copied the bytes but did not advance the
cursor, so text.prefix$ on a string that is not a scratch string (an
entry field or a literal) returned only the closing braces it adds. In
alpha.bst this left an entry labelled from its key field, with no
author, as "99" rather than "{\"O}st99".

Advance the cursor past the copied range, as append_str does.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BibTeX does not terminate generating alphabetic labels for names containing TeX accent macros

1 participant