Skip to content

Commit 8fc3590

Browse files
committed
接通C族编译数据库有界静态观察并保留上下文阻塞
1 parent 4d10eed commit 8fc3590

9 files changed

Lines changed: 269 additions & 5 deletions

File tree

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
//! 编译数据库的静态条目;不是可执行计划。
2+
3+
use serde::Deserialize;
4+
5+
/// 保存原始工作目录、文件与参数,不赋予执行或项目覆盖资格。
6+
#[derive(Debug, Deserialize, PartialEq, Eq)]
7+
#[serde(deny_unknown_fields)]
8+
pub struct CFamilyCompilationEntry {
9+
/// 编译器运行目录,尚未规范化或核验文件系统身份。
10+
pub directory: String,
11+
/// 编译单元原始路径,可能相对工作目录。
12+
pub file: String,
13+
/// 原始参数数组,尚未审计插件、响应文件或写文件选项。
14+
pub arguments: Vec<String>,
15+
/// 同时存在的命令字符串只保留为数据,禁止作为执行回退。
16+
pub command: Option<String>,
17+
/// 原始产物声明,不允许据此创建或覆盖文件。
18+
pub output: Option<String>,
19+
}
20+
21+
/// 读取最多1MiB、4096条的静态数据库;返回完整原序条目或明确阻塞原因。
22+
/// 不读取路径、不执行参数,不隐式补标准,不合并同文件的不同配置。
23+
pub fn parse_c_family_compilation_database(
24+
bytes: &[u8],
25+
) -> Result<Vec<CFamilyCompilationEntry>, &'static str> {
26+
if bytes.len() > 1024 * 1024 {
27+
return Err("compilation_database_byte_limit");
28+
}
29+
let entries: Vec<CFamilyCompilationEntry> =
30+
serde_json::from_slice(bytes).map_err(|_| "compilation_database_shape_unresolved")?;
31+
if entries.is_empty() || entries.len() > 4096 {
32+
return Err("compilation_database_entry_limit");
33+
}
34+
for entry in &entries {
35+
if entry.directory.trim().is_empty() || entry.file.trim().is_empty() {
36+
return Err("compilation_database_path_unresolved");
37+
}
38+
if entry.arguments.is_empty() || entry.arguments.len() > 256 {
39+
return Err("compilation_database_arguments_unresolved");
40+
}
41+
if entry.arguments[0].trim().is_empty()
42+
|| entry
43+
.arguments
44+
.iter()
45+
.any(|arg| arg.contains('\0') || arg.len() > 16 * 1024)
46+
|| entry.directory.contains('\0')
47+
|| entry.file.contains('\0')
48+
{
49+
return Err("compilation_database_argument_limit_or_invalid_bytes");
50+
}
51+
}
52+
Ok(entries)
53+
}
54+
55+
#[cfg(test)]
56+
mod tests {
57+
use super::parse_c_family_compilation_database;
58+
59+
#[test]
60+
fn preserves_conflicting_configurations_and_unexecuted_arguments() {
61+
let entries = parse_c_family_compilation_database(br#"[
62+
{"directory":"/build/a", "file":"../src/a.cpp", "arguments":["clang++","-Iinclude one","-DVALUE=1","-std=c++17","-c","../src/a.cpp"]},
63+
{"directory":"/build/b", "file":"../src/a.cpp", "arguments":["clang++","-Iother","-DVALUE=2","@flags.rsp","$(touch forbidden)","-c","../src/a.cpp"],"command":"never execute this"}
64+
]"#).unwrap();
65+
assert_eq!(entries.len(), 2);
66+
assert_eq!(entries[0].arguments[1], "-Iinclude one");
67+
assert_eq!(entries[1].directory, "/build/b");
68+
assert_eq!(entries[1].arguments[3], "@flags.rsp");
69+
assert_eq!(entries[1].arguments[4], "$(touch forbidden)");
70+
assert!(!entries[1].arguments.iter().any(|a| a.starts_with("-std=")));
71+
}
72+
73+
#[test]
74+
fn malformed_or_command_only_entries_never_become_clean_scope() {
75+
for bytes in [
76+
"[]",
77+
"{}",
78+
"null",
79+
r#"[{"directory":"/a","file":"a.cpp","command":"clang++ a.cpp"}]"#,
80+
r#"[{"directory":"/a","file":"a.cpp","arguments":[]}]"#,
81+
r#"[{"directory":"/a","file":"a.cpp","arguments":"clang++ a.cpp"}]"#,
82+
r#"[{"directory":"/a","directory":"/b","file":"a.cpp","arguments":["clang++"]}]"#,
83+
r#"[{"directory":"","file":"a.cpp","arguments":["clang++"]}]"#,
84+
r#"[{"directory":"/a","file":"a.cpp","arguments":["clang++","\u0000"]}]"#,
85+
] {
86+
assert!(
87+
parse_c_family_compilation_database(bytes.as_bytes()).is_err(),
88+
"{bytes}"
89+
);
90+
}
91+
let row = serde_json::json!({"directory":"/a", "file":"a.cpp", "arguments":["clang++"]});
92+
let rows = vec![row; 4097];
93+
assert!(parse_c_family_compilation_database(&serde_json::to_vec(&rows).unwrap()).is_err());
94+
for arguments in [
95+
vec!["clang++".to_owned(); 257],
96+
vec!["clang++".into(), "x".repeat(16 * 1024 + 1)],
97+
] {
98+
let row =
99+
serde_json::json!([{"directory":"/a", "file":"a.cpp", "arguments":arguments}]);
100+
assert!(
101+
parse_c_family_compilation_database(&serde_json::to_vec(&row).unwrap()).is_err()
102+
);
103+
}
104+
assert!(parse_c_family_compilation_database(&vec![b' '; 1024 * 1024 + 1]).is_err());
105+
}
106+
}

‎crates/codeguard-adapters/src/lib.rs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,3 +493,6 @@ mod gradle_owasp_report_ownership;
493493
mod gradle_owasp_report_ownership_parser;
494494
pub use gradle_owasp_report_ownership::GradleOwaspReportOwnership;
495495
pub use gradle_owasp_report_ownership_parser::parse_gradle_owasp_report_ownership;
496+
497+
mod c_family_compilation_entry;
498+
pub use c_family_compilation_entry::{CFamilyCompilationEntry, parse_c_family_compilation_database};
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
//! 有界编译数据库配置观察,不执行项目命令。
2+
3+
use crate::discovery::DiscoveryReport;
4+
use codeguard_adapters::{CheckerConfiguration, parse_c_family_compilation_database};
5+
use codeguard_core::ObservationPort;
6+
use sha2::{Digest, Sha256};
7+
use std::path::Path;
8+
9+
/// 保存配置摘要与未完成原因;参数为同次发现报告、观察端口和数据库路径。
10+
pub(crate) fn observe<P: ObservationPort>(
11+
report: &mut DiscoveryReport,
12+
observation: &P,
13+
path: &Path,
14+
relative: &str,
15+
) {
16+
let mut reason = "compilation_database_unreadable";
17+
if let Ok(bytes) = observation.read_bounded(path, 1024 * 1024) {
18+
if observation
19+
.read_bounded(path, 1024 * 1024)
20+
.is_ok_and(|current| current == bytes)
21+
{
22+
report
23+
.checker_config_sha256
24+
.insert(relative.into(), format!("{:x}", Sha256::digest(&bytes)));
25+
reason = match parse_c_family_compilation_database(&bytes) {
26+
Ok(_) => "compilation_database_arguments_observed_execution_context_unverified",
27+
Err(reason) => reason,
28+
};
29+
} else {
30+
reason = "compilation_database_changed";
31+
}
32+
}
33+
if reason != "compilation_database_arguments_observed_execution_context_unverified" {
34+
report.observation_complete = false;
35+
report.blocked_paths.push(relative.into());
36+
}
37+
report
38+
.unknown_conditions
39+
.push(format!("{reason}:{relative}"));
40+
let build_root = relative.rsplit_once('/').map_or(".", |(parent, _)| parent);
41+
report.checker_configurations.push(CheckerConfiguration {
42+
build_root: build_root.into(),
43+
checker_id: "c_family.compilation_database".into(),
44+
category: "lint".into(),
45+
configuration: "unknown".into(),
46+
configuration_ref: relative.into(),
47+
reason: reason.into(),
48+
next_action: "核验原编译数据库的工具、目录、参数、响应文件和输入身份;不要执行command字符串,不猜标准,不以配置可读证明项目检查通过".into(),
49+
});
50+
}
51+
52+
#[cfg(test)]
53+
mod tests {
54+
use codeguard_core::{ObservationPort, ObservedPathKind};
55+
use std::io;
56+
use std::path::{Path, PathBuf};
57+
use std::cell::Cell;
58+
59+
struct DatabaseObservation {
60+
bytes: &'static [u8],
61+
changed: bool,
62+
reads: Cell<u32>,
63+
}
64+
impl ObservationPort for DatabaseObservation {
65+
fn classify(&self, _: &Path) -> io::Result<ObservedPathKind> {
66+
Ok(ObservedPathKind::File)
67+
}
68+
fn children(&self, _: &Path) -> io::Result<Vec<PathBuf>> {
69+
Ok(vec![])
70+
}
71+
fn read_bounded(&self, _: &Path, limit: u64) -> io::Result<Vec<u8>> {
72+
assert_eq!(limit, 1024 * 1024);
73+
self.reads.set(self.reads.get() + 1);
74+
if self.changed && self.reads.get() > 1 {
75+
return Ok(b"[]".to_vec());
76+
}
77+
Ok(self.bytes.to_vec())
78+
}
79+
}
80+
81+
#[test]
82+
fn discovered_database_keeps_unverified_config_and_bound_digest() {
83+
let mut report = crate::discovery::empty_report(Path::new("project"));
84+
let observation = DatabaseObservation { bytes: br#"[{"directory":"/build","file":"a.cpp","arguments":["clang++","-Iinclude","a.cpp"]}]"#, changed:false, reads:Cell::new(0) };
85+
crate::discovery::observe_file(
86+
&mut report,
87+
&codeguard_adapters::legacy_registry().unwrap(),
88+
&observation,
89+
Path::new("project/build/compile_commands.json"),
90+
"build/compile_commands.json",
91+
&std::collections::BTreeSet::new(),
92+
);
93+
assert!(
94+
report
95+
.checker_config_sha256
96+
.contains_key("build/compile_commands.json")
97+
);
98+
assert_eq!(report.checker_configurations[0].configuration, "unknown");
99+
assert_eq!(report.checker_configurations[0].build_root, "build");
100+
assert!(report.declared_versions.is_empty());
101+
assert!(report.build_roots.is_empty());
102+
}
103+
104+
#[test]
105+
fn unstable_or_command_only_database_never_becomes_verified_config() {
106+
for (bytes, changed) in [
107+
(
108+
br#"[{"directory":"/build","file":"a.cpp","command":"clang++ a.cpp"}]"#.as_slice(),
109+
false,
110+
),
111+
(
112+
br#"[{"directory":"/build","file":"a.cpp","arguments":["clang++","a.cpp"]}]"#
113+
.as_slice(),
114+
true,
115+
),
116+
] {
117+
let mut report = crate::discovery::empty_report(Path::new("project"));
118+
let observation = DatabaseObservation {
119+
bytes,
120+
changed,
121+
reads: Cell::new(0),
122+
};
123+
super::observe(
124+
&mut report,
125+
&observation,
126+
Path::new("project/compile_commands.json"),
127+
"compile_commands.json",
128+
);
129+
assert!(!report.observation_complete);
130+
assert_eq!(report.blocked_paths, ["compile_commands.json"]);
131+
assert_eq!(report.checker_configurations[0].configuration, "unknown");
132+
if changed {
133+
assert!(report.checker_config_sha256.is_empty());
134+
}
135+
}
136+
}
137+
}

‎crates/codeguard-cli/src/discovery.rs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,10 @@ pub(crate) fn observe_file<P: ObservationPort>(
524524
report.blocked_paths.push(relative.into());
525525
return;
526526
};
527+
if name == "compile_commands.json" {
528+
crate::c_family_compilation_discovery::observe(report, observation, path, relative);
529+
return;
530+
}
527531
if LOCK_NAMES.contains(&name) || is_standard_python_lock_name(name) {
528532
report.lockfiles.insert(relative.into());
529533
match observation.read_bounded(path, 8 * 1024 * 1024) {

‎crates/codeguard-cli/src/lib.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,3 +567,5 @@ mod check_c_family_comments;
567567

568568
mod c_family_documentation_hook_feedback;
569569
mod c_family_documentation_host_guidance;
570+
571+
mod c_family_compilation_discovery;

‎openspec/changes/introduce-rust-codeguard-cli/specs/native-tool-adapters/spec.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1212,3 +1212,15 @@ check c/cpp/all SHALL expose original warning and structural documentation obser
12121212
- **WHEN** check selects discovered C/C++ source with an explicit original compiler and applicable c11/c++17 profile
12131213
- **THEN** original native warning and structural facts SHALL be returned and current initialized workspaces SHALL receive stable tasks and original task-verification guidance
12141214
- **AND** headers, preprocessing, full project options, detailed semantic accuracy and trusted closure SHALL remain distinct unresolved obligations
1215+
1216+
### Requirement: C-family compilation database observation SHALL preserve unexecuted context
1217+
1218+
The adapter SHALL parse bounded compilation database bytes without filesystem access or command execution. Each accepted entry SHALL preserve its directory, source file and full argument vector; it SHALL NOT infer a missing language standard, resolve headers/macros, choose among duplicate source configurations or claim project integration. Command-string-only entries SHALL remain unresolved rather than being split using a shell. Invalid, oversized or ambiguous entry shapes SHALL fail observation rather than silently yielding an empty clean scope. Later execution SHALL independently validate tool identity, paths, response files, compiler plugins, flags and immutable inputs; parsing SHALL NOT authorize arbitrary arguments.
1219+
1220+
#### Scenario: Multiple configurations for one translation unit
1221+
- **WHEN** a compilation database contains two argument-vector entries for one file with different include paths and macros
1222+
- **THEN** observation retains both entries in their original order and neither selects one nor declares project coverage
1223+
1224+
#### Scenario: Command string cannot provide trusted arguments
1225+
- **WHEN** an entry has only a command string, malformed argument fields or no working directory
1226+
- **THEN** observation is unresolved, no shell command executes and no default language standard is assigned

‎openspec/changes/introduce-rust-codeguard-cli/tasks.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@
345345
- [ ] 8.26 实现 c 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。
346346
- [ ] 8.27 完成 c 的 dependencies/CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。
347347
- [ ] 8.28 为 cpp 固化六类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。
348-
- [ ] 8.29 实现 cpp 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 新增C++17局部原生/WASM对照:12例实际执行,包含2例语义边界;1测试通过、0忽略,未授予生产资格,见[验收记录](../../../tests/acceptance/cpp17-native-wasm-differential.md)。
348+
- [ ] 8.29 实现 cpp 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 新增C++17局部原生/WASM对照:12例实际执行,包含2例语义边界;1测试通过、0忽略,未授予生产资格,见[验收记录](../../../tests/acceptance/cpp17-native-wasm-differential.md)。 编译数据库静态解析基础已补充:有界参数数组、原序多配置、命令字符串不执行及畸形/超限反例;2测试通过,已接到项目发现,记录双读稳定摘要与unknown配置;发现测试先失败后2项通过。尚未接到原生执行,不升级构建生态状态。
349349
- [ ] 8.30 完成 cpp 的 dependencies/CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。
350350
- [ ] 8.31 为 objc 固化六类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。
351351
- [ ] 8.32 实现 objc 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。

‎rulepacks/production_acceptance_plan_v1.json‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"crates/codeguard-adapters/src/clang_documentation_structure.rs": "368112aafedb8144d38cc05466586e7b3a21e6e937ceaade76133b9113fd005a",
2222
"crates/codeguard-adapters/src/gradle_dependency_check_plan.rs": "dfb85a3dfcf6c5e0697bb3eb58ecbd8f2d47feb71f60db506f765b0ee86b23f3",
2323
"crates/codeguard-adapters/src/gradle_owasp_report_ownership_parser.rs": "11d06f8905022307d99b50e68d1bfd44bd2f2fdd29d85872bef1d40864b83c00",
24-
"crates/codeguard-adapters/src/lib.rs": "b42efa9b3575329ed278aa03febafcbfc63740d4c17516f2ce8975b38d4d3a9e",
24+
"crates/codeguard-adapters/src/lib.rs": "c312683cf5f82653095f8ef00b9a824bef76655f4878b9691a077387a3cf18c9",
2525
"crates/codeguard-adapters/src/ruff_settings.rs": "23736d55ef974a9f0b571d162365c6554001fa5ee3b2b69db6deb4507466af88",
2626
"crates/codeguard-adapters/tests/clang_documentation_ast_contract.rs": "3ed16ccb058ecfa147228ddb5dac768be2ce5d0f214324766324c09bb2a60ced",
2727
"crates/codeguard-adapters/tests/clang_documentation_contract.rs": "9d603b7ccb435c5e55837156e76d506573e8703aadf7017be07b6cf28281e261",
@@ -45,7 +45,7 @@
4545
"crates/codeguard-cli/src/claude_hook_command.rs": "189ddd749e88418c91a88c1545dc0f745e3cfe982c1e840b371d848e144eefe9",
4646
"crates/codeguard-cli/src/command_catalogue.rs": "97642c880cecc63bdd6f63cc7bc387130d028f3b34cab1759f108aa02676441b",
4747
"crates/codeguard-cli/src/command_examples.rs": "593a8afd342610ae5854725253be89f6a49ecc1f6d0b68b5f2f271a746cf9686",
48-
"crates/codeguard-cli/src/discovery.rs": "0da29090f8fc0a9338066a3a87c2527f3ba0b51bb0e67a7b5c02880ed3484f03",
48+
"crates/codeguard-cli/src/discovery.rs": "21b2e03c33e01b16ba79301ddca2a09ed48e71ccf4b3520bcc5ce928a68d0d02",
4949
"crates/codeguard-cli/src/erlang_lint_command.rs": "0a223cbcd09923140a75c1b147f50e241adb5e722ee674e8bea0968921f76a37",
5050
"crates/codeguard-cli/src/eslint_lint_command.rs": "d89fd7c1da69c083beebabe3dca6222d3bb7247a558ea4c1e8caab930596ea2d",
5151
"crates/codeguard-cli/src/go_lint_command.rs": "34482e8c77d1ec997edcb1a4f912c997736b2848a784495850d2c9f2c7d7e800",
@@ -65,7 +65,7 @@
6565
"crates/codeguard-cli/src/java_p3c_scan.rs": "36e022ba1f7c157ff52a2839287d9b8497c9bdd5583212128a81df95f5fcf69e",
6666
"crates/codeguard-cli/src/javascript_syntax_probe.rs": "4ba09a558662332e953e89512d386642653d5cc59dd1336f8ec31b51137731e1",
6767
"crates/codeguard-cli/src/kotlin_lint_command.rs": "f19e0babe23cc3cce52d69d6821416d6b9fdd7b6d8b484d3413e1368fef05ad8",
68-
"crates/codeguard-cli/src/lib.rs": "72e69dceae578d2ccd435342abd69a7a7b4dbdfda17b5d4a801a3d2bf5206ae8",
68+
"crates/codeguard-cli/src/lib.rs": "d0691e38fa69e3da1ef344e42c84801d6819606eddc817255a3d5af27d5f2d8d",
6969
"crates/codeguard-cli/src/maven_javadoc_task_recheck.rs": "d55951f3a1b960e8885a1027d5375909bbd4e7a33d6461ff724ad65d9d35febd",
7070
"crates/codeguard-cli/src/native_clang_profile.rs": "64c0f8f37d2786dea44ef17bc9e3dbfa85d5d23455974bdc098547c96b372acf",
7171
"crates/codeguard-cli/src/next_command.rs": "b4277ef72e042ce7131efaf4022e05c74deb3fc69646f1d8a922db95fad8ea5a",

‎tests/acceptance/evidence/production-acceptance-plan/all.json‎

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)