Skip to content

Replace malformed unicode error helper calls with the _real variants#8359

Merged
youknowone merged 2 commits into
RustPython:mainfrom
devyubin:unicode-error-real
Jul 26, 2026
Merged

Replace malformed unicode error helper calls with the _real variants#8359
youknowone merged 2 commits into
RustPython:mainfrom
devyubin:unicode-error-real

Conversation

@devyubin

@devyubin devyubin commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Part of #8352

Summary

new_unicode_decode_error / new_unicode_encode_error construct a structurally invalid exception: it has none of the five attributes a unicode error must carry (encoding, object, start, end, reason), its args is a 1-tuple message, and str() renders as an empty string. This converts the non-Windows call sites to the _real constructors:

import csv
next(csv.reader(['\udcff'], quoting=csv.QUOTE_STRINGS))
# before: str(e) = ''            args = ('csv not utf8',)   e.encoding -> AttributeError
# after:  str(e) = "'utf-8' codec can't decode byte 0xed in position 0: csv not utf8"
#         args = ('utf-8', b'\xed\xb3\xbf', 0, 1, 'csv not utf8')   all five attributes set

Cause

The broken helpers are generated by define_exception_fn!, which goes through new_exception_msgPyBaseException::new(args). That only stores args and never runs the type's initializer, so the unicode-error attributes are never set. The _real constructors pass the full (encoding, object, start, end, reason) and set every attribute.

Fix

Converted call sites, supplying the source bytes/str and the failing offset from the previously discarded Utf8Error (valid_up_to(), span start..start + 1 matching the existing _real usages):

  • csv: all 6 sites, via a new_not_utf8_error helper next to the existing new_csv_error
  • socket: host/port bytes decode; the port surrogate check now reports the first surrogate position (same scan as PyStr::ensure_valid_utf8)
  • posix.getlogin, FsPath::bytes_as_os_str, os::bytes_as_os_str

Left for follow-ups, per the issue plan:

  • the Windows-gated sites (nt.rs, mbcs/oem codecs) — I can't test Windows locally
  • sites whose source object is not reachable at the error site (uname, array) — these need a small refactor or an error-type decision rather than a mechanical conversion

Test

  • Repro above now produces a well-formed UnicodeDecodeError; also verified socket.getaddrinfo(b"\xff\xfe", 80)('utf-8', b'\xff\xfe', 0, 1, ...) and socket.getaddrinfo("localhost", "ab\udcffcd")UnicodeEncodeError ('utf-8', 'ab\udcffcd', 2, 3, 'surrogates not allowed') (surrogate position reported precisely).
  • cargo run -- -m test test_csv / test_os / test_posixTests result: SUCCESS (no unexpected successes).
  • cargo clippy / cargo fmt: clean (no new warnings).

Assisted-by: Claude Code:claude-fable-5

Summary by CodeRabbit

  • Bug Fixes
    • Improved Unicode decoding errors in CSV operations with clearer failure details.
    • Enhanced socket address handling to report accurate errors for invalid host and port text.
    • Improved error reporting when filesystem paths contain invalid UTF-8 bytes.
    • Updated login-name decoding failures to provide standard, detailed Unicode errors.
    • Preserved the original invalid data and error location wherever possible for easier diagnosis.

new_unicode_decode_error / new_unicode_encode_error build the exception
from a bare message without running the initializer, so the result has
none of the five attributes a unicode error must carry and str() renders
as an empty string. Convert the non-Windows call sites in csv, socket,
getlogin and the fs-path decoders to the _real constructors, passing the
source bytes/str and the failing offset from the captured Utf8Error.

The Windows-gated sites (nt, mbcs/oem codecs) and the sites whose source
object is not reachable (uname, array) are left for follow-ups.

Assisted-by: Claude Code:claude-fable-5
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Unicode conversion failures now preserve original bytes, encoding context, and invalid positions when constructing decode or encode exceptions across CSV, socket, filesystem, OS, and POSIX paths.

Changes

Unicode Error Reporting

Layer / File(s) Summary
CSV UTF-8 error handling
crates/stdlib/src/csv.rs
CSV field decoding and writer output paths use a shared helper that includes the original bytes and UTF-8 error position details.
Socket and OS conversion errors
crates/stdlib/src/socket.rs, crates/vm/src/function/fspath.rs, crates/vm/src/stdlib/os.rs, crates/vm/src/stdlib/posix.rs
Host, port, filesystem path, OS path, and login-name conversion failures now construct detailed UTF-8 decode or surrogate encode exceptions.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related issues

Suggested reviewers: shaharnaveh, 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 accurately summarizes the main change: switching malformed unicode error helpers to _real variants.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/stdlib/src/csv.rs`:
- Around line 63-76: Update new_not_utf8_error to calculate the decode-error end
offset from err.error_len(): use err.valid_up_to() plus the reported length when
available, and bytes.len() for truncated sequences when it is None. Pass this
computed end offset to vm.new_unicode_decode_error_real instead of always using
err.valid_up_to() + 1, preserving the complete invalid UTF-8 span.

In `@crates/stdlib/src/socket.rs`:
- Around line 2615-2622: Update both host and port UTF-8 error handlers around
the host and port from_utf8 calls in crates/stdlib/src/socket.rs:2615-2622 and
crates/stdlib/src/socket.rs:2656-2664 to calculate the UnicodeDecodeError end
position using Utf8Error::error_len(), falling back to the remaining input
length when needed, instead of always using valid_up_to() + 1.

In `@crates/vm/src/function/fspath.rs`:
- Around line 128-136: Update the UTF-8 error mappings in
crates/vm/src/function/fspath.rs:128-136, crates/vm/src/stdlib/os.rs:133-141,
and crates/vm/src/stdlib/posix.rs:1734-1742 to use the complete failure span:
derive the end offset from e.error_len(), falling back to the source slice
length when it is None, and use the source length for unexpected EOF instead of
valid_up_to() + 1.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: 41bb1195-e3a9-4784-913d-1463dff8e1a0

📥 Commits

Reviewing files that changed from the base of the PR and between 28454cc and 02c70c4.

📒 Files selected for processing (5)
  • crates/stdlib/src/csv.rs
  • crates/stdlib/src/socket.rs
  • crates/vm/src/function/fspath.rs
  • crates/vm/src/stdlib/os.rs
  • crates/vm/src/stdlib/posix.rs

Comment thread crates/stdlib/src/csv.rs
Comment thread crates/stdlib/src/socket.rs
Comment thread crates/vm/src/function/fspath.rs
@moreal moreal added the z-ca-2026 Tag to track Contribution Academy 2026 label Jul 24, 2026

@moreal moreal 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.

Thank you for your contribution! Could you see the Coderabbit's reviews about Utf8Error::error_len()?

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (1)
crates/vm/src/vm/vm_new.rs (1)

541-548: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add regression tests for both UTF-8 span cases.

Cover invalid sequences where error_len() is Some, truncated sequences where it is None, and assert the resulting encoding, object, start, end, and reason attributes. The supplied context does not include these tests, so please verify they exist in the VM test suite.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/vm/src/vm/vm_new.rs` around lines 541 - 548, Add regression tests in
the VM test suite for UTF-8 decode errors covering both invalid sequences with
error_len() = Some and truncated sequences with error_len() = None. Assert each
resulting error’s encoding, object, start, end, and reason attributes, including
the expected span boundaries for both cases.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@crates/vm/src/vm/vm_new.rs`:
- Around line 541-548: Add regression tests in the VM test suite for UTF-8
decode errors covering both invalid sequences with error_len() = Some and
truncated sequences with error_len() = None. Assert each resulting error’s
encoding, object, start, end, and reason attributes, including the expected span
boundaries for both cases.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: ef508b01-1f91-4b5e-a1f4-f4783ab06334

📥 Commits

Reviewing files that changed from the base of the PR and between 02c70c4 and 47b6766.

📒 Files selected for processing (6)
  • crates/stdlib/src/csv.rs
  • crates/stdlib/src/socket.rs
  • crates/vm/src/function/fspath.rs
  • crates/vm/src/stdlib/os.rs
  • crates/vm/src/stdlib/posix.rs
  • crates/vm/src/vm/vm_new.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • crates/vm/src/stdlib/posix.rs
  • crates/stdlib/src/socket.rs

The conversions used valid_up_to() + 1 for the decode-error end offset,
which under-reports multi-byte invalid sequences. Take the span from the
Utf8Error instead: valid_up_to() + error_len(), or the input length for a
truncated sequence (error_len() == None), matching CPython.

Assisted-by: Claude Code:claude-opus-4-8
@devyubin
devyubin force-pushed the unicode-error-real branch from 47b6766 to b63c408 Compare July 26, 2026 02:04

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (1)
crates/stdlib/src/csv.rs (1)

63-77: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add regression coverage for both invalid UTF-8 span cases.

Please ensure tests cover a malformed sequence where error_len() is Some(n) and a truncated sequence where it is None, asserting the resulting exception’s object, start, and end attributes. As per coding guidelines, run cargo fmt and cargo clippy before completion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/stdlib/src/csv.rs` around lines 63 - 77, Add regression tests for
new_not_utf8_error covering both malformed UTF-8 with error_len() = Some(n) and
truncated UTF-8 with error_len() = None, asserting each resulting exception’s
object, start, and end attributes. Run cargo fmt and cargo clippy after
implementing the tests.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@crates/stdlib/src/csv.rs`:
- Around line 63-77: Add regression tests for new_not_utf8_error covering both
malformed UTF-8 with error_len() = Some(n) and truncated UTF-8 with error_len()
= None, asserting each resulting exception’s object, start, and end attributes.
Run cargo fmt and cargo clippy after implementing the tests.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: 3aa8e043-87c1-4675-9fe2-44935fa949b8

📥 Commits

Reviewing files that changed from the base of the PR and between 47b6766 and b63c408.

📒 Files selected for processing (5)
  • crates/stdlib/src/csv.rs
  • crates/stdlib/src/socket.rs
  • crates/vm/src/function/fspath.rs
  • crates/vm/src/stdlib/os.rs
  • crates/vm/src/stdlib/posix.rs
🚧 Files skipped from review as they are similar to previous changes (4)
  • crates/vm/src/function/fspath.rs
  • crates/vm/src/stdlib/posix.rs
  • crates/vm/src/stdlib/os.rs
  • crates/stdlib/src/socket.rs

@youknowone youknowone 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.

looks good in general.
If you are interested in further development, there is a common pattern for new_unicode_decode_error_real using utf-8 and error info. then creating another wrapper for that kind of simple case will be helpful. e.g. new_utf8_decode_error

Comment on lines +2639 to +2642
.as_wtf8()
.code_points()
.position(|c| c.to_char().is_none())
.unwrap();

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.

Suggested change
.as_wtf8()
.code_points()
.position(|c| c.to_char().is_none())
.unwrap();
.as_bytes()
// match surrogate bytes directly using bytes may also work

@youknowone
youknowone merged commit aea5fe3 into RustPython:main Jul 26, 2026
26 checks passed
@youknowone

Copy link
Copy Markdown
Member

Thank you!

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

Labels

z-ca-2026 Tag to track Contribution Academy 2026

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants