Skip to content

Commit 30a6aa8

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

File tree

5 files changed

+58
-15
lines changed

5 files changed

+58
-15
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: 52 additions & 10 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,14 +32,57 @@ 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+
}
42+
}
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.payload::<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+
std::process::exit(i.as_bigint().to_i32().unwrap());
55+
}
56+
_ => {
57+
if vm.is_none(&args.elements[0]) {
58+
return
59+
}
60+
let s = vm.to_str(&args.elements[0]);
61+
let s = s
62+
.as_ref()
63+
.ok()
64+
.map_or("<element str() failed>", |s| s.as_str());
65+
println!("{}", s);
66+
}
67+
}),
68+
_ => {
69+
print!("(");
70+
for (i, elem) in args.elements.iter().enumerate() {
71+
if i != 0 {
72+
print!(", ")
73+
}
74+
let s = vm.to_str(elem);
75+
let s = s
76+
.as_ref()
77+
.ok()
78+
.map_or("<element str() failed>", |s| s.as_str());
79+
print!("{}", s)
80+
}
81+
print!(")");
82+
}
83+
}
84+
} else {
85+
print_exception(&vm, &err);
4186
process::exit(1);
4287
}
4388
}
@@ -349,13 +394,6 @@ fn _run_string(vm: &VirtualMachine, scope: Scope, source: &str, source_path: Str
349394
vm.run_code_obj(code_obj, scope)
350395
}
351396

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-
359397
fn run_command(vm: &VirtualMachine, scope: Scope, source: String) -> PyResult<()> {
360398
debug!("Running command {}", source);
361399
_run_string(vm, scope, &source, "<stdin>".to_string())?;
@@ -560,6 +598,10 @@ fn run_shell(vm: &VirtualMachine, scope: Scope) -> PyResult<()> {
560598
};
561599

562600
if let Err(exc) = result {
601+
if objtype::isinstance(&exc, &vm.ctx.exceptions.system_exit) {
602+
repl.save_history(&repl_history_path).unwrap();
603+
return Err(exc);
604+
}
563605
print_exception(vm, &exc);
564606
}
565607
}

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)