garbage_code_hunter/language/adapter/
cpp.rs1use 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 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}