memscope_rs/core/
unwrap_safe.rs

1//! Safe unwrap utilities for memscope-rs
2//!
3//! This module provides safe, panic-free alternatives to unwrap() calls.
4//! It includes comprehensive error tracking and recovery mechanisms.
5
6use crate::core::error::{MemScopeError, MemoryOperation, SystemErrorType};
7use crate::core::safe_operations::SafeLock;
8use std::backtrace::Backtrace;
9use std::error::Error as StdError;
10use std::fmt::{self, Debug, Display};
11
12/// Custom error type for unwrap operations
13#[derive(Debug)]
14pub enum UnwrapError {
15    NoneValue {
16        context: &'static str,
17        location: Option<&'static str>,
18        backtrace: Backtrace,
19    },
20    ResultError {
21        source: Box<dyn StdError + Send + Sync>,
22        context: &'static str,
23        location: Option<&'static str>,
24        backtrace: Backtrace,
25    },
26}
27
28impl Display for UnwrapError {
29    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30        match self {
31            Self::NoneValue {
32                context, location, ..
33            } => {
34                if let Some(loc) = location {
35                    write!(f, "Attempt to unwrap None at {loc} ({context})")
36                } else {
37                    write!(f, "Attempt to unwrap None ({context})")
38                }
39            }
40            Self::ResultError {
41                source,
42                context,
43                location,
44                ..
45            } => {
46                if let Some(loc) = location {
47                    write!(f, "Unwrap failed at {loc} ({context}): {source}")
48                } else {
49                    write!(f, "Unwrap failed ({context}): {source}")
50                }
51            }
52        }
53    }
54}
55
56impl StdError for UnwrapError {
57    fn source(&self) -> Option<&(dyn StdError + 'static)> {
58        match self {
59            Self::NoneValue { .. } => None,
60            Self::ResultError { source, .. } => Some(&**source),
61        }
62    }
63}
64
65impl UnwrapError {
66    /// Get the backtrace for this error
67    pub fn backtrace(&self) -> &Backtrace {
68        match self {
69            Self::NoneValue { backtrace, .. } | Self::ResultError { backtrace, .. } => backtrace,
70        }
71    }
72}
73
74/// Extension trait for safe, panic-free unwrapping
75pub trait UnwrapSafe<T> {
76    /// Try to unwrap a value, returning a Result
77    fn try_unwrap(self, context: &'static str) -> Result<T, UnwrapError>;
78
79    /// Try to unwrap with location information
80    fn try_unwrap_at(self, context: &'static str, location: &'static str) -> Result<T, UnwrapError>
81    where
82        Self: Sized,
83    {
84        self.try_unwrap(context).map_err(|mut e| {
85            match &mut e {
86                UnwrapError::NoneValue { location: loc, .. }
87                | UnwrapError::ResultError { location: loc, .. } => {
88                    *loc = Some(location);
89                }
90            }
91            e
92        })
93    }
94
95    /// Unwrap or return a default value
96    fn unwrap_or_default_safe(self, default: T, context: &'static str) -> T
97    where
98        Self: Sized,
99    {
100        self.try_unwrap(context).unwrap_or_else(|e| {
101            tracing::warn!("{}", e);
102            default
103        })
104    }
105
106    /// Unwrap or compute a default value
107    fn unwrap_or_else_safe<F>(self, default_fn: F, context: &'static str) -> T
108    where
109        Self: Sized,
110        F: FnOnce() -> T,
111    {
112        self.try_unwrap(context).unwrap_or_else(|e| {
113            tracing::warn!("{}", e);
114            default_fn()
115        })
116    }
117
118    /// Backwards compatibility method (deprecated)
119    #[deprecated(note = "Use try_unwrap() instead")]
120    fn try_unwrap_safe(self, context: &'static str) -> Result<T, MemScopeError>
121    where
122        Self: Sized,
123    {
124        self.try_unwrap(context).map_err(|e| {
125            MemScopeError::memory(
126                MemoryOperation::Allocation,
127                format!("Failed to unwrap value: {e}"),
128            )
129        })
130    }
131
132    /// Unwrap with context information (deprecated)
133    /// Unwrap or abort the process
134    ///
135    /// # Safety
136    /// This method will abort the process on error. Only use when the program
137    /// cannot continue without this value.
138    #[deprecated(note = "Use try_unwrap() instead")]
139    fn unwrap_safe(self, context: &'static str) -> T
140    where
141        Self: Sized,
142    {
143        self.try_unwrap(context).unwrap_or_else(|e| {
144            tracing::error!("Fatal error: {}\nBacktrace:\n{:?}", e, e.backtrace());
145            std::process::abort();
146        })
147    }
148
149    /// Unwrap with context and location information (deprecated)
150    /// Unwrap or abort the process with location information
151    ///
152    /// # Safety
153    /// This method will abort the process on error. Only use when the program
154    /// cannot continue without this value.
155    #[deprecated(note = "Use try_unwrap_at() instead")]
156    fn unwrap_safe_at(self, context: &'static str, location: &'static str) -> T
157    where
158        Self: Sized,
159    {
160        self.try_unwrap_at(context, location).unwrap_or_else(|e| {
161            let backtrace = e.backtrace();
162            tracing::error!(
163                "Fatal error at {}: {}\nBacktrace:\n{:?}",
164                location,
165                e,
166                backtrace
167            );
168            std::process::abort();
169        })
170    }
171}
172
173impl<T> UnwrapSafe<T> for Option<T> {
174    fn try_unwrap(self, context: &'static str) -> Result<T, UnwrapError> {
175        match self {
176            Some(value) => {
177                tracing::trace!("Unwrap succeeded: {}", context);
178                Ok(value)
179            }
180            None => {
181                let error = UnwrapError::NoneValue {
182                    context,
183                    location: None,
184                    backtrace: Backtrace::capture(),
185                };
186                tracing::error!("Unwrap failed: {}", error);
187                Err(error)
188            }
189        }
190    }
191
192    fn unwrap_or_else_safe<F>(self, default_fn: F, context: &'static str) -> T
193    where
194        F: FnOnce() -> T,
195    {
196        self.try_unwrap(context).unwrap_or_else(|e| {
197            tracing::warn!("Using default value: {}", e);
198            default_fn()
199        })
200    }
201
202    fn try_unwrap_safe(self, context: &'static str) -> Result<T, MemScopeError> {
203        self.try_unwrap(context).map_err(|e| {
204            MemScopeError::memory(
205                MemoryOperation::Allocation,
206                format!("Failed to unwrap value: {e:?}"),
207            )
208        })
209    }
210}
211
212impl<T, E: StdError + Send + Sync + 'static> UnwrapSafe<T> for Result<T, E> {
213    fn try_unwrap(self, context: &'static str) -> Result<T, UnwrapError> {
214        match self {
215            Ok(value) => {
216                tracing::trace!("Result unwrap succeeded: {}", context);
217                Ok(value)
218            }
219            Err(error) => {
220                let error = UnwrapError::ResultError {
221                    source: Box::new(error),
222                    context,
223                    location: None,
224                    backtrace: Backtrace::capture(),
225                };
226                tracing::error!("Result unwrap failed: {error:?}");
227                Err(error)
228            }
229        }
230    }
231
232    fn try_unwrap_at(
233        self,
234        context: &'static str,
235        location: &'static str,
236    ) -> Result<T, UnwrapError> {
237        self.try_unwrap(context).map_err(|mut e| {
238            if let UnwrapError::ResultError { location: loc, .. } = &mut e {
239                *loc = Some(location);
240            }
241            e
242        })
243    }
244
245    fn unwrap_or_default_safe(self, default: T, context: &str) -> T {
246        match self {
247            Ok(value) => {
248                tracing::trace!("Safe unwrap succeeded: {}", context);
249                value
250            }
251            Err(error) => {
252                tracing::warn!("Safe unwrap failed (Error: {error:?}), using default: {context}",);
253                default
254            }
255        }
256    }
257
258    fn unwrap_or_else_safe<F>(self, default_fn: F, context: &str) -> T
259    where
260        F: FnOnce() -> T,
261    {
262        match self {
263            Ok(value) => {
264                tracing::trace!("Safe unwrap succeeded: {}", context);
265                value
266            }
267            Err(error) => {
268                tracing::warn!(
269                    "Safe unwrap failed (Error: {error:?}), using default function: {context}",
270                );
271                default_fn()
272            }
273        }
274    }
275
276    fn try_unwrap_safe(self, context: &str) -> Result<T, MemScopeError> {
277        match self {
278            Ok(value) => {
279                tracing::trace!("Safe unwrap succeeded: {context}");
280                Ok(value)
281            }
282            Err(error) => {
283                tracing::error!("Safe unwrap failed (Error: {error:?}): {context}");
284                Err(MemScopeError::system(
285                    SystemErrorType::Io,
286                    format!("Result unwrap failed in context: {context} - error: {error:?}",),
287                ))
288            }
289        }
290    }
291}
292
293/// Convenience macro for safe unwrapping with automatic context
294#[macro_export]
295macro_rules! unwrap_safe {
296    ($expr:expr) => {
297        $expr.unwrap_safe(&format!("{}:{}", file!(), line!()))
298    };
299    ($expr:expr, $context:expr) => {
300        $expr.unwrap_safe($context)
301    };
302}
303
304/// Convenience macro for safe unwrapping with location
305#[macro_export]
306macro_rules! unwrap_safe_at {
307    ($expr:expr, $context:expr) => {
308        $expr.unwrap_safe_at($context, &format!("{}:{}", file!(), line!()))
309    };
310}
311
312/// Convenience macro for safe unwrapping with default value
313#[macro_export]
314macro_rules! unwrap_or_default_safe {
315    ($expr:expr, $default:expr) => {
316        $expr.unwrap_or_default_safe($default, &format!("{}:{}", file!(), line!()))
317    };
318    ($expr:expr, $default:expr, $context:expr) => {
319        $expr.unwrap_or_default_safe($default, $context)
320    };
321}
322
323/// Convenience macro for safe unwrapping with default function
324#[macro_export]
325macro_rules! unwrap_or_else_safe {
326    ($expr:expr, $default_fn:expr) => {
327        $expr.unwrap_or_else_safe($default_fn, &format!("{}:{}", file!(), line!()))
328    };
329    ($expr:expr, $default_fn:expr, $context:expr) => {
330        $expr.unwrap_or_else_safe($default_fn, $context)
331    };
332}
333
334/// Convenience macro for trying to unwrap safely
335#[macro_export]
336macro_rules! try_unwrap_safe {
337    ($expr:expr) => {
338        $expr.try_unwrap_safe(&format!("{}:{}", file!(), line!()))
339    };
340    ($expr:expr, $context:expr) => {
341        $expr.try_unwrap_safe($context)
342    };
343}
344
345/// Statistics for unwrap operations
346#[derive(Debug, Clone, Default)]
347pub struct UnwrapStats {
348    pub successful_unwraps: u64,
349    pub failed_unwraps: u64,
350    pub default_value_uses: u64,
351    pub panic_preservations: u64,
352}
353
354impl UnwrapStats {
355    pub fn new() -> Self {
356        Self::default()
357    }
358
359    pub fn record_success(&mut self) {
360        self.successful_unwraps += 1;
361    }
362
363    pub fn record_failure(&mut self) {
364        self.failed_unwraps += 1;
365    }
366
367    pub fn record_default_use(&mut self) {
368        self.default_value_uses += 1;
369    }
370
371    pub fn record_panic_preservation(&mut self) {
372        self.panic_preservations += 1;
373    }
374
375    pub fn total_operations(&self) -> u64 {
376        self.successful_unwraps
377            + self.failed_unwraps
378            + self.default_value_uses
379            + self.panic_preservations
380    }
381
382    pub fn success_rate(&self) -> f64 {
383        let total = self.total_operations();
384        if total == 0 {
385            0.0
386        } else {
387            self.successful_unwraps as f64 / total as f64
388        }
389    }
390}
391
392use std::sync::{Mutex, OnceLock};
393
394/// Global unwrap statistics
395static GLOBAL_UNWRAP_STATS: OnceLock<Mutex<UnwrapStats>> = OnceLock::new();
396
397/// Get global unwrap statistics (read-only snapshot)
398pub fn get_unwrap_stats() -> UnwrapStats {
399    let stats_mutex = GLOBAL_UNWRAP_STATS.get_or_init(|| Mutex::new(UnwrapStats::new()));
400    match stats_mutex.safe_lock() {
401        Ok(stats) => stats.clone(),
402        Err(_) => UnwrapStats::new(),
403    }
404}
405
406/// Update global unwrap statistics with a closure
407pub fn update_unwrap_stats<F, R>(f: F) -> R
408where
409    F: FnOnce(&mut UnwrapStats) -> R,
410    R: Default,
411{
412    let stats_mutex = GLOBAL_UNWRAP_STATS.get_or_init(|| Mutex::new(UnwrapStats::new()));
413    match stats_mutex.try_lock() {
414        Ok(mut stats) => f(&mut stats),
415        Err(_) => R::default(),
416    }
417}
418
419/// Get mutable access to global unwrap statistics (for testing)
420#[cfg(test)]
421pub fn get_unwrap_stats_mut() -> Option<std::sync::MutexGuard<'static, UnwrapStats>> {
422    let stats_mutex = GLOBAL_UNWRAP_STATS.get_or_init(|| Mutex::new(UnwrapStats::new()));
423    stats_mutex.try_lock().ok()
424}
425
426#[cfg(test)]
427mod tests {
428    use super::*;
429
430    #[test]
431    fn test_unwrap_error_display() {
432        let none_error = UnwrapError::NoneValue {
433            context: "test context",
434            location: Some("test.rs:42"),
435            backtrace: Backtrace::capture(),
436        };
437
438        let display_str = format!("{none_error:?}");
439        assert!(display_str.contains("test context"));
440        assert!(display_str.contains("test.rs:42"));
441
442        let none_error_no_location = UnwrapError::NoneValue {
443            context: "test context",
444            location: None,
445            backtrace: Backtrace::capture(),
446        };
447
448        let display_str = format!("{none_error_no_location:?}");
449        assert!(display_str.contains("test context"));
450        assert!(!display_str.contains("test.rs:42"));
451    }
452
453    #[test]
454    fn test_unwrap_error_result_display() {
455        let source_error = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
456        let result_error = UnwrapError::ResultError {
457            source: Box::new(source_error),
458            context: "file operation",
459            location: Some("main.rs:10"),
460            backtrace: Backtrace::capture(),
461        };
462
463        let display_str = format!("{result_error}");
464        assert!(display_str.contains("file operation"));
465        assert!(display_str.contains("main.rs:10"));
466        assert!(display_str.contains("file not found"));
467    }
468
469    #[test]
470    fn test_option_try_unwrap_success() {
471        let option = Some(42);
472        let result = option.try_unwrap("test success");
473        assert!(result.is_ok());
474        assert_eq!(result.unwrap(), 42);
475    }
476
477    #[test]
478    fn test_option_try_unwrap_failure() {
479        let option: Option<i32> = None;
480        let result = option.try_unwrap("test failure");
481        assert!(result.is_err());
482
483        if let Err(UnwrapError::NoneValue { context, .. }) = result {
484            assert_eq!(context, "test failure");
485        } else {
486            panic!("Expected NoneValue error");
487        }
488    }
489
490    #[test]
491    fn test_option_try_unwrap_at() {
492        let option: Option<i32> = None;
493        let result = option.try_unwrap_at("test context", "test.rs:100");
494        assert!(result.is_err());
495
496        if let Err(UnwrapError::NoneValue {
497            context, location, ..
498        }) = result
499        {
500            assert_eq!(context, "test context");
501            assert_eq!(location, Some("test.rs:100"));
502        } else {
503            panic!("Expected NoneValue error with location");
504        }
505    }
506
507    #[test]
508    fn test_option_unwrap_or_default_safe() {
509        let some_option = Some(42);
510        let result = some_option.unwrap_or_default_safe(99, "test default");
511        assert_eq!(result, 42);
512
513        let none_option: Option<i32> = None;
514        let result = none_option.unwrap_or_default_safe(99, "test default");
515        assert_eq!(result, 99);
516    }
517
518    #[test]
519    fn test_option_unwrap_or_else_safe() {
520        let some_option = Some(42);
521        let result = some_option.unwrap_or_else_safe(|| 99, "test else");
522        assert_eq!(result, 42);
523
524        let none_option: Option<i32> = None;
525        let result = none_option.unwrap_or_else_safe(|| 99, "test else");
526        assert_eq!(result, 99);
527    }
528
529    #[test]
530    fn test_result_try_unwrap_success() {
531        let result: Result<i32, std::io::Error> = Ok(42);
532        let unwrap_result = result.try_unwrap("test success");
533        assert!(unwrap_result.is_ok());
534        assert_eq!(unwrap_result.unwrap(), 42);
535    }
536
537    #[test]
538    fn test_result_try_unwrap_failure() {
539        let result: Result<i32, std::io::Error> = Err(std::io::Error::other("test error"));
540        let unwrap_result = result.try_unwrap("test failure");
541        assert!(unwrap_result.is_err());
542
543        if let Err(UnwrapError::ResultError { context, .. }) = unwrap_result {
544            assert_eq!(context, "test failure");
545        } else {
546            panic!("Expected ResultError");
547        }
548    }
549
550    #[test]
551    fn test_result_try_unwrap_at() {
552        let result: Result<i32, std::io::Error> = Err(std::io::Error::other("test error"));
553        let unwrap_result = result.try_unwrap_at("test context", "main.rs:50");
554        assert!(unwrap_result.is_err());
555
556        if let Err(UnwrapError::ResultError {
557            context, location, ..
558        }) = unwrap_result
559        {
560            assert_eq!(context, "test context");
561            assert_eq!(location, Some("main.rs:50"));
562        } else {
563            panic!("Expected ResultError with location");
564        }
565    }
566
567    #[test]
568    fn test_result_unwrap_or_default_safe() {
569        let ok_result: Result<i32, std::io::Error> = Ok(42);
570        let result = ok_result.unwrap_or_default_safe(99, "test default");
571        assert_eq!(result, 42);
572
573        let err_result: Result<i32, std::io::Error> = Err(std::io::Error::other("error"));
574        let result = err_result.unwrap_or_default_safe(99, "test default");
575        assert_eq!(result, 99);
576    }
577
578    #[test]
579    fn test_result_unwrap_or_else_safe() {
580        let ok_result: Result<i32, std::io::Error> = Ok(42);
581        let result = ok_result.unwrap_or_else_safe(|| 99, "test else");
582        assert_eq!(result, 42);
583
584        let err_result: Result<i32, std::io::Error> = Err(std::io::Error::other("error"));
585        let result = err_result.unwrap_or_else_safe(|| 99, "test else");
586        assert_eq!(result, 99);
587    }
588
589    #[test]
590    fn test_unwrap_stats_creation() {
591        let stats = UnwrapStats::new();
592        assert_eq!(stats.successful_unwraps, 0);
593        assert_eq!(stats.failed_unwraps, 0);
594        assert_eq!(stats.default_value_uses, 0);
595        assert_eq!(stats.panic_preservations, 0);
596    }
597
598    #[test]
599    fn test_unwrap_stats_record_operations() {
600        let mut stats = UnwrapStats::new();
601
602        stats.record_success();
603        stats.record_failure();
604        stats.record_default_use();
605        stats.record_panic_preservation();
606
607        assert_eq!(stats.successful_unwraps, 1);
608        assert_eq!(stats.failed_unwraps, 1);
609        assert_eq!(stats.default_value_uses, 1);
610        assert_eq!(stats.panic_preservations, 1);
611    }
612
613    #[test]
614    fn test_unwrap_stats_total_operations() {
615        let mut stats = UnwrapStats::new();
616
617        stats.record_success();
618        stats.record_success();
619        stats.record_failure();
620        stats.record_default_use();
621
622        assert_eq!(stats.total_operations(), 4);
623    }
624
625    #[test]
626    fn test_unwrap_stats_success_rate() {
627        let mut stats = UnwrapStats::new();
628
629        // Test with no operations
630        assert_eq!(stats.success_rate(), 0.0);
631
632        // Test with some operations
633        stats.record_success();
634        stats.record_success();
635        stats.record_success();
636        stats.record_failure();
637
638        let expected_rate = 3.0 / 4.0;
639        assert!((stats.success_rate() - expected_rate).abs() < f64::EPSILON);
640    }
641
642    #[test]
643    fn test_update_unwrap_stats() {
644        let result = update_unwrap_stats(|stats| {
645            stats.record_success();
646            42
647        });
648        assert_eq!(result, 42);
649    }
650
651    #[test]
652    fn test_unwrap_error_backtrace() {
653        let error = UnwrapError::NoneValue {
654            context: "test",
655            location: None,
656            backtrace: Backtrace::capture(),
657        };
658
659        let _backtrace = error.backtrace();
660        // Just ensure we can access the backtrace without panic
661    }
662
663    #[test]
664    fn test_unwrap_error_source() {
665        let none_error = UnwrapError::NoneValue {
666            context: "test",
667            location: None,
668            backtrace: Backtrace::capture(),
669        };
670        assert!(none_error.source().is_none());
671
672        let source_error = std::io::Error::new(std::io::ErrorKind::NotFound, "test");
673        let result_error = UnwrapError::ResultError {
674            source: Box::new(source_error),
675            context: "test",
676            location: None,
677            backtrace: Backtrace::capture(),
678        };
679        assert!(result_error.source().is_some());
680    }
681
682    #[test]
683    fn test_option_try_unwrap_safe_deprecated() {
684        #[allow(deprecated)]
685        {
686            let some_option = Some(42);
687            let result = some_option.try_unwrap_safe("test");
688            assert!(result.is_ok());
689            assert_eq!(result.unwrap(), 42);
690
691            let none_option: Option<i32> = None;
692            let result = none_option.try_unwrap_safe("test");
693            assert!(result.is_err());
694        }
695    }
696
697    #[test]
698    fn test_result_try_unwrap_safe_deprecated() {
699        #[allow(deprecated)]
700        {
701            let ok_result: Result<i32, std::io::Error> = Ok(42);
702            let result = ok_result.try_unwrap_safe("test");
703            assert!(result.is_ok());
704            assert_eq!(result.unwrap(), 42);
705
706            let err_result: Result<i32, std::io::Error> = Err(std::io::Error::other("error"));
707            let result = err_result.try_unwrap_safe("test");
708            assert!(result.is_err());
709        }
710    }
711
712    #[test]
713    fn test_unwrap_stats_default() {
714        let stats = UnwrapStats::default();
715        assert_eq!(stats.successful_unwraps, 0);
716        assert_eq!(stats.failed_unwraps, 0);
717        assert_eq!(stats.default_value_uses, 0);
718        assert_eq!(stats.panic_preservations, 0);
719    }
720}