feat: Add backtraces to errors

This is gated behind the `debug` feature flag so only explicit debugging
cases pay the build time and runtime costs.

The builder API's stack traces are generally not too interesting.  Where
this really helps is with `clap_derive`.  We currently panic on
unexpected conditions which at least gives us a backtrace.  We'd like to
turn these into errors but to do so would lose those debuggin
backtraces, which is where this comes in.

This is a part of #2255
This commit is contained in:
Ed Page 2021-10-06 14:31:33 -05:00
parent 00f7fe5472
commit 7b5a4c9c2d
4 changed files with 91 additions and 24 deletions

View file

@ -72,6 +72,7 @@ termcolor = { version = "1.1", optional = true }
terminal_size = { version = "0.1.12", optional = true }
lazy_static = { version = "1", optional = true }
regex = { version = "1.0", optional = true }
backtrace = { version = "0.3", optional = true }
[dev-dependencies]
regex = "1.0"
@ -89,7 +90,7 @@ default = [
"suggestions",
"unicode_help",
]
debug = ["clap_derive/debug"] # Enables debug messages
debug = ["clap_derive/debug", "backtrace"] # Enables debug messages
# Used in default
std = ["indexmap/std"] # support for no_std in a backwards-compatible way

View file

@ -432,11 +432,23 @@ pub struct Error {
/// Useful when you want to render an error of your own.
pub info: Vec<String>,
pub(crate) source: Option<Box<dyn error::Error + Send + Sync>>,
backtrace: Backtrace,
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Display::fmt(&self.message, f)
#[cfg(feature = "debug")]
{
writeln!(f, "{}", self.message)?;
writeln!(f)?;
writeln!(f, "Backtrace:")?;
writeln!(f, "{}", self.backtrace)?;
}
#[cfg(not(feature = "debug"))]
{
Display::fmt(&self.message, f)?;
}
Ok(())
}
}
@ -519,6 +531,16 @@ impl Error {
self.message.print()
}
pub(crate) fn new(message: Colorizer, kind: ErrorKind) -> Self {
Self {
message,
kind,
info: vec![],
source: None,
backtrace: Backtrace::new(),
}
}
pub(crate) fn argument_conflict(
app: &App,
arg: &Arg,
@ -556,6 +578,7 @@ impl Error {
kind: ErrorKind::ArgumentConflict,
info,
source: None,
backtrace: Backtrace::new(),
}
}
@ -574,6 +597,7 @@ impl Error {
kind: ErrorKind::EmptyValue,
info: vec![arg],
source: None,
backtrace: Backtrace::new(),
}
}
@ -592,6 +616,7 @@ impl Error {
kind: ErrorKind::NoEquals,
info: vec![arg],
source: None,
backtrace: Backtrace::new(),
}
}
@ -655,6 +680,7 @@ impl Error {
kind: ErrorKind::InvalidValue,
info,
source: None,
backtrace: Backtrace::new(),
}
}
@ -686,6 +712,7 @@ impl Error {
kind: ErrorKind::InvalidSubcommand,
info: vec![subcmd],
source: None,
backtrace: Backtrace::new(),
}
}
@ -704,6 +731,7 @@ impl Error {
kind: ErrorKind::UnrecognizedSubcommand,
info: vec![subcmd],
source: None,
backtrace: Backtrace::new(),
}
}
@ -734,6 +762,7 @@ impl Error {
kind: ErrorKind::MissingRequiredArgument,
info,
source: None,
backtrace: Backtrace::new(),
}
}
@ -751,6 +780,7 @@ impl Error {
kind: ErrorKind::MissingSubcommand,
info: vec![],
source: None,
backtrace: Backtrace::new(),
}
}
@ -769,6 +799,7 @@ impl Error {
kind: ErrorKind::InvalidUtf8,
info: vec![],
source: None,
backtrace: Backtrace::new(),
}
}
@ -801,6 +832,7 @@ impl Error {
max_occurs.to_string(),
],
source: None,
backtrace: Backtrace::new(),
}
}
@ -820,6 +852,7 @@ impl Error {
kind: ErrorKind::TooManyValues,
info: vec![arg, val],
source: None,
backtrace: Backtrace::new(),
}
}
@ -848,6 +881,7 @@ impl Error {
kind: ErrorKind::TooFewValues,
info: vec![arg.to_string(), curr_vals.to_string(), min_vals.to_string()],
source: None,
backtrace: Backtrace::new(),
}
}
@ -862,6 +896,7 @@ impl Error {
kind,
info,
source,
backtrace,
} = Self::value_validation_with_color(arg, val, err, app.color());
try_help(app, &mut message);
Self {
@ -869,6 +904,7 @@ impl Error {
kind,
info,
source,
backtrace,
}
}
@ -901,6 +937,7 @@ impl Error {
kind: ErrorKind::ValueValidation,
info: vec![arg, val, err.to_string()],
source: Some(err),
backtrace: Backtrace::new(),
}
}
@ -929,6 +966,7 @@ impl Error {
kind: ErrorKind::WrongNumberOfValues,
info: vec![arg.to_string(), curr_vals.to_string(), num_vals.to_string()],
source: None,
backtrace: Backtrace::new(),
}
}
@ -947,6 +985,7 @@ impl Error {
kind: ErrorKind::UnexpectedMultipleUsage,
info: vec![arg],
source: None,
backtrace: Backtrace::new(),
}
}
@ -996,6 +1035,7 @@ impl Error {
kind: ErrorKind::UnknownArgument,
info: vec![arg],
source: None,
backtrace: Backtrace::new(),
}
}
@ -1018,6 +1058,7 @@ impl Error {
kind: ErrorKind::UnknownArgument,
info: vec![arg],
source: None,
backtrace: Backtrace::new(),
}
}
@ -1033,6 +1074,7 @@ impl Error {
kind: ErrorKind::ArgumentNotFound,
info: vec![arg],
source: None,
backtrace: Backtrace::new(),
}
}
@ -1050,6 +1092,7 @@ impl Error {
kind,
info: vec![],
source: None,
backtrace: Backtrace::new(),
}
}
}
@ -1073,6 +1116,43 @@ impl error::Error for Error {
}
}
#[cfg(feature = "debug")]
#[derive(Debug)]
struct Backtrace(backtrace::Backtrace);
#[cfg(feature = "debug")]
impl Backtrace {
fn new() -> Self {
Self(backtrace::Backtrace::new())
}
}
#[cfg(feature = "debug")]
impl Display for Backtrace {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
// `backtrace::Backtrace` uses `Debug` instead of `Display`
write!(f, "{:?}", self.0)
}
}
#[cfg(not(feature = "debug"))]
#[derive(Debug)]
struct Backtrace;
#[cfg(not(feature = "debug"))]
impl Backtrace {
fn new() -> Self {
Self
}
}
#[cfg(not(feature = "debug"))]
impl Display for Backtrace {
fn fmt(&self, _: &mut Formatter) -> fmt::Result {
Ok(())
}
}
#[cfg(test)]
mod tests {
/// Check `clap::Error` impls Send and Sync.

View file

@ -759,12 +759,10 @@ impl<'help, 'app> Parser<'help, 'app> {
} else if self.is_set(AS::SubcommandRequiredElseHelp) {
debug!("Parser::get_matches_with: SubcommandRequiredElseHelp=true");
let message = self.write_help_err()?;
return Err(ClapError {
return Err(ClapError::new(
message,
kind: ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand,
info: vec![],
source: None,
});
ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand,
));
}
self.remove_overrides(matcher);
@ -1903,12 +1901,7 @@ impl<'help, 'app> Parser<'help, 'app> {
match Help::new(HelpWriter::Buffer(&mut c), self, use_long).write_help() {
Err(e) => e.into(),
_ => ClapError {
message: c,
kind: ErrorKind::DisplayHelp,
info: vec![],
source: None,
},
_ => ClapError::new(c, ErrorKind::DisplayHelp),
}
}
@ -1918,12 +1911,7 @@ impl<'help, 'app> Parser<'help, 'app> {
let msg = self.app._render_version(use_long);
let mut c = Colorizer::new(false, self.color_help());
c.none(msg);
ClapError {
message: c,
kind: ErrorKind::DisplayVersion,
info: vec![],
source: None,
}
ClapError::new(c, ErrorKind::DisplayVersion)
}
}

View file

@ -63,12 +63,10 @@ impl<'help, 'app, 'parser> Validator<'help, 'app, 'parser> {
&& self.p.is_set(AS::ArgRequiredElseHelp)
{
let message = self.p.write_help_err()?;
return Err(Error {
return Err(Error::new(
message,
kind: ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand,
info: vec![],
source: None,
});
ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand,
));
}
self.validate_conflicts(matcher)?;
if !(self.p.is_set(AS::SubcommandsNegateReqs) && is_subcmd || reqs_validated) {