Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/src/lib/libexceptions.js
6162 views
1
/**
2
* @license
3
* Copyright 2010 The Emscripten Authors
4
* SPDX-License-Identifier: MIT
5
*/
6
7
var LibraryExceptions = {
8
#if !WASM_EXCEPTIONS
9
$uncaughtExceptionCount: '0',
10
$exceptionLast: '0',
11
$exceptionCaught: ' []',
12
13
// This class is the exception metadata which is prepended to each thrown object (in WASM memory).
14
// It is allocated in one block along with a thrown object in __cxa_allocate_exception and freed
15
// in ___cxa_free_exception. It roughly corresponds to __cxa_exception structure in libcxxabi. The
16
// class itself is just a native pointer wrapper, and contains all the necessary accessors for the
17
// fields in the native structure.
18
// TODO: Unfortunately this approach still cannot be considered thread-safe because single
19
// exception object can be simultaneously thrown in several threads and its state (except
20
// reference counter) is not protected from that. Also protection is not enough, separate state
21
// should be allocated. libcxxabi has concept of dependent exception which is used for that
22
// purpose, it references the primary exception.
23
$ExceptionInfo: class {
24
// excPtr - Thrown object pointer to wrap. Metadata pointer is calculated from it.
25
constructor(excPtr) {
26
this.excPtr = excPtr;
27
this.ptr = excPtr - {{{ C_STRUCTS.__cxa_exception.__size__ }}};
28
}
29
30
set_type(type) {
31
{{{ makeSetValue('this.ptr', C_STRUCTS.__cxa_exception.exceptionType, 'type', '*') }}};
32
}
33
34
get_type() {
35
return {{{ makeGetValue('this.ptr', C_STRUCTS.__cxa_exception.exceptionType, '*') }}};
36
}
37
38
set_destructor(destructor) {
39
{{{ makeSetValue('this.ptr', C_STRUCTS.__cxa_exception.exceptionDestructor, 'destructor', '*') }}};
40
}
41
42
get_destructor() {
43
return {{{ makeGetValue('this.ptr', C_STRUCTS.__cxa_exception.exceptionDestructor, '*') }}};
44
}
45
46
set_caught(caught) {
47
caught = caught ? 1 : 0;
48
{{{ makeSetValue('this.ptr', C_STRUCTS.__cxa_exception.caught, 'caught', 'i8') }}};
49
}
50
51
get_caught() {
52
return {{{ makeGetValue('this.ptr', C_STRUCTS.__cxa_exception.caught, 'i8') }}} != 0;
53
}
54
55
set_rethrown(rethrown) {
56
rethrown = rethrown ? 1 : 0;
57
{{{ makeSetValue('this.ptr', C_STRUCTS.__cxa_exception.rethrown, 'rethrown', 'i8') }}};
58
}
59
60
get_rethrown() {
61
return {{{ makeGetValue('this.ptr', C_STRUCTS.__cxa_exception.rethrown, 'i8') }}} != 0;
62
}
63
64
// Initialize native structure fields. Should be called once after allocated.
65
init(type, destructor) {
66
#if EXCEPTION_DEBUG
67
dbg('ExceptionInfo init: ' + [type, destructor]);
68
#endif
69
this.set_adjusted_ptr(0);
70
this.set_type(type);
71
this.set_destructor(destructor);
72
}
73
74
set_adjusted_ptr(adjustedPtr) {
75
{{{ makeSetValue('this.ptr', C_STRUCTS.__cxa_exception.adjustedPtr, 'adjustedPtr', '*') }}};
76
}
77
78
get_adjusted_ptr() {
79
return {{{ makeGetValue('this.ptr', C_STRUCTS.__cxa_exception.adjustedPtr, '*') }}};
80
}
81
},
82
83
// Here, we throw an exception after recording a couple of values that we need to remember
84
// We also remember that it was the last exception thrown as we need to know that later.
85
__cxa_throw__deps: ['$ExceptionInfo', '$exceptionLast', '$uncaughtExceptionCount'
86
#if !DISABLE_EXCEPTION_CATCHING
87
, '__cxa_increment_exception_refcount'
88
#endif
89
],
90
__cxa_throw: (ptr, type, destructor) => {
91
#if EXCEPTION_DEBUG
92
dbg('__cxa_throw: ' + [ptrToString(ptr), type, ptrToString(destructor)]);
93
#endif
94
var info = new ExceptionInfo(ptr);
95
// Initialize ExceptionInfo content after it was allocated in __cxa_allocate_exception.
96
info.init(type, destructor);
97
#if !DISABLE_EXCEPTION_CATCHING
98
___cxa_increment_exception_refcount(ptr);
99
#endif
100
{{{ storeException('exceptionLast', 'ptr') }}}
101
uncaughtExceptionCount++;
102
{{{ makeThrow('exceptionLast') }}}
103
},
104
105
// This exception will be caught twice, but while begin_catch runs twice,
106
// we early-exit from end_catch when the exception has been rethrown, so
107
// pop that here from the caught exceptions.
108
__cxa_rethrow__deps: ['$exceptionCaught', '$exceptionLast', '$uncaughtExceptionCount'
109
#if !DISABLE_EXCEPTION_CATCHING
110
, '__cxa_increment_exception_refcount'
111
#endif
112
],
113
__cxa_rethrow: () => {
114
var info = exceptionCaught.pop();
115
if (!info) {
116
abort('no exception to throw');
117
}
118
var ptr = info.excPtr;
119
if (!info.get_rethrown()) {
120
// Only pop if the corresponding push was through rethrow_primary_exception
121
exceptionCaught.push(info);
122
info.set_rethrown(true);
123
info.set_caught(false);
124
uncaughtExceptionCount++;
125
}
126
#if !DISABLE_EXCEPTION_CATCHING
127
___cxa_increment_exception_refcount(ptr);
128
#endif
129
#if EXCEPTION_DEBUG
130
dbg('__cxa_rethrow, popped ' +
131
[ptrToString(ptr), exceptionLast, 'stack', exceptionCaught]);
132
#endif
133
{{{ storeException('exceptionLast', 'ptr') }}}
134
{{{ makeThrow('exceptionLast') }}}
135
},
136
137
llvm_eh_typeid_for: (type) => type,
138
139
__cxa_begin_catch__deps: ['$exceptionCaught', '__cxa_get_exception_ptr',
140
'$uncaughtExceptionCount'],
141
__cxa_begin_catch: (ptr) => {
142
var info = new ExceptionInfo(ptr);
143
if (!info.get_caught()) {
144
info.set_caught(true);
145
uncaughtExceptionCount--;
146
}
147
info.set_rethrown(false);
148
exceptionCaught.push(info);
149
#if EXCEPTION_DEBUG
150
dbg('__cxa_begin_catch ' + [ptrToString(ptr), 'stack', exceptionCaught]);
151
#endif
152
return ___cxa_get_exception_ptr(ptr);
153
},
154
155
// We're done with a catch. Now, we can run the destructor if there is one
156
// and free the exception. Note that if the dynCall on the destructor fails
157
// due to calling apply on undefined, that means that the destructor is
158
// an invalid index into the FUNCTION_TABLE, so something has gone wrong.
159
__cxa_end_catch__deps: ['$exceptionCaught', '$exceptionLast', '__cxa_decrement_exception_refcount', 'setThrew'],
160
__cxa_end_catch: () => {
161
// Clear state flag.
162
_setThrew(0, 0);
163
#if ASSERTIONS
164
assert(exceptionCaught.length > 0);
165
#endif
166
// Call destructor if one is registered then clear it.
167
var info = exceptionCaught.pop();
168
169
#if EXCEPTION_DEBUG
170
dbg('__cxa_end_catch popped ' + [info, exceptionLast, 'stack', exceptionCaught]);
171
#endif
172
___cxa_decrement_exception_refcount(info.excPtr);
173
exceptionLast = 0; // XXX in decRef?
174
},
175
176
__cxa_uncaught_exceptions__deps: ['$uncaughtExceptionCount'],
177
__cxa_uncaught_exceptions: () => uncaughtExceptionCount,
178
179
__cxa_call_unexpected: (exception) => abort('Unexpected exception thrown, this is not properly supported - aborting'),
180
181
__cxa_current_primary_exception__deps: ['$exceptionCaught', '__cxa_increment_exception_refcount'],
182
__cxa_current_primary_exception: () => {
183
if (!exceptionCaught.length) {
184
return 0;
185
}
186
var info = exceptionCaught[exceptionCaught.length - 1];
187
___cxa_increment_exception_refcount(info.excPtr);
188
return info.excPtr;
189
},
190
191
__cxa_current_exception_type() {
192
if (!exceptionCaught.length) {
193
return 0;
194
}
195
var info = exceptionCaught[exceptionCaught.length - 1];
196
return info.get_type();
197
},
198
199
__cxa_rethrow_primary_exception__deps: ['$ExceptionInfo', '$exceptionCaught', '__cxa_rethrow'],
200
__cxa_rethrow_primary_exception: (ptr) => {
201
if (!ptr) return;
202
var info = new ExceptionInfo(ptr);
203
exceptionCaught.push(info);
204
info.set_rethrown(true);
205
___cxa_rethrow();
206
},
207
208
// Finds a suitable catch clause for when an exception is thrown.
209
// In normal compilers, this functionality is handled by the C++
210
// 'personality' routine. This is passed a fairly complex structure
211
// relating to the context of the exception and makes judgements
212
// about how to handle it. Some of it is about matching a suitable
213
// catch clause, and some of it is about unwinding. We already handle
214
// unwinding using 'if' blocks around each function, so the remaining
215
// functionality boils down to picking a suitable 'catch' block.
216
// We'll do that here, instead, to keep things simpler.
217
$findMatchingCatch__deps: ['$exceptionLast', '$ExceptionInfo', '__cxa_can_catch', '$setTempRet0'],
218
$findMatchingCatch: (args) => {
219
var thrown =
220
#if EXCEPTION_STACK_TRACES
221
exceptionLast?.excPtr;
222
#else
223
exceptionLast;
224
#endif
225
if (!thrown) {
226
// just pass through the null ptr
227
setTempRet0(0);
228
return 0;
229
}
230
var info = new ExceptionInfo(thrown);
231
info.set_adjusted_ptr(thrown);
232
var thrownType = info.get_type();
233
if (!thrownType) {
234
// just pass through the thrown ptr
235
setTempRet0(0);
236
return thrown;
237
}
238
239
// can_catch receives a **, add indirection
240
#if EXCEPTION_DEBUG
241
dbg("findMatchingCatch on " + ptrToString(thrown));
242
#endif
243
// The different catch blocks are denoted by different types.
244
// Due to inheritance, those types may not precisely match the
245
// type of the thrown object. Find one which matches, and
246
// return the type of the catch block which should be called.
247
for (var caughtType of args) {
248
if (caughtType === 0 || caughtType === thrownType) {
249
// Catch all clause matched or exactly the same type is caught
250
break;
251
}
252
var adjusted_ptr_addr = info.ptr + {{{ C_STRUCTS.__cxa_exception.adjustedPtr }}};
253
if (___cxa_can_catch(caughtType, thrownType, adjusted_ptr_addr)) {
254
#if EXCEPTION_DEBUG
255
dbg(" findMatchingCatch found " + [ptrToString(info.get_adjusted_ptr()), caughtType]);
256
#endif
257
setTempRet0(caughtType);
258
return thrown;
259
}
260
}
261
setTempRet0(thrownType);
262
return thrown;
263
},
264
265
__resumeException__deps: ['$exceptionLast'],
266
__resumeException: (ptr) => {
267
#if EXCEPTION_DEBUG
268
dbg("__resumeException " + [ptrToString(ptr), exceptionLast]);
269
#endif
270
if (!exceptionLast) {
271
{{{ storeException('exceptionLast', 'ptr') }}}
272
}
273
{{{ makeThrow('exceptionLast') }}}
274
},
275
276
#endif
277
#if WASM_EXCEPTIONS || !DISABLE_EXCEPTION_CATCHING
278
$getExceptionMessageCommon__deps: ['__get_exception_message', 'free', '$stackSave', '$stackRestore', '$stackAlloc'],
279
$getExceptionMessageCommon: (ptr) => {
280
var sp = stackSave();
281
var type_addr_addr = stackAlloc({{{ POINTER_SIZE }}});
282
var message_addr_addr = stackAlloc({{{ POINTER_SIZE }}});
283
___get_exception_message(ptr, type_addr_addr, message_addr_addr);
284
var type_addr = {{{ makeGetValue('type_addr_addr', 0, '*') }}};
285
var message_addr = {{{ makeGetValue('message_addr_addr', 0, '*') }}};
286
var type = UTF8ToString(type_addr);
287
_free(type_addr);
288
var message;
289
if (message_addr) {
290
message = UTF8ToString(message_addr);
291
_free(message_addr);
292
}
293
stackRestore(sp);
294
return [type, message];
295
},
296
#endif
297
#if WASM_EXCEPTIONS
298
$getCppExceptionTag__deps: ['__cpp_exception'],
299
// In static linking, tags are defined within the wasm module and are
300
// exported, whereas in dynamic linking, tags are defined in libcore.js in
301
// JS code and wasm modules import them.
302
$getCppExceptionTag: () => ___cpp_exception,
303
304
#if EXCEPTION_STACK_TRACES
305
// Throw a WebAssembly.Exception object with the C++ tag with a stack trace
306
// embedded. WebAssembly.Exception is a JS object representing a Wasm
307
// exception, provided by Wasm JS API:
308
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Exception
309
// In release builds, this function is not needed and the native
310
// _Unwind_RaiseException in libunwind is used instead.
311
__throw_exception_with_stack_trace__deps: ['$getCppExceptionTag', '$getExceptionMessage'],
312
__throw_exception_with_stack_trace: (ex) => {
313
var e = new WebAssembly.Exception(getCppExceptionTag(), [ex], {traceStack: true});
314
e.message = getExceptionMessage(e);
315
throw e;
316
},
317
#endif
318
319
// Given an WebAssembly.Exception object, returns the actual user-thrown
320
// C++ object address in the Wasm memory.
321
$getCppExceptionThrownObjectFromWebAssemblyException__deps: ['$getCppExceptionTag', '__thrown_object_from_unwind_exception'],
322
$getCppExceptionThrownObjectFromWebAssemblyException: (ex) => {
323
// In Wasm EH, the value extracted from WebAssembly.Exception is a pointer
324
// to the unwind header. Convert it to the actual thrown value.
325
var unwind_header = ex.getArg(getCppExceptionTag(), 0);
326
return ___thrown_object_from_unwind_exception(unwind_header);
327
},
328
329
$incrementExceptionRefcount__deps: ['__cxa_increment_exception_refcount', '$getCppExceptionThrownObjectFromWebAssemblyException'],
330
$incrementExceptionRefcount: (ex) => {
331
var ptr = getCppExceptionThrownObjectFromWebAssemblyException(ex);
332
___cxa_increment_exception_refcount(ptr);
333
},
334
335
$decrementExceptionRefcount__deps: ['__cxa_decrement_exception_refcount', '$getCppExceptionThrownObjectFromWebAssemblyException'],
336
$decrementExceptionRefcount: (ex) => {
337
var ptr = getCppExceptionThrownObjectFromWebAssemblyException(ex);
338
___cxa_decrement_exception_refcount(ptr);
339
},
340
341
$getExceptionMessage__deps: ['$getCppExceptionThrownObjectFromWebAssemblyException', '$getExceptionMessageCommon'],
342
$getExceptionMessage: (ex) => {
343
var ptr = getCppExceptionThrownObjectFromWebAssemblyException(ex);
344
return getExceptionMessageCommon(ptr);
345
},
346
347
#elif !DISABLE_EXCEPTION_CATCHING
348
// When EXCEPTION_STACK_TRACES is set, the exception is an instance of
349
// CppException, whereas EXCEPTION_STACK_TRACES is unset it is a raw pointer.
350
$exnToPtr: (exn) => {
351
#if EXCEPTION_STACK_TRACES
352
if (exn instanceof CppException) {
353
return exn.excPtr;
354
}
355
#endif
356
return exn;
357
},
358
359
$incrementExceptionRefcount__deps: ['$exnToPtr', '__cxa_increment_exception_refcount'],
360
$incrementExceptionRefcount: (exn) => ___cxa_increment_exception_refcount(exnToPtr(exn)),
361
362
$decrementExceptionRefcount__deps: ['$exnToPtr', '__cxa_decrement_exception_refcount'],
363
$decrementExceptionRefcount: (exn) => ___cxa_decrement_exception_refcount(exnToPtr(exn)),
364
365
$getExceptionMessage__deps: ['$exnToPtr', '$getExceptionMessageCommon'],
366
$getExceptionMessage: (exn) => getExceptionMessageCommon(exnToPtr(exn)),
367
368
#endif
369
};
370
371
#if !WASM_EXCEPTIONS
372
// In LLVM, exceptions generate a set of functions of form
373
// __cxa_find_matching_catch_2(), __cxa_find_matching_catch_3(), etc. where the
374
// number specifies the number of arguments. In Emscripten, route all these to
375
// a single function '__cxa_find_matching_catch' that variadically processes all
376
// of these functions using JS 'arguments' object.
377
addCxaCatch = (n) => {
378
const args = [];
379
// Confusingly, the actual number of argument is n - 2. According to the llvm
380
// code in WebAssemblyLowerEmscriptenEHSjLj.cpp:
381
// This is because a landingpad instruction contains two more arguments, a
382
// personality function and a cleanup bit, and __cxa_find_matching_catch_N
383
// functions are named after the number of arguments in the original landingpad
384
// instruction.
385
let sig = 'p';
386
for (let i = 0; i < n - 2; i++) {
387
args.push(`arg${i}`);
388
sig += 'p';
389
}
390
const argString = args.join(',');
391
LibraryManager.library[`__cxa_find_matching_catch_${n}__sig`] = sig;
392
LibraryManager.library[`__cxa_find_matching_catch_${n}__deps`] = ['$findMatchingCatch'];
393
LibraryManager.library[`__cxa_find_matching_catch_${n}`] = eval(`(${args}) => findMatchingCatch([${argString}])`);
394
};
395
396
// Add the first 2-5 catch handlers preemptively. Others get added on demand in
397
// jsifier. This is done here primarily so that these symbols end up with the
398
// correct deps in the stub library that we pass to wasm-ld.
399
// Note: __cxa_find_matching_catch_N function uses N = NumClauses + 2 so
400
// __cxa_find_matching_catch_2 is the first such function with zero clauses.
401
// See WebAssemblyLowerEmscriptenEHSjLj.cpp.
402
for (let i = 2; i < 5; i++) {
403
addCxaCatch(i)
404
}
405
#endif
406
407
addToLibrary(LibraryExceptions);
408
409