Skip to main content

garbage_code_hunter/language/adapter/
cpp.rs

1//! CppAdapter — 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 CPP_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    "(new_expression) @ci_new",
19    "(call_expression function: (identifier) @pc_func (#match? @pc_func \"^(exit|abort|assert|terminate|_Exit|quick_exit)$\"))",
20    "(throw_statement) @pc_throw",
21];
22
23pub struct CppAdapter;
24
25impl LanguageAdapter for CppAdapter {
26    fn language(&self) -> Language {
27        Language::Cpp
28    }
29
30    fn query_patterns(&self) -> &[&str] {
31        CPP_PATTERNS
32    }
33
34    fn count_panic_calls(&self, file: &ParsedFile) -> usize {
35        self.count_panic_from_batch(file, &self.batch_captures(file))
36    }
37
38    fn extract_functions(&self, file: &ParsedFile) -> Vec<FunctionNode> {
39        self.extract_functions_from_batch(file, &self.batch_captures(file))
40    }
41
42    fn max_nesting_depth(&self, file: &ParsedFile) -> usize {
43        c_cpp_common::c_scope_depth(file.root_node(), 0)
44    }
45
46    fn count_naming_violations(&self, file: &ParsedFile) -> usize {
47        self.count_naming_from_batch(file, &self.batch_captures(file))
48    }
49
50    fn count_deeply_nested_blocks(&self, file: &ParsedFile) -> usize {
51        let mut count = 0;
52        c_cpp_common::walk_c_blocks(file.root_node(), 0, 5, &mut count);
53        count
54    }
55
56    fn count_debug_calls(&self, file: &ParsedFile) -> usize {
57        self.count_debug_from_batch(file, &self.batch_captures(file))
58    }
59
60    fn count_excessive_params(&self, file: &ParsedFile, _threshold: usize) -> usize {
61        self.count_excessive_from_batch(file, &self.batch_captures(file))
62    }
63
64    fn count_magic_numbers(&self, file: &ParsedFile) -> usize {
65        self.count_magic_from_batch(file, &self.batch_captures(file))
66    }
67
68    fn count_dead_code(&self, file: &ParsedFile) -> usize {
69        count_dead_code_with(
70            file,
71            &["return;", "break;", "continue;"],
72            &["return ", "throw ", "exit(", "abort(", "terminate("],
73            "//",
74        )
75    }
76
77    fn count_duplicate_imports(&self, file: &ParsedFile) -> usize {
78        count_duplicate_imports_with(file, &["#include"])
79    }
80
81    fn count_c_issues(&self, file: &ParsedFile) -> usize {
82        self.count_c_from_batch(file, &self.batch_captures(file))
83    }
84
85    // -- _from_batch overrides --
86
87    fn extract_functions_from_batch<'a>(
88        &self,
89        _file: &ParsedFile,
90        batch: &[Vec<QueryCapture<'a>>],
91    ) -> Vec<FunctionNode> {
92        c_cpp_common::extract_functions_from_batch(batch)
93    }
94
95    fn count_naming_from_batch<'a>(
96        &self,
97        _file: &ParsedFile,
98        batch: &[Vec<QueryCapture<'a>>],
99    ) -> usize {
100        c_cpp_common::count_naming_from_batch(batch)
101    }
102
103    fn count_debug_from_batch<'a>(
104        &self,
105        file: &ParsedFile,
106        batch: &[Vec<QueryCapture<'a>>],
107    ) -> usize {
108        let base = c_cpp_common::count_debug_from_batch(batch);
109        let stream_count = file
110            .content
111            .lines()
112            .filter(|line| {
113                let t = line.trim();
114                !t.starts_with("//")
115                    && !t.starts_with("/*")
116                    && !t.starts_with("*")
117                    && (t.contains("cout") || t.contains("cerr") || t.contains("clog"))
118            })
119            .count();
120        base + stream_count
121    }
122
123    fn count_excessive_from_batch<'a>(
124        &self,
125        _file: &ParsedFile,
126        batch: &[Vec<QueryCapture<'a>>],
127    ) -> usize {
128        c_cpp_common::count_excessive_from_batch(batch)
129    }
130
131    fn count_magic_from_batch<'a>(
132        &self,
133        _file: &ParsedFile,
134        batch: &[Vec<QueryCapture<'a>>],
135    ) -> usize {
136        c_cpp_common::count_magic_from_batch(batch)
137    }
138
139    fn count_c_from_batch<'a>(&self, file: &ParsedFile, batch: &[Vec<QueryCapture<'a>>]) -> usize {
140        c_cpp_common::count_c_issues_from_batch(file, batch, &["ci_new"])
141    }
142
143    fn count_panic_from_batch<'a>(
144        &self,
145        _file: &ParsedFile,
146        batch: &[Vec<QueryCapture<'a>>],
147    ) -> usize {
148        batch
149            .iter()
150            .filter(|m| {
151                m.iter()
152                    .any(|c| c.name == "pc_func" || c.name == "pc_throw")
153            })
154            .count()
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::super::parse_code;
161    use super::*;
162
163    fn parse_cpp(code: &str) -> ParsedFile {
164        parse_code(code, "test.cpp").expect("parse")
165    }
166
167    #[test]
168    fn test_cpp_count_panic_exit() {
169        let code = r#"
170int main() {
171    exit(1);
172    abort();
173}
174"#;
175        let file = parse_cpp(code);
176        let adapter = CppAdapter;
177        assert_eq!(adapter.count_panic_calls(&file), 2);
178    }
179
180    #[test]
181    fn test_cpp_count_panic_throw() {
182        let code = "int main() { throw std::runtime_error(\"boom\"); }\n";
183        let file = parse_cpp(code);
184        let adapter = CppAdapter;
185        assert_eq!(adapter.count_panic_calls(&file), 1);
186    }
187
188    #[test]
189    fn test_cpp_count_panic_clean() {
190        let code = "int add(int x) { return x + 1; }\n";
191        let file = parse_cpp(code);
192        let adapter = CppAdapter;
193        assert_eq!(adapter.count_panic_calls(&file), 0);
194    }
195
196    #[test]
197    fn test_cpp_extract_functions() {
198        let code = r#"
199int foo() { return 1; }
200void bar(int x) {}
201"#;
202        let file = parse_cpp(code);
203        let adapter = CppAdapter;
204        let fns = adapter.extract_functions(&file);
205        assert_eq!(fns.len(), 2);
206        assert_eq!(fns[0].name, "foo");
207        assert_eq!(fns[1].name, "bar");
208    }
209
210    #[test]
211    fn test_cpp_naming_single_letter() {
212        let code = r#"
213int main() {
214    int x = 1;
215    int y = 2;
216}
217"#;
218        let file = parse_cpp(code);
219        let adapter = CppAdapter;
220        assert_eq!(adapter.count_naming_violations(&file), 2);
221    }
222
223    #[test]
224    fn test_cpp_debug_printf() {
225        let code = r#"
226int main() {
227    printf("hello");
228    fprintf(stderr, "bad");
229}
230"#;
231        let file = parse_cpp(code);
232        let adapter = CppAdapter;
233        assert_eq!(adapter.count_debug_calls(&file), 2);
234    }
235
236    #[test]
237    fn test_cpp_debug_cout() {
238        let code = r#"
239#include <iostream>
240int main() {
241    std::cout << "hello" << std::endl;
242    std::cerr << "error";
243}
244"#;
245        let file = parse_cpp(code);
246        let adapter = CppAdapter;
247        assert_eq!(adapter.count_debug_calls(&file), 2);
248    }
249
250    #[test]
251    fn test_cpp_excessive_params() {
252        let code = "void process(int a, int b, int c, int d, int e, int f) {}\n";
253        let file = parse_cpp(code);
254        let adapter = CppAdapter;
255        assert_eq!(adapter.count_excessive_params(&file, 5), 1);
256    }
257
258    #[test]
259    fn test_cpp_magic_numbers() {
260        let code = r#"
261int main() {
262    foo(41);
263    bar(100);
264}
265"#;
266        let file = parse_cpp(code);
267        let adapter = CppAdapter;
268        assert_eq!(adapter.count_magic_numbers(&file), 2);
269    }
270
271    #[test]
272    fn test_cpp_magic_numbers_skips_trivial() {
273        let code = "int main() { foo(0); bar(1); }\n";
274        let file = parse_cpp(code);
275        let adapter = CppAdapter;
276        assert_eq!(adapter.count_magic_numbers(&file), 0);
277    }
278
279    #[test]
280    fn test_cpp_c_compat_panic_exit() {
281        let code = r#"
282void main() {
283    exit(1);
284    abort();
285}
286"#;
287        let file = parse_cpp(code);
288        let adapter = CppAdapter;
289        assert_eq!(adapter.count_panic_calls(&file), 2);
290    }
291
292    #[test]
293    fn test_cpp_c_compat_panic_clean() {
294        let code = "int add(int x) { return x + 1; }\n";
295        let file = parse_cpp(code);
296        let adapter = CppAdapter;
297        assert_eq!(adapter.count_panic_calls(&file), 0);
298    }
299
300    #[test]
301    fn test_c_extract_functions() {
302        let code = r#"
303int foo() { return 1; }
304void bar(int x) {}
305"#;
306        let file = parse_cpp(code);
307        let adapter = CppAdapter;
308        let fns = adapter.extract_functions(&file);
309        assert_eq!(fns.len(), 2);
310        assert_eq!(fns[0].name, "foo");
311        assert_eq!(fns[1].name, "bar");
312    }
313
314    #[test]
315    fn test_c_naming_single_letter() {
316        let code = r#"
317void main() {
318    int x = 1;
319    int y = 2;
320}
321"#;
322        let file = parse_cpp(code);
323        let adapter = CppAdapter;
324        assert_eq!(adapter.count_naming_violations(&file), 2);
325    }
326
327    #[test]
328    fn test_c_debug_printf() {
329        let code = r#"
330void main() {
331    printf("hello");
332    fprintf(stderr, "bad");
333}
334"#;
335        let file = parse_cpp(code);
336        let adapter = CppAdapter;
337        assert_eq!(adapter.count_debug_calls(&file), 2);
338    }
339
340    #[test]
341    fn test_c_excessive_params() {
342        let code = "void process(int a, int b, int c, int d, int e, int f) {}\n";
343        let file = parse_cpp(code);
344        let adapter = CppAdapter;
345        assert_eq!(adapter.count_excessive_params(&file, 5), 1);
346    }
347
348    #[test]
349    fn test_c_magic_numbers() {
350        let code = r#"
351void main() {
352    foo(41);
353    bar(100);
354}
355"#;
356        let file = parse_cpp(code);
357        let adapter = CppAdapter;
358        assert_eq!(adapter.count_magic_numbers(&file), 2);
359    }
360
361    #[test]
362    fn test_c_magic_numbers_skips_trivial() {
363        let code = "void main() { foo(0); bar(1); }\n";
364        let file = parse_cpp(code);
365        let adapter = CppAdapter;
366        assert_eq!(adapter.count_magic_numbers(&file), 0);
367    }
368
369    #[test]
370    fn test_cpp_dead_code_after_throw() {
371        let code = r#"
372void foo() {
373    throw std::runtime_error("bad");
374    std::cout << "dead" << std::endl;
375}
376"#;
377        let file = parse_cpp(code);
378        let adapter = CppAdapter;
379        assert_eq!(adapter.count_dead_code(&file), 1);
380    }
381}