bridges/Library_cpp_uno.mk | 3 bridges/Module_bridges.mk | 5 bridges/StaticLibrary_emscriptencxxabi.mk | 16 +++ bridges/source/cpp_uno/gcc3_wasm/abi.cxx | 42 -------- bridges/source/cpp_uno/gcc3_wasm/abi.hxx | 82 ---------------- bridges/source/emscriptencxxabi/cxxabi.cxx | 53 ++++++++++ include/bridges/emscriptencxxabi/cxxabi.hxx | 107 +++++++++++++++++++++ static/StaticLibrary_unoembind.mk | 4 static/emscripten/uno.js | 12 ++ static/source/unoembindhelpers/PrimaryBindings.cxx | 23 ++++ unotest/source/embindtest/embindtest.js | 23 ++-- 11 files changed, 238 insertions(+), 132 deletions(-)
New commits: commit eedbe966bba1c070041b3a3ff836b4ef28e5bdaa Author: Stephan Bergmann <stephan.bergm...@allotropia.de> AuthorDate: Fri Jun 21 09:58:07 2024 +0200 Commit: Stephan Bergmann <stephan.bergm...@allotropia.de> CommitDate: Fri Jun 21 11:29:59 2024 +0200 Embind: Fix C++ UNO exception catching ...with a new Module.catchUnoException JS function that can be used in a JS catch block to provide the thrown UNO exception as an Any. (For a non-C++ exception, it rethrows the exception, and for a non-UNO C++ exception it maps it to css.uno.RuntimeException.) The implementation reuses parts of bridges/source/cpp_uno/gcc3_wasm/, which have been moved to a new StaticLibrary_emscriptencxxabi. Change-Id: I708fe6121c43a1b9736de5dff449f6c4f32a45f3 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/169325 Tested-by: Jenkins Reviewed-by: Stephan Bergmann <stephan.bergm...@allotropia.de> diff --git a/bridges/Library_cpp_uno.mk b/bridges/Library_cpp_uno.mk index 5d6e7c489802..5ee781e7aa8c 100644 --- a/bridges/Library_cpp_uno.mk +++ b/bridges/Library_cpp_uno.mk @@ -90,6 +90,9 @@ $(eval $(call gb_Library_add_generated_asmobjects,$(CPPU_ENV)_uno, \ $(eval $(call gb_Library_add_generated_exception_objects,$(CPPU_ENV)_uno, \ CustomTarget/bridges/gcc3_wasm/callvirtualfunction-wrapper \ )) +$(eval $(call gb_Library_use_static_libraries,$(CPPU_ENV)_uno, \ + emscriptencxxabi \ +)) endif else ifeq ($(CPUNAME),M68K) diff --git a/bridges/Module_bridges.mk b/bridges/Module_bridges.mk index def86fea4cb7..a943e6537653 100644 --- a/bridges/Module_bridges.mk +++ b/bridges/Module_bridges.mk @@ -20,7 +20,10 @@ $(eval $(call gb_Module_add_targets,bridges,\ $(if $(filter ANDROID LINUX,$(OS)),\ CustomTarget_gcc3_linux_arm) \ ) \ - $(if $(filter EMSCRIPTEN,$(OS)),CustomTarget_gcc3_wasm) \ + $(if $(filter EMSCRIPTEN,$(OS)), \ + CustomTarget_gcc3_wasm \ + StaticLibrary_emscriptencxxabi \ + ) \ )) ifeq (,$(filter build,$(gb_Module_SKIPTARGETS))) diff --git a/bridges/StaticLibrary_emscriptencxxabi.mk b/bridges/StaticLibrary_emscriptencxxabi.mk new file mode 100644 index 000000000000..2271f5734fe5 --- /dev/null +++ b/bridges/StaticLibrary_emscriptencxxabi.mk @@ -0,0 +1,16 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t; fill-column: 100 -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_StaticLibrary_StaticLibrary,emscriptencxxabi)) + +$(eval $(call gb_StaticLibrary_add_exception_objects,emscriptencxxabi, \ + bridges/source/emscriptencxxabi/cxxabi \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/bridges/source/cpp_uno/gcc3_wasm/abi.cxx b/bridges/source/cpp_uno/gcc3_wasm/abi.cxx index 5d21cd1d8522..49d7762ea3c2 100644 --- a/bridges/source/cpp_uno/gcc3_wasm/abi.cxx +++ b/bridges/source/cpp_uno/gcc3_wasm/abi.cxx @@ -10,12 +10,11 @@ #include <sal/config.h> #include <cassert> -#include <cstddef> #include <typeinfo> +#include <bridges/emscriptencxxabi/cxxabi.hxx> #include <com/sun/star/uno/RuntimeException.hpp> #include <cppu/unotype.hxx> -#include <rtl/ustrbuf.hxx> #include <rtl/ustring.hxx> #include <typelib/typedescription.h> #include <uno/any2.h> @@ -23,49 +22,12 @@ #include "abi.hxx" -namespace -{ -OUString toUnoName(char const* name) -{ - assert(name != nullptr); - OUStringBuffer b; - bool scoped = *name == 'N'; - if (scoped) - { - ++name; - } - for (;;) - { - assert(*name >= '0' && *name <= '9'); - std::size_t n = *name++ - '0'; - while (*name >= '0' && *name <= '9') - { - n = 10 * n + (*name++ - '0'); - } - b.appendAscii(name, n); - name += n; - if (!scoped) - { - assert(*name == 0); - break; - } - if (*name == 'E') - { - assert(name[1] == 0); - break; - } - b.append('.'); - } - return b.makeStringAndClear(); -} -} - void abi_wasm::mapException(__cxxabiv1::__cxa_exception* exception, std::type_info const* type, uno_Any* any, uno_Mapping* mapping) { assert(exception != nullptr); assert(type != nullptr); - OUString unoName(toUnoName(type->name())); + OUString unoName(emscriptencxxabi::toUnoName(type->name())); typelib_TypeDescription* td = nullptr; typelib_typedescription_getByName(&td, unoName.pData); if (td == nullptr) diff --git a/bridges/source/cpp_uno/gcc3_wasm/abi.hxx b/bridges/source/cpp_uno/gcc3_wasm/abi.hxx index 74e92bb9bf0a..e09b68ebd76f 100644 --- a/bridges/source/cpp_uno/gcc3_wasm/abi.hxx +++ b/bridges/source/cpp_uno/gcc3_wasm/abi.hxx @@ -11,95 +11,15 @@ #include <sal/config.h> -#include <cstddef> -#include <cstdint> -#include <exception> #include <typeinfo> #include <cxxabi.h> +#include <bridges/emscriptencxxabi/cxxabi.hxx> #include <config_cxxabi.h> #include <uno/any2.h> #include <uno/mapping.h> -#if !HAVE_CXXABI_H_CXA_EXCEPTION - -// <https://github.com/emscripten-core/emscripten/>, system/lib/libunwind/include/unwind_itanium.h, -// except where MODIFIED: -typedef std::/*MODIFIED*/ uint64_t _Unwind_Exception_Class; -struct _Unwind_Exception -{ - _Unwind_Exception_Class exception_class; - void (*exception_cleanup)(/*MODIFIED: _Unwind_Reason_Code reason, _Unwind_Exception* exc*/); -#if defined(__SEH__) && !defined(__USING_SJLJ_EXCEPTIONS__) - std::/*MODIFIED*/ uintptr_t private_[6]; -#elif !defined(__USING_WASM_EXCEPTIONS__) - std::/*MODIFIED*/ uintptr_t private_1; // non-zero means forced unwind - std::/*MODIFIED*/ uintptr_t private_2; // holds sp that phase1 found for phase2 to use -#endif -#if __SIZEOF_POINTER__ == 4 - // The implementation of _Unwind_Exception uses an attribute mode on the - // above fields which has the side effect of causing this whole struct to - // round up to 32 bytes in size (48 with SEH). To be more explicit, we add - // pad fields added for binary compatibility. - std::/*MODIFIED*/ uint32_t reserved[3]; -#endif - // The Itanium ABI requires that _Unwind_Exception objects are "double-word - // aligned". GCC has interpreted this to mean "use the maximum useful - // alignment for the target"; so do we. -} __attribute__((__aligned__)); - -// <https://github.com/emscripten-core/emscripten/>, system/lib/libcxxabi/src/cxa_exception.h, -// except where MODIFIED: -namespace __cxxabiv1 -{ -struct __cxa_exception -{ -#if defined(__LP64__) || defined(_WIN64) || defined(_LIBCXXABI_ARM_EHABI) - // Now _Unwind_Exception is marked with __attribute__((aligned)), - // which implies __cxa_exception is also aligned. Insert padding - // in the beginning of the struct, rather than before unwindHeader. - void* reserve; - // This is a new field to support C++11 exception_ptr. - // For binary compatibility it is at the start of this - // struct which is prepended to the object thrown in - // __cxa_allocate_exception. - std::/*MODIFIED*/ size_t referenceCount; -#endif - // Manage the exception object itself. - std::type_info* exceptionType; -#if 1 //MODIFIED: #ifdef __USING_WASM_EXCEPTIONS__ - // In wasm, destructors return their argument - void*(/*MODIFIED: _LIBCXXABI_DTOR_FUNC*/ *exceptionDestructor)(void*); -#else - void(/*MODIFIED: _LIBCXXABI_DTOR_FUNC*/ *exceptionDestructor)(void*); -#endif - void* /*MODIFIED: std::unexpected_handler*/ unexpectedHandler; - std::terminate_handler terminateHandler; - __cxa_exception* nextException; - int handlerCount; -#if defined(_LIBCXXABI_ARM_EHABI) - __cxa_exception* nextPropagatingException; - int propagationCount; -#else - int handlerSwitchValue; - const unsigned char* actionRecord; - const unsigned char* languageSpecificData; - void* catchTemp; - void* adjustedPtr; -#endif -#if !defined(__LP64__) && !defined(_WIN64) && !defined(_LIBCXXABI_ARM_EHABI) - // This is a new field to support C++11 exception_ptr. - // For binary compatibility it is placed where the compiler - // previously added padding to 64-bit align unwindHeader. - std::/*MODIFIED*/ size_t referenceCount; -#endif - _Unwind_Exception unwindHeader; -}; -} - -#endif - #if !HAVE_CXXABI_H_CXA_EH_GLOBALS // <https://github.com/emscripten-core/emscripten/>, system/lib/libcxxabi/src/cxa_exception.h: namespace __cxxabiv1 diff --git a/bridges/source/emscriptencxxabi/cxxabi.cxx b/bridges/source/emscriptencxxabi/cxxabi.cxx new file mode 100644 index 000000000000..19aaae7847b6 --- /dev/null +++ b/bridges/source/emscriptencxxabi/cxxabi.cxx @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <sal/config.h> + +#include <cassert> +#include <cstddef> + +#include <bridges/emscriptencxxabi/cxxabi.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/ustring.hxx> + +OUString emscriptencxxabi::toUnoName(char const* name) +{ + assert(name != nullptr); + OUStringBuffer b; + bool scoped = *name == 'N'; + if (scoped) + { + ++name; + } + for (;;) + { + assert(*name >= '0' && *name <= '9'); + std::size_t n = *name++ - '0'; + while (*name >= '0' && *name <= '9') + { + n = 10 * n + (*name++ - '0'); + } + b.appendAscii(name, n); + name += n; + if (!scoped) + { + assert(*name == 0); + break; + } + if (*name == 'E') + { + assert(name[1] == 0); + break; + } + b.append('.'); + } + return b.makeStringAndClear(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/include/bridges/emscriptencxxabi/cxxabi.hxx b/include/bridges/emscriptencxxabi/cxxabi.hxx new file mode 100644 index 000000000000..8c380961cb60 --- /dev/null +++ b/include/bridges/emscriptencxxabi/cxxabi.hxx @@ -0,0 +1,107 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include <sal/config.h> + +#include <cstddef> +#include <cstdint> +#include <exception> +#include <typeinfo> + +#include <cxxabi.h> + +#include <config_cxxabi.h> +#include <rtl/ustring.hxx> + +#if !HAVE_CXXABI_H_CXA_EXCEPTION + +// <https://github.com/emscripten-core/emscripten/>, system/lib/libunwind/include/unwind_itanium.h, +// except where MODIFIED: +typedef std::/*MODIFIED*/ uint64_t _Unwind_Exception_Class; +struct _Unwind_Exception +{ + _Unwind_Exception_Class exception_class; + void (*exception_cleanup)(/*MODIFIED: _Unwind_Reason_Code reason, _Unwind_Exception* exc*/); +#if defined(__SEH__) && !defined(__USING_SJLJ_EXCEPTIONS__) + std::/*MODIFIED*/ uintptr_t private_[6]; +#elif !defined(__USING_WASM_EXCEPTIONS__) + std::/*MODIFIED*/ uintptr_t private_1; // non-zero means forced unwind + std::/*MODIFIED*/ uintptr_t private_2; // holds sp that phase1 found for phase2 to use +#endif +#if __SIZEOF_POINTER__ == 4 + // The implementation of _Unwind_Exception uses an attribute mode on the + // above fields which has the side effect of causing this whole struct to + // round up to 32 bytes in size (48 with SEH). To be more explicit, we add + // pad fields added for binary compatibility. + std::/*MODIFIED*/ uint32_t reserved[3]; +#endif + // The Itanium ABI requires that _Unwind_Exception objects are "double-word + // aligned". GCC has interpreted this to mean "use the maximum useful + // alignment for the target"; so do we. +} __attribute__((__aligned__)); + +// <https://github.com/emscripten-core/emscripten/>, system/lib/libcxxabi/src/cxa_exception.h, +// except where MODIFIED: +namespace __cxxabiv1 +{ +struct __cxa_exception +{ +#if defined(__LP64__) || defined(_WIN64) || defined(_LIBCXXABI_ARM_EHABI) + // Now _Unwind_Exception is marked with __attribute__((aligned)), + // which implies __cxa_exception is also aligned. Insert padding + // in the beginning of the struct, rather than before unwindHeader. + void* reserve; + // This is a new field to support C++11 exception_ptr. + // For binary compatibility it is at the start of this + // struct which is prepended to the object thrown in + // __cxa_allocate_exception. + std::/*MODIFIED*/ size_t referenceCount; +#endif + // Manage the exception object itself. + std::type_info* exceptionType; +#if 1 //MODIFIED: #ifdef __USING_WASM_EXCEPTIONS__ + // In wasm, destructors return their argument + void*(/*MODIFIED: _LIBCXXABI_DTOR_FUNC*/ *exceptionDestructor)(void*); +#else + void(/*MODIFIED: _LIBCXXABI_DTOR_FUNC*/ *exceptionDestructor)(void*); +#endif + void* /*MODIFIED: std::unexpected_handler*/ unexpectedHandler; + std::terminate_handler terminateHandler; + __cxa_exception* nextException; + int handlerCount; +#if defined(_LIBCXXABI_ARM_EHABI) + __cxa_exception* nextPropagatingException; + int propagationCount; +#else + int handlerSwitchValue; + const unsigned char* actionRecord; + const unsigned char* languageSpecificData; + void* catchTemp; + void* adjustedPtr; +#endif +#if !defined(__LP64__) && !defined(_WIN64) && !defined(_LIBCXXABI_ARM_EHABI) + // This is a new field to support C++11 exception_ptr. + // For binary compatibility it is placed where the compiler + // previously added padding to 64-bit align unwindHeader. + std::/*MODIFIED*/ size_t referenceCount; +#endif + _Unwind_Exception unwindHeader; +}; +} + +#endif + +namespace emscriptencxxabi +{ +OUString toUnoName(char const* name); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/static/StaticLibrary_unoembind.mk b/static/StaticLibrary_unoembind.mk index e2a2bfacd892..754c591c4efb 100644 --- a/static/StaticLibrary_unoembind.mk +++ b/static/StaticLibrary_unoembind.mk @@ -22,6 +22,10 @@ $(eval $(call gb_StaticLibrary_use_api,unoembind,\ udkapi \ )) +$(eval $(call gb_StaticLibrary_use_static_libraries,unoembind, \ + emscriptencxxabi \ +)) + $(call gb_StaticLibrary_get_target,unoembind): $(call gb_CustomTarget_get_target,static/unoembind) # vim: set noet sw=4 ts=4: diff --git a/static/emscripten/uno.js b/static/emscripten/uno.js index cb7276d19489..bd1e693a6e77 100644 --- a/static/emscripten/uno.js +++ b/static/emscripten/uno.js @@ -15,6 +15,18 @@ Module.initUno = function() { } }; +Module.catchUnoException = function(exception) { + // Rethrow non-C++ exceptions (non-UNO C++ exceptions are mapped to css.uno.RuntimeException in + // Module.getUnoExceptionFromCxaException): + if (!(exception instanceof WebAssembly.Exception && exception.is(getCppExceptionTag()))) { + throw exception; + } + const any = Module.getUnoExceptionFromCxaException( + getCppExceptionThrownObjectFromWebAssemblyException(exception)); + decrementExceptionRefcount(exception); + return any; +} + Module.unoObject = function(interfaces, obj) { Module.initUno(); interfaces = ['com.sun.star.lang.XTypeProvider'].concat(interfaces); diff --git a/static/source/unoembindhelpers/PrimaryBindings.cxx b/static/source/unoembindhelpers/PrimaryBindings.cxx index a8c7acab9250..f1e57f281220 100644 --- a/static/source/unoembindhelpers/PrimaryBindings.cxx +++ b/static/source/unoembindhelpers/PrimaryBindings.cxx @@ -12,6 +12,7 @@ #include <emscripten.h> #include <emscripten/bind.h> +#include <bridges/emscriptencxxabi/cxxabi.hxx> #include <com/sun/star/uno/Any.hxx> #include <com/sun/star/uno/Reference.hxx> #include <com/sun/star/uno/RuntimeException.hpp> @@ -414,6 +415,28 @@ EMSCRIPTEN_BINDINGS(PrimaryBindings) css::uno::Reference<css::uno::XInterface> const& ref2) { return ref1 == ref2; }); function("rtl_uString_release", +[](std::uintptr_t ptr) { rtl_uString_release(reinterpret_cast<rtl_uString*>(ptr)); }); + function("getUnoExceptionFromCxaException", +[](std::uintptr_t ptr) { + // Cf. __get_exception_message in <https://github.com/emscripten-core/emscripten/>, + // system/lib/libcxxabi/src/cxa_exception_js_utils.cpp: + auto const header = reinterpret_cast<__cxxabiv1::__cxa_exception const*>(ptr) - 1; + css::uno::Any exc; + OUString unoName(emscriptencxxabi::toUnoName(header->exceptionType->name())); + typelib_TypeDescription* td = nullptr; + typelib_typedescription_getByName(&td, unoName.pData); + if (td == nullptr) + { + css::uno::RuntimeException e("exception type not found: " + unoName); + uno_type_any_construct( + &exc, &e, cppu::UnoType<css::uno::RuntimeException>::get().getTypeLibType(), + cpp_acquire); + } + else + { + uno_any_construct(&exc, reinterpret_cast<void*>(ptr), td, cpp_acquire); + typelib_typedescription_release(td); + } + return exc; + }); jsRegisterChar(&typeid(char16_t)); jsRegisterString(&typeid(OUString)); diff --git a/unotest/source/embindtest/embindtest.js b/unotest/source/embindtest/embindtest.js index 9f4276197c3d..d47fed4703ab 100644 --- a/unotest/source/embindtest/embindtest.js +++ b/unotest/source/embindtest/embindtest.js @@ -640,11 +640,11 @@ Module.addOnPostRun(function() { test.throwRuntimeException(); console.assert(false); } catch (e) { - const [type, message] = getExceptionMessage(e); - console.assert(type === 'com::sun::star::uno::RuntimeException'); - console.assert(message === undefined); //TODO - //TODO: verify css.uno.RuntimeException's Message startsWith('test') - decrementExceptionRefcount(e); + const any = Module.catchUnoException(e); + console.assert(any.getType() == 'com.sun.star.uno.RuntimeException'); + const exc = any.get(); + console.assert(exc.Message.startsWith('test')); + any.delete(); } const obj = Module.unoObject( ['com.sun.star.task.XJob', 'com.sun.star.task.XJobExecutor'], @@ -1086,11 +1086,14 @@ Module.addOnPostRun(function() { console.assert(false); ret.delete(); } catch (e) { - const [type, message] = getExceptionMessage(e); - console.assert(type === 'com::sun::star::reflection::InvocationTargetException'); - console.assert(message === undefined); //TODO - //TODO: inspect wrapped css.uno.RuntimeException - decrementExceptionRefcount(e); + const any = Module.catchUnoException(e); + console.assert(any.getType() == 'com.sun.star.reflection.InvocationTargetException'); + const target = any.get().TargetException; + console.assert(target.getType() == 'com.sun.star.uno.RuntimeException'); + const exc = target.get(); + console.assert(exc.Message.startsWith('test')); + any.delete(); + target.delete(); } params.delete(); outparamindex.delete();