From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- .../rust/error-support/src/errorsupport.udl | 16 ++ third_party/rust/error-support/src/handling.rs | 112 ++++++++++++++ third_party/rust/error-support/src/lib.rs | 162 +++++++++++++++++++++ third_party/rust/error-support/src/macros.rs | 62 ++++++++ third_party/rust/error-support/src/redact.rs | 75 ++++++++++ third_party/rust/error-support/src/reporting.rs | 71 +++++++++ 6 files changed, 498 insertions(+) create mode 100644 third_party/rust/error-support/src/errorsupport.udl create mode 100644 third_party/rust/error-support/src/handling.rs create mode 100644 third_party/rust/error-support/src/lib.rs create mode 100644 third_party/rust/error-support/src/macros.rs create mode 100644 third_party/rust/error-support/src/redact.rs create mode 100644 third_party/rust/error-support/src/reporting.rs (limited to 'third_party/rust/error-support/src') diff --git a/third_party/rust/error-support/src/errorsupport.udl b/third_party/rust/error-support/src/errorsupport.udl new file mode 100644 index 0000000000..40482bc4e9 --- /dev/null +++ b/third_party/rust/error-support/src/errorsupport.udl @@ -0,0 +1,16 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +namespace errorsupport { + // Set the global error reporter. This is typically done early in startup. + void set_application_error_reporter(ApplicationErrorReporter error_reporter); + // Unset the global error reporter. This is typically done at shutdown for + // platforms that want to cleanup references like Desktop. + void unset_application_error_reporter(); +}; + +callback interface ApplicationErrorReporter { + void report_error(string type_name, string message); + void report_breadcrumb(string message, string module, u32 line, u32 column); +}; diff --git a/third_party/rust/error-support/src/handling.rs b/third_party/rust/error-support/src/handling.rs new file mode 100644 index 0000000000..f01fbc2b00 --- /dev/null +++ b/third_party/rust/error-support/src/handling.rs @@ -0,0 +1,112 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Helpers for components to "handle" errors. + +/// Describes what error reporting action should be taken. +#[derive(Debug, Default)] +pub struct ErrorReporting { + /// If Some(level), will write a log message at that level. + log_level: Option, + /// If Some(report_class) will call the error reporter with details. + report_class: Option, +} + +/// Specifies how an "internal" error is converted to an "external" public error and +/// any logging or reporting that should happen. +pub struct ErrorHandling { + /// The external error that should be returned. + pub err: E, + /// How the error should be reported. + pub reporting: ErrorReporting, +} + +impl ErrorHandling { + /// Create an ErrorHandling instance with an error conversion. + /// + /// ErrorHandling instance are created using a builder-style API. This is always the first + /// function in the chain, optionally followed by `log()`, `report()`, etc. + pub fn convert(err: E) -> Self { + Self { + err, + reporting: ErrorReporting::default(), + } + } + + /// Add logging to an ErrorHandling instance + pub fn log(self, level: log::Level) -> Self { + Self { + err: self.err, + reporting: ErrorReporting { + log_level: Some(level), + ..self.reporting + }, + } + } + + /// Add reporting to an ErrorHandling instance + pub fn report(self, report_class: impl Into) -> Self { + Self { + err: self.err, + reporting: ErrorReporting { + report_class: Some(report_class.into()), + ..self.reporting + }, + } + } + + // Convenience functions for the most common error reports + + /// log a warning + pub fn log_warning(self) -> Self { + self.log(log::Level::Warn) + } + + /// log an info + pub fn log_info(self) -> Self { + self.log(log::Level::Info) + } + + /// Add reporting to an ErrorHandling instance and also log an Error + pub fn report_error(self, report_class: impl Into) -> Self { + Self { + err: self.err, + reporting: ErrorReporting { + log_level: Some(log::Level::Error), + report_class: Some(report_class.into()), + }, + } + } +} + +/// A trait to define how errors are converted and reported. +pub trait GetErrorHandling { + type ExternalError; + + /// Return how to handle our internal errors + fn get_error_handling(&self) -> ErrorHandling; +} + +/// Handle the specified "internal" error, taking any logging or error +/// reporting actions and converting the error to the public error. +/// Called by our `handle_error` macro so needs to be public. +pub fn convert_log_report_error(e: IE) -> EE +where + IE: GetErrorHandling + std::error::Error, + EE: std::error::Error, +{ + let handling = e.get_error_handling(); + let reporting = handling.reporting; + if let Some(level) = reporting.log_level { + log::log!(level, "{}", e.to_string()); + } + if let Some(report_class) = reporting.report_class { + // notify the error reporter if the feature is enabled. + // XXX - should we arrange for the `report_class` to have the + // original crate calling this as a prefix, or will we still be + // able to identify that? + crate::report_error_to_app(report_class, e.to_string()); + } + handling.err +} diff --git a/third_party/rust/error-support/src/lib.rs b/third_party/rust/error-support/src/lib.rs new file mode 100644 index 0000000000..075e833f10 --- /dev/null +++ b/third_party/rust/error-support/src/lib.rs @@ -0,0 +1,162 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +mod macros; + +#[cfg(feature = "backtrace")] +/// Re-export of the `backtrace` crate for use in macros and +/// to ensure the needed version is kept in sync in dependents. +pub use backtrace; + +#[cfg(not(feature = "backtrace"))] +/// A compatibility shim for `backtrace`. +pub mod backtrace { + use std::fmt; + + pub struct Backtrace; + + impl fmt::Debug for Backtrace { + #[cold] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Not available") + } + } +} + +mod redact; +pub use redact::*; + +mod reporting; +pub use reporting::{ + report_breadcrumb, report_error_to_app, set_application_error_reporter, + unset_application_error_reporter, ApplicationErrorReporter, +}; + +pub use error_support_macros::handle_error; + +mod handling; +pub use handling::{convert_log_report_error, ErrorHandling, ErrorReporting, GetErrorHandling}; + +/// XXX - Most of this is now considered deprecated - only FxA uses it, and +/// should be replaced with the facilities in the `handling` module. + +/// Define a wrapper around the the provided ErrorKind type. +/// See also `define_error` which is more likely to be what you want. +#[macro_export] +macro_rules! define_error_wrapper { + ($Kind:ty) => { + pub type Result = std::result::Result; + struct ErrorData { + kind: $Kind, + backtrace: Option>, + } + + impl ErrorData { + #[cold] + fn new(kind: $Kind) -> Self { + ErrorData { + kind, + #[cfg(feature = "backtrace")] + backtrace: Some(std::sync::Mutex::new( + $crate::backtrace::Backtrace::new_unresolved(), + )), + #[cfg(not(feature = "backtrace"))] + backtrace: None, + } + } + + #[cfg(feature = "backtrace")] + #[cold] + fn get_backtrace(&self) -> Option<&std::sync::Mutex<$crate::backtrace::Backtrace>> { + self.backtrace.as_ref().map(|mutex| { + mutex.lock().unwrap().resolve(); + mutex + }) + } + + #[cfg(not(feature = "backtrace"))] + #[cold] + fn get_backtrace(&self) -> Option<&std::sync::Mutex<$crate::backtrace::Backtrace>> { + None + } + } + + impl std::fmt::Debug for ErrorData { + #[cfg(feature = "backtrace")] + #[cold] + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let mut bt = self.backtrace.unwrap().lock().unwrap(); + bt.resolve(); + write!(f, "{:?}\n\n{}", bt, self.kind) + } + + #[cfg(not(feature = "backtrace"))] + #[cold] + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.kind) + } + } + + #[derive(Debug, thiserror::Error)] + pub struct Error(Box); + impl Error { + #[cold] + pub fn kind(&self) -> &$Kind { + &self.0.kind + } + + #[cold] + pub fn backtrace(&self) -> Option<&std::sync::Mutex<$crate::backtrace::Backtrace>> { + self.0.get_backtrace() + } + } + + impl std::fmt::Display for Error { + #[cold] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.kind(), f) + } + } + + impl From<$Kind> for Error { + // Cold to optimize in favor of non-error cases. + #[cold] + fn from(ctx: $Kind) -> Error { + Error(Box::new(ErrorData::new(ctx))) + } + } + }; +} + +/// Define a set of conversions from external error types into the provided +/// error kind. Use `define_error` to do this at the same time as +/// `define_error_wrapper`. +#[macro_export] +macro_rules! define_error_conversions { + ($Kind:ident { $(($variant:ident, $type:ty)),* $(,)? }) => ($( + impl From<$type> for Error { + // Cold to optimize in favor of non-error cases. + #[cold] + fn from(e: $type) -> Self { + Error::from($Kind::$variant(e)) + } + } + )*); +} + +/// All the error boilerplate (okay, with a couple exceptions in some cases) in +/// one place. +#[macro_export] +macro_rules! define_error { + ($Kind:ident { $(($variant:ident, $type:ty)),* $(,)? }) => { + $crate::define_error_wrapper!($Kind); + $crate::define_error_conversions! { + $Kind { + $(($variant, $type)),* + } + } + }; +} + +uniffi::include_scaffolding!("errorsupport"); diff --git a/third_party/rust/error-support/src/macros.rs b/third_party/rust/error-support/src/macros.rs new file mode 100644 index 0000000000..11bf6dbca5 --- /dev/null +++ b/third_party/rust/error-support/src/macros.rs @@ -0,0 +1,62 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/// Tell the application to report an error +/// +/// If configured by the application, this sent to the application, which should report it to a +/// Sentry-like system. This should only be used for errors that we don't expect to see and will +/// work on fixing if we see a non-trivial volume of them. +/// +/// type_name identifies the error. It should be the main text that gets shown to the +/// user and also how the error system groups errors together. It should be in UpperCamelCase +/// form. +/// +/// Good type_names require some trial and error, for example: +/// - Start with the error kind variant name +/// - Add more text to distinguish errors more. For example an error code, or an extra word +/// based on inspecting the error details +#[macro_export] +macro_rules! report_error { + ($type_name:expr, $($arg:tt)*) => { + let message = std::format!($($arg)*); + ::log::warn!("report {}: {}", $type_name, message); + $crate::report_error_to_app($type_name.to_string(), message.to_string()); + }; +} + +/// Log a breadcrumb if we see an `Result::Err` value +/// +/// Use this macro to wrap a function call that returns a `Result<>`. If that call returns an +/// error, then we will log a breadcrumb for it. This can be used to track down the codepath where +/// an error happened. +#[macro_export] +macro_rules! trace_error { + ($result:expr) => {{ + let result = $result; + if let Err(e) = &result { + $crate::breadcrumb!("Saw error: {}", e); + }; + result + }}; +} + +/// Tell the application to log a breadcrumb +/// +/// Breadcrumbs are log-like entries that get tracked by the error reporting system. When we +/// report an error, recent breadcrumbs will be associated with it. +#[macro_export] +macro_rules! breadcrumb { + ($($arg:tt)*) => { + { + let message = std::format!($($arg)*); + ::log::info!("breadcrumb: {}", message); + $crate::report_breadcrumb( + message, + std::module_path!().to_string(), + std::line!(), + std::column!(), + ); + } + }; +} diff --git a/third_party/rust/error-support/src/redact.rs b/third_party/rust/error-support/src/redact.rs new file mode 100644 index 0000000000..01a333ee35 --- /dev/null +++ b/third_party/rust/error-support/src/redact.rs @@ -0,0 +1,75 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Functions to redact strings to remove PII before logging them + +/// Redact a URL. +/// +/// It's tricky to redact an URL without revealing PII. We check for various known bad URL forms +/// and report them, otherwise we just log "". +pub fn redact_url(url: &str) -> String { + if url.is_empty() { + return "".to_string(); + } + match url.find(':') { + None => "".to_string(), + Some(n) => { + let mut chars = url[0..n].chars(); + match chars.next() { + // No characters in the scheme + None => return "".to_string(), + Some(c) => { + // First character must be alphabetic + if !c.is_ascii_alphabetic() { + return "".to_string(); + } + } + } + for c in chars { + // Subsequent characters must be in the set ( alpha | digit | "+" | "-" | "." ) + if !(c.is_ascii_alphanumeric() || c == '+' || c == '-' || c == '.') { + return "".to_string(); + } + } + "".to_string() + } + } +} + +/// Redact compact jwe string (Five base64 segments, separated by `.` chars) +pub fn redact_compact_jwe(url: &str) -> String { + url.replace(|ch| ch != '.', "x") +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_redact_url() { + assert_eq!(redact_url("http://some.website.com/index.html"), ""); + assert_eq!(redact_url("about:config"), ""); + assert_eq!(redact_url(""), ""); + assert_eq!(redact_url("://some.website.com/"), ""); + assert_eq!(redact_url("some.website.com/"), ""); + assert_eq!(redact_url("some.website.com/"), ""); + assert_eq!( + redact_url("abc%@=://some.website.com/"), + "" + ); + assert_eq!( + redact_url("0https://some.website.com/"), + "" + ); + assert_eq!( + redact_url("a+weird-but.lega1-SCHEME://some.website.com/"), + "" + ); + } + + #[test] + fn test_redact_compact_jwe() { + assert_eq!(redact_compact_jwe("abc.1234.x3243"), "xxx.xxxx.xxxxx") + } +} diff --git a/third_party/rust/error-support/src/reporting.rs b/third_party/rust/error-support/src/reporting.rs new file mode 100644 index 0000000000..cf0f1ebd0b --- /dev/null +++ b/third_party/rust/error-support/src/reporting.rs @@ -0,0 +1,71 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use parking_lot::RwLock; +use std::sync::atomic::{AtomicU32, Ordering}; + +/// Counter for breadcrumb messages +/// +/// We are currently seeing breadcrumbs that may indicate that the reporting is unreliable. In +/// some reports, the breadcrumbs seem like they may be duplicated and/or out of order. This +/// counter is a temporary measure to check out that theory. +static BREADCRUMB_COUNTER: AtomicU32 = AtomicU32::new(0); + +fn get_breadcrumb_counter_value() -> u32 { + // Notes: + // - fetch_add is specified to wrap around in case of overflow, which seems okay. + // - By itself, this does not guarentee that breadcrumb logs will be ordered the same way as + // the counter values. If two threads are running at the same time, it's very possible + // that thread A gets the lower breadcrumb value, but thread B wins the race to report its + // breadcrumb. However, if we expect operations to be synchronized, like with places DB, + // then the breadcrumb counter values should always increase by 1. + BREADCRUMB_COUNTER.fetch_add(1, Ordering::Relaxed) +} + +/// Application error reporting trait +/// +/// The application that's consuming application-services implements this via a UniFFI callback +/// interface, then calls `set_application_error_reporter()` to setup a global +/// ApplicationErrorReporter. +pub trait ApplicationErrorReporter: Sync + Send { + /// Send an error report to a Sentry-like error reporting system + /// + /// type_name should be used to group errors together + fn report_error(&self, type_name: String, message: String); + /// Send a breadcrumb to a Sentry-like error reporting system + fn report_breadcrumb(&self, message: String, module: String, line: u32, column: u32); +} + +// ApplicationErrorReporter to use if the app doesn't set one +struct DefaultApplicationErrorReporter; +impl ApplicationErrorReporter for DefaultApplicationErrorReporter { + fn report_error(&self, _type_name: String, _message: String) {} + fn report_breadcrumb(&self, _message: String, _module: String, _line: u32, _column: u32) {} +} + +lazy_static::lazy_static! { + // RwLock rather than a Mutex, since we only expect to set this once. + pub(crate) static ref APPLICATION_ERROR_REPORTER: RwLock> = RwLock::new(Box::new(DefaultApplicationErrorReporter)); +} + +pub fn set_application_error_reporter(reporter: Box) { + *APPLICATION_ERROR_REPORTER.write() = reporter; +} + +pub fn unset_application_error_reporter() { + *APPLICATION_ERROR_REPORTER.write() = Box::new(DefaultApplicationErrorReporter) +} + +pub fn report_error_to_app(type_name: String, message: String) { + APPLICATION_ERROR_REPORTER + .read() + .report_error(type_name, message); +} + +pub fn report_breadcrumb(message: String, module: String, line: u32, column: u32) { + let message = format!("{} ({})", message, get_breadcrumb_counter_value()); + APPLICATION_ERROR_REPORTER + .read() + .report_breadcrumb(message, module, line, column); +} -- cgit v1.2.3