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