mago_analyzer/plugin/libraries/stdlib/session/
session_set_save_handler.rs1use 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#[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}