gobject_linter/rules/
use_g_set_str.rs1use gobject_ast::model::{
2 AssignmentOp, Expression, FileModel, FunctionDefItem, Statement, UnaryOp,
3};
4
5use crate::{
6 ast_context::AstContext,
7 config::Config,
8 rules::{Fix, Rule, Violation},
9};
10
11pub struct UseGSetStr;
12
13impl Rule for UseGSetStr {
14 fn name(&self) -> &'static str {
15 "use_g_set_str"
16 }
17
18 fn description(&self) -> &'static str {
19 "Suggest g_set_str() instead of manual g_free and g_strdup"
20 }
21
22 fn category(&self) -> crate::rules::Category {
23 crate::rules::Category::Complexity
24 }
25
26 fn fixable(&self) -> bool {
27 true
28 }
29
30 fn min_glib_version(&self) -> Option<(u32, u32)> {
31 Some((2, 76))
32 }
33
34 fn check_func_impl(
35 &self,
36 _ast_context: &AstContext,
37 config: &Config,
38 func: &FunctionDefItem,
39 file: &FileModel,
40 violations: &mut Vec<Violation>,
41 ) {
42 Statement::walk_pairs(&func.body_statements, &mut |s1, s2| {
43 self.try_free_then_strdup(s1, s2, file, config, violations);
44 });
45 }
46}
47
48impl UseGSetStr {
49 fn try_free_then_strdup(
50 &self,
51 s1: &Statement,
52 s2: &Statement,
53 file: &FileModel,
54 config: &Config,
55 violations: &mut Vec<Violation>,
56 ) -> bool {
57 let Some(var_name) = self.extract_gfree_var(s1) else {
59 return false;
60 };
61
62 let Some((assign_var, new_val)) = self.extract_strdup_assignment(s2) else {
64 return false;
65 };
66
67 if assign_var != var_name {
68 return false;
69 }
70
71 let replacement = config
72 .style
73 .format_addr_call_stmt("g_set_str", var_name, &[new_val]);
74 let message = format!("Use {replacement} instead of g_free and g_strdup");
75 let s2_end = s2.location().find_semicolon_end();
77 let fixes = vec![
78 Fix::delete_line(s1.location()),
80 Fix::new(s2.location().start_byte, s2_end, replacement),
82 ];
83
84 violations.push(self.violation_with_fixes_at(&file.path, s1.location(), message, fixes));
85 true
86 }
87
88 fn extract_gfree_var<'a>(&'a self, stmt: &'a Statement) -> Option<&'a str> {
90 let Statement::Expression(expr_stmt) = stmt else {
91 return None;
92 };
93
94 let Expression::Call(call) = expr_stmt.as_ref() else {
95 return None;
96 };
97
98 if call.is_function("g_free") {
99 return call.get_arg(0)?.location().as_str();
100 } else if call.is_function("g_clear_pointer") {
101 if call.arguments.len() != 2 {
103 return None;
104 }
105
106 let second_arg = call.get_arg(1)?;
108
109 if let Expression::Identifier(id) = second_arg {
110 if id.name != "g_free" {
111 return None;
112 }
113 } else {
114 return None;
115 }
116
117 let first_arg = call.get_arg(0)?;
119 if let Expression::Unary(unary) = first_arg
120 && unary.operator == UnaryOp::AddressOf
121 {
122 return unary.operand.location().as_str();
123 }
124 }
125
126 None
127 }
128
129 fn extract_strdup_assignment<'a>(&self, stmt: &'a Statement) -> Option<(&'a str, &'a str)> {
132 let Statement::Expression(expr_stmt) = stmt else {
133 return None;
134 };
135
136 let Expression::Assignment(assign) = expr_stmt.as_ref() else {
137 return None;
138 };
139
140 if assign.operator != AssignmentOp::Assign {
141 return None;
142 }
143
144 if let Expression::Call(call) = &*assign.rhs
146 && call.is_function("g_strdup")
147 && !call.arguments.is_empty()
148 {
149 let new_val = call.get_arg(0)?.location().as_str()?;
150 let var_name = assign.lhs_as_text();
151 if !var_name.is_empty() {
152 return Some((var_name, new_val));
153 }
154 }
155
156 if let Expression::Conditional(cond) = &*assign.rhs
158 && cond.then_expr.is_call_to_any(&["g_strdup", "g_strndup"])
159 {
160 let cond_text = cond.condition.location().as_str()?;
162 let var_name = assign.lhs_as_text();
163 if !var_name.is_empty() {
164 return Some((var_name, cond_text));
165 }
166 }
167
168 None
169 }
170}