svMultiPhysics
Loading...
Searching...
No Matches
Exception.h
1// SPDX-FileCopyrightText: Copyright (c) Stanford University, The Regents of the
2// University of California, and others. SPDX-License-Identifier: BSD-3-Clause
3
4#ifndef SVMP_CORE_EXCEPTION_H
5#define SVMP_CORE_EXCEPTION_H
6
7#include <cstdint>
8#include <cstdlib>
9#include <exception>
10#include <iostream>
11#include <sstream>
12#include <string>
13#include <string_view>
14#include <type_traits>
15#include <utility>
16#include <vector>
17
18#if !defined(NDEBUG) || defined(DEBUG) || defined(_DEBUG)
19#define SVMP_EXCEPTION_DEBUG_MODE 1
20#else
21#define SVMP_EXCEPTION_DEBUG_MODE 0
22#endif
23
24namespace svmp {
25
26enum class StatusCode : std::uint8_t {
27 Success = 0,
28 InvalidArgument,
29 InvalidState,
30 ParseError,
31 IOError,
32 ResourceExhausted,
33 DependencyError,
34 MPIError,
35 NotImplemented,
36 UnsupportedOperation,
37 InternalError,
38 Unknown = 255
39};
40
41inline const char *status_code_to_string(StatusCode status) noexcept {
42 switch (status) {
43 case StatusCode::Success:
44 return "Success";
45 case StatusCode::InvalidArgument:
46 return "Invalid argument";
47 case StatusCode::InvalidState:
48 return "Invalid state";
49 case StatusCode::ParseError:
50 return "Parse error";
51 case StatusCode::IOError:
52 return "I/O error";
53 case StatusCode::ResourceExhausted:
54 return "Resource exhausted";
55 case StatusCode::DependencyError:
56 return "Dependency error";
57 case StatusCode::MPIError:
58 return "MPI error";
59 case StatusCode::NotImplemented:
60 return "Not implemented";
61 case StatusCode::UnsupportedOperation:
62 return "Unsupported operation";
63 case StatusCode::InternalError:
64 return "Internal error";
65 default:
66 return "Unknown error";
67 }
68}
69
71 const char *file;
72 int line;
73 const char *function;
74};
75
76class StackTraceFrame final {
77public:
78 StackTraceFrame() = default;
79
80 StackTraceFrame(std::string symbol, std::string module, std::string file,
81 int line, std::uintptr_t address) noexcept
82 : symbol_(std::move(symbol)), module_(std::move(module)),
83 file_(std::move(file)), line_(line), address_(address) {}
84
85 const std::string &symbol() const noexcept { return symbol_; }
86 const std::string &module() const noexcept { return module_; }
87 const std::string &file() const noexcept { return file_; }
88 int line() const noexcept { return line_; }
89 std::uintptr_t address() const noexcept { return address_; }
90
91private:
92 std::string symbol_;
93 std::string module_;
94 std::string file_;
95 int line_ = 0;
96 std::uintptr_t address_ = 0;
97};
98
99class StackTrace final {
100public:
101 bool empty() const noexcept { return frames_.empty(); }
102 std::size_t size() const noexcept { return frames_.size(); }
103 const std::vector<StackTraceFrame> &frames() const noexcept {
104 return frames_;
105 }
106 void add_frame(StackTraceFrame frame) { frames_.push_back(std::move(frame)); }
107
108private:
109 std::vector<StackTraceFrame> frames_;
110};
111
113 bool has_stack_trace;
114 bool has_symbol_resolution;
115 bool has_demangling;
116};
117
118class PlatformSupport final {
119public:
120 static PlatformCapabilities capabilities() noexcept;
121 static int query_mpi_rank() noexcept;
122 static StackTrace capture_stack_trace();
123 static std::string demangle_symbol(const char *symbol);
124 static void finalize_mpi_if_needed() noexcept;
125 static void abort_mpi_if_needed(int exit_code) noexcept;
126};
127} // namespace svmp
128
129#define SVMP_CORE_EXCEPTION_INCLUDE_PLATFORM_SUPPORT
130#include "PlatformSupport.inl"
131#undef SVMP_CORE_EXCEPTION_INCLUDE_PLATFORM_SUPPORT
132
133namespace svmp {
134
135class ExceptionContext final {
136public:
137 StatusCode status_code() const noexcept { return status_code_; }
138 const std::string &file() const noexcept { return file_; }
139 int line() const noexcept { return line_; }
140 const std::string &function() const noexcept { return function_; }
141 int mpi_rank() const noexcept { return mpi_rank_; }
142 const StackTrace &stack_trace() const noexcept { return stack_trace_; }
143
144 void set_status_code(StatusCode status_code) noexcept {
145 status_code_ = status_code;
146 }
147
148 void set_source_location(const char *file, int line, const char *function) {
149 file_ = (file == nullptr) ? std::string() : std::string(file);
150 line_ = line;
151 function_ = (function == nullptr) ? std::string() : std::string(function);
152 }
153
154 void set_mpi_rank(int mpi_rank) noexcept { mpi_rank_ = mpi_rank; }
155 void set_stack_trace(StackTrace stack_trace) {
156 stack_trace_ = std::move(stack_trace);
157 }
158
159private:
160 StatusCode status_code_ = StatusCode::Unknown;
161 std::string file_;
162 int line_ = 0;
163 std::string function_;
164 int mpi_rank_ = -1;
165 StackTrace stack_trace_;
166};
167
168namespace ExceptionFormatter {
169
170inline std::string format(const ExceptionContext &context,
171 const std::string &message,
172 std::string_view subsystem_label = "Exception") {
173 if (subsystem_label.empty()) {
174 subsystem_label = "Exception";
175 }
176
177 std::ostringstream oss;
178
179 oss << "[" << subsystem_label << "] "
180 << status_code_to_string(context.status_code());
181 if (context.mpi_rank() >= 0) {
182 oss << " (Rank " << context.mpi_rank() << ")";
183 }
184 oss << "\n";
185
186 if (!context.file().empty()) {
187 oss << " Location: " << context.file() << ":" << context.line();
188 if (!context.function().empty()) {
189 oss << " in " << context.function() << "()";
190 }
191 oss << "\n";
192 }
193
194 oss << " Message: " << message << "\n";
195
196 if (!context.stack_trace().empty()) {
197 oss << " Stack trace:\n";
198 std::size_t frame_index = 0;
199 for (const auto &frame : context.stack_trace().frames()) {
200 oss << " #" << frame_index++ << " ";
201 if (!frame.symbol().empty()) {
202 oss << frame.symbol();
203 } else {
204 std::ostringstream address;
205 address << "0x" << std::hex << frame.address();
206 oss << address.str();
207 }
208
209 if (!frame.module().empty()) {
210 oss << " [" << frame.module() << "]";
211 }
212
213 if (!frame.file().empty()) {
214 oss << " (" << frame.file();
215 if (frame.line() > 0) {
216 oss << ":" << frame.line();
217 }
218 oss << ")";
219 }
220
221 oss << "\n";
222 }
223 }
224
225 return oss.str();
226}
227
228} // namespace ExceptionFormatter
229
230class ExceptionBase;
231
232namespace ExceptionRuntime {
233
234inline int query_mpi_rank() noexcept {
235 return PlatformSupport::query_mpi_rank();
236}
237
238inline StackTrace capture_stack_trace() {
239 return PlatformSupport::capture_stack_trace();
240}
241
242inline void finalize_mpi_if_needed() noexcept {
243 PlatformSupport::finalize_mpi_if_needed();
244}
245
246void install_terminate_handler();
247
248inline void report_unhandled_exception(const std::exception &exception) {
249 std::cerr << exception.what() << std::endl;
250}
251
252inline void abort_mpi_if_needed(int exit_code) noexcept {
253 PlatformSupport::abort_mpi_if_needed(exit_code);
254}
255
256} // namespace ExceptionRuntime
257
258class ExceptionBase : public std::exception {
259public:
260 const char *what() const noexcept override { return what_.c_str(); }
261 StatusCode status_code() const noexcept { return context_.status_code(); }
262 const std::string &message() const noexcept { return message_; }
263 const ExceptionContext &context() const noexcept { return context_; }
264
265 void add_context(const std::string &context) {
266 message_ = context + "\n -> " + message_;
267 rebuild_what();
268 }
269
270 /// @brief Record the originating source location and refresh what().
271 ///
272 /// @details Called by raise() after construction, so exception constructors
273 /// do not need to accept (and forward) the file/line/function themselves.
274 void set_source_location(const SourceLocation &location) {
275 context_.set_source_location(location.file, location.line,
276 location.function);
277 rebuild_what();
278 }
279
280 virtual ~ExceptionBase() noexcept = default;
281
282protected:
283 ExceptionBase(std::string message, StatusCode status,
284 std::string_view subsystem_label, const char *file = "",
285 int line = 0, const char *function = "")
286 : message_(std::move(message)),
287 subsystem_label_(subsystem_label.empty() ? std::string_view("Exception")
288 : subsystem_label) {
289 context_.set_status_code(status);
290 context_.set_source_location(file, line, function);
291 context_.set_mpi_rank(ExceptionRuntime::query_mpi_rank());
292#if SVMP_EXCEPTION_DEBUG_MODE
293 context_.set_stack_trace(ExceptionRuntime::capture_stack_trace());
294#endif
295 rebuild_what();
296 }
297
298 void rebuild_what() {
299 what_ = ExceptionFormatter::format(context_, message_, subsystem_label_);
300 }
301
302 std::string message_;
303 ExceptionContext context_;
304 std::string_view subsystem_label_;
305 std::string what_;
306};
307
309public:
310 CoreException(const std::string &message,
311 StatusCode status = StatusCode::Unknown, const char *file = "",
312 int line = 0, const char *function = "")
313 : ExceptionBase(message, status, "Core Exception", file, line, function) {
314 }
315};
316
317/**
318 * @brief Define a simple, message-only exception type in one line.
319 *
320 * @details Expands to a class @p Name deriving from @p Base with a single
321 * `explicit Name(const std::string& message)` constructor that records @p
322 * Status. Use it for exceptions that carry only a message; write the class by
323 * hand when it needs extra structured context (members and accessors). The
324 * source location is stamped by raise(), so no file/line/function constructor
325 * is needed.
326 *
327 * @p Base must be an ExceptionBase-derived type whose constructor accepts
328 * `(const std::string&, StatusCode)` (CoreException, FEException, and the
329 * subsystem bases do).
330 *
331 * @code
332 * SVMP_DEFINE_EXCEPTION(ParseException, CoreException, StatusCode::ParseError);
333 * @endcode
334 */
335#define SVMP_DEFINE_EXCEPTION(Name, Base, Status) \
336 class Name : public Base { \
337 public: \
338 explicit Name(const std::string &message) : Base(message, (Status)) {} \
339 }
340
341/// @brief A parsing or input-format error.
342SVMP_DEFINE_EXCEPTION(ParseException, CoreException, StatusCode::ParseError);
343
344/// @brief A required dependency is missing or failed to load.
345SVMP_DEFINE_EXCEPTION(DependencyException, CoreException,
346 StatusCode::DependencyError);
347
348/// @brief A requested operation or feature is not implemented.
349///
350/// @details The default exception raised by not_implemented().
351SVMP_DEFINE_EXCEPTION(NotImplementedException, CoreException,
352 StatusCode::NotImplemented);
353
354/// @brief An index is outside its valid range.
355///
356/// @details The default exception raised by check_index(); the status code is
357/// InvalidArgument because an out-of-range index is a caller error.
358SVMP_DEFINE_EXCEPTION(IndexOutOfRangeException, CoreException,
359 StatusCode::InvalidArgument);
360
361/// @brief Internal error exception.
362///
363/// @details An exception raising the status code InternalError, used for
364/// unexpected conditions that indicate a bug in the code.
365SVMP_DEFINE_EXCEPTION(InternalErrorException, CoreException,
366 StatusCode::InternalError);
367
368/// @brief An exception raised when a file cannot be opened.
369///
370/// @details Raises a message in the format "Could not open file <file_name>",
371/// with the file name provided as constructor argument.
373public:
374 FileNotFoundException(const std::string &file_name, const char *file = "",
375 int line = 0, const char *function = "")
376 : CoreException("Could not open file " + file_name, StatusCode::IOError,
377 file, line, function) {}
378};
379
380/// @brief An exception raised when a file cannot be parsed or has an invalid
381/// format.
382///
383/// @details Raises a message in the format "Error parsing file <file_name>.
384/// <message>", with the file name and a descriptive message provided as
385/// constructor arguments.
387public:
388 FileFormatException(const std::string &file_name, const std::string &message,
389 const char *file = "", int line = 0,
390 const char *function = "")
391 : CoreException("Error parsing file " + file_name + ". " + message,
392 StatusCode::IOError, file, line, function) {}
393};
394
395inline void ExceptionRuntime::install_terminate_handler() {
396 std::set_terminate([]() {
397 try {
398 const std::exception_ptr current = std::current_exception();
399 if (current != nullptr) {
400 std::rethrow_exception(current);
401 }
402 } catch (const std::exception &exception) {
403 ExceptionRuntime::report_unhandled_exception(exception);
404 } catch (...) {
405 std::cerr << "[Unhandled Exception] Unknown non-std exception"
406 << std::endl;
407 }
408
409 ExceptionRuntime::abort_mpi_if_needed(EXIT_FAILURE);
410 std::abort();
411 });
412}
413
414/**
415 * @brief A diagnostic message bundled with the source location where it was
416 * written.
417 *
418 * @details The core helpers (raise(), check(), throw_if(), check_not_null())
419 * take a Diagnostic in place of an explicit source location. Its
420 * file/line/function arguments default to the compiler builtins
421 * __builtin_FILE()/__builtin_LINE()/
422 * __builtin_FUNCTION(), which capture the caller's location, so a string
423 * literal or std::string passed at the call site is implicitly wrapped into a
424 * Diagnostic that records exactly where the call appears -- callers pass no
425 * explicit location:
426 * @code
427 * svmp::check<MyException>(ptr != nullptr, "pointer must not be null");
428 * @endcode
429 */
431public:
432 /**
433 * @brief Wrap a message, capturing the caller's source location by default.
434 * @param message The diagnostic message.
435 * @param file Source file; defaults to the caller's via __builtin_FILE().
436 * @param line Source line; defaults to the caller's via __builtin_LINE().
437 * @param function Function; defaults to the caller's via
438 * __builtin_FUNCTION().
439 */
440 Diagnostic(const char *message, const char *file = __builtin_FILE(),
441 int line = __builtin_LINE(),
442 const char *function = __builtin_FUNCTION())
443 : message_(message), location_{file, line, function} {}
444 /**
445 * @brief Wrap a message, capturing the caller's source location by default.
446 * @param message The diagnostic message.
447 * @param file Source file; defaults to the caller's via __builtin_FILE().
448 * @param line Source line; defaults to the caller's via __builtin_LINE().
449 * @param function Function; defaults to the caller's via
450 * __builtin_FUNCTION().
451 */
452 Diagnostic(std::string message, const char *file = __builtin_FILE(),
453 int line = __builtin_LINE(),
454 const char *function = __builtin_FUNCTION())
455 : message_(std::move(message)), location_{file, line, function} {}
456
457 /**
458 * @brief The diagnostic message.
459 * @return The stored message.
460 */
461 const std::string &message() const noexcept { return message_; }
462 /**
463 * @brief The source location captured when the Diagnostic was constructed.
464 * @return The stored source location.
465 */
466 const SourceLocation &location() const noexcept { return location_; }
467
468private:
469 std::string message_;
470 SourceLocation location_;
471};
472
473/**
474 * @brief Construct @p ExceptionT from the diagnostic message and @p args, stamp
475 * the source location, and throw it.
476 *
477 * @details @p diagnostic carries the message and the source location captured
478 * at the call site; @p args are forwarded to the exception constructor after
479 * the message. The location is recorded via
480 * ExceptionBase::set_source_location(), so exception types never need a
481 * file/line/function constructor -- a `(message)` (plus any structured-context)
482 * constructor is enough.
483 */
484template <class ExceptionT, class... Args>
485[[noreturn]] void raise(Diagnostic diagnostic, Args &&...args) {
486 static_assert(
487 std::is_base_of_v<ExceptionBase, ExceptionT>,
488 "raise<>() requires an svmp::ExceptionBase-derived exception type");
489 ExceptionT exception(diagnostic.message(), std::forward<Args>(args)...);
490 exception.set_source_location(diagnostic.location());
491 throw exception;
492}
493
494/**
495 * @brief Raise @p ExceptionT when @p condition is false (a required condition).
496 *
497 * @details The general success-condition check, used for argument, state, and
498 * invariant validation: `check<E>(ptr != nullptr, "...")`. throw_if() is the
499 * logical inverse -- it raises when its condition is true.
500 */
501template <class ExceptionT, class... Args>
502void check(bool condition, Diagnostic diagnostic, Args &&...args) {
503 if (!condition) {
504 raise<ExceptionT>(std::move(diagnostic), std::forward<Args>(args)...);
505 }
506}
507
508/**
509 * @brief Raise @p ExceptionT when @p ptr is null.
510 */
511template <class ExceptionT, class PointerT, class... Args>
512void check_not_null(PointerT ptr, Diagnostic diagnostic, Args &&...args) {
513 if (ptr == nullptr) {
514 raise<ExceptionT>(std::move(diagnostic), std::forward<Args>(args)...);
515 }
516}
517
518/**
519 * @brief Raise @p ExceptionT when @p condition is true.
520 *
521 * @details The logical inverse of check(): check() raises when its condition is
522 * false (a required condition); throw_if() raises when its condition is true (a
523 * failure condition). The two are not interchangeable.
524 */
525template <class ExceptionT, class... Args>
526void throw_if(bool condition, Diagnostic diagnostic, Args &&...args) {
527 if (condition) {
528 raise<ExceptionT>(std::move(diagnostic), std::forward<Args>(args)...);
529 }
530}
531
532/**
533 * @brief Raise an exception when @p index is outside [0, @p size).
534 *
535 * @details @p ExceptionT defaults to IndexOutOfRangeException; supply a
536 * different type only when a subsystem needs its own exception. The bounds
537 * message and the source location are generated automatically.
538 */
539template <class ExceptionT = IndexOutOfRangeException, class IndexT,
540 class SizeT>
541void check_index(IndexT index, SizeT size, const char *file = __builtin_FILE(),
542 int line = __builtin_LINE(),
543 const char *function = __builtin_FUNCTION()) {
544 const long long index_value = static_cast<long long>(index);
545 const long long size_value = static_cast<long long>(size);
546 check<ExceptionT>(index_value >= 0 && index_value < size_value,
547 Diagnostic("Index " + std::to_string(index_value) +
548 " out of bounds [0, " +
549 std::to_string(size_value) + ")",
550 file, line, function));
551}
552
553/**
554 * @brief Raise an exception reporting an unimplemented feature, with a message.
555 *
556 * @details @p ExceptionT defaults to NotImplementedException, so most call
557 * sites pass only a message:
558 * @code
559 * svmp::not_implemented("GPU assembly is not supported");
560 * @endcode
561 * Pass a different exception type explicitly only when a subsystem needs one.
562 */
563template <class ExceptionT = NotImplementedException>
564[[noreturn]] void not_implemented(std::string message,
565 const char *file = __builtin_FILE(),
566 int line = __builtin_LINE(),
567 const char *function = __builtin_FUNCTION()) {
568 raise<ExceptionT>(Diagnostic(std::move(message), file, line, function));
569}
570
571} // namespace svmp
572
573#if SVMP_EXCEPTION_DEBUG_MODE
574#define SVMP_DEBUG_CHECK(ExceptionT, condition, ...) \
575 do { \
576 if (!(condition)) { \
577 ::svmp::raise<ExceptionT>(__VA_ARGS__); \
578 } \
579 } while (false)
580#else
581#define SVMP_DEBUG_CHECK(ExceptionT, condition, ...) \
582 do { \
583 } while (false)
584#endif
585
586#endif // SVMP_CORE_EXCEPTION_H
Definition Exception.h:308
A required dependency is missing or failed to load.
Definition Exception.h:346
A diagnostic message bundled with the source location where it was written.
Definition Exception.h:430
const SourceLocation & location() const noexcept
The source location captured when the Diagnostic was constructed.
Definition Exception.h:466
Diagnostic(const char *message, const char *file=__builtin_FILE(), int line=__builtin_LINE(), const char *function=__builtin_FUNCTION())
Wrap a message, capturing the caller's source location by default.
Definition Exception.h:440
const std::string & message() const noexcept
The diagnostic message.
Definition Exception.h:461
Diagnostic(std::string message, const char *file=__builtin_FILE(), int line=__builtin_LINE(), const char *function=__builtin_FUNCTION())
Wrap a message, capturing the caller's source location by default.
Definition Exception.h:452
Definition Exception.h:258
void set_source_location(const SourceLocation &location)
Record the originating source location and refresh what().
Definition Exception.h:274
Definition Exception.h:135
An exception raised when a file cannot be parsed or has an invalid format.
Definition Exception.h:386
An exception raised when a file cannot be opened.
Definition Exception.h:372
An index is outside its valid range.
Definition Exception.h:359
Internal error exception.
Definition Exception.h:366
A requested operation or feature is not implemented.
Definition Exception.h:352
A parsing or input-format error.
Definition Exception.h:342
Definition Exception.h:118
Definition Exception.h:76
Definition Exception.h:99
Definition Exception.h:112
Definition Exception.h:70