Skip to main content

mago_analyzer/plugin/libraries/stdlib/session/
session_set_save_handler.rs

1use mago_reporting::Annotation;
2use mago_reporting::Issue;
3use mago_span::HasSpan;
4use mago_syntax::cst::Argument;
5use mago_syntax::cst::Expression;
6use mago_syntax::cst::FunctionCall;
7
8use crate::code::IssueCode;
9use crate::plugin::context::HookContext;
10use crate::plugin::hook::FunctionCallHook;
11use crate::plugin::hook::HookResult;
12use crate::plugin::provider::Provider;
13use crate::plugin::provider::ProviderMeta;
14
15/// Hook for `session_set_save_handler()`.
16///
17/// This function has two signatures:
18///   1. session_set_save_handler(callable, callable, callable, callable, callable, callable,
19///      ?callable, ?callable, ?callable): bool
20///   2. session_set_save_handler(SessionHandlerInterface, bool): bool
21///
22/// When the 1st argument is an object, only 1-2 arguments are allowed.
23/// When the 1st argument is callable, at least 6 arguments are required.
24#[derive(Default)]
25pub struct SessionSetSaveHandlerHook;
26
27impl Provider for SessionSetSaveHandlerHook {
28    fn meta() -> &'static ProviderMeta {
29        static META: ProviderMeta = ProviderMeta::new(
30            "php::session::session_set_save_handler",
31            "session_set_save_handler",
32            "Validates session_set_save_handler argument combinations.",
33        );
34
35        &META
36    }
37}
38
39impl FunctionCallHook for SessionSetSaveHandlerHook {
40    fn after_function_call(&self, call: &FunctionCall<'_>, context: &mut HookContext<'_, '_>) -> HookResult<()> {
41        let Expression::Identifier(identifier) = call.function else {
42            return Ok(());
43        };
44
45        if !identifier.value().eq_ignore_ascii_case(b"session_set_save_handler") {
46            return Ok(());
47        }
48
49        let arguments = &call.argument_list.arguments;
50        if arguments.is_empty() {
51            return Ok(());
52        }
53
54        let Some(first_arg) = arguments.get(0) else {
55            return Ok(());
56        };
57
58        let first_arg_expr = match first_arg {
59            Argument::Positional(arg) => arg.value,
60            Argument::Named(arg) => arg.value,
61        };
62
63        let Some(first_arg_type) = context.get_expression_type(first_arg_expr) else {
64            return Ok(());
65        };
66
67        let is_object_form = !first_arg_type.has_callable() && first_arg_type.is_objecty();
68
69        if is_object_form {
70            if arguments.len() > 2 {
71                let Some(third_arg) = arguments.get(2) else {
72                    return Ok(());
73                };
74
75                let span = match third_arg {
76                    Argument::Positional(arg) => arg.value.span(),
77                    Argument::Named(arg) => arg.span(),
78                };
79
80                context.report(
81                    IssueCode::TooManyArguments,
82                    Issue::error("Too many arguments provided for function `session_set_save_handler`.")
83                        .with_annotation(
84                            Annotation::primary(span).with_message("Unexpected argument provided here"),
85                        )
86                        .with_annotation(
87                            Annotation::secondary(call.function.span()).with_message("For this function call"),
88                        )
89                        .with_note(format!(
90                            "When the first argument is a `SessionHandlerInterface`, `session_set_save_handler()` expects at most 2 arguments, but received {}.",
91                            arguments.len()
92                        ))
93                        .with_help("Remove the extra arguments. The object form only accepts the handler and an optional `$register_shutdown` boolean."),
94                );
95            }
96        } else {
97            if arguments.len() < 6 {
98                context.report(
99                    IssueCode::TooFewArguments,
100                    Issue::error("Too few arguments provided for function `session_set_save_handler`.")
101                        .with_annotation(
102                            Annotation::primary(call.argument_list.span())
103                                .with_message(format!("Only {} argument(s) provided", arguments.len())),
104                        )
105                        .with_annotation(
106                            Annotation::secondary(call.function.span()).with_message("For this function call"),
107                        )
108                        .with_note(format!(
109                            "The callable form of `session_set_save_handler()` requires at least 6 arguments (`$open`, `$close`, `$read`, `$write`, `$destroy`, `$gc`), but only {} were provided.",
110                            arguments.len()
111                        ))
112                        .with_help("Provide all 6 required callback arguments, or pass a `SessionHandlerInterface` object instead."),
113                );
114            }
115        }
116
117        Ok(())
118    }
119}