1use std::collections::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
70impl Context {
71 pub fn new() -> Self {
72 let mut ctx = Self {
73 vars: IndexMap::new(),
74 assigned_vars: HashSet::new(),
75 possibly_assigned_vars: HashSet::new(),
76 self_fqcn: None,
77 parent_fqcn: None,
78 static_fqcn: None,
79 fn_return_type: None,
80 inside_loop: false,
81 inside_finally: false,
82 inside_constructor: false,
83 strict_types: false,
84 tainted_vars: HashSet::new(),
85 read_vars: HashSet::new(),
86 param_names: HashSet::new(),
87 byref_param_names: HashSet::new(),
88 diverges: false,
89 };
90 for sg in &[
92 "_SERVER", "_GET", "_POST", "_REQUEST", "_SESSION", "_COOKIE", "_FILES", "_ENV",
93 "GLOBALS",
94 ] {
95 ctx.vars.insert(sg.to_string(), mir_types::Union::mixed());
96 ctx.assigned_vars.insert(sg.to_string());
97 }
98 ctx
99 }
100
101 pub fn for_function(
103 params: &[mir_codebase::FnParam],
104 return_type: Option<Union>,
105 self_fqcn: Option<Arc<str>>,
106 parent_fqcn: Option<Arc<str>>,
107 static_fqcn: Option<Arc<str>>,
108 strict_types: bool,
109 is_static: bool,
110 ) -> Self {
111 Self::for_method(
112 params,
113 return_type,
114 self_fqcn,
115 parent_fqcn,
116 static_fqcn,
117 strict_types,
118 false,
119 is_static,
120 )
121 }
122
123 #[allow(clippy::too_many_arguments)]
125 pub fn for_method(
126 params: &[mir_codebase::FnParam],
127 return_type: Option<Union>,
128 self_fqcn: Option<Arc<str>>,
129 parent_fqcn: Option<Arc<str>>,
130 static_fqcn: Option<Arc<str>>,
131 strict_types: bool,
132 inside_constructor: bool,
133 is_static: bool,
134 ) -> Self {
135 let mut ctx = Self::new();
136 ctx.fn_return_type = return_type;
137 ctx.self_fqcn = self_fqcn.clone();
138 ctx.parent_fqcn = parent_fqcn;
139 ctx.static_fqcn = static_fqcn;
140 ctx.strict_types = strict_types;
141 ctx.inside_constructor = inside_constructor;
142
143 for p in params {
144 let elem_ty = p.ty.clone().unwrap_or_else(Union::mixed);
145 let ty = if p.is_variadic {
148 let already_collection = elem_ty.types.iter().any(|a| {
149 matches!(
150 a,
151 mir_types::Atomic::TList { .. }
152 | mir_types::Atomic::TNonEmptyList { .. }
153 | mir_types::Atomic::TArray { .. }
154 | mir_types::Atomic::TNonEmptyArray { .. }
155 )
156 });
157 if already_collection {
158 elem_ty
159 } else {
160 mir_types::Union::single(mir_types::Atomic::TList {
161 value: Box::new(elem_ty),
162 })
163 }
164 } else {
165 elem_ty
166 };
167 let name = p.name.as_ref().trim_start_matches('$').to_string();
168 ctx.vars.insert(name.clone(), ty);
169 ctx.assigned_vars.insert(name.clone());
170 ctx.param_names.insert(name.clone());
171 if p.is_byref {
172 ctx.byref_param_names.insert(name);
173 }
174 }
175
176 if !is_static {
179 if let Some(fqcn) = self_fqcn {
180 let this_ty = mir_types::Union::single(mir_types::Atomic::TNamedObject {
181 fqcn,
182 type_params: vec![],
183 });
184 ctx.vars.insert("this".to_string(), this_ty);
185 ctx.assigned_vars.insert("this".to_string());
186 }
187 }
188
189 ctx
190 }
191
192 pub fn get_var(&self, name: &str) -> Union {
194 let name = name.trim_start_matches('$');
195 self.vars.get(name).cloned().unwrap_or_else(Union::mixed)
196 }
197
198 pub fn set_var(&mut self, name: impl Into<String>, ty: Union) {
200 let name: String = name.into();
201 let name = name.trim_start_matches('$').to_string();
202 self.vars.insert(name.clone(), ty);
203 self.assigned_vars.insert(name);
204 }
205
206 pub fn var_is_defined(&self, name: &str) -> bool {
208 let name = name.trim_start_matches('$');
209 self.assigned_vars.contains(name)
210 }
211
212 pub fn var_possibly_defined(&self, name: &str) -> bool {
214 let name = name.trim_start_matches('$');
215 self.assigned_vars.contains(name) || self.possibly_assigned_vars.contains(name)
216 }
217
218 pub fn taint_var(&mut self, name: &str) {
220 let name = name.trim_start_matches('$').to_string();
221 self.tainted_vars.insert(name);
222 }
223
224 pub fn is_tainted(&self, name: &str) -> bool {
226 let name = name.trim_start_matches('$');
227 self.tainted_vars.contains(name)
228 }
229
230 pub fn unset_var(&mut self, name: &str) {
232 let name = name.trim_start_matches('$');
233 self.vars.shift_remove(name);
234 self.assigned_vars.remove(name);
235 self.possibly_assigned_vars.remove(name);
236 }
237
238 pub fn fork(&self) -> Context {
240 self.clone()
241 }
242
243 pub fn merge_branches(pre: &Context, if_ctx: Context, else_ctx: Option<Context>) -> Context {
249 let else_ctx = else_ctx.unwrap_or_else(|| pre.clone());
250
251 if if_ctx.diverges && !else_ctx.diverges {
254 let mut result = else_ctx;
255 result.diverges = false;
256 return result;
257 }
258 if else_ctx.diverges && !if_ctx.diverges {
261 let mut result = if_ctx;
262 result.diverges = false;
263 return result;
264 }
265 if if_ctx.diverges && else_ctx.diverges {
267 let mut result = pre.clone();
268 result.diverges = true;
269 return result;
270 }
271
272 let mut result = pre.clone();
273
274 let all_names: HashSet<&String> = if_ctx.vars.keys().chain(else_ctx.vars.keys()).collect();
276
277 for name in all_names {
278 let in_if = if_ctx.assigned_vars.contains(name);
279 let in_else = else_ctx.assigned_vars.contains(name);
280 let in_pre = pre.assigned_vars.contains(name);
281
282 let ty_if = if_ctx.vars.get(name);
283 let ty_else = else_ctx.vars.get(name);
284
285 match (ty_if, ty_else) {
286 (Some(a), Some(b)) => {
287 let merged = Union::merge(a, b);
288 result.vars.insert(name.clone(), merged);
289 if in_if && in_else {
290 result.assigned_vars.insert(name.clone());
291 } else {
292 result.possibly_assigned_vars.insert(name.clone());
293 }
294 }
295 (Some(a), None) => {
296 if in_pre {
297 let pre_ty = pre.vars.get(name).cloned().unwrap_or_else(Union::mixed);
299 let merged = Union::merge(a, &pre_ty);
300 result.vars.insert(name.clone(), merged);
301 result.assigned_vars.insert(name.clone());
302 } else {
303 let ty = a.clone().possibly_undefined();
305 result.vars.insert(name.clone(), ty);
306 result.possibly_assigned_vars.insert(name.clone());
307 }
308 }
309 (None, Some(b)) => {
310 if in_pre {
311 let pre_ty = pre.vars.get(name).cloned().unwrap_or_else(Union::mixed);
312 let merged = Union::merge(&pre_ty, b);
313 result.vars.insert(name.clone(), merged);
314 result.assigned_vars.insert(name.clone());
315 } else {
316 let ty = b.clone().possibly_undefined();
317 result.vars.insert(name.clone(), ty);
318 result.possibly_assigned_vars.insert(name.clone());
319 }
320 }
321 (None, None) => {}
322 }
323 }
324
325 for name in if_ctx
327 .tainted_vars
328 .iter()
329 .chain(else_ctx.tainted_vars.iter())
330 {
331 result.tainted_vars.insert(name.clone());
332 }
333
334 for name in if_ctx.read_vars.iter().chain(else_ctx.read_vars.iter()) {
336 result.read_vars.insert(name.clone());
337 }
338
339 result.diverges = false;
342
343 result
344 }
345}
346
347impl Default for Context {
348 fn default() -> Self {
349 Self::new()
350 }
351}