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.bergmann@allotropia.de>
This commit is contained in:
Stephan Bergmann 2024-06-21 09:58:07 +02:00
parent 54f7308641
commit eedbe966bb
11 changed files with 238 additions and 132 deletions

View File

@ -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)

View File

@ -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)))

View File

@ -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:

View File

@ -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)

View File

@ -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

View File

@ -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: */

View File

@ -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: */

View File

@ -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:

View File

@ -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);

View File

@ -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));

View File

@ -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();