# ORC-RT Error Handling Policy ## Overview ORC-RT uses a structured error handling system based on the `orc_rt::Error` and `orc_rt::Expected` 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` 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` 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 bool isA() const; // Exception interop (when exceptions enabled) void throwOnFailure(); }; } ``` ### Expected ```cpp template 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`: ```cpp class CustomError : public ErrorExtends { 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("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 Usage ```cpp Expected loadData(StringRef Path) { auto FileOrErr = openFile(Path); if (auto Err = FileOrErr.takeError()) return Err; return parseData(*FileOrErr); } // Alternative form Expected 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` 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 → Expected // T → Expected 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& E) { // Catch specific error types } catch (std::unique_ptr& 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 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()); // 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 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( "Failed to parse config file '" + Path + "': invalid JSON at line " + std::to_string(LineNum) ); // Bad: Vague return make_error("Parse error"); ``` ### 3. Appropriate Error Granularity ```cpp // Good: Specific error types enable targeted handling class FileNotFoundError : public ErrorExtends { // ... specific to missing files }; class PermissionError : public ErrorExtends { // ... 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("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` 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.