Skip to content

Fix crash on Python 3.12+ #76

Open
abizer wants to merge 3 commits intocknd:masterfrom
abizer:py314-lineno-fix
Open

Fix crash on Python 3.12+ #76
abizer wants to merge 3 commits intocknd:masterfrom
abizer:py314-lineno-fix

Conversation

@abizer
Copy link
Copy Markdown

@abizer abizer commented Apr 14, 2026

Summary

Fixes issue where structured exception logging via stackprinter crashes on Python 3.12+ for certain tracebacks with `AssertionError` at `source_inspection.py:62` (and downstream `TypeError: '<=' not supported between instances of 'int' and 'NoneType'` in consumers like Loguru).

Root cause

Since CPython 3.12, `tb.tb_lineno` and `frame.f_lineno` can return `None` when the instruction at `tb_lasti` / `f_lasti` has no line mapping — e.g. synthetic RESUME/CACHE opcodes or certain async-suspension edge cases. `extraction.get_info` passed that `None` straight through to `source_inspection.annotate`, which blew up on `assert isinstance(lineno, int)`. In 3.11 `tb_lineno` was a plain `T_INT` PyMemberDef so the None path never existed — which is why the upstream maintainer is seeing it as a 3.14 regression even though the underlying CPython change landed in 3.12.

Confirmed by reading `Python/traceback.c`'s `tb_lineno_get` at the v3.14.2 tag (`Py_RETURN_NONE` when `PyCode_Addr2Line(code, lasti) < 0`), and reproduced locally with a synthetic `types.TracebackType(None, frame, 1<<20, -1)` that triggers the exact crash from production.

Changes

  1. `extraction.get_info` — after reading `tb.tb_lineno` or `frame.f_lineno`, fall back to `frame.f_code.co_firstlineno` when the value is `None`, so the frame still renders with the function header highlighted instead of crashing downstream formatters.
  2. Regression test — `test_tb_with_none_lineno` constructs a synthetic traceback with an out-of-range `tb_lasti` so `tb.tb_lineno` returns `None`, then formats through `stackprinter.format()` end-to-end. Skips on interpreters where the getter doesn't return `None`.
  3. Release 0.2.13 — `setup.py` version bump + CHANGELOG entry.
  4. CI matrix — Python 3.13 and 3.14 added to the build matrix so future regressions in this area get caught upstream instead of only in downstream consumers. Also adds a minimal `.gitignore` covering `pycache`, `.egg-info`, `build/`, `dist/`, `.venv`, and `.pytest_cache`.

Test plan

  • `pytest tests/` passes on Python 3.11, 3.12, and 3.14 (8/8 including the new regression test)
  • Reproducer for ARE-2675 now renders a formatted frame instead of crashing with `AssertionError`
  • Existing demos (`demo.py`, `demo_chained_exceptions.py`, `demo_logging.py`) still produce expected output on 3.14

🤖 Generated with Claude Code

abizer and others added 3 commits April 14, 2026 01:48
Since CPython 3.12, `tb.tb_lineno` and `frame.f_lineno` can return None
when the instruction at tb_lasti / f_lasti has no line mapping (for
example certain async suspension points or synthetic RESUME/CACHE
opcodes). `extraction.get_info` passed that None straight through to
`source_inspection.annotate`, which blew up on
`assert isinstance(lineno, int)`.

Fall back to `frame.f_code.co_firstlineno` so a frame still renders
instead of crashing downstream formatters. Adds a regression test that
forces the None case via a synthetic TracebackType with out-of-range
tb_lasti.

Bumps version to 0.2.13.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extend the test matrix so Python 3.12+ regressions like the one fixed
in the previous commit get caught upstream instead of only in
downstream consumers. Also adds a minimal .gitignore covering
__pycache__, *.egg-info, build/, dist/, .venv*, and .pytest_cache so
local dev doesn't leave untracked build artifacts in the worktree.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.

1 participant