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).
Error::success() - a lightweight, zero-cost valueError objects containing typed error informationExpected<T> to combine success values with error handlingRecoverable Errors: Environmental issues that can be handled gracefully
Error and Expected<T> return typesStringError, MyCustomErrorProgrammatic Errors: Violations of API contracts or program invariants
Important: Library Design Principles
ORC-RT is a library and must never call terminating functions like
exit(),abort(), orstd::terminate()in response to recoverable errors. Libraries should always return errors to their callers, allowing the application to decide how to handle them.
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(); }; }
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(); };
Use ErrorExtends<ThisT, ParentT>:
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 processFile(StringRef Path) { if (auto Err = openFile(Path)) return Err; // Propagate error if (auto Err = validateFormat(Path)) return Err; return Error::success(); }
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 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:
// 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.
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
ErrorandExpected<T>return types for error reporting. Exceptions should only be used at the boundaries:
- Converting external exceptions to errors when calling exception-throwing external code
- 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)
runCapturingExceptions: Converts exceptions to errors
// 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
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 }
Use runCapturingExceptions to prevent exceptions from unwinding through ORC runtime:
Error safeCallback(std::function<void()> UserCallback) { return runCapturingExceptions([&]() { UserCallback(); // User code might throw }); }
ExceptionError preserves C++ exceptions as Error values:
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
// 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
// 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");
// 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 */ } );
// 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(); }
Error::success() is zero-cost// Good: Early return, minimal overhead Error fastPath(bool condition) { if (ORC_RT_LIKELY(condition)) return Error::success(); return make_error<StringError>("Rare error case"); }
ORC_RT_ENABLE_EXCEPTIONS=Off)throwOnFailure() and runCapturingExceptions() are not availableExceptionError is not availableError/Expected<T> exclusivelyORC_RT_ENABLE_EXCEPTIONS=On)ExceptionError preserves exception values across Error boundariesORC-RT's error handling system provides:
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.