Skip to main content

gobject_linter/rules/
g_error_init.rs

1use gobject_ast::model::{FileModel, FunctionDefItem, Statement, VariableDecl};
2
3use crate::{
4    ast_context::AstContext,
5    config::Config,
6    rules::{Fix, Rule, Violation},
7};
8
9pub struct GErrorInit;
10
11impl Rule for GErrorInit {
12    fn name(&self) -> &'static str {
13        "g_error_init"
14    }
15
16    fn description(&self) -> &'static str {
17        "Ensure GError* variables are initialized to NULL"
18    }
19
20    fn category(&self) -> crate::rules::Category {
21        crate::rules::Category::Correctness
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        self.check_block(&func.body_statements, file, violations);
37    }
38}
39
40impl GErrorInit {
41    fn check_block(&self, stmts: &[Statement], file: &FileModel, violations: &mut Vec<Violation>) {
42        for (i, stmt) in stmts.iter().enumerate() {
43            if let Statement::Declaration(decl) = stmt {
44                self.check_declaration(decl, &stmts[i + 1..], file, violations);
45            }
46            stmt.for_each_child_block(|block| {
47                self.check_block(block, file, violations);
48            });
49        }
50    }
51
52    fn check_declaration(
53        &self,
54        decl: &VariableDecl,
55        following: &[Statement],
56        file: &FileModel,
57        violations: &mut Vec<Violation>,
58    ) {
59        if !decl.type_info.is_base_type("GError") || !decl.type_info.is_pointer() {
60            return;
61        }
62
63        let is_initialized_to_null = match &decl.initializer {
64            None => false,
65            Some(expr) if expr.is_null() || expr.is_zero() => true,
66            Some(_) => return,
67        };
68
69        if is_initialized_to_null {
70            return;
71        }
72
73        if self.first_use_is_assignment(&decl.name, following) {
74            return;
75        }
76
77        let insert_pos = decl.location.end_byte - 1;
78
79        let fix = Fix::new(insert_pos, insert_pos, " = NULL".to_string());
80
81        violations.push(self.violation_with_fix_at(
82            &file.path,
83            &decl.location,
84            format!("GError *{} must be initialized to NULL", decl.name),
85            fix,
86        ));
87    }
88
89    fn first_use_is_assignment(&self, var: &str, stmts: &[Statement]) -> bool {
90        for stmt in stmts {
91            let mut references_var = false;
92            stmt.visit_expressions(&mut |expr| {
93                if expr.contains_identifier(var) {
94                    references_var = true;
95                }
96            });
97            if !references_var {
98                continue;
99            }
100            return stmt.is_assignment_to(var, |_| true);
101        }
102        true
103    }
104}