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> {
21 self.call_stack.push_interactive_session();
22 Ok(())
23 }
24
25 pub fn end_interactive_session(&mut self) -> Result<(), error::Error> {
28 if self
29 .call_stack
30 .current_frame()
31 .is_none_or(|frame| !frame.frame_type.is_interactive_session())
32 {
33 return Err(error::ErrorKind::NotInInteractiveSession.into());
34 }
35
36 self.call_stack.pop();
37
38 Ok(())
39 }
40
41 pub fn start_command_string_mode(&mut self) {
44 self.call_stack.push_command_string();
45 }
46
47 pub fn end_command_string_mode(&mut self) -> Result<(), error::Error> {
50 if self
51 .call_stack
52 .current_frame()
53 .is_none_or(|frame| !frame.frame_type.is_command_string())
54 {
55 return Err(error::ErrorKind::NotExecutingCommandString.into());
56 }
57
58 self.call_stack.pop();
59
60 Ok(())
61 }
62
63 pub(crate) fn enter_trap_handler(
67 &mut self,
68 signal: crate::traps::TrapSignal,
69 handler: Option<&crate::traps::TrapHandler>,
70 ) -> ShellGuard<'_, SE> {
71 let last_exit_status = self.last_exit_status;
72 self.call_stack.push_trap_handler(signal, handler);
73 let frames = self.call_stack.depth();
74 ShellGuard::new(
75 self,
76 Leave::TrapHandler {
77 frames,
78 last_exit_status,
79 },
80 )
81 }
82
83 pub(crate) fn enter_script(
85 &mut self,
86 call_type: callstack::ScriptCallType,
87 source_info: &crate::SourceInfo,
88 args: impl IntoIterator<Item = String>,
89 ) -> ShellGuard<'_, SE> {
90 self.call_stack.push_script(call_type, source_info, args);
91 let frames = self.call_stack.depth();
92 ShellGuard::new(self, Leave::Script { frames })
93 }
94
95 pub(crate) fn enter_function(
104 &mut self,
105 name: &str,
106 function: &functions::Registration,
107 args: impl IntoIterator<Item = String>,
108 ) -> Result<ShellGuard<'_, SE>, error::Error> {
109 if let Some(max_call_depth) = self.options.max_function_call_depth
110 && self.call_stack.function_call_depth() >= max_call_depth
111 {
112 return Err(error::ErrorKind::MaxFunctionCallDepthExceeded.into());
113 }
114
115 if tracing::enabled!(target: trace_categories::FUNCTIONS, tracing::Level::DEBUG) {
116 let depth = self.call_stack.function_call_depth();
117 let prefix = repeated_char_str(' ', depth);
118 tracing::debug!(target: trace_categories::FUNCTIONS, "Entering func [depth={depth}]: {prefix}{name}");
119 }
120
121 self.call_stack.push_function(name, function, args);
122 self.env.push_scope(env::EnvironmentScope::Local);
123 let leave = Leave::Function {
124 frames: self.call_stack.depth(),
125 scopes: self.env.scope_depth(),
126 };
127
128 Ok(ShellGuard::new(self, leave))
129 }
130
131 pub(crate) fn enter_scope(&mut self, scope: env::EnvironmentScope) -> ShellGuard<'_, SE> {
134 self.env.push_scope(scope);
135 let scopes = self.env.scope_depth();
136 ShellGuard::new(self, Leave::Scope { scopes, scope })
137 }
138
139 pub(crate) const fn block_trap_delivery(&mut self) -> ShellGuard<'_, SE> {
142 self.call_stack.acquire_trap_delivery_block();
143 ShellGuard::new(self, Leave::TrapBlock)
144 }
145
146 pub fn current_shell_args(&self) -> &[String] {
149 for frame in self.call_stack.iter() {
150 match frame.frame_type {
151 crate::callstack::FrameType::Function(..) => return &frame.args,
153 _ if frame.frame_type.is_run_script() => return &frame.args,
155 _ if frame.frame_type.is_sourced_script() && !frame.args.is_empty() => {
157 return &frame.args;
158 }
159 _ => (),
160 }
161 }
162
163 self.args.as_slice()
164 }
165
166 pub fn current_shell_args_mut(&mut self) -> &mut Vec<String> {
169 for frame in self.call_stack.iter_mut() {
170 match frame.frame_type {
171 crate::callstack::FrameType::Function(..) => return &mut frame.args,
173 _ if frame.frame_type.is_run_script() => return &mut frame.args,
175 _ if frame.frame_type.is_sourced_script() && !frame.args.is_empty() => {
177 return &mut frame.args;
178 }
179 _ => (),
180 }
181 }
182
183 &mut self.args
184 }
185}
186
187fn repeated_char_str(c: char, count: usize) -> String {
188 (0..count).map(|_| c).collect()
189}
190
191pub(crate) struct ShellGuard<'a, SE: crate::extensions::ShellExtensions> {
198 shell: &'a mut crate::Shell<SE>,
199 leave: Leave,
200}
201
202#[derive(Clone, Copy, Debug)]
205enum Leave {
206 Function {
207 frames: usize,
208 scopes: usize,
209 },
210 Script {
211 frames: usize,
212 },
213 TrapHandler {
214 frames: usize,
215 last_exit_status: u8,
216 },
217 Scope {
218 scopes: usize,
219 scope: env::EnvironmentScope,
220 },
221 TrapBlock,
222}
223
224impl<'a, SE: crate::extensions::ShellExtensions> ShellGuard<'a, SE> {
225 const fn new(shell: &'a mut crate::Shell<SE>, leave: Leave) -> Self {
226 Self { shell, leave }
227 }
228}
229
230impl<SE: crate::extensions::ShellExtensions> std::ops::Deref for ShellGuard<'_, SE> {
231 type Target = crate::Shell<SE>;
232
233 fn deref(&self) -> &Self::Target {
234 self.shell
235 }
236}
237
238impl<SE: crate::extensions::ShellExtensions> std::ops::DerefMut for ShellGuard<'_, SE> {
239 fn deref_mut(&mut self) -> &mut Self::Target {
240 self.shell
241 }
242}
243
244impl<SE: crate::extensions::ShellExtensions> ShellGuard<'_, SE> {
245 fn frame_on_top(&self, depth: usize, is_expected: fn(&callstack::FrameType) -> bool) -> bool {
248 let stack = &self.shell.call_stack;
249 stack.depth() == depth
250 && stack
251 .current_frame()
252 .is_some_and(|frame| is_expected(&frame.frame_type))
253 }
254
255 fn pop_frame(&mut self, depth: usize, is_expected: fn(&callstack::FrameType) -> bool) -> bool {
258 let on_top = self.frame_on_top(depth, is_expected);
259 if on_top {
260 self.shell.call_stack.pop();
261 }
262 on_top
263 }
264
265 fn pop_scope(&mut self, depth: usize, scope_type: env::EnvironmentScope) -> bool {
268 self.shell.env.scope_depth() == depth && self.shell.env.pop_scope(scope_type).is_ok()
269 }
270
271 fn report_imbalance(&self) {
273 tracing::error!(
274 "shell state out of balance when leaving {:?}: call stack {} deep, {} variable scopes",
275 self.leave,
276 self.shell.call_stack.depth(),
277 self.shell.env.scope_depth()
278 );
279 }
280}
281
282impl<SE: crate::extensions::ShellExtensions> Drop for ShellGuard<'_, SE> {
283 fn drop(&mut self) {
284 let left = match self.leave {
290 Leave::Function { frames, scopes } => {
291 let left = self.frame_on_top(frames, callstack::FrameType::is_function)
293 && self.pop_scope(scopes, env::EnvironmentScope::Local);
294 if left
295 && let Some(frame) = self.shell.call_stack.pop()
296 && let callstack::FrameType::Function(call) = frame.frame_type
297 && tracing::enabled!(target: trace_categories::FUNCTIONS, tracing::Level::DEBUG)
298 {
299 let depth = self.shell.call_stack.function_call_depth();
300 let prefix = repeated_char_str(' ', depth);
301 tracing::debug!(target: trace_categories::FUNCTIONS, "Exiting func [depth={depth}]: {prefix}{}", call.function_name);
302 }
303 left
304 }
305 Leave::Script { frames } => self.pop_frame(frames, callstack::FrameType::is_script),
306 Leave::TrapHandler {
307 frames,
308 last_exit_status,
309 } => {
310 self.shell.last_exit_status = last_exit_status;
311 self.pop_frame(frames, callstack::FrameType::is_trap_handler)
312 }
313 Leave::Scope { scopes, scope } => self.pop_scope(scopes, scope),
314 Leave::TrapBlock => {
315 let blocked = self.shell.call_stack.is_trap_delivery_suppressed();
316 self.shell.call_stack.release_trap_delivery_block();
317 blocked
318 }
319 };
320
321 if !left {
322 self.report_imbalance();
323 }
324 }
325}
326
327#[cfg(test)]
328#[allow(clippy::panic_in_result_fn, reason = "assertions in a fallible test")]
329mod tests {
330 use super::*;
331
332 #[tokio::test]
336 async fn guards_pop_nothing_when_out_of_balance() -> Result<(), error::Error> {
337 let mut shell = crate::Shell::builder()
338 .profile(crate::ProfileLoadBehavior::Skip)
339 .rc(crate::RcLoadBehavior::Skip)
340 .build()
341 .await?;
342 let params = shell.default_exec_params();
343 shell
344 .run_string("f() { :; }", &crate::SourceInfo::default(), ¶ms)
345 .await?;
346 let f = shell
347 .funcs()
348 .get("f")
349 .cloned()
350 .ok_or_else(|| error::ErrorKind::FunctionNotFound("f".into()))?;
351 let frames = shell.call_stack.depth();
352 let scopes = shell.env.scope_depth();
353
354 let mut function = shell.enter_function("f", &f, std::iter::empty())?;
356 function.env_mut().push_scope(env::EnvironmentScope::Local);
357 drop(function);
358 assert_eq!(shell.call_stack.depth(), frames + 1);
359 assert_eq!(shell.env.scope_depth(), scopes + 2);
360
361 let mut script = shell.enter_script(
363 callstack::ScriptCallType::Source,
364 &crate::SourceInfo::default(),
365 std::iter::empty(),
366 );
367 script.call_stack.pop();
368 script.start_command_string_mode();
369 drop(script);
370 assert_eq!(shell.call_stack.depth(), frames + 2);
371
372 Ok(())
373 }
374}