gobject_linter/rules/
missing_autoptr_cleanup.rs1use std::{
2 collections::{HashMap, HashSet},
3 path::Path,
4};
5
6use gobject_ast::model::{
7 DefineKind, GObjectTypeKind, PreprocessorDirective, SourceLocation, TopLevelItem,
8};
9
10use crate::{
11 ast_context::AstContext,
12 config::Config,
13 rules::{Category, Fix, Rule, Violation},
14};
15
16pub struct MissingAutoptrCleanup;
17
18enum CleanupKind<'a> {
19 Boxed { free_func: &'a str },
20 OldStyleGObject,
21 Pointer,
22}
23
24impl CleanupKind<'_> {
25 fn cleanup_func(&self) -> Option<&str> {
26 match self {
27 CleanupKind::Boxed { free_func } => Some(free_func),
28 CleanupKind::OldStyleGObject => Some("g_object_unref"),
29 CleanupKind::Pointer => None,
30 }
31 }
32}
33
34struct GetTypeDecl<'a> {
35 path: &'a Path,
36 location: &'a SourceLocation,
37 decls_block: Option<&'a SourceLocation>,
38}
39
40impl Rule for MissingAutoptrCleanup {
41 fn name(&self) -> &'static str {
42 "missing_autoptr_cleanup"
43 }
44
45 fn description(&self) -> &'static str {
46 "Detect boxed types without G_DEFINE_AUTOPTR_CLEANUP_FUNC"
47 }
48
49 fn long_description(&self) -> Option<&'static str> {
50 Some(
51 "Detects types that don't have automatic g_autoptr() support:\n\
52 - Boxed types (G_DEFINE_BOXED_TYPE*) without G_DEFINE_AUTOPTR_CLEANUP_FUNC\n\
53 - Old-style GObject types (G_DEFINE_TYPE*) that should use G_DECLARE_* or have explicit cleanup\n\
54 Modern GLib code should support g_autoptr() for automatic memory management.",
55 )
56 }
57
58 fn category(&self) -> Category {
59 Category::Style
60 }
61
62 fn fixable(&self) -> bool {
63 true
64 }
65
66 fn check_all(
67 &self,
68 ast_context: &AstContext,
69 _config: &Config,
70 violations: &mut Vec<Violation>,
71 ) {
72 let mut types_needing_cleanup: Vec<(&Path, &str, &str, &SourceLocation, CleanupKind)> =
73 Vec::new();
74 let mut declared_types: HashSet<&str> = HashSet::new();
75 let mut autoptr_cleanups: HashSet<&str> = HashSet::new();
76
77 let mut get_type_decls: HashMap<&str, GetTypeDecl> = HashMap::new();
79 let mut header_func_names: HashSet<&str> = HashSet::new();
80 let mut header_typedefs: HashSet<&str> = HashSet::new();
81 for (path, file) in ast_context.iter_header_files() {
82 for (name, _) in file.iter_typedef_pairs() {
83 header_typedefs.insert(name);
84 }
85 let decls_block = file.iter_all_items().find_map(|item| match item {
87 TopLevelItem::Preprocessor(PreprocessorDirective::GObjectDeclsBlock {
88 location,
89 ..
90 }) => Some(location),
91 _ => None,
92 });
93
94 for decl in file.iter_function_declarations() {
95 header_func_names.insert(&decl.name);
96 if let Some(prefix) = decl.name.strip_suffix("_get_type") {
97 get_type_decls.insert(
98 prefix,
99 GetTypeDecl {
100 path,
101 location: &decl.location,
102 decls_block,
103 },
104 );
105 }
106 }
107 }
108
109 for (path, file) in ast_context.iter_all_files() {
110 for gobject_type in file.iter_all_gobject_types() {
111 match &gobject_type.kind {
112 GObjectTypeKind::DefineBoxed { free_func, .. } => {
113 types_needing_cleanup.push((
114 path,
115 &gobject_type.type_name,
116 &gobject_type.function_prefix,
117 &gobject_type.location,
118 CleanupKind::Boxed { free_func },
119 ));
120 }
121 GObjectTypeKind::Define(DefineKind::Pointer) => {
122 types_needing_cleanup.push((
123 path,
124 &gobject_type.type_name,
125 &gobject_type.function_prefix,
126 &gobject_type.location,
127 CleanupKind::Pointer,
128 ));
129 }
130 GObjectTypeKind::Define(_) => {
131 types_needing_cleanup.push((
132 path,
133 &gobject_type.type_name,
134 &gobject_type.function_prefix,
135 &gobject_type.location,
136 CleanupKind::OldStyleGObject,
137 ));
138 }
139 GObjectTypeKind::Declare { .. } if !gobject_type.manually_registered => {
140 declared_types.insert(&gobject_type.type_name);
141 }
142 _ => {}
143 }
144 }
145
146 for item in file.iter_all_items() {
147 if let TopLevelItem::Preprocessor(directive) = item
148 && let PreprocessorDirective::AutoptrCleanupFunc { type_name, .. } = directive
149 {
150 autoptr_cleanups.insert(type_name);
151 }
152 }
153 }
154
155 for (define_path, type_name, func_prefix, define_location, kind) in &types_needing_cleanup {
156 if declared_types.contains(type_name) {
157 continue;
158 }
159
160 if autoptr_cleanups.contains(type_name) {
161 continue;
162 }
163
164 match kind {
165 CleanupKind::Boxed { free_func } if !header_func_names.contains(*free_func) => {
166 continue;
167 }
168 CleanupKind::OldStyleGObject if !header_typedefs.contains(type_name) => {
169 continue;
170 }
171 _ => {}
172 }
173
174 let cleanup_func = kind.cleanup_func();
175
176 if let Some(decl) = get_type_decls.get(*func_prefix) {
177 let message = Self::make_message(type_name, kind);
178 let fix = cleanup_func.and_then(|func| Self::make_fix(decl, type_name, func));
179
180 let violation = if let Some(fix) = fix {
181 self.violation_with_fix_at(decl.path, decl.location, message, fix)
182 } else {
183 self.violation_at(decl.path, decl.location, message)
184 };
185 violations.push(violation);
186 } else {
187 let message = Self::make_message(type_name, kind);
188 violations.push(self.violation_at(define_path, define_location, message));
189 }
190 }
191 }
192}
193
194impl MissingAutoptrCleanup {
195 fn make_message(type_name: &str, kind: &CleanupKind) -> String {
196 match kind {
197 CleanupKind::Boxed { .. } => {
198 format!(
199 "Boxed type '{}' is missing G_DEFINE_AUTOPTR_CLEANUP_FUNC macro",
200 type_name
201 )
202 }
203 CleanupKind::OldStyleGObject => {
204 format!(
205 "GObject type '{}' defined with G_DEFINE_TYPE* should either use G_DECLARE_* or have G_DEFINE_AUTOPTR_CLEANUP_FUNC",
206 type_name
207 )
208 }
209 CleanupKind::Pointer => {
210 format!(
211 "Pointer type '{}' is missing G_DEFINE_AUTOPTR_CLEANUP_FUNC macro",
212 type_name
213 )
214 }
215 }
216 }
217
218 fn make_fix(decl: &GetTypeDecl, type_name: &str, cleanup_func: &str) -> Option<Fix> {
219 let block_loc = decl.decls_block?;
220 let source = block_loc.source();
221 let mut pos = block_loc.end_byte;
222 while pos > 0 && source[pos - 1] != b'\n' {
223 pos -= 1;
224 }
225 let macro_text = format!("G_DEFINE_AUTOPTR_CLEANUP_FUNC ({type_name}, {cleanup_func})\n");
226 let has_blank = pos >= 2 && source[pos - 1] == b'\n' && source[pos - 2] == b'\n';
227 let prefix = if has_blank { "" } else { "\n" };
228 Some(Fix::new(pos, pos, format!("{prefix}{macro_text}")))
229 }
230}