[orc-rt] Add Error-handling documentation. (#177547)
This commit is contained in:
parent
cbb2aa1704
commit
90bece366d
367
orc-rt/docs/ErrorHandling.md
Normal file
367
orc-rt/docs/ErrorHandling.md
Normal file
@ -0,0 +1,367 @@
|
||||
# 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.
|
||||
Loading…
x
Reference in New Issue
Block a user