Skip to main content

gobject_linter/rules/
missing_autoptr_cleanup.rs

1use 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        // Collect declarations from headers
78        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            // Find the GObjectDeclsBlock (G_BEGIN_DECLS/G_END_DECLS) in this header
86            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}