-
Notifications
You must be signed in to change notification settings - Fork 3.3k
/
Copy pathlibrary_exceptions.js
459 lines (412 loc) · 16.6 KB
/
library_exceptions.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
/**
* @license
* Copyright 2010 The Emscripten Authors
* SPDX-License-Identifier: MIT
*/
var LibraryExceptions = {
#if !WASM_EXCEPTIONS
$uncaughtExceptionCount: '0',
$exceptionLast: '0',
$exceptionCaught: ' []',
// This class is the exception metadata which is prepended to each thrown object (in WASM memory).
// It is allocated in one block among with a thrown object in __cxa_allocate_exception and freed
// in ___cxa_free_exception. It roughly corresponds to __cxa_exception structure in libcxxabi. The
// class itself is just a native pointer wrapper, and contains all the necessary accessors for the
// fields in the native structure.
// TODO: Unfortunately this approach still cannot be considered thread-safe because single
// exception object can be simultaneously thrown in several threads and its state (except
// reference counter) is not protected from that. Also protection is not enough, separate state
// should be allocated. libcxxabi has concept of dependent exception which is used for that
// purpose, it references the primary exception.
//
// excPtr - Thrown object pointer to wrap. Metadata pointer is calculated from it.
$ExceptionInfo__docs: '/** @constructor */',
$ExceptionInfo__deps: ['__cxa_is_pointer_type',
#if EXCEPTION_DEBUG
'$ptrToString',
#endif
],
$ExceptionInfo: function(excPtr) {
this.excPtr = excPtr;
this.ptr = excPtr - {{{ C_STRUCTS.__cxa_exception.__size__ }}};
this.set_type = function(type) {
{{{ makeSetValue('this.ptr', C_STRUCTS.__cxa_exception.exceptionType, 'type', '*') }}};
};
this.get_type = function() {
return {{{ makeGetValue('this.ptr', C_STRUCTS.__cxa_exception.exceptionType, '*') }}};
};
this.set_destructor = function(destructor) {
{{{ makeSetValue('this.ptr', C_STRUCTS.__cxa_exception.exceptionDestructor, 'destructor', '*') }}};
};
this.get_destructor = function() {
return {{{ makeGetValue('this.ptr', C_STRUCTS.__cxa_exception.exceptionDestructor, '*') }}};
};
this.set_refcount = function(refcount) {
{{{ makeSetValue('this.ptr', C_STRUCTS.__cxa_exception.referenceCount, 'refcount', 'i32') }}};
};
this.set_caught = function (caught) {
caught = caught ? 1 : 0;
{{{ makeSetValue('this.ptr', C_STRUCTS.__cxa_exception.caught, 'caught', 'i8') }}};
};
this.get_caught = function () {
return {{{ makeGetValue('this.ptr', C_STRUCTS.__cxa_exception.caught, 'i8') }}} != 0;
};
this.set_rethrown = function (rethrown) {
rethrown = rethrown ? 1 : 0;
{{{ makeSetValue('this.ptr', C_STRUCTS.__cxa_exception.rethrown, 'rethrown', 'i8') }}};
};
this.get_rethrown = function () {
return {{{ makeGetValue('this.ptr', C_STRUCTS.__cxa_exception.rethrown, 'i8') }}} != 0;
};
// Initialize native structure fields. Should be called once after allocated.
this.init = function(type, destructor) {
#if EXCEPTION_DEBUG
err('ExceptionInfo init: ' + [type, destructor]);
#endif
this.set_adjusted_ptr(0);
this.set_type(type);
this.set_destructor(destructor);
this.set_refcount(0);
this.set_caught(false);
this.set_rethrown(false);
}
this.add_ref = function() {
#if SHARED_MEMORY
Atomics.add(HEAP32, (this.ptr + {{{ C_STRUCTS.__cxa_exception.referenceCount }}}) >> 2, 1);
#else
var value = {{{ makeGetValue('this.ptr', C_STRUCTS.__cxa_exception.referenceCount, 'i32') }}};
{{{ makeSetValue('this.ptr', C_STRUCTS.__cxa_exception.referenceCount, 'value + 1', 'i32') }}};
#endif
};
// Returns true if last reference released.
this.release_ref = function() {
#if SHARED_MEMORY
var prev = Atomics.sub(HEAP32, (this.ptr + {{{ C_STRUCTS.__cxa_exception.referenceCount }}}) >> 2, 1);
#else
var prev = {{{ makeGetValue('this.ptr', C_STRUCTS.__cxa_exception.referenceCount, 'i32') }}};
{{{ makeSetValue('this.ptr', C_STRUCTS.__cxa_exception.referenceCount, 'prev - 1', 'i32') }}};
#endif
#if ASSERTIONS
assert(prev > 0);
#endif
return prev === 1;
};
this.set_adjusted_ptr = function(adjustedPtr) {
{{{ makeSetValue('this.ptr', C_STRUCTS.__cxa_exception.adjustedPtr, 'adjustedPtr', '*') }}};
};
this.get_adjusted_ptr = function() {
return {{{ makeGetValue('this.ptr', C_STRUCTS.__cxa_exception.adjustedPtr, '*') }}};
};
// Get pointer which is expected to be received by catch clause in C++ code. It may be adjusted
// when the pointer is casted to some of the exception object base classes (e.g. when virtual
// inheritance is used). When a pointer is thrown this method should return the thrown pointer
// itself.
this.get_exception_ptr = function() {
// Work around a fastcomp bug, this code is still included for some reason in a build without
// exceptions support.
var isPointer = {{{ exportedAsmFunc('___cxa_is_pointer_type') }}}(this.get_type());
if (isPointer) {
return {{{ makeGetValue('this.excPtr', '0', '*') }}};
}
var adjusted = this.get_adjusted_ptr();
if (adjusted !== 0) return adjusted;
return this.excPtr;
};
},
$exception_addRef: function (info) {
#if EXCEPTION_DEBUG
err('exception_addRef ' + ptrToString(info.excPtr));
#endif
info.add_ref();
},
$exception_decRef__deps: ['__cxa_free_exception'
#if EXCEPTION_DEBUG
, '$exceptionLast', '$exceptionCaught'
#endif
],
$exception_decRef: function(info) {
#if EXCEPTION_DEBUG
err('exception_decRef ' + ptrToString(info.excPtr));
#endif
// A rethrown exception can reach refcount 0; it must not be discarded
// Its next handler will clear the rethrown flag and addRef it, prior to
// final decRef and destruction here
if (info.release_ref() && !info.get_rethrown()) {
var destructor = info.get_destructor();
if (destructor) {
// In Wasm, destructors return 'this' as in ARM
{{{ makeDynCall('pp', 'destructor') }}}(info.excPtr);
}
___cxa_free_exception(info.excPtr);
#if EXCEPTION_DEBUG
err('decref freeing exception ' + [ptrToString(info.excPtr), exceptionLast, 'stack', exceptionCaught]);
#endif
}
},
// Exceptions
__cxa_allocate_exception__sig: 'pp',
__cxa_allocate_exception: function(size) {
// Thrown object is prepended by exception metadata block
return _malloc(size + {{{ C_STRUCTS.__cxa_exception.__size__ }}}) + {{{ C_STRUCTS.__cxa_exception.__size__ }}};
},
__cxa_free_exception__deps: ['$ExceptionInfo'],
__cxa_free_exception__sig: 'vp',
__cxa_free_exception: function(ptr) {
#if ABORTING_MALLOC || ASSERTIONS
try {
#endif
return _free(new ExceptionInfo(ptr).ptr);
#if ABORTING_MALLOC || ASSERTIONS
} catch(e) {
#if ASSERTIONS
err('exception during __cxa_free_exception: ' + e);
#endif
}
#endif
},
__cxa_increment_exception_refcount__deps: ['$exception_addRef', '$ExceptionInfo'],
__cxa_increment_exception_refcount__sig: 'vp',
__cxa_increment_exception_refcount: function(ptr) {
if (!ptr) return;
exception_addRef(new ExceptionInfo(ptr));
},
__cxa_decrement_exception_refcount__deps: ['$exception_decRef', '$ExceptionInfo'],
__cxa_decrement_exception_refcount__sig: 'vp',
__cxa_decrement_exception_refcount: function(ptr) {
if (!ptr) return;
exception_decRef(new ExceptionInfo(ptr));
},
// Here, we throw an exception after recording a couple of values that we need to remember
// We also remember that it was the last exception thrown as we need to know that later.
__cxa_throw__sig: 'vppp',
__cxa_throw__deps: ['$ExceptionInfo', '$exceptionLast', '$uncaughtExceptionCount'],
__cxa_throw: function(ptr, type, destructor) {
#if EXCEPTION_DEBUG
err('__cxa_throw: ' + [ptrToString(ptr), type, ptrToString(destructor)]);
#endif
var info = new ExceptionInfo(ptr);
// Initialize ExceptionInfo content after it was allocated in __cxa_allocate_exception.
info.init(type, destructor);
exceptionLast = ptr;
uncaughtExceptionCount++;
{{{ makeThrow('ptr') }}}
},
// This exception will be caught twice, but while begin_catch runs twice,
// we early-exit from end_catch when the exception has been rethrown, so
// pop that here from the caught exceptions.
__cxa_rethrow__deps: ['$exceptionCaught', '$exceptionLast', '$uncaughtExceptionCount'],
__cxa_rethrow__sig: 'v',
__cxa_rethrow: function() {
var info = exceptionCaught.pop();
if (!info) {
abort('no exception to throw');
}
var ptr = info.excPtr;
if (!info.get_rethrown()) {
// Only pop if the corresponding push was through rethrow_primary_exception
exceptionCaught.push(info);
info.set_rethrown(true);
info.set_caught(false);
uncaughtExceptionCount++;
}
#if EXCEPTION_DEBUG
err('__cxa_rethrow, popped ' +
[ptrToString(ptr), exceptionLast, 'stack', exceptionCaught]);
#endif
exceptionLast = ptr;
{{{ makeThrow('ptr') }}}
},
llvm_eh_typeid_for__sig: 'ip',
llvm_eh_typeid_for: function(type) {
return type;
},
__cxa_begin_catch__deps: ['$exceptionCaught', '$exception_addRef', '$uncaughtExceptionCount'],
__cxa_begin_catch__sig: 'pp',
__cxa_begin_catch: function(ptr) {
var info = new ExceptionInfo(ptr);
if (!info.get_caught()) {
info.set_caught(true);
uncaughtExceptionCount--;
}
info.set_rethrown(false);
exceptionCaught.push(info);
#if EXCEPTION_DEBUG
err('__cxa_begin_catch ' + [ptrToString(ptr), 'stack', exceptionCaught]);
#endif
exception_addRef(info);
return info.get_exception_ptr();
},
// We're done with a catch. Now, we can run the destructor if there is one
// and free the exception. Note that if the dynCall on the destructor fails
// due to calling apply on undefined, that means that the destructor is
// an invalid index into the FUNCTION_TABLE, so something has gone wrong.
__cxa_end_catch__deps: ['$exceptionCaught', '$exceptionLast', '$exception_decRef'],
__cxa_end_catch__sig: 'v',
__cxa_end_catch: function() {
// Clear state flag.
_setThrew(0);
#if ASSERTIONS
assert(exceptionCaught.length > 0);
#endif
// Call destructor if one is registered then clear it.
var info = exceptionCaught.pop();
#if EXCEPTION_DEBUG
err('__cxa_end_catch popped ' + [info, exceptionLast, 'stack', exceptionCaught]);
#endif
exception_decRef(info);
exceptionLast = 0; // XXX in decRef?
},
__cxa_get_exception_ptr__deps: ['$ExceptionInfo'],
__cxa_get_exception_ptr__sig: 'pp',
__cxa_get_exception_ptr: function(ptr) {
#if EXCEPTION_DEBUG
err('__cxa_get_exception_ptr ' + ptrToString(ptr));
#endif
return new ExceptionInfo(ptr).get_exception_ptr();
},
__cxa_uncaught_exceptions__deps: ['$uncaughtExceptionCount'],
__cxa_uncaught_exceptions: function() {
return uncaughtExceptionCount;
},
__cxa_call_unexpected: function(exception) {
err('Unexpected exception thrown, this is not properly supported - aborting');
#if !MINIMAL_RUNTIME
ABORT = true;
#endif
throw exception;
},
__cxa_current_primary_exception__deps: ['$exceptionCaught', '$exception_addRef'],
__cxa_current_primary_exception: function() {
if (!exceptionCaught.length) {
return 0;
}
var info = exceptionCaught[exceptionCaught.length - 1];
exception_addRef(info);
return info.excPtr;
},
__cxa_rethrow_primary_exception__deps: ['$ExceptionInfo', '$exceptionCaught', '__cxa_rethrow'],
__cxa_rethrow_primary_exception: function(ptr) {
if (!ptr) return;
var info = new ExceptionInfo(ptr);
exceptionCaught.push(info);
info.set_rethrown(true);
___cxa_rethrow();
},
// Finds a suitable catch clause for when an exception is thrown.
// In normal compilers, this functionality is handled by the C++
// 'personality' routine. This is passed a fairly complex structure
// relating to the context of the exception and makes judgements
// about how to handle it. Some of it is about matching a suitable
// catch clause, and some of it is about unwinding. We already handle
// unwinding using 'if' blocks around each function, so the remaining
// functionality boils down to picking a suitable 'catch' block.
// We'll do that here, instead, to keep things simpler.
__cxa_find_matching_catch__deps: ['$exceptionLast', '$ExceptionInfo', '__resumeException', '__cxa_can_catch'],
//__cxa_find_matching_catch__sig: 'p',
__cxa_find_matching_catch: function() {
var thrown = exceptionLast;
if (!thrown) {
// just pass through the null ptr
setTempRet0(0);
return 0;
}
var info = new ExceptionInfo(thrown);
info.set_adjusted_ptr(thrown);
var thrownType = info.get_type();
if (!thrownType) {
// just pass through the thrown ptr
setTempRet0(0);
return thrown;
}
var typeArray = Array.prototype.slice.call(arguments);
// can_catch receives a **, add indirection
#if EXCEPTION_DEBUG
err("__cxa_find_matching_catch on " + ptrToString(thrown));
#endif
// The different catch blocks are denoted by different types.
// Due to inheritance, those types may not precisely match the
// type of the thrown object. Find one which matches, and
// return the type of the catch block which should be called.
for (var i = 0; i < typeArray.length; i++) {
var caughtType = typeArray[i];
if (caughtType === 0 || caughtType === thrownType) {
// Catch all clause matched or exactly the same type is caught
break;
}
var adjusted_ptr_addr = info.ptr + {{{ C_STRUCTS.__cxa_exception.adjustedPtr }}};
if ({{{ exportedAsmFunc('___cxa_can_catch') }}}(caughtType, thrownType, adjusted_ptr_addr)) {
#if EXCEPTION_DEBUG
err(" __cxa_find_matching_catch found " + [ptrToString(info.get_adjusted_ptr()), caughtType]);
#endif
setTempRet0(caughtType);
return thrown;
}
}
setTempRet0(thrownType);
return thrown;
},
__resumeException__deps: ['$exceptionLast'],
__resumeException__sig: 'vp',
__resumeException: function(ptr) {
#if EXCEPTION_DEBUG
err("__resumeException " + [ptrToString(ptr), exceptionLast]);
#endif
if (!exceptionLast) { exceptionLast = ptr; }
{{{ makeThrow('ptr') }}}
},
#endif
#if WASM_EXCEPTIONS
$getCppExceptionTag: function() {
return Module['asm']['__cpp_exception'];
},
$getCppExceptionThrownValue__deps: ['$getCppExceptionTag'],
$getCppExceptionThrownValue: function(ex) {
return ex.getArg(getCppExceptionTag(), 0);
},
$incrementExceptionRefcount__deps: ['__increment_wasm_exception_refcount', '$getCppExceptionThrownValue'],
$incrementExceptionRefcount: function(obj) {
var ptr = getCppExceptionThrownValue(obj);
___increment_wasm_exception_refcount(ptr);
},
$decrementExceptionRefcount__deps: ['__decrement_wasm_exception_refcount', '$getCppExceptionThrownValue'],
$decrementExceptionRefcount: function(obj) {
var ptr = getCppExceptionThrownValue(obj);
___decrement_wasm_exception_refcount(ptr);
},
$getExceptionMessage__deps: ['__get_exception_message', 'free', '$getCppExceptionThrownValue'],
$getExceptionMessage: function(ptr) {
// In Wasm EH, the thrown object is a WebAssembly.Exception. Extract the
// thrown value from it.
var obj = getCppExceptionThrownValue(ptr);
var utf8_addr = ___get_exception_message(obj);
var result = UTF8ToString(utf8_addr);
_free(utf8_addr);
return result;
},
#elif !DISABLE_EXCEPTION_CATCHING
$incrementExceptionRefcount__deps: ['__cxa_increment_exception_refcount'],
$incrementExceptionRefcount: function(ptr) {
___cxa_increment_exception_refcount(ptr);
},
$decrementExceptionRefcount__deps: ['__cxa_decrement_exception_refcount'],
$decrementExceptionRefcount: function(ptr) {
___cxa_decrement_exception_refcount(ptr);
},
$getExceptionMessage__deps: ['__get_exception_message', 'free'],
$getExceptionMessage: function(ptr) {
var utf8_addr = ___get_exception_message(ptr);
var result = UTF8ToString(utf8_addr);
_free(utf8_addr);
return result;
},
#endif
};
// In LLVM, exceptions generate a set of functions of form __cxa_find_matching_catch_1(), __cxa_find_matching_catch_2(), etc.
// where the number specifies the number of arguments. In Emscripten, route all these to a single function '__cxa_find_matching_catch'
// that variadically processes all of these functions using JS 'arguments' object.
addCxaCatch = function(n) {
LibraryManager.library['__cxa_find_matching_catch_' + n] = LibraryExceptions['__cxa_find_matching_catch'];
LibraryManager.library['__cxa_find_matching_catch_' + n + '__sig'] = new Array(n + 2).join('p');
LibraryManager.library['__cxa_find_matching_catch_' + n + '__deps'] = LibraryExceptions['__cxa_find_matching_catch__deps'];
};
mergeInto(LibraryManager.library, LibraryExceptions);