diff --git a/Cargo.lock b/Cargo.lock index aa30bdc8..de06984f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,7 +1160,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.8.44" +version = "0.8.45" dependencies = [ "chrono", "fast-float", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.8.44" +version = "0.8.45" dependencies = [ "num-bigint", "oxide_log", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.8.44" +version = "0.8.45" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.8.44" +version = "0.8.45" dependencies = [ "ansi_term", "bumpalo", @@ -1225,7 +1225,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.8.44" +version = "0.8.45" dependencies = [ "lru", "oxide_bytecode", @@ -1234,7 +1234,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.8.44" +version = "0.8.45" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1253,7 +1253,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.8.44" +version = "0.8.45" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1267,7 +1267,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.8.44" +version = "0.8.45" dependencies = [ "num-bigint", "oxc_allocator", @@ -1282,7 +1282,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.8.44" +version = "0.8.45" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1291,7 +1291,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.8.44" +version = "0.8.45" dependencies = [ "bumpalo", "dashmap", @@ -1306,7 +1306,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.8.44" +version = "0.8.45" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1318,7 +1318,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.8.44" +version = "0.8.45" dependencies = [ "tracing", "tracing-appender", @@ -1327,7 +1327,7 @@ dependencies = [ [[package]] name = "oxide_parser" -version = "0.8.44" +version = "0.8.45" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1342,7 +1342,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.8.44" +version = "0.8.45" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1358,7 +1358,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.8.44" +version = "0.8.45" dependencies = [ "num-bigint", "num-traits", @@ -1370,7 +1370,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.8.44" +version = "0.8.45" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1387,7 +1387,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.8.44" +version = "0.8.45" dependencies = [ "bumpalo", "hashbrown 0.14.5", @@ -1399,7 +1399,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.8.44" +version = "0.8.45" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 3498fe98..c9adedb7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ members = [ ] [workspace.package] -version = "0.8.44" +version = "0.8.45" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_cli/tests/server_cli.rs b/crates/oxide_cli/tests/server_cli.rs index 39c1ac3c..8636f588 100644 --- a/crates/oxide_cli/tests/server_cli.rs +++ b/crates/oxide_cli/tests/server_cli.rs @@ -52,6 +52,18 @@ fn wait_for_healthy(deadline: Duration) { } } +/// 轮询判 server 进程退出(50 毫秒间隔、给定截止)。 +/// +/// 删 sidecar 文件早于进程实际退出,二者间隙随构建环境变化(带内存 +/// sanitizer 的构建 atexit 收尾更久),立即断言会撞上未退出窗口,故有界轮询。 +fn wait_pid_dead(pid: u32, deadline: Duration) { + let end = Instant::now() + deadline; + while sidecar::pid_alive(pid) { + assert!(Instant::now() < end, "server 进程应在有界时间内退出"); + std::thread::sleep(Duration::from_millis(50)); + } +} + /// 发送请求帧并读回一帧响应。 fn send_and_recv(stream: &mut UnixStream, request: &ServerRequest) -> ServerResponse { stream @@ -200,7 +212,7 @@ fn server_start_e2e() { assert!(!socket.exists(), "socket 文件应被删除"); assert!(!sidecar_path.exists(), "sidecar 文件应被删除"); - assert!(!sidecar::pid_alive(id.pid), "server 进程应已退出"); + wait_pid_dead(id.pid, Duration::from_secs(30)); } /// start 双启动幂等:已有存活 server 时再跑 start,断言第二次退 0 且打印 @@ -393,7 +405,7 @@ fn server_log_e2e() { assert!(Instant::now() < deadline, "server 应在有界时间内退出"); std::thread::sleep(Duration::from_millis(50)); } - assert!(!sidecar::pid_alive(id.pid), "server 进程应已退出"); + wait_pid_dead(id.pid, Duration::from_secs(30)); // 日志文件有意保留(跨重启的诊断工件)。 assert!(log_path.exists(), "日志文件退出后应保留"); @@ -607,5 +619,5 @@ fn server_watchdog_sigint_stops_server() { assert!(Instant::now() < deadline, "server 应在有界时间内退出"); std::thread::sleep(Duration::from_millis(50)); } - assert!(!sidecar::pid_alive(id.pid), "server 进程应已退出"); + wait_pid_dead(id.pid, Duration::from_secs(30)); } diff --git a/scripts/asan_gate.sh b/scripts/asan_gate.sh new file mode 100755 index 00000000..265c628d --- /dev/null +++ b/scripts/asan_gate.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# ASan 常设门:按地址 sanitizer 配方跑全 workspace 单测。 +# +# 用法: scripts/asan_gate.sh +# 无参数。nightly 工具链加地址 sanitizer 构建并跑 cargo test --workspace。 +# +# 配方说明:detect_leaks=0 因 PermInterner 与静态表设计性永久泄漏, +# 泄漏检测纯噪声;quarantine_size_mb=64 收隔离区,压高并发下内存放大。 +# RUSTC 指到 asan_rustc_wrapper.sh:仅 oxide_* crate 带 sanitizer 插桩, +# 依赖树(含 proc-macro)不带(proc-macro 动态库加载约束,见包装器注释)。 +# +# 退出码: 0 全部通过;1 构建失败或测试失败或 ASan 报告;2 nightly 工具链缺失。 + +set -u + +cd "$(dirname "$0")/.." + +# 前置检查:nightly 工具链必须已装(rust-toolchain.toml 钉 stable,须显式切换)。 +if ! RUSTUP_TOOLCHAIN=nightly cargo --version >/dev/null 2>&1; then + echo "缺 nightly 工具链:rustup toolchain install nightly" >&2 + exit 2 +fi + +export RUSTUP_TOOLCHAIN=nightly +export RUSTFLAGS="-Zsanitizer=address" +export CARGO_TARGET_DIR=target-asan +export ASAN_OPTIONS="detect_leaks=0:quarantine_size_mb=64" +# proc-macro 动态库加载进无 ASan 运行时的 rustc 会失败, +# 经包装器剥离其 sanitizer 标志(见包装器头部注释)。 +export RUSTC="$PWD/scripts/asan_rustc_wrapper.sh" + +cargo test --workspace diff --git a/scripts/asan_rustc_wrapper.sh b/scripts/asan_rustc_wrapper.sh new file mode 100755 index 00000000..0414176d --- /dev/null +++ b/scripts/asan_rustc_wrapper.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# ASan 构建用 rustc 包装器:仅引擎 crate 与最终链接单元带地址 sanitizer 标志。 +# +# 门的目标是检测引擎代码的真实内存错误,故只给 oxide_* 工作区 crate +# 插桩,整个依赖树(含 proc-macro)不带 sanitizer 编译。两个原因: +# 1. proc-macro 以动态库加载进未带 ASan 运行时的 rustc 进程, +# 带 sanitizer 的 proc-macro 动态库加载即失败(未定义 ASan 符号); +# 2. proc-macro 动态库把自身依赖(proc-macro2、syn 等)以 rlib 链入, +# 只剥 proc-macro 自身的标志不够,ASan 插桩的依赖仍会带进动态库。 +# +# 最终链接单元(--test 测试目标、--crate-type bin 的二进制目标)也要带 +# 标志:它们链入带插桩的引擎 rlib,须由 rustc 链接 ASan 运行时; +# 构建脚本(crate 名 build_script_build)是独立可执行文件,不插桩。 +# +# 用法:常设门把 RUSTC 指到本脚本。按 --crate-name 与编译类型参数裁决, +# 保留或剥离 -Zsanitizer=address 后透传给 rustc。 + +crate_name="" +is_bin=0 +has_test=0 +prev="" +for a in "$@"; do + if [ "$prev" = "--crate-name" ]; then + crate_name="$a" + fi + if [ "$prev" = "--crate-type" ]; then + case "$a" in + bin) is_bin=1 ;; + esac + fi + case "$a" in + --crate-type=bin) is_bin=1 ;; + --test) has_test=1 ;; + esac + prev="$a" +done + +keep=0 +case "$crate_name" in + build_script_build) ;; + oxide*) keep=1 ;; + *) + if [ "$is_bin" -eq 1 ] || [ "$has_test" -eq 1 ]; then + keep=1 + fi + ;; +esac + +if [ "$keep" -eq 1 ]; then + exec rustc "$@" +fi + +out=() +for a in "$@"; do + case "$a" in + -Zsanitizer=address) continue ;; + esac + out+=("$a") +done + +exec rustc "${out[@]}"