codegraph_c/pipeline/
conditionals.rs1#[derive(Debug, Clone, PartialEq)]
9pub enum ConditionalStrategy {
10 KeepAll,
12 StripAll,
14 EvaluateSimple,
16}
17
18#[derive(Debug, Clone, PartialEq)]
20enum ConditionalState {
21 Active,
23 Disabled,
25 ElseActive,
27 ElseDisabled,
29}
30
31pub fn evaluate_conditionals(source: &str, strategy: ConditionalStrategy) -> (String, usize) {
35 match strategy {
36 ConditionalStrategy::KeepAll => (source.to_string(), 0),
37 ConditionalStrategy::StripAll => strip_all_preprocessor(source),
38 ConditionalStrategy::EvaluateSimple => evaluate_simple_conditionals(source),
39 }
40}
41
42fn strip_all_preprocessor(source: &str) -> (String, usize) {
44 let mut result = String::with_capacity(source.len());
45 let mut stripped_count = 0;
46
47 for line in source.lines() {
48 let trimmed = line.trim();
49
50 if trimmed.starts_with('#') {
51 if trimmed.starts_with("#include") {
52 result.push_str(line);
53 result.push('\n');
54 } else {
55 result.push('\n');
57 stripped_count += 1;
58 }
59 } else {
60 result.push_str(line);
61 result.push('\n');
62 }
63 }
64
65 (result, stripped_count)
66}
67
68fn evaluate_simple_conditionals(source: &str) -> (String, usize) {
70 let mut result = String::with_capacity(source.len());
71 let mut stripped_count = 0;
72
73 let mut state_stack: Vec<ConditionalState> = vec![ConditionalState::Active];
76
77 let mut in_multiline_define = false;
79 let mut multiline_define_content = String::new();
80
81 let lines: Vec<&str> = source.lines().collect();
82 let mut i = 0;
83
84 while i < lines.len() {
85 let line = lines[i];
86 let trimmed = line.trim();
87
88 if in_multiline_define {
90 let continues = line.trim_end().ends_with('\\');
92 let content = if continues {
94 line.trim_end().trim_end_matches('\\').trim_end()
95 } else {
96 line.trim()
97 };
98 let escaped_content = content.replace("/*", "/+").replace("*/", "+/");
101 multiline_define_content.push(' ');
102 multiline_define_content.push_str(&escaped_content);
103
104 if !continues {
105 result.push_str("/* ");
107 result.push_str(&multiline_define_content);
108 result.push_str(" */\n");
109 in_multiline_define = false;
110 multiline_define_content.clear();
111 } else {
112 result.push('\n');
114 }
115 i += 1;
116 continue;
117 }
118
119 if let Some(directive) = get_preprocessor_directive(trimmed) {
120 match directive {
121 PreprocessorDirective::If(condition) => {
122 let current_state = state_stack.last().unwrap_or(&ConditionalState::Active);
123
124 let new_state = if *current_state == ConditionalState::Active
125 || *current_state == ConditionalState::ElseActive
126 {
127 if is_false_condition(&condition) {
129 ConditionalState::Disabled
130 } else {
131 ConditionalState::Active
132 }
133 } else {
134 ConditionalState::Disabled
136 };
137
138 state_stack.push(new_state);
139
140 result.push('\n');
142 stripped_count += 1;
143 }
144
145 PreprocessorDirective::Ifdef(_) | PreprocessorDirective::Ifndef(_) => {
146 let current_state = state_stack.last().unwrap_or(&ConditionalState::Active);
147
148 let new_state = if *current_state == ConditionalState::Active
151 || *current_state == ConditionalState::ElseActive
152 {
153 ConditionalState::Active
154 } else {
155 ConditionalState::Disabled
156 };
157
158 state_stack.push(new_state);
159 result.push('\n');
161 stripped_count += 1;
162 }
163
164 PreprocessorDirective::Elif(condition) => {
165 if let Some(state) = state_stack.last_mut() {
166 match state {
167 ConditionalState::Disabled => {
168 if !is_false_condition(&condition) {
170 *state = ConditionalState::Active;
171 }
172 }
173 ConditionalState::Active => {
174 *state = ConditionalState::Disabled;
176 }
177 ConditionalState::ElseActive | ConditionalState::ElseDisabled => {
178 }
180 }
181 }
182 result.push('\n');
183 stripped_count += 1;
184 }
185
186 PreprocessorDirective::Else => {
187 if let Some(state) = state_stack.last_mut() {
188 *state = match state {
189 ConditionalState::Active => ConditionalState::ElseDisabled,
190 ConditionalState::Disabled => ConditionalState::ElseActive,
191 ConditionalState::ElseActive | ConditionalState::ElseDisabled => {
192 state.clone()
194 }
195 };
196 }
197 result.push('\n');
198 stripped_count += 1;
199 }
200
201 PreprocessorDirective::Endif => {
202 if state_stack.len() > 1 {
203 state_stack.pop();
204 }
205 result.push('\n');
206 stripped_count += 1;
207 }
208
209 PreprocessorDirective::Include => {
210 result.push_str(line);
212 result.push('\n');
213 }
214
215 PreprocessorDirective::Define
216 | PreprocessorDirective::Undef
217 | PreprocessorDirective::Pragma
218 | PreprocessorDirective::Error
219 | PreprocessorDirective::Warning => {
220 let current_state = state_stack.last().unwrap_or(&ConditionalState::Active);
221 if *current_state == ConditionalState::Active
222 || *current_state == ConditionalState::ElseActive
223 {
224 let continues = line.trim_end().ends_with('\\');
228 if continues {
229 let content = trimmed.trim_end_matches('\\').trim_end();
232 let escaped = content.replace("/*", "/+").replace("*/", "+/");
233 multiline_define_content.clear();
234 multiline_define_content.push_str(&escaped);
235 in_multiline_define = true;
236 result.push('\n');
238 } else {
239 let escaped = trimmed.replace("/*", "/+").replace("*/", "+/");
242 result.push_str("/* ");
243 result.push_str(&escaped);
244 result.push_str(" */\n");
245 }
246 } else {
247 result.push('\n');
248 }
249 stripped_count += 1;
250 }
251
252 PreprocessorDirective::Other => {
253 let current_state = state_stack.last().unwrap_or(&ConditionalState::Active);
254 if *current_state == ConditionalState::Active
255 || *current_state == ConditionalState::ElseActive
256 {
257 result.push_str(line);
258 result.push('\n');
259 } else {
260 result.push('\n');
261 stripped_count += 1;
262 }
263 }
264 }
265 } else {
266 let current_state = state_stack.last().unwrap_or(&ConditionalState::Active);
268 if *current_state == ConditionalState::Active
269 || *current_state == ConditionalState::ElseActive
270 {
271 result.push_str(line);
272 result.push('\n');
273 } else {
274 result.push('\n');
276 stripped_count += 1;
277 }
278 }
279 i += 1;
280 }
281
282 (result, stripped_count)
283}
284
285#[derive(Debug, Clone)]
286enum PreprocessorDirective {
287 If(String),
288 Ifdef(#[allow(dead_code)] String),
289 Ifndef(#[allow(dead_code)] String),
290 Elif(String),
291 Else,
292 Endif,
293 Include,
294 Define,
295 Undef,
296 Pragma,
297 Error,
298 Warning,
299 Other,
300}
301
302fn get_preprocessor_directive(trimmed: &str) -> Option<PreprocessorDirective> {
303 if !trimmed.starts_with('#') {
304 return None;
305 }
306
307 let rest = trimmed[1..].trim_start();
308
309 if rest.starts_with("if ") || rest == "if" {
310 let condition = rest.strip_prefix("if").unwrap_or("").trim().to_string();
311 Some(PreprocessorDirective::If(condition))
312 } else if rest.starts_with("ifdef ") || rest.starts_with("ifdef\t") {
313 let name = rest
314 .strip_prefix("ifdef")
315 .unwrap_or("")
316 .split_whitespace()
317 .next()
318 .unwrap_or("")
319 .to_string();
320 Some(PreprocessorDirective::Ifdef(name))
321 } else if rest.starts_with("ifndef ") || rest.starts_with("ifndef\t") {
322 let name = rest
323 .strip_prefix("ifndef")
324 .unwrap_or("")
325 .split_whitespace()
326 .next()
327 .unwrap_or("")
328 .to_string();
329 Some(PreprocessorDirective::Ifndef(name))
330 } else if rest.starts_with("elif ") {
331 let condition = rest.strip_prefix("elif").unwrap_or("").trim().to_string();
332 Some(PreprocessorDirective::Elif(condition))
333 } else if rest == "else" || rest.starts_with("else ") || rest.starts_with("else\t") {
334 Some(PreprocessorDirective::Else)
335 } else if rest == "endif"
336 || rest.starts_with("endif ")
337 || rest.starts_with("endif\t")
338 || rest.starts_with("endif/")
339 {
340 Some(PreprocessorDirective::Endif)
341 } else if rest.starts_with("include") {
342 Some(PreprocessorDirective::Include)
343 } else if rest.starts_with("define") {
344 Some(PreprocessorDirective::Define)
345 } else if rest.starts_with("undef") {
346 Some(PreprocessorDirective::Undef)
347 } else if rest.starts_with("pragma") {
348 Some(PreprocessorDirective::Pragma)
349 } else if rest.starts_with("error") {
350 Some(PreprocessorDirective::Error)
351 } else if rest.starts_with("warning") {
352 Some(PreprocessorDirective::Warning)
353 } else {
354 Some(PreprocessorDirective::Other)
355 }
356}
357
358fn is_false_condition(condition: &str) -> bool {
360 let condition = condition.trim();
361
362 if condition == "0" {
364 return true;
365 }
366
367 if condition == "(0)" {
369 return true;
370 }
371
372 if condition == "!1" {
375 return true;
376 }
377
378 false
382}
383
384#[cfg(test)]
385mod tests {
386 use super::*;
387
388 #[test]
389 fn test_keep_all_strategy() {
390 let source = r#"
391#if 0
392int x;
393#endif
394int y;
395"#;
396 let (result, count) = evaluate_conditionals(source, ConditionalStrategy::KeepAll);
397 assert_eq!(result, source);
398 assert_eq!(count, 0);
399 }
400
401 #[test]
402 fn test_strip_all_strategy() {
403 let source = r#"#include <stdio.h>
404#define FOO 1
405#if FOO
406int x;
407#endif
408int y;
409"#;
410 let (result, count) = evaluate_conditionals(source, ConditionalStrategy::StripAll);
411
412 assert!(result.contains("#include <stdio.h>"));
414 assert!(!result.contains("#define"));
416 assert!(!result.contains("#if"));
418 assert!(!result.contains("#endif"));
419 assert!(result.contains("int x;"));
421 assert!(result.contains("int y;"));
422 assert!(count > 0);
423 }
424
425 #[test]
426 fn test_evaluate_simple_if_0() {
427 let source = r#"int a;
428#if 0
429int b;
430#endif
431int c;
432"#;
433 let (result, _count) = evaluate_conditionals(source, ConditionalStrategy::EvaluateSimple);
434
435 assert!(result.contains("int a;"));
436 assert!(!result.contains("int b;"));
437 assert!(result.contains("int c;"));
438 }
439
440 #[test]
441 fn test_evaluate_simple_if_0_else() {
442 let source = r#"int a;
443#if 0
444int b;
445#else
446int c;
447#endif
448int d;
449"#;
450 let (result, _count) = evaluate_conditionals(source, ConditionalStrategy::EvaluateSimple);
451
452 assert!(result.contains("int a;"));
453 assert!(!result.contains("int b;"));
454 assert!(result.contains("int c;"));
455 assert!(result.contains("int d;"));
456 }
457
458 #[test]
459 fn test_evaluate_nested_conditionals() {
460 let source = r#"int a;
461#if 0
462int b;
463#if 1
464int c;
465#endif
466int d;
467#endif
468int e;
469"#;
470 let (result, _count) = evaluate_conditionals(source, ConditionalStrategy::EvaluateSimple);
471
472 assert!(result.contains("int a;"));
473 assert!(!result.contains("int b;"));
474 assert!(!result.contains("int c;")); assert!(!result.contains("int d;"));
476 assert!(result.contains("int e;"));
477 }
478
479 #[test]
480 fn test_evaluate_elif() {
481 let source = r#"int a;
482#if 0
483int b;
484#elif 1
485int c;
486#else
487int d;
488#endif
489int e;
490"#;
491 let (result, _count) = evaluate_conditionals(source, ConditionalStrategy::EvaluateSimple);
492
493 assert!(result.contains("int a;"));
494 assert!(!result.contains("int b;"));
495 assert!(result.contains("int e;"));
498 }
499
500 #[test]
501 fn test_preserve_line_numbers() {
502 let source = "line1\n#if 0\nline3\n#endif\nline5\n";
503 let (result, _count) = evaluate_conditionals(source, ConditionalStrategy::EvaluateSimple);
504
505 let line_count = result.lines().count();
507 let original_line_count = source.lines().count();
508
509 assert_eq!(line_count, original_line_count);
511 }
512
513 #[test]
514 fn test_ifdef_content_kept_directive_stripped() {
515 let source = r#"#ifdef CONFIG_FOO
516int x;
517#endif
518"#;
519 let (result, _count) = evaluate_conditionals(source, ConditionalStrategy::EvaluateSimple);
520
521 assert!(result.contains("int x;"));
524 assert!(!result.contains("#ifdef"));
526 }
527
528 #[test]
529 fn test_is_false_condition() {
530 assert!(is_false_condition("0"));
531 assert!(is_false_condition(" 0 "));
532 assert!(is_false_condition("(0)"));
533 assert!(is_false_condition("!1"));
534
535 assert!(!is_false_condition("1"));
536 assert!(!is_false_condition("FOO"));
537 assert!(!is_false_condition("defined(BAR)"));
538 }
539
540 #[test]
541 fn test_complex_kernel_code() {
542 let source = r#"
543#include <linux/module.h>
544
545#if 0
546/* Disabled debug code */
547#define DEBUG 1
548static void debug_print(void) {}
549#endif
550
551MODULE_LICENSE("GPL");
552
553#ifdef CONFIG_DEBUG
554static int debug_level = 1;
555#else
556static int debug_level = 0;
557#endif
558"#;
559
560 let (result, _count) = evaluate_conditionals(source, ConditionalStrategy::EvaluateSimple);
561
562 assert!(result.contains("#include <linux/module.h>"));
564 assert!(!result.contains("debug_print"));
566 assert!(result.contains("debug_level"));
568 }
569}