GeezerVison is showing github.com — Fix str.expandtabs aborting on a tab size of zero by luantaraschi · Pull Request #8562 · RustPython/RustPython
Conversation
luantaraschi
commented
Aug 20, 2026
•
edited by coderabbitai
Bot
Loading
Uh oh!
There was an error while loading. Please reload this page.
"a\tb".expandtabs(0) takes the interpreter down:
$ rustpython -c 'print("a\tb".expandtabs(0))'
thread 'main' panicked at library/alloc/src/raw_vec/mod.rs:28:5:
capacity overflow
CPython returns 'ab': with no width to advance to, the tabs come out and nothing else moves. expandtabs(-1) is the same, because ExpandTabsArgs::tabsize sends every negative value to 0.
rustpython_common::str::expandtabs walks the string with the tab stop in tab_size and the current column in col_count, and on a tab it does tab_size - col_count. Both start at zero, so the first character makes col_count 1 while tab_size stays 0, and the subtraction underflows. The run of spaces asked for next is usize::MAX, and the allocation aborts the process. That is why a tab has to follow something: "\ta".expandtabs(0) starts the tab at column 0, subtracts 0 from 0, and comes out right by accident.
BytesInner::expandtabs already returns early for this, filtering the tabs out. The string version now does the same thing.
Reachable from any Python code, no C API and no unusual build needed:
"\t".join(parts).expandtabs(user_supplied_width)
Checked against CPython 3.14.7 over 23 subjects by 10 tab sizes by str, bytes and bytearray, 460 cases. All 460 agree now. Without the change 42 of them abort the process, and the bytes half is not among them, which is where the shape of the fix came from.
Tests are in crates/common/src/str.rs next to the function and in extra_tests/snippets/builtin_str.py alongside the expandtabs overflow cases that landed in #8524. Both cover the zero and negative sizes, ASCII and non-ASCII, tabs after a newline and after a carriage return, and a set of ordinary tab sizes so a fix that reached too far would show up.
Lib/test/string_tests.py has no case for a zero tab size, so test_str and test_bytes pass either way. They still pass here, along with test_textwrap, 523 tests.
Summary by CodeRabbit
-
Bug Fixes
- Fixed
expandtabs handling for zero tab sizes, preventing excessive memory allocation and process termination.
- Zero tab sizes now remove tab characters consistently across strings, bytes, and bytearrays.
- Preserved existing behavior for positive tab sizes, including multiline and multibyte text.
-
Tests
- Added coverage for zero, negative, and positive tab sizes across varied input types and content.
- Added coverage for Unicode-safe replacement using empty patterns.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
coderabbitai
Bot
commented
Aug 20, 2026
•
edited
Loading
Uh oh!
There was an error while loading. Please reload this page.
|

No actionable comments were generated in the recent review. 🎉
ℹ️ Recent review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro Plus
Run ID: facf520a-6186-4010-82c9-56e8a01639e1
📥 Commits
Reviewing files that changed from the base of the PR and between a707729 and 410844f.
📒 Files selected for processing (1)
extra_tests/snippets/builtin_str.py
Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.
📝 Walkthrough
Walkthrough
expandtabs now removes tabs for a zero tab size instead of allocating an oversized space run. Rust and Python tests cover zero, negative, and positive tab sizes. Python tests also cover empty-pattern replacement.
Changes
Expandtabs zero-tab-size handling
| Layer / File(s) |
Summary |
Zero tab-size behavior crates/common/src/str.rs, extra_tests/snippets/builtin_str.py |
expandtabs removes tab characters when tab_size is zero. Rust and Python tests cover zero, negative, and positive tab sizes across strings, bytes, bytearrays, Unicode content, and line boundaries. |
Empty-pattern replacement coverage
| Layer / File(s) |
Summary |
Empty-pattern replacement regression coverage extra_tests/snippets/builtin_str.py |
Python tests cover empty-pattern replacement for ASCII, Unicode, empty inputs, insertion counts, and ordinary replacement behavior. |
Estimated code review effort: 2 (Simple) | ~10 minutes
Merge Risk: ⚪ Minimal · up to 41084
This change prevents process termination for zero or negative tab sizes and adds focused coverage; no actionable merge-blocking risk remains after normal checks and review.
Suggested reviewers: youknowone
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
| Check name |
Status |
Explanation |
| Description Check |
✅ Passed |
Check skipped - CodeRabbit’s high-level summary is enabled. |
| Title check |
✅ Passed |
The title clearly and concisely describes the primary change: preventing str.expandtabs from aborting when the tab size is zero. |
| Docstring Coverage |
✅ Passed |
Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 2 files. |
| Linked Issues check |
✅ Passed |
Check skipped because no linked issues were found for this pull request. |
| Out of Scope Changes check |
✅ Passed |
Check skipped because no linked issues were found for this pull request. |
✨ Finishing Touches
🧪 Generate unit tests (beta)
- Create PR with unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
❤️ Share
Comment @coderabbitai help to get the list of available commands.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
`"a\tb".expandtabs(0)` panicked with a capacity overflow. CPython returns
`'ab'`: with no width to advance to, the tabs come out and nothing else
moves. `expandtabs(-1)` is the same call, since `ExpandTabsArgs::tabsize`
sends every negative value to 0.
`expandtabs` keeps the tab stop in `tab_size` and the current column in
`col_count`, and on a tab it does `tab_size - col_count`. With a tab size of
zero both start at 0, the first character makes `col_count` 1 while
`tab_size` stays 0, and the subtraction underflows. The run of spaces asked
for next is `usize::MAX`, and the allocation aborts the process. A tab has to
follow something on the line to reach it: `"\ta".expandtabs(0)` subtracts 0
from 0 and comes out right by accident.
`BytesInner::expandtabs` already returns early for this and filters the tabs
out. The string version now does the same.
Assisted-by: Claude Code:claude-opus-5
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
youknowone
pushed a commit
that referenced
this pull request
Sep 16, 2026
`"a\tb".expandtabs(0)` panicked with a capacity overflow. CPython returns
`'ab'`: with no width to advance to, the tabs come out and nothing else
moves. `expandtabs(-1)` is the same call, since `ExpandTabsArgs::tabsize`
sends every negative value to 0.
`expandtabs` keeps the tab stop in `tab_size` and the current column in
`col_count`, and on a tab it does `tab_size - col_count`. With a tab size of
zero both start at 0, the first character makes `col_count` 1 while
`tab_size` stays 0, and the subtraction underflows. The run of spaces asked
for next is `usize::MAX`, and the allocation aborts the process. A tab has to
follow something on the line to reach it: `"\ta".expandtabs(0)` subtracts 0
from 0 and comes out right by accident.
`BytesInner::expandtabs` already returns early for this and filters the tabs
out. The string version now does the same.
Assisted-by: Claude Code:claude-opus-5
Uh oh!
There was an error while loading. Please reload this page.
Assignees
No one assigned
Milestone
No milestone
Development
Successfully merging this pull request may close these issues.
Uh oh!
There was an error while loading. Please reload this page.
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.
|