From 02a920395c98a706a8c404b4ce17ffb3850c6fd2 Mon Sep 17 00:00:00 2001 From: Himadri Bhattacharjee Date: Fri, 22 Mar 2024 00:42:03 +0530 Subject: [PATCH] fix: use environment variables to prevent command_not_found from recursing (#11090) - fixes #11014 # Description When the `command_not_found` hook is entered, we set an environment variable for context. If an unknown command is encountered and the `command_not_found` context environment variable is already present, it implies a command in the hook closure is also not found. We stop the recursion right there. # User-Facing Changes Incorrect `command_not_found` hooks can be caught without panicking. # Tests + Formatting Tests are passing. # After Submitting --- crates/nu-command/src/system/run_external.rs | 50 +++++++++++++------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index 2834866919..4b9f84eee4 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -401,25 +401,41 @@ impl ExternalCommand { let mut engine_state = engine_state.clone(); if let Some(hook) = engine_state.config.hooks.command_not_found.clone() { + let canary = "ENTERED_COMMAND_NOT_FOUND"; let stack = &mut stack.start_capture(); - if let Ok(PipelineData::Value(Value::String { val, .. }, ..)) = - eval_hook( - &mut engine_state, - stack, - None, - vec![( - "cmd_name".into(), - Value::string( - self.name.item.to_string(), - self.name.span, - ), - )], - &hook, - "command_not_found", - ) - { - err_str = format!("{}\n{}", err_str, val); + if stack.has_env_var(&engine_state, canary) { + return Err(ShellError::ExternalCommand { + label: "command_not_found handler could not be run".into(), + help: "make sure the command_not_found closure itself does not use unknown commands".to_string(), + span: self.name.span, + }); } + stack.add_env_var( + canary.to_string(), + Value::bool(true, Span::unknown()), + ); + match eval_hook( + &mut engine_state, + stack, + None, + vec![( + "cmd_name".into(), + Value::string(self.name.item.to_string(), self.name.span), + )], + &hook, + "command_not_found", + ) { + Ok(PipelineData::Value(Value::String { val, .. }, ..)) => { + err_str = format!("{}\n{}", err_str, val); + } + + Err(err) => { + stack.remove_env_var(&engine_state, canary); + return Err(err); + } + _ => {} + } + stack.remove_env_var(&engine_state, canary); } }