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:

Reply via email to