Skip to main content

gobject_linter/rules/
inconsistent_function_signature.rs

1use std::{collections::HashMap, path::Path};
2
3use gobject_ast::model::{Parameter, SourceLocation, TypeInfo};
4
5use crate::{
6    ast_context::AstContext,
7    config::Config,
8    rules::{Rule, Violation},
9};
10
11pub struct InconsistentFunctionSignature;
12
13struct DeclInfo<'a> {
14    return_type: &'a TypeInfo,
15    parameters: &'a [Parameter],
16}
17
18struct DefInfo<'a> {
19    location: SourceLocation,
20    path: &'a Path,
21    return_type: &'a TypeInfo,
22    parameters: &'a [Parameter],
23}
24
25impl Rule for InconsistentFunctionSignature {
26    fn name(&self) -> &'static str {
27        "inconsistent_function_signature"
28    }
29
30    fn description(&self) -> &'static str {
31        "Detect functions whose return type or parameter types in the declaration do not match the definition"
32    }
33
34    fn category(&self) -> crate::rules::Category {
35        crate::rules::Category::Suspicious
36    }
37
38    fn check_all(
39        &self,
40        ast_context: &AstContext,
41        _config: &Config,
42        violations: &mut Vec<Violation>,
43    ) {
44        let mut global_decls: HashMap<&str, DeclInfo> = HashMap::new();
45        let mut all_defs: HashMap<&str, Vec<DefInfo>> = HashMap::new();
46        let mut static_violations: Vec<Violation> = Vec::new();
47
48        for (path, file) in ast_context.iter_all_files() {
49            let ext = path.extension().and_then(|e| e.to_str());
50
51            if ext == Some("h") {
52                for decl in file.iter_function_declarations() {
53                    if !decl.is_static {
54                        global_decls
55                            .entry(decl.name.as_str())
56                            .or_insert_with(|| DeclInfo {
57                                return_type: &decl.return_type,
58                                parameters: &decl.parameters,
59                            });
60                    }
61                }
62            }
63
64            if ext == Some("c") {
65                let mut local_decls: HashMap<&str, DeclInfo> = HashMap::new();
66                for decl in file.iter_function_declarations() {
67                    local_decls
68                        .entry(decl.name.as_str())
69                        .or_insert_with(|| DeclInfo {
70                            return_type: &decl.return_type,
71                            parameters: &decl.parameters,
72                        });
73                }
74
75                for func in file.iter_function_definitions() {
76                    if func.is_static {
77                        if let Some(decl) = local_decls.get(func.name.as_str()) {
78                            self.check_signatures(
79                                &func.name,
80                                decl.return_type,
81                                decl.parameters,
82                                &func.return_type,
83                                &func.parameters,
84                                path,
85                                &func.location,
86                                &mut static_violations,
87                            );
88                        }
89                    } else {
90                        all_defs
91                            .entry(func.name.as_str())
92                            .or_default()
93                            .push(DefInfo {
94                                location: func.location.clone(),
95                                path,
96                                return_type: &func.return_type,
97                                parameters: &func.parameters,
98                            });
99                    }
100                }
101            }
102        }
103
104        for (name, defs) in &all_defs {
105            let Some(decl) = global_decls.get(name) else {
106                continue;
107            };
108
109            let first = &defs[0];
110            let definitions_agree = defs.iter().skip(1).all(|d| {
111                first.return_type.matches(d.return_type)
112                    && self.params_match(first.parameters, d.parameters)
113            });
114            if !definitions_agree {
115                continue;
116            }
117
118            for def in defs {
119                self.check_signatures(
120                    name,
121                    decl.return_type,
122                    decl.parameters,
123                    def.return_type,
124                    def.parameters,
125                    def.path,
126                    &def.location,
127                    violations,
128                );
129            }
130        }
131
132        violations.extend(static_violations);
133    }
134}
135
136impl InconsistentFunctionSignature {
137    /// `(void)` and `()` both mean "no parameters" in C.
138    fn effective_params<'a>(&self, params: &'a [Parameter]) -> &'a [Parameter] {
139        if let [
140            Parameter::Regular {
141                name: None,
142                type_info,
143                ..
144            },
145        ] = params
146            && type_info.base_type == "void"
147            && type_info.pointer_depth == 0
148        {
149            return &[];
150        }
151        params
152    }
153
154    fn params_match(&self, a: &[Parameter], b: &[Parameter]) -> bool {
155        let a = self.effective_params(a);
156        let b = self.effective_params(b);
157        a.len() == b.len()
158            && a.iter().zip(b.iter()).all(|(pa, pb)| match (pa, pb) {
159                (
160                    Parameter::Regular { type_info: ta, .. },
161                    Parameter::Regular { type_info: tb, .. },
162                ) => ta.matches(tb),
163                (Parameter::Variadic, Parameter::Variadic) => true,
164                _ => false,
165            })
166    }
167
168    #[allow(clippy::too_many_arguments)]
169    fn check_signatures(
170        &self,
171        name: &str,
172        decl_ret: &TypeInfo,
173        decl_params: &[Parameter],
174        def_ret: &TypeInfo,
175        def_params: &[Parameter],
176        path: &Path,
177        location: &SourceLocation,
178        violations: &mut Vec<Violation>,
179    ) {
180        if !decl_ret.matches(def_ret) {
181            violations.push(self.violation_at(
182                path,
183                location,
184                format!(
185                    "'{}' declared as returning '{}' but defined as returning '{}'",
186                    name,
187                    decl_ret.display_name(),
188                    def_ret.display_name(),
189                ),
190            ));
191        }
192
193        let decl_params = self.effective_params(decl_params);
194        let def_params = self.effective_params(def_params);
195
196        if decl_params.len() != def_params.len() {
197            violations.push(self.violation_at(
198                path,
199                location,
200                format!(
201                    "'{}' declared with {} parameter(s) but defined with {}",
202                    name,
203                    decl_params.len(),
204                    def_params.len(),
205                ),
206            ));
207            return;
208        }
209
210        for (i, (dp, fp)) in decl_params.iter().zip(def_params.iter()).enumerate() {
211            match (dp, fp) {
212                (Parameter::Variadic, Parameter::Variadic) => {}
213                (
214                    Parameter::Regular {
215                        type_info: dt,
216                        name: dn,
217                        ..
218                    },
219                    Parameter::Regular {
220                        type_info: ft,
221                        name: fn_,
222                        ..
223                    },
224                ) => {
225                    if !dt.matches(ft) {
226                        let param_id = dn
227                            .as_deref()
228                            .or(fn_.as_deref())
229                            .map_or_else(|| format!("{}", i + 1), |n| format!("'{n}'"));
230                        violations.push(self.violation_at(
231                            path,
232                            location,
233                            format!(
234                                "'{}' parameter {} declared as '{}' but defined as '{}'",
235                                name,
236                                param_id,
237                                dt.display_name(),
238                                ft.display_name(),
239                            ),
240                        ));
241                    }
242                }
243                _ => {
244                    violations.push(self.violation_at(
245                        path,
246                        location,
247                        format!("'{}' parameter {} variadic mismatch between declaration and definition", name, i + 1),
248                    ));
249                }
250            }
251        }
252    }
253}