Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,9 @@ cargo build --locked -p codeguard-cli

Version output includes `cli_version`, `target`, and protocol information. Discovery and init preview return observations and unknowns. Dry-run creates neither `AGENTS.md` nor a project data directory. Add Cargo `--offline` only when dependencies are cached.

The workspace requires `std`. The opt-in `wasm-precheck` build feature uses a bounded Rust worker with pinned Java, Python, TypeScript, TSX, and Zig grammar candidates. Java and TypeScript/TSX have narrow single-file fallback paths; `lint python` can provide bounded suspected syntax positions when Ruff is unavailable or no project Ruff configuration is declared. Native Ruff results take priority. It is absent from the published npm `0.1.2` package. No `no_std`, completed project-wide WASM fallback, zero-unsafe, or fastest-runtime claim is made. Runtime OS calls require safety review; a comprehensive security audit is not claimed.
The workspace requires `std`. The opt-in `wasm-precheck` build feature uses a bounded Rust worker with seven pinned grammar candidates: Java, Objective-C, Python, Solidity, TypeScript, TSX, and Zig. Java and TypeScript/TSX have narrow single-file fallback paths; `lint python` can provide bounded suspected syntax positions when Ruff is unavailable or no project Ruff configuration is declared. Native Ruff results take priority. It is absent from the published npm `0.1.2` package. No `no_std`, completed project-wide WASM fallback, zero-unsafe, or fastest-runtime claim is made. Runtime OS calls require safety review; a comprehensive security audit is not claimed.

The current source-built CLI's `codeguard grammar status --format=json` reports the coverage gap without loading grammars or running lint: the pinned CodeGraph source has 30 vendored WASM files plus two distinct grammars resolved through its dependency; CodeGuard has five unqualified asset candidates (Java, Python, TypeScript, TSX, Zig) and zero released syntax capabilities. Zig uses a byte-pinned import adaptation so the corrected CodeGraph grammar can load in Rust; it has no public `lint zig` route or release qualification yet. [Zig's limited acceptance record](tests/acceptance/zig-grammar-candidate.md) distinguishes this from released syntax support. Python has a source-build-only, unqualified CLI fallback path; its report remains incomplete and cannot approve delivery. The separate [coverage inventory](grammars/codegraph-coverage.json) is not an approved asset manifest. COBOL's 16.4 MB source asset also exceeds the current Rust loader's 8 MiB limit.
The current source-built CLI's `codeguard grammar status --format=json` reports the coverage gap without loading grammars or running lint: the pinned CodeGraph source has 30 vendored WASM files plus two distinct grammars resolved through its dependency; CodeGuard has seven unqualified asset candidates (Java, Objective-C, Python, Solidity, TypeScript, TSX, Zig) and zero released syntax capabilities. The two dependency grammars retain their source bytes and use a byte-pinned legacy `dylink` metadata conversion for Rust loading; they have no public lint route or release qualification. Zig uses a byte-pinned import adaptation so the corrected CodeGraph grammar can load in Rust; it has no public `lint zig` route or release qualification yet. [Zig's limited acceptance record](tests/acceptance/zig-grammar-candidate.md) distinguishes this from released syntax support. Python has a source-build-only, unqualified CLI fallback path; its report remains incomplete and cannot approve delivery. The separate [coverage inventory](grammars/codegraph-coverage.json) is not an approved asset manifest. COBOL's 16.4 MB source asset also exceeds the current Rust loader's 8 MiB limit.

### npm installation and one-off use

Expand Down
2 changes: 1 addition & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ cargo build --locked -p codeguard-cli

工作区要求 `std`。可选 `wasm-precheck` 构建特性已用固定的 Java、Python、TypeScript、TSX grammar 候选资产与有界 Rust worker。Java 和 TypeScript/TSX 有局部单文件兜底;`lint python` 在 Ruff 不可用或项目未声明 Ruff 配置时可报告有界疑似语法位置,原生 Ruff 结果优先。公开 npm `0.1.2` 不含该路径。不声称 `no_std`、已完成项目级 WASM 兜底、零 unsafe 或性能最快。运行层系统调用需要安全审查,当前不宣称已完成整体安全审计。

当前源码构建的 CLI 可用 `codeguard grammar status --format=json` 只读展示来源覆盖缺口,不加载 grammar 或运行 lint:固定 CodeGraph 来源含 30 份随仓 WASM,另有两种独立 grammar 从其依赖取得;CodeGuard 当前有 Java、Python、TypeScript、TSX 四份未验收资产候选,已发行语法能力为零。Python 已有仅限源码可选构建的 CLI 候选兜底,结果保持不完整,不能批准交付。[覆盖库存](grammars/codegraph-coverage.json)不是已批准资产清单。COBOL 来源资产约 16.4 MB,还超过当前 Rust 加载器的 8 MiB 限额。
当前源码构建的 CLI 可用 `codeguard grammar status --format=json` 只读展示来源覆盖缺口,不加载 grammar 或运行 lint:固定 CodeGraph 来源含 30 份随仓 WASM,另有两种独立 grammar 从其依赖取得;CodeGuard 当前有 Java、Objective-C、Python、Solidity、TypeScript、TSX、Zig 七份未验收资产候选,已发行语法能力为零。Objective-C 与 Solidity 的依赖包原始字节、许可证和固定散列的 `dylink` 元数据转换已入库,可作为 Rust 隔离 worker 的候选观察,但没有公开 lint 路由或发行验收;Zig 也仍是未验收候选。Python 已有仅限源码可选构建的 CLI 候选兜底,结果保持不完整,不能批准交付。[覆盖库存](grammars/codegraph-coverage.json)不是已批准资产清单。COBOL 来源资产约 16.4 MB,还超过当前 Rust 加载器的 8 MiB 限额。

### npm 安装与一次性调用

Expand Down
99 changes: 96 additions & 3 deletions crates/codeguard-adapters/src/grammar_asset_manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ pub struct GrammarAssetManifest {
pub codegraph_license_sha256: String,
/// 上游记录的构建工具版本与调用;只作来源声明。
pub build_tool: String,
/// CodeGraph 锁定的依赖包完整性;适用于 Objective-C 与 Solidity。
pub dependency_package_integrity: Option<String>,
/// 依赖包随附许可证路径。
pub dependency_package_license: Option<String>,
/// 依赖包许可证摘要。
pub dependency_package_license_sha256: Option<String>,
/// 精确且无重复的候选语言资产。
pub assets: Vec<GrammarAsset>,
}
Expand All @@ -52,6 +58,10 @@ pub struct GrammarAsset {
pub sha256: String,
/// WASM 原始字节长度。
pub bytes: usize,
/// 可重现适配前的依赖包原始路径。
pub source_wasm: Option<String>,
/// 可重现适配前的原始摘要。
pub source_sha256: Option<String>,
/// 相对于 grammars/ 的许可证路径。
pub license: String,
/// 许可证原始字节摘要。
Expand All @@ -70,13 +80,22 @@ pub struct GrammarAsset {
pub release_status: String,
}

/// 读取仓内固定清单,并检查 CodeGraph 许可证及五份资产的原始字节。
/// 读取仓内固定清单,并检查来源、许可证及候选资产字节。
pub fn bundled_grammar_candidates() -> Result<GrammarAssetManifest, String> {
let manifest = parse_grammar_asset_manifest(include_bytes!("../../../grammars/manifest.json"))?;
let codegraph_license = include_bytes!("../../../grammars/LICENSE.codegraph");
if digest(codegraph_license) != CODEGRAPH_LICENSE_SHA256 {
return Err("CodeGraph 许可证字节与固定来源不符".into());
}
if digest(include_bytes!(
"../../../grammars/LICENSE.tree-sitter-wasms"
)) != manifest
.dependency_package_license_sha256
.as_deref()
.unwrap_or("")
{
return Err("依赖包许可证字节与固定来源不符".into());
}
for asset in &manifest.assets {
let (wasm, license) = match asset.language.as_str() {
"java" => (
Expand All @@ -99,6 +118,14 @@ pub fn bundled_grammar_candidates() -> Result<GrammarAssetManifest, String> {
include_bytes!("../../../grammars/zig/parser.wasm").as_slice(),
include_bytes!("../../../grammars/zig/LICENSE").as_slice(),
),
"objc" => (
include_bytes!("../../../grammars/objc/parser.wasm").as_slice(),
include_bytes!("../../../grammars/objc/LICENSE").as_slice(),
),
"solidity" => (
include_bytes!("../../../grammars/solidity/parser.wasm").as_slice(),
include_bytes!("../../../grammars/solidity/LICENSE").as_slice(),
),
_ => return Err("候选语言未知".into()),
};
verify_grammar_asset(asset, wasm, license)?;
Expand All @@ -107,6 +134,18 @@ pub fn bundled_grammar_candidates() -> Result<GrammarAssetManifest, String> {
{
return Err("Zig grammar 来源适配关系不符".into());
}
if matches!(asset.language.as_str(), "objc" | "solidity") {
let source = if asset.language == "objc" {
include_bytes!("../../../grammars/objc/source.wasm").as_slice()
} else {
include_bytes!("../../../grammars/solidity/source.wasm").as_slice()
Comment on lines +137 to +141

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep raw grammar sources out of default CLI binaries

In builds without wasm-precheck, grammar status still calls bundled_grammar_candidates, so these unconditional include_bytes! references keep both the source and adapted Objective-C/Solidity modules reachable in the normal CLI. The four files total about 15.5 MiB, meaning this ostensibly optional worker change substantially enlarges every default/native npm binary even though those builds cannot invoke __syntax-worker; gate the byte-level adaptation verification behind the feature or move it to build/test-time validation.

Useful? React with 👍 / 👎.

};
if asset.source_sha256.as_deref() != Some(digest(source).as_str())
|| crate::adapt_legacy_dylink(&asset.language, source)? != wasm
{
return Err("依赖 grammar 来源适配关系不符".into());
}
}
}
Ok(manifest)
}
Expand All @@ -126,7 +165,14 @@ pub fn parse_grammar_asset_manifest(raw: &[u8]) -> Result<GrammarAssetManifest,
|| manifest.codegraph_license_sha256 != CODEGRAPH_LICENSE_SHA256
|| manifest.build_tool
!= "tree-sitter-cli 0.25.10 build --wasm; Zig: tree-sitter-cli 0.27.0 generate --abi 15 + Zig 0.16.0 wasm32-wasi"
|| manifest.assets.len() != 5
|| manifest.dependency_package_integrity.as_deref()
!= Some(
"tree-sitter-wasms@0.1.13 sha512-wT+cR6DwaIz80/vho3AvSF0N4txuNx/5bcRKoXouOfClpxh/qqrF4URNLQXbbt8MaAxeksZcZd1j8gcGjc+QxQ==",
)
|| manifest.dependency_package_license.as_deref() != Some("LICENSE.tree-sitter-wasms")
|| manifest.dependency_package_license_sha256.as_deref()
!= Some("6b0382b16279f26ff69014300541967a356a666eb0b91b422f6862f6b7dad17e")
|| manifest.assets.len() != 7
{
return Err("grammar 清单版本、来源或资产数量不符".into());
}
Expand All @@ -135,7 +181,17 @@ pub fn parse_grammar_asset_manifest(raw: &[u8]) -> Result<GrammarAssetManifest,
.iter()
.map(|asset| asset.language.as_str())
.collect();
if languages != BTreeSet::from(["java", "python", "typescript", "tsx", "zig"]) {
if languages
!= BTreeSet::from([
"java",
"objc",
"python",
"solidity",
"typescript",
"tsx",
"zig",
])
{
return Err("grammar 语言资产缺失或重复".into());
}
for asset in &manifest.assets {
Expand Down Expand Up @@ -191,9 +247,46 @@ pub fn parse_grammar_asset_manifest(raw: &[u8]) -> Result<GrammarAssetManifest,
"zig/LICENSE",
"0eea8dc45e89deeb03c7799bbbc7b4688f365fb274562f4540ecfebdea82e727",
),
"objc" => (
"https://github.com/amaanq/tree-sitter-objc",
"dea2b2d6a1253c2f0f58b543c10d62edd2f860f2",
"2.1.0",
"objc/parser.wasm",
"2606d4c5809fab61de44d328072d1371d53aa4aff5734436cb2a0ce8db7f2b0c",
7708267,
"objc/LICENSE",
"099c44248f8cf353123211318680e93465587c005a4b3730ce8cb5334de043d6",
),
"solidity" => (
"https://github.com/JoranHonig/tree-sitter-solidity",
"b239a95f94cfcc6e7b3e961bc73a28d55e214f02",
"1.2.0",
"solidity/parser.wasm",
"ba02ba3c98c8ce976ed962d727ef48940b3a18dd2243830a8158b558de64b4f2",
423943,
"solidity/LICENSE",
"8844f0cc9b76b9c8a9d0251904eb7536b6bb9976e0ec577e8f27ab96d42523ef",
),
_ => return Err("grammar 语言未知".into()),
};
if asset.dialect != asset.language
|| (match asset.language.as_str() {
"objc" => {
asset.source_wasm.as_deref() != Some("objc/source.wasm")
|| asset.source_sha256.as_deref()
!= Some(
"7c1b5bfdca7e64b6c63b6040bb7ba0afc347df116f9030ca32f8535d7377f6ff",
)
}
"solidity" => {
asset.source_wasm.as_deref() != Some("solidity/source.wasm")
|| asset.source_sha256.as_deref()
!= Some(
"160745e470f234cae903a9ba445d19e758d0b02e1197401fc765976c6254d2b6",
)
}
_ => asset.source_wasm.is_some() || asset.source_sha256.is_some(),
})
|| asset.grammar_repository != repo
|| asset.grammar_commit != commit
|| asset.grammar_version != version
Expand Down
102 changes: 102 additions & 0 deletions crates/codeguard-adapters/src/legacy_dylink_compat.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
//! 将固定依赖包的旧版 dylink 元数据转换成当前 Rust WasmStore 所需格式。

use sha2::{Digest, Sha256};

/// 仅改写固定来源 WASM 的动态链接自定义段,并验证结果摘要。
/// 参数为语言名与原始字节;返回可由 Rust 加载的字节,不修改语法指令。
pub fn adapt_legacy_dylink(language: &str, source: &[u8]) -> Result<Vec<u8>, String> {
let (source_sha, output_sha) = match language {
"objc" => (
"7c1b5bfdca7e64b6c63b6040bb7ba0afc347df116f9030ca32f8535d7377f6ff",
"2606d4c5809fab61de44d328072d1371d53aa4aff5734436cb2a0ce8db7f2b0c",
),
"solidity" => (
"160745e470f234cae903a9ba445d19e758d0b02e1197401fc765976c6254d2b6",
"ba02ba3c98c8ce976ed962d727ef48940b3a18dd2243830a8158b558de64b4f2",
),
_ => return Err("未知的旧版 dylink grammar".into()),
};
if format!("{:x}", Sha256::digest(source)) != source_sha
|| !source.starts_with(b"\0asm\x01\0\0\0")
{
return Err("旧版 dylink grammar 来源字节不符".into());
}
let mut output = source[..8].to_vec();
let mut offset = 8;
let mut converted = false;
while offset < source.len() {
let start = offset;
let id = source[offset];
offset += 1;
let (length, payload_start) = read_leb(source, offset)?;
let end = payload_start
.checked_add(length)
.filter(|end| *end <= source.len())
.ok_or("WASM section 越界")?;
if id == 0 {
let (name_len, name_start) = read_leb(source, payload_start)?;
let name_end = name_start
.checked_add(name_len)
.filter(|end_name| *end_name <= end)
.ok_or("WASM 自定义段名称越界")?;
if &source[name_start..name_end] == b"dylink" {
if converted {
return Err("重复 dylink 段".into());
}
let legacy = &source[name_end..end];
let mut position = 0;
for _ in 0..4 {
position = read_leb(legacy, position)?.1;
}
if legacy[position..].iter().any(|byte| *byte != 0) || legacy.len() != position + 1
{
return Err("旧版 dylink 额外依赖布局不支持".into());
}
let mut body = vec![8];
body.extend_from_slice(b"dylink.0");
body.push(1);
write_leb(position, &mut body);
body.extend_from_slice(&legacy[..position]);
output.push(0);
write_leb(body.len(), &mut output);
output.extend_from_slice(&body);
converted = true;
offset = end;
continue;
}
}
output.extend_from_slice(&source[start..end]);
offset = end;
}
if !converted || format!("{:x}", Sha256::digest(&output)) != output_sha {
return Err("旧版 dylink grammar 适配结果不符".into());
}
Ok(output)
}

fn read_leb(bytes: &[u8], mut offset: usize) -> Result<(usize, usize), String> {
let mut value = 0usize;
for shift in (0..35).step_by(7) {
let byte = *bytes.get(offset).ok_or("WASM LEB 长度缺失")?;
offset += 1;
value |= usize::from(byte & 0x7f) << shift;
if byte & 0x80 == 0 {
return Ok((value, offset));
}
}
Err("WASM LEB 长度无效".into())
}

fn write_leb(mut value: usize, output: &mut Vec<u8>) {
loop {
let mut byte = (value & 0x7f) as u8;
value >>= 7;
if value != 0 {
byte |= 0x80;
}
output.push(byte);
if value == 0 {
break;
}
}
}
2 changes: 2 additions & 0 deletions crates/codeguard-adapters/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,13 @@ mod eslint_message_report;
mod eslint_parsed;
mod go_candidate;
mod grammar_asset_manifest;
mod legacy_dylink_compat;
mod zig_wasm_compat;
pub use grammar_asset_manifest::{
GrammarAsset, GrammarAssetManifest, bundled_grammar_candidates, parse_grammar_asset_manifest,
verify_grammar_asset,
};
pub use legacy_dylink_compat::adapt_legacy_dylink;
pub use zig_wasm_compat::adapt_zig_wasm;
mod go_list_package;
mod go_list_scope;
Expand Down
Loading
Loading