use jsdet_core::{Bridge, CompiledModule, EmptyBridge, Error, SandboxConfig, Value};
use std::sync::Arc;
use std::thread;
fn module() -> Result<CompiledModule, Box<dyn std::error::Error>> {
let m = CompiledModule::new().map_err(|e| e.to_string())?;
Ok(m)
}
fn config() -> SandboxConfig {
SandboxConfig::default()
}
#[test]
fn test_oom_mid_operation() -> Result<(), Box<dyn std::error::Error>> {
let m = module()?;
let mut cfg = config();
cfg.max_memory_bytes = 4 * 1024 * 1024;
cfg.timeout_ms = 5000;
let script = "
let arr = [];
for (let i = 0; i < 1000000; i++) {
arr.push('x'.repeat(10000));
}
"
.to_string();
let result = m.execute(&[script], Arc::new(EmptyBridge), &cfg);
match result {
Ok(res) => {
assert!(
!res.errors.is_empty(),
"OOM should result in a JS-level error if not a trap"
);
}
Err(e) => {
assert!(
matches!(e, Error::Trap(_) | Error::MemoryExceeded { .. }),
"Expected Trap or MemoryExceeded, got: {:?}",
e
);
}
}
let normal_script = "1 + 1".to_string();
let result_normal = m.execute(&[normal_script], Arc::new(EmptyBridge), &config());
let ok_res =
result_normal.expect("Module must remain usable after an OOM in a sandboxed instance");
assert!(
ok_res.errors.is_empty(),
"Normal script must run clean after an OOM failure in a prior instance"
);
Ok(())
}
#[test]
fn test_io_error_injection_cached_module() -> Result<(), Box<dyn std::error::Error>> {
let result_empty = CompiledModule::load_cached(&[]);
match result_empty {
Ok(_) => panic!("Empty cache load must fail with WasmInit error"),
Err(e) => assert!(
matches!(e, Error::WasmInit(_)),
"Expected WasmInit error, got {:?}",
e
),
}
let result_single = CompiledModule::load_cached(&[0x00]);
match result_single {
Ok(_) => panic!("Single byte cache load must fail with WasmInit error"),
Err(e) => assert!(
matches!(e, Error::WasmInit(_)),
"Expected WasmInit error, got {:?}",
e
),
}
let m = module()?;
let valid_bytes = m.serialize().map_err(|e| e.to_string())?;
assert!(
!valid_bytes.is_empty(),
"Serialized bytes must not be empty"
);
if valid_bytes.len() > 100 {
let truncated = &valid_bytes[0..valid_bytes.len() / 2];
let result_trunc = CompiledModule::load_cached(truncated);
match result_trunc {
Ok(_) => panic!("Truncated cache load must fail with WasmInit error"),
Err(e) => assert!(
matches!(e, Error::WasmInit(_)),
"Expected WasmInit error, got {:?}",
e
),
}
}
Ok(())
}
#[test]
fn test_concurrent_stress_32_threads() -> Result<(), Box<dyn std::error::Error>> {
let m = Arc::new(module()?);
let mut handles = vec![];
for i in 0..32 {
let module_clone = Arc::clone(&m);
handles.push(thread::spawn(move || {
let cfg = config();
let script = format!("var a = {}; a + a;", i);
module_clone.execute(&[script], Arc::new(EmptyBridge), &cfg)
}));
}
for (i, handle) in handles.into_iter().enumerate() {
let result = handle
.join()
.map_err(|_| format!("Thread {} panicked", i))?;
let res = result.unwrap_or_else(|e| panic!("Thread {} execution failed: {:?}", i, e));
assert!(
res.errors.is_empty(),
"Thread {} produced JS execution errors",
i
);
}
Ok(())
}
#[test]
fn test_adversarial_input_boundaries() -> Result<(), Box<dyn std::error::Error>> {
let m = module()?;
let cfg = config();
let zeros = vec![0u8; 1024];
let res_zeros = CompiledModule::load_cached(&zeros);
match res_zeros {
Ok(_) => panic!("All-zero bytes must result in WasmInit error"),
Err(e) => assert!(
matches!(e, Error::WasmInit(_)),
"Expected WasmInit error, got {:?}",
e
),
}
let ffs = vec![0xFFu8; 1024];
let res_ffs = CompiledModule::load_cached(&ffs);
match res_ffs {
Ok(_) => panic!("All-0xFF bytes must result in WasmInit error"),
Err(e) => assert!(
matches!(e, Error::WasmInit(_)),
"Expected WasmInit error, got {:?}",
e
),
}
let mut alt = vec![0u8; 1024];
for i in 0..1024 {
alt[i] = if i % 2 == 0 { 0xAA } else { 0x55 };
}
let res_alt = CompiledModule::load_cached(&alt);
match res_alt {
Ok(_) => panic!("Alternating bytes must result in WasmInit error"),
Err(e) => assert!(
matches!(e, Error::WasmInit(_)),
"Expected WasmInit error, got {:?}",
e
),
}
let res_empty_script = m.execute(&["".to_string()], Arc::new(EmptyBridge), &cfg)?;
assert!(
res_empty_script.errors.is_empty(),
"Empty script must run without internal errors"
);
let null_script = String::from_utf8(vec![0x00; 100_000]).unwrap_or_default();
let res_null = m.execute(&[null_script], Arc::new(EmptyBridge), &cfg);
match res_null {
Ok(res) => assert!(
!res.errors.is_empty(),
"Null bytes script should result in a parsing error"
),
Err(e) => assert!(
matches!(e, Error::Trap(_)),
"Null bytes script failing via trap is acceptable: {:?}",
e
),
}
let collision_script = "
let obj = {};
for(let i=0; i<10000; i++) {
obj['a' + i] = i;
}
"
.to_string();
let res_collision = m.execute(&[collision_script], Arc::new(EmptyBridge), &cfg)?;
assert!(
res_collision.errors.is_empty(),
"Hash collision script failed with JS errors"
);
Ok(())
}
#[test]
fn test_integer_overflow_probes() -> Result<(), Box<dyn std::error::Error>> {
let m = module()?;
let mut cfg = config();
cfg.max_scripts = usize::MAX;
cfg.max_script_bytes = usize::MAX;
cfg.max_total_script_bytes = usize::MAX;
cfg.max_observations = usize::MAX;
cfg.max_fuel = u64::MAX;
let res_extreme = m.execute(&["var a = 1;".to_string()], Arc::new(EmptyBridge), &cfg)?;
assert!(
res_extreme.errors.is_empty(),
"Extreme configuration caused script execution to fail"
);
let mut scripts = Vec::new();
for _ in 0..256 {
scripts.push("1;".to_string());
}
let res_256 = m.execute(&scripts, Arc::new(EmptyBridge), &config())?;
assert!(
res_256.errors.is_empty(),
"Executing 256 scripts resulted in internal errors"
);
let overflow_observations = "
for(let i=0; i<100000; i++) {
// Force bridge calls to generate observations
try { fetch('http://example.com/' + i); } catch(e) {}
}
"
.to_string();
let res_overflow = m.execute(&[overflow_observations], Arc::new(EmptyBridge), &config())?;
assert!(
res_overflow.observations.len() <= config().max_observations,
"Observations exceeded maximum limit"
);
Ok(())
}
struct MaliciousBridge;
impl Bridge for MaliciousBridge {
fn call(&self, _api: &str, _args: &[Value]) -> Result<Value, String> {
Ok(Value::Null)
}
fn get_property(&self, _object: &str, _property: &str) -> Result<Value, String> {
Ok(Value::Null)
}
fn set_property(&self, _object: &str, _property: &str, _value: &Value) -> Result<(), String> {
Ok(())
}
fn provided_globals(&self) -> Vec<String> {
vec!["fetch".to_string()]
}
fn bootstrap_js(&self) -> String {
"function fetch() { Bridge.call('fetch', Array.prototype.slice.call(arguments)); }"
.to_string()
}
}
#[test]
fn test_bridge_adversarial() -> Result<(), Box<dyn std::error::Error>> {
let m = module()?;
let cfg = config();
let script = "
try {
fetch('http://example.com');
} catch(e) {}
"
.to_string();
let res = m.execute(&[script], Arc::new(MaliciousBridge), &cfg)?;
assert!(
res.errors.is_empty(),
"Malicious Bridge call caused JS errors"
);
Ok(())
}