mir_analyzer/call/
static_call.rs1use std::sync::Arc;
2
3use php_ast::ast::{ExprKind, StaticDynMethodCallExpr, StaticMethodCallExpr};
4use php_ast::Span;
5
6use mir_issues::{IssueKind, Severity};
7use mir_types::Union;
8
9use crate::context::Context;
10use crate::expr::ExpressionAnalyzer;
11use crate::symbol::SymbolKind;
12
13use super::args::{
14 check_args, expr_can_be_passed_by_reference, spread_element_type, substitute_static_in_return,
15 CheckArgsParams,
16};
17use super::CallAnalyzer;
18
19impl CallAnalyzer {
20 pub fn analyze_static_method_call<'a, 'arena, 'src>(
21 ea: &mut ExpressionAnalyzer<'a>,
22 call: &StaticMethodCallExpr<'arena, 'src>,
23 ctx: &mut Context,
24 span: Span,
25 ) -> Union {
26 let method_name = match &call.method.kind {
27 ExprKind::Identifier(name) => name.as_str(),
28 _ => return Union::mixed(),
29 };
30
31 let fqcn = match &call.class.kind {
32 ExprKind::Identifier(name) => ea.codebase.resolve_class_name(&ea.file, name.as_ref()),
33 _ => return Union::mixed(),
34 };
35
36 let fqcn = resolve_static_class(&fqcn, ctx);
37
38 let arg_types: Vec<Union> = call
39 .args
40 .iter()
41 .map(|arg| {
42 let ty = ea.analyze(&arg.value, ctx);
43 if arg.unpack {
44 spread_element_type(&ty)
45 } else {
46 ty
47 }
48 })
49 .collect();
50 let arg_spans: Vec<Span> = call.args.iter().map(|a| a.span).collect();
51
52 if let Some(method) = ea.codebase.get_method(&fqcn, method_name) {
53 if !ea.inference_only {
54 let (line, col_start, col_end) = ea.span_to_ref_loc(call.method.span);
55 ea.codebase.mark_method_referenced_at(
56 &fqcn,
57 method_name,
58 ea.file.clone(),
59 line,
60 col_start,
61 col_end,
62 );
63 }
64 if let Some(msg) = method.deprecated.clone() {
65 ea.emit(
66 IssueKind::DeprecatedMethodCall {
67 class: fqcn.clone(),
68 method: method_name.to_string(),
69 message: Some(msg).filter(|m| !m.is_empty()),
70 },
71 Severity::Info,
72 span,
73 );
74 }
75 let arg_names: Vec<Option<String>> = call
76 .args
77 .iter()
78 .map(|a| a.name.as_ref().map(|n| n.to_string_repr().into_owned()))
79 .collect();
80 let arg_can_be_byref: Vec<bool> = call
81 .args
82 .iter()
83 .map(|a| expr_can_be_passed_by_reference(&a.value))
84 .collect();
85 check_args(
86 ea,
87 CheckArgsParams {
88 fn_name: method_name,
89 params: &method.params,
90 arg_types: &arg_types,
91 arg_spans: &arg_spans,
92 arg_names: &arg_names,
93 arg_can_be_byref: &arg_can_be_byref,
94 call_span: span,
95 has_spread: call.args.iter().any(|a| a.unpack),
96 },
97 );
98 let ret_raw = method
99 .effective_return_type()
100 .cloned()
101 .unwrap_or_else(Union::mixed);
102 let fqcn_arc: Arc<str> = Arc::from(fqcn.as_str());
103 let ret = substitute_static_in_return(ret_raw, &fqcn_arc);
104 ea.record_symbol(
105 call.method.span,
106 SymbolKind::StaticCall {
107 class: fqcn_arc,
108 method: Arc::from(method_name),
109 },
110 ret.clone(),
111 );
112 ret
113 } else if ea.codebase.type_exists(&fqcn) && !ea.codebase.has_unknown_ancestor(&fqcn) {
114 let is_interface = ea.codebase.interfaces.contains_key(fqcn.as_str());
115 let is_abstract = ea.codebase.is_abstract_class(&fqcn);
116 if is_interface
117 || is_abstract
118 || ea.codebase.get_method(&fqcn, "__callStatic").is_some()
119 {
120 Union::mixed()
121 } else {
122 ea.emit(
123 IssueKind::UndefinedMethod {
124 class: fqcn,
125 method: method_name.to_string(),
126 },
127 Severity::Error,
128 span,
129 );
130 Union::mixed()
131 }
132 } else if !ea.codebase.type_exists(&fqcn)
133 && !matches!(fqcn.as_str(), "self" | "static" | "parent")
134 {
135 ea.emit(
136 IssueKind::UndefinedClass { name: fqcn },
137 Severity::Error,
138 call.class.span,
139 );
140 Union::mixed()
141 } else {
142 Union::mixed()
143 }
144 }
145
146 pub fn analyze_static_dyn_method_call<'a, 'arena, 'src>(
147 ea: &mut ExpressionAnalyzer<'a>,
148 call: &StaticDynMethodCallExpr<'arena, 'src>,
149 ctx: &mut Context,
150 ) -> Union {
151 for arg in call.args.iter() {
152 ea.analyze(&arg.value, ctx);
153 }
154 Union::mixed()
155 }
156}
157
158fn resolve_static_class(name: &str, ctx: &Context) -> String {
159 match name.to_lowercase().as_str() {
160 "self" => ctx.self_fqcn.as_deref().unwrap_or("self").to_string(),
161 "parent" => ctx.parent_fqcn.as_deref().unwrap_or("parent").to_string(),
162 "static" => ctx
163 .static_fqcn
164 .as_deref()
165 .unwrap_or(ctx.self_fqcn.as_deref().unwrap_or("static"))
166 .to_string(),
167 _ => name.to_string(),
168 }
169}