Files
Unlucky-Launcher/crates/core/src/platform/child.rs
T
AskaEth 6dd88a27a3 fix(core): 守卫的剥注释不再吞文件 + 变异 D 变红(M213 复审 P1-1 / P2-1)
复审 P1-1:`code_lines()` 对每一行找 `/*`,找着就当块注释开头。本仓库的文档注释里到处是
glob(`/usr/lib/jvm/*`、`rules.d/*.yaml`、`crash-reports/*.txt`、`.github/workflows/**`),
字符串里也有(`"mods/*.jar"`),这些 `/*` 没有配对的 `*/`,于是 in_block 一旦为真就再也
不会变假 —— 该文件从那一行到 EOF 全部被吞掉。实测 crates/core/src 里 8/371 个文件中招,
其中一个是 game/java/discover.rs(它自己有一处 reg query),复审判的变异 D 因此全绿。

- code_lines 只在「去掉前导空白后以 /* 开头」的行上进入块注释态,行中/字符串里的 /* 一律
  当普通代码。本仓库零处行首块注释,所以结构上不可能再吞代码。
- 新增 code_lines_is_not_fooled_by_glob_slashes_in_strings_or_docs:字符串里的 /* 不许
  吞掉后一行、文档 glob 不许吞掉真代码、真块注释仍要被剥。
- 新增 the_scan_reaches_the_end_of_every_file:逐文件核对**最后一行代码的行号**必须出现在
  扫面结果里(比数总行数尖锐 —— 文件普遍以 } 结尾,光比内容会被别处的 } 蒙混),外加
  全仓可见行占比 ≥60% 的兜底(本机实测 81%)。
- 变异 D(discover.rs:156 改回裸 Command::new)现在 exit 101 并点名该行;A/B/C 复验仍红。

复审 P2-1:原注释说「两个 flag 不能叠加」,按 MSDN 这是错的。改成:两个 flag 在 Win32 层
可任意组合,真正的坑是 std 的 creation_flags 是 setter(后一次静默覆盖前一次)。结论不变
(launch/process.rs 仍零改动),顺带修正 DETACHED_PROCESS 的说明。
2026-11-09 15:07:56 +08:00

66 lines
3.6 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 子进程构造器:本 crate 里**唯一**允许直接 `std::process::Command::new` 的地方(M213)。
//!
//! ## 为什么需要它(这不是风格问题,是窗口问题)
//!
//! Windows 上「某个子进程要不要一块控制台」由**创建者的可执行文件类型**决定:一个**自己没有
//! 控制台**的进程(GUI 程序的 PE Subsystem = 2;本仓库的 `unlucky-launcher.exe` 就是,M196 已改)
//! 去 spawn 一个**控制台程序**(`java.exe` / `reg.exe` / `tzutil.exe` / `cmd.exe` /
//! `powershell.exe` …)时,Windows 会为这个子进程**新建一块控制台**——桌面上看到的就是
//! 「黑框闪一下」。所以探 N 个 JDK 就闪 N 次 `java -version`,用户的原话是「疯狂闪」。
//!
//! 抑制手段只有一个:创建时带 `CREATE_NO_WINDOW`(`0x0800_0000`)。
//! **`Stdio::null()` 不管这件事**——它只把子进程的 stdout/stderr 指到空设备,控制台窗口照样会被
//! 创建。所以到处补 `.stdout(Stdio::null())` 是无效修法。
//!
//! ## 谁**不许**走这里
//!
//! **游戏进程**([`crate::launch::process::RunningProcess::spawn`])保留自己的
//! `CREATE_NEW_PROCESS_GROUP`(`0x0000_0200`),以便 `taskkill /T` 递归回收整棵进程树。
//!
//! 这两个 flag 在 **Win32 层是可以任意组合的**:[Process Creation Flags][flags] 原文
//! 「They can be specified in any combination, except as noted」,而两条各自 not 掉的只有
//! `CREATE_NEW_CONSOLE` / `DETACHED_PROCESS`,与这一组无关。真正的坑在 **Rust std 这一层**:
//! `CommandExt::creation_flags` 是 **setter 不是累加器**——「先 `quiet()` 再
//! `.creation_flags(CREATE_NEW_PROCESS_GROUP)`」会把 `CREATE_NO_WINDOW` **静默覆盖掉**。
//!
//! 而「游戏要不要带自己的控制台窗口」是**产品决定**(玩家要看游戏自己那段输出),不是顺手
//! 抑制的事——所以这里不复用 `quiet`,显式只设 group flag,M213 一个字没动它。
//!
//! 那边是唯一另一处合法的直接构造点,两处都由 `crates/core/tests/child_quiet.rs` 的守卫钉住。
//!
//! [flags]: https://learn.microsoft.com/windows/win32/procthread/process-creation-flags
use std::ffi::OsStr;
use std::process::Command;
/// `CREATE_NO_WINDOW`:子进程不获得控制台窗口。
///
/// 见 <https://learn.microsoft.com/windows/win32/procthread/process-creation-flags>。
/// 别和 `CREATE_NEW_CONSOLE`(`0x0000_0010`,意思正好相反)或
/// `DETACHED_PROCESS`(`0x0000_0008`,子进程不继承父控制台、也不新建;标准句柄由
/// `STARTUPINFO` 给,给了照样可用)搞混。
#[cfg(windows)]
const CREATE_NO_WINDOW: u32 = 0x0800_0000;
/// 起一个**不该在桌面上留下窗口**的子进程。
///
/// - Windows:`.creation_flags(CREATE_NO_WINDOW)`;
/// - 其它平台:原样返回,一个字不改(POSIX 没有「spawn 就新开一个终端」这回事,子进程的
/// 窗口从来不由我们造)。
///
/// 返回值就是普通 [`std::process::Command`],调用方照旧 `.arg(..)` / `.env(..)` /
/// `.output()` / `.status()`,或者整个交给 `game::java::proc::run_with_timeout` 去带超时跑。
/// 本 crate 是 `#![forbid(unsafe_code)]`,而 `CommandExt::creation_flags` 是 std 的安全 API,
/// 所以这里不需要任何 `unsafe`。
#[must_use]
pub fn quiet<S: AsRef<OsStr>>(program: S) -> Command {
#[allow(unused_mut)]
let mut command = Command::new(program);
#[cfg(windows)]
{
use std::os::windows::process::CommandExt;
command.creation_flags(CREATE_NO_WINDOW);
}
command
}