Skip to content

Commit ba8c0d9

Browse files
committed
Use sys.excepthook when exceptions bubble to the top level
1 parent 3d5ea1b commit ba8c0d9

File tree

11 files changed

+115
-35
lines changed

11 files changed

+115
-35
lines changed

examples/freeze/main.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ fn main() -> vm::pyobject::PyResult<()> {
1919
);
2020

2121
if let Err(err) = res {
22-
vm::exceptions::print_exception(&vm, &err)
22+
vm::exceptions::print_exception(&vm, err);
2323
}
2424

2525
Ok(())

examples/mini_repl.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ fn main() -> vm::pyobject::PyResult<()> {
103103
}
104104
}
105105
Err(e) => {
106-
vm::exceptions::print_exception(&vm, &e);
106+
vm::exceptions::print_exception(&vm, e);
107107
}
108108
}
109109
}

src/main.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ fn main() {
7575
}
7676
}
7777
} else {
78-
print_exception(&vm, &err);
78+
print_exception(&vm, err);
7979
}
8080
process::exit(1);
8181
}

src/shell.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ pub fn run_shell(vm: &VirtualMachine, scope: Scope) -> PyResult<()> {
131131
repl.save_history(&repl_history_path).unwrap();
132132
return Err(exc);
133133
}
134-
print_exception(vm, &exc);
134+
print_exception(vm, exc);
135135
}
136136
}
137137
repl.save_history(&repl_history_path).unwrap();

vm/src/builtins.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ mod decl {
3535
use crate::scope::Scope;
3636
#[cfg(feature = "rustpython-parser")]
3737
use crate::stdlib::ast;
38+
use crate::sysmodule;
3839
use crate::vm::VirtualMachine;
3940
use rustpython_common::hash::PyHash;
4041

@@ -647,7 +648,7 @@ mod decl {
647648
pub fn print(objects: Args, options: PrintOptions, vm: &VirtualMachine) -> PyResult<()> {
648649
let file = match options.file {
649650
Some(f) => f,
650-
None => vm.get_attribute(vm.sys_module.clone(), "stdout")?,
651+
None => sysmodule::get_stdout(vm)?,
651652
};
652653
let write = |obj: PyStringRef| vm.call_method(&file, "write", vec![obj.into_object()]);
653654

vm/src/exceptions.rs

+44-11
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,20 @@ use crate::obj::objstr::{PyString, PyStringRef};
55
use crate::obj::objtraceback::PyTracebackRef;
66
use crate::obj::objtuple::{PyTuple, PyTupleRef};
77
use crate::obj::objtype::{self, PyClass, PyClassRef};
8-
use crate::py_serde;
8+
use crate::py_io::{self, Write};
99
use crate::pyobject::{
1010
PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject,
1111
TypeProtocol,
1212
};
1313
use crate::slots::PyTpFlags;
1414
use crate::types::create_type;
1515
use crate::VirtualMachine;
16+
use crate::{py_serde, sysmodule};
1617

1718
use itertools::Itertools;
1819
use std::fmt;
1920
use std::fs::File;
20-
use std::io::{self, BufRead, BufReader, Write};
21+
use std::io::{self, BufRead, BufReader};
2122

2223
use crossbeam_utils::atomic::AtomicCell;
2324

@@ -146,18 +147,35 @@ impl PyBaseException {
146147
}
147148
}
148149

149-
/// Print exception chain
150-
pub fn print_exception(vm: &VirtualMachine, exc: &PyBaseExceptionRef) {
151-
let stderr = io::stderr();
152-
let mut stderr = stderr.lock();
153-
let _ = write_exception(&mut stderr, vm, exc);
150+
/// Print exception chain by calling sys.excepthook
151+
pub fn print_exception(vm: &VirtualMachine, exc: PyBaseExceptionRef) {
152+
let write_fallback = |exc, errstr| {
153+
if let Ok(stderr) = sysmodule::get_stderr(vm) {
154+
let mut stderr = py_io::PyWriter(stderr, vm);
155+
// if this fails stderr might be closed -- ignore it
156+
let _ = writeln!(stderr, "{}", errstr);
157+
let _ = write_exception(&mut stderr, vm, exc);
158+
} else {
159+
eprintln!("{}\nlost sys.stderr", errstr);
160+
let _ = write_exception(&mut io::stderr(), vm, exc);
161+
}
162+
};
163+
if let Ok(excepthook) = vm.get_attribute(vm.sys_module.clone(), "excepthook") {
164+
let (exc_type, exc_val, exc_tb) = split(exc, vm);
165+
if let Err(eh_exc) = vm.invoke(&excepthook, vec![exc_type, exc_val, exc_tb]) {
166+
write_fallback(&eh_exc, "Error in sys.excepthook:");
167+
write_fallback(&eh_exc, "Original exception was:");
168+
}
169+
} else {
170+
write_fallback(&exc, "missing sys.excepthook");
171+
}
154172
}
155173

156174
pub fn write_exception<W: Write>(
157175
output: &mut W,
158176
vm: &VirtualMachine,
159177
exc: &PyBaseExceptionRef,
160-
) -> io::Result<()> {
178+
) -> Result<(), W::Error> {
161179
if let Some(cause) = exc.cause() {
162180
write_exception(output, vm, &cause)?;
163181
writeln!(
@@ -175,7 +193,11 @@ pub fn write_exception<W: Write>(
175193
write_exception_inner(output, vm, exc)
176194
}
177195

178-
fn print_source_line<W: Write>(output: &mut W, filename: &str, lineno: usize) -> io::Result<()> {
196+
fn print_source_line<W: Write>(
197+
output: &mut W,
198+
filename: &str,
199+
lineno: usize,
200+
) -> Result<(), W::Error> {
179201
// TODO: use io.open() method instead, when available, according to https://github.com/python/cpython/blob/master/Python/traceback.c#L393
180202
// TODO: support different encodings
181203
let file = match File::open(filename) {
@@ -198,7 +220,10 @@ fn print_source_line<W: Write>(output: &mut W, filename: &str, lineno: usize) ->
198220
}
199221

200222
/// Print exception occurrence location from traceback element
201-
fn write_traceback_entry<W: Write>(output: &mut W, tb_entry: &PyTracebackRef) -> io::Result<()> {
223+
fn write_traceback_entry<W: Write>(
224+
output: &mut W,
225+
tb_entry: &PyTracebackRef,
226+
) -> Result<(), W::Error> {
202227
let filename = tb_entry.frame.code.source_path.to_owned();
203228
writeln!(
204229
output,
@@ -215,7 +240,7 @@ pub fn write_exception_inner<W: Write>(
215240
output: &mut W,
216241
vm: &VirtualMachine,
217242
exc: &PyBaseExceptionRef,
218-
) -> io::Result<()> {
243+
) -> Result<(), W::Error> {
219244
if let Some(tb) = exc.traceback.read().clone() {
220245
writeln!(output, "Traceback (most recent call last):")?;
221246
for tb in tb.iter() {
@@ -341,6 +366,14 @@ impl ExceptionCtor {
341366
}
342367
}
343368

369+
pub fn split(
370+
exc: PyBaseExceptionRef,
371+
vm: &VirtualMachine,
372+
) -> (PyObjectRef, PyObjectRef, PyObjectRef) {
373+
let tb = exc.traceback().map_or(vm.get_none(), |tb| tb.into_object());
374+
(exc.class().into_object(), exc.into_object(), tb)
375+
}
376+
344377
/// Similar to PyErr_NormalizeException in CPython
345378
pub fn normalize(
346379
exc_type: PyObjectRef,

vm/src/frame.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -663,7 +663,9 @@ impl ExecutingFrame<'_> {
663663
bytecode::Instruction::PrintExpr => {
664664
let expr = self.pop_value();
665665

666-
let displayhook = vm.get_attribute(vm.sys_module.clone(), "displayhook")?;
666+
let displayhook = vm
667+
.get_attribute(vm.sys_module.clone(), "displayhook")
668+
.map_err(|_| vm.new_runtime_error("lost sys.displayhook".to_owned()))?;
667669
vm.invoke(&displayhook, vec![expr])?;
668670

669671
Ok(None)

vm/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ mod frozen;
6868
pub mod function;
6969
pub mod import;
7070
pub mod obj;
71+
mod py_io;
7172
pub mod py_serde;
7273
pub mod pyobject;
7374
mod pystr;

vm/src/py_io.rs

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
use crate::exceptions::PyBaseExceptionRef;
2+
use crate::pyobject::PyObjectRef;
3+
use crate::VirtualMachine;
4+
use std::{fmt, io};
5+
6+
pub trait Write {
7+
type Error;
8+
fn write_fmt(&mut self, args: fmt::Arguments) -> Result<(), Self::Error>;
9+
}
10+
11+
impl<W> Write for W
12+
where
13+
W: io::Write,
14+
{
15+
type Error = io::Error;
16+
fn write_fmt(&mut self, args: fmt::Arguments) -> io::Result<()> {
17+
<W as io::Write>::write_fmt(self, args)
18+
}
19+
}
20+
21+
pub struct PyWriter<'vm>(pub PyObjectRef, pub &'vm VirtualMachine);
22+
23+
impl Write for PyWriter<'_> {
24+
type Error = PyBaseExceptionRef;
25+
fn write_fmt(&mut self, args: fmt::Arguments) -> Result<(), Self::Error> {
26+
let PyWriter(obj, vm) = self;
27+
vm.call_method(obj, "write", vec![vm.new_str(args.to_string())])
28+
.map(drop)
29+
}
30+
}

vm/src/sysmodule.rs

+28-15
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
use std::{env, mem, path};
22

3-
use crate::builtins;
43
use crate::frame::FrameRef;
54
use crate::function::{Args, OptionalArg, PyFuncArgs};
65
use crate::obj::objstr::PyStringRef;
7-
use crate::pyobject::{
8-
IntoPyObject, ItemProtocol, PyClassImpl, PyContext, PyObjectRef, PyResult, TypeProtocol,
9-
};
10-
use crate::version;
6+
use crate::pyobject::{IntoPyObject, ItemProtocol, PyClassImpl, PyContext, PyObjectRef, PyResult};
117
use crate::vm::{PySettings, VirtualMachine};
8+
use crate::{builtins, exceptions, py_io, version};
129
use rustpython_common::hash::{PyHash, PyUHash};
1310
use rustpython_common::rc::PyRc;
1411

@@ -196,17 +193,11 @@ fn sys_intern(value: PyStringRef) -> PyStringRef {
196193
}
197194

198195
fn sys_exc_info(vm: &VirtualMachine) -> PyObjectRef {
199-
let exc_info = match vm.current_exception() {
200-
Some(exception) => vec![
201-
exception.class().into_object(),
202-
exception.clone().into_object(),
203-
exception
204-
.traceback()
205-
.map_or(vm.get_none(), |tb| tb.into_object()),
206-
],
207-
None => vec![vm.get_none(), vm.get_none(), vm.get_none()],
196+
let (ty, val, tb) = match vm.current_exception() {
197+
Some(exception) => exceptions::split(exception, vm),
198+
None => (vm.get_none(), vm.get_none(), vm.get_none()),
208199
};
209-
vm.ctx.new_tuple(exc_info)
200+
vm.ctx.new_tuple(vec![ty, val, tb])
210201
}
211202

212203
fn sys_git_info(vm: &VirtualMachine) -> PyObjectRef {
@@ -312,6 +303,26 @@ fn sys_getwindowsversion(vm: &VirtualMachine) -> PyResult<crate::obj::objtuple::
312303
}
313304
}
314305

306+
pub fn get_stdout(vm: &VirtualMachine) -> PyResult {
307+
vm.get_attribute(vm.sys_module.clone(), "stdout")
308+
.map_err(|_| vm.new_runtime_error("lost sys.stdout".to_owned()))
309+
}
310+
pub fn get_stderr(vm: &VirtualMachine) -> PyResult {
311+
vm.get_attribute(vm.sys_module.clone(), "stderr")
312+
.map_err(|_| vm.new_runtime_error("lost sys.stderr".to_owned()))
313+
}
314+
315+
fn sys_excepthook(
316+
exc_type: PyObjectRef,
317+
exc_val: PyObjectRef,
318+
exc_tb: PyObjectRef,
319+
vm: &VirtualMachine,
320+
) -> PyResult<()> {
321+
let exc = exceptions::normalize(exc_type, exc_val, exc_tb, vm)?;
322+
let stderr = get_stderr(vm)?;
323+
exceptions::write_exception(&mut py_io::PyWriter(stderr, vm), vm, &exc)
324+
}
325+
315326
const PLATFORM: &str = {
316327
cfg_if::cfg_if! {
317328
if #[cfg(any(target_os = "linux", target_os = "android"))] {
@@ -553,6 +564,8 @@ settrace() -- set the global debug tracing function
553564
"audit" => ctx.new_function(sys_audit),
554565
"displayhook" => ctx.new_function(sys_displayhook),
555566
"__displayhook__" => ctx.new_function(sys_displayhook),
567+
"excepthook" => ctx.new_function(sys_excepthook),
568+
"__excepthook__" => ctx.new_function(sys_excepthook),
556569
});
557570

558571
#[cfg(windows)]

vm/src/vm.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -570,7 +570,7 @@ impl VirtualMachine {
570570
}
571571

572572
// TODO: #[track_caller] when stabilized
573-
fn _py_panic_failed(&self, exc: &PyBaseExceptionRef, msg: &str) -> ! {
573+
fn _py_panic_failed(&self, exc: PyBaseExceptionRef, msg: &str) -> ! {
574574
#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))]
575575
{
576576
let show_backtrace = env::var_os("RUST_BACKTRACE").map_or(false, |v| &v != "0");
@@ -598,11 +598,11 @@ impl VirtualMachine {
598598
}
599599
pub fn unwrap_pyresult<T>(&self, result: PyResult<T>) -> T {
600600
result.unwrap_or_else(|exc| {
601-
self._py_panic_failed(&exc, "called `vm.unwrap_pyresult()` on an `Err` value")
601+
self._py_panic_failed(exc, "called `vm.unwrap_pyresult()` on an `Err` value")
602602
})
603603
}
604604
pub fn expect_pyresult<T>(&self, result: PyResult<T>, msg: &str) -> T {
605-
result.unwrap_or_else(|exc| self._py_panic_failed(&exc, msg))
605+
result.unwrap_or_else(|exc| self._py_panic_failed(exc, msg))
606606
}
607607

608608
pub fn new_scope_with_builtins(&self) -> Scope {

0 commit comments

Comments
 (0)