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 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}