Change v4 exceptions to use the common C++ ABIs foreign exceptions
authorSimon Hausmann <simon.hausmann@digia.com>
Tue, 1 Oct 2013 13:10:33 +0000 (15:10 +0200)
committerThe Qt Project <gerrit-noreply@qt-project.org>
Thu, 3 Oct 2013 07:24:32 +0000 (09:24 +0200)
On platforms where we use the common C++ ABI, throw the exception not using a
dummy C++ exception structure and the throw keyboard, but instead use the
lower-level _Unwind_RaiseException to throw a foreign exception.  It is caught
with the existing "catch (...)" and re-throw is implemented similarly, by
grabbing the current exception from the globals (a standardized data structure)
and re-throwing it.

On platforms such as ARM that lack hooks for supplying our unwind tables to the
system run-time, this patch will make it possible to link the unwinder
statically into libQtQml (libgcc or libunwind) and thus force it to use our
unwind tables, because throwing or re-throwing will always go through our
statically linked code through direct calls to _Unwind_RaiseException (instead
of libstdc++).

Change-Id: Ic2ac056fc7ed9e93fb51e30ab45f35b260487c5f
Reviewed-by: Lars Knoll <lars.knoll@digia.com>
src/qml/jsruntime/jsruntime.pri
src/qml/jsruntime/qv4context.cpp
src/qml/jsruntime/qv4exception.cpp
src/qml/jsruntime/qv4exception_gcc.cpp
src/qml/jsruntime/qv4exception_p.h

index 1739765..88cd3a9 100644 (file)
@@ -112,12 +112,11 @@ linux*|mac {
     LIBS += -ldl
 }
 
-# Only on Android/ARM at the moment, because only there we have issues
-# replacing __gnu_Unwind_Find_exidx with our own implementation,
-# and thus require static libgcc linkage.
-android:equals(QT_ARCH, "arm"):*g++* {
-    static_libgcc = $$system($$QMAKE_CXX -print-file-name=libgcc.a)
-    LIBS += $$static_libgcc
+!win32:!ios {
+    *g++*:equals(QT_ARCH, "arm") {
+        static_libgcc = $$system($$QMAKE_CXX -print-file-name=libgcc.a)
+        LIBS += $$static_libgcc
+    }
     SOURCES += $$PWD/qv4exception_gcc.cpp
     DEFINES += V4_CXX_ABI_EXCEPTION
 }
index 78bf662..4bba0bf 100644 (file)
@@ -648,7 +648,7 @@ void ExecutionContext::throwUnimplemented(const QString &message)
 ReturnedValue ExecutionContext::catchException(StackTrace *trace)
 {
     if (!engine->hasException)
-        throw;
+        Exception::rethrow();
     while (engine->current != this)
         engine->popContext();
     if (trace)
@@ -666,7 +666,7 @@ void ExecutionContext::rethrowException()
         while (engine->current != this)
             engine->popContext();
     }
-    throw;
+    Exception::rethrow();
 }
 
 void ExecutionContext::throwReferenceError(const ValueRef value)
index f55dc94..8cee153 100644 (file)
@@ -98,6 +98,11 @@ void Exception::throwException(ExecutionContext *context, const ValueRef value)
 }
 
 #if !defined(V4_CXX_ABI_EXCEPTION)
+void Exception::rethrow()
+{
+    throw;
+}
+
 struct DummyException
 {};
 
index 0d230ea..d3c406f 100644 (file)
 
 #include "qv4exception_p.h"
 
-#include <private/qv4scopedvalue_p.h>
+// On arm we link libgcc statically and want to avoid exporting the _Unwind* symbols
+#if defined(Q_PROCESSOR_ARM)
+#define HIDE_EXPORTS
+#endif
+
 #include <unwind.h>
-#include <cxxabi.h>
-#include <bits/atomic_word.h>
-#include <typeinfo>
 #include <exception>
 
-/*
- * This is a little bit hacky as it relies on the fact that exceptions are
- * reference counted in libstdc++ and that affects the layout of the standardized
- * cxa_exception, making it bigger. LLVM's libcxxabi stores the reference count
- * differently, so this here is entirely GNU libstdc++ specific.
- *
- * Eliminating this dependency is doable but requires replacing the use of C++ exceptions
- * with foreign exceptions (a different exception class) and then using __cxa_get_globals
- * to get hold of the exception inside the catch (...). AFAICS that would be portable.
- */
-
 namespace {
 
 // 2.1.1 from http://mentorembedded.github.io/cxx-abi/abi-eh.html
@@ -82,67 +72,63 @@ struct cxa_exception {
     _Unwind_Exception unwindHeader;
 };
 
-// This is what libstdc++ actually allocates
-struct gcc_refcounted_compatible_exception {
-    _Atomic_word refCount;
-    cxa_exception x;
+struct cxa_eh_globals
+{
+    cxa_exception *caughtExceptions;
+    unsigned int uncaughtExceptions;
+#ifdef __ARM_EABI_UNWINDER__
+    cxa_exception* propagatingExceptions;
+#endif
 };
 
 }
 
+extern "C" cxa_eh_globals *__cxa_get_globals();
+
 static void exception_cleanup(_Unwind_Reason_Code, _Unwind_Exception *ex)
 {
-    gcc_refcounted_compatible_exception *exception = reinterpret_cast<gcc_refcounted_compatible_exception *>(ex + 1) - 1;
-    if (!--exception->refCount) {
-        if (exception->x.exceptionDestructor)
-            exception->x.exceptionDestructor(ex + 1);
-        abi::__cxa_free_exception(ex + 1);
-    }
+    free(ex);
 }
 
-struct DummyException
-{
-    virtual ~DummyException() {}
-};
+QT_BEGIN_NAMESPACE
 
-static void exception_destructor(void *ex)
+using namespace QV4;
+
+void Exception::rethrow()
 {
-    reinterpret_cast<DummyException *>(ex)->~DummyException();
-}
+    cxa_eh_globals *globals = __cxa_get_globals();
+    cxa_exception *exception = globals->caughtExceptions;
 
-QT_BEGIN_NAMESPACE
+    // Make sure we only re-throw our foreign exceptions. For general re-throw
+    // we'd need different code.
+#ifndef __ARM_EABI_UNWINDER__
+    Q_ASSERT(exception->unwindHeader.exception_class == 0x514d4c4a53563400); // QMLJSV40
+#endif
 
-using namespace QV4;
+    globals->caughtExceptions = 0;
+    _Unwind_RaiseException(&exception->unwindHeader);
+}
 
 void Exception::throwInternal()
 {
-    void *rawException = abi::__cxa_allocate_exception(sizeof(QV4::Exception));
-    gcc_refcounted_compatible_exception *refCountedException = reinterpret_cast<gcc_refcounted_compatible_exception *>(rawException) - 1;
-    cxa_exception *exception = &refCountedException->x;
-
-    (void)new (rawException) DummyException();
-
-    refCountedException->refCount = 1;
-    exception->typeInfo = const_cast<std::type_info*>(&typeid(DummyException));
-    exception->exceptionDestructor = &exception_destructor;
-    exception->unexpectedHandler = std::unexpected;
-    exception->terminateHandler = std::terminate;
-    exception->unwindHeader.exception_cleanup = &exception_cleanup;
+    _Unwind_Exception *exception = (_Unwind_Exception*)malloc(sizeof(_Unwind_Exception));
+    memset(exception, 0, sizeof(*exception));
+    exception->exception_cleanup = &exception_cleanup;
+
 #ifdef __ARM_EABI_UNWINDER__
-    exception->unwindHeader.exception_class[0] = 'G';
-    exception->unwindHeader.exception_class[1] = 'N';
-    exception->unwindHeader.exception_class[2] = 'U';
-    exception->unwindHeader.exception_class[3] = 'C';
-    exception->unwindHeader.exception_class[4] = 'C';
-    exception->unwindHeader.exception_class[5] = '+';
-    exception->unwindHeader.exception_class[6] = '+';
-    exception->unwindHeader.exception_class[7] = 0;
+    exception->exception_class[0] = 'Q';
+    exception->exception_class[1] = 'M';
+    exception->exception_class[2] = 'L';
+    exception->exception_class[3] = 'J';
+    exception->exception_class[4] = 'S';
+    exception->exception_class[5] = 'V';
+    exception->exception_class[6] = '4';
+    exception->exception_class[7] = 0;
 #else
-    exception->unwindHeader.exception_class = 0x474e5543432b2b00; // GNUCC++0
+    exception->exception_class = 0x514d4c4a53563400; // QMLJSV40
 #endif
 
-    _Unwind_RaiseException(&exception->unwindHeader);
-    abi::__cxa_begin_catch(rawException);
+    _Unwind_RaiseException(exception);
     std::terminate();
 }
 
index 1f02d11..8889630 100644 (file)
@@ -51,6 +51,7 @@ namespace QV4 {
 
 struct Q_QML_EXPORT Exception {
     static void Q_NORETURN throwException(ExecutionContext *throwingContext, const ValueRef exceptionValue);
+    static void Q_NORETURN rethrow();
 
 private:
     static void Q_NORETURN throwInternal();