use super::*; fn run(s: &str) -> Result { evaluate(s, "null", None, Limits::default()) } #[test] fn module_default_and_named() { assert_eq!( evaluate( "export default x => ({value:x}); export const other=x=>x+1", "42", None, Limits::default() ) .unwrap(), "{\"value\":42}" ); assert_eq!( evaluate( "export const other=x=>x+1", "42", Some("other"), Limits::default() ) .unwrap(), "43" ); assert_eq!(run("export default x=>null").unwrap(), "null"); } #[test] fn promise_settlement() { assert_eq!( run("export default async x => { await Promise.resolve(); return 42; }").unwrap(), "42" ); assert_eq!( run("await Promise.resolve(); export default x=>true").unwrap(), "true" ); for s in [ "export default x=>Promise.reject(new Error('rejected'))", "export default async x=>{ throw 'rejected'; }", "await Promise.reject('rejected'); export default x=>true", ] { let e = run(s).unwrap_err(); assert_eq!(e.kind, ErrorKind::Throw); assert_eq!(e.message, "rejected"); } assert_eq!( run("export default x=>new Promise(()=>{})") .unwrap_err() .kind, ErrorKind::Engine ); } #[test] fn typed_errors() { assert_eq!(run("export default (").unwrap_err().kind, ErrorKind::Syntax); assert_eq!( run("export default x=>{throw new Error('oops')}") .unwrap_err() .kind, ErrorKind::Throw ); for s in [ "export default x=>undefined", "export default x=>(()=>1)", "export default x=>{let a={};a.a=a;return a}", "export default x=>Symbol()", ] { assert_eq!(run(s).unwrap_err().kind, ErrorKind::NonJson); } assert_eq!( evaluate("export default x=>x", "not json", None, Limits::default()) .unwrap_err() .kind, ErrorKind::Syntax ); assert_eq!(run("export const x=1").unwrap_err().kind, ErrorKind::Engine); assert_eq!(run("export default 1").unwrap_err().kind, ErrorKind::Engine); } #[test] fn recursion_is_bounded() { for stack in [4096, 16384, usize::MAX] { let e = evaluate( "export default x=>{function f(){return f()+1}return f()}", "null", None, Limits { max_stack_bytes: Some(stack), ..Limits::default() }, ) .unwrap_err(); eprintln!("STACK {stack}: {e:?}"); assert_eq!(e.kind, ErrorKind::Limit); assert!(e.message.contains("stack")); } } #[test] fn runaway_loop_and_jobs() { for s in [ "export default x=>{while(true){}}", "while(true){};export default x=>x", "export default x=>({toJSON(){while(true){}}})", "export default x=>{throw {get message(){while(true){}}}}", "export default x=>new Promise(()=>{function f(){Promise.resolve().then(f)};f()})", ] { let start = Instant::now(); assert_eq!(run(s).unwrap_err().kind, ErrorKind::Limit); assert!(start.elapsed() < Duration::from_secs(2)); } } #[test] fn runaway_allocation() { let e = evaluate( "export default x=>{let a=[];while(1)a.push('abcdefghijklmnopqrstuvwxyz'+a.length)}", "null", None, Limits { max_time_ms: Some(1000), max_memory_bytes: Some(1024 * 1024), ..Limits::default() }, ) .unwrap_err(); eprintln!("HEAP {e:?}"); assert_eq!(e.kind, ErrorKind::Limit); assert!(e.message.contains("memory")); } #[test] fn forbidden_globals_and_imports() { for name in [ "Date", "performance", "Math.random", "fetch", "XMLHttpRequest", "require", "process", "std", "os", "fs", "crypto", "console", "setTimeout", ] { assert_eq!( run(&format!("export default x=>typeof {name}")).unwrap(), "\"undefined\"", "{name}" ); } assert!(run("import x from 'fs';export default x=>x").is_err()); assert_eq!( run("export default x=>import('fs')").unwrap_err().kind, ErrorKind::Throw ); } #[test] fn isolation_and_limits() { run("export default x=>{globalThis.leak=42;return true}").unwrap(); assert_eq!( run("export default x=>typeof leak").unwrap(), "\"undefined\"" ); assert_eq!( Limits { max_time_ms: Some(u64::MAX), max_memory_bytes: Some(usize::MAX), max_stack_bytes: Some(usize::MAX) } .clamped() .max_time_ms, Some(1000) ); assert_eq!( Limits { max_time_ms: Some(0), ..Limits::default() } .clamped() .max_time_ms, Some(1) ); } #[test] fn timings() { let start = Instant::now(); assert_eq!(run("export default x=>true").unwrap(), "true"); eprintln!("TIMING first eval {}us", start.elapsed().as_micros()); let start = Instant::now(); for _ in 0..100 { assert_eq!(run("export default x=>true").unwrap(), "true"); } eprintln!("TIMING 100 evals {}us", start.elapsed().as_micros()); }