Skip to main content

mir_analyzer/call/
static_call.rs

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