diff --git a/lumen_runtime/src/lib.rs b/lumen_runtime/src/lib.rs index 2c50bdc1c..ee4be97be 100644 --- a/lumen_runtime/src/lib.rs +++ b/lumen_runtime/src/lib.rs @@ -43,6 +43,7 @@ mod logging; mod map; pub mod otp; mod reference; +mod registry; mod stacktrace; mod system; mod term; diff --git a/lumen_runtime/src/otp/erlang.rs b/lumen_runtime/src/otp/erlang.rs index d15efbb8e..c58cd61ca 100644 --- a/lumen_runtime/src/otp/erlang.rs +++ b/lumen_runtime/src/otp/erlang.rs @@ -3,6 +3,7 @@ use std::cmp::Ordering; use std::convert::TryInto; use std::num::FpCategory; +use std::sync::Arc; use num_bigint::BigInt; use num_traits::Zero; @@ -15,7 +16,9 @@ use crate::integer::{big, small}; use crate::list::Cons; use crate::map::Map; use crate::otp; +use crate::process::local::pid_to_process; use crate::process::{IntoProcess, Process, TryIntoInProcess}; +use crate::registry::{self, Registered}; use crate::stacktrace; use crate::term::{Tag, Tag::*, Term}; use crate::time; @@ -1121,6 +1124,52 @@ pub fn raise_3(class: Term, reason: Term, stacktrace: Term) -> Result { } } +pub fn register_2(name: Term, pid_or_port: Term, process_arc: Arc) -> Result { + match name.tag() { + Atom => match unsafe { name.atom_to_string() }.as_ref().as_ref() { + "undefined" => Err(badarg!()), + _ => { + let mut writable_registry = registry::RW_LOCK_REGISTERED_BY_NAME.write().unwrap(); + + if !writable_registry.contains_key(&name) { + match pid_or_port.tag() { + LocalPid => { + let pid_process_arc = if pid_or_port.tagged == process_arc.pid.tagged { + process_arc + } else { + match pid_to_process(pid_or_port) { + Some(pid_process_arc) => pid_process_arc, + _ => return Err(badarg!()), + } + }; + + let mut locked_registered_name = + pid_process_arc.registered_name.lock().unwrap(); + + match *locked_registered_name { + None => { + writable_registry.insert( + name, + Registered::Process(Arc::clone(&pid_process_arc)), + ); + *locked_registered_name = Some(name); + + Ok(true.into()) + } + Some(_) => Err(badarg!()), + } + } + _ => Err(badarg!()), + } + } else { + Err(badarg!()) + } + } + }, + _ => Err(badarg!()), + } +} + /// `rem/2` infix operator. Integer remainder. pub fn rem_2(dividend: Term, divisor: Term, process: &Process) -> Result { integer_infix_operator!(dividend, divisor, process, %) diff --git a/lumen_runtime/src/otp/erlang/tests.rs b/lumen_runtime/src/otp/erlang/tests.rs index 4f8199d74..7901be2bd 100644 --- a/lumen_runtime/src/otp/erlang/tests.rs +++ b/lumen_runtime/src/otp/erlang/tests.rs @@ -88,6 +88,7 @@ mod number_or_badarith_1; mod or_2; mod orelse_2; mod raise_3; +mod register_2; mod rem_2; mod self_0; mod setelement_3; @@ -131,3 +132,10 @@ where { f(&process::local::new()) } + +fn with_process_arc(f: F) +where + F: FnOnce(Arc) -> (), +{ + f(process::local::new()) +} diff --git a/lumen_runtime/src/otp/erlang/tests/register_2.rs b/lumen_runtime/src/otp/erlang/tests/register_2.rs new file mode 100644 index 000000000..b64083d0a --- /dev/null +++ b/lumen_runtime/src/otp/erlang/tests/register_2.rs @@ -0,0 +1,76 @@ +use super::*; + +mod with_atom_name; + +#[test] +fn with_local_reference_name_errors_badarg() { + with_name_errors_badarg(|process| Term::local_reference(&process)); +} + +#[test] +fn with_empty_list_right_errors_badarg() { + with_name_errors_badarg(|_| Term::EMPTY_LIST); +} + +#[test] +fn with_list_right_errors_badarg() { + with_name_errors_badarg(|process| { + Term::cons(0.into_process(&process), 1.into_process(&process), &process) + }); +} + +#[test] +fn with_small_integer_right_errors_badarg() { + with_name_errors_badarg(|process| 0.into_process(&process)); +} + +#[test] +fn with_big_integer_right_errors_badarg() { + with_name_errors_badarg(|process| (crate::integer::small::MAX + 1).into_process(&process)); +} + +#[test] +fn with_float_right_errors_badarg() { + with_name_errors_badarg(|process| 0.0.into_process(&process)); +} + +#[test] +fn with_local_pid_right_errors_badarg() { + with_name_errors_badarg(|_| Term::local_pid(0, 1).unwrap()); +} + +#[test] +fn with_external_pid_right_errors_badarg() { + with_name_errors_badarg(|process| Term::external_pid(1, 2, 3, &process).unwrap()); +} + +#[test] +fn with_tuple_right_errors_badarg() { + with_name_errors_badarg(|process| Term::slice_to_tuple(&[], &process)); +} + +#[test] +fn with_map_right_errors_badarg() { + with_name_errors_badarg(|process| Term::slice_to_map(&[], &process)); +} + +#[test] +fn with_heap_binary_right_errors_badarg() { + with_name_errors_badarg(|process| Term::slice_to_binary(&[], &process)); +} + +#[test] +fn with_subbinary_right_errors_badarg() { + with_name_errors_badarg(|process| bitstring!(1 :: 1, &process)); +} + +fn with_name_errors_badarg(name: N) +where + N: FnOnce(&Process) -> Term, +{ + with_process_arc(|process_arc| { + let pid = Term::local_pid(0, 0).unwrap(); + + assert_badarg!(erlang::register_2(name(&process_arc), pid, process_arc)); + }); +} diff --git a/lumen_runtime/src/otp/erlang/tests/register_2/with_atom_name.rs b/lumen_runtime/src/otp/erlang/tests/register_2/with_atom_name.rs new file mode 100644 index 000000000..b2756a0af --- /dev/null +++ b/lumen_runtime/src/otp/erlang/tests/register_2/with_atom_name.rs @@ -0,0 +1,31 @@ +use super::*; + +mod without_registered_name; + +#[test] +fn with_undefined_atom_errors_badarg() { + with_name_errors_badarg(|_| Term::str_to_atom("undefined", DoNotCare).unwrap()) +} + +#[test] +fn with_registered_name_errors_badarg() { + let registered_name = Term::str_to_atom("registered_name", DoNotCare).unwrap(); + let registered_process_arc = process::local::new(); + + assert_eq!( + erlang::register_2( + registered_name, + registered_process_arc.pid, + registered_process_arc + ), + Ok(true.into()) + ); + + let unregistered_process_arc = process::local::new(); + + assert_badarg!(erlang::register_2( + registered_name, + unregistered_process_arc.pid, + unregistered_process_arc + )); +} diff --git a/lumen_runtime/src/otp/erlang/tests/register_2/with_atom_name/without_registered_name.rs b/lumen_runtime/src/otp/erlang/tests/register_2/with_atom_name/without_registered_name.rs new file mode 100644 index 000000000..4f6ae2e97 --- /dev/null +++ b/lumen_runtime/src/otp/erlang/tests/register_2/with_atom_name/without_registered_name.rs @@ -0,0 +1,91 @@ +use super::*; + +use std::sync::atomic::AtomicUsize; + +mod with_local_pid; + +#[test] +fn with_atom_pid_or_process_errors_badarg() { + with_pid_or_port_errors_badarg(|_| Term::str_to_atom("pid_or_port", DoNotCare).unwrap()) +} + +#[test] +fn with_empty_list_pid_or_process_errors_badarg() { + with_pid_or_port_errors_badarg(|_| Term::EMPTY_LIST); +} + +#[test] +fn with_list_pid_or_process_errors_badarg() { + with_pid_or_port_errors_badarg(|process| { + Term::cons(0.into_process(&process), 1.into_process(&process), &process) + }); +} + +#[test] +fn with_small_integer_pid_or_process_errors_badarg() { + with_pid_or_port_errors_badarg(|process| 0.into_process(&process)); +} + +#[test] +fn with_big_integer_pid_or_process_errors_badarg() { + with_pid_or_port_errors_badarg(|process| { + (crate::integer::small::MAX + 1).into_process(&process) + }); +} + +#[test] +fn with_float_pid_or_process_errors_badarg() { + with_pid_or_port_errors_badarg(|process| 0.0.into_process(&process)); +} + +#[test] +fn with_external_pid_pid_or_process_errors_badarg() { + with_pid_or_port_errors_badarg(|process| Term::external_pid(1, 2, 3, &process).unwrap()); +} + +#[test] +fn with_tuple_pid_or_process_errors_badarg() { + with_pid_or_port_errors_badarg(|process| Term::slice_to_tuple(&[], &process)); +} + +#[test] +fn with_map_pid_or_process_errors_badarg() { + with_pid_or_port_errors_badarg(|process| Term::slice_to_map(&[], &process)); +} + +#[test] +fn with_heap_binary_pid_or_process_errors_badarg() { + with_pid_or_port_errors_badarg(|process| Term::slice_to_binary(&[], &process)); +} + +#[test] +fn with_subbinary_pid_or_process_errors_badarg() { + with_pid_or_port_errors_badarg(|process| bitstring!(1 :: 1, &process)); +} + +static REGISTERED_NAME_COUNTER: AtomicUsize = AtomicUsize::new(0); + +fn registered_name() -> Term { + Term::str_to_atom( + format!( + "registered{}", + REGISTERED_NAME_COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst) + ) + .as_ref(), + DoNotCare, + ) + .unwrap() +} + +fn with_pid_or_port_errors_badarg

(pid_or_port: P) +where + P: FnOnce(&Process) -> Term, +{ + with_process_arc(|process_arc| { + assert_badarg!(erlang::register_2( + registered_name(), + pid_or_port(&process_arc), + process_arc + )); + }) +} diff --git a/lumen_runtime/src/otp/erlang/tests/register_2/with_atom_name/without_registered_name/with_local_pid.rs b/lumen_runtime/src/otp/erlang/tests/register_2/with_atom_name/without_registered_name/with_local_pid.rs new file mode 100644 index 000000000..3008ff8f6 --- /dev/null +++ b/lumen_runtime/src/otp/erlang/tests/register_2/with_atom_name/without_registered_name/with_local_pid.rs @@ -0,0 +1,63 @@ +use super::*; + +use crate::registry::Registered; + +#[test] +fn without_process() { + with_process_arc(|process_arc| { + let pid_or_port = process::identifier::local::next(); + + assert_badarg!(erlang::register_2( + registered_name(), + pid_or_port, + process_arc + )); + }); +} + +#[test] +fn with_same_process() { + with_process_arc(|process_arc| { + let name = registered_name(); + let pid_or_port = process_arc.pid; + + assert_eq!( + erlang::register_2(name, pid_or_port, process_arc.clone()), + Ok(true.into()) + ); + assert_eq!(*process_arc.registered_name.lock().unwrap(), Some(name)); + assert_eq!( + registry::RW_LOCK_REGISTERED_BY_NAME + .read() + .unwrap() + .get(&name), + Some(&Registered::Process(process_arc)) + ); + }); +} + +#[test] +fn with_different_process() { + with_process_arc(|process_arc| { + let name = registered_name(); + + let another_process_arc = process::local::new(); + let pid_or_port = another_process_arc.pid; + + assert_eq!( + erlang::register_2(name, pid_or_port, process_arc), + Ok(true.into()) + ); + assert_eq!( + *another_process_arc.registered_name.lock().unwrap(), + Some(name) + ); + assert_eq!( + registry::RW_LOCK_REGISTERED_BY_NAME + .read() + .unwrap() + .get(&name), + Some(&Registered::Process(another_process_arc)) + ); + }); +} diff --git a/lumen_runtime/src/process.rs b/lumen_runtime/src/process.rs index 823895b4e..5db4d8da1 100644 --- a/lumen_runtime/src/process.rs +++ b/lumen_runtime/src/process.rs @@ -1,4 +1,6 @@ ///! The memory specific to a process in the VM. +#[cfg(test)] +use std::fmt::{self, Debug}; use std::sync::Mutex; use im::hashmap::HashMap; @@ -21,6 +23,7 @@ pub mod local; pub struct Process { pub pid: Term, + pub registered_name: Mutex>, big_integer_arena: Mutex>, byte_arena: Mutex>, cons_arena: Mutex>, @@ -38,6 +41,7 @@ impl Process { fn new() -> Self { Process { pid: identifier::local::next(), + registered_name: Default::default(), big_integer_arena: Default::default(), byte_arena: Default::default(), cons_arena: Default::default(), @@ -192,6 +196,20 @@ impl Process { } } +#[cfg(test)] +impl Debug for Process { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self.pid) + } +} + +#[cfg(test)] +impl PartialEq for Process { + fn eq(&self, other: &Process) -> bool { + self.pid == other.pid + } +} + pub trait TryFromInProcess: Sized { fn try_from_in_process(value: T, process: &Process) -> Result; } diff --git a/lumen_runtime/src/process/local.rs b/lumen_runtime/src/process/local.rs index c2483793b..d5f0470c4 100644 --- a/lumen_runtime/src/process/local.rs +++ b/lumen_runtime/src/process/local.rs @@ -8,6 +8,13 @@ lazy_static! { static ref RW_LOCK_ARC_PROCESS_BY_PID: RwLock>> = Default::default(); } +pub fn pid_to_process(pid: Term) -> Option> { + match RW_LOCK_ARC_PROCESS_BY_PID.read().unwrap().get(&pid) { + Some(ref process_arc) => Some(Arc::clone(process_arc)), + None => None, + } +} + #[cfg(test)] pub fn new() -> Arc { let process = Process::new(); diff --git a/lumen_runtime/src/registry.rs b/lumen_runtime/src/registry.rs new file mode 100644 index 000000000..a2f114e64 --- /dev/null +++ b/lumen_runtime/src/registry.rs @@ -0,0 +1,16 @@ +/// Maps registered names (`Atom`) to `LocalPid` or `Port` +use std::collections::HashMap; +use std::sync::{Arc, RwLock}; + +use crate::process::Process; +use crate::term::Term; + +#[cfg_attr(test, derive(Debug, PartialEq))] +pub enum Registered { + Process(Arc), +} + +lazy_static! { + pub static ref RW_LOCK_REGISTERED_BY_NAME: RwLock> = + Default::default(); +}