33use std:: cell:: RefCell ;
44use std:: collections:: HashMap ;
55use std:: ffi:: c_void;
6+ use std:: sync:: atomic:: { AtomicBool , Ordering } ;
67use std:: sync:: OnceLock ;
78
89use v8:: MapFnTo ;
@@ -11,6 +12,28 @@ use v8::ValueSerializerHelper;
1112
1213use crate :: host_call:: BridgeCallContext ;
1314
15+ // JSON codec flag: when true, use JSON.stringify/JSON.parse instead of V8
16+ // ValueSerializer/ValueDeserializer for IPC payloads. Activated by
17+ // SECURE_EXEC_V8_CODEC=json for runtimes whose node:v8 module doesn't
18+ // produce real V8 serialization format (e.g. Bun).
19+ static USE_JSON_CODEC : AtomicBool = AtomicBool :: new ( false ) ;
20+
21+ /// Initialize the codec from the SECURE_EXEC_V8_CODEC environment variable.
22+ /// Call once at process startup before any sessions are created.
23+ pub fn init_codec ( ) {
24+ if let Ok ( val) = std:: env:: var ( "SECURE_EXEC_V8_CODEC" ) {
25+ if val == "json" {
26+ USE_JSON_CODEC . store ( true , Ordering :: Relaxed ) ;
27+ eprintln ! ( "secure-exec-v8: using JSON codec for IPC payloads" ) ;
28+ }
29+ }
30+ }
31+
32+ /// Returns true if the JSON codec is active.
33+ pub fn is_json_codec ( ) -> bool {
34+ USE_JSON_CODEC . load ( Ordering :: Relaxed )
35+ }
36+
1437/// External references for V8 snapshot serialization.
1538/// Maps function pointer indices in the snapshot to current addresses.
1639/// Must be identical at snapshot creation and restore time.
@@ -50,10 +73,14 @@ impl v8::ValueDeserializerImpl for DefaultDeserializerDelegate {}
5073/// Serialize a V8 value to bytes using V8's built-in ValueSerializer.
5174/// Handles all V8 types natively: primitives, strings, arrays, objects,
5275/// Uint8Array, Date, Map, Set, RegExp, Error, and circular references.
76+ /// When JSON codec is active, uses JSON.stringify instead.
5377pub fn serialize_v8_value (
5478 scope : & mut v8:: HandleScope ,
5579 value : v8:: Local < v8:: Value > ,
5680) -> Result < Vec < u8 > , String > {
81+ if is_json_codec ( ) {
82+ return serialize_json_value ( scope, value) ;
83+ }
5784 let context = scope. get_current_context ( ) ;
5885 let serializer = v8:: ValueSerializer :: new ( scope, Box :: new ( DefaultSerializerDelegate ) ) ;
5986 serializer. write_header ( ) ;
@@ -85,6 +112,10 @@ pub fn deserialize_v8_value<'s>(
85112 scope : & mut v8:: HandleScope < ' s > ,
86113 data : & [ u8 ] ,
87114) -> Result < v8:: Local < ' s , v8:: Value > , String > {
115+ // When JSON codec is active, incoming payloads are JSON, not V8 binary
116+ if is_json_codec ( ) {
117+ return deserialize_json_value ( scope, data) ;
118+ }
88119 let context = scope. get_current_context ( ) ;
89120 let deserializer =
90121 v8:: ValueDeserializer :: new ( scope, Box :: new ( DefaultDeserializerDelegate ) , data) ;
@@ -96,6 +127,32 @@ pub fn deserialize_v8_value<'s>(
96127 . ok_or_else ( || "V8 ValueDeserializer: failed to deserialize value" . to_string ( ) )
97128}
98129
130+ /// Serialize a V8 value to JSON bytes using V8's built-in JSON.stringify.
131+ /// Used when SECURE_EXEC_V8_CODEC=json for runtimes like Bun.
132+ pub fn serialize_json_value (
133+ scope : & mut v8:: HandleScope ,
134+ value : v8:: Local < v8:: Value > ,
135+ ) -> Result < Vec < u8 > , String > {
136+ let context = scope. get_current_context ( ) ;
137+ let json_str = v8:: json:: stringify ( scope, value)
138+ . ok_or_else ( || "JSON.stringify failed" . to_string ( ) ) ?;
139+ let _ = context; // context used implicitly by stringify
140+ Ok ( json_str. to_rust_string_lossy ( scope) . into_bytes ( ) )
141+ }
142+
143+ /// Deserialize JSON bytes to a V8 value using V8's built-in JSON.parse.
144+ pub fn deserialize_json_value < ' s > (
145+ scope : & mut v8:: HandleScope < ' s > ,
146+ data : & [ u8 ] ,
147+ ) -> Result < v8:: Local < ' s , v8:: Value > , String > {
148+ let json_str = std:: str:: from_utf8 ( data)
149+ . map_err ( |e| format ! ( "JSON codec: invalid UTF-8: {}" , e) ) ?;
150+ let v8_str = v8:: String :: new ( scope, json_str)
151+ . ok_or_else ( || "JSON codec: failed to create V8 string" . to_string ( ) ) ?;
152+ v8:: json:: parse ( scope, v8_str)
153+ . ok_or_else ( || "JSON codec: JSON.parse failed" . to_string ( ) )
154+ }
155+
99156/// Pre-allocated serialization buffers reused across bridge calls within a session.
100157/// Grows to high-water mark; cleared (not deallocated) between calls via buf.clear().
101158pub struct SessionBuffers {
0 commit comments