From 0fbc17c2dd488d1b2159e3e2d654a3122c7f4ef6 Mon Sep 17 00:00:00 2001 From: Christopher Dryden Date: Mon, 15 Dec 2025 04:23:09 +0000 Subject: [PATCH] clap_localization: return error instead of calling exit() for fuzzer compatibility --- src/uu/date/src/date.rs | 23 +--- src/uucore/src/lib/mods/clap_localization.rs | 107 ++++++------------- tests/by-util/test_date.rs | 4 +- 3 files changed, 38 insertions(+), 96 deletions(-) diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index 145583f9e..d02ca4a47 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -169,28 +169,7 @@ fn parse_military_timezone_with_offset(s: &str) -> Option { #[uucore::main] #[allow(clippy::cognitive_complexity)] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let args: Vec = args.collect(); - let matches = match uu_app().try_get_matches_from(&args) { - Ok(matches) => matches, - Err(e) => { - match e.kind() { - clap::error::ErrorKind::DisplayHelp | clap::error::ErrorKind::DisplayVersion => { - return Err(e.into()); - } - _ => { - // Convert unknown options to be treated as invalid date format - // This ensures consistent exit status 1 instead of clap's exit status 77 - if let Some(arg) = args.get(1) { - return Err(USimpleError::new( - 1, - translate!("date-error-invalid-date", "date" => arg.to_string_lossy()), - )); - } - return Err(USimpleError::new(1, e.to_string())); - } - } - } - }; + let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?; // Check for extra operands (multiple positional arguments) if let Some(formats) = matches.get_many::(OPT_FORMAT) { diff --git a/src/uucore/src/lib/mods/clap_localization.rs b/src/uucore/src/lib/mods/clap_localization.rs index 5a54bf7c3..e0a0ce84e 100644 --- a/src/uucore/src/lib/mods/clap_localization.rs +++ b/src/uucore/src/lib/mods/clap_localization.rs @@ -11,7 +11,7 @@ //! instead of parsing error strings, providing a more robust solution. //! -use crate::error::UResult; +use crate::error::{UResult, USimpleError}; use crate::locale::translate; use clap::error::{ContextKind, ErrorKind}; @@ -108,43 +108,37 @@ impl<'a> ErrorFormatter<'a> { where F: FnOnce(), { + let code = self.print_error(err, exit_code); + callback(); + std::process::exit(code); + } + + /// Print error and return exit code (no exit call) + pub fn print_error(&self, err: &Error, exit_code: i32) -> i32 { match err.kind() { ErrorKind::DisplayHelp | ErrorKind::DisplayVersion => self.handle_display_errors(err), - ErrorKind::UnknownArgument => { - self.handle_unknown_argument_with_callback(err, exit_code, callback) - } + ErrorKind::UnknownArgument => self.handle_unknown_argument(err, exit_code), ErrorKind::InvalidValue | ErrorKind::ValueValidation => { - self.handle_invalid_value_with_callback(err, exit_code, callback) - } - ErrorKind::MissingRequiredArgument => { - self.handle_missing_required_with_callback(err, exit_code, callback) + self.handle_invalid_value(err, exit_code) } + ErrorKind::MissingRequiredArgument => self.handle_missing_required(err, exit_code), ErrorKind::TooFewValues | ErrorKind::TooManyValues | ErrorKind::WrongNumberOfValues => { // These need full clap formatting eprint!("{}", err.render()); - callback(); - std::process::exit(exit_code); + exit_code } - _ => self.handle_generic_error_with_callback(err, exit_code, callback), + _ => self.handle_generic_error(err, exit_code), } } /// Handle help and version display - fn handle_display_errors(&self, err: &Error) -> ! { + fn handle_display_errors(&self, err: &Error) -> i32 { print!("{}", err.render()); - std::process::exit(0); + 0 } - /// Handle unknown argument errors with callback - fn handle_unknown_argument_with_callback( - &self, - err: &Error, - exit_code: i32, - callback: F, - ) -> ! - where - F: FnOnce(), - { + /// Handle unknown argument errors + fn handle_unknown_argument(&self, err: &Error, exit_code: i32) -> i32 { if let Some(invalid_arg) = err.get(ContextKind::InvalidArg) { let arg_str = invalid_arg.to_string(); let error_word = translate!("common-error"); @@ -179,21 +173,13 @@ impl<'a> ErrorFormatter<'a> { self.print_usage_and_help(); } else { - self.print_simple_error_with_callback( - &translate!("clap-error-unexpected-argument-simple"), - exit_code, - || {}, - ); + self.print_simple_error_msg(&translate!("clap-error-unexpected-argument-simple")); } - callback(); - std::process::exit(exit_code); + exit_code } - /// Handle invalid value errors with callback - fn handle_invalid_value_with_callback(&self, err: &Error, exit_code: i32, callback: F) -> ! - where - F: FnOnce(), - { + /// Handle invalid value errors + fn handle_invalid_value(&self, err: &Error, exit_code: i32) -> i32 { let invalid_arg = err.get(ContextKind::InvalidArg); let invalid_value = err.get(ContextKind::InvalidValue); @@ -245,32 +231,22 @@ impl<'a> ErrorFormatter<'a> { eprintln!(); eprintln!("{}", translate!("common-help-suggestion")); } else { - self.print_simple_error(&err.render().to_string(), exit_code); + self.print_simple_error_msg(&err.render().to_string()); } // InvalidValue errors traditionally use exit code 1 for backward compatibility // But if a utility explicitly requests a high exit code (>= 125), respect it // This allows utilities like runcon (125) to override the default while preserving // the standard behavior for utilities using normal error codes (1, 2, etc.) - let actual_exit_code = if matches!(err.kind(), ErrorKind::InvalidValue) && exit_code < 125 { + if matches!(err.kind(), ErrorKind::InvalidValue) && exit_code < 125 { 1 // Force exit code 1 for InvalidValue unless using special exit codes } else { exit_code // Respect the requested exit code for special cases - }; - callback(); - std::process::exit(actual_exit_code); + } } - /// Handle missing required argument errors with callback - fn handle_missing_required_with_callback( - &self, - err: &Error, - exit_code: i32, - callback: F, - ) -> ! - where - F: FnOnce(), - { + /// Handle missing required argument errors + fn handle_missing_required(&self, err: &Error, exit_code: i32) -> i32 { let rendered_str = err.render().to_string(); let lines: Vec<&str> = rendered_str.lines().collect(); @@ -313,15 +289,11 @@ impl<'a> ErrorFormatter<'a> { } _ => eprint!("{}", err.render()), } - callback(); - std::process::exit(exit_code); + exit_code } - /// Handle generic errors with callback - fn handle_generic_error_with_callback(&self, err: &Error, exit_code: i32, callback: F) -> ! - where - F: FnOnce(), - { + /// Handle generic errors + fn handle_generic_error(&self, err: &Error, exit_code: i32) -> i32 { let rendered_str = err.render().to_string(); if let Some(main_error_line) = rendered_str.lines().next() { self.print_localized_error_line(main_error_line); @@ -330,27 +302,16 @@ impl<'a> ErrorFormatter<'a> { } else { eprint!("{}", err.render()); } - callback(); - std::process::exit(exit_code); + exit_code } - /// Print a simple error message - fn print_simple_error(&self, message: &str, exit_code: i32) -> ! { - self.print_simple_error_with_callback(message, exit_code, || {}) - } - - /// Print a simple error message with callback - fn print_simple_error_with_callback(&self, message: &str, exit_code: i32, callback: F) -> ! - where - F: FnOnce(), - { + /// Print a simple error message (no exit) + fn print_simple_error_msg(&self, message: &str) { let error_word = translate!("common-error"); eprintln!( "{}: {message}", self.color_mgr.colorize(&error_word, Color::Red) ); - callback(); - std::process::exit(exit_code); } /// Print error line with localized "error:" prefix @@ -478,7 +439,9 @@ where if e.exit_code() == 0 { e.into() // Preserve help/version } else { - handle_clap_error_with_exit_code(e, exit_code) + let formatter = ErrorFormatter::new(crate::util_name()); + let code = formatter.print_error(&e, exit_code); + USimpleError::new(code, "") } }) } diff --git a/tests/by-util/test_date.rs b/tests/by-util/test_date.rs index 689211bf9..319e3ab03 100644 --- a/tests/by-util/test_date.rs +++ b/tests/by-util/test_date.rs @@ -37,7 +37,7 @@ fn test_invalid_long_option() { new_ucmd!() .arg("--fB") .fails_with_code(1) - .stderr_contains("invalid date '--fB'"); + .stderr_contains("unexpected argument '--fB'"); } #[test] @@ -45,7 +45,7 @@ fn test_invalid_short_option() { new_ucmd!() .arg("-w") .fails_with_code(1) - .stderr_contains("invalid date '-w'"); + .stderr_contains("unexpected argument '-w'"); } #[test]