1use std::convert::Infallible;
2use std::error::Error as StdError;
3use std::fmt;
4use std::io;
5use std::ops::Deref;
6use std::panic;
7use std::ptr::{self, addr_of_mut};
8
9use ndarray::ShapeError;
10
11use crate::error_codes::{MajorErrorCode, MinorErrorCode};
12
13#[cfg(not(feature = "1.10.0"))]
14use hdf5_sys::h5::hssize_t;
15use hdf5_sys::h5e::{
16 H5E_DEFAULT, H5E_WALK_DOWNWARD, H5E_auto2_t, H5E_error2_t, H5Eget_current_stack, H5Eget_msg,
17 H5Eprint2, H5Eset_auto2, H5Ewalk2,
18};
19
20use crate::internal_prelude::*;
21
22pub(crate) unsafe fn silence_errors_no_sync(silence: bool) {
27 let h5eprint: Option<unsafe extern "C" fn(hid_t, *mut libc::FILE) -> herr_t> =
30 Some(H5Eprint2 as _);
31 let h5eprint: H5E_auto2_t = std::mem::transmute(h5eprint);
32 H5Eset_auto2(H5E_DEFAULT, if silence { None } else { h5eprint }, ptr::null_mut());
33}
34
35pub fn silence_errors(silence: bool) {
37 h5lock!(silence_errors_no_sync(silence));
38}
39
40#[repr(transparent)]
42#[derive(Clone)]
43pub struct ErrorStack(Handle);
44
45impl ObjectClass for ErrorStack {
46 const NAME: &'static str = "errorstack";
47 const VALID_TYPES: &'static [H5I_type_t] = &[H5I_ERROR_STACK];
48
49 fn from_handle(handle: Handle) -> Self {
50 Self(handle)
51 }
52
53 fn handle(&self) -> &Handle {
54 &self.0
55 }
56
57 }
59
60impl ErrorStack {
61 pub(crate) fn from_current() -> Result<Self> {
62 let stack_id = h5lock!(H5Eget_current_stack());
63 Handle::try_new(stack_id).map(Self)
64 }
65
66 pub fn expand(self) -> Result<ExpandedErrorStack> {
70 struct CallbackData {
71 stack: ExpandedErrorStack,
72 err: Option<Error>,
73 }
74 unsafe extern "C" fn callback(
75 _: c_uint, err_desc: *const H5E_error2_t, data: *mut c_void,
76 ) -> herr_t {
77 panic::catch_unwind(|| unsafe {
78 let data = &mut *(data.cast::<CallbackData>());
79 if data.err.is_some() {
80 return 0;
81 }
82 let closure = |e: H5E_error2_t| -> Result<ErrorFrame> {
83 let (desc, func) = (string_from_cstr(e.desc), string_from_cstr(e.func_name));
84 let major = get_h5_str(|m, s| H5Eget_msg(e.maj_num, ptr::null_mut(), m, s))?;
85 let minor = get_h5_str(|m, s| H5Eget_msg(e.min_num, ptr::null_mut(), m, s))?;
86 let major_code = MajorErrorCode::from_id(e.maj_num);
87 let minor_code = MinorErrorCode::from_id(e.min_num);
88 let line = e.line.try_into().unwrap();
89 let file_name = string_from_cstr(e.file_name);
90 Ok(ErrorFrame::new(
91 &desc, &func, &major, &minor, major_code, minor_code, line, file_name,
92 ))
93 };
94 match closure(*err_desc) {
95 Ok(frame) => {
96 data.stack.push(frame);
97 }
98 Err(err) => {
99 data.err = Some(err);
100 }
101 }
102 0
103 })
104 .unwrap_or(-1)
105 }
106
107 let mut data = CallbackData { stack: ExpandedErrorStack::new(), err: None };
108 let data_ptr: *mut c_void = addr_of_mut!(data).cast::<c_void>();
109
110 let stack_id = self.handle().id();
111 h5lock!({
112 H5Ewalk2(stack_id, H5E_WALK_DOWNWARD, Some(callback), data_ptr);
113 });
114
115 data.err.map_or(Ok(data.stack), Err)
116 }
117}
118
119#[derive(Clone, Debug)]
121pub struct ErrorFrame {
122 desc: String,
123 func: String,
124 major: String,
125 minor: String,
126 description: String,
127 major_code: MajorErrorCode,
128 minor_code: MinorErrorCode,
129 line: usize,
130 file_name: String,
131}
132
133impl ErrorFrame {
134 pub(crate) fn new(
135 desc: &str, func: &str, major: &str, minor: &str, major_code: MajorErrorCode,
136 minor_code: MinorErrorCode, line: usize, file_name: String,
137 ) -> Self {
138 Self {
139 desc: desc.into(),
140 func: func.into(),
141 major: major.into(),
142 minor: minor.into(),
143 description: format!("{func}(): {desc}"),
144 major_code,
145 minor_code,
146 line,
147 file_name: file_name.into(),
148 }
149 }
150
151 pub fn desc(&self) -> &str {
153 self.desc.as_ref()
154 }
155
156 pub fn func(&self) -> &str {
158 self.func.as_ref()
159 }
160
161 pub fn major_code(&self) -> MajorErrorCode {
163 self.major_code
164 }
165
166 pub fn minor_code(&self) -> MinorErrorCode {
168 self.minor_code
169 }
170
171 pub fn description(&self) -> &str {
173 self.description.as_ref()
174 }
175
176 pub fn line(&self) -> usize {
178 self.line
179 }
180
181 pub fn file_name(&self) -> &str {
183 self.file_name.as_ref()
184 }
185
186 pub fn detail(&self) -> Option<String> {
188 Some(format!("Error in {}(): {} [{}: {}]", self.func, self.desc, self.major, self.minor))
189 }
190}
191
192#[derive(Clone, Debug)]
194pub struct ExpandedErrorStack {
195 frames: Vec<ErrorFrame>,
196 description: Option<String>,
197}
198
199impl Deref for ExpandedErrorStack {
200 type Target = [ErrorFrame];
201
202 fn deref(&self) -> &Self::Target {
203 &self.frames
204 }
205}
206
207impl Default for ExpandedErrorStack {
208 fn default() -> Self {
209 Self::new()
210 }
211}
212
213impl ExpandedErrorStack {
214 pub(crate) fn new() -> Self {
215 Self { frames: Vec::new(), description: None }
216 }
217
218 pub(crate) fn push(&mut self, frame: ErrorFrame) {
219 self.frames.push(frame);
220
221 self.description = match self.frames[..] {
222 [] => unreachable!(),
223 [ref first] => Some(first.description().to_owned()),
224 [ref first, .., ref last] => Some(format!("{}: {}", first.description(), last.desc())),
225 }
226 }
227
228 pub fn top(&self) -> Option<&ErrorFrame> {
230 self.first()
231 }
232
233 pub fn major_codes(&self) -> impl DoubleEndedIterator<Item = MajorErrorCode> + '_ {
235 self.frames.iter().map(ErrorFrame::major_code)
236 }
237
238 pub fn minor_codes(&self) -> impl DoubleEndedIterator<Item = MinorErrorCode> + '_ {
240 self.frames.iter().map(ErrorFrame::minor_code)
241 }
242
243 pub fn contains_major(&self, code: MajorErrorCode) -> bool {
245 self.major_codes().any(|c| c == code)
246 }
247
248 pub fn contains_minor(&self, code: MinorErrorCode) -> bool {
253 self.minor_codes().any(|c| c == code)
254 }
255
256 pub fn description(&self) -> &str {
258 match self.description {
259 None => "unknown library error",
260 Some(ref desc) => desc.as_ref(),
261 }
262 }
263
264 pub fn detail(&self) -> Option<String> {
266 self.top().and_then(ErrorFrame::detail)
267 }
268}
269
270#[derive(Clone)]
272pub enum Error {
273 HDF5(ErrorStack),
275 Internal(String),
277}
278
279pub type Result<T, E = Error> = ::std::result::Result<T, E>;
282
283impl Error {
284 pub fn query() -> Result<Self> {
287 if let Ok(stack) = ErrorStack::from_current() {
288 Ok(Self::HDF5(stack))
289 } else {
290 Err(Self::Internal("Could not get errorstack".to_owned()))
291 }
292 }
293
294 pub fn stack(&self) -> Option<ExpandedErrorStack> {
296 match *self {
297 Self::Internal(_) => None,
298 Self::HDF5(ref stack) => stack.clone().expand().ok(),
299 }
300 }
301
302 pub fn contains_major(&self, code: MajorErrorCode) -> bool {
305 self.stack().is_some_and(|s| s.contains_major(code))
306 }
307
308 pub fn contains_minor(&self, code: MinorErrorCode) -> bool {
322 self.stack().is_some_and(|s| s.contains_minor(code))
323 }
324}
325
326impl From<&str> for Error {
327 fn from(desc: &str) -> Self {
328 Self::Internal(desc.into())
329 }
330}
331
332impl From<String> for Error {
333 fn from(desc: String) -> Self {
334 Self::Internal(desc)
335 }
336}
337
338impl From<Infallible> for Error {
339 fn from(_: Infallible) -> Self {
340 unreachable!("Infallible error can never be constructed")
341 }
342}
343
344impl fmt::Debug for Error {
345 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
346 match *self {
347 Self::Internal(ref desc) => f.write_str(desc),
348 Self::HDF5(ref stack) => match stack.clone().expand() {
349 Ok(stack) => f.write_str(stack.description()),
350 Err(_) => f.write_str("Could not get error stack"),
351 },
352 }
353 }
354}
355
356impl fmt::Display for Error {
357 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
358 match *self {
359 Self::Internal(ref desc) => f.write_str(desc),
360 Self::HDF5(ref stack) => match stack.clone().expand() {
361 Ok(stack) => f.write_str(stack.description()),
362 Err(_) => f.write_str("Could not get error stack"),
363 },
364 }
365 }
366}
367
368impl StdError for Error {}
369
370impl From<ShapeError> for Error {
371 fn from(err: ShapeError) -> Self {
372 format!("shape error: {err}").into()
373 }
374}
375
376impl From<Error> for io::Error {
377 fn from(err: Error) -> Self {
378 Self::new(io::ErrorKind::Other, err)
379 }
380}
381
382pub fn h5check<T: H5ErrorCode>(value: T) -> Result<T> {
383 H5ErrorCode::h5check(value)
384}
385
386#[allow(unused)]
387pub fn is_err_code<T: H5ErrorCode>(value: T) -> bool {
388 H5ErrorCode::is_err_code(value)
389}
390
391pub trait H5ErrorCode: Copy {
392 fn is_err_code(value: Self) -> bool;
393
394 fn h5check(value: Self) -> Result<Self> {
395 if Self::is_err_code(value) { Err(Error::query().unwrap_or_else(|e| e)) } else { Ok(value) }
396 }
397}
398
399impl H5ErrorCode for hsize_t {
400 fn is_err_code(value: Self) -> bool {
401 value == 0
402 }
403}
404
405impl H5ErrorCode for herr_t {
406 fn is_err_code(value: Self) -> bool {
407 value < 0
408 }
409}
410
411#[cfg(feature = "1.10.0")]
412impl H5ErrorCode for hid_t {
413 fn is_err_code(value: Self) -> bool {
414 value < 0
415 }
416}
417
418#[cfg(not(feature = "1.10.0"))]
419impl H5ErrorCode for hssize_t {
420 fn is_err_code(value: Self) -> bool {
421 value < 0
422 }
423}
424
425impl H5ErrorCode for libc::ssize_t {
426 fn is_err_code(value: Self) -> bool {
427 value < 0
428 }
429}
430
431#[cfg(test)]
432pub mod tests {
433 use hdf5_sys::h5p::{H5Pclose, H5Pcreate};
434
435 use crate::error_codes::{MAJOR_CODES, MINOR_CODES};
436 use crate::globals::{H5E_CANTOPENFILE, H5E_FILE, H5P_ROOT};
437 use crate::internal_prelude::*;
438 use crate::test::with_tmp_path;
439 use crate::{MajorErrorCode, MinorErrorCode};
440 use hdf5_sys::h5e::H5Eget_msg;
441 use std::fs;
442 use std::ptr;
443
444 use super::ExpandedErrorStack;
445
446 #[test]
452 pub fn test_descriptions_match_the_linked_library() {
453 assert!(!MAJOR_CODES.is_empty() && !MINOR_CODES.is_empty());
454 for (&id, &code) in MAJOR_CODES.iter() {
455 assert_eq!(scrub(code.description().unwrap()), scrub(&msg_of(id)), "{code:?}");
456 }
457 for (&id, &code) in MINOR_CODES.iter() {
458 #[cfg(all(feature = "1.10.5", not(feature = "1.12.0")))]
462 if id == *crate::globals::H5E_LOGFAIL {
463 assert_eq!(code, MinorErrorCode::Logging);
464 assert_eq!(msg_of(id), "old H5E_LOGGING_g (maintained for binary compatibility)");
465 continue;
466 }
467 assert_eq!(scrub(code.description().unwrap()), scrub(&msg_of(id)), "{code:?}");
468 }
469 }
470
471 fn msg_of(id: hid_t) -> String {
472 h5lock!(get_h5_str(|m, s| H5Eget_msg(id, ptr::null_mut(), m, s))).unwrap()
473 }
474
475 fn scrub(s: &str) -> String {
477 s.replace("atom", "ID")
478 .replace("accessability", "accessibility")
479 .replace("accessibilty", "accessibility")
480 }
481
482 #[test]
486 pub fn test_frame_codes_agree_with_frame_text() {
487 with_tmp_path(|path| {
488 fs::write(&path, b"garbage data").unwrap();
489 let err = File::open(&path).unwrap_err();
490 let stack = err.stack().unwrap();
491 assert!(stack.len() >= 3, "expected a multi-frame stack, got {}", stack.len());
492
493 for frame in stack.iter() {
494 let major_code_desc = frame.major_code().description().unwrap();
496 let minor_code_desc = frame.minor_code().description().unwrap();
497 let maj_min_desc = format!("[{major_code_desc}: {minor_code_desc}]");
498 let detail = frame.detail().unwrap();
499 assert!(
500 scrub(&detail).ends_with(&scrub(&maj_min_desc)),
501 "{detail:?} does not end with {maj_min_desc:?}"
502 );
503 }
504 assert_eq!(stack.major_codes().count(), stack.len());
505 assert_eq!(stack.minor_codes().count(), stack.len());
506 });
507 }
508
509 #[test]
513 pub fn test_symbol_mapping_is_unambiguous() {
514 for (&id, &code) in MAJOR_CODES.iter() {
515 assert!(MajorErrorCode::all().contains(&code), "{code:?} is not a declared variant");
516 assert_ne!(id, H5I_INVALID_HID);
517 }
518 for (&id, &code) in MINOR_CODES.iter() {
519 assert!(MinorErrorCode::all().contains(&code), "{code:?} is not a declared variant");
520 assert_ne!(id, H5I_INVALID_HID);
521 }
522 assert_eq!(MajorErrorCode::from_id(*H5E_FILE), MajorErrorCode::File);
524 assert_eq!(MinorErrorCode::from_id(*H5E_CANTOPENFILE), MinorErrorCode::CantOpenFile);
525 }
526
527 #[test]
528 pub fn test_unknown_error_code_is_preserved() {
529 let minor = MinorErrorCode::from_id(H5I_INVALID_HID);
531 assert_eq!(minor, MinorErrorCode::Other(H5I_INVALID_HID));
532 assert_eq!(minor.name(), None);
533 assert_eq!(minor.description(), None);
534 assert_eq!(minor.to_string(), format!("unknown error code ({H5I_INVALID_HID})"));
535
536 let major = MajorErrorCode::from_id(H5I_INVALID_HID);
537 assert_eq!(major, MajorErrorCode::Other(H5I_INVALID_HID));
538 assert_eq!(major.name(), None);
539 assert_eq!(major.description(), None);
540 assert_eq!(major.to_string(), format!("unknown error code ({H5I_INVALID_HID})"));
541
542 assert_eq!(MinorErrorCode::from_id(*H5E_FILE), MinorErrorCode::Other(*H5E_FILE));
544 assert_eq!(
545 MajorErrorCode::from_id(*H5E_CANTOPENFILE),
546 MajorErrorCode::Other(*H5E_CANTOPENFILE)
547 );
548 }
549
550 #[test]
551 pub fn test_error_codes_on_stack() {
552 let err = h5lock!({
553 let plist_id = H5Pcreate(*H5P_ROOT);
554 H5Pclose(plist_id);
555 H5Pclose(plist_id);
556 Error::query()
557 })
558 .unwrap();
559 let stack = err.stack().unwrap();
560 let top = stack.top().unwrap();
561 assert_eq!(top.major_code(), MajorErrorCode::PropertyList);
562 assert_eq!(top.major_code().name(), Some("H5E_PLIST"));
563 assert_eq!(top.minor_code(), MinorErrorCode::CantFree);
564 assert!(err.contains_major(MajorErrorCode::PropertyList));
565 assert!(!err.contains_major(MajorErrorCode::File));
566 assert!(stack.major_codes().all(|c| !matches!(c, MajorErrorCode::Other(_))));
568 assert!(stack.minor_codes().all(|c| !matches!(c, MinorErrorCode::Other(_))));
569 }
570
571 #[test]
573 pub fn test_file_error_codes_are_distinguishable() {
574 with_tmp_path(|path| {
576 fs::write(&path, b"garbage data").unwrap();
577 let err = File::open_rw(&path).unwrap_err();
578 assert!(err.contains_minor(MinorErrorCode::NotHdf5), "{err:?}");
579 assert!(err.contains_major(MajorErrorCode::File));
580 #[cfg(feature = "1.12.0")]
582 assert!(err.contains_major(MajorErrorCode::VirtualObjectLayer), "{err:?}");
583 let stack = err.stack().unwrap();
584 assert!(stack.major_codes().all(|c| !matches!(c, MajorErrorCode::Other(_))), "{err:?}");
585 assert!(stack.minor_codes().all(|c| !matches!(c, MinorErrorCode::Other(_))), "{err:?}");
586 });
587
588 with_tmp_path(|path| {
590 let err = File::open_rw(&path).unwrap_err();
591 assert!(err.contains_minor(MinorErrorCode::CantOpenFile), "{err:?}");
592 assert!(!err.contains_minor(MinorErrorCode::NotHdf5), "{err:?}");
593 });
594
595 with_tmp_path(|path| {
600 File::create(&path).unwrap();
601 let err = File::create_excl(&path).unwrap_err();
602 let minors: Vec<_> = err.stack().unwrap().minor_codes().collect();
603 assert!(err.contains_major(MajorErrorCode::File), "minors={minors:?}: {err}");
604 assert!(!err.contains_minor(MinorErrorCode::NotHdf5), "minors={minors:?}: {err}");
605 });
606 }
607
608 #[test]
610 pub fn test_internal_error_has_no_codes() {
611 let err = Error::Internal("nope".into());
612 assert!(err.stack().is_none());
613 assert!(!err.contains_minor(MinorErrorCode::NotHdf5));
614 assert!(!err.contains_major(MajorErrorCode::File));
615 }
616
617 #[test]
618 pub fn test_error_code_display() {
619 assert_eq!(MinorErrorCode::NotHdf5.to_string(), "Not an HDF5 file");
620 assert_eq!(MajorErrorCode::File.to_string(), "File accessibility");
621 }
622
623 #[test]
624 pub fn test_error_stack() {
625 let stack = h5lock!({
626 let plist_id = H5Pcreate(*H5P_ROOT);
627 H5Pclose(plist_id);
628 Error::query()
629 })
630 .unwrap();
631 let stack = match stack {
632 Error::HDF5(stack) => stack,
633 Error::Internal(internal) => panic!("Expected hdf5 error, not {}", internal),
634 }
635 .expand()
636 .unwrap();
637 assert!(stack.is_empty());
638
639 let stack = h5lock!({
640 let plist_id = H5Pcreate(*H5P_ROOT);
641 H5Pclose(plist_id);
642 H5Pclose(plist_id);
643 Error::query()
644 })
645 .unwrap();
646 let stack = match stack {
647 Error::HDF5(stack) => stack,
648 Error::Internal(internal) => panic!("Expected hdf5 error, not {}", internal),
649 }
650 .expand()
651 .unwrap();
652 assert_eq!(stack.description(), "H5Pclose(): can't close: can't locate ID");
653 assert_eq!(
654 &stack.detail().unwrap(),
655 "Error in H5Pclose(): can't close [Property lists: Unable to free object]"
656 );
657
658 assert!(stack.len() >= 2 && stack.len() <= 4); assert!(!stack.is_empty());
660
661 assert_eq!(stack[0].description(), "H5Pclose(): can't close");
662 assert_eq!(
663 &stack[0].detail().unwrap(),
664 "Error in H5Pclose(): can't close \
665 [Property lists: Unable to free object]"
666 );
667
668 #[cfg(not(feature = "1.14.0"))]
669 {
670 assert_eq!(stack.last().unwrap().description(), "H5I_dec_ref(): can't locate ID");
671 assert_eq!(
672 &stack.last().unwrap().detail().unwrap(),
673 "Error in H5I_dec_ref(): can't locate ID \
674 [Object atom: Unable to find atom information (already closed?)]"
675 );
676 }
677 #[cfg(feature = "1.14.0")]
678 {
679 assert_eq!(stack.last().unwrap().description(), "H5I__dec_ref(): can't locate ID");
680 assert_eq!(
681 &stack.last().unwrap().detail().unwrap(),
682 "Error in H5I__dec_ref(): can't locate ID \
683 [Object ID: Unable to find ID information (already closed?)]"
684 );
685 }
686
687 let empty_stack = ExpandedErrorStack::new();
688 assert!(empty_stack.is_empty());
689 assert_eq!(empty_stack.len(), 0);
690 }
691
692 #[test]
693 pub fn test_h5call() {
694 let result_no_error = h5call!({
695 let plist_id = H5Pcreate(*H5P_ROOT);
696 H5Pclose(plist_id)
697 });
698 assert!(result_no_error.is_ok());
699
700 let result_error = h5call!({
701 let plist_id = H5Pcreate(*H5P_ROOT);
702 H5Pclose(plist_id);
703 H5Pclose(plist_id)
704 });
705 assert!(result_error.is_err());
706 }
707
708 #[test]
709 pub fn test_h5try() {
710 fn f1() -> Result<herr_t> {
711 h5try!(H5Pcreate(*H5P_ROOT));
712 Ok(100)
713 }
714
715 assert_eq!(f1().unwrap(), 100);
716
717 fn f2() -> Result<herr_t> {
718 h5try!(H5Pcreate(123456));
719 Ok(100)
720 }
721
722 assert!(f2().is_err());
723 }
724}