@@ -312,5 +312,163 @@ mod tests {
312312 "all concurrent callers should get the same cached Arc"
313313 ) ;
314314 }
315+
316+ // --- Part 10: WASM disabled after snapshot restore ---
317+ // Verifies that set_allow_wasm_code_generation_callback is not captured
318+ // in the snapshot — disable_wasm() must be re-applied after every restore.
319+ {
320+ let bridge_code = "(function() { globalThis.__wasm_test = true; })();" ;
321+ let blob = create_snapshot ( bridge_code) . expect ( "snapshot creation" ) ;
322+ let mut isolate = create_isolate_from_snapshot ( blob, None ) ;
323+
324+ // Apply WASM disable (same as session.rs does after restore)
325+ crate :: execution:: disable_wasm ( & mut isolate) ;
326+
327+ let scope = & mut v8:: HandleScope :: new ( & mut isolate) ;
328+ let context = v8:: Context :: new ( scope, Default :: default ( ) ) ;
329+ let scope = & mut v8:: ContextScope :: new ( scope, context) ;
330+
331+ // Attempt WebAssembly.compile — should throw
332+ let wasm_test_code = r#"
333+ (function() {
334+ try {
335+ var bytes = new Uint8Array([0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00]);
336+ new WebAssembly.Module(bytes);
337+ return "ALLOWED";
338+ } catch (e) {
339+ return "BLOCKED:" + e.message;
340+ }
341+ })()
342+ "# ;
343+ let source = v8:: String :: new ( scope, wasm_test_code) . unwrap ( ) ;
344+ let script = v8:: Script :: compile ( scope, source, None ) . unwrap ( ) ;
345+ let result = script. run ( scope) . unwrap ( ) ;
346+ let result_str = result. to_rust_string_lossy ( scope) ;
347+
348+ assert ! (
349+ result_str. starts_with( "BLOCKED:" ) ,
350+ "WASM should be blocked after snapshot restore + disable_wasm(), got: {}" ,
351+ result_str
352+ ) ;
353+ }
354+
355+ // --- Part 11: Session isolation — fresh contexts from same snapshot ---
356+ // Verifies that state set in one session's context does not leak
357+ // to another session's context (fresh context per session).
358+ {
359+ let bridge_code = "(function() { globalThis.__shared_bridge = 'ok'; })();" ;
360+ let blob = create_snapshot ( bridge_code) . expect ( "snapshot creation" ) ;
361+ let blob_bytes: Vec < u8 > = blob. to_vec ( ) ;
362+
363+ // "Session A": set a global variable
364+ {
365+ let mut isolate = create_isolate_from_snapshot ( blob_bytes. clone ( ) , None ) ;
366+ let scope = & mut v8:: HandleScope :: new ( & mut isolate) ;
367+ let context = v8:: Context :: new ( scope, Default :: default ( ) ) ;
368+ let scope = & mut v8:: ContextScope :: new ( scope, context) ;
369+
370+ let source = v8:: String :: new ( scope, "globalThis.__session_secret = 'session-a-data';" ) . unwrap ( ) ;
371+ let script = v8:: Script :: compile ( scope, source, None ) . unwrap ( ) ;
372+ script. run ( scope) ;
373+
374+ // Verify session A can see its own data
375+ let check = v8:: String :: new ( scope, "globalThis.__session_secret" ) . unwrap ( ) ;
376+ let script = v8:: Script :: compile ( scope, check, None ) . unwrap ( ) ;
377+ let result = script. run ( scope) . unwrap ( ) ;
378+ assert_eq ! ( result. to_rust_string_lossy( scope) , "session-a-data" ) ;
379+ }
380+
381+ // "Session B": fresh context from same snapshot should NOT see session A's data
382+ {
383+ let mut isolate = create_isolate_from_snapshot ( blob_bytes. clone ( ) , None ) ;
384+ let scope = & mut v8:: HandleScope :: new ( & mut isolate) ;
385+ let context = v8:: Context :: new ( scope, Default :: default ( ) ) ;
386+ let scope = & mut v8:: ContextScope :: new ( scope, context) ;
387+
388+ let source = v8:: String :: new ( scope, "typeof globalThis.__session_secret" ) . unwrap ( ) ;
389+ let script = v8:: Script :: compile ( scope, source, None ) . unwrap ( ) ;
390+ let result = script. run ( scope) . unwrap ( ) ;
391+ assert_eq ! (
392+ result. to_rust_string_lossy( scope) ,
393+ "undefined" ,
394+ "session B should not see session A's global state"
395+ ) ;
396+ }
397+ }
398+
399+ // --- Part 12: External references survive snapshot restore ---
400+ // Verifies that FunctionTemplates registered on a restored isolate
401+ // correctly dispatch to Rust bridge callbacks via external_refs().
402+ {
403+ use std:: cell:: RefCell ;
404+ use crate :: bridge:: {
405+ register_sync_bridge_fns, register_async_bridge_fns,
406+ SessionBuffers , PendingPromises ,
407+ } ;
408+ use crate :: host_call:: BridgeCallContext ;
409+
410+ let bridge_code = "(function() { globalThis.__ext_ref_test = true; })();" ;
411+ let blob = create_snapshot ( bridge_code) . expect ( "snapshot creation" ) ;
412+ let mut isolate = create_isolate_from_snapshot ( blob, None ) ;
413+ crate :: execution:: disable_wasm ( & mut isolate) ;
414+
415+ // Create minimal BridgeCallContext (sync call will fail but we
416+ // test that the FunctionTemplate dispatches without crash)
417+ let ( ipc_tx, _ipc_rx) = crossbeam_channel:: unbounded :: < Vec < u8 > > ( ) ;
418+ let ( cmd_tx, cmd_rx) = crossbeam_channel:: unbounded :: < crate :: session:: SessionCommand > ( ) ;
419+ let call_id_router: crate :: host_call:: CallIdRouter =
420+ Arc :: new ( Mutex :: new ( std:: collections:: HashMap :: new ( ) ) ) ;
421+
422+ let receiver = crate :: host_call:: ReaderResponseReceiver :: new (
423+ Box :: new ( std:: io:: Cursor :: new ( Vec :: < u8 > :: new ( ) ) ) ,
424+ ) ;
425+ let sender = crate :: host_call:: ChannelFrameSender :: new ( ipc_tx) ;
426+ let bridge_ctx = BridgeCallContext :: with_receiver (
427+ Box :: new ( sender) ,
428+ Box :: new ( receiver) ,
429+ "test-session" . to_string ( ) ,
430+ call_id_router,
431+ ) ;
432+ let session_buffers = RefCell :: new ( SessionBuffers :: new ( ) ) ;
433+ let pending = PendingPromises :: new ( ) ;
434+
435+ let scope = & mut v8:: HandleScope :: new ( & mut isolate) ;
436+ let context = v8:: Context :: new ( scope, Default :: default ( ) ) ;
437+ let scope = & mut v8:: ContextScope :: new ( scope, context) ;
438+
439+ // Register bridge functions on the restored isolate
440+ let _sync_store = register_sync_bridge_fns (
441+ scope,
442+ & bridge_ctx as * const BridgeCallContext ,
443+ & session_buffers as * const RefCell < SessionBuffers > ,
444+ & [ "_testSync" ] ,
445+ ) ;
446+ let _async_store = register_async_bridge_fns (
447+ scope,
448+ & bridge_ctx as * const BridgeCallContext ,
449+ & pending as * const PendingPromises ,
450+ & session_buffers as * const RefCell < SessionBuffers > ,
451+ & [ "_testAsync" ] ,
452+ ) ;
453+
454+ // Verify the functions exist as globals
455+ let check = v8:: String :: new ( scope, "typeof _testSync" ) . unwrap ( ) ;
456+ let script = v8:: Script :: compile ( scope, check, None ) . unwrap ( ) ;
457+ let result = script. run ( scope) . unwrap ( ) ;
458+ assert_eq ! (
459+ result. to_rust_string_lossy( scope) ,
460+ "function" ,
461+ "_testSync should be a function on restored isolate"
462+ ) ;
463+
464+ let check = v8:: String :: new ( scope, "typeof _testAsync" ) . unwrap ( ) ;
465+ let script = v8:: Script :: compile ( scope, check, None ) . unwrap ( ) ;
466+ let result = script. run ( scope) . unwrap ( ) ;
467+ assert_eq ! (
468+ result. to_rust_string_lossy( scope) ,
469+ "function" ,
470+ "_testAsync should be a function on restored isolate"
471+ ) ;
472+ }
315473 }
316474}
0 commit comments