diff --git a/Cargo.lock b/Cargo.lock index e05958928c..a8d85ef06d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,6 +33,25 @@ dependencies = [ "scoped_threadpool 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "arr_macro" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "arr_macro_impl 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-hack 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "arr_macro_impl" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro-hack 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.39 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "arrayvec" version = "0.4.11" @@ -1011,6 +1030,7 @@ dependencies = [ name = "rustpython-vm" version = "0.1.0" dependencies = [ + "arr_macro 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "blake2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1058,6 +1078,7 @@ dependencies = [ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicode_categories 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "unicode_names2 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "wtf8 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1855,6 +1876,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum aho-corasick 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "36b7aa1ccb7d7ea3f437cf025a2ab1c47cc6c1bc9fc84918ff449def12f5e282" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" "checksum argon2rs 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3f67b0b6a86dae6e67ff4ca2b6201396074996379fba2b92ff649126f37cb392" +"checksum arr_macro 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d262b83f2f573121554ad6e764cd444303df85d86e5fcebc81903ddcf8dd3a97" +"checksum arr_macro_impl 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8decbe97ffec939e44228d91e5d0829ceb1616c6ed0984c09df164b1e7ebaafc" "checksum arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b8d73f9beda665eaa98ab9e4f7442bd4e7de6652587de55b2525e52e29c1b0ba" "checksum ascii-canvas 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b385d69402821a1c254533a011a312531cbcc0e3e24f19bbb4747a5a2daf37e2" "checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" diff --git a/tests/snippets/stdlib_signal.py b/tests/snippets/stdlib_signal.py new file mode 100644 index 0000000000..eb4a25f90d --- /dev/null +++ b/tests/snippets/stdlib_signal.py @@ -0,0 +1,44 @@ +import signal +import time +import sys +from testutils import assert_raises + +assert_raises(TypeError, lambda: signal.signal(signal.SIGINT, 2)) + +signals = [] + +def handler(signum, frame): + signals.append(signum) + + +signal.signal(signal.SIGILL, signal.SIG_IGN); +assert signal.getsignal(signal.SIGILL) is signal.SIG_IGN + +old_signal = signal.signal(signal.SIGILL, signal.SIG_DFL) +assert old_signal is signal.SIG_IGN +assert signal.getsignal(signal.SIGILL) is signal.SIG_DFL + + +# unix +if "win" not in sys.platform: + signal.signal(signal.SIGALRM, handler) + assert signal.getsignal(signal.SIGALRM) is handler + + signal.alarm(1) + time.sleep(2.0) + assert signals == [signal.SIGALRM] + + signal.signal(signal.SIGALRM, signal.SIG_IGN) + signal.alarm(1) + time.sleep(2.0) + + assert signals == [signal.SIGALRM] + + signal.signal(signal.SIGALRM, handler) + signal.alarm(1) + time.sleep(2.0) + + assert signals == [signal.SIGALRM, signal.SIGALRM] + + + diff --git a/vm/Cargo.toml b/vm/Cargo.toml index d4913b782f..6ffc8c3782 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -63,6 +63,7 @@ bitflags = "1.1" libc = "0.2" nix = "0.14.1" wtf8 = "0.0.3" +arr_macro = "0.1.2" flame = { version = "0.2", optional = true } flamer = { version = "0.3", optional = true } diff --git a/vm/src/frame.rs b/vm/src/frame.rs index b70cb658c4..b9ba09905c 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -23,6 +23,9 @@ use crate::vm::VirtualMachine; use indexmap::IndexMap; use itertools::Itertools; +#[cfg(not(target_arch = "wasm32"))] +use crate::stdlib::signal::check_signals; + #[derive(Clone, Debug)] struct Block { /// The type of block. @@ -163,6 +166,10 @@ impl Frame { /// Execute a single instruction. #[allow(clippy::cognitive_complexity)] fn execute_instruction(&self, vm: &VirtualMachine) -> FrameResult { + #[cfg(not(target_arch = "wasm32"))] + { + check_signals(vm); + } let instruction = self.fetch_instruction(); flame_guard!(format!("Frame::execute_instruction({:?})", instruction)); diff --git a/vm/src/stdlib/mod.rs b/vm/src/stdlib/mod.rs index dea27dfe08..b41342c229 100644 --- a/vm/src/stdlib/mod.rs +++ b/vm/src/stdlib/mod.rs @@ -38,6 +38,8 @@ pub mod io; mod os; #[cfg(all(unix, not(any(target_os = "android", target_os = "redox"))))] mod pwd; +#[cfg(not(target_arch = "wasm32"))] +pub mod signal; use crate::pyobject::PyObjectRef; @@ -92,6 +94,7 @@ pub fn get_module_inits() -> HashMap { modules.insert("_io".to_string(), Box::new(io::make_module)); modules.insert("_os".to_string(), Box::new(os::make_module)); modules.insert("socket".to_string(), Box::new(socket::make_module)); + modules.insert("signal".to_string(), Box::new(signal::make_module)); } // Unix-only diff --git a/vm/src/stdlib/signal.rs b/vm/src/stdlib/signal.rs new file mode 100644 index 0000000000..cabeb12f7f --- /dev/null +++ b/vm/src/stdlib/signal.rs @@ -0,0 +1,165 @@ +use crate::obj::objint::PyIntRef; +use crate::pyobject::{IdProtocol, PyObjectRef, PyResult}; +use crate::vm::VirtualMachine; + +use std::sync::atomic::{AtomicBool, Ordering}; + +use num_traits::cast::ToPrimitive; + +use arr_macro::arr; + +#[cfg(unix)] +use nix::unistd::alarm as sig_alarm; + +use libc; + +#[cfg(not(windows))] +use libc::{SIG_DFL, SIG_ERR, SIG_IGN}; + +#[cfg(windows)] +const SIG_DFL: libc::sighandler_t = 0; +#[cfg(windows)] +const SIG_IGN: libc::sighandler_t = 1; +#[cfg(windows)] +const SIG_ERR: libc::sighandler_t = !0; + +const NSIG: usize = 64; + +// We cannot use the NSIG const in the arr macro. This will fail compilation if NSIG is different. +static mut TRIGGERS: [AtomicBool; NSIG] = arr![AtomicBool::new(false); 64]; + +extern "C" fn run_signal(signum: i32) { + unsafe { + TRIGGERS[signum as usize].store(true, Ordering::Relaxed); + } +} + +fn signal( + signalnum: PyIntRef, + handler: PyObjectRef, + vm: &VirtualMachine, +) -> PyResult> { + if !vm.isinstance(&handler, &vm.ctx.function_type())? + && !vm.isinstance(&handler, &vm.ctx.bound_method_type())? + && !vm.isinstance(&handler, &vm.ctx.builtin_function_or_method_type())? + { + return Err(vm.new_type_error("Hanlder must be callable".to_string())); + } + let signal_module = vm.import("signal", &vm.ctx.new_tuple(vec![]), 0)?; + let sig_dfl = vm.get_attribute(signal_module.clone(), "SIG_DFL")?; + let sig_ign = vm.get_attribute(signal_module, "SIG_IGN")?; + let signalnum = signalnum.as_bigint().to_i32().unwrap(); + check_signals(vm); + let sig_handler = if handler.is(&sig_dfl) { + SIG_DFL + } else if handler.is(&sig_ign) { + SIG_IGN + } else { + run_signal as libc::sighandler_t + }; + let old = unsafe { libc::signal(signalnum, sig_handler) }; + if old == SIG_ERR { + return Err(vm.new_os_error("Failed to set signal".to_string())); + } + let old_handler = vm.signal_handlers.borrow_mut().insert(signalnum, handler); + Ok(old_handler) +} + +fn getsignal(signalnum: PyIntRef, vm: &VirtualMachine) -> PyResult> { + let signalnum = signalnum.as_bigint().to_i32().unwrap(); + Ok(vm.signal_handlers.borrow_mut().get(&signalnum).cloned()) +} + +#[cfg(unix)] +fn alarm(time: PyIntRef, _vm: &VirtualMachine) -> u32 { + let time = time.as_bigint().to_u32().unwrap(); + let prev_time = if time == 0 { + sig_alarm::cancel() + } else { + sig_alarm::set(time) + }; + prev_time.unwrap_or(0) +} + +#[allow(clippy::needless_range_loop)] +pub fn check_signals(vm: &VirtualMachine) { + for signum in 1..NSIG { + let triggerd = unsafe { TRIGGERS[signum].swap(false, Ordering::Relaxed) }; + if triggerd { + let handler = vm + .signal_handlers + .borrow() + .get(&(signum as i32)) + .expect("Handler should be set") + .clone(); + vm.invoke(handler, vec![vm.new_int(signum), vm.get_none()]) + .expect("Test"); + } + } +} + +fn stub_func(_vm: &VirtualMachine) -> PyResult { + panic!("Do not use directly"); +} + +pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { + let ctx = &vm.ctx; + + let sig_dfl = ctx.new_rustfunc(stub_func); + let sig_ign = ctx.new_rustfunc(stub_func); + + let module = py_module!(vm, "signal", { + "signal" => ctx.new_rustfunc(signal), + "getsignal" => ctx.new_rustfunc(getsignal), + "SIG_DFL" => sig_dfl, + "SIG_IGN" => sig_ign, + "SIGABRT" => ctx.new_int(libc::SIGABRT as u8), + "SIGFPE" => ctx.new_int(libc::SIGFPE as u8), + "SIGILL" => ctx.new_int(libc::SIGILL as u8), + "SIGINT" => ctx.new_int(libc::SIGINT as u8), + "SIGSEGV" => ctx.new_int(libc::SIGSEGV as u8), + "SIGTERM" => ctx.new_int(libc::SIGTERM as u8), + }); + extend_module_platform_specific(vm, module) +} + +#[cfg(unix)] +fn extend_module_platform_specific(vm: &VirtualMachine, module: PyObjectRef) -> PyObjectRef { + let ctx = &vm.ctx; + + extend_module!(vm, module, { + "alarm" => ctx.new_rustfunc(alarm), + "SIGHUP" => ctx.new_int(libc::SIGHUP as u8), + "SIGQUIT" => ctx.new_int(libc::SIGQUIT as u8), + "SIGTRAP" => ctx.new_int(libc::SIGTRAP as u8), + "SIGBUS" => ctx.new_int(libc::SIGBUS as u8), + "SIGKILL" => ctx.new_int(libc::SIGKILL as u8), + "SIGUSR1" => ctx.new_int(libc::SIGUSR1 as u8), + "SIGUSR2" => ctx.new_int(libc::SIGUSR2 as u8), + "SIGPIPE" => ctx.new_int(libc::SIGPIPE as u8), + "SIGALRM" => ctx.new_int(libc::SIGALRM as u8), + "SIGSTKFLT" => ctx.new_int(libc::SIGSTKFLT as u8), + "SIGCHLD" => ctx.new_int(libc::SIGCHLD as u8), + "SIGCONT" => ctx.new_int(libc::SIGCONT as u8), + "SIGSTOP" => ctx.new_int(libc::SIGSTOP as u8), + "SIGTSTP" => ctx.new_int(libc::SIGTSTP as u8), + "SIGTTIN" => ctx.new_int(libc::SIGTTIN as u8), + "SIGTTOU" => ctx.new_int(libc::SIGTTOU as u8), + "SIGURG" => ctx.new_int(libc::SIGURG as u8), + "SIGXCPU" => ctx.new_int(libc::SIGXCPU as u8), + "SIGXFSZ" => ctx.new_int(libc::SIGXFSZ as u8), + "SIGVTALRM" => ctx.new_int(libc::SIGVTALRM as u8), + "SIGPROF" => ctx.new_int(libc::SIGPROF as u8), + "SIGWINCH" => ctx.new_int(libc::SIGWINCH as u8), + "SIGIO" => ctx.new_int(libc::SIGIO as u8), + "SIGPWR" => ctx.new_int(libc::SIGPWR as u8), + "SIGSYS" => ctx.new_int(libc::SIGSYS as u8), + }); + + module +} + +#[cfg(not(unix))] +fn extend_module_platform_specific(_vm: &VirtualMachine, module: PyObjectRef) -> PyObjectRef { + module +} diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 4c231eebde..bbfea7fb60 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -62,6 +62,7 @@ pub struct VirtualMachine { pub trace_func: RefCell, pub use_tracing: RefCell, pub settings: PySettings, + pub signal_handlers: RefCell>, } /// Struct containing all kind of settings for the python vm. @@ -160,6 +161,7 @@ impl VirtualMachine { trace_func, use_tracing: RefCell::new(false), settings, + signal_handlers: Default::default(), }; builtins::make_module(&vm, builtins.clone());