197 lines
5.3 KiB
Rust
197 lines
5.3 KiB
Rust
use super::*;
|
|
fn run(s: &str) -> Result<String, Error> {
|
|
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());
|
|
}
|