| /** |
| * @license |
| * Copyright 2010 The Emscripten Authors |
| * SPDX-License-Identifier: MIT |
| */ |
| |
| var LibraryExceptions = { |
| $uncaughtExceptionCount: '0', |
| $exceptionLast: '0', |
| $exceptionCaught: ' []', |
| |
| // Static fields for ExceptionInfo class. |
| $ExceptionInfoAttrs: { |
| // ExceptionInfo native structure layout. |
| DESTRUCTOR_OFFSET: 0, |
| REFCOUNT_OFFSET: Runtime.POINTER_SIZE, |
| TYPE_OFFSET: Runtime.POINTER_SIZE + 4, |
| CAUGHT_OFFSET: Runtime.POINTER_SIZE + 8, |
| RETHROWN_OFFSET: Runtime.POINTER_SIZE + 9, |
| |
| // Total structure size with padding, should be multiple of allocation alignment. |
| SIZE: alignMemory(Runtime.POINTER_SIZE + 10) |
| }, |
| |
| $ExceptionInfo__deps: ['$ExceptionInfoAttrs'], |
| // 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: function(excPtr) { |
| this.excPtr = excPtr; |
| this.ptr = excPtr - ExceptionInfoAttrs.SIZE; |
| |
| this.set_type = function(type) { |
| {{{ makeSetValue('this.ptr', 'ExceptionInfoAttrs.TYPE_OFFSET', 'type', '*') }}}; |
| }; |
| |
| this.get_type = function() { |
| return {{{ makeGetValue('this.ptr', 'ExceptionInfoAttrs.TYPE_OFFSET', '*') }}}; |
| }; |
| |
| this.set_destructor = function(destructor) { |
| {{{ makeSetValue('this.ptr', 'ExceptionInfoAttrs.DESTRUCTOR_OFFSET', 'destructor', '*') }}}; |
| }; |
| |
| this.get_destructor = function() { |
| return {{{ makeGetValue('this.ptr', 'ExceptionInfoAttrs.DESTRUCTOR_OFFSET', '*') }}}; |
| }; |
| |
| this.set_refcount = function(refcount) { |
| {{{ makeSetValue('this.ptr', 'ExceptionInfoAttrs.REFCOUNT_OFFSET', 'refcount', 'i32') }}}; |
| }; |
| |
| this.set_caught = function (caught) { |
| caught = caught ? 1 : 0; |
| {{{ makeSetValue('this.ptr', 'ExceptionInfoAttrs.CAUGHT_OFFSET', 'caught', 'i8') }}}; |
| }; |
| |
| this.get_caught = function () { |
| return {{{ makeGetValue('this.ptr', 'ExceptionInfoAttrs.CAUGHT_OFFSET', 'i8') }}} != 0; |
| }; |
| |
| this.set_rethrown = function (rethrown) { |
| rethrown = rethrown ? 1 : 0; |
| {{{ makeSetValue('this.ptr', 'ExceptionInfoAttrs.RETHROWN_OFFSET', 'rethrown', 'i8') }}}; |
| }; |
| |
| this.get_rethrown = function () { |
| return {{{ makeGetValue('this.ptr', 'ExceptionInfoAttrs.RETHROWN_OFFSET', 'i8') }}} != 0; |
| }; |
| |
| // Initialize native structure fields. Should be called once after allocated. |
| this.init = function(type, destructor) { |
| 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 USE_PTHREADS |
| Atomics.add(HEAP32, (this.ptr + ExceptionInfoAttrs.REFCOUNT_OFFSET) >> 2, 1); |
| #else |
| var value = {{{ makeGetValue('this.ptr', 'ExceptionInfoAttrs.REFCOUNT_OFFSET', 'i32') }}}; |
| {{{ makeSetValue('this.ptr', 'ExceptionInfoAttrs.REFCOUNT_OFFSET', 'value + 1', 'i32') }}}; |
| #endif |
| }; |
| |
| // Returns true if last reference released. |
| this.release_ref = function() { |
| #if USE_PTHREADS |
| var prev = Atomics.sub(HEAP32, (this.ptr + ExceptionInfoAttrs.REFCOUNT_OFFSET) >> 2, 1); |
| #else |
| var prev = {{{ makeGetValue('this.ptr', 'ExceptionInfoAttrs.REFCOUNT_OFFSET', 'i32') }}}; |
| {{{ makeSetValue('this.ptr', 'ExceptionInfoAttrs.REFCOUNT_OFFSET', 'prev - 1', 'i32') }}}; |
| #endif |
| #if ASSERTIONS |
| assert(prev > 0); |
| #endif |
| return prev === 1; |
| }; |
| }, |
| |
| $CatchInfo__deps: ['$ExceptionInfo', '__cxa_is_pointer_type'], |
| // This native structure is returned from __cxa_find_matching_catch, and serves as catching |
| // context, i.e. stores information required to proceed with a specific selected catch. It stores |
| // base and adjusted pointers of a thrown object. It is allocated dynamically and should be freed |
| // when it is done with a specific catch (i.e. either in __cxa_end_catch when caught or in |
| // __resumeException when no catch clause matched). The class itself is just a native pointer |
| // wrapper, and contains all the necessary accessors for the fields in the native structure. |
| // ptr - Native structure pointer to wrap, the structure is allocated when not specified. |
| $CatchInfo: function(ptr) { |
| |
| this.free = function() { |
| _free(this.ptr); |
| this.ptr = 0; |
| }; |
| |
| this.set_base_ptr = function(basePtr) { |
| {{{ makeSetValue('this.ptr', '0', 'basePtr', '*') }}}; |
| }; |
| |
| this.get_base_ptr = function() { |
| return {{{ makeGetValue('this.ptr', '0', '*') }}}; |
| }; |
| |
| this.set_adjusted_ptr = function(adjustedPtr) { |
| var ptrSize = {{{ Runtime.POINTER_SIZE }}}; |
| {{{ makeSetValue('this.ptr', 'ptrSize', 'adjustedPtr', '*') }}}; |
| }; |
| |
| this.get_adjusted_ptr = function() { |
| var ptrSize = {{{ Runtime.POINTER_SIZE }}}; |
| return {{{ makeGetValue('this.ptr', 'ptrSize', '*') }}}; |
| }; |
| |
| // 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_exception_info().get_type()); |
| if (isPointer) { |
| return {{{ makeGetValue('this.get_base_ptr()', '0', '*') }}}; |
| } |
| var adjusted = this.get_adjusted_ptr(); |
| if (adjusted !== 0) return adjusted; |
| return this.get_base_ptr(); |
| }; |
| |
| this.get_exception_info = function() { |
| return new ExceptionInfo(this.get_base_ptr()); |
| }; |
| |
| if (ptr === undefined) { |
| this.ptr = _malloc({{{ Runtime.POINTER_SIZE * 2 }}}); |
| this.set_adjusted_ptr(0); |
| } else { |
| this.ptr = ptr; |
| } |
| }, |
| |
| $exception_addRef: function (info) { |
| #if EXCEPTION_DEBUG |
| err('addref ' + 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('decref ' + 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('ii', 'destructor') }}}(info.excPtr); |
| } |
| ___cxa_free_exception(info.excPtr); |
| #if EXCEPTION_DEBUG |
| err('decref freeing exception ' + [info.excPtr, exceptionLast, 'stack', exceptionCaught]); |
| #endif |
| } |
| }, |
| |
| // Exceptions |
| __cxa_allocate_exception__deps: ['$ExceptionInfoAttrs'], |
| __cxa_allocate_exception__sig: 'vi', |
| __cxa_allocate_exception: function(size) { |
| // Thrown object is prepended by exception metadata block |
| return _malloc(size + ExceptionInfoAttrs.SIZE) + ExceptionInfoAttrs.SIZE; |
| }, |
| |
| __cxa_free_exception__deps: ['$ExceptionInfo'], |
| __cxa_free_exception__sig: 'vi', |
| __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: function(ptr) { |
| if (!ptr) return; |
| exception_addRef(new ExceptionInfo(ptr)); |
| }, |
| |
| __cxa_decrement_exception_refcount__deps: ['$exception_decRef', '$ExceptionInfo'], |
| __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: 'viii', |
| __cxa_throw__deps: ['$ExceptionInfo', '$exceptionLast', '$uncaughtExceptionCount'], |
| __cxa_throw: function(ptr, type, destructor) { |
| #if EXCEPTION_DEBUG |
| err('Compiled code throwing an exception, ' + [ptr,type,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 catchInfo = exceptionCaught.pop(); |
| if (!catchInfo) { |
| abort('no exception to throw'); |
| } |
| var info = catchInfo.get_exception_info(); |
| var ptr = catchInfo.get_base_ptr(); |
| if (!info.get_rethrown()) { |
| // Only pop if the corresponding push was through rethrow_primary_exception |
| exceptionCaught.push(catchInfo); |
| info.set_rethrown(true); |
| info.set_caught(false); |
| uncaughtExceptionCount++; |
| } else { |
| catchInfo.free(); |
| } |
| #if EXCEPTION_DEBUG |
| err('Compiled code RE-throwing an exception, popped ' + |
| [ptr, exceptionLast, 'stack', exceptionCaught]); |
| #endif |
| exceptionLast = ptr; |
| {{{ makeThrow('ptr') }}} |
| }, |
| |
| llvm_eh_typeid_for: function(type) { |
| return type; |
| }, |
| |
| __cxa_begin_catch__deps: ['$CatchInfo', '$exceptionCaught', '$exception_addRef', |
| '$uncaughtExceptionCount'], |
| __cxa_begin_catch: function(ptr) { |
| var catchInfo = new CatchInfo(ptr); |
| var info = catchInfo.get_exception_info(); |
| if (!info.get_caught()) { |
| info.set_caught(true); |
| uncaughtExceptionCount--; |
| } |
| info.set_rethrown(false); |
| exceptionCaught.push(catchInfo); |
| #if EXCEPTION_DEBUG |
| err('cxa_begin_catch ' + [ptr, 'stack', exceptionCaught]); |
| #endif |
| exception_addRef(info); |
| return catchInfo.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', |
| '$CatchInfo'], |
| __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 catchInfo = exceptionCaught.pop(); |
| |
| #if EXCEPTION_DEBUG |
| err('cxa_end_catch popped ' + [catchInfo, exceptionLast, 'stack', exceptionCaught]); |
| #endif |
| exception_decRef(catchInfo.get_exception_info()); |
| catchInfo.free(); |
| exceptionLast = 0; // XXX in decRef? |
| }, |
| |
| __cxa_get_exception_ptr__deps: ['$CatchInfo'], |
| __cxa_get_exception_ptr: function(ptr) { |
| #if EXCEPTION_DEBUG |
| err('cxa_get_exception_ptr ' + ptr); |
| #endif |
| return new CatchInfo(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', '$CatchInfo'], |
| __cxa_current_primary_exception: function() { |
| if (!exceptionCaught.length) { |
| return 0; |
| } |
| var catchInfo = exceptionCaught[exceptionCaught.length - 1]; |
| exception_addRef(catchInfo.get_exception_info()); |
| return catchInfo.get_base_ptr(); |
| }, |
| |
| __cxa_rethrow_primary_exception__deps: ['$CatchInfo', '$exceptionCaught', '__cxa_rethrow'], |
| __cxa_rethrow_primary_exception: function(ptr) { |
| if (!ptr) return; |
| var catchInfo = new CatchInfo(); |
| catchInfo.set_base_ptr(ptr); |
| var info = catchInfo.get_exception_info(); |
| exceptionCaught.push(catchInfo); |
| 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', '$CatchInfo', '__resumeException', '__cxa_can_catch'], |
| __cxa_find_matching_catch: function() { |
| var thrown = exceptionLast; |
| if (!thrown) { |
| // just pass through the null ptr |
| {{{ makeStructuralReturn([0, 0]) }}}; |
| } |
| var info = new ExceptionInfo(thrown); |
| var thrownType = info.get_type(); |
| var catchInfo = new CatchInfo(); |
| catchInfo.set_base_ptr(thrown); |
| if (!thrownType) { |
| // just pass through the thrown ptr |
| {{{ makeStructuralReturn(['catchInfo.ptr', 0]) }}}; |
| } |
| var typeArray = Array.prototype.slice.call(arguments); |
| |
| // can_catch receives a **, add indirection |
| #if EXCEPTION_DEBUG |
| out("can_catch on " + [thrown]); |
| #endif |
| var stackTop = stackSave(); |
| var exceptionThrowBuf = stackAlloc(4); |
| {{{ makeSetValue('exceptionThrowBuf', '0', 'thrown', '*') }}}; |
| // 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; |
| } |
| if ({{{ exportedAsmFunc('___cxa_can_catch') }}}(caughtType, thrownType, exceptionThrowBuf)) { |
| var adjusted = {{{ makeGetValue('exceptionThrowBuf', '0', '*') }}}; |
| if (thrown !== adjusted) { |
| catchInfo.set_adjusted_ptr(adjusted); |
| } |
| #if EXCEPTION_DEBUG |
| out(" can_catch found " + [adjusted, caughtType]); |
| #endif |
| {{{ makeStructuralReturn(['catchInfo.ptr', 'caughtType']) }}}; |
| } |
| } |
| stackRestore(stackTop); |
| {{{ makeStructuralReturn(['catchInfo.ptr', 'thrownType']) }}}; |
| }, |
| |
| __resumeException__deps: ['$exceptionLast', '$CatchInfo'], |
| __resumeException: function(catchInfoPtr) { |
| var catchInfo = new CatchInfo(catchInfoPtr); |
| var ptr = catchInfo.get_base_ptr(); |
| #if EXCEPTION_DEBUG |
| out("Resuming exception " + [ptr, exceptionLast]); |
| #endif |
| if (!exceptionLast) { exceptionLast = ptr; } |
| catchInfo.free(); |
| {{{ makeThrow('ptr') }}} |
| }, |
| }; |
| |
| // 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('i'); |
| LibraryManager.library['__cxa_find_matching_catch_' + n + '__deps'] = LibraryExceptions['__cxa_find_matching_catch__deps']; |
| }; |
| |
| mergeInto(LibraryManager.library, LibraryExceptions); |