Skip to content

Commit 23d8d70

Browse files
committed
接通 C/C++ 明确标准的 Clang 原生诊断
1 parent 8541424 commit 23d8d70

19 files changed

Lines changed: 1772 additions & 13 deletions

‎README.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -824,3 +824,5 @@ JavaScript fallback candidates now reach project checks and confirmed edit hooks
824824
These additions describe the current source candidate; the public npm release is unchanged.
825825

826826
Standalone `lint <registered-language> FILE` now accepts every canonical registry ID. Existing native language adapters keep their original paths; other languages return `syntax_lint_feedback` with an explicit native-adapter gap and unknown configuration. WASM-enabled source builds additionally run matching bounded grammar candidates and reuse `.codeguard/` confirmation tasks with `--workspace ABS_ROOT`. This is partial syntax feedback, not native lint coverage or a new published npm capability.
827+
828+
C/C++ source builds also accept explicit standalone native context: `codeguard lint c main.c --clang-tool /ABS/PATH/clang --standard c11 --format=json` (C++ uses `cpp`/`c++17`). This measured Apple Clang 21 profile reports original native rules and byte positions plus a reusable verification argv. It does not yet synchronize Clang tasks or replace project lint; unresolved preprocessor context remains incomplete.

‎README.zh-CN.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,3 +807,5 @@ JavaScript 回退候选已接入项目检查与确认编辑 Hook:重复顶层
807807
以上为当前源码候选的新增能力,公开 npm 发行未包含本批修改。
808808

809809
独立 `lint <注册表规范语言> FILE` 已接受全部规范语言ID:已有专用原生适配器保留原路径,其余输出 `syntax_lint_feedback`,明确原生适配缺口及配置未知。启用WASM的源码构建追加匹配的有界grammar候选,提供 `--workspace ABS_ROOT` 时复用 `.codeguard/` 原生确认任务。这是局部语法反馈,不代表原生lint全覆盖,也未更新公开npm能力。
810+
811+
C/C++源码构建还支持明确的独立原生上下文:`codeguard lint c main.c --clang-tool /ABS/PATH/clang --standard c11 --format=json`(C++使用 `cpp`/`c++17`)。已实测Apple Clang21档案返回原生规则、字节位置及可复用复检argv;尚未同步Clang原生任务,不替代项目lint,预处理上下文未解析时保持未完成。
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
//! 固定 Clang stdin SARIF 读者;不使用诊断自由文本或外部文件定位。
2+
use serde_json::{Value, json};
3+
4+
/// 从固定Apple Clang21的SARIF提取同一冻结源码的原生规则及UTF-8字节位置。
5+
/// 参数为有界报告、源码与原生成功状态;未知身份/列单位/报告矛盾返回错误。
6+
pub fn parse_clang_stdin_sarif(
7+
bytes: &[u8],
8+
source: &[u8],
9+
success: bool,
10+
) -> Result<Vec<Value>, &'static str> {
11+
if bytes.len() > 64 * 1024 || source.len() > 1024 * 1024 {
12+
return Err("clang_report_invalid");
13+
}
14+
let text = std::str::from_utf8(source).map_err(|_| "clang_report_invalid")?;
15+
let report = crate::parse_unique_json(bytes).map_err(|_| "clang_report_invalid")?;
16+
let runs = report["runs"]
17+
.as_array()
18+
.filter(|rows| rows.len() == 1)
19+
.ok_or("clang_report_invalid")?;
20+
let run = &runs[0];
21+
let driver = &run["tool"]["driver"];
22+
if report["version"] != "2.1.0"
23+
|| run["columnKind"] != "unicodeCodePoints"
24+
|| driver["name"] != "clang"
25+
|| driver["version"] != "Apple clang version 21.0.0 (clang-2100.3.34.2)"
26+
|| !run["invocations"]
27+
.as_array()
28+
.is_some_and(|rows| rows.len() == 1 && rows[0]["executionSuccessful"] == success)
29+
{
30+
return Err("clang_report_invalid");
31+
}
32+
let results = run["results"]
33+
.as_array()
34+
.filter(|rows| rows.len() <= 64)
35+
.ok_or("clang_report_invalid")?;
36+
let rules = driver["rules"].as_array().ok_or("clang_report_invalid")?;
37+
let artifacts = run["artifacts"]
38+
.as_array()
39+
.filter(|rows| rows.len() <= 1)
40+
.ok_or("clang_report_invalid")?;
41+
if artifacts
42+
.iter()
43+
.any(|a| a["location"]["uri"] != "file://" || a["location"]["index"] != 0)
44+
{
45+
return Err("clang_report_invalid");
46+
}
47+
let mut diagnostics = Vec::new();
48+
let mut error_count = 0;
49+
for result in results {
50+
let id = result["ruleId"]
51+
.as_str()
52+
.filter(|s| {
53+
!s.is_empty()
54+
&& s.len() <= 128
55+
&& s.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'_')
56+
})
57+
.ok_or("clang_report_invalid")?;
58+
let index = result["ruleIndex"]
59+
.as_u64()
60+
.and_then(|i| usize::try_from(i).ok())
61+
.ok_or("clang_report_invalid")?;
62+
if rules.get(index).is_none_or(|r| r["id"] != id) {
63+
return Err("clang_report_invalid");
64+
}
65+
let level = result["level"]
66+
.as_str()
67+
.filter(|level| matches!(*level, "error" | "warning" | "note"))
68+
.ok_or("clang_report_invalid")?;
69+
let locations = result["locations"]
70+
.as_array()
71+
.filter(|rows| !rows.is_empty() && rows.len() <= 4)
72+
.ok_or("clang_report_invalid")?;
73+
let mut first = None;
74+
for location in locations {
75+
let physical = &location["physicalLocation"];
76+
if artifacts.len() != 1
77+
|| physical["artifactLocation"]["uri"] != "file://"
78+
|| physical["artifactLocation"]["index"] != 0
79+
{
80+
return Err("clang_report_invalid");
81+
}
82+
let region = &physical["region"];
83+
let line = region["startLine"]
84+
.as_u64()
85+
.filter(|n| *n > 0)
86+
.and_then(|n| usize::try_from(n).ok())
87+
.ok_or("clang_report_invalid")?;
88+
let column = region["startColumn"]
89+
.as_u64()
90+
.filter(|n| *n > 0)
91+
.and_then(|n| usize::try_from(n).ok())
92+
.ok_or("clang_report_invalid")?;
93+
let source_line = text
94+
.split('\n')
95+
.nth(line - 1)
96+
.ok_or("clang_report_invalid")?;
97+
let offset = if column == source_line.chars().count() + 1 {
98+
source_line.len()
99+
} else {
100+
source_line
101+
.char_indices()
102+
.nth(column - 1)
103+
.map(|(offset, _)| offset)
104+
.ok_or("clang_report_invalid")?
105+
};
106+
if first.is_none() {
107+
first = Some(
108+
json!({"rule_id":format!("clang.{id}"),"line":line,"column_byte":offset+1,"level":level}),
109+
);
110+
}
111+
}
112+
if level == "error" {
113+
error_count += 1;
114+
}
115+
if level != "note" {
116+
diagnostics.push(first.ok_or("clang_report_invalid")?);
117+
}
118+
}
119+
if success != (error_count == 0) {
120+
return Err("clang_report_invalid");
121+
}
122+
Ok(diagnostics)
123+
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,3 +431,6 @@ pub use shellcheck_json::parse_shellcheck_json1;
431431

432432
mod cfquery_projection_rule;
433433
pub use cfquery_projection_rule::{cfquery_projection_rule_sha256, cfquery_projection_span_valid};
434+
435+
mod clang_sarif;
436+
pub use clang_sarif::parse_clang_stdin_sarif;
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
use serde_json::{Value, json};
2+
fn report() -> Value {
3+
json!({"version":"2.1.0","runs":[{"columnKind":"unicodeCodePoints","invocations":[{"executionSuccessful":false}],"artifacts":[{"location":{"uri":"file://","index":0}}],"tool":{"driver":{"name":"clang","version":"Apple clang version 21.0.0 (clang-2100.3.34.2)","rules":[{"id":"err_expected_expression"}]}},"results":[{"level":"error","ruleId":"err_expected_expression","ruleIndex":0,"locations":[{"physicalLocation":{"artifactLocation":{"uri":"file://","index":0},"region":{"startLine":1,"startColumn":22}}}]}]}]})
4+
}
5+
#[test]
6+
fn unicode_columns_bind_to_same_source_and_every_result_is_validated() {
7+
let source = "char *s=\"é\"; int x = ;\n".as_bytes();
8+
let valid = report();
9+
let rows = codeguard_adapters::parse_clang_stdin_sarif(
10+
&serde_json::to_vec(&valid).unwrap(),
11+
source,
12+
false,
13+
)
14+
.unwrap();
15+
assert_eq!(rows[0]["column_byte"], 23);
16+
for pointer in [
17+
"/version",
18+
"/runs/0/columnKind",
19+
"/runs/0/tool/driver/version",
20+
"/runs/0/results/0/ruleIndex",
21+
"/runs/0/results/0/locations/0/physicalLocation/artifactLocation/uri",
22+
"/runs/0/results/0/locations/0/physicalLocation/region/startColumn",
23+
] {
24+
let mut bad = valid.clone();
25+
*bad.pointer_mut(pointer).unwrap() = json!("untrusted");
26+
assert!(
27+
codeguard_adapters::parse_clang_stdin_sarif(
28+
&serde_json::to_vec(&bad).unwrap(),
29+
source,
30+
false
31+
)
32+
.is_err(),
33+
"{pointer}"
34+
);
35+
}
36+
let mut second = valid.clone();
37+
let mut bad = second["runs"][0]["results"][0].clone();
38+
bad["locations"][0]["physicalLocation"]["region"]["startLine"] = json!(100);
39+
second["runs"][0]["results"]
40+
.as_array_mut()
41+
.unwrap()
42+
.push(bad);
43+
assert!(
44+
codeguard_adapters::parse_clang_stdin_sarif(
45+
&serde_json::to_vec(&second).unwrap(),
46+
source,
47+
false
48+
)
49+
.is_err()
50+
);
51+
assert!(
52+
codeguard_adapters::parse_clang_stdin_sarif(
53+
&serde_json::to_vec(&valid).unwrap(),
54+
source,
55+
true
56+
)
57+
.is_err()
58+
);
59+
let mut empty = valid;
60+
empty["runs"][0]["results"] = json!([]);
61+
assert!(
62+
codeguard_adapters::parse_clang_stdin_sarif(
63+
&serde_json::to_vec(&empty).unwrap(),
64+
source,
65+
false
66+
)
67+
.is_err()
68+
);
69+
empty["runs"][0]["invocations"][0]["executionSuccessful"] = json!(true);
70+
empty["runs"][0]["artifacts"] = json!([]);
71+
assert!(
72+
codeguard_adapters::parse_clang_stdin_sarif(
73+
&serde_json::to_vec(&empty).unwrap(),
74+
source,
75+
true
76+
)
77+
.unwrap()
78+
.is_empty()
79+
);
80+
let bytes = serde_json::to_string(&empty).unwrap().replacen(
81+
"\"version\":\"2.1.0\"",
82+
"\"version\":\"1.0\",\"version\":\"2.1.0\"",
83+
1,
84+
);
85+
assert!(codeguard_adapters::parse_clang_stdin_sarif(bytes.as_bytes(), source, true).is_err());
86+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
//! 显式Clang标准请求的单文件原生反馈;完整项目模型及工作台原生导入尚待接线。
2+
use crate::syntax_lint_arguments::SyntaxLintArguments;
3+
use serde_json::{Value, json};
4+
use std::time::Instant;
5+
6+
/// 保留原生Clang结果与输入身份;参数为已校验请求及共同截止时间,返回不具门禁权威的局部反馈。
7+
pub(crate) fn observe(args: &SyntaxLintArguments, deadline: Instant) -> Value {
8+
let mut report = json!({"schema_version":"0.2.0","report_type":"syntax_lint_feedback","selection":{"category":"lint","language":args.language},
9+
"status":"incomplete","coverage_proven":false,"delivery_decision":"not_evaluated","findings":[],
10+
"native":{"status":"incomplete","reason":"clang_source_scope_unavailable","version":null,"tool_sha256":null,"diagnostics":[]},
11+
"native_context":{"standard":args.standard,"scope":"explicit_standalone_stdin","project_configuration":"unknown","warning_profile":"clang-wall-extra-pedantic-v1"},
12+
"source_sha256":null,"syntax_candidates":null,"syntax_tasks":null,"setup":{"requirement":"required","task_id":null},"reason":"native_project_coverage_unverified",
13+
"next_action":"按原工具、语言及明确标准复检;原生语法观察不替代项目clang-tidy、编译数据库、头文件/宏、安全或依赖检查。本入口尚未持久同步Clang原生任务,不凭零诊断关闭历史任务"});
14+
let source_path = args
15+
.source
16+
.canonicalize()
17+
.unwrap_or_else(|_| args.source.clone());
18+
let mut command = json!([
19+
"codeguard",
20+
"lint",
21+
args.language,
22+
source_path,
23+
"--clang-tool",
24+
args.clang_tool,
25+
"--standard",
26+
args.standard,
27+
"--format=json"
28+
]);
29+
if let Some(workspace) = &args.workspace {
30+
command
31+
.as_array_mut()
32+
.expect("复检命令为参数数组")
33+
.extend([json!("--workspace"), json!(workspace)]);
34+
}
35+
report["path"] = json!(source_path);
36+
report["verification_command"] = command;
37+
let source = match crate::plain_syntax_source::read_plain_source(&args.source) {
38+
Ok(source) => source,
39+
Err(reason) => {
40+
report["native"]["reason"] = json!(reason);
41+
return report;
42+
}
43+
};
44+
let Ok(path) = args.source.canonicalize() else {
45+
return report;
46+
};
47+
if args.workspace.as_ref().is_some_and(|root| {
48+
root.canonicalize().ok().as_ref() != Some(root) || !path.starts_with(root)
49+
}) {
50+
return report;
51+
}
52+
if !((args.language == "c" && path.extension().is_some_and(|s| s == "c"))
53+
|| (args.language == "cpp"
54+
&& path
55+
.extension()
56+
.and_then(|s| s.to_str())
57+
.is_some_and(|s| matches!(s, "cpp" | "cc" | "cxx" | "C" | "CPP" | "cp" | "c++"))))
58+
{
59+
return report;
60+
}
61+
use sha2::{Digest, Sha256};
62+
report["source_sha256"] = json!(format!("{:x}", Sha256::digest(&source)));
63+
let mut native = crate::clang_syntax_probe::observe(
64+
args.clang_tool.as_deref().expect("明确Clang请求已核对工具"),
65+
&args.language,
66+
args.standard.as_deref().expect("明确Clang请求已核对标准"),
67+
&source,
68+
deadline,
69+
);
70+
if !crate::plain_syntax_source::read_plain_source(&args.source)
71+
.is_ok_and(|current| current == source)
72+
{
73+
native["status"] = json!("incomplete");
74+
native["reason"] = json!("clang_source_changed");
75+
native["diagnostics"] = json!([]);
76+
}
77+
report["native"] = native;
78+
report
79+
}

0 commit comments

Comments
 (0)