Skip to content

Commit e89d00f

Browse files
committed
Correctly handle a SystemExit unwound to the top level of execution
1 parent 452e82f commit e89d00f

File tree

5 files changed

+46
-16
lines changed

5 files changed

+46
-16
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ rustpython-compiler = {path = "compiler", version = "0.1.1"}
2727
rustpython-parser = {path = "parser", version = "0.1.1"}
2828
rustpython-vm = {path = "vm", version = "0.1.1"}
2929
dirs = "2.0"
30+
num-traits = "0.2.8"
3031

3132
flame = { version = "0.2", optional = true }
3233
flamescope = { version = "0.1", optional = true }

src/main.rs

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ use clap::{App, AppSettings, Arg, ArgMatches};
88
use rustpython_compiler::{compile, error::CompileError, error::CompileErrorType};
99
use rustpython_parser::error::ParseErrorType;
1010
use rustpython_vm::{
11-
import, print_exception,
11+
import, match_class,
12+
obj::{objint::PyInt, objtuple::PyTuple, objtype},
13+
print_exception,
1214
pyobject::{ItemProtocol, PyObjectRef, PyResult},
1315
scope::Scope,
1416
util, PySettings, VirtualMachine,
@@ -30,17 +32,47 @@ fn main() {
3032
let vm = VirtualMachine::new(settings);
3133

3234
let res = run_rustpython(&vm, &matches);
33-
// See if any exception leaked out:
34-
handle_exception(&vm, res);
3535

3636
#[cfg(feature = "flame-it")]
3737
{
3838
main_guard.end();
3939
if let Err(e) = write_profile(&matches) {
4040
error!("Error writing profile information: {}", e);
41-
process::exit(1);
4241
}
4342
}
43+
44+
// See if any exception leaked out:
45+
if let Err(err) = res {
46+
if objtype::isinstance(&err, &vm.ctx.exceptions.system_exit) {
47+
let args = vm.get_attribute(err.clone(), "args").unwrap();
48+
let args = args.downcast::<PyTuple>().expect("'args' must be a tuple");
49+
match args.elements.len() {
50+
0 => return,
51+
1 => match_class!(match args.elements[0].clone() {
52+
i @ PyInt => {
53+
use num_traits::cast::ToPrimitive;
54+
process::exit(i.as_bigint().to_i32().unwrap());
55+
}
56+
arg => {
57+
if vm.is_none(&arg) {
58+
return;
59+
}
60+
if let Ok(s) = vm.to_str(&arg) {
61+
println!("{}", s);
62+
}
63+
}
64+
}),
65+
_ => {
66+
if let Ok(r) = vm.to_repr(args.as_object()) {
67+
println!("{}", r);
68+
}
69+
}
70+
}
71+
} else {
72+
print_exception(&vm, &err);
73+
}
74+
process::exit(1);
75+
}
4476
}
4577

4678
fn parse_arguments<'a>(app: App<'a, '_>) -> ArgMatches<'a> {
@@ -349,13 +381,6 @@ fn _run_string(vm: &VirtualMachine, scope: Scope, source: &str, source_path: Str
349381
vm.run_code_obj(code_obj, scope)
350382
}
351383

352-
fn handle_exception<T>(vm: &VirtualMachine, result: PyResult<T>) {
353-
if let Err(err) = result {
354-
print_exception(vm, &err);
355-
process::exit(1);
356-
}
357-
}
358-
359384
fn run_command(vm: &VirtualMachine, scope: Scope, source: String) -> PyResult<()> {
360385
debug!("Running command {}", source);
361386
_run_string(vm, scope, &source, "<stdin>".to_string())?;
@@ -560,6 +585,10 @@ fn run_shell(vm: &VirtualMachine, scope: Scope) -> PyResult<()> {
560585
};
561586

562587
if let Err(exc) = result {
588+
if objtype::isinstance(&exc, &vm.ctx.exceptions.system_exit) {
589+
repl.save_history(&repl_history_path).unwrap();
590+
return Err(exc);
591+
}
563592
print_exception(vm, &exc);
564593
}
565594
}

vm/src/sysmodule.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,10 +171,9 @@ fn sys_git_info(vm: &VirtualMachine) -> PyObjectRef {
171171
])
172172
}
173173

174-
// TODO: raise a SystemExit here
175-
fn sys_exit(code: OptionalArg<i32>, _vm: &VirtualMachine) -> PyResult<()> {
176-
let code = code.unwrap_or(0);
177-
std::process::exit(code)
174+
fn sys_exit(code: OptionalArg<PyObjectRef>, vm: &VirtualMachine) -> PyResult {
175+
let code = code.unwrap_or_else(|| vm.new_int(0));
176+
Err(vm.new_exception_obj(vm.ctx.exceptions.system_exit.clone(), vec![code])?)
178177
}
179178

180179
pub fn make_module(vm: &VirtualMachine, module: PyObjectRef, builtins: PyObjectRef) {

vm/src/vm.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ impl VirtualMachine {
303303
}
304304

305305
#[cfg_attr(feature = "flame-it", flame("VirtualMachine"))]
306-
fn new_exception_obj(&self, exc_type: PyClassRef, args: Vec<PyObjectRef>) -> PyResult {
306+
pub fn new_exception_obj(&self, exc_type: PyClassRef, args: Vec<PyObjectRef>) -> PyResult {
307307
// TODO: add repr of args into logging?
308308
vm_trace!("New exception created: {}", exc_type.name);
309309
self.invoke(&exc_type.into_object(), args)

0 commit comments

Comments
 (0)