Skip to content

Commit 71af7b0

Browse files
committed
Improve sys module, stdio, and traceback formatting
- Register latin-1/iso8859-1 codec aliases early for stdio bootstrap - Fix stdio_errors: use "strict" when encoding is explicitly set - Use traceback._print_exception_bltin in excepthook - Cache source in linecache for <string> tracebacks - Add StatelessIncrementalEncoder/Decoder to _io - Move sys.flags thread_inherit_context/context_aware_warnings to getset properties - Skip exception slot sync when no exceptions are active to avoid O(depth) topmost_exception() walk on every function call - Remove 5 expectedFailure markers in test_sys
1 parent 822de80 commit 71af7b0

File tree

11 files changed

+254
-107
lines changed

11 files changed

+254
-107
lines changed

.cspell.dict/cpython.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ badsyntax
1111
baseinfo
1212
basetype
1313
binop
14+
bltin
1415
boolop
1516
BUILDSTDLIB
1617
bxor

Lib/test/test_sys.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,6 @@ class SysModuleTest(unittest.TestCase):
209209
def tearDown(self):
210210
test.support.reap_children()
211211

212-
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: (42,) != 42
213212
def test_exit(self):
214213
# call with two arguments
215214
self.assertRaises(TypeError, sys.exit, 42, 42)
@@ -850,7 +849,6 @@ def test_subinterp_intern_singleton(self):
850849
'''))
851850
self.assertTrue(sys._is_interned(s))
852851

853-
@unittest.expectedFailure # TODO: RUSTPYTHON; needs update for context_aware_warnings
854852
def test_sys_flags(self):
855853
self.assertTrue(sys.flags)
856854
attrs = ("debug",
@@ -1055,12 +1053,10 @@ def check_locale_surrogateescape(self, locale):
10551053
'stdout: surrogateescape\n'
10561054
'stderr: backslashreplace\n')
10571055

1058-
@unittest.expectedFailure # TODO: RUSTPYTHON; stderr: backslashreplace
10591056
@support.requires_subprocess()
10601057
def test_c_locale_surrogateescape(self):
10611058
self.check_locale_surrogateescape('C')
10621059

1063-
@unittest.expectedFailure # TODO: RUSTPYTHON; stderr: backslashreplace
10641060
@support.requires_subprocess()
10651061
def test_posix_locale_surrogateescape(self):
10661062
self.check_locale_surrogateescape('POSIX')
@@ -1224,7 +1220,6 @@ def test_getandroidapilevel(self):
12241220
self.assertIsInstance(level, int)
12251221
self.assertGreater(level, 0)
12261222

1227-
@unittest.expectedFailure # TODO: RUSTPYTHON; b'ZeroDivisionError: division by zero']
12281223
@force_not_colorized
12291224
@support.requires_subprocess()
12301225
def test_sys_tracebacklimit(self):

crates/stdlib/src/faulthandler.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,8 @@ mod decl {
9696
all_threads: AtomicBool::new(true),
9797
};
9898

99-
/// Arc<Mutex<Vec<FrameRef>>> - shared frame slot for a thread
10099
#[cfg(feature = "threading")]
101-
type ThreadFrameSlot = Arc<parking_lot::Mutex<Vec<crate::vm::frame::FrameRef>>>;
100+
type ThreadFrameSlot = Arc<rustpython_vm::vm::thread::ThreadSlot>;
102101

103102
// Watchdog thread state for dump_traceback_later
104103
struct WatchdogState {
@@ -410,7 +409,7 @@ mod decl {
410409
if tid == current_tid {
411410
continue;
412411
}
413-
let frames_guard = slot.lock();
412+
let frames_guard = slot.frames.lock();
414413
dump_traceback_thread_frames(fd, tid, false, &frames_guard);
415414
puts(fd, "\n");
416415
}
@@ -870,7 +869,7 @@ mod decl {
870869
#[cfg(feature = "threading")]
871870
{
872871
for (tid, slot) in &thread_frame_slots {
873-
let frames = slot.lock();
872+
let frames = slot.frames.lock();
874873
dump_traceback_thread_frames(fd, *tid, false, &frames);
875874
}
876875
}

crates/vm/src/builtins/frame.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ impl Py<Frame> {
215215
let registry = vm.state.thread_frames.lock();
216216
for slot in registry.values() {
217217
if let Some(frame) = slot
218+
.frames
218219
.lock()
219220
.iter()
220221
.find(|f| {

crates/vm/src/stdlib/io.rs

Lines changed: 120 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2605,6 +2605,92 @@ mod _io {
26052605
}
26062606
}
26072607

2608+
#[pyclass(module = "_io", name, no_attr)]
2609+
#[derive(Debug, PyPayload)]
2610+
struct StatelessIncrementalEncoder {
2611+
encode: PyObjectRef,
2612+
errors: Option<PyStrRef>,
2613+
name: Option<PyStrRef>,
2614+
}
2615+
2616+
#[pyclass]
2617+
impl StatelessIncrementalEncoder {
2618+
#[pymethod]
2619+
fn encode(
2620+
&self,
2621+
input: PyObjectRef,
2622+
_final: OptionalArg<bool>,
2623+
vm: &VirtualMachine,
2624+
) -> PyResult {
2625+
let mut args: Vec<PyObjectRef> = vec![input];
2626+
if let Some(errors) = &self.errors {
2627+
args.push(errors.to_owned().into());
2628+
}
2629+
let res = self.encode.call(args, vm)?;
2630+
let tuple: PyTupleRef = res.try_into_value(vm)?;
2631+
if tuple.len() != 2 {
2632+
return Err(vm.new_type_error("encoder must return a tuple (object, integer)"));
2633+
}
2634+
Ok(tuple[0].clone())
2635+
}
2636+
2637+
#[pymethod]
2638+
fn reset(&self) {}
2639+
2640+
#[pymethod]
2641+
fn setstate(&self, _state: PyObjectRef) {}
2642+
2643+
#[pymethod]
2644+
fn getstate(&self, vm: &VirtualMachine) -> PyObjectRef {
2645+
vm.ctx.new_int(0).into()
2646+
}
2647+
2648+
#[pygetset]
2649+
fn name(&self) -> Option<PyStrRef> {
2650+
self.name.clone()
2651+
}
2652+
}
2653+
2654+
#[pyclass(module = "_io", name, no_attr)]
2655+
#[derive(Debug, PyPayload)]
2656+
struct StatelessIncrementalDecoder {
2657+
decode: PyObjectRef,
2658+
errors: Option<PyStrRef>,
2659+
}
2660+
2661+
#[pyclass]
2662+
impl StatelessIncrementalDecoder {
2663+
#[pymethod]
2664+
fn decode(
2665+
&self,
2666+
input: PyObjectRef,
2667+
_final: OptionalArg<bool>,
2668+
vm: &VirtualMachine,
2669+
) -> PyResult {
2670+
let mut args: Vec<PyObjectRef> = vec![input];
2671+
if let Some(errors) = &self.errors {
2672+
args.push(errors.to_owned().into());
2673+
}
2674+
let res = self.decode.call(args, vm)?;
2675+
let tuple: PyTupleRef = res.try_into_value(vm)?;
2676+
if tuple.len() != 2 {
2677+
return Err(vm.new_type_error("decoder must return a tuple (object, integer)"));
2678+
}
2679+
Ok(tuple[0].clone())
2680+
}
2681+
2682+
#[pymethod]
2683+
fn getstate(&self, vm: &VirtualMachine) -> (PyBytesRef, u64) {
2684+
(vm.ctx.empty_bytes.to_owned(), 0)
2685+
}
2686+
2687+
#[pymethod]
2688+
fn setstate(&self, _state: PyTupleRef, _vm: &VirtualMachine) {}
2689+
2690+
#[pymethod]
2691+
fn reset(&self) {}
2692+
}
2693+
26082694
#[pyattr]
26092695
#[pyclass(name = "TextIOWrapper", base = _TextIOBase)]
26102696
#[derive(Debug, Default)]
@@ -2828,7 +2914,25 @@ mod _io {
28282914

28292915
let encoder = if vm.call_method(buffer, "writable", ())?.try_to_bool(vm)? {
28302916
let incremental_encoder =
2831-
codec.get_incremental_encoder(Some(errors.to_owned()), vm)?;
2917+
match codec.get_incremental_encoder(Some(errors.to_owned()), vm) {
2918+
Ok(encoder) => encoder,
2919+
Err(err)
2920+
if err.fast_isinstance(vm.ctx.exceptions.type_error)
2921+
|| err.fast_isinstance(vm.ctx.exceptions.attribute_error) =>
2922+
{
2923+
let name = vm
2924+
.get_attribute_opt(codec.as_tuple().to_owned().into(), "name")?
2925+
.and_then(|obj| obj.downcast::<PyStr>().ok());
2926+
StatelessIncrementalEncoder {
2927+
encode: codec.get_encode_func().to_owned(),
2928+
errors: Some(errors.to_owned()),
2929+
name,
2930+
}
2931+
.into_ref(&vm.ctx)
2932+
.into()
2933+
}
2934+
Err(err) => return Err(err),
2935+
};
28322936
let encoding_name = vm.get_attribute_opt(incremental_encoder.clone(), "name")?;
28332937
let encode_func = encoding_name.and_then(|name| {
28342938
let name = name.downcast_ref::<PyStr>()?;
@@ -2843,7 +2947,21 @@ mod _io {
28432947
};
28442948

28452949
let decoder = if vm.call_method(buffer, "readable", ())?.try_to_bool(vm)? {
2846-
let decoder = codec.get_incremental_decoder(Some(errors.to_owned()), vm)?;
2950+
let decoder = match codec.get_incremental_decoder(Some(errors.to_owned()), vm) {
2951+
Ok(decoder) => decoder,
2952+
Err(err)
2953+
if err.fast_isinstance(vm.ctx.exceptions.type_error)
2954+
|| err.fast_isinstance(vm.ctx.exceptions.attribute_error) =>
2955+
{
2956+
StatelessIncrementalDecoder {
2957+
decode: codec.get_decode_func().to_owned(),
2958+
errors: Some(errors.to_owned()),
2959+
}
2960+
.into_ref(&vm.ctx)
2961+
.into()
2962+
}
2963+
Err(err) => return Err(err),
2964+
};
28472965
if let Newlines::Universal | Newlines::Passthrough = newline {
28482966
let args = IncrementalNewlineDecoderArgs {
28492967
decoder,

crates/vm/src/stdlib/sys.rs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -841,6 +841,15 @@ mod sys {
841841
Ok(exc) => {
842842
// Try Python traceback module first for richer output
843843
// (enables features like keyword typo suggestions in SyntaxError)
844+
if let Ok(tb_mod) = vm.import("traceback", 0)
845+
&& let Ok(print_exc_builtin) = tb_mod.get_attr("_print_exception_bltin", vm)
846+
&& print_exc_builtin
847+
.call((exc.as_object().to_owned(),), vm)
848+
.is_ok()
849+
{
850+
return Ok(());
851+
}
852+
// Fallback to public traceback.print_exception API
844853
if let Ok(tb_mod) = vm.import("traceback", 0)
845854
&& let Ok(print_exc) = tb_mod.get_attr("print_exception", vm)
846855
&& print_exc.call((exc.as_object().to_owned(),), vm).is_ok()
@@ -1558,10 +1567,6 @@ mod sys {
15581567
safe_path: bool,
15591568
/// -X warn_default_encoding, PYTHONWARNDEFAULTENCODING
15601569
warn_default_encoding: u8,
1561-
/// -X thread_inherit_context, whether new threads inherit context from parent
1562-
thread_inherit_context: bool,
1563-
/// -X context_aware_warnings, whether warnings are context aware
1564-
context_aware_warnings: bool,
15651570
}
15661571

15671572
impl FlagsData {
@@ -1585,8 +1590,6 @@ mod sys {
15851590
int_max_str_digits: settings.int_max_str_digits,
15861591
safe_path: settings.safe_path,
15871592
warn_default_encoding: settings.warn_default_encoding as u8,
1588-
thread_inherit_context: settings.thread_inherit_context,
1589-
context_aware_warnings: settings.context_aware_warnings,
15901593
}
15911594
}
15921595
}
@@ -1600,6 +1603,16 @@ mod sys {
16001603
fn slot_new(_cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult {
16011604
Err(vm.new_type_error("cannot create 'sys.flags' instances"))
16021605
}
1606+
1607+
#[pygetset]
1608+
fn context_aware_warnings(&self, vm: &VirtualMachine) -> bool {
1609+
vm.state.config.settings.context_aware_warnings
1610+
}
1611+
1612+
#[pygetset]
1613+
fn thread_inherit_context(&self, vm: &VirtualMachine) -> bool {
1614+
vm.state.config.settings.thread_inherit_context
1615+
}
16031616
}
16041617

16051618
#[cfg(feature = "threading")]

crates/vm/src/stdlib/thread.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -891,7 +891,7 @@ pub(crate) mod _thread {
891891
let registry = vm.state.thread_frames.lock();
892892
registry
893893
.iter()
894-
.filter_map(|(id, slot)| slot.lock().last().cloned().map(|f| (*id, f)))
894+
.filter_map(|(id, slot)| slot.frames.lock().last().cloned().map(|f| (*id, f)))
895895
.collect()
896896
}
897897

crates/vm/src/vm/interpreter.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,6 @@ where
121121
#[cfg(feature = "threading")]
122122
thread_frames: parking_lot::Mutex::new(std::collections::HashMap::new()),
123123
#[cfg(feature = "threading")]
124-
thread_exceptions: parking_lot::Mutex::new(std::collections::HashMap::new()),
125-
#[cfg(feature = "threading")]
126124
thread_handles: parking_lot::Mutex::new(Vec::new()),
127125
#[cfg(feature = "threading")]
128126
shutdown_handles: parking_lot::Mutex::new(Vec::new()),

0 commit comments

Comments
 (0)