Skip to content

Commit cf074bc

Browse files
committed
Split std::exception stuff from internal/wexcept into internal/cxxerr
1 parent 3aa2b55 commit cf074bc

File tree

6 files changed

+308
-292
lines changed

6 files changed

+308
-292
lines changed

internal/cxxerr/exception.go

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
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

Comments
 (0)