1use gobject_ast::model::{
2 AssignmentOp, BinaryExpression, BinaryOp, Expression, FileModel, FunctionDefItem, IfStatement,
3 SourceLocation, Statement, UnaryExpression, UnaryOp,
4};
5
6use crate::{
7 ast_context::AstContext,
8 config::Config,
9 rules::{Fix, Rule, Violation},
10};
11
12#[derive(Clone, Copy)]
13enum NullCheck {
14 Null,
15 Zero,
16 NullOrZero,
17}
18
19impl NullCheck {
20 fn matches(self, expr: &Expression) -> bool {
21 match self {
22 Self::Null => expr.is_null(),
23 Self::Zero => expr.is_zero(),
24 Self::NullOrZero => expr.is_null() || expr.is_zero(),
25 }
26 }
27}
28
29#[derive(Clone, Copy)]
30enum ClearReplacement {
31 Object,
32 Pointer,
33 HandleId,
34 SignalHandler,
35 WeakPointer,
36 List { clear_func: &'static str },
37 Error,
38}
39
40#[derive(Clone, Copy)]
41struct ClearMapping {
42 source_func: &'static str,
43 replacement: ClearReplacement,
44 null_check: NullCheck,
45 min_version: (u32, u32),
46}
47
48macro_rules! PointerMapping {
49 ($name:literal) => {
50 ClearMapping {
51 source_func: $name,
52 replacement: ClearReplacement::Pointer,
53 null_check: NullCheck::NullOrZero,
54 min_version: (2, 28),
55 }
56 };
57}
58
59const CLEAR_MAPPINGS: &[ClearMapping] = &[
60 ClearMapping {
61 source_func: "g_source_remove",
62 replacement: ClearReplacement::HandleId,
63 null_check: NullCheck::Zero,
64 min_version: (2, 56),
65 },
66 PointerMapping!("g_source_destroy"),
67 ClearMapping {
68 source_func: "g_signal_handler_disconnect",
69 replacement: ClearReplacement::SignalHandler,
70 null_check: NullCheck::Zero,
71 min_version: (2, 0),
72 },
73 ClearMapping {
74 source_func: "g_object_remove_weak_pointer",
75 replacement: ClearReplacement::WeakPointer,
76 null_check: NullCheck::Null,
77 min_version: (2, 56),
78 },
79 ClearMapping {
80 source_func: "g_list_free",
81 replacement: ClearReplacement::List {
82 clear_func: "g_clear_list",
83 },
84 null_check: NullCheck::Null,
85 min_version: (2, 64),
86 },
87 ClearMapping {
88 source_func: "g_slist_free",
89 replacement: ClearReplacement::List {
90 clear_func: "g_clear_slist",
91 },
92 null_check: NullCheck::Null,
93 min_version: (2, 64),
94 },
95 ClearMapping {
96 source_func: "g_error_free",
97 replacement: ClearReplacement::Error,
98 null_check: NullCheck::Null,
99 min_version: (2, 0),
100 },
101 ClearMapping {
102 source_func: "g_object_unref",
103 replacement: ClearReplacement::Object,
104 null_check: NullCheck::NullOrZero,
105 min_version: (2, 28),
106 },
107 PointerMapping!("g_free"),
108 PointerMapping!("g_hash_table_destroy"),
109 PointerMapping!("g_hash_table_unref"),
110 PointerMapping!("g_array_unref"),
111 PointerMapping!("g_async_queue_unref"),
112 PointerMapping!("g_byte_array_unref"),
113 PointerMapping!("g_bytes_unref"),
114 PointerMapping!("g_checksum_free"),
115 PointerMapping!("g_date_free"),
116 PointerMapping!("g_date_time_unref"),
117 PointerMapping!("g_key_file_free"),
118 PointerMapping!("g_option_context_free"),
119 PointerMapping!("g_ptr_array_unref"),
120 PointerMapping!("g_queue_free"),
121 PointerMapping!("g_sequence_free"),
122 PointerMapping!("g_time_zone_unref"),
123 PointerMapping!("g_uri_unref"),
124 PointerMapping!("g_variant_unref"),
125 PointerMapping!("g_variant_dict_unref"),
126 PointerMapping!("g_variant_iter_unref"),
127 PointerMapping!("g_variant_type_unref"),
128 PointerMapping!("gdk_color_state_unref"),
129 PointerMapping!("gdk_event_unref"),
130 PointerMapping!("gdk_texture_downloader_free"),
131 PointerMapping!("gsk_path_unref"),
132 PointerMapping!("gsk_path_builder_unref"),
133 PointerMapping!("gsk_path_measure_unref"),
134 PointerMapping!("gsk_render_node_unref"),
135 PointerMapping!("gsk_render_replay_free"),
136 PointerMapping!("gsk_stroke_free"),
137 PointerMapping!("gsk_transform_unref"),
138 PointerMapping!("gtk_expression_unref"),
139 PointerMapping!("gtk_expression_watch_unref"),
140];
141
142impl ClearMapping {
143 fn is_enabled(&self, config: &Config) -> bool {
144 if let Some((major, minor)) = config.min_glib_version
145 && (major < self.min_version.0
146 || (major == self.min_version.0 && minor < self.min_version.1))
147 {
148 return false;
149 }
150 true
151 }
152}
153
154fn address_of(var_name: &str) -> String {
155 if let Some(inner) = var_name.strip_prefix('*') {
156 inner.to_string()
157 } else {
158 format!("&{var_name}")
159 }
160}
161
162fn format_replacement(
163 mapping: &ClearMapping,
164 var_name: &str,
165 obj: Option<&str>,
166 style: &crate::config::Style,
167) -> String {
168 let addr = address_of(var_name);
169 match mapping.replacement {
170 ClearReplacement::Object => style.format_call_stmt("g_clear_object", &[&addr]),
171 ClearReplacement::Pointer => {
172 style.format_call_stmt("g_clear_pointer", &[&addr, mapping.source_func])
173 }
174 ClearReplacement::HandleId => {
175 style.format_call_stmt("g_clear_handle_id", &[&addr, mapping.source_func])
176 }
177 ClearReplacement::SignalHandler => {
178 let obj = obj.unwrap_or("obj");
179 style.format_call_stmt("g_clear_signal_handler", &[&addr, obj])
180 }
181 ClearReplacement::WeakPointer => style.format_call_stmt("g_clear_weak_pointer", &[&addr]),
182 ClearReplacement::List { clear_func } => {
183 style.format_call_stmt(clear_func, &[&addr, "NULL"])
184 }
185 ClearReplacement::Error => style.format_call_stmt("g_clear_error", &[&addr]),
186 }
187}
188
189pub struct UseClearFunctions;
190
191impl Rule for UseClearFunctions {
192 fn name(&self) -> &'static str {
193 "use_clear_functions"
194 }
195
196 fn description(&self) -> &'static str {
197 "Suggest g_clear_* functions instead of manual cleanup and NULL/zero assignment"
198 }
199
200 fn category(&self) -> crate::rules::Category {
201 crate::rules::Category::Complexity
202 }
203
204 fn fixable(&self) -> bool {
205 true
206 }
207
208 fn check_func_impl(
209 &self,
210 _ast_context: &AstContext,
211 config: &Config,
212 func: &FunctionDefItem,
213 file: &FileModel,
214 violations: &mut Vec<Violation>,
215 ) {
216 self.check_statements(config, file, &func.body_statements, violations);
217 }
218}
219
220impl UseClearFunctions {
221 fn check_statements(
222 &self,
223 config: &Config,
224 file: &FileModel,
225 statements: &[Statement],
226 violations: &mut Vec<Violation>,
227 ) {
228 let mut i = 0;
230 while i < statements.len() {
231 if self.try_signal_handler_if_guarded(&statements[i], config, file, violations) {
233 i += 1;
234 continue;
235 }
236
237 if self.try_handle_id_if_pattern(&statements[i], config, file, violations) {
239 i += 1;
240 continue;
241 }
242
243 if let Statement::If(if_stmt) = &statements[i]
245 && self.try_generic_if_pattern(if_stmt, config, file, violations)
246 {
247 i += 1;
248 continue;
249 }
250
251 if i + 1 < statements.len() {
253 if let Some(matched) = self.try_consecutive_pair(
254 &statements[i],
255 &statements[i + 1],
256 config,
257 file,
258 violations,
259 ) && matched
260 {
261 i += 2;
262 continue;
263 }
264
265 if self.try_signal_disconnect_then_zero(
267 &statements[i],
268 &statements[i + 1],
269 config,
270 file,
271 violations,
272 ) {
273 i += 2;
274 continue;
275 }
276 }
277
278 if self.try_bare_disconnect_on_member(
280 &statements[i],
281 statements,
282 config,
283 file,
284 violations,
285 ) {
286 i += 1;
287 continue;
288 }
289
290 if let Statement::If(if_stmt) = &statements[i] {
292 self.check_statements(config, file, &if_stmt.then_body, violations);
293 if let Some(else_body) = &if_stmt.else_body {
294 self.check_statements(config, file, else_body, violations);
295 }
296 } else {
297 statements[i].for_each_child_block(|body| {
298 self.check_statements(config, file, body, violations);
299 });
300 }
301
302 i += 1;
303 }
304
305 for stmt in statements {
307 if let Statement::If(if_stmt) = stmt {
308 self.check_unnecessary_braces(if_stmt, file, violations);
309 }
310 }
311 }
312
313 fn try_consecutive_pair(
314 &self,
315 stmt1: &Statement,
316 stmt2: &Statement,
317 config: &Config,
318 file: &FileModel,
319 violations: &mut Vec<Violation>,
320 ) -> Option<bool> {
321 let call = stmt1.extract_call()?;
322
323 for mapping in CLEAR_MAPPINGS {
324 if !mapping.is_enabled(config) {
325 continue;
326 }
327 if matches!(mapping.replacement, ClearReplacement::SignalHandler) {
329 continue;
330 }
331
332 if !call.is_function(mapping.source_func) {
333 continue;
334 }
335
336 let var_name = match mapping.replacement {
337 ClearReplacement::WeakPointer => {
338 self.extract_weak_pointer_var(call.arguments.get(1)?)?
339 }
340 _ => call.get_arg(0)?.location().as_str()?,
341 };
342
343 if !stmt2.is_assignment_to(var_name, |expr| mapping.null_check.matches(expr)) {
344 continue;
345 }
346
347 let replacement = format_replacement(mapping, var_name, None, &config.style);
348 let message = format!(
349 "Use {} instead of {} and NULL/zero assignment",
350 replacement.trim_end_matches(';'),
351 mapping.source_func
352 );
353
354 let stmt1_end = stmt1.location().find_semicolon_end();
355 let fixes = vec![
356 Fix::new(stmt1.location().start_byte, stmt1_end, replacement),
357 Fix::delete_line(stmt2.location()),
358 ];
359
360 violations.push(self.violation_with_fixes_at(
361 &file.path,
362 stmt1.location(),
363 message,
364 fixes,
365 ));
366
367 return Some(true);
368 }
369 Some(false)
370 }
371
372 fn try_generic_if_pattern(
373 &self,
374 if_stmt: &IfStatement,
375 config: &Config,
376 file: &FileModel,
377 violations: &mut Vec<Violation>,
378 ) -> bool {
379 if self.has_logical_operators(&if_stmt.condition) {
380 return false;
381 }
382
383 let Some(checked_var) = self.find_variable_in_condition(&if_stmt.condition) else {
384 return false;
385 };
386
387 if if_stmt.then_body.len() != 2 {
388 return false;
389 }
390
391 let Some(mapping) = self.find_unref_in_body(&if_stmt.then_body, checked_var, config) else {
392 return false;
393 };
394
395 if !self.has_null_assignment(&if_stmt.then_body, checked_var, mapping.null_check) {
396 return false;
397 }
398
399 let replacement = format_replacement(&mapping, checked_var, None, &config.style);
400 let message = format!(
401 "Use {} instead of manual NULL check, unref, and assignment",
402 replacement.trim_end_matches(';')
403 );
404
405 let fix = Fix::new(
406 if_stmt.location.start_byte,
407 if_stmt.location.end_byte,
408 replacement,
409 );
410
411 violations.push(self.violation_with_fix_at(&file.path, &if_stmt.location, message, fix));
412
413 true
414 }
415
416 fn find_variable_in_condition<'a>(&self, expr: &'a Expression) -> Option<&'a str> {
417 if let Some(var) = expr.extract_variable_name() {
418 return Some(var);
419 }
420
421 match expr {
422 Expression::Binary(BinaryExpression {
423 left: l,
424 operator: op,
425 right: r,
426 ..
427 }) => {
428 let l_empty = l.is_null() || l.is_zero();
429 let r_empty = r.is_null() || r.is_zero();
430 match (op, l_empty, r_empty) {
431 (BinaryOp::NotEqual, true, false) => r.extract_variable_name(),
432 (BinaryOp::Less, true, false) => r.extract_variable_name(),
433 (BinaryOp::NotEqual, false, true) => l.extract_variable_name(),
434 (BinaryOp::Greater, false, true) => l.extract_variable_name(),
435 _ => None,
436 }
437 }
438 Expression::Unary(UnaryExpression {
439 operator: UnaryOp::Not,
440 operand: op,
441 ..
442 }) => op.extract_variable_name(),
443 _ => expr.location().as_str(),
444 }
445 }
446
447 fn has_logical_operators(&self, expr: &Expression) -> bool {
448 let mut found = false;
449 expr.walk(&mut |e| {
450 if let Expression::Binary(bin) = e
451 && matches!(bin.operator, BinaryOp::LogicalAnd | BinaryOp::LogicalOr)
452 {
453 found = true;
454 }
455 });
456 found
457 }
458
459 fn find_unref_in_body(
460 &self,
461 statements: &[Statement],
462 var_name: &str,
463 config: &Config,
464 ) -> Option<ClearMapping> {
465 for stmt in statements {
466 if let Some(call) = stmt.extract_call() {
467 for mapping in CLEAR_MAPPINGS {
468 if !mapping.is_enabled(config) {
469 continue;
470 }
471 if matches!(
473 mapping.replacement,
474 ClearReplacement::SignalHandler
475 | ClearReplacement::HandleId
476 | ClearReplacement::WeakPointer
477 ) {
478 continue;
479 }
480 if call.is_function(mapping.source_func) {
481 for arg in &call.arguments {
482 if let Some(arg_text) = arg.location().as_str()
483 && arg_text.contains(var_name)
484 {
485 return Some(*mapping);
486 }
487 }
488 }
489 }
490 }
491 }
492 None
493 }
494
495 fn has_null_assignment(
496 &self,
497 statements: &[Statement],
498 var_name: &str,
499 null_check: NullCheck,
500 ) -> bool {
501 statements
502 .iter()
503 .any(|stmt| stmt.is_assignment_to(var_name, |expr| null_check.matches(expr)))
504 }
505
506 fn try_handle_id_if_pattern(
507 &self,
508 stmt: &Statement,
509 config: &Config,
510 file: &FileModel,
511 violations: &mut Vec<Violation>,
512 ) -> bool {
513 let Statement::If(if_stmt) = stmt else {
514 return false;
515 };
516 let conversions = self.check_handle_cleanup_then_zero(config, &if_stmt.then_body);
517
518 if conversions.is_empty() {
519 return false;
520 }
521
522 let stmt_count = if_stmt.then_body.len();
523 let has_else = if_stmt.else_body.is_some();
524 let cond_id = if_stmt.extract_nonzero_check_variable();
525
526 for (var_name, mapping, first_loc, second_loc) in conversions {
527 let replacement = format_replacement(&mapping, &var_name, None, &config.style);
528 let message = format!(
529 "Use {} instead of {} and zero assignment",
530 replacement.trim_end_matches(';'),
531 mapping.source_func
532 );
533 let can_remove_if = !has_else && cond_id == Some(var_name.as_str()) && stmt_count == 2;
534
535 let fix = if can_remove_if {
536 Fix::new(
537 if_stmt.location.start_byte,
538 if_stmt.location.end_byte,
539 replacement,
540 )
541 } else if stmt_count == 2 {
542 let first_loc = if_stmt.then_body[0].location();
543 let (brace_start, brace_end) = first_loc.find_braces_around();
544 let brace_loc = first_loc.with_byte_range(brace_start, brace_start);
545 let (line_start, _) = brace_loc.find_line_bounds();
546 let indent = brace_loc.extract_line_indentation();
547 let fix_start = line_start.saturating_sub(1);
548 let formatted_replacement = format!("\n{}{}", indent, replacement);
549
550 Fix::new(fix_start, brace_end, formatted_replacement)
551 } else {
552 Fix::new(
553 first_loc.start_byte,
554 second_loc.find_semicolon_end(),
555 replacement,
556 )
557 };
558
559 violations.push(self.violation_with_fix_at(&file.path, &first_loc, message, fix));
560 }
561 true
562 }
563
564 fn check_handle_cleanup_then_zero(
565 &self,
566 config: &Config,
567 statements: &[Statement],
568 ) -> Vec<(String, ClearMapping, SourceLocation, SourceLocation)> {
569 let mut results = Vec::new();
570
571 Statement::for_each_pair(statements, |first, second| {
572 if let Some((var_name, mapping)) = self.extract_handle_cleanup(first, config)
573 && second.is_assignment_to(&var_name, Expression::is_zero)
574 {
575 results.push((
576 var_name,
577 mapping,
578 first.location().clone(),
579 second.location().clone(),
580 ));
581 }
582 });
583
584 results
585 }
586
587 fn extract_handle_cleanup(
588 &self,
589 stmt: &Statement,
590 config: &Config,
591 ) -> Option<(String, ClearMapping)> {
592 let call = stmt.extract_call()?;
593 let func_name = call.function_name_str()?;
594
595 let mapping = CLEAR_MAPPINGS.iter().find(|m| {
596 matches!(m.replacement, ClearReplacement::HandleId)
597 && m.source_func == func_name
598 && m.is_enabled(config)
599 })?;
600
601 let arg_expr = call.get_arg(0)?;
602 let var_name = arg_expr.location().as_str()?.trim().to_owned();
603
604 Some((var_name, *mapping))
605 }
606
607 fn check_unnecessary_braces(
608 &self,
609 if_stmt: &IfStatement,
610 file: &FileModel,
611 violations: &mut Vec<Violation>,
612 ) {
613 if if_stmt.then_body.len() == 1
614 && if_stmt.then_has_braces
615 && let Statement::Expression(expr_stmt) = &if_stmt.then_body[0]
616 && let Expression::Call(call) = expr_stmt.as_ref()
617 && call.is_function("g_clear_handle_id")
618 && let Some(cond_var) = if_stmt.extract_nonzero_check_variable()
619 && let Some(cleared_var) = call.get_arg_text(0).and_then(|s| s.strip_prefix('&'))
620 && cond_var == cleared_var
621 {
622 let call_text = call.location.as_str().unwrap_or("");
623 let loc = if_stmt.then_body[0].location();
624 let fix = Fix::new(
625 loc.start_byte,
626 loc.find_semicolon_end(),
627 format!("{};", call_text),
628 );
629
630 violations.push(self.violation_with_fix_at(
631 &file.path,
632 &if_stmt.location,
633 "Remove unnecessary braces around single g_clear_handle_id call".to_string(),
634 fix,
635 ));
636 }
637 }
638
639 fn try_signal_handler_if_guarded(
640 &self,
641 stmt: &Statement,
642 config: &Config,
643 file: &FileModel,
644 violations: &mut Vec<Violation>,
645 ) -> bool {
646 let signal_mapping = match self.find_signal_mapping(config) {
647 Some(m) => m,
648 None => return false,
649 };
650
651 let Statement::If(if_stmt) = stmt else {
652 return false;
653 };
654
655 if if_stmt.has_else() {
656 return false;
657 }
658
659 let Some(guarded_id) = if_stmt.extract_nonzero_check_variable() else {
660 return false;
661 };
662
663 if if_stmt.then_body.len() != 2 {
664 return false;
665 }
666
667 let Some((obj, handler_id)) = self.extract_disconnect_args(&if_stmt.then_body[0]) else {
668 return false;
669 };
670
671 if handler_id != guarded_id {
672 return false;
673 }
674
675 if !self.is_zero_assign(&if_stmt.then_body[1], handler_id) {
676 return false;
677 }
678
679 let replacement = format_replacement(&signal_mapping, handler_id, Some(obj), &config.style);
680 let message = format!(
681 "Use {} instead of if-guarded g_signal_handler_disconnect",
682 replacement.trim_end_matches(';')
683 );
684 let fix = Fix::new(
685 if_stmt.location.start_byte,
686 if_stmt.location.end_byte,
687 replacement,
688 );
689
690 violations.push(self.violation_with_fix_at(&file.path, &if_stmt.location, message, fix));
691 true
692 }
693
694 fn try_signal_disconnect_then_zero(
695 &self,
696 s1: &Statement,
697 s2: &Statement,
698 config: &Config,
699 file: &FileModel,
700 violations: &mut Vec<Violation>,
701 ) -> bool {
702 let signal_mapping = match self.find_signal_mapping(config) {
703 Some(m) => m,
704 None => return false,
705 };
706
707 let Some((obj, handler_id)) = self.extract_disconnect_args(s1) else {
708 return false;
709 };
710
711 if !self.is_zero_assign(s2, handler_id) {
712 return false;
713 }
714
715 let replacement = format_replacement(&signal_mapping, handler_id, Some(obj), &config.style);
716 let message = format!(
717 "Use {} instead of g_signal_handler_disconnect and zeroing the ID",
718 replacement.trim_end_matches(';')
719 );
720 let s1_end = s1.location().find_semicolon_end();
721
722 let fixes = vec![
723 Fix::new(s1.location().start_byte, s1_end, replacement),
724 Fix::delete_line(s2.location()),
725 ];
726
727 violations.push(self.violation_with_fixes_at(&file.path, s1.location(), message, fixes));
728 true
729 }
730
731 fn try_bare_disconnect_on_member(
732 &self,
733 stmt: &Statement,
734 all_stmts: &[Statement],
735 config: &Config,
736 file: &FileModel,
737 violations: &mut Vec<Violation>,
738 ) -> bool {
739 let signal_mapping = match self.find_signal_mapping(config) {
740 Some(m) => m,
741 None => return false,
742 };
743
744 let Some((obj, handler_id)) = self.extract_disconnect_args(stmt) else {
745 return false;
746 };
747
748 if !handler_id.contains("->") {
749 return false;
750 }
751
752 let base = handler_id.split("->").next().unwrap_or("").trim();
753 if base.is_empty() {
754 return false;
755 }
756
757 if self.is_freed_in_stmts(all_stmts, base) || self.is_freed_in_stmts(all_stmts, obj) {
758 return false;
759 }
760
761 let replacement = format_replacement(&signal_mapping, handler_id, Some(obj), &config.style);
762 let message = format!(
763 "Use {} instead of g_signal_handler_disconnect (also zeroes the stored ID)",
764 replacement.trim_end_matches(';')
765 );
766 let stmt_end = stmt.location().find_semicolon_end();
767 let fix = Fix::new(stmt.location().start_byte, stmt_end, replacement);
768
769 violations.push(self.violation_with_fix_at(&file.path, stmt.location(), message, fix));
770 true
771 }
772
773 fn find_signal_mapping(&self, config: &Config) -> Option<ClearMapping> {
774 CLEAR_MAPPINGS
775 .iter()
776 .find(|m| {
777 matches!(m.replacement, ClearReplacement::SignalHandler) && m.is_enabled(config)
778 })
779 .copied()
780 }
781
782 fn extract_disconnect_args<'a>(&self, stmt: &'a Statement) -> Option<(&'a str, &'a str)> {
783 let call = stmt.extract_call()?;
784
785 if !call.is_function("g_signal_handler_disconnect") {
786 return None;
787 }
788
789 if call.arguments.len() != 2 {
790 return None;
791 }
792
793 let obj = call.get_arg(0)?.extract_variable_name()?;
794 let handler_id = call.get_arg(1)?.extract_variable_name()?;
795
796 Some((obj, handler_id))
797 }
798
799 fn is_zero_assign(&self, stmt: &Statement, expected_id: &str) -> bool {
800 let Statement::Expression(expr_stmt) = stmt else {
801 return false;
802 };
803
804 let Expression::Assignment(assign) = expr_stmt.as_ref() else {
805 return false;
806 };
807
808 assign.lhs_as_text() == expected_id
809 && assign.operator == AssignmentOp::Assign
810 && assign.rhs.is_zero()
811 }
812
813 fn is_freed_in_stmts(&self, stmts: &[Statement], target: &str) -> bool {
814 for stmt in stmts {
815 let Statement::Expression(expr_stmt) = stmt else {
816 continue;
817 };
818
819 let Expression::Call(call) = expr_stmt.as_ref() else {
820 continue;
821 };
822
823 if !call.function_contains("free")
824 && !call.function_contains("unref")
825 && !call.function_contains("destroy")
826 && !call.function_contains("clear")
827 {
828 continue;
829 }
830
831 for arg in &call.arguments {
832 if self.arg_references(arg, target) {
833 return true;
834 }
835 }
836 }
837 false
838 }
839
840 fn arg_references(&self, arg: &Expression, target: &str) -> bool {
841 let mut found = false;
842 arg.walk(&mut |e| match e {
843 Expression::Identifier(id) if id.name == target => {
844 found = true;
845 }
846 Expression::FieldAccess(f) if f.location.as_str() == Some(target) => {
847 found = true;
848 }
849 _ => {}
850 });
851 found
852 }
853
854 fn extract_weak_pointer_var<'a>(&self, expr: &'a Expression) -> Option<&'a str> {
855 let inner_expr = match expr {
857 Expression::Cast(cast) => &*cast.operand,
858 other => other,
859 };
860
861 if let Expression::Unary(unary) = inner_expr
862 && unary.operator == UnaryOp::AddressOf
863 {
864 return unary.operand.extract_variable_name();
865 }
866
867 None
868 }
869}