| # ORC-RT Error Handling Policy |
| |
| ## Overview |
| |
| ORC-RT uses a structured error handling system based on the `orc_rt::Error` and |
| `orc_rt::Expected<T>` classes. This system provides type-safe error propagation |
| that works consistently across different compilation configurations (with or |
| without C++ exceptions). |
| |
| ## Fundamental Principles |
| |
| ### 1. Error Representation |
| - **Success**: Represented by `Error::success()` - a lightweight, zero-cost value |
| - **Failure**: Represented by `Error` objects containing typed error information |
| - **Values with Potential Errors**: Use `Expected<T>` to combine success values |
| with error handling |
| |
| ### 2. Error Categories |
| |
| **Recoverable Errors**: Environmental issues that can be handled gracefully |
| - File I/O failures, network issues, malformed input |
| - Use `Error` and `Expected<T>` return types |
| - Examples: `StringError`, `MyCustomError` |
| |
| **Programmatic Errors**: Violations of API contracts or program invariants |
| - Use assertions |
| - Should terminate the program immediately |
| - Examples: Unexpected null pointers, invalid enum values |
| |
| > **Important: Library Design Principles** |
| > |
| > **ORC-RT is a library and must never call terminating functions** like `exit()`, |
| > `abort()`, or `std::terminate()` in response to recoverable errors. Libraries |
| > should always return errors to their callers, allowing the application to decide |
| > how to handle them. |
| |
| ## Core Error Types |
| |
| ### Error |
| ```cpp |
| namespace orc_rt { |
| class Error { |
| public: |
| // Create success value |
| static Error success(); |
| |
| // Check for failure |
| explicit operator bool(); // true = failure, false = success |
| |
| // Type checking |
| template<typename ErrT> bool isA() const; |
| |
| // Exception interop (when exceptions enabled) |
| void throwOnFailure(); |
| }; |
| } |
| ``` |
| |
| ### Expected<T> |
| ```cpp |
| template<typename T> |
| class Expected { |
| public: |
| // Construction |
| Expected(T Value); |
| Expected(Error Err); |
| |
| // Check for success |
| explicit operator bool(); // true = success, false = failure |
| |
| // Access value (success case) |
| T& operator*(); |
| T* operator->(); |
| |
| // Extract error (failure case) |
| Error takeError(); |
| }; |
| ``` |
| |
| ## Defining Custom Error Types |
| |
| Use `ErrorExtends<ThisT, ParentT>`: |
| |
| ```cpp |
| class CustomError : public ErrorExtends<CustomError, ErrorInfoBase> { |
| public: |
| CustomError(std::string Message) : Message(std::move(Message)) {} |
| |
| std::string toString() const noexcept override { |
| return "CustomError: " + Message; |
| } |
| |
| const std::string& getMessage() const { return Message; } |
| |
| private: |
| std::string Message; |
| }; |
| |
| // Usage |
| Error doSomething() { |
| if (/* error condition */) |
| return make_error<CustomError>("Something went wrong"); |
| return Error::success(); |
| } |
| ``` |
| |
| ## Error Handling Patterns |
| |
| ### Basic Error Propagation |
| ```cpp |
| Error processFile(StringRef Path) { |
| if (auto Err = openFile(Path)) |
| return Err; // Propagate error |
| |
| if (auto Err = validateFormat(Path)) |
| return Err; |
| |
| return Error::success(); |
| } |
| ``` |
| |
| ### Expected<T> Usage |
| ```cpp |
| Expected<Data> loadData(StringRef Path) { |
| auto FileOrErr = openFile(Path); |
| if (auto Err = FileOrErr.takeError()) |
| return Err; |
| |
| return parseData(*FileOrErr); |
| } |
| |
| // Alternative form |
| Expected<Data> loadData(StringRef Path) { |
| if (auto FileOrErr = openFile(Path)) { |
| auto& File = *FileOrErr; |
| return parseData(File); |
| } else { |
| return FileOrErr.takeError(); |
| } |
| } |
| ``` |
| |
| ### Error Consumption |
| Error values are most commonly passed up the stack (having interrupted whatever |
| operation raised the error). Eventually errors must be consumed (failure to do |
| so will trigger an assertion). Errors may be consumed using one of the |
| following patterns: |
| |
| ```cpp |
| // 1. Handle specific error types |
| handleAllErrors(mayFail(), |
| [](const CustomError& CE) { |
| // Handle CustomError |
| }, |
| [](ErrorInfoBase& EIB) { |
| // Handle any other error |
| } |
| ); |
| |
| // 2. Report errors to the Session: |
| // This should be done for Errors that cannot be passed further up the stack |
| // (e.g. the have reached the root of some thread) |
| { |
| if (auto Err = mayFail()) |
| S.reportError(std::move(Err)); |
| // thread ends here. |
| } |
| |
| // 3. Convert to string and log: |
| // This option may be used in contexts where a reference to the Session is |
| // not available. |
| logError(toString(mayFail())); |
| |
| // 4 Consume and ignore (explicit) |
| // Errors can be explicitly consumed in cases where a failure is known to be |
| // benign. |
| if (auto Err = tryPopulateFromOnDiskCache(...)) |
| consumeError(std::move(Err)); // Error indicates cache unavailable. Benign. |
| ``` |
| |
| ## Exception Interoperability |
| |
| When `ORC_RT_ENABLE_EXCEPTIONS=On`, ORC-RT provides bidirectional conversion |
| between errors and exceptions. |
| |
| > **Important: Exception Usage Policy** |
| > |
| > **ORC-RT should not use exceptions internally.** All ORC-RT functions should |
| > use `Error` and `Expected<T>` return types for error reporting. Exceptions |
| > should only be used at the boundaries: |
| > |
| > 1. **Converting external exceptions to errors** when calling |
| > exception-throwing external code |
| > 2. **Converting errors to exceptions** when returning from ORC-RT to |
| > exception-expecting client code |
| > |
| > This policy ensures that: |
| > - ORC-RT works consistently whether exceptions are enabled or disabled |
| > - Error handling behavior is predictable and doesn't depend on exception |
| > propagation |
| > - The library remains compatible with codebases that disable exceptions |
| > (most LLVM projects) |
| |
| ### Core Interop APIs |
| |
| **`runCapturingExceptions`**: Converts exceptions to errors |
| ```cpp |
| // Return type depends on callback: |
| // void → Error |
| // Error → Error |
| // Expected<T> → Expected<T> |
| // T → Expected<T> |
| |
| auto Result = runCapturingExceptions([]() { |
| return riskyOperation(); // might throw |
| }); |
| ``` |
| |
| **`Error::throwOnFailure`**: Converts errors to exceptions |
| ```cpp |
| try { |
| auto Err = orcOperation(); |
| Err.throwOnFailure(); // Throws if Err represents failure |
| } catch (std::unique_ptr<StringError>& E) { |
| // Catch specific error types |
| } catch (std::unique_ptr<ErrorInfoBase>& E) { |
| // Catch any ORC error |
| } catch (...) { |
| // Catch other exceptions |
| } |
| ``` |
| |
| ### Exception Boundary Pattern |
| |
| Use `runCapturingExceptions` to prevent exceptions from unwinding through ORC |
| runtime: |
| |
| ```cpp |
| Error safeCallback(std::function<void()> UserCallback) { |
| return runCapturingExceptions([&]() { |
| UserCallback(); // User code might throw |
| }); |
| } |
| ``` |
| |
| ### ExceptionError Type |
| |
| `ExceptionError` preserves C++ exceptions as `Error` values: |
| |
| ```cpp |
| auto Err = runCapturingExceptions([]() { |
| throw std::runtime_error("C++ exception"); |
| }); |
| |
| // Err contains an ExceptionError wrapping the std::runtime_error |
| assert(Err.isA<ExceptionError>()); |
| |
| // Can be rethrown with original type preserved |
| Err.throwOnFailure(); // Rethrows std::runtime_error |
| ``` |
| |
| ## Best Practices |
| |
| ### 1. Consistent Return Types |
| ```cpp |
| // Good: Consistent error handling |
| Expected<Data> loadData(StringRef Path); |
| Error saveData(const Data& D, StringRef Path); |
| |
| // Bad: Mixed error handling |
| Data loadDataOrDie(StringRef Path); // Inconsistent |
| bool saveData(const Data& D, StringRef Path, std::string* Error); // C-style |
| ``` |
| |
| ### 2. Meaningful Error Messages |
| ```cpp |
| // Good: Descriptive, actionable |
| return make_error<StringError>( |
| "Failed to parse config file '" + Path + "': invalid JSON at line " + |
| std::to_string(LineNum) |
| ); |
| |
| // Bad: Vague |
| return make_error<StringError>("Parse error"); |
| ``` |
| |
| ### 3. Appropriate Error Granularity |
| ```cpp |
| // Good: Specific error types enable targeted handling |
| class FileNotFoundError : public ErrorExtends<FileNotFoundError, ErrorInfoBase> { |
| // ... specific to missing files |
| }; |
| |
| class PermissionError : public ErrorExtends<PermissionError, ErrorInfoBase> { |
| // ... specific to permission issues |
| }; |
| |
| // Usage allows specific handling |
| handleAllErrors(openFile(Path), |
| [](const FileNotFoundError& E) { /* try alternative locations */ }, |
| [](const PermissionError& E) { /* request elevated access */ }, |
| [](ErrorInfoBase& E) { /* generic fallback */ } |
| ); |
| ``` |
| |
| ### 4. Exception Safety in Mixed Environments |
| ```cpp |
| // Safe pattern: Isolate exception-throwing code |
| Error integrateWithExceptionThrowingLibrary() { |
| return runCapturingExceptions([&]() { |
| externalLibrary.riskyOperation(); |
| return Error::success(); |
| }); |
| } |
| |
| // Unsafe: Exceptions can unwind through Error values |
| Error unsafeIntegration() { |
| if (auto Err = orcOperation()) { |
| log("Failed"); // might throw! |
| return Err; // ASSERTION FAILURE if log() throws |
| } |
| return Error::success(); |
| } |
| ``` |
| |
| ### 5. Performance Considerations |
| - `Error::success()` is zero-cost |
| - Avoid creating error objects in hot paths when possible |
| - Use early returns to minimize deep nesting |
| |
| ```cpp |
| // Good: Early return, minimal overhead |
| Error fastPath(bool condition) { |
| if (ORC_RT_LIKELY(condition)) |
| return Error::success(); |
| |
| return make_error<StringError>("Rare error case"); |
| } |
| ``` |
| |
| ## Configuration Impact |
| |
| ### Exception Disabled (`ORC_RT_ENABLE_EXCEPTIONS=Off`) |
| - `throwOnFailure()` and `runCapturingExceptions()` are not available |
| - `ExceptionError` is not available |
| - All error handling uses `Error`/`Expected<T>` exclusively |
| - Compatible with LLVM projects that disable exceptions |
| |
| ### Exceptions Enabled (`ORC_RT_ENABLE_EXCEPTIONS=On`) |
| - Full interoperability between errors and exceptions |
| - Safe integration with exception-throwing external libraries |
| - `ExceptionError` preserves exception values across Error boundaries |
| - Compatible with standard C++ codebases using exceptions |
| |
| ## Summary |
| |
| ORC-RT's error handling system provides: |
| |
| - **Type Safety**: Errors have specific types that can be handled appropriately |
| - **Performance**: Zero-cost success path, efficient error propagation |
| - **Flexibility**: Works with or without C++ exceptions |
| - **Interoperability**: Supports integration with exception-throwing code |
| - **Consistency**: Uniform error handling across the entire codebase |
| |
| By following these guidelines, ORC-RT maintains robust error handling that works |
| across diverse integration environments while providing clear, actionable error |
| information to users and developers. |