1use crate::{callstack, env, error, functions, trace_categories};
4
5impl<SE: crate::extensions::ShellExtensions> crate::Shell<SE> {
6 pub fn in_sourced_script(&self) -> bool {
9 self.call_stack.in_sourced_script()
10 }
11
12 pub fn in_function(&self) -> bool {
15 self.call_stack.in_function()
16 }
17
18 pub fn start_interactive_session(&mut self) -> Result<(), error::Error> {
25 self.call_stack.push_interactive_session();
26 Ok(())
27 }
28
29 pub fn end_interactive_session(&mut self) -> Result<(), error::Error> {
37 if self
38 .call_stack
39 .current_frame()
40 .is_none_or(|frame| !frame.frame_type.is_interactive_session())
41 {
42 return Err(error::ErrorKind::NotInInteractiveSession.into());
43 }
44
45 self.call_stack.pop();
46
47 Ok(())
48 }
49
50 pub fn start_command_string_mode(&mut self) {
53 self.call_stack.push_command_string();
54 }
55
56 pub fn end_command_string_mode(&mut self) -> Result<(), error::Error> {
64 if self
65 .call_stack
66 .current_frame()
67 .is_none_or(|frame| !frame.frame_type.is_command_string())
68 {
69 return Err(error::ErrorKind::NotExecutingCommandString.into());
70 }
71
72 self.call_stack.pop();
73
74 Ok(())
75 }
76
77 pub(crate) fn enter_trap_handler(
81 &mut self,
82 signal: crate::traps::TrapSignal,
83 handler: Option<&crate::traps::TrapHandler>,
84 ) -> ShellGuard<'_, SE> {
85 let last_exit_status = self.last_exit_status;
86 self.call_stack.push_trap_handler(signal, handler);
87 let frames = self.call_stack.depth();
88 ShellGuard::new(
89 self,
90 Leave::TrapHandler {
91 frames,
92 last_exit_status,
93 },
94 )
95 }
96
97 pub(crate) fn enter_script(
99 &mut self,
100 call_type: callstack::ScriptCallType,
101 source_info: &crate::SourceInfo,
102 args: impl IntoIterator<Item = String>,
103 ) -> ShellGuard<'_, SE> {
104 self.call_stack.push_script(call_type, source_info, args);
105 let frames = self.call_stack.depth();
106 ShellGuard::new(self, Leave::Script { frames })
107 }
108
109 pub(crate) fn enter_function(
118 &mut self,
119 name: &str,
120 function: &functions::Registration,
121 args: impl IntoIterator<Item = String>,
122 ) -> Result<ShellGuard<'_, SE>, error::Error> {
123 if let Some(max_call_depth) = self.options.max_function_call_depth
124 && self.call_stack.function_call_depth() >= max_call_depth
125 {
126 return Err(error::ErrorKind::MaxFunctionCallDepthExceeded.into());
127 }
128
129 if tracing::enabled!(target: trace_categories::FUNCTIONS, tracing::Level::DEBUG) {
130 let depth = self.call_stack.function_call_depth();
131 let prefix = repeated_char_str(' ', depth);
132 tracing::debug!(target: trace_categories::FUNCTIONS, "Entering func [depth={depth}]: {prefix}{name}");
133 }
134
135 self.call_stack.push_function(name, function, args);
136 self.env.push_scope(env::EnvironmentScope::Local);
137 let leave = Leave::Function {
138 frames: self.call_stack.depth(),
139 scopes: self.env.scope_depth(),
140 };
141
142 Ok(ShellGuard::new(self, leave))
143 }
144
145 pub(crate) fn enter_scope(&mut self, scope: env::EnvironmentScope) -> ShellGuard<'_, SE> {
148 self.env.push_scope(scope);
149 let scopes = self.env.scope_depth();
150 ShellGuard::new(self, Leave::Scope { scopes, scope })
151 }
152
153 pub(crate) const fn block_trap_delivery(&mut self) -> ShellGuard<'_, SE> {
156 self.call_stack.acquire_trap_delivery_block();
157 ShellGuard::new(self, Leave::TrapBlock)
158 }
159
160 pub fn current_shell_args(&self) -> &[String] {
163 for frame in self.call_stack.iter() {
164 match frame.frame_type {
165 crate::callstack::FrameType::Function(..) => return &frame.args,
167 _ if frame.frame_type.is_run_script() => return &frame.args,
169 _ if frame.frame_type.is_sourced_script() && !frame.args.is_empty() => {
171 return &frame.args;
172 }
173 _ => (),
174 }
175 }
176
177 self.args.as_slice()
178 }
179
180 pub fn current_shell_args_mut(&mut self) -> &mut Vec<String> {
183 for frame in self.call_stack.iter_mut() {
184 match frame.frame_type {
185 crate::callstack::FrameType::Function(..) => return &mut frame.args,
187 _ if frame.frame_type.is_run_script() => return &mut frame.args,
189 _ if frame.frame_type.is_sourced_script() && !frame.args.is_empty() => {
191 return &mut frame.args;
192 }
193 _ => (),
194 }
195 }
196
197 &mut self.args
198 }
199}
200
201fn repeated_char_str(c: char, count: usize) -> String {
202 (0..count).map(|_| c).collect()
203}
204
205pub(crate) struct ShellGuard<'a, SE: crate::extensions::ShellExtensions> {
212 shell: &'a mut crate::Shell<SE>,
213 leave: Leave,
214}
215
216#[derive(Clone, Copy, Debug)]
219enum Leave {
220 Function {
221 frames: usize,
222 scopes: usize,
223 },
224 Script {
225 frames: usize,
226 },
227 TrapHandler {
228 frames: usize,
229 last_exit_status: u8,
230 },
231 Scope {
232 scopes: usize,
233 scope: env::EnvironmentScope,
234 },
235 TrapBlock,
236}
237
238impl<'a, SE: crate::extensions::ShellExtensions> ShellGuard<'a, SE> {
239 const fn new(shell: &'a mut crate::Shell<SE>, leave: Leave) -> Self {
240 Self { shell, leave }
241 }
242}
243
244impl<SE: crate::extensions::ShellExtensions> std::ops::Deref for ShellGuard<'_, SE> {
245 type Target = crate::Shell<SE>;
246
247 fn deref(&self) -> &Self::Target {
248 self.shell
249 }
250}
251
252impl<SE: crate::extensions::ShellExtensions> std::ops::DerefMut for ShellGuard<'_, SE> {
253 fn deref_mut(&mut self) -> &mut Self::Target {
254 self.shell
255 }
256}
257
258impl<SE: crate::extensions::ShellExtensions> ShellGuard<'_, SE> {
259 fn frame_on_top(&self, depth: usize, is_expected: fn(&callstack::FrameType) -> bool) -> bool {
262 let stack = &self.shell.call_stack;
263 stack.depth() == depth
264 && stack
265 .current_frame()
266 .is_some_and(|frame| is_expected(&frame.frame_type))
267 }
268
269 fn pop_frame(&mut self, depth: usize, is_expected: fn(&callstack::FrameType) -> bool) -> bool {
272 let on_top = self.frame_on_top(depth, is_expected);
273 if on_top {
274 self.shell.call_stack.pop();
275 }
276 on_top
277 }
278
279 fn pop_scope(&mut self, depth: usize, scope_type: env::EnvironmentScope) -> bool {
282 self.shell.env.scope_depth() == depth && self.shell.env.pop_scope(scope_type).is_ok()
283 }
284
285 fn report_imbalance(&self) {
287 tracing::error!(
288 "shell state out of balance when leaving {:?}: call stack {} deep, {} variable scopes",
289 self.leave,
290 self.shell.call_stack.depth(),
291 self.shell.env.scope_depth()
292 );
293 }
294}
295
296impl<SE: crate::extensions::ShellExtensions> Drop for ShellGuard<'_, SE> {
297 fn drop(&mut self) {
298 let left = match self.leave {
304 Leave::Function { frames, scopes } => {
305 let left = self.frame_on_top(frames, callstack::FrameType::is_function)
307 && self.pop_scope(scopes, env::EnvironmentScope::Local);
308 if left
309 && let Some(frame) = self.shell.call_stack.pop()
310 && let callstack::FrameType::Function(call) = frame.frame_type
311 && tracing::enabled!(target: trace_categories::FUNCTIONS, tracing::Level::DEBUG)
312 {
313 let depth = self.shell.call_stack.function_call_depth();
314 let prefix = repeated_char_str(' ', depth);
315 tracing::debug!(target: trace_categories::FUNCTIONS, "Exiting func [depth={depth}]: {prefix}{}", call.function_name);
316 }
317 left
318 }
319 Leave::Script { frames } => self.pop_frame(frames, callstack::FrameType::is_script),
320 Leave::TrapHandler {
321 frames,
322 last_exit_status,
323 } => {
324 self.shell.last_exit_status = last_exit_status;
325 self.pop_frame(frames, callstack::FrameType::is_trap_handler)
326 }
327 Leave::Scope { scopes, scope } => self.pop_scope(scopes, scope),
328 Leave::TrapBlock => {
329 let blocked = self.shell.call_stack.is_trap_delivery_suppressed();
330 self.shell.call_stack.release_trap_delivery_block();
331 blocked
332 }
333 };
334
335 if !left {
336 self.report_imbalance();
337 }
338 }
339}
340
341#[cfg(test)]
342#[allow(clippy::panic_in_result_fn, reason = "assertions in a fallible test")]
343mod tests {
344 use super::*;
345
346 #[tokio::test]
350 async fn guards_pop_nothing_when_out_of_balance() -> Result<(), error::Error> {
351 let mut shell = crate::Shell::builder()
352 .profile(crate::ProfileLoadBehavior::Skip)
353 .rc(crate::RcLoadBehavior::Skip)
354 .build()
355 .await?;
356 let params = shell.default_exec_params();
357 shell
358 .run_string("f() { :; }", &crate::SourceInfo::default(), ¶ms)
359 .await?;
360 let f = shell
361 .funcs()
362 .get("f")
363 .cloned()
364 .ok_or_else(|| error::ErrorKind::FunctionNotFound("f".into()))?;
365 let frames = shell.call_stack.depth();
366 let scopes = shell.env.scope_depth();
367
368 let mut function = shell.enter_function("f", &f, std::iter::empty())?;
370 function.env_mut().push_scope(env::EnvironmentScope::Local);
371 drop(function);
372 assert_eq!(shell.call_stack.depth(), frames + 1);
373 assert_eq!(shell.env.scope_depth(), scopes + 2);
374
375 let mut script = shell.enter_script(
377 callstack::ScriptCallType::Source,
378 &crate::SourceInfo::default(),
379 std::iter::empty(),
380 );
381 script.call_stack.pop();
382 script.start_command_string_mode();
383 drop(script);
384 assert_eq!(shell.call_stack.depth(), frames + 2);
385
386 Ok(())
387 }
388}