Skip to content

Commit 867c91b

Browse files
authored
Merge pull request #1459 from RustPython/coolreader18/sysexit
Correctly handle a SystemExit unwound to the top level of execution
2 parents 28142e0 + e304504 commit 867c91b

File tree

7 files changed

+57
-28
lines changed

7 files changed

+57
-28
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 }

Lib/_sre.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1155,17 +1155,17 @@ def set_bigcharset(self, ctx):
11551155
block_index = char_code >> 8
11561156
# NB: there are CODESIZE block indices per bytecode
11571157
a = array.array("B")
1158-
a.fromstring(array.array(CODESIZE == 2 and "H" or "I",
1159-
[ctx.peek_code(block_index / CODESIZE)]).tostring())
1158+
a.frombytes(array.array(CODESIZE == 2 and "H" or "I",
1159+
[ctx.peek_code(block_index // CODESIZE)]).tobytes())
11601160
block = a[block_index % CODESIZE]
1161-
ctx.skip_code(256 / CODESIZE) # skip block indices
1162-
block_value = ctx.peek_code(block * (32 / CODESIZE)
1161+
ctx.skip_code(256 // CODESIZE) # skip block indices
1162+
block_value = ctx.peek_code(block * (32 // CODESIZE)
11631163
+ ((char_code & 255) >> (CODESIZE == 2 and 4 or 5)))
11641164
if block_value & (1 << (char_code & ((8 * CODESIZE) - 1))):
11651165
return self.ok
11661166
else:
1167-
ctx.skip_code(256 / CODESIZE) # skip block indices
1168-
ctx.skip_code(count * (32 / CODESIZE)) # skip blocks
1167+
ctx.skip_code(256 // CODESIZE) # skip block indices
1168+
ctx.skip_code(count * (32 // CODESIZE)) # skip blocks
11691169
def unknown(self, ctx):
11701170
return False
11711171

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/cformat.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -225,14 +225,13 @@ impl CFormatSpec {
225225
"-"
226226
};
227227

228-
// TODO: Support precision
229228
let magnitude_string = match self.format_type {
230229
CFormatType::Float(CFloatType::PointDecimal) => {
231-
if Some(CFormatQuantity::Amount(6)) != self.precision {
232-
return Err("Not yet implemented for %#.#f types".to_string());
233-
} else {
234-
format!("{:.6}", magnitude)
235-
}
230+
let precision = match self.precision {
231+
Some(CFormatQuantity::Amount(p)) => p,
232+
_ => 6,
233+
};
234+
format!("{:.*}", precision, magnitude)
236235
}
237236
CFormatType::Float(CFloatType::Exponent(_)) => {
238237
return Err("Not yet implemented for %e and %E".to_string())

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)