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 inside_loop: bool,
37
38 pub inside_finally: bool,
40
41 pub inside_constructor: bool,
43
44 pub strict_types: bool,
46
47 pub tainted_vars: HashSet<String>,
50
51 pub read_vars: HashSet<String>,
54
55 pub param_names: HashSet<String>,
58
59 pub byref_param_names: HashSet<String>,
62
63 pub diverges: bool,
68
69 pub var_locations: HashMap<String, (u32, u16, u32, u16)>,
72}
73
74impl Context {
75 pub fn new() -> Self {
76 let mut ctx = Self {
77 vars: IndexMap::new(),
78 assigned_vars: HashSet::new(),
79 possibly_assigned_vars: HashSet::new(),
80 self_fqcn: None,
81 parent_fqcn: None,
82 static_fqcn: None,
83 fn_return_type: None,
84 inside_loop: false,
85 inside_finally: false,
86 inside_constructor: false,
87 strict_types: false,
88 tainted_vars: HashSet::new(),
89 read_vars: HashSet::new(),
90 param_names: HashSet::new(),
91 byref_param_names: HashSet::new(),
92 diverges: false,
93 var_locations: HashMap::new(),
94 };
95 for sg in &[
97 "_SERVER", "_GET", "_POST", "_REQUEST", "_SESSION", "_COOKIE", "_FILES", "_ENV",
98 "GLOBALS",
99 ] {
100 ctx.vars.insert(sg.to_string(), mir_types::Union::mixed());
101 ctx.assigned_vars.insert(sg.to_string());
102 }
103 ctx
104 }
105
106 pub fn for_function(
108 params: &[mir_codebase::FnParam],
109 return_type: Option<Union>,
110 self_fqcn: Option<Arc<str>>,
111 parent_fqcn: Option<Arc<str>>,
112 static_fqcn: Option<Arc<str>>,
113 strict_types: bool,
114 is_static: bool,
115 ) -> Self {
116 Self::for_method(
117 params,
118 return_type,
119 self_fqcn,
120 parent_fqcn,
121 static_fqcn,
122 strict_types,
123 false,
124 is_static,
125 )
126 }
127
128 #[allow(clippy::too_many_arguments)]
130 pub fn for_method(
131 params: &[mir_codebase::FnParam],
132 return_type: Option<Union>,
133 self_fqcn: Option<Arc<str>>,
134 parent_fqcn: Option<Arc<str>>,
135 static_fqcn: Option<Arc<str>>,
136 strict_types: bool,
137 inside_constructor: bool,
138 is_static: bool,
139 ) -> Self {
140 let mut ctx = Self::new();
141 ctx.fn_return_type = return_type;
142 ctx.self_fqcn = self_fqcn.clone();
143 ctx.parent_fqcn = parent_fqcn;
144 ctx.static_fqcn = static_fqcn;
145 ctx.strict_types = strict_types;
146 ctx.inside_constructor = inside_constructor;
147
148 for p in params {
149 let elem_ty = p.ty.clone().unwrap_or_else(Union::mixed);
150 let ty = if p.is_variadic {
153 let already_collection = elem_ty.types.iter().any(|a| {
154 matches!(
155 a,
156 mir_types::Atomic::TList { .. }
157 | mir_types::Atomic::TNonEmptyList { .. }
158 | mir_types::Atomic::TArray { .. }
159 | mir_types::Atomic::TNonEmptyArray { .. }
160 )
161 });
162 if already_collection {
163 elem_ty
164 } else {
165 mir_types::Union::single(mir_types::Atomic::TList {
166 value: Box::new(elem_ty),
167 })
168 }
169 } else {
170 elem_ty
171 };
172 let name = p.name.as_ref().trim_start_matches('$').to_string();
173 ctx.vars.insert(name.clone(), ty);
174 ctx.assigned_vars.insert(name.clone());
175 ctx.param_names.insert(name.clone());
176 if p.is_byref {
177 ctx.byref_param_names.insert(name);
178 }
179 }
180
181 if !is_static {
184 if let Some(fqcn) = self_fqcn {
185 let this_ty = mir_types::Union::single(mir_types::Atomic::TNamedObject {
186 fqcn,
187 type_params: vec![],
188 });
189 ctx.vars.insert("this".to_string(), this_ty);
190 ctx.assigned_vars.insert("this".to_string());
191 }
192 }
193
194 ctx
195 }
196
197 pub fn get_var(&self, name: &str) -> Union {
199 let name = name.trim_start_matches('$');
200 self.vars.get(name).cloned().unwrap_or_else(Union::mixed)
201 }
202
203 pub fn set_var(&mut self, name: impl Into<String>, ty: Union) {
205 let name: String = name.into();
206 let name = name.trim_start_matches('$').to_string();
207 self.vars.insert(name.clone(), ty);
208 self.assigned_vars.insert(name);
209 }
210
211 pub fn var_is_defined(&self, name: &str) -> bool {
213 let name = name.trim_start_matches('$');
214 self.assigned_vars.contains(name)
215 }
216
217 pub fn var_possibly_defined(&self, name: &str) -> bool {
219 let name = name.trim_start_matches('$');
220 self.assigned_vars.contains(name) || self.possibly_assigned_vars.contains(name)
221 }
222
223 pub fn taint_var(&mut self, name: &str) {
225 let name = name.trim_start_matches('$').to_string();
226 self.tainted_vars.insert(name);
227 }
228
229 pub fn is_tainted(&self, name: &str) -> bool {
231 let name = name.trim_start_matches('$');
232 self.tainted_vars.contains(name)
233 }
234
235 pub fn record_var_location(
237 &mut self,
238 name: &str,
239 line: u32,
240 col_start: u16,
241 line_end: u32,
242 col_end: u16,
243 ) {
244 let name = name.trim_start_matches('$');
245 self.var_locations
246 .entry(name.to_string())
247 .or_insert((line, col_start, line_end, col_end));
248 }
249
250 pub fn unset_var(&mut self, name: &str) {
252 let name = name.trim_start_matches('$');
253 self.vars.shift_remove(name);
254 self.assigned_vars.remove(name);
255 self.possibly_assigned_vars.remove(name);
256 }
257
258 pub fn fork(&self) -> Context {
260 self.clone()
261 }
262
263 pub fn merge_branches(pre: &Context, if_ctx: Context, else_ctx: Option<Context>) -> Context {
269 let else_ctx = else_ctx.unwrap_or_else(|| pre.clone());
270
271 if if_ctx.diverges && !else_ctx.diverges {
274 let mut result = else_ctx;
275 result.diverges = false;
276 return result;
277 }
278 if else_ctx.diverges && !if_ctx.diverges {
281 let mut result = if_ctx;
282 result.diverges = false;
283 return result;
284 }
285 if if_ctx.diverges && else_ctx.diverges {
287 let mut result = pre.clone();
288 result.diverges = true;
289 return result;
290 }
291
292 let mut result = pre.clone();
293
294 let all_names: HashSet<&String> = if_ctx.vars.keys().chain(else_ctx.vars.keys()).collect();
296
297 for name in all_names {
298 let in_if = if_ctx.assigned_vars.contains(name);
299 let in_else = else_ctx.assigned_vars.contains(name);
300 let in_pre = pre.assigned_vars.contains(name);
301
302 let ty_if = if_ctx.vars.get(name);
303 let ty_else = else_ctx.vars.get(name);
304
305 match (ty_if, ty_else) {
306 (Some(a), Some(b)) => {
307 let merged = Union::merge(a, b);
308 result.vars.insert(name.clone(), merged);
309 if in_if && in_else {
310 result.assigned_vars.insert(name.clone());
311 } else {
312 result.possibly_assigned_vars.insert(name.clone());
313 }
314 }
315 (Some(a), None) => {
316 if in_pre {
317 let pre_ty = pre.vars.get(name).cloned().unwrap_or_else(Union::mixed);
319 let merged = Union::merge(a, &pre_ty);
320 result.vars.insert(name.clone(), merged);
321 result.assigned_vars.insert(name.clone());
322 } else {
323 let ty = a.clone().possibly_undefined();
325 result.vars.insert(name.clone(), ty);
326 result.possibly_assigned_vars.insert(name.clone());
327 }
328 }
329 (None, Some(b)) => {
330 if in_pre {
331 let pre_ty = pre.vars.get(name).cloned().unwrap_or_else(Union::mixed);
332 let merged = Union::merge(&pre_ty, b);
333 result.vars.insert(name.clone(), merged);
334 result.assigned_vars.insert(name.clone());
335 } else {
336 let ty = b.clone().possibly_undefined();
337 result.vars.insert(name.clone(), ty);
338 result.possibly_assigned_vars.insert(name.clone());
339 }
340 }
341 (None, None) => {}
342 }
343 }
344
345 for name in if_ctx
347 .tainted_vars
348 .iter()
349 .chain(else_ctx.tainted_vars.iter())
350 {
351 result.tainted_vars.insert(name.clone());
352 }
353
354 for name in if_ctx.read_vars.iter().chain(else_ctx.read_vars.iter()) {
356 result.read_vars.insert(name.clone());
357 }
358
359 for (name, loc) in if_ctx
361 .var_locations
362 .iter()
363 .chain(else_ctx.var_locations.iter())
364 {
365 result.var_locations.entry(name.clone()).or_insert(*loc);
366 }
367
368 result.diverges = false;
371
372 result
373 }
374}
375
376impl Default for Context {
377 fn default() -> Self {
378 Self::new()
379 }
380}