|
| 1 | +// Package cxxerr wraps C++ exceptions. |
| 2 | +package cxxerr |
| 3 | + |
| 4 | +import ( |
| 5 | + "cmp" |
| 6 | + "strings" |
| 7 | +) |
| 8 | + |
| 9 | +// Exception represents a C++ exception. |
| 10 | +type Exception struct { |
| 11 | + typ string |
| 12 | + what string |
| 13 | + std Std |
| 14 | +} |
| 15 | + |
| 16 | +// Wrap wraps a C++ exception with the specified mangled type, unmangled std |
| 17 | +// name (empty if not a std exception, std:: prefix is required), and optional |
| 18 | +// message. If typ isn't provided but std is, it will be set automatically. |
| 19 | +func Wrap(typ, std, what string) error { |
| 20 | + exc := new(Exception) |
| 21 | + exc.typ = typ |
| 22 | + if typ := simpleDemangleClass(typ); typ != "" { |
| 23 | + exc.typ = typ |
| 24 | + } |
| 25 | + if std, ok := strings.CutPrefix(std, stdExceptionPrefix); ok { |
| 26 | + exc.std = Std(std) |
| 27 | + } |
| 28 | + if exc.typ == "" && exc.std != "" { |
| 29 | + exc.typ = exc.std.Error() |
| 30 | + } |
| 31 | + exc.what = what |
| 32 | + return exc |
| 33 | +} |
| 34 | + |
| 35 | +// Error creates a new C++ std exception with the specified method. |
| 36 | +func Error(std Std, what string) error { |
| 37 | + if strings.HasPrefix(string(std), stdExceptionPrefix) { |
| 38 | + panic("invalid StdException") |
| 39 | + } |
| 40 | + return &Exception{ |
| 41 | + typ: std.Error(), |
| 42 | + what: what, |
| 43 | + std: cmp.Or(std, stdExceptionBase), |
| 44 | + } |
| 45 | +} |
| 46 | + |
| 47 | +// Type returns the C++ type name of the exception. To get the first known |
| 48 | +// standard library type, use [Unwrap] or [errors.As] with [Std] as the |
| 49 | +// target. |
| 50 | +func (e *Exception) Type() string { |
| 51 | + return e.typ |
| 52 | +} |
| 53 | + |
| 54 | +// What returns the error message. |
| 55 | +func (e *Exception) What() string { |
| 56 | + return e.what |
| 57 | +} |
| 58 | + |
| 59 | +// Error returns a string describing the error message, including all parent |
| 60 | +// error types. |
| 61 | +func (e *Exception) Error() string { |
| 62 | + var b strings.Builder |
| 63 | + b.WriteString(cmp.Or(e.Type(), stdExceptionBase.Error())) |
| 64 | + if u, ok := e.Unwrap().(*Exception); ok && u.std != "" && u.std != stdExceptionBase { |
| 65 | + b.WriteString(" (") |
| 66 | + b.WriteString(u.std.Parents()) |
| 67 | + b.WriteString(")") |
| 68 | + } |
| 69 | + if s := e.What(); s != "" { |
| 70 | + b.WriteString(": ") |
| 71 | + b.WriteString(e.What()) |
| 72 | + } |
| 73 | + return b.String() |
| 74 | +} |
| 75 | + |
| 76 | +// Unwrap returns a copy of e with the type set to the parent stdlib class, if |
| 77 | +// any. |
| 78 | +func (e *Exception) Unwrap() error { |
| 79 | + std := e.std |
| 80 | + if std == "" { |
| 81 | + return nil |
| 82 | + } |
| 83 | + if e.typ == std.Error() { |
| 84 | + var ok bool |
| 85 | + if std, ok = std.Unwrap().(Std); !ok { |
| 86 | + return nil |
| 87 | + } |
| 88 | + } |
| 89 | + return Error(std, e.what) |
| 90 | +} |
| 91 | + |
| 92 | +// As converts an Exception to a StdException. |
| 93 | +func (e *Exception) As(target any) bool { |
| 94 | + if e.std != "" { |
| 95 | + if target, ok := target.(*Std); ok { |
| 96 | + *target = e.std |
| 97 | + return true |
| 98 | + } |
| 99 | + } |
| 100 | + return false |
| 101 | +} |
| 102 | + |
| 103 | +// Is allows [errors.Is] to work with [Std] values. |
| 104 | +func (e *Exception) Is(target error) bool { |
| 105 | + if e.std != "" { |
| 106 | + if target, ok := target.(Std); ok { |
| 107 | + return e.std.Is(target) // we don't need to check the parents ourselves since we implement Unwrap for each one |
| 108 | + } |
| 109 | + } |
| 110 | + return false |
| 111 | +} |
| 112 | + |
| 113 | +// Std represents a standard library exception type. |
| 114 | +// |
| 115 | +// https://en.cppreference.com/w/cpp/error/exception.html |
| 116 | +type Std string |
| 117 | + |
| 118 | +const ( |
| 119 | + stdExceptionPrefix = "std::" |
| 120 | + stdExceptionBase Std = "exception" |
| 121 | +) |
| 122 | + |
| 123 | +const ( |
| 124 | + LogicError Std = "logic_error" |
| 125 | + RuntimeError Std = "runtime_error" |
| 126 | + BadTypeid Std = "bad_typeid" |
| 127 | + BadCast Std = "bad_cast" |
| 128 | + BadAlloc Std = "bad_alloc" |
| 129 | + BadException Std = "bad_exception" |
| 130 | + BadVariantAccess Std = "bad_variant_access" |
| 131 | + |
| 132 | + InvalidArgument Std = "invalid_argument" |
| 133 | + DomainError Std = "domain_error" |
| 134 | + LengthError Std = "length_error" |
| 135 | + OutOfRange Std = "out_of_range" |
| 136 | + FutureError Std = "future_error" |
| 137 | + |
| 138 | + RangeError Std = "range_error" |
| 139 | + OverflowError Std = "overflow_error" |
| 140 | + UnderflowError Std = "underflow_error" |
| 141 | + RegexError Std = "regex_error" |
| 142 | + SystemError Std = "system_error" |
| 143 | + NonexistentLocalTime Std = "nonexistent_local_time" |
| 144 | + AmbiguousLocalTime Std = "ambiguous_local_time" |
| 145 | + FormatError Std = "format_error" |
| 146 | + |
| 147 | + IostreamFailure Std = "ios_base::failure" |
| 148 | + FilesystemError Std = "filesystem::filesystem_error" |
| 149 | + |
| 150 | + BadAnyCast Std = "bad_any_cast" |
| 151 | + |
| 152 | + BadArrayNewLength Std = "bad_array_new_length" |
| 153 | +) |
| 154 | + |
| 155 | +func (std Std) Error() string { |
| 156 | + if std == "" { |
| 157 | + return stdExceptionBase.Error() |
| 158 | + } |
| 159 | + return stdExceptionPrefix + string(std) |
| 160 | +} |
| 161 | + |
| 162 | +// Parents returns a space-separated string of std and all parent types. |
| 163 | +func (std Std) Parents() string { |
| 164 | + var b strings.Builder |
| 165 | + for x, ok := std, true; ok && x != "" && x != stdExceptionBase; x, ok = x.Unwrap().(Std) { |
| 166 | + if b.Len() != 0 { |
| 167 | + b.WriteString(" ") |
| 168 | + } |
| 169 | + b.WriteString(stdExceptionPrefix) |
| 170 | + b.WriteString(string(x)) |
| 171 | + } |
| 172 | + return b.String() |
| 173 | +} |
| 174 | + |
| 175 | +// Unwrap gets the parent class of std, if any. |
| 176 | +func (std Std) Unwrap() error { |
| 177 | + if std != "" && std != stdExceptionBase { |
| 178 | + switch std { |
| 179 | + case LogicError, RuntimeError, BadTypeid, BadCast, BadAlloc, BadException, BadVariantAccess: |
| 180 | + return stdExceptionBase |
| 181 | + case InvalidArgument, DomainError, LengthError, OutOfRange, FutureError: |
| 182 | + return LogicError |
| 183 | + case RangeError, OverflowError, UnderflowError, RegexError, SystemError, NonexistentLocalTime, AmbiguousLocalTime, FormatError: |
| 184 | + return RuntimeError |
| 185 | + case IostreamFailure, FilesystemError: |
| 186 | + return SystemError |
| 187 | + case BadAnyCast: |
| 188 | + return BadCast |
| 189 | + case BadArrayNewLength: |
| 190 | + return BadAlloc |
| 191 | + } |
| 192 | + } |
| 193 | + return nil |
| 194 | +} |
| 195 | + |
| 196 | +// Is returns true if std is the same error as target. Use [errors.Is] to check |
| 197 | +// the entire hierachy. |
| 198 | +func (std Std) Is(target error) bool { |
| 199 | + if target, ok := target.(Std); ok && std != "" { |
| 200 | + return std == target || target == "" || target == stdExceptionBase |
| 201 | + } |
| 202 | + return false |
| 203 | +} |
| 204 | + |
| 205 | +// simpleDemangleClass demangles a small subset of C++ class names (for the |
| 206 | +// Itanium C++ ABI). If invalid or unsupported, an empty string is returned. |
| 207 | +// Notably, it does not support templates. |
| 208 | +func simpleDemangleClass(s string) string { |
| 209 | + var b strings.Builder |
| 210 | + s, _ = strings.CutPrefix(s, "_Z") |
| 211 | + s, nested := strings.CutPrefix(s, "N") |
| 212 | + s, std := strings.CutPrefix(s, "St") |
| 213 | + if std { |
| 214 | + b.WriteString("std") |
| 215 | + } |
| 216 | + for s != "" { |
| 217 | + var n int |
| 218 | + for s != "" && !(n == 0 && s[0] == '0') && n <= len(s) && '0' <= s[0] && s[0] <= '9' { |
| 219 | + n *= 10 |
| 220 | + n += int(s[0] - '0') |
| 221 | + s = s[1:] |
| 222 | + } |
| 223 | + if n == 0 || n > len(s) { |
| 224 | + return "" |
| 225 | + } |
| 226 | + if b.Len() != 0 { |
| 227 | + b.WriteString("::") |
| 228 | + } |
| 229 | + b.WriteString(s[:n]) |
| 230 | + s = s[n:] |
| 231 | + if !nested && s != "" { |
| 232 | + break |
| 233 | + } |
| 234 | + if !nested || s == "E" { |
| 235 | + return b.String() |
| 236 | + } |
| 237 | + } |
| 238 | + return "" |
| 239 | +} |
0 commit comments