gobject_linter/rules/
unnecessary_null_check.rs1use gobject_ast::model::{Expression, FileModel, FunctionDefItem, IfStatement, Statement};
2
3use crate::{
4 ast_context::AstContext,
5 config::Config,
6 rules::{Fix, Rule, Violation},
7};
8
9pub struct UnnecessaryNullCheck;
10
11impl Rule for UnnecessaryNullCheck {
12 fn name(&self) -> &'static str {
13 "unnecessary_null_check"
14 }
15
16 fn description(&self) -> &'static str {
17 "Detect unnecessary NULL checks before g_free/g_clear_* functions"
18 }
19
20 fn category(&self) -> crate::rules::Category {
21 crate::rules::Category::Suspicious
22 }
23
24 fn fixable(&self) -> bool {
25 true
26 }
27
28 fn check_func_impl(
29 &self,
30 _ast_context: &AstContext,
31 _config: &Config,
32 func: &FunctionDefItem,
33 file: &FileModel,
34 violations: &mut Vec<Violation>,
35 ) {
36 for stmt in &func.body_statements {
39 for if_stmt in stmt.iter_if_statements() {
40 self.check_if_statement(if_stmt, file, violations);
41 }
42 }
43 }
44}
45
46impl UnnecessaryNullCheck {
47 fn check_if_statement(
48 &self,
49 if_stmt: &IfStatement,
50 file: &FileModel,
51 violations: &mut Vec<Violation>,
52 ) {
53 if if_stmt.has_else() {
56 return;
57 }
58
59 let Some(checked_var) = if_stmt.extract_null_check_variable() else {
61 return;
62 };
63
64 if !if_stmt.has_single_statement() {
67 return;
68 }
69
70 let Statement::Expression(expr_stmt) = &if_stmt.then_body[0] else {
72 return;
73 };
74
75 let Expression::Call(call) = expr_stmt.as_ref() else {
77 return;
78 };
79
80 let Some(func_name) = call.function_name_str() else {
82 return;
83 };
84 if !func_name.starts_with("g_free") && !func_name.starts_with("g_clear_") {
85 return;
86 }
87
88 let references_var = call
90 .arguments
91 .iter()
92 .any(|e| e.contains_identifier(checked_var));
93
94 if !references_var {
95 return;
96 }
97
98 let loc = expr_stmt.location();
101 let stmt_end = loc.find_semicolon_end();
102 let stmt_loc = loc.with_byte_range(loc.start_byte, stmt_end);
103 let stmt_text = stmt_loc.as_str().unwrap_or_default();
104
105 let fix = Fix::new(
106 if_stmt.location.start_byte,
107 if_stmt.location.end_byte,
108 stmt_text.to_string(),
109 );
110
111 violations.push(self.violation_with_fix_at(
112 &file.path,
113 &if_stmt.location,
114 format!(
115 "Remove unnecessary NULL check before {} ({} handles NULL)",
116 func_name, func_name
117 ),
118 fix,
119 ));
120 }
121}