Skip to content

Prevent NVDA freeze when brailling math with certain Unicode characters#20320

Draft
AAClause wants to merge 5 commits into
nvaccess:betafrom
AAClause:i20319
Draft

Prevent NVDA freeze when brailling math with certain Unicode characters#20320
AAClause wants to merge 5 commits into
nvaccess:betafrom
AAClause:i20319

Conversation

@AAClause

@AAClause AAClause commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Link to issue number:

Fixes #20319

Summary of the issue:

NVDA can freeze when MathCAT panics while processing MathML that contains certain Unicode characters (commonly mathematical alphanumeric symbols such as 𝑎 U+1D44E and 𝑏 U+1D44F in , , etc.).
MathCAT's Nemeth braille post-processing can panic on invalid UTF-8 string slicing when real math characters collide with internal placeholder characters. PyO3 exposes this Rust panic as pyo3_runtime.PanicException, which does not inherit from Exception. NVDA's existing except Exception handlers in mathPres/MathCAT/MathCAT.py therefore do not catch it, leaving an unhandled exception on the main thread and triggering watchdog freeze recovery.

Description of user facing changes:

When MathCAT panics on problematic MathML, NVDA no longer becomes unresponsive. The failure is handled like other MathCAT errors: it is logged, the user is notified (e.g. "Error in brailling math."), and NVDA remains usable.
Speech/braille for the affected formula may still be missing or incorrect until MathCAT itself is fixed upstream; this PR only ensures NVDA fails gracefully.

Description of developer facing changes:

  • Adds MathCATError, a normal Exception subclass used to represent MathCAT failures including PyO3 panics.
  • Adds _callMathCAT(), a thin wrapper around all libmathcat calls that converts pyo3_runtime.PanicException into MathCATError, so existing except Exception blocks continue to work without importing PyO3 types (which are not reliably importable at module load time).
  • Adds unit tests in tests/unit/test_mathPres/test_callMathCAT.py.

Description of development approach:

All direct libmathcat.* calls in MathCAT.py are routed through _callMathCAT(). The wrapper:

  1. Re-raises normal Exception subclasses unchanged.
  2. Re-raises KeyboardInterrupt and SystemExit unchanged.
  3. Detects PyO3 panics by type name and module (PanicException from pyo3_runtime) and raises MathCATError with the panic message, preserving the original as cause.
  4. Re-raises any other BaseException unchanged.

This approach was chosen because:

  • Importing PanicException from pyo3_runtime is unreliable outside an active panic context, so isinstance() checks against an imported class do not work.
  • Broad except BaseException at every call site is noisy and easy to get wrong.
  • Rewriting MathML content (e.g. normalizing mathematical alphanumeric symbols) would mask upstream MathCAT bugs and risk altering legitimate math notation.
    The underlying MathCAT panic (unsafe UTF-8 slicing / placeholder collision in Nemeth braille code) remains an upstream issue in MathCAT.

Testing strategy:

Performed manual testing using problematic math formulas.

Before: NVDA freezes; log shows unhandled pyo3_runtime.PanicException during libmathcat.GetBraille().
After: NVDA stays responsive; log shows handled MathCATError; user hears/sees the existing MathCAT error notification.

Known issues with pull request:

  • This does not fix MathCAT's internal panic or produce correct braille/speech for MathML that triggers it. That requires an upstream MathCAT fix.
  • Panic detection relies on PyO3's current convention (PanicException in module pyo3_runtime). A future PyO3 change could require updating the check.

Code Review Checklist:

  • Documentation:
    • Change log entry
    • User Documentation
    • Developer / Technical Documentation
    • Context sensitive help for GUI changes
  • Testing:
    • Unit tests
    • System (end to end) tests
    • Manual testing
  • UX of all users considered:
    • Speech
    • Braille
    • Low Vision
    • Different web browsers
    • Localization in other languages / culture than English
  • API is compatible with existing add-ons.
  • Security precautions taken.

@AAClause AAClause marked this pull request as ready for review June 11, 2026 13:22
@AAClause AAClause requested a review from a team as a code owner June 11, 2026 13:22
@AAClause AAClause requested review from SaschaCowley and Copilot June 11, 2026 13:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Prevents NVDA from freezing when MathCAT (via PyO3) raises a Rust panic by translating those panics into regular Python exceptions, and documents the user-visible fix.

Changes:

  • Added a MathCAT wrapper (_callMathCAT) to convert PyO3 panic exceptions into a MathCATError.
  • Routed multiple libmathcat calls through _callMathCAT so existing except Exception handlers can catch failures.
  • Added a changelog entry describing the MathCAT panic/freeze fix.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
user_docs/en/changes.md Documents the freeze fix for MathCAT panics in release notes.
source/mathPres/MathCAT/MathCAT.py Adds panic-to-Exception translation and applies it across MathCAT/libmathcat calls.

Comment thread source/mathPres/MathCAT/MathCAT.py
Comment thread source/mathPres/MathCAT/MathCAT.py
Comment thread source/mathPres/MathCAT/MathCAT.py Outdated
@AAClause AAClause changed the title Prevent NVDA freezing when MathCAT panics on Unicode MathML Prevent NVDA freeze when brailling math with certain Unicode characters Jun 12, 2026

@seanbudd seanbudd left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice fix, thanks

@SaschaCowley SaschaCowley added the conceptApproved Similar 'triaged' for issues, PR accepted in theory, implementation needs review. label Jun 16, 2026
@seanbudd seanbudd added this to the 2026.2 milestone Jun 16, 2026
@seanbudd seanbudd changed the base branch from master to beta June 16, 2026 05:36
@seanbudd seanbudd requested a review from a team as a code owner June 16, 2026 05:36
@seanbudd seanbudd requested a review from Qchristensen June 16, 2026 05:36
@seanbudd

Copy link
Copy Markdown
Member

can this be updated to retarget to beta please?
And on master can we revert this patch and update mathcat instead?

@seanbudd seanbudd marked this pull request as draft June 16, 2026 05:36
AAClause added 5 commits June 18, 2026 10:01
PyO3 exposes Rust panics as PanicException, which is not caught by except Exception. Route libmathcat calls through a wrapper that converts these panics into MathCATError so existing MathCAT error handling can run.

Fixes nvaccess#20319
Simplify exception handling to a single BaseException handler and detect
PyO3 panics via isinstance with a string-based fallback.
NVDA ships libmathcat_py only; pyo3_runtime is not importable. Detect PyO3 panics by exception type name and module instead.
@seanbudd

Copy link
Copy Markdown
Member

is this ready for re-review?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

conceptApproved Similar 'triaged' for issues, PR accepted in theory, implementation needs review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

NVDA freezes when MathCAT panics on MathML with Unicode math characters (PyO3 PanicException not caught)

4 participants