9.9 KiB
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
Errorobjects 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
ErrorandExpected<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(), orstd::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
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
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>:
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
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
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:
// 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
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)
Core Interop APIs
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
}
Exception Boundary Pattern
Use runCapturingExceptions to prevent exceptions from unwinding through ORC
runtime:
Error safeCallback(std::function<void()> UserCallback) {
return runCapturingExceptions([&]() {
UserCallback(); // User code might throw
});
}
ExceptionError Type
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
Best Practices
1. Consistent Return Types
// 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
// 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
// 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
// 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
// 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()andrunCapturingExceptions()are not availableExceptionErroris 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
ExceptionErrorpreserves 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.