Skip to main content

garbage_code_hunter/language/adapter/
c.rs

1//! CAdapter — C language adapter.
2
3use super::c_cpp_common;
4use super::{count_dead_code_with, count_duplicate_imports_with, FunctionNode, LanguageAdapter};
5use crate::language::Language;
6use crate::treesitter::engine::ParsedFile;
7use crate::treesitter::query::QueryCapture;
8
9const C_PATTERNS: &[&str] = &[
10    "(function_definition declarator: (function_declarator declarator: (identifier) @ex_name)) @ex_fn",
11    "(init_declarator declarator: (identifier) @nv_var)",
12    "(call_expression function: (identifier) @dp_func (#match? @dp_func \"^(printf|fprintf|puts|putchar)$\"))",
13    "(function_declarator parameters: (parameter_list) @ep_params)",
14    "(number_literal) @mn_num",
15    "(goto_statement) @ci_goto",
16    "(sizeof_expression) @ci_sizeof",
17    "(call_expression function: (identifier) @ci_malloc (#match? @ci_malloc \"^(malloc|calloc|realloc)$\"))",
18    "(call_expression function: (identifier) @pc_func (#match? @pc_func \"^(exit|abort|assert|_Exit|quick_exit|longjmp)$\"))",
19];
20
21pub struct CAdapter;
22
23impl LanguageAdapter for CAdapter {
24    fn language(&self) -> Language {
25        Language::C
26    }
27
28    fn query_patterns(&self) -> &[&str] {
29        C_PATTERNS
30    }
31
32    fn count_panic_calls(&self, file: &ParsedFile) -> usize {
33        self.count_panic_from_batch(file, &self.batch_captures(file))
34    }
35
36    fn extract_functions(&self, file: &ParsedFile) -> Vec<FunctionNode> {
37        self.extract_functions_from_batch(file, &self.batch_captures(file))
38    }
39
40    fn max_nesting_depth(&self, file: &ParsedFile) -> usize {
41        c_cpp_common::c_scope_depth(file.root_node(), 0)
42    }
43
44    fn count_naming_violations(&self, file: &ParsedFile) -> usize {
45        self.count_naming_from_batch(file, &self.batch_captures(file))
46    }
47
48    fn count_deeply_nested_blocks(&self, file: &ParsedFile) -> usize {
49        let mut count = 0;
50        c_cpp_common::walk_c_blocks(file.root_node(), 0, 5, &mut count);
51        count
52    }
53
54    fn count_debug_calls(&self, file: &ParsedFile) -> usize {
55        self.count_debug_from_batch(file, &self.batch_captures(file))
56    }
57
58    fn count_excessive_params(&self, file: &ParsedFile, _threshold: usize) -> usize {
59        self.count_excessive_from_batch(file, &self.batch_captures(file))
60    }
61
62    fn count_magic_numbers(&self, file: &ParsedFile) -> usize {
63        self.count_magic_from_batch(file, &self.batch_captures(file))
64    }
65
66    fn count_dead_code(&self, file: &ParsedFile) -> usize {
67        count_dead_code_with(
68            file,
69            &["return;", "break;", "continue;"],
70            &[
71                "return ",
72                "exit(",
73                "abort(",
74                "_Exit(",
75                "quick_exit(",
76                "goto ",
77            ],
78            "//",
79        )
80    }
81
82    fn count_duplicate_imports(&self, file: &ParsedFile) -> usize {
83        count_duplicate_imports_with(file, &["#include"])
84    }
85
86    fn count_c_issues(&self, file: &ParsedFile) -> usize {
87        self.count_c_from_batch(file, &self.batch_captures(file))
88    }
89
90    // -- _from_batch overrides --
91
92    fn extract_functions_from_batch<'a>(
93        &self,
94        _file: &ParsedFile,
95        batch: &[Vec<QueryCapture<'a>>],
96    ) -> Vec<FunctionNode> {
97        c_cpp_common::extract_functions_from_batch(batch)
98    }
99
100    fn count_naming_from_batch<'a>(
101        &self,
102        _file: &ParsedFile,
103        batch: &[Vec<QueryCapture<'a>>],
104    ) -> usize {
105        c_cpp_common::count_naming_from_batch(batch)
106    }
107
108    fn count_debug_from_batch<'a>(
109        &self,
110        _file: &ParsedFile,
111        batch: &[Vec<QueryCapture<'a>>],
112    ) -> usize {
113        c_cpp_common::count_debug_from_batch(batch)
114    }
115
116    fn count_excessive_from_batch<'a>(
117        &self,
118        _file: &ParsedFile,
119        batch: &[Vec<QueryCapture<'a>>],
120    ) -> usize {
121        c_cpp_common::count_excessive_from_batch(batch)
122    }
123
124    fn count_magic_from_batch<'a>(
125        &self,
126        _file: &ParsedFile,
127        batch: &[Vec<QueryCapture<'a>>],
128    ) -> usize {
129        c_cpp_common::count_magic_from_batch(batch)
130    }
131
132    fn count_c_from_batch<'a>(&self, file: &ParsedFile, batch: &[Vec<QueryCapture<'a>>]) -> usize {
133        c_cpp_common::count_c_issues_from_batch(file, batch, &[])
134    }
135
136    fn count_panic_from_batch<'a>(
137        &self,
138        _file: &ParsedFile,
139        batch: &[Vec<QueryCapture<'a>>],
140    ) -> usize {
141        batch
142            .iter()
143            .filter(|m| m.iter().any(|c| c.name == "pc_func"))
144            .count()
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::super::parse_code;
151    use super::*;
152
153    fn parse_c(code: &str) -> ParsedFile {
154        parse_code(code, "test.c").expect("parse")
155    }
156
157    #[test]
158    fn test_c_count_panic_exit() {
159        let code = r#"
160void main() {
161    exit(1);
162    abort();
163}
164"#;
165        let file = parse_c(code);
166        let adapter = CAdapter;
167        assert_eq!(adapter.count_panic_calls(&file), 2);
168    }
169
170    #[test]
171    fn test_c_count_panic_assert() {
172        let code = "void main() { assert(x > 0); }\n";
173        let file = parse_c(code);
174        let adapter = CAdapter;
175        assert_eq!(adapter.count_panic_calls(&file), 1);
176    }
177
178    #[test]
179    fn test_c_count_panic_clean() {
180        let code = "int add(int x) { return x + 1; }\n";
181        let file = parse_c(code);
182        let adapter = CAdapter;
183        assert_eq!(adapter.count_panic_calls(&file), 0);
184    }
185
186    #[test]
187    fn test_c_extract_functions() {
188        let code = r#"
189int foo() { return 1; }
190void bar(int x) {}
191"#;
192        let file = parse_c(code);
193        let adapter = CAdapter;
194        let fns = adapter.extract_functions(&file);
195        assert_eq!(fns.len(), 2);
196        assert_eq!(fns[0].name, "foo");
197        assert_eq!(fns[1].name, "bar");
198    }
199
200    #[test]
201    fn test_c_naming_single_letter() {
202        let code = r#"
203void main() {
204    int x = 1;
205    int y = 2;
206}
207"#;
208        let file = parse_c(code);
209        let adapter = CAdapter;
210        assert_eq!(adapter.count_naming_violations(&file), 2);
211    }
212
213    #[test]
214    fn test_c_debug_printf() {
215        let code = r#"
216void main() {
217    printf("hello");
218    fprintf(stderr, "bad");
219}
220"#;
221        let file = parse_c(code);
222        let adapter = CAdapter;
223        assert_eq!(adapter.count_debug_calls(&file), 2);
224    }
225
226    #[test]
227    fn test_c_excessive_params() {
228        let code = "void process(int a, int b, int c, int d, int e, int f) {}\n";
229        let file = parse_c(code);
230        let adapter = CAdapter;
231        assert_eq!(adapter.count_excessive_params(&file, 5), 1);
232    }
233
234    #[test]
235    fn test_c_magic_numbers() {
236        let code = r#"
237void main() {
238    foo(41);
239    bar(100);
240}
241"#;
242        let file = parse_c(code);
243        let adapter = CAdapter;
244        assert_eq!(adapter.count_magic_numbers(&file), 2);
245    }
246
247    #[test]
248    fn test_c_magic_numbers_skips_trivial() {
249        let code = "void main() { foo(0); bar(1); }\n";
250        let file = parse_c(code);
251        let adapter = CAdapter;
252        assert_eq!(adapter.count_magic_numbers(&file), 0);
253    }
254
255    #[test]
256    fn test_c_dead_code_after_return() {
257        let code = r#"
258int foo() {
259    return 42;
260    printf("dead");
261}
262"#;
263        let file = parse_c(code);
264        let adapter = CAdapter;
265        assert_eq!(adapter.count_dead_code(&file), 1);
266    }
267
268    #[test]
269    fn test_c_sizeof_type() {
270        let code = "void main() { int x = sizeof(int); }\n";
271        let file = parse_c(code);
272        let adapter = CAdapter;
273        assert!(adapter.count_c_issues(&file) >= 1);
274    }
275
276    #[test]
277    fn test_c_malloc_no_check() {
278        let code = r#"
279void main() {
280    int *p = malloc(100);
281    *p = 42;
282}
283"#;
284        let file = parse_c(code);
285        let adapter = CAdapter;
286        assert!(adapter.count_c_issues(&file) >= 1);
287    }
288}