// The web layer of Relax's std (std/sys/web.rs, fs_web.rs, process_web.rs, env_web.rs, ...) against // rustc's std for wasm32-unknown-unknown. `run()` prints one line per observation through the // host's `js_console_log`; the output must equal the one of the same program built by rustc for // wasm32 and run in node (host.js). Panics do not unwind in the rustc build, so the observations of // panicking calls are Relax-only (lines prefixed `RELAX-ONLY`), checked against // relax_only.expected. Driver: run.sh passes --cfg std_reference only to rustc; // both builds have main and the exported run entry, independent of cfg(relax). use std::cell::{Cell, RefCell}; use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, VecDeque}; use std::ffi::{CString, OsString}; use std::fmt::Write as _; use std::hash::{BuildHasherDefault, DefaultHasher}; use std::io::{self, BufRead, BufReader, BufWriter, Cursor, IsTerminal, Read, Write}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, TcpStream, ToSocketAddrs, UdpSocket}; use std::path::{Path, PathBuf}; use std::rc::Rc; use std::sync::atomic::{AtomicBool, AtomicI64, AtomicPtr, AtomicU32, AtomicUsize, Ordering}; use std::sync::mpsc; use std::sync::{Arc, Barrier, Condvar, LazyLock, Mutex, Once, OnceLock, RwLock}; use std::time::Duration; extern "C" { fn js_console_log(ptr: *const u8, len: usize); } fn p(s: &str) { unsafe { js_console_log(s.as_ptr(), s.len()) } } macro_rules! out { ($($a:tt)*) => {{ let mut s = String::new(); let _ = write!(s, $($a)*); s.push('\n'); p(&s); }}; } fn show(name: &str, r: &io::Result) { match r { Ok(v) => out!("{name}: Ok({v:?})"), Err(e) => out!("{name}: Err kind={:?} display={e} debug={e:?}", e.kind()), } } fn fmt_section() { out!("fmt: {:>8.3}|{:<5}|{:^7}|{:#x}|{:08.2}|{:e}|{:+}", 3.14159f64, "ab", "mid", 255, -2.5f32, 1234.5f64, 7); out!("fmt: {:?} {:?} {:?}", "tab\t\"q\"\u{1f600}\n", 'x', '\''); out!("fmt: {} {} {} {}", f32::MAX, f64::MIN_POSITIVE, 1.0e21f64, 0.1f32 + 0.2f32); out!("fmt: {:?} {:?} {:?}", f64::NAN, -f64::INFINITY, -0.0f64); out!("fmt: {:#?}", (1u8, "a", [Some(1i32), None])); out!("fmt: {:10.3?}|{:<10?}|{:#b}|{:#o}", 1.5f64, Some(2u8), 5u8, 64u8); out!("fmt: {} {}", i128::MIN, u128::MAX); let w = 12; out!("fmt: {:>w$}|{:.*}", "r", 3, 2.718281828f64, w = w); out!("fmt: {:?}", String::from("x").as_bytes()); out!("fmt: {}", format_args!("{}-{}", 1, 2)); } fn str_section() { let s = "Hello, Wörld! ß straße"; out!("str: {} {} {}", s.to_uppercase(), s.to_lowercase(), s.len()); out!("str: {:?}", s.split(|c: char| !c.is_alphanumeric()).filter(|x| !x.is_empty()).collect::>()); out!("str: {:?}", s.char_indices().filter(|(_, c)| !c.is_ascii()).collect::>()); out!("str: {:?} {:?} {:?}", "42".parse::(), "-1".parse::().is_err(), "3.5e2".parse::()); out!("str: {:?} {:?}", "x".repeat(3), "a,b,,c".rsplitn(2, ',').collect::>()); out!("str: {:?}", String::from_utf8_lossy(&[72, 105, 0xff, 33])); let bad = std::hint::black_box([0xc3u8, 0x28]); out!("str: {:?}", std::str::from_utf8(&bad).map_err(|e| e.to_string())); out!("str: {:?} {}", " trim\t".trim(), "ab".cmp("b") as i32); let c = CString::new("abc").unwrap(); out!("str: {:?} {:?} {:?}", c, c.as_bytes_with_nul(), CString::new("a\0b").is_err()); let o = OsString::from("os str"); out!("str: {:?} {:?}", o, o.to_str()); } fn collections_section() { let mut v: Vec = (0..20).map(|i| (i * 7919) % 31).collect(); v.sort(); v.dedup(); out!("vec: {:?} cap_ge_len={}", v, v.capacity() >= v.len()); let mut v2 = vec![5, 3, 8, 1]; v2.sort_by(|a, b| b.cmp(a)); v2.insert(1, 99); v2.retain(|x| x % 2 == 1); out!("vec: {:?} {:?}", v2, v2.binary_search(&99).is_ok()); let mut bm = BTreeMap::new(); for (i, w) in ["delta", "alpha", "charlie", "bravo"].iter().enumerate() { bm.insert(*w, i); } out!("btree: {:?} first={:?}", bm, bm.iter().next()); let bs: BTreeSet<_> = [5, 1, 9, 3].into_iter().collect(); out!("btreeset: {:?} {:?}", bs, bs.range(2..9).collect::>()); // a fixed hasher: the layout of the table is deterministic let mut hm: HashMap> = HashMap::default(); for (i, w) in ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"].iter().enumerate() { hm.insert(i as u32 * 37, w); } let mut keys: Vec<_> = hm.keys().copied().collect(); keys.sort(); out!("hashmap: len={} keys={:?} get={:?}", hm.len(), keys, hm.get(&37)); let hs: HashSet = (0..100).collect(); out!("hashset: {} {}", hs.len(), hs.iter().sum::()); let mut dq: VecDeque = VecDeque::new(); for i in 0..6 { if i % 2 == 0 { dq.push_back(i) } else { dq.push_front(i) } } out!("deque: {:?} {:?}", dq, dq.iter().rev().take(2).collect::>()); let mut heap = BinaryHeap::new(); for x in [4, 9, 1, 7] { heap.push(x) } out!("heap: {:?} {:?}", heap.pop(), heap.into_sorted_vec()); } fn rc_sync_section() { let a = Arc::new(5); let b = a.clone(); let w = Arc::downgrade(&a); out!("arc: {} {} {} {:?}", Arc::strong_count(&a), Arc::weak_count(&a), *b + 1, w.upgrade()); drop(a); drop(b); out!("arc: {:?}", w.upgrade()); let r = Rc::new(RefCell::new(vec![1])); r.borrow_mut().push(2); out!("rc: {:?} {} {:?}", r, Rc::strong_count(&r), r.try_borrow_mut().is_ok()); let m = Mutex::new(10); { let mut g = m.lock().unwrap(); *g += 1; out!("mutex: try_lock while held = {}", m.try_lock().is_err()); } out!("mutex: {:?} {:?} {}", m.lock().map(|g| *g).ok(), m, m.is_poisoned()); let rw = RwLock::new(String::from("rw")); { let r1 = rw.read().unwrap(); let r2 = rw.read().unwrap(); out!("rwlock: {} {} write_blocked={}", *r1, r2.len(), rw.try_write().is_err()); } rw.write().unwrap().push('!'); out!("rwlock: {:?}", rw); let cv = Condvar::new(); cv.notify_one(); cv.notify_all(); out!("condvar: {:?}", cv); static ONCE: Once = Once::new(); let mut n = 0; ONCE.call_once(|| n += 1); ONCE.call_once(|| n += 1); out!("once: {} {}", n, ONCE.is_completed()); let oc: OnceLock = OnceLock::new(); out!("oncelock: {:?} {:?} {:?}", oc.get(), oc.get_or_init(|| "v".into()), oc.set("w".into())); static LAZY: LazyLock> = LazyLock::new(|| vec![1, 2, 3]); out!("lazy: {:?}", *LAZY); let bar = Barrier::new(1); out!("barrier: {}", bar.wait().is_leader()); thread_local! { static TL: Cell = const { Cell::new(7) }; static TLR: RefCell> = RefCell::new(vec!["init".to_string()]); } TL.with(|c| c.set(c.get() * 6)); TLR.with(|v| v.borrow_mut().push("more".into())); out!("thread_local: {} {:?}", TL.get(), TLR.with(|v| v.borrow().clone())); let (tx, rx) = mpsc::channel(); tx.send(1).unwrap(); tx.send(2).unwrap(); let tx2 = tx.clone(); drop(tx); out!("mpsc: {:?} {:?} {:?}", rx.recv(), rx.try_recv(), rx.try_recv()); tx2.send(3).unwrap(); drop(tx2); out!("mpsc: {:?} {:?}", rx.recv(), rx.recv()); let (stx, srx) = mpsc::sync_channel::(1); out!("sync_channel: {:?} {:?} {:?}", stx.try_send(1), stx.try_send(2), srx.try_recv()); } fn atomics_section() { let a = AtomicUsize::new(5); out!("atomic: {} {} {:?} {:?}", a.fetch_add(3, Ordering::SeqCst), a.fetch_sub(1, Ordering::Relaxed), a.compare_exchange(7, 100, Ordering::AcqRel, Ordering::Acquire), a.compare_exchange(7, 1, Ordering::SeqCst, Ordering::SeqCst)); let b = AtomicBool::new(false); out!("atomic: {} {} {}", b.swap(true, Ordering::SeqCst), b.fetch_and(false, Ordering::SeqCst), b.load(Ordering::SeqCst)); let c = AtomicI64::new(-5); out!("atomic: {} {} {}", c.fetch_max(3, Ordering::SeqCst), c.fetch_xor(0xff, Ordering::SeqCst), c.load(Ordering::SeqCst)); let d = AtomicU32::new(0xf0); out!("atomic: {:?} {}", d.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |x| Some(x >> 4)), d.load(Ordering::SeqCst)); let mut x = 5u32; let ptr = AtomicPtr::new(&mut x as *mut u32); out!("atomic: {}", unsafe { *ptr.load(Ordering::SeqCst) }); std::sync::atomic::fence(Ordering::SeqCst); } fn thread_section() { let r = std::thread::Builder::new().name("worker".into()).spawn(|| 1); match r { Ok(_) => out!("thread: spawned"), Err(e) => out!("thread: spawn Err kind={:?} display={e}", e.kind()), } match std::thread::available_parallelism() { Ok(n) => out!("thread: parallelism {n}"), Err(e) => out!("thread: parallelism Err kind={:?} display={e}", e.kind()), } std::thread::yield_now(); out!("thread: panicking={}", std::thread::panicking()); } fn io_section() { let mut so = io::stdout(); show("stdout.write", &so.write(b"to the sink")); show("stdout.write_all", &so.write_all(b"all\n").map(|_| ())); show("stdout.flush", &so.flush()); show("stderr.write", &io::stderr().write(b"err")); println!("a line to nowhere"); eprintln!("an error line to nowhere"); print!("no newline"); out!("stdout.is_terminal={} stdin.is_terminal={} stderr.is_terminal={}", io::stdout().is_terminal(), io::stdin().is_terminal(), io::stderr().is_terminal()); let mut line = String::new(); show("stdin.read_line", &io::stdin().read_line(&mut line)); out!("stdin line: {:?}", line); let mut buf = Vec::new(); show("stdin.read_to_end", &io::stdin().read_to_end(&mut buf)); show("stdin.read_exact", &io::stdin().read_exact(&mut [0u8; 1])); show("stdin.lines", &io::stdin().lock().lines().next().transpose()); let mut c = Cursor::new(b"line one\nline two\nthree".to_vec()); let mut s = String::new(); c.read_line(&mut s).unwrap(); out!("cursor: {:?} pos={}", s, c.position()); let rest: Vec = BufReader::new(c).lines().map(|l| l.unwrap()).collect(); out!("bufreader: {:?}", rest); let mut bw = BufWriter::new(Vec::new()); write!(bw, "{}-{}", 1, 2).unwrap(); out!("bufwriter: {:?}", bw.into_inner().unwrap()); let mut sink = Vec::new(); out!("copy: {:?}", io::copy(&mut &b"12345"[..], &mut sink).unwrap()); let e = io::Error::new(io::ErrorKind::Other, "custom"); out!("error: {e} | {e:?} | {:?}", e.kind()); let e = io::Error::from_raw_os_error(5); out!("error: {e} | {e:?} | {:?} | {:?}", e.kind(), e.raw_os_error()); let e = io::Error::last_os_error(); out!("error: {e} | {:?} | {:?}", e.kind(), e.raw_os_error()); let e: io::Error = io::ErrorKind::NotFound.into(); out!("error: {e} | {e:?}"); let e = io::Error::new(io::ErrorKind::InvalidInput, io::Error::from(io::ErrorKind::TimedOut)); out!("error: {e} | {e:?} | src={:?}", std::error::Error::source(&e).map(|s| s.to_string())); let (mut a, mut b) = ([1u8, 2, 3, 4], [0u8; 2]); out!("slice io: {:?} {:?}", (&a[..]).read(&mut b).unwrap(), b); out!("slice io: {:?} {:?}", (&mut a[..]).write(&[9, 9]).unwrap(), a); show("pipe", &io::pipe().map(|_| ())); } fn fs_section() { show("fs::read", &std::fs::read("/etc/passwd")); show("fs::read_to_string", &std::fs::read_to_string("a.txt")); show("fs::write", &std::fs::write("out.bin", b"x")); show("File::open", &std::fs::File::open("x").map(|_| ())); show("File::create", &std::fs::File::create("x").map(|_| ())); show("OpenOptions", &std::fs::OpenOptions::new().read(true).write(true).append(true).create(true).open("x").map(|_| ())); show("fs::metadata", &std::fs::metadata("/").map(|_| ())); show("fs::symlink_metadata", &std::fs::symlink_metadata("/").map(|_| ())); show("fs::read_dir", &std::fs::read_dir(".").map(|_| ())); show("fs::create_dir", &std::fs::create_dir("d")); show("fs::create_dir_all(\"\")", &std::fs::create_dir_all("")); show("fs::create_dir_all(\"/\")", &std::fs::create_dir_all("/")); show("fs::create_dir_all(a/b)", &std::fs::create_dir_all("a/b")); show("DirBuilder", &std::fs::DirBuilder::new().recursive(true).create("x/y")); show("fs::remove_file", &std::fs::remove_file("x")); show("fs::remove_dir", &std::fs::remove_dir("x")); show("fs::remove_dir_all", &std::fs::remove_dir_all("x")); show("fs::rename", &std::fs::rename("a", "b")); show("fs::copy", &std::fs::copy("a", "b")); show("fs::hard_link", &std::fs::hard_link("a", "b")); show("fs::canonicalize", &std::fs::canonicalize(".")); show("fs::read_link", &std::fs::read_link("l")); show("fs::exists", &std::fs::exists("x")); out!("Path::exists={} is_file={} is_dir={}", Path::new("/").exists(), Path::new("x").is_file(), Path::new("/").is_dir()); show("Path::try_exists", &Path::new("x").try_exists()); show("Path::canonicalize", &Path::new("x").canonicalize()); show("Path::read_dir", &Path::new("x").read_dir().map(|_| ())); } fn path_section() { let pb = PathBuf::from("/usr/local/lib/libfoo.so.1"); out!("path: {:?} {:?} {:?} {:?}", pb.parent(), pb.file_name(), pb.extension(), pb.file_stem()); out!("path: abs={} rel={} has_root={}", pb.is_absolute(), pb.is_relative(), pb.has_root()); out!("path: {:?}", pb.components().collect::>()); let mut j = PathBuf::from("base/dir"); j.push("sub"); j.push("/rooted"); j.set_extension("txt"); out!("path: {:?} {:?}", j, Path::new("a").join("/b").join("c/")); out!("path: {:?} {:?}", Path::new("a/./b/../c//d").components().collect::>(), Path::new("a/b").strip_prefix("a")); out!("path: {} {}", Path::new("x/y.z").display(), std::path::MAIN_SEPARATOR); show("path::absolute", &std::path::absolute("rel/path")); show("path::absolute(/abs)", &std::path::absolute("/abs/../path")); out!("path: {:?} {:?}", Path::new("/a/b").starts_with("/a"), Path::new("/a/b").ends_with("b")); } fn env_section() { out!("env: args count={} {:?} {:?}", std::env::args().count(), std::env::args(), std::env::args_os()); out!("env: len={} next={:?} rev={:?}", std::env::args().len(), std::env::args().next(), std::env::args().next_back()); out!("env: {:?} {:?}", std::env::var("HOME"), std::env::var_os("PATH")); out!("env: {}", std::env::var("X").unwrap_err()); show("env::current_dir", &std::env::current_dir()); show("env::current_exe", &std::env::current_exe()); show("env::set_current_dir", &std::env::set_current_dir("/")); #[allow(deprecated)] let home = std::env::home_dir(); out!("env: home={:?}", home); out!("env: {:?} {:?} {:?} {:?} {:?}", std::env::consts::FAMILY, std::env::consts::OS, std::env::consts::EXE_SUFFIX, std::env::consts::DLL_EXTENSION, std::env::consts::DLL_PREFIX); match std::env::join_paths(["a", "b"]) { Ok(v) => out!("env: join_paths {:?}", v), Err(e) => out!("env: join_paths Err {e} | {e:?}"), } } fn process_section() { use std::process::{Command, ExitCode, Stdio}; let mut c = Command::new("ls"); c.arg("-l").args(["a", "b c"]).env("K", "v").env("Z", "w").env_remove("GONE").current_dir("/tmp"); out!("command: {:?}", c); out!("command: {:#?}", c); out!("command: {:?} {:?} {:?} {:?}", c.get_program(), c.get_args().collect::>(), c.get_envs().collect::>(), c.get_current_dir()); out!("command: {:?} {:?}", c.get_args(), c.get_envs()); show("spawn", &c.spawn().map(|_| ())); show("output", &c.output().map(|_| ())); show("status", &c.status().map(|_| ())); c.stdin(Stdio::null()).stdout(Stdio::piped()).stderr(Stdio::inherit()); out!("command: {:#?}", c); let mut e = Command::new("env"); e.env_clear().env("A", "1"); out!("command: {:?}", e); out!("exitcode: {:?} {:?}", ExitCode::SUCCESS, ExitCode::from(3)); } fn net_section() { show("TcpStream::connect(ip)", &TcpStream::connect("127.0.0.1:80").map(|_| ())); show("TcpStream::connect(name)", &TcpStream::connect("example.com:80").map(|_| ())); show("TcpListener::bind", &std::net::TcpListener::bind("0.0.0.0:0").map(|_| ())); show("UdpSocket::bind", &UdpSocket::bind("0.0.0.0:0").map(|_| ())); show("to_socket_addrs(ip)", &"1.2.3.4:5".to_socket_addrs().map(|i| i.collect::>())); show("to_socket_addrs(name)", &"localhost:80".to_socket_addrs().map(|i| i.collect::>())); show("to_socket_addrs(bad)", &"nocolon".to_socket_addrs().map(|i| i.collect::>())); show("to_socket_addrs(port)", &"host:99999".to_socket_addrs().map(|i| i.collect::>())); show("to_socket_addrs((str,port))", &("localhost", 80).to_socket_addrs().map(|i| i.collect::>())); out!("addr: {:?} {:?}", "[::1]:8080".parse::(), "1.2.3.4".parse::()); out!("addr: {} {} {}", Ipv4Addr::new(10, 0, 0, 1).is_private(), Ipv4Addr::LOCALHOST, Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1)); out!("addr: {:?} {}", "256.0.0.1".parse::().map_err(|e| e.to_string()), Ipv6Addr::from([0u8; 16])); } fn time_section() { use std::time::{SystemTime, UNIX_EPOCH}; let d = Duration::new(5, 999_999_999) + Duration::from_millis(2); out!("duration: {:?} {:?} {} {}", d, d.checked_sub(Duration::from_secs(9)), d.as_secs_f64(), d.subsec_micros()); out!("duration: {:?} {:?}", Duration::from_secs_f64(1.5).mul_f32(2.0), Duration::MAX.checked_add(Duration::from_nanos(1))); out!("systemtime: {:?} {:?}", UNIX_EPOCH, UNIX_EPOCH + Duration::new(3, 5)); out!("systemtime: {:?} {:?}", UNIX_EPOCH.checked_sub(Duration::from_secs(1)), (UNIX_EPOCH + Duration::from_secs(10)).duration_since(UNIX_EPOCH)); let later = UNIX_EPOCH + Duration::from_secs(2); out!("systemtime: {:?}", UNIX_EPOCH.duration_since(later).map_err(|e| (e.to_string(), e.duration()))); out!("systemtime: {:?} {}", later.checked_add(Duration::from_secs(1)) > Some(later), later == later); let _ = SystemTime::UNIX_EPOCH; } fn misc_section() { out!("misc: {}", std::mem::size_of::()); let b: Box = Box::new(5i32); out!("misc: {:?} {:?}", b.downcast_ref::(), b.downcast_ref::()); let r: Result = "x".parse(); out!("misc: {:?}", r); out!("misc: {:?} {:?}", 10u8.checked_mul(30), 7i32.rem_euclid(-3)); out!("misc: {:?} {:?}", u32::from_str_radix("zz", 36), char::from_u32(0xd800)); out!("misc: {:?}", (1..=4).map(|x| x * x).rev().collect::>()); let mut it = [1, 2, 3, 4, 5].chunks(2); out!("misc: {:?} {:?}", it.next(), it.last()); let x = std::hint::black_box(41) + 1; out!("misc: {}", x); out!("misc: {:?}", std::panic::catch_unwind(|| ()).is_ok()); } fn body() { out!("web_std start"); fmt_section(); str_section(); collections_section(); rc_sync_section(); atomics_section(); thread_section(); io_section(); fs_section(); path_section(); env_section(); process_section(); net_section(); time_section(); misc_section(); out!("web_std end"); } // Each abort probe runs in a fresh Node instance. Keep its closure even when the // capture only executes the non-panicking sentinel; the hook must precede the trap. #[cfg(not(std_reference))] #[no_mangle] #[relax(keep_reachable)] pub extern "C" fn panic_probe(probe: u32) { fn caught(name: &'static str, f: F) { std::panic::set_hook(Box::new(move |info| { let location = info.location().expect("panic hook location"); assert!(location.line() > 0 && !location.file().is_empty()); out!("RELAX-ONLY {name}: panicked {:?}", info.payload_as_str()); })); let _ = std::panic::catch_unwind(f); out!("RELAX-ONLY {name}: returned"); } match probe { 0 => caught("Instant::now", || drop(std::time::Instant::now())), 1 => caught("SystemTime::now", || drop(std::time::SystemTime::now())), 2 => caught("set_var", || std::env::set_var("K", "V")), 3 => caught("remove_var", || std::env::remove_var("K")), 4 => caught("vars", || drop(std::env::vars())), 5 => caught("vars_os", || drop(std::env::vars_os())), 6 => caught("temp_dir", || drop(std::env::temp_dir())), 7 => caught("split_paths", || drop(std::env::split_paths("a:b"))), 8 => caught("process::id", || drop(std::process::id())), 9 => caught("File::open unwrap", || drop(std::fs::File::open("x").unwrap())), 10 => caught("bounds", || drop([0u8; 1][std::hint::black_box(2)])), 11 => caught("divide zero", || drop(42u64 / std::hint::black_box(0))), 12 => caught("wide overflow", || drop(i128::MIN / std::hint::black_box(-1))), _ => {} } } fn main() { body(); #[cfg(not(std_reference))] panic_probe(13); } #[no_mangle] pub extern "C" fn run() { main(); }