diff --git a/crates/oxide_cli/src/main.rs b/crates/oxide_cli/src/main.rs index 9ff61b8d..f017a25a 100644 --- a/crates/oxide_cli/src/main.rs +++ b/crates/oxide_cli/src/main.rs @@ -322,7 +322,12 @@ fn main() -> ExitCode { /// - watchdog spawn 脱离的 server 子进程,崩溃后经同一入口重启。 fn server_command(command: ServerCommands) -> ExitCode { match command { - ServerCommands::Start { rm, idle_timeout, workers, foreground } => { + ServerCommands::Start { + rm, + idle_timeout, + workers, + foreground, + } => { if rm { let mut config = RmServerConfig { idle_timeout: Duration::from_secs(idle_timeout), @@ -427,11 +432,7 @@ fn server_command(command: ServerCommands) -> ExitCode { /// 渲染 `ForgeStatus` 为多行文本:首行条目数,容量 / gc / 清缓存 / lookup /// 行按旗标结果附加。 fn render_forge_status( - target: ForgeTarget, - entries: usize, - capacity: usize, - gc_collected: Option, - cache_cleared: bool, + target: ForgeTarget, entries: usize, capacity: usize, gc_collected: Option, cache_cleared: bool, lookup: Option, ) -> String { let mut lines = vec![format!("{} forge: {entries} entries", target.as_str())]; @@ -573,10 +574,7 @@ fn restart_command() -> ExitCode { fn log_command(follow: bool, level: Option, lines: Option) -> ExitCode { let path = sidecar::well_known_log_path(); if !path.exists() { - eprintln!( - "{}", - Red.paint(format!("日志文件不存在:{}(server 可能未启动过)", path.display())) - ); + eprintln!("{}", Red.paint(format!("日志文件不存在:{}(server 可能未启动过)", path.display()))); return ExitCode::FAILURE; } diff --git a/crates/oxide_cli/src/server/forge.rs b/crates/oxide_cli/src/server/forge.rs index 9bc696f1..d9d07614 100644 --- a/crates/oxide_cli/src/server/forge.rs +++ b/crates/oxide_cli/src/server/forge.rs @@ -31,11 +31,7 @@ use super::protocol::{ForgeTarget, LookupResult, ServerResponse}; /// - --gc 重置各空闲 VM 的 session_bytes_allocated 为清扫后存活字节。 /// - --clear-cache 清空字节码缓存(后续 eval 重编译)。 pub fn handle_forge_query( - target: ForgeTarget, - gc: bool, - clear_cache: bool, - lookup: Option, - kernel: &Arc, + target: ForgeTarget, gc: bool, clear_cache: bool, lookup: Option, kernel: &Arc, pool: &Arc, ) -> ServerResponse { // 读目标 forge 条目数与容量。 @@ -140,7 +136,8 @@ mod tests { other => panic!("应得 ForgeStatus 帧,实得 {other:?}"), } - let response = handle_forge_query(ForgeTarget::String, false, false, Some("not-interned".into()), &kernel, &pool); + let response = + handle_forge_query(ForgeTarget::String, false, false, Some("not-interned".into()), &kernel, &pool); match response { ServerResponse::ForgeStatus { lookup, .. } => { assert_eq!(lookup, Some(LookupResult::Absent), "未 intern 键应得 Absent"); diff --git a/crates/oxide_cli/src/server/liveness.rs b/crates/oxide_cli/src/server/liveness.rs index ecff1f9a..ad4c2afd 100644 --- a/crates/oxide_cli/src/server/liveness.rs +++ b/crates/oxide_cli/src/server/liveness.rs @@ -146,7 +146,8 @@ pub fn liveness_scan_with_waits( /// - 可能向 sidecar 记录的进程发 SIGTERM;可能删除 socket 与 sidecar 文件。 /// - 杀进程决策与失败记入跨重启保留的日志文件。 fn kill_zombie( - id: &sidecar::ServerIdentity, sidecar_path: &Path, socket_path: &Path, zombie_exit_window: Duration, kill_wait: Duration, + id: &sidecar::ServerIdentity, sidecar_path: &Path, socket_path: &Path, zombie_exit_window: Duration, + kill_wait: Duration, ) -> LivenessOutcome { // 有界等待 sidecar 消失:覆盖旧 server 退出序列窗口(排水 5 秒,sidecar // 最后删除)。消失即无操作,`claim_sidecar` 的 O_EXCL 创建成功。 @@ -313,11 +314,8 @@ mod tests { let sidecar = dir.path("sidecar.json"); let socket = dir.path("server.sock"); let listener = UnixListener::bind(&socket).expect("绑定监听者应成功"); - sidecar::write_exclusive( - &sidecar::ServerIdentity::new(socket.to_str().unwrap(), "0.0.0"), - &sidecar, - ) - .expect("写 sidecar 应成功"); + sidecar::write_exclusive(&sidecar::ServerIdentity::new(socket.to_str().unwrap(), "0.0.0"), &sidecar) + .expect("写 sidecar 应成功"); let outcome = liveness_scan(&sidecar, &socket); assert_eq!(outcome, LivenessOutcome::Noop, "存活 socket 应判无操作"); assert!(sidecar.exists(), "sidecar 应原样"); @@ -395,7 +393,8 @@ mod tests { sidecar::write_exclusive(&id, &sidecar).expect("写 sidecar 应成功"); // 注入短僵尸退出窗口(500 毫秒)加短杀进程等待(300 毫秒):子进程忽略 // SIGTERM,扫描判杀进程失败,文件原样。 - let outcome = liveness_scan_with_waits(&sidecar, &socket, Duration::from_millis(500), Duration::from_millis(300)); + let outcome = + liveness_scan_with_waits(&sidecar, &socket, Duration::from_millis(500), Duration::from_millis(300)); assert_eq!(outcome, LivenessOutcome::KillFailed, "忽略 SIGTERM 应判杀进程失败"); assert!(sidecar.exists(), "sidecar 应原样"); assert!(!socket.exists(), "socket 文件应保持不存在"); diff --git a/crates/oxide_cli/src/server/log.rs b/crates/oxide_cli/src/server/log.rs index d4a47967..0e37c41d 100644 --- a/crates/oxide_cli/src/server/log.rs +++ b/crates/oxide_cli/src/server/log.rs @@ -45,7 +45,11 @@ pub fn line_level(line: &str) -> Option { /// `ERROR`/`WARN`/`INFO` 行。 pub fn filter_tail(lines: &[String], min_level: Option, last_n: Option) -> Vec { let mut filtered: Vec = match min_level { - Some(min) => lines.iter().filter(|line| line_level(line).is_some_and(|level| level <= min)).cloned().collect(), + Some(min) => lines + .iter() + .filter(|line| line_level(line).is_some_and(|level| level <= min)) + .cloned() + .collect(), None => lines.to_vec(), }; match last_n { @@ -85,7 +89,10 @@ pub fn follow(path: &Path, min_level: Option) { /// # 副作用 /// - 向 writer 写行。 fn follow_to(path: &Path, min_level: Option, writer: &mut W) { - let mut file = OpenOptions::new().read(true).open(path).expect("日志文件应存在(调用方已检查)"); + let mut file = OpenOptions::new() + .read(true) + .open(path) + .expect("日志文件应存在(调用方已检查)"); let mut offset = file.seek(SeekFrom::End(0)).expect("定位日志文件末尾应成功"); let mut buf = [0u8; 8192]; let mut pending = String::new(); @@ -176,11 +183,7 @@ mod tests { // info 留 ERROR/WARN/INFO 行,丢 DEBUG 行。 assert_eq!( filter_tail(&lines, Some(Level::Info), None), - vec![ - "1700000000.001 INFO a: one", - "1700000000.002 ERROR a: two", - "1700000000.004 WARN a: four", - ] + vec!["1700000000.001 INFO a: one", "1700000000.002 ERROR a: two", "1700000000.004 WARN a: four",] ); // debug 留 DEBUG 与更严重行(全留);trace 全留。 assert_eq!(filter_tail(&lines, Some(Level::Debug), None), lines); diff --git a/crates/oxide_cli/src/server/mod.rs b/crates/oxide_cli/src/server/mod.rs index b8ee91fe..15f4d7cf 100644 --- a/crates/oxide_cli/src/server/mod.rs +++ b/crates/oxide_cli/src/server/mod.rs @@ -41,7 +41,7 @@ pub mod server; pub mod sidecar; /// spawn 与就绪探测:start、restart、watchdog 三条路径共用的唯一共享入口。 pub mod spawn; -/// worker 池:固定 N 常驻 worker、各持自有 VM 池、mpsc 轮询路由。 -pub mod workers; /// watchdog:前台监控进程,三态裁决加崩溃预算,崩溃后自动重启。 pub mod watchdog; +/// worker 池:固定 N 常驻 worker、各持自有 VM 池、mpsc 轮询路由。 +pub mod workers; diff --git a/crates/oxide_cli/src/server/protocol.rs b/crates/oxide_cli/src/server/protocol.rs index c5b3cab9..595584f9 100644 --- a/crates/oxide_cli/src/server/protocol.rs +++ b/crates/oxide_cli/src/server/protocol.rs @@ -201,12 +201,20 @@ impl ServerResponse { } /// 请求帧可取的 `type` 值(须与 `ServerRequest` 变体保持同步)。 -const REQUEST_TAGS: &[&str] = - &["eval", "forge_query", "version", "status", "health", "info", "shutdown", "yield"]; +const REQUEST_TAGS: &[&str] = &["eval", "forge_query", "version", "status", "health", "info", "shutdown", "yield"]; /// 响应帧可取的 `type` 值(须与 `ServerResponse` 变体保持同步)。 -const RESPONSE_TAGS: &[&str] = - &["eval_result", "forge_status", "version", "status", "health", "info", "shutdown", "yield", "error"]; +const RESPONSE_TAGS: &[&str] = &[ + "eval_result", + "forge_status", + "version", + "status", + "health", + "info", + "shutdown", + "yield", + "error", +]; /// 序列化请求为帧字符串(含结尾换行)。 /// diff --git a/crates/oxide_cli/src/server/server.rs b/crates/oxide_cli/src/server/server.rs index 85aa8101..d9aaa42a 100644 --- a/crates/oxide_cli/src/server/server.rs +++ b/crates/oxide_cli/src/server/server.rs @@ -208,9 +208,7 @@ pub fn run_server(config: &ServerConfig) -> Result<(), ServerError> { // 命令。 match liveness::liveness_scan(&config.sidecar_path, &config.socket_path) { liveness::LivenessOutcome::Noop | liveness::LivenessOutcome::Cleaned => {} - liveness::LivenessOutcome::KillFailed => { - return Err(ServerError::ClaimRefused(ClaimResult::RefusedAmbiguous)) - } + liveness::LivenessOutcome::KillFailed => return Err(ServerError::ClaimRefused(ClaimResult::RefusedAmbiguous)), } // 认领 sidecar:同版本拒绝启动,异版本走交接路径,存活证据不足即返回错误。 @@ -1208,11 +1206,17 @@ mod tests { fn yield_takeover_end_to_end() { let config = unique_paths("yield_e2e"); // A 用版本 0.0.1。 - let config_a = ServerConfig { version: "0.0.1".to_string(), ..config.clone() }; + let config_a = ServerConfig { + version: "0.0.1".to_string(), + ..config.clone() + }; let handle_a = start_server(config_a); // B 用版本 0.0.2:认领得 VersionMismatch 转交接路径。 - let config_b = ServerConfig { version: "0.0.2".to_string(), ..config.clone() }; + let config_b = ServerConfig { + version: "0.0.2".to_string(), + ..config.clone() + }; let handle_b = std::thread::spawn(move || run_server(&config_b)); // A 应在 30 秒内有界退出(排水加退出序列)。 diff --git a/crates/oxide_cli/src/server/spawn.rs b/crates/oxide_cli/src/server/spawn.rs index 4b094486..860551fd 100644 --- a/crates/oxide_cli/src/server/spawn.rs +++ b/crates/oxide_cli/src/server/spawn.rs @@ -30,10 +30,7 @@ pub fn spawn_detached_server(workers: Option) -> std::io::Result, - counters: Arc, - min_pool_size: usize, - max_pool_size: Option, + kernel: Arc, counters: Arc, min_pool_size: usize, max_pool_size: Option, worker_count: usize, ) -> (WorkerRouter, Vec>) { let n = worker_count.max(1); @@ -126,11 +123,7 @@ pub fn spawn_workers( fn worker_loop(receiver: mpsc::Receiver, kernel: Arc, pool: Arc) { for task in receiver { match task { - WorkerTask::Eval { - code, - max_steps, - reply, - } => { + WorkerTask::Eval { code, max_steps, reply } => { let response = eval::handle_eval(&code, max_steps, &kernel, &pool); let _ = reply.send(response); } diff --git a/crates/oxide_cli/tests/server_cli.rs b/crates/oxide_cli/tests/server_cli.rs index 8636f588..2af84179 100644 --- a/crates/oxide_cli/tests/server_cli.rs +++ b/crates/oxide_cli/tests/server_cli.rs @@ -81,8 +81,7 @@ fn server_help_lists_subcommands() { assert!(output.status.success(), "server --help 应退 0"); let stdout = String::from_utf8_lossy(&output.stdout); for name in [ - "start", "stop", "status", "health", "info", "version", "cleanup", "restart", "log", "forge", - "watchdog", + "start", "stop", "status", "health", "info", "version", "cleanup", "restart", "log", "forge", "watchdog", ] { assert!(stdout.contains(name), "help 应列出 {name}:{stdout}"); } @@ -124,10 +123,7 @@ fn server_control_arms_no_server_exit_1() { let output = oxide(&["server", sub]); assert_eq!(output.status.code(), Some(1), "{sub} 应退 1"); let stderr = String::from_utf8_lossy(&output.stderr); - assert!( - stderr.contains("无已注册的 server"), - "{sub} 应打印无 server 提示:{stderr}" - ); + assert!(stderr.contains("无已注册的 server"), "{sub} 应打印无 server 提示:{stderr}"); } } @@ -145,10 +141,7 @@ fn server_forge_no_server_exit_1() { let output = oxide(&["server", "forge", "code"]); assert_eq!(output.status.code(), Some(1), "forge 应退 1"); let stderr = String::from_utf8_lossy(&output.stderr); - assert!( - stderr.contains("无已注册的 server"), - "forge 应打印无 server 提示:{stderr}" - ); + assert!(stderr.contains("无已注册的 server"), "forge 应打印无 server 提示:{stderr}"); } /// cleanup 退 0(幂等:有残留则清理、无残留则报无残留),事后全局路径无文件。 diff --git a/crates/oxide_cli/tests/server_client.rs b/crates/oxide_cli/tests/server_client.rs index 7e7de720..2f201d5b 100644 --- a/crates/oxide_cli/tests/server_client.rs +++ b/crates/oxide_cli/tests/server_client.rs @@ -53,8 +53,7 @@ fn control_client_end_to_end() { // 状态请求:worker 池异步预热,轮询至总数不小于 1 后核对字段。 let deadline = Instant::now() + Duration::from_secs(5); let response = loop { - let response = - send_control_request_from(&config.sidecar_path, &ServerRequest::Status).expect("状态请求应成功"); + let response = send_control_request_from(&config.sidecar_path, &ServerRequest::Status).expect("状态请求应成功"); if let ServerResponse::Status { pool_total, .. } = &response { if *pool_total >= 1 { break response; @@ -89,12 +88,8 @@ fn control_client_end_to_end() { } // 关闭请求:得确认帧,server 线程正常退出,socket 与 sidecar 文件均被删。 - let response = - send_control_request_from(&config.sidecar_path, &ServerRequest::Shutdown).expect("关闭请求应成功"); - assert!( - matches!(response, ServerResponse::Shutdown), - "应得关闭确认帧:{response:?}" - ); + let response = send_control_request_from(&config.sidecar_path, &ServerRequest::Shutdown).expect("关闭请求应成功"); + assert!(matches!(response, ServerResponse::Shutdown), "应得关闭确认帧:{response:?}"); let result = handle.join().expect("server 线程应正常退出"); assert!(result.is_ok(), "server 应正常退出:{result:?}"); assert!(!config.socket_path.exists(), "socket 文件应被删除"); diff --git a/crates/oxide_cli/tests/server_signal_int.rs b/crates/oxide_cli/tests/server_signal_int.rs index 199653ae..62946ba9 100644 --- a/crates/oxide_cli/tests/server_signal_int.rs +++ b/crates/oxide_cli/tests/server_signal_int.rs @@ -13,11 +13,7 @@ use oxide_cli::server::server::{run_server, ServerConfig, ServerError}; /// 唯一临时路径(进程号加测试名):单进程内唯一,进程间以进程号隔离。 /// worker 数取小值 2:与既有单测同口径,大值徒增预热成本。 fn unique_paths(test_name: &str) -> ServerConfig { - let dir = std::env::temp_dir().join(format!( - "oxide_server_signal_int_{}_{}", - std::process::id(), - test_name - )); + let dir = std::env::temp_dir().join(format!("oxide_server_signal_int_{}_{}", std::process::id(), test_name)); std::fs::create_dir_all(&dir).expect("测试目录创建应成功"); ServerConfig { socket_path: dir.join("server.sock"), diff --git a/crates/oxide_cli/tests/server_signal_term.rs b/crates/oxide_cli/tests/server_signal_term.rs index df9a49a1..45e96202 100644 --- a/crates/oxide_cli/tests/server_signal_term.rs +++ b/crates/oxide_cli/tests/server_signal_term.rs @@ -13,11 +13,7 @@ use oxide_cli::server::server::{run_server, ServerConfig, ServerError}; /// 唯一临时路径(进程号加测试名):单进程内唯一,进程间以进程号隔离。 /// worker 数取小值 2:与既有单测同口径,大值徒增预热成本。 fn unique_paths(test_name: &str) -> ServerConfig { - let dir = std::env::temp_dir().join(format!( - "oxide_server_signal_term_{}_{}", - std::process::id(), - test_name - )); + let dir = std::env::temp_dir().join(format!("oxide_server_signal_term_{}_{}", std::process::id(), test_name)); std::fs::create_dir_all(&dir).expect("测试目录创建应成功"); ServerConfig { socket_path: dir.join("server.sock"), diff --git a/crates/oxide_types/src/private_key/tests.rs b/crates/oxide_types/src/private_key/tests.rs index 5a93402d..a531e923 100644 --- a/crates/oxide_types/src/private_key/tests.rs +++ b/crates/oxide_types/src/private_key/tests.rs @@ -105,7 +105,10 @@ fn symbol_key_realm_dimensions_distinct() { assert_ne!(encode_symbol_key(0, 15), encode_symbol_key(1, 15)); assert_ne!(encode_symbol_key(1, 15), encode_symbol_key(1, 16)); // 跨 realm 同名 well-known 符号也产生不同键。 - assert_ne!(encode_symbol_key(0, WELL_KNOWN_SYMBOL_ITERATOR), encode_symbol_key(2, WELL_KNOWN_SYMBOL_ITERATOR)); + assert_ne!( + encode_symbol_key(0, WELL_KNOWN_SYMBOL_ITERATOR), + encode_symbol_key(2, WELL_KNOWN_SYMBOL_ITERATOR) + ); } #[test] diff --git a/crates/oxide_vm/src/async_func.rs b/crates/oxide_vm/src/async_func.rs index 774ff4a0..1adbe527 100644 --- a/crates/oxide_vm/src/async_func.rs +++ b/crates/oxide_vm/src/async_func.rs @@ -88,7 +88,8 @@ impl Vm { &mut self, callee: JsValue, this_value: JsValue, args: &[JsValue], promise: JsValue, resolve: JsValue, reject: JsValue, ) -> JsValue { - let proto_val = JsValue::from_js_object(self.realm.session.borrow().builtin_world().object_proto.as_ptr() as *mut JsObject); + let proto_val = + JsValue::from_js_object(self.realm.session.borrow().builtin_world().object_proto.as_ptr() as *mut JsObject); let ptr = self.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, proto_val)); let obj = unsafe { &mut *ptr }; obj.type_tag = JsObject::OBJ_TYPE_ASYNC; @@ -607,13 +608,20 @@ pub(crate) fn init_async_intrinsics(vm: &mut Vm) { let af_ctor_fn_ptr = unsafe { NativeFnPtr::from_raw(oxide_builtins::function::async_function_constructor:: as *const ()) }; let (af_ctor_ptr, af_ctor_is_new) = - match vm.realm.session.borrow().builtin_world().find_fn_wrapper(af_reuse_key, af_ctor_fn_ptr, 1) { + match vm + .realm + .session + .borrow() + .builtin_world() + .find_fn_wrapper(af_reuse_key, af_ctor_fn_ptr, 1) + { Some(ptr) => (ptr, false), None => { // [[Prototype]] = Function 构造器本体(与 Function 构造器同链, // `Object.getPrototypeOf(AsyncFunction) === Function` 语义)。 - let fn_ctor_val = - JsValue::from_js_object(vm.realm.session.borrow().builtin_world().function_constructor.as_ptr() as *mut JsObject); + let fn_ctor_val = JsValue::from_js_object( + vm.realm.session.borrow().builtin_world().function_constructor.as_ptr() as *mut JsObject, + ); let mut af_ctor = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, fn_ctor_val)); af_ctor.set_function(true); af_ctor.set_native_arg_count(1); @@ -638,7 +646,11 @@ pub(crate) fn init_async_intrinsics(vm: &mut Vm) { af_ctor.set_shape_id(tag_shape); let af_ctor_ptr = Box::into_raw(af_ctor); // 登记进 world 释放表(带复用键):session 收尾统一释放构造器本体与属性区。 - vm.realm.session.borrow().builtin_world().track_fn_wrapper(af_ctor_ptr, af_reuse_key); + vm.realm + .session + .borrow() + .builtin_world() + .track_fn_wrapper(af_ctor_ptr, af_reuse_key); (af_ctor_ptr, true) } }; diff --git a/crates/oxide_vm/src/async_generator.rs b/crates/oxide_vm/src/async_generator.rs index 0c1575c6..9771fd17 100644 --- a/crates/oxide_vm/src/async_generator.rs +++ b/crates/oxide_vm/src/async_generator.rs @@ -103,7 +103,8 @@ impl Vm { pub(crate) fn create_async_generator_object( &mut self, callee: JsValue, this_value: JsValue, args: &[JsValue], ) -> Result { - let gen_proto_val = JsValue::from_js_object(self.realm.async_generator_proto.borrow().as_ptr() as *mut JsObject); + let gen_proto_val = + JsValue::from_js_object(self.realm.async_generator_proto.borrow().as_ptr() as *mut JsObject); let obj = self.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, gen_proto_val)); let obj_ref = unsafe { &mut *obj }; obj_ref.type_tag = JsObject::OBJ_TYPE_ASYNC_GENERATOR; @@ -1604,7 +1605,8 @@ pub(crate) fn init_async_generator_intrinsics(vm: &mut Vm) { let proto2_si = sf.intern("prototype").0; let proto2_shape = sh.make_shape(agf_proto.shape_id(), proto2_si); agf_proto.set_shape_id(proto2_shape); - let ppos2 = agf_proto.push_prop(JsValue::from_js_object(vm.realm.async_generator_proto.borrow().as_ptr() as *mut JsObject)); + let ppos2 = + agf_proto.push_prop(JsValue::from_js_object(vm.realm.async_generator_proto.borrow().as_ptr() as *mut JsObject)); agf_proto.set_data_meta(ppos2, PropAttributes::new(false, false, true)); // agf_proto[Symbol.toStringTag] = "AsyncGeneratorFunction"(数据属性,w/e/c = false/false/true)。 let tag2_key = oxide_types::private_key::encode_symbol_key( @@ -1624,7 +1626,8 @@ pub(crate) fn init_async_generator_intrinsics(vm: &mut Vm) { // SAFETY: agf_ctor_ptr 为 Box 原分配(已登记释放表、生命周期覆盖 session),本 Vm 独占。 unsafe { let ctor_mut = &mut *agf_ctor_ptr; - let proto_val = JsValue::from_js_object(vm.realm.async_generator_function_proto.borrow().as_ptr() as *mut JsObject); + let proto_val = + JsValue::from_js_object(vm.realm.async_generator_function_proto.borrow().as_ptr() as *mut JsObject); let name_val = JsValue::perm_string(sf.string_ptr(sf.intern("AsyncGeneratorFunction").0)); if agf_ctor_is_new { let lpos = ctor_mut.push_prop(JsValue::int(1)); diff --git a/crates/oxide_vm/src/bindings/bind_typed_array.rs b/crates/oxide_vm/src/bindings/bind_typed_array.rs index f585ed54..af19d5d8 100644 --- a/crates/oxide_vm/src/bindings/bind_typed_array.rs +++ b/crates/oxide_vm/src/bindings/bind_typed_array.rs @@ -256,7 +256,10 @@ pub fn bind_typed_array(core: &Arc, session: &KernelSession, global: core, session, shared_proto, - oxide_types::private_key::encode_symbol_key(realm_id, oxide_types::private_key::WELL_KNOWN_SYMBOL_TO_STRING_TAG), + oxide_types::private_key::encode_symbol_key( + realm_id, + oxide_types::private_key::WELL_KNOWN_SYMBOL_TO_STRING_TAG, + ), "get [Symbol.toStringTag]", oxide_builtins::typed_array::typed_array_to_string_tag_getter:: as *const (), ); diff --git a/crates/oxide_vm/src/bindings/bind_weak.rs b/crates/oxide_vm/src/bindings/bind_weak.rs index e0f3e445..990e295e 100644 --- a/crates/oxide_vm/src/bindings/bind_weak.rs +++ b/crates/oxide_vm/src/bindings/bind_weak.rs @@ -89,8 +89,10 @@ pub fn bind_weak_map(core: &Arc, session: &mut KernelSession, global proto.ensure_hash_props().push(JsValue::from_js_object(ctor_ptr)); let ctor_pos = proto.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); proto.set_data_meta(ctor_pos, PropAttributes::new(true, false, true)); - let tag_key = - oxide_types::private_key::encode_symbol_key(realm_id, oxide_types::private_key::WELL_KNOWN_SYMBOL_TO_STRING_TAG); + let tag_key = oxide_types::private_key::encode_symbol_key( + realm_id, + oxide_types::private_key::WELL_KNOWN_SYMBOL_TO_STRING_TAG, + ); let tag_shape = core.shape_forge().make_shape(proto.shape_id(), tag_key); proto.set_shape_id(tag_shape); proto.ensure_hash_props().push(JsValue::perm_string( diff --git a/crates/oxide_vm/src/bindings/mod.rs b/crates/oxide_vm/src/bindings/mod.rs index 1f9aeae9..d97f994b 100644 --- a/crates/oxide_vm/src/bindings/mod.rs +++ b/crates/oxide_vm/src/bindings/mod.rs @@ -510,8 +510,10 @@ fn bind_iterator_protos(core: &Arc, session: &KernelSession, realm_i oxide_builtins::iterator::iterator_constructor_setter:: as *const (), ); } - let sym_to_string_tag = - oxide_types::private_key::encode_symbol_key(realm_id, oxide_types::private_key::WELL_KNOWN_SYMBOL_TO_STRING_TAG); + let sym_to_string_tag = oxide_types::private_key::encode_symbol_key( + realm_id, + oxide_types::private_key::WELL_KNOWN_SYMBOL_TO_STRING_TAG, + ); if core .shape_forge() .lookup_position(iter_proto.shape_id(), sym_to_string_tag) @@ -603,8 +605,10 @@ fn bind_iterator_protos(core: &Arc, session: &KernelSession, realm_i realm_id, ); } - let sym_adispose = - oxide_types::private_key::encode_symbol_key(realm_id, oxide_types::private_key::WELL_KNOWN_SYMBOL_ASYNC_DISPOSE); + let sym_adispose = oxide_types::private_key::encode_symbol_key( + realm_id, + oxide_types::private_key::WELL_KNOWN_SYMBOL_ASYNC_DISPOSE, + ); if core .shape_forge() .lookup_position(aiter_proto.shape_id(), sym_adispose) diff --git a/crates/oxide_vm/src/generator.rs b/crates/oxide_vm/src/generator.rs index bbe019c6..63a910fb 100644 --- a/crates/oxide_vm/src/generator.rs +++ b/crates/oxide_vm/src/generator.rs @@ -892,7 +892,8 @@ pub(crate) fn init_generator_intrinsics(vm: &mut Vm) { let proto2_si = sf.intern("prototype").0; let proto2_shape = sh.make_shape(gf_proto.shape_id(), proto2_si); gf_proto.set_shape_id(proto2_shape); - let ppos2 = gf_proto.push_prop(JsValue::from_js_object(vm.realm.generator_proto.borrow().as_ptr() as *mut JsObject)); + let ppos2 = + gf_proto.push_prop(JsValue::from_js_object(vm.realm.generator_proto.borrow().as_ptr() as *mut JsObject)); gf_proto.set_data_meta(ppos2, oxide_types::object::PropAttributes::new(false, false, true)); // gf_proto[Symbol.toStringTag] = "GeneratorFunction"(数据属性,w/e/c = false/false/true)。 let tag2_key = oxide_types::private_key::encode_symbol_key( diff --git a/crates/oxide_vm/src/promise/aggregate.rs b/crates/oxide_vm/src/promise/aggregate.rs index ec8b1453..638b5dd2 100644 --- a/crates/oxide_vm/src/promise/aggregate.rs +++ b/crates/oxide_vm/src/promise/aggregate.rs @@ -25,7 +25,8 @@ impl Vm { /// 创建聚合静态方法(all/race/allSettled/any)的共享记录对象:剩余计数 /// 初始 1,结果数组(race 无)与能力 resolve/reject 闭包全部存为自身属性。 fn make_agg_record(&mut self, values: JsValue, resolve: JsValue, reject: JsValue) -> JsValue { - let proto_val = JsValue::from_js_object(self.realm.session.borrow().builtin_world().object_proto.as_ptr() as *mut JsObject); + let proto_val = + JsValue::from_js_object(self.realm.session.borrow().builtin_world().object_proto.as_ptr() as *mut JsObject); let ptr = self.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, proto_val)); // SAFETY: ptr 由 alloc_object 新建,返回非空 arena 指针;本次 native 调用内不搬移,借出期间无别名。 let obj = unsafe { &mut *ptr }; @@ -43,7 +44,8 @@ impl Vm { /// 创建 keyed 聚合静态方法的共享记录对象:剩余计数(初始 1)、结果平行数组、 /// 键平行数组与能力 resolve/reject 闭包存为自身属性。 fn make_agg_record_keyed(&mut self, values: JsValue, keys: JsValue, resolve: JsValue, reject: JsValue) -> JsValue { - let proto_val = JsValue::from_js_object(self.realm.session.borrow().builtin_world().object_proto.as_ptr() as *mut JsObject); + let proto_val = + JsValue::from_js_object(self.realm.session.borrow().builtin_world().object_proto.as_ptr() as *mut JsObject); let ptr = self.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, proto_val)); // SAFETY: ptr 由 alloc_object 新建,返回非空 arena 指针;本次 native 调用内不搬移,借出期间无别名。 let obj = unsafe { &mut *ptr }; @@ -96,7 +98,8 @@ impl Vm { /// 构造 allSettled 的结算记录 `{status, : value}` 普通对象。 fn make_settled_record(&mut self, status: &str, value_field: &str, value: JsValue) -> JsValue { - let proto_val = JsValue::from_js_object(self.realm.session.borrow().builtin_world().object_proto.as_ptr() as *mut JsObject); + let proto_val = + JsValue::from_js_object(self.realm.session.borrow().builtin_world().object_proto.as_ptr() as *mut JsObject); let ptr = self.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, proto_val)); // SAFETY: ptr 由 alloc_object 新建,返回非空 arena 指针;写 status/value 属性(含 new_string 分配)期间不搬移,借出期间无别名。 let obj = unsafe { &mut *ptr }; @@ -132,7 +135,8 @@ impl Vm { /// 把 errors 可迭代值收集为新数组(IterableToList)。不可迭代抛 TypeError。 fn aggregate_errors_to_list(&mut self, errors: JsValue) -> Result { - let array_proto = JsValue::from_js_object(self.realm.session.borrow().builtin_world().array_proto.as_ptr() as *mut JsObject); + let array_proto = + JsValue::from_js_object(self.realm.session.borrow().builtin_world().array_proto.as_ptr() as *mut JsObject); let ptr = self.alloc_object(JsObject::new_array(EMPTY_SHAPE_ID, array_proto, 0)); let list = JsValue::from_js_object(ptr); let mut index = 0usize; @@ -148,7 +152,8 @@ impl Vm { /// 建普通数组对象并依次写入元素(Promise.try 转发实参的承载)。 fn make_plain_array(&mut self, elements: Vec) -> JsValue { - let array_proto = JsValue::from_js_object(self.realm.session.borrow().builtin_world().array_proto.as_ptr() as *mut JsObject); + let array_proto = + JsValue::from_js_object(self.realm.session.borrow().builtin_world().array_proto.as_ptr() as *mut JsObject); let ptr = self.alloc_object(JsObject::new_array(EMPTY_SHAPE_ID, array_proto, 0)); // SAFETY: ptr 由 alloc_object 新建,返回非空 arena 指针;元素写入不搬移对象。 let obj = unsafe { &mut *ptr }; @@ -210,8 +215,9 @@ impl Vm { proto.set_data_meta(msg_pos, PropAttributes::new(true, false, true)); // %AggregateError% 构造器:proto = %Error%(NativeError 构造器继承 Error 构造器),length 2。 - let error_ctor_val = - JsValue::from_js_object(self.realm.session.borrow().builtin_world().error_constructor.as_ptr() as *mut JsObject); + let error_ctor_val = JsValue::from_js_object( + self.realm.session.borrow().builtin_world().error_constructor.as_ptr() as *mut JsObject, + ); let mut ctor = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, error_ctor_val)); ctor.set_function(true); ctor.type_tag = JsObject::OBJ_TYPE_CONSTRUCTOR; @@ -239,18 +245,24 @@ impl Vm { Self::swap_intrinsic_proto(&mut self.realm.aggregate_error_constructor.borrow_mut(), *ctor); // SAFETY: aggregate_error_proto 为堆址固定的 P(Arc 透明包装),本行前刚经 swap_intrinsic_proto 落地;与 ctor_mut 分属不同对象,无别名。 let proto_mut = unsafe { &mut *self.realm.aggregate_error_proto.borrow().as_mut_ptr() }; - proto_mut - .set_prop_at(0u32, JsValue::from_js_object(self.realm.aggregate_error_constructor.borrow().as_ptr() as *mut JsObject)); + proto_mut.set_prop_at( + 0u32, + JsValue::from_js_object(self.realm.aggregate_error_constructor.borrow().as_ptr() as *mut JsObject), + ); // SAFETY: aggregate_error_constructor 同为堆址固定的 P(Arc 透明包装);此处写 prototype 槽位(下标 2),与 proto_mut 分属不同对象,无别名。 let ctor_mut = unsafe { &mut *self.realm.aggregate_error_constructor.borrow().as_mut_ptr() }; - ctor_mut.set_prop_at(2u32, JsValue::from_js_object(self.realm.aggregate_error_proto.borrow().as_ptr() as *mut JsObject)); + ctor_mut.set_prop_at( + 2u32, + JsValue::from_js_object(self.realm.aggregate_error_proto.borrow().as_ptr() as *mut JsObject), + ); // 绑定 global(槽已存在则更新)。 let global_ptr = self.realm.session.borrow().global_object().as_ptr() as *mut JsObject; // SAFETY: global 对象由 session 持有,存活整个 session;本函数内只改其 shape/属性区,期间无 reset 或对象搬移。 let global = unsafe { &mut *global_ptr }; let si = self.kernel_core.perm_interner().intern("AggregateError").0; - let ctor_val = JsValue::from_js_object(self.realm.aggregate_error_constructor.borrow().as_ptr() as *mut JsObject); + let ctor_val = + JsValue::from_js_object(self.realm.aggregate_error_constructor.borrow().as_ptr() as *mut JsObject); if let Some(pos) = self.kernel_core.shape_forge().lookup_position(global.shape_id(), si) { global.set_prop_at(pos, ctor_val); } else { @@ -353,7 +365,8 @@ pub(super) fn promise_static_with_resolvers(vm: &mut Vm, args: &[u8]) -> NativeR Ok(t) => t, Err(err) => return NativeResult::Err(err), }; - let object_proto = JsValue::from_js_object(vm.realm.session.borrow().builtin_world().object_proto.as_ptr() as *mut JsObject); + let object_proto = + JsValue::from_js_object(vm.realm.session.borrow().builtin_world().object_proto.as_ptr() as *mut JsObject); let ptr = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, object_proto)); // SAFETY: ptr 由 alloc_object 新建,返回非空 arena 指针;写 promise/resolve/reject 属性期间不搬移,借出期间无别名。 let obj = unsafe { &mut *ptr }; @@ -478,7 +491,8 @@ fn perform_promise_combine( let values = if kind == AggregateKind::Race { JsValue::undefined() } else { - let array_proto = JsValue::from_js_object(vm.realm.session.borrow().builtin_world().array_proto.as_ptr() as *mut JsObject); + let array_proto = + JsValue::from_js_object(vm.realm.session.borrow().builtin_world().array_proto.as_ptr() as *mut JsObject); let ptr = vm.alloc_object(JsObject::new_array(EMPTY_SHAPE_ID, array_proto, 0)); JsValue::from_js_object(ptr) }; diff --git a/crates/oxide_vm/src/promise/constructor.rs b/crates/oxide_vm/src/promise/constructor.rs index 4d242943..b0e043cf 100644 --- a/crates/oxide_vm/src/promise/constructor.rs +++ b/crates/oxide_vm/src/promise/constructor.rs @@ -117,8 +117,9 @@ impl Vm { self.realm.session.borrow().builtin_world().data_view_constructor.as_ptr() as *mut JsObject, ); let this_val = if is_data_view { - let data_view_proto_val = - JsValue::from_js_object(self.realm.session.borrow().builtin_world().data_view_proto.as_ptr() as *mut JsObject); + let data_view_proto_val = JsValue::from_js_object( + self.realm.session.borrow().builtin_world().data_view_proto.as_ptr() as *mut JsObject, + ); JsValue::from_js_object(self.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, data_view_proto_val))) } else { let this_ptr = self.alloc_ctor_this(nt_obj, ctor_obj, new_target)?; @@ -156,7 +157,8 @@ impl Vm { &mut self, nt_obj: &JsObject, ctor_obj: &JsObject, nt_val: JsValue, ) -> Result<*mut JsObject, JsValue> { let proto_si = self.kernel_core.perm_interner().intern("prototype").0; - let object_proto = JsValue::from_js_object(self.realm.session.borrow().builtin_world().object_proto.as_ptr() as *mut JsObject); + let object_proto = + JsValue::from_js_object(self.realm.session.borrow().builtin_world().object_proto.as_ptr() as *mut JsObject); let is_native_ctor = ctor_obj.native_fn().is_some() && ctor_obj.type_tag == JsObject::OBJ_TYPE_CONSTRUCTOR; let proto_val = if is_native_ctor { match self.resolve_property(nt_obj, proto_si) { @@ -337,12 +339,16 @@ impl Vm { // SAFETY: `promise_proto` 为 `P`(Arc 透明包装),堆址固定且本行前刚经 // swap_intrinsic_proto 落地;与下一处 ctor_mut 指向不同对象,无别名。 let proto_mut = unsafe { &mut *self.realm.promise_proto.borrow().as_mut_ptr() }; - proto_mut.set_prop_at(0u32, JsValue::from_js_object(self.realm.promise_constructor.borrow().as_ptr() as *mut JsObject)); + proto_mut.set_prop_at( + 0u32, + JsValue::from_js_object(self.realm.promise_constructor.borrow().as_ptr() as *mut JsObject), + ); // SAFETY: `promise_constructor` 同为堆址固定的存活 `P`(Arc 透明包装); // 此处写 prototype 槽位(下标 2),与 proto_mut 分属不同对象,无别名。 let ctor_mut = unsafe { &mut *self.realm.promise_constructor.borrow().as_mut_ptr() }; // prototype 槽位在 length/name 之后(下标 2)。 - ctor_mut.set_prop_at(2u32, JsValue::from_js_object(self.realm.promise_proto.borrow().as_ptr() as *mut JsObject)); + ctor_mut + .set_prop_at(2u32, JsValue::from_js_object(self.realm.promise_proto.borrow().as_ptr() as *mut JsObject)); // Promise[Symbol.species] 访问器:getter 返回 receiver,派生类沿静态原型链 // 解析 @@species 得自身构造器。ctor 经本函数每次调用全新构造,同键槽不累积。 diff --git a/crates/oxide_vm/src/session_gc.rs b/crates/oxide_vm/src/session_gc.rs index 0c50eb74..6690edad 100644 --- a/crates/oxide_vm/src/session_gc.rs +++ b/crates/oxide_vm/src/session_gc.rs @@ -774,7 +774,14 @@ impl SessionGc { /// forwarding 表与根指针重写。在字符串清扫之后运行。 /// 返回释放的字节数。 pub(crate) fn sweep_session_bigints(&mut self, vm: &mut Vm) -> u64 { - let old = vm.realm.gc.borrow_mut().session_bigint_ptrs.borrow_mut().drain(..).collect::>(); + let old = vm + .realm + .gc + .borrow_mut() + .session_bigint_ptrs + .borrow_mut() + .drain(..) + .collect::>(); let mut freed = 0u64; let mut live = Vec::with_capacity(old.len()); for ptr in old { @@ -809,7 +816,14 @@ impl SessionGc { /// 统计、不重算存活账目。 /// 返回释放的字节数。 pub(crate) fn sweep_session_cells(&mut self, vm: &mut Vm) -> u64 { - let old = vm.realm.gc.borrow_mut().session_cell_ptrs.borrow_mut().drain(..).collect::>(); + let old = vm + .realm + .gc + .borrow_mut() + .session_cell_ptrs + .borrow_mut() + .drain(..) + .collect::>(); let mut freed = 0u64; let mut live = Vec::with_capacity(old.len()); for ptr in old { diff --git a/crates/oxide_vm/src/session_gc/tests.rs b/crates/oxide_vm/src/session_gc/tests.rs index 25c96b23..82409c38 100644 --- a/crates/oxide_vm/src/session_gc/tests.rs +++ b/crates/oxide_vm/src/session_gc/tests.rs @@ -563,7 +563,8 @@ fn weak_map_dead_key_freed_by_in_run_sweep() { // 键无强根:仅表内弱边引用。 let key = plain_object(&mut vm); // P 键(builtin 原型,不可死):对照条目须保留。 - let p_key = JsValue::from_js_object(vm.realm.session.borrow().builtin_world().object_proto.as_ptr() as *mut JsObject); + let p_key = + JsValue::from_js_object(vm.realm.session.borrow().builtin_world().object_proto.as_ptr() as *mut JsObject); unsafe { oxide_builtins::weak_map::weak_map_insert( &mut *wm, @@ -1196,7 +1197,10 @@ fn live_closure_cell_survives_full_collect() { collect(&mut vm); // 只断言表成员——sweep 已释放的指针解引用即 UB。 - assert!(vm.realm.gc.borrow().session_cell_ptrs.borrow().contains(&cell_ptr), "活 cell 应跨完整收集存活"); + assert!( + vm.realm.gc.borrow().session_cell_ptrs.borrow().contains(&cell_ptr), + "活 cell 应跨完整收集存活" + ); } /// 活跃帧 cell_stack 根:内层函数 upvalue cell 在活跃帧 cell_stack 上(低阈值使 @@ -1256,7 +1260,10 @@ fn suspended_state_box_cell_root_survives_collect() { collect(&mut vm); // 只断言表成员——sweep 已释放的指针解引用即 UB。 - assert!(vm.realm.gc.borrow().session_cell_ptrs.borrow().contains(&cell_ptr), "挂起帧 cell 应跨完整收集存活"); + assert!( + vm.realm.gc.borrow().session_cell_ptrs.borrow().contains(&cell_ptr), + "挂起帧 cell 应跨完整收集存活" + ); // 恢复后读值:cell 未被误释放。 let read = vm.run(&Arc::new(compile("globalThis.g.next().value"))).expect("read"); @@ -1394,7 +1401,12 @@ fn strings_only_collection_preserves_all_root_kinds() { assert!(vm.realm.gc.borrow().session_string_ptrs.contains(®_str.as_string_ptr_mut())); assert!(vm.realm.gc.borrow().session_string_ptrs.contains(&prop_str.as_string_ptr_mut())); - assert!(vm.realm.gc.borrow().session_string_ptrs.contains(&epoch_str.as_string_ptr_mut())); + assert!(vm + .realm + .gc + .borrow() + .session_string_ptrs + .contains(&epoch_str.as_string_ptr_mut())); assert!(!vm.realm.gc.borrow().session_string_ptrs.contains(&dead_ptr)); // 存活串内容可读,地址稳定。 assert_eq!(unsafe { (*reg_str.as_string_ptr_mut()).as_str() }, "reg-root".repeat(16)); @@ -1713,7 +1725,10 @@ fn active_and_suspended_for_in_do_not_block_in_run_collection() { vm.maybe_collect_in_run(); assert_eq!(vm.session_gc_stats().total_collections, 1, "无门控:活跃 for-in 存在时收集照常执行"); - assert!(!vm.realm.gc.borrow().session_object_ptrs.contains(&dead), "死 session 对象应被原地释放并出表"); + assert!( + !vm.realm.gc.borrow().session_object_ptrs.contains(&dead), + "死 session 对象应被原地释放并出表" + ); // 挂起形:生成器在 for-in 内 yield 后 run 结束,迭代器经状态盒持有 // (vm.iters 已空)——键经状态盒边入根收集标活,收集照常执行。 diff --git a/crates/oxide_vm/src/vm/gc_hooks.rs b/crates/oxide_vm/src/vm/gc_hooks.rs index 3ae9956f..91e08fd2 100644 --- a/crates/oxide_vm/src/vm/gc_hooks.rs +++ b/crates/oxide_vm/src/vm/gc_hooks.rs @@ -93,7 +93,10 @@ impl Vm { f(RootGroup::ExceptionValue, self.exception_value.unwrap_or(JsValue::undefined())); f(RootGroup::PendingException, self.pending_exception.unwrap_or(JsValue::undefined())); f(RootGroup::LastUncaught, self.last_uncaught_value.unwrap_or(JsValue::undefined())); - f(RootGroup::PendingLengthException, self.pending_length_exception.unwrap_or(JsValue::undefined())); + f( + RootGroup::PendingLengthException, + self.pending_length_exception.unwrap_or(JsValue::undefined()), + ); // 悬挂的 return 完成持有返回值,是 GC 根。 if let Some(Completion::Return { value, .. }) = self.pending_completion { f(RootGroup::PendingCompletion, value); @@ -150,7 +153,10 @@ impl Vm { } } } - f(RootGroup::Global, JsValue::from_js_object(self.realm.session.borrow().global_object().as_ptr() as *mut JsObject)); + f( + RootGroup::Global, + JsValue::from_js_object(self.realm.session.borrow().global_object().as_ptr() as *mut JsObject), + ); } /// GC 根统一枚举入口:遍历的字段清单与 `for_each_value` 相同。 @@ -266,7 +272,8 @@ impl Vm { // SAFETY: ptr 在字符串表登记,收尾前有效。 bytes += (std::mem::size_of::() + unsafe { (*ptr).payload_bytes() }) as u64; } - bytes += (self.realm.gc.borrow().session_bigint_ptrs.borrow().len() * std::mem::size_of::()) as u64; + bytes += (self.realm.gc.borrow().session_bigint_ptrs.borrow().len() * std::mem::size_of::()) + as u64; bytes += (self.realm.gc.borrow().session_cell_ptrs.borrow().len() * std::mem::size_of::()) as u64; bytes } diff --git a/crates/oxide_vm/src/vm_pool.rs b/crates/oxide_vm/src/vm_pool.rs index dfb17ae0..37610d9b 100644 --- a/crates/oxide_vm/src/vm_pool.rs +++ b/crates/oxide_vm/src/vm_pool.rs @@ -114,10 +114,7 @@ impl VmPool { /// # 副作用 /// - 同步创建 `min_size` 个 Vm。 pub fn with_counters( - kernel_core: Arc, - min_size: usize, - max_size: Option, - counters: Arc, + kernel_core: Arc, min_size: usize, max_size: Option, counters: Arc, ) -> Arc { let warm = min_size.min(max_size.unwrap_or(min_size)); let pool = Arc::new(Self { diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index ba2b340f..eb473be1 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -80,7 +80,10 @@ impl Vm { aggregate_error_proto: RefCell::new(P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()))), async_function_proto: RefCell::new(P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()))), async_generator_proto: RefCell::new(P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()))), - async_generator_function_proto: RefCell::new(P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()))), + async_generator_function_proto: RefCell::new(P::new(JsObject::new_empty( + EMPTY_SHAPE_ID, + JsValue::null(), + ))), gc: RefCell::new(GcState { session_gc: crate::session_gc::SessionGc::new(), session_object_ptrs: Vec::new(), @@ -230,7 +233,10 @@ impl Vm { aggregate_error_proto: RefCell::new(P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()))), async_function_proto: RefCell::new(P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()))), async_generator_proto: RefCell::new(P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()))), - async_generator_function_proto: RefCell::new(P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()))), + async_generator_function_proto: RefCell::new(P::new(JsObject::new_empty( + EMPTY_SHAPE_ID, + JsValue::null(), + ))), gc: RefCell::new(GcState { session_gc: crate::session_gc::SessionGc::new(), session_object_ptrs: Vec::new(), @@ -400,9 +406,7 @@ impl Vm { self.realm_id(), ); } - *self.realm - .object_prototype - .borrow_mut() = P::clone(&self.realm.session.borrow().builtin_world().object_proto); + *self.realm.object_prototype.borrow_mut() = P::clone(&self.realm.session.borrow().builtin_world().object_proto); self.init_generator_intrinsics(); self.init_promise_intrinsics(); self.init_async_intrinsics(); @@ -418,9 +422,7 @@ impl Vm { pub fn full_reset_legacy_for_bench(&mut self) { *self.realm.session.borrow_mut() = KernelSession::new(&self.kernel_core); bindings::init_kernel_builtins(&self.kernel_core, &mut self.realm.session.borrow_mut(), self.realm_id()); - *self.realm - .object_prototype - .borrow_mut() = P::clone(&self.realm.session.borrow().builtin_world().object_proto); + *self.realm.object_prototype.borrow_mut() = P::clone(&self.realm.session.borrow().builtin_world().object_proto); self.init_generator_intrinsics(); self.init_promise_intrinsics(); self.init_async_intrinsics(); @@ -762,7 +764,9 @@ impl Vm { } else if is_generator { JsValue::from_js_object(self.realm.generator_proto.borrow().as_ptr() as *mut JsObject) } else { - JsValue::from_js_object(self.realm.session.borrow().builtin_world().object_proto.as_ptr() as *mut JsObject) + JsValue::from_js_object( + self.realm.session.borrow().builtin_world().object_proto.as_ptr() as *mut JsObject + ) }; // prototype 子对象与函数本体同走 session 分配:`f.prototype === // globalThis.f.prototype` 要求两侧同一对象,逃逸写不会克隆出第二份。 diff --git a/crates/oxide_vm/src/vm_support/tests.rs b/crates/oxide_vm/src/vm_support/tests.rs index 1de39a8a..ccc84a8f 100644 --- a/crates/oxide_vm/src/vm_support/tests.rs +++ b/crates/oxide_vm/src/vm_support/tests.rs @@ -288,7 +288,10 @@ fn full_reset_clean_keeps_session_objects() { assert!(std::ptr::eq(world_ptr, Arc::as_ptr(&vm.realm.session.borrow().builtin_world))); assert!(std::ptr::eq(global_ptr, vm.realm.session.borrow().global_object.as_ptr())); - assert!(std::ptr::eq(object_proto_ptr, vm.realm.session.borrow().builtin_world().object_proto.as_ptr())); + assert!(std::ptr::eq( + object_proto_ptr, + vm.realm.session.borrow().builtin_world().object_proto.as_ptr() + )); assert!(!vm.realm.session.borrow().is_dirty_since_snapshot()); } @@ -326,8 +329,14 @@ fn full_reset_dirty_builtin_rebinds_global_slot() { vm.full_reset(); - assert!(std::ptr::eq(old_object_proto, vm.realm.session.borrow().builtin_world().object_proto.as_ptr())); - assert!(!std::ptr::eq(old_array_proto, vm.realm.session.borrow().builtin_world().array_proto.as_ptr())); + assert!(std::ptr::eq( + old_object_proto, + vm.realm.session.borrow().builtin_world().object_proto.as_ptr() + )); + assert!(!std::ptr::eq( + old_array_proto, + vm.realm.session.borrow().builtin_world().array_proto.as_ptr() + )); assert!(std::ptr::eq( global_prop(&vm, "Array").as_js_object_ptr(), vm.realm.session.borrow().builtin_world().array_constructor.as_ptr() as *mut JsObject @@ -599,7 +608,10 @@ fn full_reset_refreshes_object_prototype_after_object_dirty() { vm.full_reset(); - assert!(!std::ptr::eq(old_object_proto, vm.realm.session.borrow().builtin_world().object_proto.as_ptr())); + assert!(!std::ptr::eq( + old_object_proto, + vm.realm.session.borrow().builtin_world().object_proto.as_ptr() + )); assert!(std::ptr::eq( vm.realm.object_prototype.borrow().as_ptr(), vm.realm.session.borrow().builtin_world().object_proto.as_ptr() @@ -618,8 +630,14 @@ fn full_reset_object_dirty_rebinds_iterator_family() { vm.full_reset(); // object 家族与迭代器原型全部重建(新原型链到新 Object.prototype)。 - assert!(!std::ptr::eq(old_object_proto, vm.realm.session.borrow().builtin_world().object_proto.as_ptr())); - assert!(!std::ptr::eq(old_iterator_proto, vm.realm.session.borrow().builtin_world().iterator_proto.as_ptr())); + assert!(!std::ptr::eq( + old_object_proto, + vm.realm.session.borrow().builtin_world().object_proto.as_ptr() + )); + assert!(!std::ptr::eq( + old_iterator_proto, + vm.realm.session.borrow().builtin_world().iterator_proto.as_ptr() + )); // global 保留(dirty.global=false):其 Iterator 函数对象的 prototype // 属性须对齐到重建后的 %IteratorPrototype%。 let iter_val = global_prop(&vm, "Iterator"); @@ -872,7 +890,10 @@ fn full_reset_value_overwrite_on_builtin_proto_rebuilds_family() { vm.full_reset(); - assert!(!std::ptr::eq(old_array_proto, vm.realm.session.borrow().builtin_world().array_proto.as_ptr())); + assert!(!std::ptr::eq( + old_array_proto, + vm.realm.session.borrow().builtin_world().array_proto.as_ptr() + )); let t = run_source(&mut vm, "typeof Array.prototype.push"); assert_eq!(vm.lookup_str(t).as_deref(), Some("function")); assert_eq!(run_source(&mut vm, "[1, 2].push(3)"), JsValue::int(3)); @@ -892,7 +913,10 @@ fn full_reset_value_overwrite_with_object_value_drops_stale_pointer() { vm.full_reset(); - assert!(!std::ptr::eq(old_array_proto, vm.realm.session.borrow().builtin_world().array_proto.as_ptr())); + assert!(!std::ptr::eq( + old_array_proto, + vm.realm.session.borrow().builtin_world().array_proto.as_ptr() + )); let t = run_source(&mut vm, "typeof Array.prototype.push"); assert_eq!(vm.lookup_str(t).as_deref(), Some("function")); assert!(!vm.realm.session.borrow().is_dirty_since_snapshot()); @@ -1028,7 +1052,10 @@ fn full_reset_global_function_wrapper_property_does_not_survive() { let _ = run_source(&mut vm, "0"); let _ = run_source(&mut vm, "parseInt.custom = 1; 0"); - assert!(vm.realm.session.borrow().is_dirty_since_snapshot(), "global 函数 wrapper 写应被脏检测捕获"); + assert!( + vm.realm.session.borrow().is_dirty_since_snapshot(), + "global 函数 wrapper 写应被脏检测捕获" + ); vm.full_reset(); @@ -1080,7 +1107,10 @@ fn benign_user_object_writes_do_not_dirty_leaked_objects() { vm.full_reset(); - assert!(std::ptr::eq(world_ptr, Arc::as_ptr(&vm.realm.session.borrow().builtin_world)), "无污染不应重建 world"); + assert!( + std::ptr::eq(world_ptr, Arc::as_ptr(&vm.realm.session.borrow().builtin_world)), + "无污染不应重建 world" + ); assert!(!vm.realm.session.borrow().is_dirty_since_snapshot()); } @@ -1101,7 +1131,10 @@ fn benign_run_does_not_falsely_dirty_builtins() { vm.full_reset(); - assert!(std::ptr::eq(world_ptr, Arc::as_ptr(&vm.realm.session.borrow().builtin_world)), "builtin world 不应重建"); + assert!( + std::ptr::eq(world_ptr, Arc::as_ptr(&vm.realm.session.borrow().builtin_world)), + "builtin world 不应重建" + ); assert!(!vm.realm.session.borrow().is_dirty_since_snapshot()); } diff --git a/crates/oxide_vm/tests/gc_stress_tests.rs b/crates/oxide_vm/tests/gc_stress_tests.rs index a9a31010..008d8fe5 100644 --- a/crates/oxide_vm/tests/gc_stress_tests.rs +++ b/crates/oxide_vm/tests/gc_stress_tests.rs @@ -41,17 +41,11 @@ fn gc_pressure_mode_env_toggle() { let result = eval_on(&mut vm, SCRIPT).expect("脚本不应运行失败"); assert!(result.as_bool(), "存活钉读回应完整"); let total = vm.session_gc_stats().total_collections; - assert!( - total > 100, - "压力模式应每个顶层边界做一次完整收集,实际 {total}" - ); + assert!(total > 100, "压力模式应每个顶层边界做一次完整收集,实际 {total}"); // 关臂:移除环境变量后同一脚本走退化路径。 std::env::remove_var("OXIDE_GC_PRESSURE"); let mut vm = Vm::new(); let result = eval_on(&mut vm, SCRIPT).expect("脚本不应运行失败"); assert!(result.as_bool(), "存活钉读回应完整"); - assert_eq!( - vm.session_gc_stats().total_collections, 0, - "关臂小脚本分配不超水位,收集应为零" - ); + assert_eq!(vm.session_gc_stats().total_collections, 0, "关臂小脚本分配不超水位,收集应为零"); }