fix(m196): r1 复审 P1(非 Windows dead_code)+ P2 四条
P1(阻断):console.rs 的 `INVALID_HANDLE_VALUE` 只被 `#[cfg(target_os = "windows")]` 的 usable() 用到,而这个模块在 Linux/macOS 上也编译(platform/mod.rs 的 pub mod win32; 没有 cfg)⇒ ubuntu 的 `clippy -- -D warnings` 会把它升级成 dead_code error。 按「精确 cfg」修(不用 dwm.rs 那种文件级 allow(dead_code):那会永久掩掉本文件在非 Windows 上的所有死项)。自证方式是真实文件翻转 cfg 的 rustc probe:修前 exit 1 (`constant INVALID_HANDLE_VALUE is never used`)、修后 exit 0 零告警;同一条命令写进 了注释,下一个人可以照着跑。 P2-1:自测条数按实现改成 8 条(红 6 / 绿 2),三处(脚本 docstring、release-build.yml 注释、README §12)一致。 P2-2:文件头那组句柄读数换成 release 产物的实测值(out 0x0→0x1F0 / err 0x0→0x1F4), 并说明数字随实例变化、单测里的 0xE4/0xE8 是示例。 P2-3:README §15 补这段边角语义——但**按实测改写了复审的结论**:两种「既无控制台也不 重定向」的宿主形态(父有句柄不继承 / 父子都无句柄)× 两条命令,child_exit_code 全是 0, 不是 EXIT_INTERNAL;原因是 Rust std 在 Windows 上对取不到的句柄直接返回 Ok(独立探针 实测 SetStdHandle(out)=0x0 / 0x1234 / (HANDLE)-1 三种取值 write/flush 都是 Ok), 所以 runner.rs 那条「写失败 ⇒ EXIT_INTERNAL」的分支不会被触发。 P2-4:NoParentConsole → NoConsole(这个名字同时覆盖「attach 成功但句柄仍不可用」), 诊断文案与注释同步。
This commit is contained in:
1 parent
14f9318e09
commit
3fd33a672f
4 files changed
+52
-18
No files matched your search
@@ -265,7 +265,7 @@ jobs:
|
||||
# 就是这么活到 2026-11-08 的。本步只认那 4 个字节:GUI 产物必须是 WINDOWS_GUI(2),非 2 即红。
|
||||
# 反方向一并守:CLI 产物 `ull.exe` 必须仍是 CONSOLE(3)——它靠真控制台做 stdout/stderr,
|
||||
# 谁把属性抄到它头上,`ull --version | …` 会静默丢输出。守门本体在 packaging/check_pe_subsystem.py
|
||||
# (零第三方依赖,本地同一份代码可跑;`--self-test` 注入 5 条回归证明它会红)。
|
||||
# (零第三方依赖,本地同一份代码可跑;`--self-test` 注入 8 条回归(红 6 / 绿 2)证明它会红)。
|
||||
- name: PE 子系统门禁(GUI 产物必须 WINDOWS_GUI/2,CLI 产物必须 CONSOLE/3)
|
||||
if: runner.os == 'Windows'
|
||||
shell: bash
|
||||
|
||||
@@ -18,10 +18,11 @@
|
||||
//! | ④ 双击 | 0 / 0 | 丢了(本来也没人看) | 要(但会失败,见下) |
|
||||
//!
|
||||
//! ③ 的补救实测:`AttachConsole(ATTACH_PARENT_PROCESS)` 返回 1 之后 `GetStdHandle` 立刻给出
|
||||
//! 控制台句柄(实测 `0x0` → `0xE4` / `0xE8`),随后的 `WriteFile` 与 Rust 的 `eprintln!`
|
||||
//! 都真的落进了屏幕缓冲区(同一个控制台模型宿主读回的文本见 M196 报告)。所以**不需要**再自己
|
||||
//! `CreateFileW("CONOUT$")` + `SetStdHandle`:那一层是多余的,而且它会覆盖用户自己重定向的
|
||||
//! 句柄——①②两种情形会被打坏。
|
||||
//! 控制台句柄(release 产物在 cmd 模型下实测 `out 0x0→0x1F0` / `err 0x0→0x1F4`),随后
|
||||
//! `WriteFile` 与 Rust 的 `eprintln!` 都真的落进了屏幕缓冲区(同一模型宿主读回的文本见 M196
|
||||
//! 报告)。具体数字随进程与控制台实例变化(调试构建里是 `0x208`/`0x20C`),**能跨实例复现的
|
||||
//! 是「从 0 变成非 0」这件事本身**。所以**不需要**再自己 `CreateFileW("CONOUT$")` +
|
||||
//! `SetStdHandle`:那一层是多余的,而且它会覆盖用户自己重定向的句柄——①②两种情形会被打坏。
|
||||
//!
|
||||
//! ④ 的边界:双击时祖先进程(explorer)没有控制台,`AttachConsole` 返回 0
|
||||
//! (`GetLastError` = 6 `ERROR_INVALID_HANDLE`),`GetConsoleWindow` 仍是 0、`CONOUT$` 也打不开。
|
||||
@@ -59,6 +60,22 @@ const STD_ERROR_HANDLE: u32 = 0xFFFF_FFF4; // -12
|
||||
const ATTACH_PARENT_PROCESS: u32 = 0xFFFF_FFFF;
|
||||
|
||||
/// `FALSE`/`TRUE` 之外唯一需要认的无效句柄:`(HANDLE)-1`。
|
||||
///
|
||||
/// **必须带下面那行 `cfg`**:唯一使用点 [`usable`] 只活在 Windows 分支里,而本模块在
|
||||
/// Linux/macOS 上也编译(`platform/mod.rs` 的 `pub mod win32;` 没有 cfg)。漏了它,
|
||||
/// `ci.yml` 的 ubuntu clippy(`-- -D warnings`)会把它升级成 `dead_code` error——而本机只有
|
||||
/// windows-gnu 工具链,非 Windows 的 cfg 一次都编不到,本地抓不住(M196 r1 复审 P1)。
|
||||
/// 同目录 `transparency/dwm.rs` 用的是文件级 `#![cfg_attr(not(windows), allow(dead_code))]`;
|
||||
/// 这里刻意**不用**那种写法:本文件的 Windows-only 面很小,每一项各自带 cfg 才能让编译器
|
||||
/// 继续在非 Windows 上替我们盯着「又漏了一个」。
|
||||
///
|
||||
/// 自证办法(本机只有 Windows triple、交叉编 iced 会卡在 x11/fontconfig 时):把本文件的
|
||||
/// `target_os = "windows"` 全换成不存在的目标名,再
|
||||
/// `rustc --edition 2021 --crate-type lib -D warnings --crate-name console_probe <那份拷贝>`
|
||||
/// ——漏了上面那行 cfg 时它是 exit 1 + `constant INVALID_HANDLE_VALUE is never used`,
|
||||
/// 补齐后 exit 0 且零告警(**其他**任何非 Windows 死项也会一起报出来,所以这一个 probe 就是
|
||||
/// 「本文件在 Linux 上能不能过 -D warnings」的整份答案)。
|
||||
#[cfg(target_os = "windows")]
|
||||
const INVALID_HANDLE_VALUE: isize = -1;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
@@ -87,8 +104,11 @@ pub enum ConsoleOutcome {
|
||||
/// 附着后 `(stdout, stderr)`。
|
||||
after: (isize, isize),
|
||||
},
|
||||
/// 祖先进程没有控制台(④,或者服务/计划任务):附着失败,**也没有新建任何窗口**。
|
||||
NoParentConsole {
|
||||
/// 没有可写的控制台(④ / 服务 / 计划任务):附着失败**或**附着成功但句柄仍不可用。
|
||||
///
|
||||
/// 两种情况在调用点上是同一件事(都没有输出通道),所以合并成一个结论;`last_error` 与
|
||||
/// 两对句柄一起进诊断文本,读的人能自己分清是哪一种。**没有新建任何窗口**。
|
||||
NoConsole {
|
||||
/// 附着前 `(stdout, stderr)`。
|
||||
before: (isize, isize),
|
||||
/// 附着后 `(stdout, stderr)`。
|
||||
@@ -115,12 +135,12 @@ impl ConsoleOutcome {
|
||||
"已附着父进程控制台:out 0x{:X}→0x{:X},err 0x{:X}→0x{:X}",
|
||||
before.0, after.0, before.1, after.1
|
||||
),
|
||||
Self::NoParentConsole {
|
||||
Self::NoConsole {
|
||||
before,
|
||||
after,
|
||||
last_error,
|
||||
} => format!(
|
||||
"父进程没有控制台(attach 失败,GetLastError={}):out 0x{:X}→0x{:X},err 0x{:X}→0x{:X};未新建窗口",
|
||||
"没有可用的控制台(attach 失败或句柄仍不可用,GetLastError={}):out 0x{:X}→0x{:X},err 0x{:X}→0x{:X};未新建窗口",
|
||||
last_error, before.0, after.0, before.1, after.1
|
||||
),
|
||||
}
|
||||
@@ -130,7 +150,7 @@ impl ConsoleOutcome {
|
||||
#[must_use]
|
||||
pub fn output_visible(self) -> bool {
|
||||
match self {
|
||||
Self::NotWindows | Self::NoParentConsole { .. } => false,
|
||||
Self::NotWindows | Self::NoConsole { .. } => false,
|
||||
Self::AlreadyUsable { .. } | Self::Attached { .. } => true,
|
||||
}
|
||||
}
|
||||
@@ -192,7 +212,7 @@ pub fn attach_parent_console() -> ConsoleOutcome {
|
||||
} else {
|
||||
// 附着成功但句柄仍不可用,与「父进程根本没有控制台」在调用点上是同一件事:
|
||||
// 都没有可写的输出通道,所以合并成一个结论(`describe` 里照样带着读数)。
|
||||
ConsoleOutcome::NoParentConsole {
|
||||
ConsoleOutcome::NoConsole {
|
||||
before,
|
||||
after,
|
||||
last_error,
|
||||
@@ -218,6 +238,7 @@ mod tests {
|
||||
#[test]
|
||||
fn describe_carries_the_runtime_readings() {
|
||||
// 报告里贴的是这些行:句柄值必须真的出现在文本里,不然「已附着」就只是一句话。
|
||||
// 下面的 0xE4/0xE8 是**示例值**(真实读数随进程与控制台实例变化,见模块文档 ③)。
|
||||
let attached = ConsoleOutcome::Attached {
|
||||
before: (0, 0),
|
||||
after: (0xE4, 0xE8),
|
||||
@@ -227,7 +248,7 @@ mod tests {
|
||||
assert!(text.contains("0xE8"), "{text}");
|
||||
assert!(attached.output_visible());
|
||||
|
||||
let missing = ConsoleOutcome::NoParentConsole {
|
||||
let missing = ConsoleOutcome::NoConsole {
|
||||
before: (0, 0),
|
||||
after: (0, 0),
|
||||
last_error: 6,
|
||||
|
||||
+16
-4
@@ -65,7 +65,7 @@ Git Bash 下会静默落进 Docker VM(脚本内部用 `cygpath -m` 归一)
|
||||
| `packaging/release-notes.md` | GitHub Release 说明模板(`{{VERSION}}` / `{{SUMS}}` 占位) | ≤400 |
|
||||
| `packaging/check_screenshot.py` | 截图非空白判定(CI 门禁 + 本地手动跑,零第三方依赖) | ≤400 |
|
||||
| `packaging/check_publish_slots.py` | 发布名单守卫(M164):名单 ⇄ 产物两个方向;`--self-test` 注入 8 条回归 | ≤400 |
|
||||
| `packaging/check_pe_subsystem.py` | PE 子系统守卫(M196):**读产物 PE 头的 `Subsystem`**(GUI 必须 2 / CLI 必须 3),非 2 即红;`--self-test` 注入 5 条回归(见 §15) | ≤400 |
|
||||
| `packaging/check_pe_subsystem.py` | PE 子系统守卫(M196):**读产物 PE 头的 `Subsystem`**(GUI 必须 2 / CLI 必须 3),非 2 即红;`--self-test` 注入 8 条回归(红 6 / 绿 2)(见 §15) | ≤400 |
|
||||
| `packaging/check_test_source_off.py` | 测试通道守门(M169):workflow / 打包脚本 / `Cargo.toml default` 都不许打开 `update-test-source`;归属按 `jobs.*.steps[]` 列表项判、默认拒绝;`--self-test` 按 `tests/guard_vectors.json` 注入 | ≤400 |
|
||||
| `packaging/tests/guard_vectors.json` | 守门自测向量(基线 + 32 条:26 红 / 6 绿),数据与代码分开 | ≤400 |
|
||||
| `packaging/ci-runtime-smoke.md` | §11 的细节:断言口径/阈值依据/失效模式/复跑 | ≤400 |
|
||||
@@ -158,10 +158,22 @@ CUI(3),`grep -rn windows_subsystem crates/` 当时零命中。同一个修复
|
||||
四种启动情形的实测表在该文件顶部):重定向/管道两个句柄本来就可用(一个字节都不动)、cmd 里直接跑
|
||||
附着后输出落进控制台、双击时附着失败且**绝不新建窗口**。
|
||||
- 守门:`python3 packaging/check_pe_subsystem.py --gui <exe> --cli <exe>`,读 `MZ`→`e_lfanew`(`0x3C`)→
|
||||
`PE | ||||