Skip to content

Commit 0fbc17c

Browse files
ChrisDrydensylvestre
authored andcommitted
clap_localization: return error instead of calling exit() for fuzzer compatibility
1 parent 54102d7 commit 0fbc17c

File tree

3 files changed

+38
-96
lines changed

3 files changed

+38
-96
lines changed

src/uu/date/src/date.rs

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -169,28 +169,7 @@ fn parse_military_timezone_with_offset(s: &str) -> Option<i32> {
169169
#[uucore::main]
170170
#[allow(clippy::cognitive_complexity)]
171171
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
172-
let args: Vec<std::ffi::OsString> = args.collect();
173-
let matches = match uu_app().try_get_matches_from(&args) {
174-
Ok(matches) => matches,
175-
Err(e) => {
176-
match e.kind() {
177-
clap::error::ErrorKind::DisplayHelp | clap::error::ErrorKind::DisplayVersion => {
178-
return Err(e.into());
179-
}
180-
_ => {
181-
// Convert unknown options to be treated as invalid date format
182-
// This ensures consistent exit status 1 instead of clap's exit status 77
183-
if let Some(arg) = args.get(1) {
184-
return Err(USimpleError::new(
185-
1,
186-
translate!("date-error-invalid-date", "date" => arg.to_string_lossy()),
187-
));
188-
}
189-
return Err(USimpleError::new(1, e.to_string()));
190-
}
191-
}
192-
}
193-
};
172+
let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?;
194173

195174
// Check for extra operands (multiple positional arguments)
196175
if let Some(formats) = matches.get_many::<String>(OPT_FORMAT) {

src/uucore/src/lib/mods/clap_localization.rs

Lines changed: 35 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
//! instead of parsing error strings, providing a more robust solution.
1212
//!
1313
14-
use crate::error::UResult;
14+
use crate::error::{UResult, USimpleError};
1515
use crate::locale::translate;
1616

1717
use clap::error::{ContextKind, ErrorKind};
@@ -108,43 +108,37 @@ impl<'a> ErrorFormatter<'a> {
108108
where
109109
F: FnOnce(),
110110
{
111+
let code = self.print_error(err, exit_code);
112+
callback();
113+
std::process::exit(code);
114+
}
115+
116+
/// Print error and return exit code (no exit call)
117+
pub fn print_error(&self, err: &Error, exit_code: i32) -> i32 {
111118
match err.kind() {
112119
ErrorKind::DisplayHelp | ErrorKind::DisplayVersion => self.handle_display_errors(err),
113-
ErrorKind::UnknownArgument => {
114-
self.handle_unknown_argument_with_callback(err, exit_code, callback)
115-
}
120+
ErrorKind::UnknownArgument => self.handle_unknown_argument(err, exit_code),
116121
ErrorKind::InvalidValue | ErrorKind::ValueValidation => {
117-
self.handle_invalid_value_with_callback(err, exit_code, callback)
118-
}
119-
ErrorKind::MissingRequiredArgument => {
120-
self.handle_missing_required_with_callback(err, exit_code, callback)
122+
self.handle_invalid_value(err, exit_code)
121123
}
124+
ErrorKind::MissingRequiredArgument => self.handle_missing_required(err, exit_code),
122125
ErrorKind::TooFewValues | ErrorKind::TooManyValues | ErrorKind::WrongNumberOfValues => {
123126
// These need full clap formatting
124127
eprint!("{}", err.render());
125-
callback();
126-
std::process::exit(exit_code);
128+
exit_code
127129
}
128-
_ => self.handle_generic_error_with_callback(err, exit_code, callback),
130+
_ => self.handle_generic_error(err, exit_code),
129131
}
130132
}
131133

132134
/// Handle help and version display
133-
fn handle_display_errors(&self, err: &Error) -> ! {
135+
fn handle_display_errors(&self, err: &Error) -> i32 {
134136
print!("{}", err.render());
135-
std::process::exit(0);
137+
0
136138
}
137139

138-
/// Handle unknown argument errors with callback
139-
fn handle_unknown_argument_with_callback<F>(
140-
&self,
141-
err: &Error,
142-
exit_code: i32,
143-
callback: F,
144-
) -> !
145-
where
146-
F: FnOnce(),
147-
{
140+
/// Handle unknown argument errors
141+
fn handle_unknown_argument(&self, err: &Error, exit_code: i32) -> i32 {
148142
if let Some(invalid_arg) = err.get(ContextKind::InvalidArg) {
149143
let arg_str = invalid_arg.to_string();
150144
let error_word = translate!("common-error");
@@ -179,21 +173,13 @@ impl<'a> ErrorFormatter<'a> {
179173

180174
self.print_usage_and_help();
181175
} else {
182-
self.print_simple_error_with_callback(
183-
&translate!("clap-error-unexpected-argument-simple"),
184-
exit_code,
185-
|| {},
186-
);
176+
self.print_simple_error_msg(&translate!("clap-error-unexpected-argument-simple"));
187177
}
188-
callback();
189-
std::process::exit(exit_code);
178+
exit_code
190179
}
191180

192-
/// Handle invalid value errors with callback
193-
fn handle_invalid_value_with_callback<F>(&self, err: &Error, exit_code: i32, callback: F) -> !
194-
where
195-
F: FnOnce(),
196-
{
181+
/// Handle invalid value errors
182+
fn handle_invalid_value(&self, err: &Error, exit_code: i32) -> i32 {
197183
let invalid_arg = err.get(ContextKind::InvalidArg);
198184
let invalid_value = err.get(ContextKind::InvalidValue);
199185

@@ -245,32 +231,22 @@ impl<'a> ErrorFormatter<'a> {
245231
eprintln!();
246232
eprintln!("{}", translate!("common-help-suggestion"));
247233
} else {
248-
self.print_simple_error(&err.render().to_string(), exit_code);
234+
self.print_simple_error_msg(&err.render().to_string());
249235
}
250236

251237
// InvalidValue errors traditionally use exit code 1 for backward compatibility
252238
// But if a utility explicitly requests a high exit code (>= 125), respect it
253239
// This allows utilities like runcon (125) to override the default while preserving
254240
// the standard behavior for utilities using normal error codes (1, 2, etc.)
255-
let actual_exit_code = if matches!(err.kind(), ErrorKind::InvalidValue) && exit_code < 125 {
241+
if matches!(err.kind(), ErrorKind::InvalidValue) && exit_code < 125 {
256242
1 // Force exit code 1 for InvalidValue unless using special exit codes
257243
} else {
258244
exit_code // Respect the requested exit code for special cases
259-
};
260-
callback();
261-
std::process::exit(actual_exit_code);
245+
}
262246
}
263247

264-
/// Handle missing required argument errors with callback
265-
fn handle_missing_required_with_callback<F>(
266-
&self,
267-
err: &Error,
268-
exit_code: i32,
269-
callback: F,
270-
) -> !
271-
where
272-
F: FnOnce(),
273-
{
248+
/// Handle missing required argument errors
249+
fn handle_missing_required(&self, err: &Error, exit_code: i32) -> i32 {
274250
let rendered_str = err.render().to_string();
275251
let lines: Vec<&str> = rendered_str.lines().collect();
276252

@@ -313,15 +289,11 @@ impl<'a> ErrorFormatter<'a> {
313289
}
314290
_ => eprint!("{}", err.render()),
315291
}
316-
callback();
317-
std::process::exit(exit_code);
292+
exit_code
318293
}
319294

320-
/// Handle generic errors with callback
321-
fn handle_generic_error_with_callback<F>(&self, err: &Error, exit_code: i32, callback: F) -> !
322-
where
323-
F: FnOnce(),
324-
{
295+
/// Handle generic errors
296+
fn handle_generic_error(&self, err: &Error, exit_code: i32) -> i32 {
325297
let rendered_str = err.render().to_string();
326298
if let Some(main_error_line) = rendered_str.lines().next() {
327299
self.print_localized_error_line(main_error_line);
@@ -330,27 +302,16 @@ impl<'a> ErrorFormatter<'a> {
330302
} else {
331303
eprint!("{}", err.render());
332304
}
333-
callback();
334-
std::process::exit(exit_code);
335-
}
336-
337-
/// Print a simple error message
338-
fn print_simple_error(&self, message: &str, exit_code: i32) -> ! {
339-
self.print_simple_error_with_callback(message, exit_code, || {})
305+
exit_code
340306
}
341307

342-
/// Print a simple error message with callback
343-
fn print_simple_error_with_callback<F>(&self, message: &str, exit_code: i32, callback: F) -> !
344-
where
345-
F: FnOnce(),
346-
{
308+
/// Print a simple error message (no exit)
309+
fn print_simple_error_msg(&self, message: &str) {
347310
let error_word = translate!("common-error");
348311
eprintln!(
349312
"{}: {message}",
350313
self.color_mgr.colorize(&error_word, Color::Red)
351314
);
352-
callback();
353-
std::process::exit(exit_code);
354315
}
355316

356317
/// Print error line with localized "error:" prefix
@@ -478,7 +439,9 @@ where
478439
if e.exit_code() == 0 {
479440
e.into() // Preserve help/version
480441
} else {
481-
handle_clap_error_with_exit_code(e, exit_code)
442+
let formatter = ErrorFormatter::new(crate::util_name());
443+
let code = formatter.print_error(&e, exit_code);
444+
USimpleError::new(code, "")
482445
}
483446
})
484447
}

tests/by-util/test_date.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,15 @@ fn test_invalid_long_option() {
3737
new_ucmd!()
3838
.arg("--fB")
3939
.fails_with_code(1)
40-
.stderr_contains("invalid date '--fB'");
40+
.stderr_contains("unexpected argument '--fB'");
4141
}
4242

4343
#[test]
4444
fn test_invalid_short_option() {
4545
new_ucmd!()
4646
.arg("-w")
4747
.fails_with_code(1)
48-
.stderr_contains("invalid date '-w'");
48+
.stderr_contains("unexpected argument '-w'");
4949
}
5050

5151
#[test]

0 commit comments

Comments
 (0)