1use std::collections::{HashMap, HashSet};
3use std::sync::Arc;
4
5use indexmap::IndexMap;
6use mir_types::Union;
7
8#[derive(Debug, Clone)]
13pub struct Context {
14 pub vars: IndexMap<String, Union>,
16
17 pub assigned_vars: HashSet<String>,
19
20 pub possibly_assigned_vars: HashSet<String>,
22
23 pub self_fqcn: Option<Arc<str>>,
25
26 pub parent_fqcn: Option<Arc<str>>,
28
29 pub static_fqcn: Option<Arc<str>>,
31
32 pub fn_return_type: Option<Union>,
34
35 pub fn_declared_throws: Arc<[Arc<str>]>,
37
38 pub inside_loop: bool,
40
41 pub inside_finally: bool,
43
44 pub inside_constructor: bool,
46
47 pub strict_types: bool,
49
50 pub tainted_vars: HashSet<String>,
53
54 pub read_vars: HashSet<String>,
57
58 pub param_names: HashSet<String>,
61
62 pub byref_param_names: HashSet<String>,
65
66 pub diverges: bool,
71
72 pub var_locations: HashMap<String, (u32, u16, u32, u16)>,
75
76 pub template_param_names: HashSet<String>,
79}
80
81impl Context {
82 pub fn new() -> Self {
83 let mut ctx = Self {
84 vars: IndexMap::new(),
85 assigned_vars: HashSet::new(),
86 possibly_assigned_vars: HashSet::new(),
87 self_fqcn: None,
88 parent_fqcn: None,
89 static_fqcn: None,
90 fn_return_type: None,
91 fn_declared_throws: Arc::from([]),
92 inside_loop: false,
93 inside_finally: false,
94 inside_constructor: false,
95 strict_types: false,
96 tainted_vars: HashSet::new(),
97 read_vars: HashSet::new(),
98 param_names: HashSet::new(),
99 byref_param_names: HashSet::new(),
100 diverges: false,
101 var_locations: HashMap::new(),
102 template_param_names: HashSet::new(),
103 };
104 for sg in &[
106 "_SERVER", "_GET", "_POST", "_REQUEST", "_SESSION", "_COOKIE", "_FILES", "_ENV",
107 "GLOBALS",
108 ] {
109 ctx.vars.insert(sg.to_string(), mir_types::Union::mixed());
110 ctx.assigned_vars.insert(sg.to_string());
111 }
112 ctx
113 }
114
115 #[allow(clippy::too_many_arguments)]
117 pub fn for_function(
118 params: &[mir_codebase::FnParam],
119 return_type: Option<Union>,
120 declared_throws: Arc<[Arc<str>]>,
121 self_fqcn: Option<Arc<str>>,
122 parent_fqcn: Option<Arc<str>>,
123 static_fqcn: Option<Arc<str>>,
124 strict_types: bool,
125 is_static: bool,
126 ) -> Self {
127 Self::for_method(
128 params,
129 return_type,
130 declared_throws,
131 self_fqcn,
132 parent_fqcn,
133 static_fqcn,
134 strict_types,
135 false,
136 is_static,
137 )
138 }
139
140 #[allow(clippy::too_many_arguments)]
142 pub fn for_method(
143 params: &[mir_codebase::FnParam],
144 return_type: Option<Union>,
145 declared_throws: Arc<[Arc<str>]>,
146 self_fqcn: Option<Arc<str>>,
147 parent_fqcn: Option<Arc<str>>,
148 static_fqcn: Option<Arc<str>>,
149 strict_types: bool,
150 inside_constructor: bool,
151 is_static: bool,
152 ) -> Self {
153 Self::for_method_with_templates(
154 params,
155 return_type,
156 declared_throws,
157 self_fqcn,
158 parent_fqcn,
159 static_fqcn,
160 strict_types,
161 inside_constructor,
162 is_static,
163 None,
164 )
165 }
166
167 #[allow(clippy::too_many_arguments)]
169 pub fn for_method_with_templates(
170 params: &[mir_codebase::FnParam],
171 return_type: Option<Union>,
172 declared_throws: Arc<[Arc<str>]>,
173 self_fqcn: Option<Arc<str>>,
174 parent_fqcn: Option<Arc<str>>,
175 static_fqcn: Option<Arc<str>>,
176 strict_types: bool,
177 inside_constructor: bool,
178 is_static: bool,
179 template_params: Option<&[mir_codebase::TemplateParam]>,
180 ) -> Self {
181 let mut ctx = Self::new();
182 ctx.fn_return_type = return_type;
183 ctx.fn_declared_throws = declared_throws;
184 ctx.self_fqcn = self_fqcn.clone();
185 ctx.parent_fqcn = parent_fqcn;
186 ctx.static_fqcn = static_fqcn;
187 ctx.strict_types = strict_types;
188 ctx.inside_constructor = inside_constructor;
189
190 let mut template_bounds_map: std::collections::HashMap<String, Union> =
192 std::collections::HashMap::new();
193 if let Some(templates) = template_params {
194 for tp in templates {
195 ctx.template_param_names.insert(tp.name.to_string());
196 if let Some(bound) = &tp.bound {
197 template_bounds_map.insert(tp.name.to_string(), bound.clone());
198 }
199 }
200 }
201
202 for p in params {
203 let mut elem_ty =
204 p.ty.as_ref()
205 .map(|arc| (**arc).clone())
206 .unwrap_or_else(Union::mixed);
207
208 if elem_ty.types.len() == 1 {
212 match &elem_ty.types[0] {
213 mir_types::Atomic::TNamedObject { fqcn, type_params }
214 if type_params.is_empty() && !fqcn.contains('\\') =>
215 {
216 if let Some(bound) = template_bounds_map.get(fqcn.as_ref()) {
217 elem_ty = bound.clone();
218 }
219 }
220 mir_types::Atomic::TTemplateParam { as_type, .. } if !as_type.is_mixed() => {
221 elem_ty = (**as_type).clone();
224 }
225 _ => {}
226 }
227 }
228
229 let ty = if p.is_variadic {
232 let already_collection = elem_ty.types.iter().any(|a| {
233 matches!(
234 a,
235 mir_types::Atomic::TList { .. }
236 | mir_types::Atomic::TNonEmptyList { .. }
237 | mir_types::Atomic::TArray { .. }
238 | mir_types::Atomic::TNonEmptyArray { .. }
239 )
240 });
241 if already_collection {
242 elem_ty
243 } else {
244 mir_types::Union::single(mir_types::Atomic::TList {
245 value: Box::new(elem_ty),
246 })
247 }
248 } else {
249 elem_ty
250 };
251 let name = p.name.as_ref().trim_start_matches('$').to_string();
252 ctx.vars.insert(name.clone(), ty);
253 ctx.assigned_vars.insert(name.clone());
254 ctx.param_names.insert(name.clone());
255 if p.is_byref {
256 ctx.byref_param_names.insert(name);
257 }
258 }
259
260 if !is_static {
263 if let Some(fqcn) = self_fqcn {
264 let this_ty = mir_types::Union::single(mir_types::Atomic::TNamedObject {
265 fqcn,
266 type_params: vec![],
267 });
268 ctx.vars.insert("this".to_string(), this_ty);
269 ctx.assigned_vars.insert("this".to_string());
270 }
271 }
272
273 ctx
274 }
275
276 pub fn get_var(&self, name: &str) -> Union {
278 let name = name.trim_start_matches('$');
279 self.vars.get(name).cloned().unwrap_or_else(Union::mixed)
280 }
281
282 pub fn set_var(&mut self, name: impl Into<String>, ty: Union) {
284 let name: String = name.into();
285 let name = name.trim_start_matches('$').to_string();
286 self.vars.insert(name.clone(), ty);
287 self.assigned_vars.insert(name);
288 }
289
290 pub fn var_is_defined(&self, name: &str) -> bool {
292 let name = name.trim_start_matches('$');
293 self.assigned_vars.contains(name)
294 }
295
296 pub fn var_possibly_defined(&self, name: &str) -> bool {
298 let name = name.trim_start_matches('$');
299 self.assigned_vars.contains(name) || self.possibly_assigned_vars.contains(name)
300 }
301
302 pub fn taint_var(&mut self, name: &str) {
304 let name = name.trim_start_matches('$').to_string();
305 self.tainted_vars.insert(name);
306 }
307
308 pub fn is_tainted(&self, name: &str) -> bool {
310 let name = name.trim_start_matches('$');
311 self.tainted_vars.contains(name)
312 }
313
314 pub fn record_var_location(
316 &mut self,
317 name: &str,
318 line: u32,
319 col_start: u16,
320 line_end: u32,
321 col_end: u16,
322 ) {
323 let name = name.trim_start_matches('$');
324 self.var_locations
325 .entry(name.to_string())
326 .or_insert((line, col_start, line_end, col_end));
327 }
328
329 pub fn unset_var(&mut self, name: &str) {
331 let name = name.trim_start_matches('$');
332 self.vars.shift_remove(name);
333 self.assigned_vars.remove(name);
334 self.possibly_assigned_vars.remove(name);
335 }
336
337 pub fn fork(&self) -> Context {
339 self.clone()
340 }
341
342 pub fn merge_branches(pre: &Context, if_ctx: Context, else_ctx: Option<Context>) -> Context {
348 let else_ctx = else_ctx.unwrap_or_else(|| pre.clone());
349
350 if if_ctx.diverges && !else_ctx.diverges {
353 let mut result = else_ctx;
354 result.diverges = false;
355 return result;
356 }
357 if else_ctx.diverges && !if_ctx.diverges {
360 let mut result = if_ctx;
361 result.diverges = false;
362 return result;
363 }
364 if if_ctx.diverges && else_ctx.diverges {
366 let mut result = pre.clone();
367 result.diverges = true;
368 return result;
369 }
370
371 let mut result = pre.clone();
372
373 let all_names: HashSet<&String> = if_ctx.vars.keys().chain(else_ctx.vars.keys()).collect();
375
376 for name in all_names {
377 let in_if = if_ctx.assigned_vars.contains(name);
378 let in_else = else_ctx.assigned_vars.contains(name);
379 let in_pre = pre.assigned_vars.contains(name);
380
381 let ty_if = if_ctx.vars.get(name);
382 let ty_else = else_ctx.vars.get(name);
383
384 match (ty_if, ty_else) {
385 (Some(a), Some(b)) => {
386 let merged = Union::merge(a, b);
387 let name_cloned = name.clone();
388 result.vars.insert(name_cloned.clone(), merged);
389 if in_if && in_else {
390 result.assigned_vars.insert(name_cloned);
391 } else {
392 result.possibly_assigned_vars.insert(name_cloned);
393 }
394 }
395 (Some(a), None) => {
396 if in_pre {
397 let pre_ty = pre.vars.get(name).cloned().unwrap_or_else(Union::mixed);
399 let merged = Union::merge(a, &pre_ty);
400 let name_cloned = name.clone();
401 result.vars.insert(name_cloned.clone(), merged);
402 result.assigned_vars.insert(name_cloned);
403 } else {
404 let ty = a.clone().possibly_undefined();
406 let name_cloned = name.clone();
407 result.vars.insert(name_cloned.clone(), ty);
408 result.possibly_assigned_vars.insert(name_cloned);
409 }
410 }
411 (None, Some(b)) => {
412 if in_pre {
413 let pre_ty = pre.vars.get(name).cloned().unwrap_or_else(Union::mixed);
414 let merged = Union::merge(&pre_ty, b);
415 let name_cloned = name.clone();
416 result.vars.insert(name_cloned.clone(), merged);
417 result.assigned_vars.insert(name_cloned);
418 } else {
419 let ty = b.clone().possibly_undefined();
420 let name_cloned = name.clone();
421 result.vars.insert(name_cloned.clone(), ty);
422 result.possibly_assigned_vars.insert(name_cloned);
423 }
424 }
425 (None, None) => {}
426 }
427 }
428
429 for name in if_ctx
431 .tainted_vars
432 .iter()
433 .chain(else_ctx.tainted_vars.iter())
434 {
435 result.tainted_vars.insert(name.clone());
436 }
437
438 for name in if_ctx.read_vars.iter().chain(else_ctx.read_vars.iter()) {
440 result.read_vars.insert(name.clone());
441 }
442
443 for (name, loc) in if_ctx
445 .var_locations
446 .iter()
447 .chain(else_ctx.var_locations.iter())
448 {
449 result.var_locations.entry(name.clone()).or_insert(*loc);
450 }
451
452 result.diverges = false;
455
456 result
457 }
458}
459
460impl Default for Context {
461 fn default() -> Self {
462 Self::new()
463 }
464}