fix/audit-findings #5

Merged
trevon merged 14 commits from fix/audit-findings into main 2026-09-25 02:39:30 +00:00
4 changed files with 42 additions and 0 deletions
Showing only changes of commit e03fa40ccc - Show all commits

fix(c): parse .c files with the C grammar

`.c` files were parsed with tree-sitter-cpp, so valid C that uses C++
keywords as identifiers (`int delete = 0; int new = 1;`, a parameter
named `class`) produced grammar-error diagnostics and lost symbols.

`.c` files now parse with tree-sitter-c (pinned =0.24.2, backend
`c:0.24.2`) while staying on the shared C/C++ extraction path, so C
sources and the headers they include still share one include/declaration
index. Headers (`.h`) keep the C++ grammar, which parses the C subset.

Test: cpp_xtu::c_sources_parse_with_the_c_grammar fails before this
change (grammar errors in util.c).
trvon 2026-09-24 20:14:21 -06:00
Signed by: trevon
GPG key ID: 38686EA797D7A8A7

11
Cargo.lock generated
View file

@ -84,6 +84,7 @@ dependencies = [
"serde_json",
"tempfile",
"tree-sitter",
"tree-sitter-c",
"tree-sitter-cpp",
"tree-sitter-dart",
"tree-sitter-javascript",
@ -423,6 +424,16 @@ dependencies = [
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-c"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9b2eb57a55fed6b00812912e730b7a275cf4fe98bfd6a5d76263d4438371728"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-cpp"
version = "0.23.4"

View file

@ -16,6 +16,7 @@ regex = "1.12"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tree-sitter = "=0.26.13"
tree-sitter-c = "=0.24.2"
tree-sitter-cpp = "=0.23.4"
tree-sitter-javascript = "=0.25.0"
tree-sitter-typescript = "=0.23.2"

View file

@ -184,6 +184,9 @@ fn preprocessor_alternative_lines(source: &str) -> Vec<bool> {
pub(crate) fn extract(language: &str, path: &str, source: &str) -> Result<Document> {
let (grammar, version) = match language {
// `.c` shares the C/C++ extraction path (and header index) but must parse
// as C: identifiers such as `new`, `delete` or `class` are valid C.
"cpp" if path.ends_with(".c") => (tree_sitter_c::LANGUAGE, "c:0.24.2"),
"cpp" => (tree_sitter_cpp::LANGUAGE, "cpp:0.23.4"),
"rust" => (tree_sitter_rust::LANGUAGE, "rust:0.24.2"),
"javascript" => (tree_sitter_javascript::LANGUAGE, "javascript:0.25.0"),

View file

@ -406,3 +406,30 @@ fn preprocessor_alternative_branches_are_not_call_targets() {
Some("alternative")
);
}
#[test]
fn c_sources_parse_with_the_c_grammar() {
// `.c` files went through the C++ grammar, so valid C using C++ keywords as
// identifiers produced grammar-error diagnostics and damaged extraction.
let model = scan_files(&[
("src/util.h", "int twice(int new);\n"),
(
"src/util.c",
"#include \"util.h\"\nint twice(int new) { int delete = new; return delete * 2; }\n",
),
(
"src/main.c",
"#include \"util.h\"\nint main(void) { int class = 1; return twice(class); }\n",
),
]);
let grammar_errors: Vec<_> = model
.diagnostics
.iter()
.filter(|d| d.path.as_deref().is_some_and(|p| p.ends_with(".c")))
.map(|d| d.message.clone())
.collect();
assert!(grammar_errors.is_empty(), "{grammar_errors:?}");
let (target, reason) = single_call(&model, function(&model, "src/main.c", "main"));
assert_eq!(target.path.as_deref(), Some("src/util.c"));
assert_eq!(reason, None);
}