blob: 420bdc5b09a8db002a76fd0e1fb1cf7ec9838857 [file] [log] [blame] [view] [edit]
# 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.