368 lines
9.9 KiB
Markdown
368 lines
9.9 KiB
Markdown
# 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.
|