This is an automated email from the ASF dual-hosted git repository.
jimjag pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/openoffice.git
The following commit(s) were added to refs/heads/trunk by this push:
new 9ff3e22531 Synthesise UNO exception RTTI on macOS/arm64
9ff3e22531 is described below
commit 9ff3e22531fe821dc01eb80c3b7fce8ee067d164
Author: Jim Jagielski <[email protected]>
AuthorDate: Mon Aug 3 14:06:09 2026 -0400
Synthesise UNO exception RTTI on macOS/arm64
RTTI::getRTTI() in the arm64 C++-UNO bridge resolved exception
type_info via dlsym(RTLD_DEFAULT, "_ZTIN...E"). On arm64 Darwin this
can never succeed: clang emits the typeinfo of any keyless class -
which is every UNO exception - as hidden regardless of visibility
attributes, version scripts, or export lists, whereas the same class
gets exported typeinfo on x86_64 Darwin.
---
.../source/cpp_uno/s5abi_macosx_aarch64/except.cxx | 162 ++++++++++++++++-----
.../source/cpp_uno/s5abi_macosx_aarch64/share.hxx | 19 +++
main/solenv/bin/addsym-macosx.sh | 10 +-
main/solenv/src/component.map | 13 +-
4 files changed, 159 insertions(+), 45 deletions(-)
diff --git a/main/bridges/source/cpp_uno/s5abi_macosx_aarch64/except.cxx
b/main/bridges/source/cpp_uno/s5abi_macosx_aarch64/except.cxx
index 82ee649b72..238cdb2f68 100644
--- a/main/bridges/source/cpp_uno/s5abi_macosx_aarch64/except.cxx
+++ b/main/bridges/source/cpp_uno/s5abi_macosx_aarch64/except.cxx
@@ -29,6 +29,7 @@
#endif
#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>
#include <cxxabi.h>
@@ -85,6 +86,38 @@ Mutex & exceptionMapsMutex()
return mutex;
}
+// libc++ marks a type_info whose object is not unique across images by setting
+// the top bit of type_info::__type_name; comparison then falls back to strcmp
+// of the mangled name (see __non_unique_arm_rtti_bit_impl in <typeinfo>). On
+// arm64 Darwin clang emits the typeinfo of every keyless class -- which is
every
+// UNO exception -- hidden and therefore non-unique, so a synthesised object
must
+// set the bit too, or std::type_info::operator== degenerates to an address
+// comparison and never matches the handler's real typeinfo.
+sal_uIntPtr const NON_UNIQUE_RTTI_BIT =
+ static_cast< sal_uIntPtr >(1) << (8 * sizeof (sal_uIntPtr) - 1);
+
+RttiSiClassLayout const * siDonor()
+{
+ return reinterpret_cast< RttiSiClassLayout const * >(
&typeid(RttiDonorDerived) );
+}
+RttiClassLayout const * classDonor()
+{
+ return reinterpret_cast< RttiClassLayout const * >( &typeid(RttiDonorBase)
);
+}
+
+// Refuse to synthesise unless the donors really have the layout we assume.
+bool rttiDonorsUsable()
+{
+ return sizeof (void *) == 8
+ && siDonor()->pBase == static_cast< void const * >( classDonor() );
+}
+
+// Mirror the platform's own convention rather than assuming it.
+bool rttiIsNonUnique()
+{
+ return (siDonor()->nName & NON_UNIQUE_RTTI_BIT) != 0;
+}
+
}
void dummy_can_throw_anything( char const * )
@@ -129,6 +162,24 @@ static OUString toUNOname( char const * p ) SAL_THROW( () )
#endif
}
+//==================================================================================================
+static OString mangledRttiSymbol( OUString const & unoName ) SAL_THROW( () )
+{
+ OStringBuffer buf( 64 );
+ buf.append( RTL_CONSTASCII_STRINGPARAM("_ZTIN") );
+ sal_Int32 index = 0;
+ do
+ {
+ OUString token( unoName.getToken( 0, '.', index ) );
+ buf.append( token.getLength() );
+ OString c_token( OUStringToOString( token, RTL_TEXTENCODING_ASCII_US )
);
+ buf.append( c_token );
+ }
+ while (index >= 0);
+ buf.append( 'E' );
+ return buf.makeStringAndClear();
+}
+
//==================================================================================================
class RTTI
{
@@ -136,6 +187,11 @@ class RTTI
Mutex m_mutex;
t_rtti_map m_rttis;
+ t_rtti_map m_generatedRttis;
+
+ type_info * synthesiseRTTI(
+ OString const & rSymbolName,
+ typelib_CompoundTypeDescription * pTypeDescr ) SAL_THROW( () );
public:
RTTI() SAL_THROW( () );
@@ -157,62 +213,92 @@ RTTI::~RTTI() SAL_THROW( () )
//__________________________________________________________________________________________________
type_info * RTTI::getRTTI( typelib_CompoundTypeDescription *pTypeDescr )
SAL_THROW( () )
{
- type_info * rtti;
-
OUString const & unoName = *(OUString const *)&pTypeDescr->aBase.pTypeName;
+ // Recursive: synthesiseRTTI() re-enters getRTTI() for the base chain.
+ // osl::Mutex is a PTHREAD_MUTEX_RECURSIVE (sal/osl/unx/mutex.c), so this
+ // is safe. Lock order against exceptionMapsMutex() is unchanged.
MutexGuard guard( m_mutex );
{
MutexGuard observedGuard( exceptionMapsMutex() );
ObservedRttiMap::const_iterator observed( observedRttis().find(
unoName ) );
if ( observed != observedRttis().end() )
- return observed->second;
+ return observed->second; // a real typeinfo always wins
}
t_rtti_map::const_iterator iFind( m_rttis.find( unoName ) );
- if (iFind == m_rttis.end())
+ if (iFind != m_rttis.end())
+ return iFind->second;
+
+ OString symName( mangledRttiSymbol( unoName ) );
+ type_info * rtti = static_cast<std::type_info *>(dlsym( RTLD_DEFAULT,
symName.getStr() ));
+ if (rtti != 0)
{
- // RTTI symbol
- OStringBuffer buf( 64 );
- buf.append( RTL_CONSTASCII_STRINGPARAM("_ZTIN") );
- sal_Int32 index = 0;
- do
- {
- OUString token( unoName.getToken( 0, '.', index ) );
- buf.append( token.getLength() );
- OString c_token( OUStringToOString( token,
RTL_TEXTENCODING_ASCII_US ) );
- buf.append( c_token );
- }
- while (index >= 0);
- buf.append( 'E' );
+ m_rttis.insert( t_rtti_map::value_type( unoName, rtti ) );
+ return rtti;
+ }
- OString symName( buf.makeStringAndClear() );
- rtti = static_cast<std::type_info *>(dlsym( RTLD_DEFAULT,
symName.getStr() ));
+ t_rtti_map::const_iterator iGen( m_generatedRttis.find( unoName ) );
+ if (iGen != m_generatedRttis.end())
+ return iGen->second;
+
+ // On arm64 Darwin, clang emits the typeinfo of every keyless class (which
+ // is every UNO exception) hidden, so the dlsym() lookup above can never
+ // succeed here -- see solenv/src/component.map for the full explanation.
+ // Synthesise one instead of degrading straight to a RuntimeException.
+ rtti = synthesiseRTTI( symName, pTypeDescr );
+ if (rtti != 0)
+ m_generatedRttis.insert( t_rtti_map::value_type( unoName, rtti ) );
+ return rtti;
+}
- if (rtti)
- {
- m_rttis.insert( t_rtti_map::value_type( unoName, rtti ) );
- }
- else
- {
- // Unlike the gcc3_* bridges this one does NOT synthesise a
- // __class_type_info when the lookup fails. Hand-built type_info
- // objects are not reliably matched by libc++abi's throw/catch
- // machinery, so a miss is reported instead of silently producing
an
- // exception that no handler can catch. Lookup relies on the
- // typeinfo actually being exported: see the _ZTI*/_ZTS* entries in
- // solenv/src/component.map and the typeinfo carve-out in
- // solenv/bin/addsym-macosx.sh.
- rtti = 0;
- }
+//__________________________________________________________________________________________________
+type_info * RTTI::synthesiseRTTI(
+ OString const & rSymbolName,
+ typelib_CompoundTypeDescription * pTypeDescr ) SAL_THROW( () )
+{
+ if (! rttiDonorsUsable())
+ return 0; // keep the loud
RuntimeException fallback
+
+ type_info * pBaseRtti = 0;
+ if (pTypeDescr->pBaseTypeDescription != 0)
+ {
+ // The whole chain must resolve: libc++abi walks __base_type when
matching
+ // a handler for a base class and would dereference a null link.
+ pBaseRtti = getRTTI(
+ (typelib_CompoundTypeDescription *)
pTypeDescr->pBaseTypeDescription );
+ if (pBaseRtti == 0)
+ return 0;
}
- else
+
+ // The mangled type name is the symbol name without its "_ZTI" prefix.
+ char * pName = strdup( rSymbolName.getStr() + 4 );
+ if (pName == 0)
+ return 0;
+ sal_uIntPtr nName = reinterpret_cast< sal_uIntPtr >( pName );
+ if (rttiIsNonUnique())
+ nName |= NON_UNIQUE_RTTI_BIT;
+
+ // Deliberately never freed; these live for the life of the process
+ // (the module already builds with -DLEAK_STATIC_DATA).
+ if (pBaseRtti != 0)
{
- rtti = iFind->second;
+ RttiSiClassLayout * p = static_cast< RttiSiClassLayout * >(
+ calloc( 1, sizeof (RttiSiClassLayout) ) );
+ if (p == 0) { free( pName ); return 0; }
+ p->pVtable = siDonor()->pVtable;
+ p->nName = nName;
+ p->pBase = pBaseRtti;
+ return reinterpret_cast< type_info * >( p );
}
- return rtti;
+ RttiClassLayout * p = static_cast< RttiClassLayout * >(
+ calloc( 1, sizeof (RttiClassLayout) ) );
+ if (p == 0) { free( pName ); return 0; }
+ p->pVtable = classDonor()->pVtable;
+ p->nName = nName;
+ return reinterpret_cast< type_info * >( p );
}
//--------------------------------------------------------------------------------------------------
diff --git a/main/bridges/source/cpp_uno/s5abi_macosx_aarch64/share.hxx
b/main/bridges/source/cpp_uno/s5abi_macosx_aarch64/share.hxx
index aeac9e526f..cfc71d2869 100644
--- a/main/bridges/source/cpp_uno/s5abi_macosx_aarch64/share.hxx
+++ b/main/bridges/source/cpp_uno/s5abi_macosx_aarch64/share.hxx
@@ -32,6 +32,25 @@ namespace CPPU_CURRENT_NAMESPACE
void dummy_can_throw_anything( char const * );
+// Donor types for RTTI synthesis. Their type_info objects are emitted by the
+// compiler, so they carry the real libc++abi vtables and the platform's own
+// uniqueness convention. They must stay ordinary namespace-scope classes with
+// no virtual functions and a single public non-virtual base -- exactly the
+// shape of a generated UNO exception -- so that typeid(RttiDonorDerived) is a
+// __si_class_type_info and typeid(RttiDonorBase) a __class_type_info.
+// Do not move them into an anonymous namespace.
+struct RttiDonorBase { sal_Int32 dummy; };
+struct RttiDonorDerived : public RttiDonorBase { sal_Int32 dummy2; };
+
+// Itanium ABI object layouts
(http://itanium-cxx-abi.github.io/cxx-abi/abi.html#rtti).
+// libc++abi does not publish __cxxabiv1::__class_type_info, and declaring a
+// look-alike class is not an option: it would get its own vtable, and
+// __class_type_info::can_catch() dynamic_casts the thrown type to the real
+// libc++abi class, so no typed handler would ever match. We therefore build
+// raw storage in the ABI layout and install a borrowed, genuine vtable.
+struct RttiClassLayout { void const * pVtable; sal_uIntPtr nName; };
+struct RttiSiClassLayout { void const * pVtable; sal_uIntPtr nName; void const
* pBase; };
+
extern "C" void *__cxa_allocate_exception(
std::size_t thrown_size ) throw();
extern "C" void __cxa_free_exception( void *thrown_exception ) throw();
diff --git a/main/solenv/bin/addsym-macosx.sh b/main/solenv/bin/addsym-macosx.sh
index ec1ca18332..2b2d5e9d3b 100755
--- a/main/solenv/bin/addsym-macosx.sh
+++ b/main/solenv/bin/addsym-macosx.sh
@@ -46,8 +46,12 @@ s#$#$#' | tr '\n' '|' | sed "s#|\$##" >$2
# symbols will erroneously be added to the generated export symbols list file.
# Typeinfo and typeinfo-name symbols (__ZTI*, __ZTS*) are exempt from that
# filter: they have vague linkage and legitimately carry the same value, and
-# the macOS/arm64 C++-UNO bridge resolves exception typeinfo via dlsym(), so
-# dropping them would silently degrade UNO exceptions to RuntimeExceptions.
-# See solenv/src/component.map.
+# exporting them is correct for cross-library catch/dynamic_cast on every
+# platform that has a real version script. It does not, however, rescue the
+# macOS/arm64 C++-UNO bridge's dlsym()-based exception lookup: clang emits the
+# typeinfo of every keyless class (every UNO exception) as hidden on arm64
+# Darwin no matter what an export list says, so that bridge instead falls back
+# to synthesising RTTI when dlsym() misses. See solenv/src/component.map and
+# bridges/source/cpp_uno/s5abi_macosx_aarch64/except.cxx.
awk -v SYMBOLSREGEXP="`cat $2`" '
match ($6,SYMBOLSREGEXP) > 0 && $6 !~ /_GLOBAL_/ { if (($2 != 1) && (($2 !=
"1f") || ($6 ~ /^__ZT[IS]/))) print $6 }'
diff --git a/main/solenv/src/component.map b/main/solenv/src/component.map
index 21ff867ffc..f09f6bd704 100644
--- a/main/solenv/src/component.map
+++ b/main/solenv/src/component.map
@@ -25,10 +25,15 @@
# "dynamic_cast" then fail to match. Exporting them restores the
one-definition
# behaviour that libraries without a version script already have.
#
-# The macOS/arm64 C++-UNO bridge additionally depends on this: it resolves
-# exception typeinfo with dlsym() rather than synthesising it, so an unexported
-# _ZTI symbol degrades a UNO exception into a RuntimeException. See
-# bridges/source/cpp_uno/s5abi_macosx_aarch64/except.cxx (RTTI::getRTTI) and
+# Exporting is also the right intent on macOS/arm64, where the C++-UNO bridge
+# resolves exception typeinfo with dlsym() first and falls back to synthesis
+# only if that fails. In practice it always falls back there: clang emits the
+# typeinfo of every keyless class (which is every UNO exception) as hidden on
+# arm64 Darwin regardless of any version script or export list, so these _ZTI*
+# entries do not reach dlsym() on that platform. Keep them anyway -- they are
+# correct and load-bearing for every other target -- and see
+# bridges/source/cpp_uno/s5abi_macosx_aarch64/except.cxx (RTTI::getRTTI and
+# RTTI::synthesiseRTTI) for how arm64 copes without them, and
# solenv/bin/addsym-macosx.sh.
UDK_3_0_0 {
global: