From 0f6598b5f0fe5ad2dcb3fe1e48eeaf5f7f5cc823 Mon Sep 17 00:00:00 2001 From: tiye Date: Sun, 23 Aug 2026 12:52:16 +0800 Subject: [PATCH 1/2] fix: rebase qdocco source guard onto core benchmark --- PERFORMANCE.md | 4 ++-- RFCs/0083-qdocco-markdown.md | 2 ++ src/bin/qdocco.rs | 10 ++++++++++ tests/cli_tools.rs | 7 +++++++ 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/PERFORMANCE.md b/PERFORMANCE.md index 78db7c0..5dc732a 100644 --- a/PERFORMANCE.md +++ b/PERFORMANCE.md @@ -138,7 +138,7 @@ for character, index in 'a☕中x' by 2 then sum += index sum ``` -signed-by-iteration(10,000 次): +signed-by-iteration(20,000 次): ```coffee sum = 0 @@ -543,7 +543,7 @@ rest 绑定会复制剩余元素到新的不可变数组,以保持宿主存储 `qbench --json` 现在包含 `signed-by-iteration`,并在每轮编译、验证和执行后检查 `3333`。该负载不把数组复制到宿主侧,反向位置由 VM 的有符号步长直接推进;运行基准时应与 `stepped-iteration` 一起比较编译、验证和执行三个阶段。 -本次可复现实测在 Apple M1 Pro(T6000、16 GB 统一内存)、Darwin 25.5.0 上运行;编译器为 `rustc 1.94.0 (4a4ef493e 2026-03-02)`(完整 commit `4a4ef493e3a1488c6e321570238084b38948f6db`,LLVM 21.1.8),release 命令为 `cargo run --locked --release --bin qbench -- --json --iterations 100`。它得到 `signed-by-iteration` 一条记录:编译总计 `618750 ns`,验证总计 `28917 ns`,执行总计 `2363209 ns`,期望值为 `3333`。这是单次开发机样本,只用于确认工作负载已纳入语义护栏与性能采集;跨版本比较仍须按本报告口径重复至少三次并取中位数。 +本次可复现实测(Apple arm64、Darwin 25.5.0、`rustc 1.94.0`,release,命令 `cargo run --locked --release --bin qbench -- --json --iterations 100`)得到 `signed-by-iteration` 一条记录:编译总计 `618750 ns`,验证总计 `28917 ns`,执行总计 `2363209 ns`,期望值为 `3333`。这是单次开发机样本,只用于确认工作负载已纳入语义护栏与性能采集;跨版本比较仍须按本报告口径重复至少三次并取中位数。 标准 `cargo bench --locked --bench core`(10,000 次)同样已纳入该负载;本次样本为编译 `38.544 ms`、验证 `2.032 ms`、执行 `157.776 ms`,期望值 `3333`。它与正向 `stepped-iteration` 的执行样本(`124.616 ms`)同场输出,便于观察有符号步进的额外边界检查成本。 diff --git a/RFCs/0083-qdocco-markdown.md b/RFCs/0083-qdocco-markdown.md index 198bd36..995470a 100644 --- a/RFCs/0083-qdocco-markdown.md +++ b/RFCs/0083-qdocco-markdown.md @@ -11,6 +11,8 @@ `qdocco --markdown FILE [-o OUTPUT]` 先按普通 `qdocco` 路径读取、编译、验证并执行文件,再生成 Markdown。默认输出为输入文件同名的 `.md`;`-o` 指定其他路径。文档包含 `## Notes` 说明栏、`quickcoffee` 代码栏和 `## Final value` 最终值栏。代码围栏至少使用四个反引号,并按源码中最长的连续反引号序列自适应加长,以保留源文本而不让其中的 Markdown 标记执行。 +输出路径经规范化后不得与输入源码相同(包括指向输入的符号链接),避免 `-o` 意外破坏文学源文件;此时命令以用法错误退出。 + `--markdown` 与 `--check` 互斥;冲突返回退出码 2。读取、解析、验证或执行错误沿用现有非零退出语义。`qdocco --check` 仍只验证而不写任何产物,HTML 默认行为不变。 ## 验收 diff --git a/src/bin/qdocco.rs b/src/bin/qdocco.rs index 3ae655e..272715b 100644 --- a/src/bin/qdocco.rs +++ b/src/bin/qdocco.rs @@ -6,6 +6,12 @@ use std::{env, fs, path::PathBuf, process::ExitCode}; fn usage() { eprintln!("Usage: qdocco [--check | --markdown] FILE [-o OUTPUT]\n qdocco --version"); } +fn same_path(left: &PathBuf, right: &PathBuf) -> bool { + match (fs::canonicalize(left), fs::canonicalize(right)) { + (Ok(left), Ok(right)) => left == right, + _ => left == right, + } +} fn escape(input: &str) -> String { input .replace('&', "&") @@ -127,6 +133,10 @@ fn main() -> ExitCode { if !check { let destination = output.unwrap_or_else(|| input.with_extension(if markdown { "md" } else { "html" })); + if same_path(&input, &destination) { + eprintln!("output path must differ from the input source"); + return ExitCode::from(2); + } let document = if markdown { render_markdown(&source, &result.to_string()) } else { diff --git a/tests/cli_tools.rs b/tests/cli_tools.rs index 696a3df..60bc4c6 100644 --- a/tests/cli_tools.rs +++ b/tests/cli_tools.rs @@ -72,6 +72,13 @@ fn qdocco_renders_escaped_source_and_checks() { ); let fenced_document = fs::read_to_string(&fenced_output).unwrap(); assert!(fenced_document.contains("`````quickcoffee\n# ````\ntrue\n`````")); + let overwrite = Command::new(bin("qdocco")) + .args([input.to_str().unwrap(), "-o", input.to_str().unwrap()]) + .output() + .unwrap(); + assert_eq!(overwrite.status.code(), Some(2)); + assert!(String::from_utf8_lossy(&overwrite.stderr).contains("output path must differ")); + assert_eq!(fs::read_to_string(&input).unwrap(), "## \n1 + 2\n"); let conflict = Command::new(bin("qdocco")) .args(["--check", "--markdown", input.to_str().unwrap()]) .output() From 75e51893bd19726cc57da589f10c6e92095df020 Mon Sep 17 00:00:00 2001 From: tiye Date: Sun, 23 Aug 2026 12:53:09 +0800 Subject: [PATCH 2/2] test: cover qdocco symlink overwrite guard --- tests/cli_tools.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/cli_tools.rs b/tests/cli_tools.rs index 60bc4c6..9246544 100644 --- a/tests/cli_tools.rs +++ b/tests/cli_tools.rs @@ -79,6 +79,25 @@ fn qdocco_renders_escaped_source_and_checks() { assert_eq!(overwrite.status.code(), Some(2)); assert!(String::from_utf8_lossy(&overwrite.stderr).contains("output path must differ")); assert_eq!(fs::read_to_string(&input).unwrap(), "## \n1 + 2\n"); + #[cfg(unix)] + { + let linked_output = temp.join("demo-link.html"); + std::os::unix::fs::symlink(&input, &linked_output).unwrap(); + let overwrite_via_symlink = Command::new(bin("qdocco")) + .args([ + input.to_str().unwrap(), + "-o", + linked_output.to_str().unwrap(), + ]) + .output() + .unwrap(); + assert_eq!(overwrite_via_symlink.status.code(), Some(2)); + assert!( + String::from_utf8_lossy(&overwrite_via_symlink.stderr) + .contains("output path must differ") + ); + assert_eq!(fs::read_to_string(&input).unwrap(), "## \n1 + 2\n"); + } let conflict = Command::new(bin("qdocco")) .args(["--check", "--markdown", input.to_str().unwrap()]) .output()