Skip to content

_socket, sys, posix, mapdict, faulthandler, _warnings: parity fixes - #871

Merged
youknowone merged 7 commits into
mainfrom
import
Jul 29, 2026
Merged

_socket, sys, posix, mapdict, faulthandler, _warnings: parity fixes#871
youknowone merged 7 commits into
mainfrom
import

Conversation

@youknowone

@youknowone youknowone commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Seven commits on top of main, each a separate parity fix.

commit area
_socket: encode an IDNA hostname through the codec, not an encode attribute _socket
executioncontext: drop the space-null guard from the trace hooks tracing
faulthandler: coerce all_threads and chain as integers faulthandler
mapdict: release the code-owned cache lock before touching an instance attribute caching
posix: give the result structseqs their dotted names and named-only extras posix
_warnings: take warn's source positionally _warnings, pyre-macros
sys, _io, builtins: carry the launcher's option state into sys and the std streams launcher, sys, _io, builtins

Authorship

Only the _socket commit was written and verified here. The other six are the
output of a codex session running in the same worktree; they were left
uncommitted and are split into themed commits and given commit messages citing
the upstream lines they follow, but their code has not been reviewed
line-by-line here.

_socket — what the change rests on

socket_idna_converter looked up encode on the host object and called it.
interp_socket.py:108-129 idna_converter reaches the codec through
space.encode_unicode_object, which is objspace.py:786 ->
unicodeobject.py:1683 encode_object; type_methods::encode_object is the
counterpart already shared by str.encode, bytes(str, ...) and
bytearray(str, ...).

A review suggestion motivating this said the codec route avoids invoking a
str subclass's encode override. Both oracles refute that: encodings/idna.py
Codec.encode itself calls input.encode('ascii'), so the override runs either
way.

CPython 3.14 : RuntimeError : subclass encode was called
PyPy         : RuntimeError : encoding with 'idna' codec failed (RuntimeError: subclass encode was called)
pyre (after) : RuntimeError : subclass encode was called

The change is made on the structural ground above, not that one. Observable
behaviour on the normal paths:

                CPython 3.14                     pyre
nonascii        gaierror                         gaierror (xn--4ca.example.invalid)
surrogate       UnicodeEncodeError 'idna' ...    UnicodeEncodeError 'idna' ...
nullbyte        TypeError                        TypeError
int             TypeError                        TypeError

Verification

ba42511c81, HEAD pinned across every stage:

stage result
LLBC extraction ok
dynasm release build ok
cranelift release build ok
cargo test --all --no-default-features --features dynasm ok — 100 test result: ok, 0 failed
pyre/check.py dynasm 331/332, cranelift 331/332, wasm 328/329
cargo test -p pyre-sandbox --test e2e_interact -- --ignored ok — 2 passed, 0 failed

The one check.py failure is synth/ast_compile_roundtrip on all three
backends, reported as BASEFAIL / cpython/pypy output mismatch. The two
oracles disagree with each other, so no baseline is established:

CPython 3.14 : hand reject named-target-not-name TypeError
PyPy         : hand reject named-target-not-name ValueError

That benchmark arrived with a510d58a73 (#856) and is untouched by this
branch, so the failure is not attributable to it.

The first cargo test --all run aborted in
pyre-object functional::range_obj_tests::iter_routes_increment_overflow_to_longrange
(handle_alloc_error -> SIGABRT). No commit here touches pyre-object; the
test passes alone, the crate's 279 tests pass together, and the same command at
the same commit passes on re-run, with sibling worktrees running concurrent
builds during the failing run.

🤖 Generated with Claude Code

Follow-up in this branch

The first push failed sandbox build + e2e (ubuntu-24.04): both e2e tests died
in the guest with

panicked at pyre/pyre-interpreter/src/module/sys/vm.rs:2029:6:
standard file descriptor must produce a binary buffer: PyError { kind: OSError, ... }

make_std_stream opened the binary layer for fd 0/1/2 and called .expect on
the result, so a descriptor the sandbox controller does not expose aborted
interpreter startup. app_main.py:465-470 create_stdio takes that failure as
except OSError rather than aborting. Its return None is not the right
mapping here — these streams keep instance-override methods that reach the
descriptor without the buffer, and the e2e guests assert on print output — so
the open failure now leaves the buffer absent and the stream in place.

Reproduced locally (2 failed), fixed, re-run green (2 passed), and squashed
into the sys, _io, builtins commit.

…ribute

`socket_idna_converter` looked up `encode` on the host object and called
it; `idna_converter` reaches the codec through `space.encode_unicode_object`
(`objspace.py encode_unicode_object` -> `unicodeobject.py encode_object`).
The non-ASCII branch now calls `type_methods::encode_object` with the `idna`
codec, which is the same helper `str.encode`, `bytes(str, ...)` and
`bytearray(str, ...)` share.

Encoding returns the bytes directly, so the non-bytes result check and the
`take_call_error` fallback are gone.

Assisted-by: Claude
`bytecode_only_trace` and `run_trace_func` returned early when `self.space`
was null. `space` is the interpreter owner rather than a tracing flag, and it
is PY_NULL in this runtime, so the test suppressed every line event while
call and return events still reached `_trace`. Both hooks now guard only on
a null frame.

Assisted-by: Claude
`handler.py:174` declares `@unwrap_spec(all_threads=int, chain=int)` with
defaults `all_threads=1, chain=0`, so both arguments go through
`gateway_int_w` and a non-integer raises. The module took them by
truthiness instead. `enable` also takes `file=None, all_threads=True`
per `handler.py:141-145`.

Assisted-by: Claude
`load_attr_slowpath` and `store_attr_slowpath` held the immortal-node cache
entry's code-owned lock across the slow path. That extends the lock over
arbitrary descriptor calls, unlike the single GIL critical section upstream,
and admits instance-A -> code -> instance-B / instance-B -> code lock cycles.
The entry is copied under the lock, which is then released before the
instance is touched.

Assisted-by: Claude
…xtras

`stat_result`, `uname_result`, `statvfs_result` and `times_result` carry
`__name__` "stat_result" with repr "os.stat_result". Following
`app_posix.py:20-37`, slots 7..10 hold the hidden integer timestamps and the
float `st_atime`/`st_mtime`/`st_ctime` are named-only extras that are never
indexable; `app_posix.py:38-69` orders the remaining named-only extras.

Assisted-by: Claude
`_warnings.warn`'s `source` is positional-or-keyword; only
`skip_file_prefixes` is keyword-only. The `#[kwonly]` marker moved off
`source`.

pyre-macros: a signature carrying kw-only or **kwargs markers is bound by
the gateway before the wrapper runs, so the scope it hands over holds every
positional and kw-only slot and `args.len()` is no longer the caller's
positional count. `BuiltinCode.funcrun_obj` likewise runs the activation
straight after `args.parse_obj(...)`, so the wrapper's positional-count
preamble is skipped for those signatures.

Assisted-by: Claude
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The PR adds functional standard input, propagates launcher flags into sys, revises standard stream construction, adds POSIX process APIs, updates warning and signal handling, changes IDNA fallback encoding, and synchronizes mapdict attribute caches for free-threading.

Changes

Runtime state and standard I/O

Layer / File(s) Summary
Launcher flags and sys runtime state
pyre/pyrex/src/lib.rs, pyre/pyre-interpreter/src/importing.rs, pyre/pyre-interpreter/src/module/sys/vm.rs
Launcher options, warning settings, encoding, buffering, and original argv are captured and exposed through sys.
Standard stream construction and encoding
pyre/pyre-interpreter/src/module/_io/textio.rs, pyre/pyre-interpreter/src/module/sys/vm.rs
Standard streams retain binary buffers, use live encoding policies, expose stdin readline, and initialize TextIOWrapper state from supplied values.
Interactive input implementation
pyre/pyre-interpreter/src/builtins.rs
input() now writes prompts, flushes streams, reads stdin, handles EOF, validates results, and strips one trailing newline.
Warning source arguments and wrapper binding
pyre/pyre-interpreter/src/module/_warnings/mod.rs, pyre/extra_tests/snippets/stdlib_warnings.py, pyre/pyre-macros/src/lib.rs
_warnings.warn accepts positional source, with coverage for source identity and arity errors; generated wrappers avoid duplicate positional validation for keyword-bound parameters.
sys helpers and constant layout
pyre/pyre-interpreter/src/module/sys/vm.rs
sys.getsizeof and related registrations are reorganized while the stdlib module-name set remains unchanged.

Process and platform APIs

Layer / File(s) Summary
POSIX exec and session APIs
pyre/pyre-interpreter/src/module/posix/interp_posix.rs
Adds execv, execve, and getsid, validates arguments and environments, converts OS failures, and removes HAVE_FEXECVE.
Faulthandler signal registration
pyre/pyre-interpreter/src/module/faulthandler/handler.rs
Wires faulthandler APIs to host signal handlers and lets crash helpers accept an optional release_gil argument.
Socket hostname IDNA fallback
pyre/pyre-interpreter/src/module/_socket/interp_socket.rs
Routes fallback hostname encoding through the type-level idna encoder.
POSIX supporting changes
pyre/pyre-interpreter/src/module/posix/interp_posix.rs
Applies formatting, fork-loop, structseq, registration, and explicit unsafe-scope updates without changing the described semantics.

Free-threaded attribute caches

Layer / File(s) Summary
LOAD_ATTR cache synchronization
pyre/pyre-interpreter/src/objspace/std/mapdict.rs
LOAD_ATTR cache publication and reads now coordinate code-cache locking with instance-map validation.
STORE_ATTR cache synchronization
pyre/pyre-interpreter/src/objspace/std/mapdict.rs
STORE_ATTR writes and transitions revalidate instance maps and fall back to general attribute setting when needed.

Interpreter execution guards

Layer / File(s) Summary
Frame-based tracing guards
pyre/pyre-interpreter/src/executioncontext.rs
Tracing fast paths now return early only when the frame pointer is null.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Launcher
  participant RuntimeFlags
  participant SysModule
  participant StandardStreams
  participant BuiltinsInput
  Launcher->>RuntimeFlags: parse -X, -W, -u, -b, and environment settings
  RuntimeFlags->>SysModule: publish argv, warnings, xoptions, and encoding state
  SysModule->>StandardStreams: create streams using live encoding and buffering policy
  BuiltinsInput->>StandardStreams: flush prompt streams and read stdin.readline
  StandardStreams-->>BuiltinsInput: return input line or EOF
Loading

Possibly related PRs

Poem

A rabbit hops through streams of light,
Prompts are flushed and flags take flight.
Maps hold tight through threads that race,
Warnings find their proper place.
Execs leap forth—what a bright delight!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is related, but it is broad and file-list oriented rather than describing the main behavioral change. Use a concise, specific title that names the primary user-facing change, such as launcher/sys flag propagation and stdio/input parity, instead of listing modules.
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch import

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.

@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown

🤖 Codex parity review

Static analysis of this diff vs the local RPython/PyPy sources (commit ba42511).
Updated: 2026-07-29T03:35:25.021Z

Files in the reviewed diff
pyre/extra_tests/snippets/stdlib_warnings.py
pyre/pyre-interpreter/src/builtins.rs
pyre/pyre-interpreter/src/executioncontext.rs
pyre/pyre-interpreter/src/importing.rs
pyre/pyre-interpreter/src/module/_io/textio.rs
pyre/pyre-interpreter/src/module/_socket/interp_socket.rs
pyre/pyre-interpreter/src/module/_warnings/mod.rs
pyre/pyre-interpreter/src/module/faulthandler/handler.rs
pyre/pyre-interpreter/src/module/posix/interp_posix.rs
pyre/pyre-interpreter/src/module/sys/vm.rs
pyre/pyre-interpreter/src/objspace/std/mapdict.rs
pyre/pyre-macros/src/lib.rs
pyre/pyrex/src/lib.rs

1. Regressions to PyPy parity introduced by this patch

None.

2. Other mismatches introduced by this patch

  • pyre/pyrex/src/lib.rs:238 ↔ pypy/interpreter/app_main.py:751-752 — launcher finalization records -u but never reads PYTHONUNBUFFERED; PyPy enables unbuffered stdout/stderr when that environment variable is non-empty.

  • pyre/pyre-interpreter/src/module/sys/vm.rs:1937 ↔ pypy/interpreter/app_main.py:401-437 — stdio defaults unconditionally to "utf-8"/"strict". PyPy uses the locale codec when no encoding is supplied and uses surrogateescape in C/POSIX locales or UTF-8 mode unless an explicit encoding/error policy overrides it.

  • pyre/pyre-interpreter/src/module/sys/vm.rs:1945 ↔ pypy/interpreter/app_main.py:472-481PYTHONIOENCODING=latin1 remains exposed as "latin1"; PyPy normalizes a supplied codec name through _codecs.lookup() (for example, to "iso8859-1").

  • pyre/pyre-interpreter/src/module/sys/vm.rs:2026 ↔ pypy/interpreter/app_main.py:465-470 — every _io.open() failure is converted into a TextIOWrapper whose .buffer is None. PyPy returns None only for EBADF and propagates every other OSError; it does not create a malformed stream.

  • pyre/pyre-interpreter/src/module/sys/vm.rs:2045 ↔ pypy/interpreter/app_main.py:483-497line_buffering is set only for -u or stderr. PyPy also enables it when the underlying raw descriptor is a TTY, so sys.stdout.line_buffering is wrong on an interactive terminal.

  • pyre/pyre-interpreter/src/module/sys/vm.rs:627 ↔ pypy/interpreter/app_main.py:785-789_xoptions includes command-line -X values only; PyPy additionally sets sys._xoptions["no_debug_ranges"] = True when PYTHONNODEBUGRANGES is set and environment processing is enabled.

  • pyre/pyre-interpreter/src/builtins.rs:2906 ↔ pypy/module/__builtin__/app_io.py:33-44input() converts every exception while fetching sys.stdin/stdout/stderr into RuntimeError("input: lost sys.*"). PyPy converts only AttributeError; other descriptor/property exceptions propagate unchanged.

  • pyre/pyre-interpreter/src/builtins.rs:2922 ↔ pypy/module/__builtin__/app_io.py:8-15 — prompt output requires stdout.flush() to exist. PyPy writes the prompt, then ignores a missing flush attribute; only an existing flush method’s exception propagates.

  • pyre/pyre-interpreter/src/builtins.rs:2915 ↔ pypy/module/__builtin__/app_io.py:48-53 — the new input() implementation omits PyPy’s sys.__raw_input__ TTY hook, so readline-enabled interactive input takes the ordinary stdin.readline() path.

3. Pre-existing mismatches (already present before this patch)

  • pyre/pyre-interpreter/src/module/sys/vm.rs:1825 ↔ pypy/module/__builtin__/app_io.py:32,52sys.audit and sys.addaudithook are no-ops, so PyPy’s builtins.input and builtins.input/result audit events cannot be observed. This infrastructure was already a no-op before the new input() port.

  • pyre/pyre-interpreter/src/module/faulthandler/handler.rs:125 ↔ pypy/module/faulthandler/handler.py:158-173dump_traceback() writes a fixed placeholder and dump_traceback_later() is a no-op, whereas PyPy delegates to Handler to emit real current/all-thread tracebacks and schedule/cancel delayed dumps. The patch only reformatted or relocated this existing behavior.

4. Structural adaptations

  • pyre/pyre-interpreter/src/objspace/std/mapdict.rs:1214 ↔ pypy/objspace/std/mapdict.py:1480-1490 — PyPy relies on its GIL for the cache/map observation; Pyre snapshots cache entries under a code lock and observes instance state under a separate lock. This is a necessary free-threading adaptation, and the patch preserves the PyPy fast/slow-path decisions while avoiding nested lock cycles.

  • pyre/pyre-interpreter/src/module/_warnings/mod.rs:812 ↔ pypy/module/_warnings/interp_warnings.py:352-356skip_file_prefixes is a CPython 3.14-only keyword-only extension; PyPy’s local warn() has no equivalent parameter. The positional source correction itself matches PyPy.

  • pyre/pyre-interpreter/src/importing.rs:1660 ↔ pypy/interpreter/app_main.py:697-700 — PyPy retains launch options in an interpreter-local Python options dictionary, while Pyre stores process launch state in atomics and mutex-protected Rust vectors. This is a Rust ownership/thread-visibility adaptation; the raw -X/warning option storage shape remains ordered lists before public sys objects are built.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@pyre/pyre-interpreter/src/module/faulthandler/handler.rs`:
- Around line 62-82: Update faulthandler.enable and the
faulthandler_signal_handler path so the validated descriptor from
faulthandler_extract_fd is stored in a global atomic before enabling fatal
handlers, and have the signal handler read that value instead of hardcoding fd
2. Preserve the existing enable success/error behavior and ensure the descriptor
is wired to enable_fatal_handlers for file-specific dumps.

In `@pyre/pyre-interpreter/src/module/sys/vm.rs`:
- Around line 1981-2011: Update stdio_stdin_readline to apply universal-newline
translation to the bytes returned by buffer.readline before decoding, converting
CRLF and standalone CR line endings to LF. Preserve the existing argument
validation, bytes validation, encoding/error selection, and final decoded-string
return behavior.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d21160e5-954e-4039-95c7-cc088a06f9cd

📥 Commits

Reviewing files that changed from the base of the PR and between 31a4f20 and 1b2ea1b.

📒 Files selected for processing (13)
  • pyre/extra_tests/snippets/stdlib_warnings.py
  • pyre/pyre-interpreter/src/builtins.rs
  • pyre/pyre-interpreter/src/executioncontext.rs
  • pyre/pyre-interpreter/src/importing.rs
  • pyre/pyre-interpreter/src/module/_io/textio.rs
  • pyre/pyre-interpreter/src/module/_socket/interp_socket.rs
  • pyre/pyre-interpreter/src/module/_warnings/mod.rs
  • pyre/pyre-interpreter/src/module/faulthandler/handler.rs
  • pyre/pyre-interpreter/src/module/posix/interp_posix.rs
  • pyre/pyre-interpreter/src/module/sys/vm.rs
  • pyre/pyre-interpreter/src/objspace/std/mapdict.rs
  • pyre/pyre-macros/src/lib.rs
  • pyre/pyrex/src/lib.rs

Comment on lines +62 to +82
// `handler.py:141-145 enable` — file=None, all_threads=True.
let _fd =
faulthandler_extract_fd(args.first().copied().unwrap_or(pyre_object::PY_NULL))?;
#[cfg(all(unix, feature = "host_env"))]
{
let ok = rustpython_host_env::faulthandler::enable_fatal_handlers(
faulthandler_signal_handler,
libc::SA_NODEFER | libc::SA_ONSTACK,
);
if ok {
FAULTHANDLER_ENABLED.store(true, std::sync::atomic::Ordering::Relaxed);
return Ok(pyre_object::w_none());
}
return Err(crate::PyError::runtime_error(
"faulthandler.enable: sigaction failed",
));
}
return Err(crate::PyError::runtime_error(
"faulthandler.enable: sigaction failed",
));
}
#[cfg(not(all(unix, feature = "host_env")))]
Err(crate::PyError::not_implemented(
"faulthandler.enable requires host_env feature",
))
#[cfg(not(all(unix, feature = "host_env")))]
Err(crate::PyError::not_implemented(
"faulthandler.enable requires host_env feature",
))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

enable() validates file but never wires its fd to the actual crash handler — dumps always go to fd 2.

_fd is computed via faulthandler_extract_fd(...) purely to validate the file argument, then discarded; enable_fatal_handlers only takes the handler function pointer and sigaction flags, and faulthandler_signal_handler (lines 21-29) hardcodes write_fd(2, ...). So faulthandler.enable(file=some_other_fd) silently writes fatal-error dumps to stderr instead of the requested fd — contradicting both CPython/PyPy semantics and this review cohort's own description ("faulthandler.enable and register wire validated file descriptors ... to host signal handlers"), which is true for register() (lines 196-202 pass fd through) but not for enable().

Since the signal handler is a fixed extern "C" fn and can't capture state, route the validated fd through a global (mirroring the existing FAULTHANDLER_ENABLED atomic) that the handler reads at crash time.

🛠️ Suggested fix
 static FAULTHANDLER_ENABLED: std::sync::atomic::AtomicBool =
     std::sync::atomic::AtomicBool::new(false);
+static FAULTHANDLER_FD: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(2);

 extern "C" fn faulthandler_signal_handler(signum: libc::c_int) {
     let name =
         rustpython_host_env::faulthandler::fatal_signal_name(signum).unwrap_or("unknown signal");
     let msg = format!("Fatal Python error: {name}\n");
-    rustpython_host_env::faulthandler::write_fd(2, msg.as_bytes());
+    let fd = FAULTHANDLER_FD.load(std::sync::atomic::Ordering::Relaxed);
+    rustpython_host_env::faulthandler::write_fd(fd, msg.as_bytes());
     rustpython_host_env::faulthandler::signal_default_and_raise(signum);
 }
-                let _fd =
+                let fd =
                     faulthandler_extract_fd(args.first().copied().unwrap_or(pyre_object::PY_NULL))?;
                 #[cfg(all(unix, feature = "host_env"))]
                 {
+                    FAULTHANDLER_FD.store(fd, std::sync::atomic::Ordering::Relaxed);
                     let ok = rustpython_host_env::faulthandler::enable_fatal_handlers(
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// `handler.py:141-145 enable` — file=None, all_threads=True.
let _fd =
faulthandler_extract_fd(args.first().copied().unwrap_or(pyre_object::PY_NULL))?;
#[cfg(all(unix, feature = "host_env"))]
{
let ok = rustpython_host_env::faulthandler::enable_fatal_handlers(
faulthandler_signal_handler,
libc::SA_NODEFER | libc::SA_ONSTACK,
);
if ok {
FAULTHANDLER_ENABLED.store(true, std::sync::atomic::Ordering::Relaxed);
return Ok(pyre_object::w_none());
}
return Err(crate::PyError::runtime_error(
"faulthandler.enable: sigaction failed",
));
}
return Err(crate::PyError::runtime_error(
"faulthandler.enable: sigaction failed",
));
}
#[cfg(not(all(unix, feature = "host_env")))]
Err(crate::PyError::not_implemented(
"faulthandler.enable requires host_env feature",
))
#[cfg(not(all(unix, feature = "host_env")))]
Err(crate::PyError::not_implemented(
"faulthandler.enable requires host_env feature",
))
static FAULTHANDLER_ENABLED: std::sync::atomic::AtomicBool =
std::sync::atomic::AtomicBool::new(false);
static FAULTHANDLER_FD: std::sync::atomic::AtomicI32 =
std::sync::atomic::AtomicI32::new(2);
extern "C" fn faulthandler_signal_handler(signum: libc::c_int) {
let name =
rustpython_host_env::faulthandler::fatal_signal_name(signum).unwrap_or("unknown signal");
let msg = format!("Fatal Python error: {name}\n");
let fd = FAULTHANDLER_FD.load(std::sync::atomic::Ordering::Relaxed);
rustpython_host_env::faulthandler::write_fd(fd, msg.as_bytes());
rustpython_host_env::faulthandler::signal_default_and_raise(signum);
}
let fd =
faulthandler_extract_fd(args.first().copied().unwrap_or(pyre_object::PY_NULL))?;
#[cfg(all(unix, feature = "host_env"))]
{
FAULTHANDLER_FD.store(fd, std::sync::atomic::Ordering::Relaxed);
let ok = rustpython_host_env::faulthandler::enable_fatal_handlers(
faulthandler_signal_handler,
libc::SA_NODEFER | libc::SA_ONSTACK,
);
if ok {
FAULTHANDLER_ENABLED.store(true, std::sync::atomic::Ordering::Relaxed);
return Ok(pyre_object::w_none());
}
return Err(crate::PyError::runtime_error(
"faulthandler.enable: sigaction failed",
));
}
#[cfg(not(all(unix, feature = "host_env")))]
Err(crate::PyError::not_implemented(
"faulthandler.enable requires host_env feature",
))
🤖 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 `@pyre/pyre-interpreter/src/module/faulthandler/handler.rs` around lines 62 -
82, Update faulthandler.enable and the faulthandler_signal_handler path so the
validated descriptor from faulthandler_extract_fd is stored in a global atomic
before enabling fatal handlers, and have the signal handler read that value
instead of hardcoding fd 2. Preserve the existing enable success/error behavior
and ensure the descriptor is wired to enable_fatal_handlers for file-specific
dumps.

Comment on lines +1981 to +2011
fn stdio_stdin_readline(args: &[PyObjectRef]) -> crate::PyResult {
if args.len() > 1 {
return Err(crate::PyError::type_error(format!(
"readline() takes at most one argument ({} given)",
args.len()
)));
}
let sys = crate::importing::get_sys_module("sys")
.ok_or_else(|| crate::PyError::runtime_error("lost sys.stdin"))?;
let stdin = crate::baseobjspace::getattr_str(sys, "stdin")?;
let buffer = crate::baseobjspace::getattr_str(stdin, "buffer")?;
let bytes = crate::baseobjspace::call_method(buffer, "readline", args);
if bytes.is_null() {
return Err(crate::call::take_call_error()
.unwrap_or_else(|| crate::PyError::runtime_error("readline failed")));
}
if !unsafe { pyre_object::is_bytes(bytes) } {
return Err(crate::PyError::type_error(
"underlying readline() should have returned a bytes-like object",
));
}
let _roots = pyre_object::gc_roots::push_roots();
pyre_object::gc_roots::pin_root(bytes);
let bytes_slot = pyre_object::gc_roots::shadow_stack_len() - 1;
let (encoding, errors) = live_stdio_encoding_errors("stdin", "strict");
crate::typedef::bytes_method_decode(&[
pyre_object::gc_roots::shadow_stack_get(bytes_slot),
w_str_new(&encoding),
w_str_new(&errors),
])
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## Locate files"
fd -a 'vm.rs|builtins.rs' . | sed 's#^\./##'

echo "## Outline relevant files"
ast-grep outline pyre/pyre-interpreter/src/module/sys/vm.rs --view expanded 2>/dev/null | sed -n '1,220p' || true
echo "## vm.rs around stdio_stdin_readline"
sed -n '1940,2030p' pyre/pyre-interpreter/src/module/sys/vm.rs
echo "## vm.rs around line 2116"
sed -n '2080,2140p' pyre/pyre-interpreter/src/module/sys/vm.rs

echo "## relevant builtins input usages"
rg -n "builtin_input|input\(|remove trailing|strip|trailing" pyre/pyre-interpreter/src module tests -g '*.rs' | sed -n '1,220p'

echo "## sys stdin allocation references"
rg -n "readuniversal|readtranslate|allocate_stdio|sys\.stdin|stdin|TextIOWrapper|Buffered|BufferedReader" pyre/pyre-interpreter/src/module pyre -g '*.rs' | sed -n '1,260p'

Repository: youknowone/pyre

Length of output: 33421


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## vm.rs stdin allocation lines 2013-2085"
sed-2140p pyre/pyre-interpreter/src/module/sys/vm.rs -n '2013,2085'

echo "## builtin_input"
sed-2960p pyre/pyre-interpreter/src/builtins.rs -n '2897,2950'

echo "## stdio encoding / allocation config references"
rg -n "readuniversal|readtranslate|TextIOWrapper|decode\(\)|decode.*\(|sys\.stdin|sys.stdin|allocate_stdio|create_stdio|make_std_stream|stdio_stdin_readline" pyre/pyre-interpreter/src pyre -g '*.rs' -g '*.py' | sed -n '1,220p'

echo "## Read/write TextIOWrapper/stdio sources in repo"
git ls-files | rg -i 'TextIOWrapper|textio|stdio|buffer|file|io|interp_(stdio|io)|stream' || true

Repository: youknowone/pyre

Length of output: 242


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## vm.rs stdin allocation lines 2013-2085"
sed -n '2013,2085p' pyre/pyre-interpreter/src/module/sys/vm.rs

echo "## builtin_input"
sed -n '2897,2950p' pyre/pyre-interpreter/src/builtins.rs

echo "## stdio encoding / allocation config references"
rg -n "readuniversal|readtranslate|TextIOWrapper|decode\(\)|decode.*\(|sys\.stdin|sys.stdin|allocate_stdio|create_stdio|make_std_stream|stdio_stdin_readline" pyre/pyre-interpreter/src pyre -g '*.rs' -g '*.py' | sed -n '1,220p'

echo "## Read/write TextIOWrapper/stdio sources in repo"
git ls-files | rg -i 'TextIOWrapper|textio|stdio|buffer|file|io|interp_(stdio|io)|stream' || true

Repository: youknowone/pyre

Length of output: 50371


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## locate allocate_stdio definitions"
rg -n "allocate_stdio|alloc_stdio|readuniversal|readtranslate|TextIOWrapper" pyre/pyre-interpreter/src pyre/pyre-js/src pyre/pyre-wasm/src pyre/extras pyrex/pyre -g '*.rs' -g '*.py' --max-count 100

echo "## locate _io module definition area"
fd -a '_io' pyre/pyre-interpreter/src pyre/pyre-js/src pyre/pyre-wasm/src pyrex/pyre -t d | sed -n '1,40p'

echo "## read Rust allocate_stdio implementation around hits"
for f in $(rg -l "allocate_stdio" pyre/pyre-interpreter/src pyre/pyre-js/src pyre/pyre-wasm/src pyrex/pyre -g '*.rs'); do
  echo "### $f"
  rg -n -C 20 "fn allocate_stdio|allocate_stdio\\(" "$f" | sed -n '1,260p'
done

echo "## read CPython/Pypo relevant io.py/config snippets if present"
rg -n "readuniversal|readtranslate|stdlib_newline|universal|newline" lib-python/3/io.py lib_pypy/_io pypy/module/_io lib-python/3/test/test_io.py lib-python/3/test/test_textio.py -g '*.py' | sed -n '1,220p'

Repository: youknowone/pyre

Length of output: 6461


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## allocate_stdio implementation"
sed -n '803,845p' pyre/pyre-interpreter/src/module/_io/textio.rs

echo "## readuniversal/readtranslate decoding path"
sed -n '494,525p' pyre/pyre-interpreter/src/module/_io/textio.rs
sed -n '645,665p' pyre/pyre-interpreter/src/module/_io/textio.rs

echo "## TextIOWrapper method registration and decode helper"
sed -n '114,160p' pyre/pyre-interpreter/src/module/_io/textio.rs
rg -n "decode_reader|decode_bytes|readtranslate|readuniversal" pyre/pyre-interpreter/src/module/_io/textio.rs | sed -n '1,180p'

Repository: youknowone/pyre

Length of output: 5843


🌐 Web query:

CPython io.TextIOWrapper readline CRLF universal newlines translate \r\n to \n

💡 Result:

In CPython's io.TextIOWrapper, the readline method handles CRLF and other line endings based on the newline parameter passed to the constructor [1][2]. When the newline parameter is set to None (the default behavior for open), universal newlines mode is enabled [1][3]. In this mode, the TextIOWrapper automatically recognizes all common line endings—'\n' (LF), '\r' (CR), and '\r\n' (CRLF)—and translates them into a single '\n' (LF) character before returning the line to the caller [1][4]. The internal state machine for readline specifically handles the translation [5]. When it encounters a carriage return ('\r'), it peeks at the next character to determine if it is a line feed ('\n') [5]. If a CRLF sequence is detected, it is consumed and translated as a single '\n' [1][5]. If the newline parameter is set to a specific value (such as '', '\n', '\r', or '\r\n'), the behavior changes [1][2]: 1. If newline is '', universal newlines mode remains enabled, but the line endings are not translated; they are returned to the caller exactly as they appear in the source [1][4]. 2. If newline is set to a specific string (e.g., '\r\n'), only that specific sequence is recognized as a line terminator, and it is returned to the caller untranslated [1][2]. The newline translation applies to all reading methods, including read, readline, and readlines [6]. Additionally, when universal newlines mode is enabled (newline=None), the TextIOWrapper object maintains a newlines attribute that tracks the types of line endings encountered in the stream (e.g., None, '\n', '\r', '\r\n', or a tuple of these) [6][2][5].

Citations:


Preserve universal-newline translation in stdio_stdin_readline.

sys.stdin.readline() currently calls buffer.readline(), which returns decoded raw bytes split on \n. For CRLF input, this decoded line retains \r; input() then only strips a trailing '\n', so the carriage return is returned to the caller. Since the stdin TextIOWrapper is configured with readuniversal: true, readtranslate: true, apply the same CRLF/CR to LF translation here before returning the string.

🤖 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 `@pyre/pyre-interpreter/src/module/sys/vm.rs` around lines 1981 - 2011, Update
stdio_stdin_readline to apply universal-newline translation to the bytes
returned by buffer.readline before decoding, converting CRLF and standalone CR
line endings to LF. Preserve the existing argument validation, bytes validation,
encoding/error selection, and final decoded-string return behavior.

…e std streams

The launcher parsed `-b`, `-u`, `-X`, `-W` and PYTHONIOENCODING without
handing them on. `set_runtime_flags` now also takes `bytes_warning`,
`unbuffered`, the raw `-X` and `-W` strings and the stdio encoding.
`app_main.py` keeps the `-X` strings in `options['_xoptions']` as a list
until sys initialization builds the public dict, so they are stored as lists
here too; `sys._xoptions`, `sys.warnoptions` and `sys.orig_argv` are built
from them.

_io: `allocate_stdio` takes the buffer object plus `line_buffering` and
`write_through` instead of defaulting them to none/false.

builtins: `input` was a stub returning "". It resolves sys.stdin, sys.stdout
and sys.stderr, flushes stderr, writes and flushes the prompt through
stdout, then reads one line from stdin (`app_io.py:input`), so redirected
streams are used; end of input raises EOFError.

Assisted-by: Claude
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