瀏覽代碼

Merge branch '181-add-ctkException-class'

* 181-add-ctkException-class:
  Added support for backtraces in CTK exceptions.
  Fixed rethrow method to work polymorphically.
  Added a getter for the exception message.
Sascha Zelzer 13 年之前
父節點
當前提交
7c1d820279

+ 8 - 0
Libs/Core/CMakeLists.txt

@@ -30,6 +30,7 @@ set(KIT_SRCS
   ctkAbstractQObjectFactory.tpp
   ctkAbstractLibraryFactory.h
   ctkAbstractLibraryFactory.tpp
+  ctkBackTrace.cpp
   ctkBooleanMapper.cpp
   ctkBooleanMapper.h
   ctkCallback.cpp
@@ -129,6 +130,13 @@ ctkMacroBuildLib(
   LIBRARY_TYPE ${CTK_LIBRARY_MODE}
   )
 
+# Needed for ctkBackTrace
+if(UNIX)
+  target_link_libraries(${PROJECT_NAME} dl)
+elseif(WIN32 AND NOT MINGW)
+  target_link_libraries(${PROJECT_NAME} dbghelp)
+endif()
+
 if(CTK_WRAP_PYTHONQT_FULL OR CTK_WRAP_PYTHONQT_LIGHT)
   ctkMacroBuildLibWrapper(
     TARGET ${PROJECT_NAME}

+ 5 - 0
Libs/Core/Testing/Cpp/CMakeLists.txt

@@ -1,5 +1,6 @@
 # Dummy plugin used by ctkAbstractPluginFactoryTest1
 add_definitions( -DCTKDummyPlugin)
+
 ctkMacroBuildLib(
   NAME "CTKDummyPlugin"
   EXPORT_DIRECTIVE "CTK_DUMMY_EXPORT"
@@ -22,6 +23,7 @@ set(KITTests_SRCS
   ctkAbstractObjectFactoryTest1.cpp
   ctkAbstractPluginFactoryTest1.cpp
   ctkAbstractQObjectFactoryTest1.cpp
+  ctkBackTraceTest.cpp
   ctkBooleanMapperTest.cpp
   ctkCallbackTest1.cpp
   ctkCheckableModelHelperTest1.cpp
@@ -33,6 +35,7 @@ set(KITTests_SRCS
   ctkErrorLogFDMessageHandlerWithThreadsTest1.cpp
   ctkErrorLogQtMessageHandlerWithThreadsTest1.cpp
   ctkErrorLogStreamMessageHandlerWithThreadsTest1.cpp
+  ctkExceptionTest.cpp
   ctkHistogramTest1.cpp
   ctkLoggerTest1.cpp
   ctkModelTesterTest1.cpp
@@ -126,6 +129,7 @@ SIMPLE_TEST( ctkAbstractLibraryFactoryTest1 ${ctkDummyPluginPATH} )
 SIMPLE_TEST( ctkAbstractObjectFactoryTest1 )
 SIMPLE_TEST( ctkAbstractPluginFactoryTest1 ${ctkDummyPluginPATH} )
 SIMPLE_TEST( ctkAbstractQObjectFactoryTest1 )
+SIMPLE_TEST( ctkBackTraceTest )
 if(HAVE_BFD)
   SIMPLE_TEST( ctkBinaryFileDescriptorTest1 $<TARGET_FILE:ctkBinaryFileDescriptorTestHelper> )
 endif()
@@ -142,6 +146,7 @@ SIMPLE_TEST( ctkErrorLogModelTest4 )
 SIMPLE_TEST( ctkErrorLogFDMessageHandlerWithThreadsTest1 )
 SIMPLE_TEST( ctkErrorLogQtMessageHandlerWithThreadsTest1 )
 SIMPLE_TEST( ctkErrorLogStreamMessageHandlerWithThreadsTest1 )
+SIMPLE_TEST( ctkExceptionTest )
 SIMPLE_TEST( ctkHistogramTest1 )
 SIMPLE_TEST( ctkLoggerTest1 )
 set_property(TEST ctkLoggerTest1 PROPERTY PASS_REGULAR_EXPRESSION "logger.debug\nlogger.info\nlogger.trace\nlogger.warn\nlogger.error\nlogger.fatal")

+ 64 - 0
Libs/Core/Testing/Cpp/ctkBackTraceTest.cpp

@@ -0,0 +1,64 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) German Cancer Research Center,
+    Division of Medical and Biological Informatics
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=========================================================================*/
+
+#include <ctkBackTrace.h>
+
+#include <QDebug>
+
+//-----------------------------------------------------------------------------
+void Q_DECL_EXPORT bt_func1()
+{
+  ctkBackTrace bt;
+  qDebug() << bt.stackTrace();
+
+  if (!bt.stackFrame(1).contains("ctkBackTrace"))
+  {
+    qCritical() << "Stack frame for ctkBackTrace::ctkBackTrace(...) missing";
+    exit(EXIT_FAILURE);
+  }
+
+  if (!bt.stackFrame(2).contains("bt_func1"))
+  {
+    qCritical() << "Stack frame for bt_func1() missing";
+    exit(EXIT_FAILURE);
+  }
+
+  if (!bt.stackFrame(3).contains("bt_func2"))
+  {
+    qCritical() << "Stack frame for bt_func2() missing";
+    exit(EXIT_FAILURE);
+  }
+}
+
+//-----------------------------------------------------------------------------
+void Q_DECL_EXPORT bt_func2()
+{
+  bt_func1();
+}
+
+
+//-----------------------------------------------------------------------------
+int ctkBackTraceTest(int /*argc*/, char* /*argv*/[])
+{
+  bt_func2();
+
+  return EXIT_SUCCESS;
+}

+ 106 - 0
Libs/Core/Testing/Cpp/ctkExceptionTest.cpp

@@ -0,0 +1,106 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) German Cancer Research Center,
+    Division of Medical and Biological Informatics
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=========================================================================*/
+
+#include <ctkException.h>
+
+#include <QDebug>
+
+#include <cstring>
+#include <iostream>
+
+//-----------------------------------------------------------------------------
+void exc_func1()
+{
+  throw ctkException("My exception");
+}
+
+//-----------------------------------------------------------------------------
+void exc_func2()
+{
+  try {
+    exc_func1();
+  }
+  catch (const ctkException& exc)
+  {
+    throw ctkRuntimeException("runtime test error", exc);
+  }
+}
+
+//-----------------------------------------------------------------------------
+int ctkExceptionTest(int /*argc*/, char* /*argv*/[])
+{
+  try
+  {
+    exc_func2();
+  }
+  catch (const ctkException& exc)
+  {
+	  qDebug() << exc.printStackTrace();
+    if (std::strcmp(exc.name(), "ctkRuntimeException"))
+    {
+      std::cerr << "Exception name mismatch." << std::endl;
+      return EXIT_FAILURE;
+    }
+
+    if (std::strcmp(exc.what(), "ctkRuntimeException: runtime test error"))
+    {
+      std::cerr << "Exception what() mismatch." << std::endl;
+      return EXIT_FAILURE;
+    }
+
+    if(exc.message() != "runtime test error")
+    {
+      std::cerr << "Exception message mismatch." << std::endl;
+      return EXIT_FAILURE;
+    }
+
+    if(std::strcmp(exc.cause()->name(), "ctkException"))
+    {
+      std::cerr << "Exception cause mismatch." << std::endl;
+      return EXIT_FAILURE;
+    }
+
+    QList<QString> trace = exc.stackTrace();
+    ctkException* clonedExc = exc.clone();
+    if (trace != clonedExc->stackTrace())
+    {
+      std::cerr << "Cloned exception stack trace mismatch." << std::endl;
+      return EXIT_FAILURE;
+    }
+
+    try
+    {
+      clonedExc->rethrow();
+    }
+    catch (const ctkRuntimeException&)
+    {
+      delete clonedExc;
+    }
+    catch (const ctkException&)
+    {
+      std::cerr << "Wrong exception type rethrown" << std::endl;
+      delete clonedExc;
+      return EXIT_FAILURE;
+    }
+  }
+
+  return EXIT_SUCCESS;
+}

+ 323 - 0
Libs/Core/ctkBackTrace.cpp

@@ -0,0 +1,323 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) German Cancer Research Center,
+    Division of Medical and Biological Informatics
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=========================================================================*/
+
+//
+// Code taken from http://thread.gmane.org/gmane.comp.lib.boost.devel/209982
+// and modified for CTK.
+//
+// Original Copyright (c) 2010 Artyom Beilis (Tonkikh)
+//
+// Distributed under the Boost Software License, Version 1.0.
+// (See http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include "ctkBackTrace.h"
+
+#include <QList>
+
+#include <vector>
+
+#if defined(__linux) || defined(__APPLE__) || defined(__sun)
+#define CTK_HAVE_EXECINFO
+#define CTK_HAVE_DLADDR
+#endif
+
+#if defined(__GNUC__)
+#define CTK_HAVE_ABI_CXA_DEMANGLE
+#endif
+
+#ifdef CTK_HAVE_EXECINFO
+#include <execinfo.h>
+#endif
+
+#ifdef CTK_HAVE_ABI_CXA_DEMANGLE
+#include <cxxabi.h>
+#endif
+
+#ifdef CTK_HAVE_DLADDR
+#include <dlfcn.h>
+#endif
+
+#include <stdlib.h>
+#include <sstream>
+
+#if defined(Q_CC_MSVC)
+#include <windows.h>
+#include <stdlib.h>
+#include <dbghelp.h>
+#endif
+
+// --------------------------------------------------------------------------
+size_t const ctkBackTrace::DefaultStackSize = 32;
+
+// --------------------------------------------------------------------------
+struct ctkBackTracePrivate
+{
+  std::vector<void *> Frames;
+
+  int trace(void** addresses, int size) const;
+  std::string getSymbol(void* address) const;
+};
+
+// --------------------------------------------------------------------------
+ctkBackTrace::ctkBackTrace(const ctkBackTrace& other)
+  : d(new ctkBackTracePrivate(*other.d.data()))
+{
+}
+
+ctkBackTrace::ctkBackTrace(size_t framesNumber)
+  : d(new ctkBackTracePrivate)
+{
+  if(framesNumber == 0)
+    return;
+  d->Frames.resize(framesNumber, 0);
+  int size = d->trace(&d->Frames.front(), framesNumber);
+  d->Frames.resize(size);
+}
+
+// --------------------------------------------------------------------------
+ctkBackTrace::~ctkBackTrace() throw()
+{
+}
+
+// --------------------------------------------------------------------------
+size_t ctkBackTrace::stackSize() const
+{
+  return d->Frames.size();
+}
+
+// --------------------------------------------------------------------------
+void* ctkBackTrace::returnAddress(unsigned frameNumber) const
+{
+  if(frameNumber < stackSize())
+    return d->Frames[frameNumber];
+  return 0;
+}
+
+// --------------------------------------------------------------------------
+QString ctkBackTrace::stackFrame(unsigned frameNumber) const
+{
+  if(frameNumber < d->Frames.size())
+    return QString::fromStdString(d->getSymbol(d->Frames[frameNumber]));
+  return QString();
+}
+
+// --------------------------------------------------------------------------
+QList<QString> ctkBackTrace::stackTrace() const
+{
+  QList<QString> trace;
+
+  if(d->Frames.empty())
+    return trace;
+
+  for (std::size_t i = 0; i < d->Frames.size(); ++i)
+  {
+    std::string s = d->getSymbol(d->Frames[i]);
+    if (!s.empty())
+    {
+      trace.push_back(QString::fromStdString(s));
+    }
+  }
+
+  return trace;
+
+  //std::ostringstream res;
+  //d->writeSymbols(&d->Frames.front(), d->Frames.size(), res, framePrefix.toStdString());
+  //return QString::fromStdString(res.str());
+}
+
+#if defined(CTK_HAVE_EXECINFO)
+
+// --------------------------------------------------------------------------
+int ctkBackTracePrivate::trace(void** array, int n) const
+{
+  return :: backtrace(array,n);
+}
+
+#elif defined(Q_CC_MSVC)
+
+// --------------------------------------------------------------------------
+int ctkBackTracePrivate::trace(void** array, int n) const
+{
+  if(n>=63)
+    n=62;
+  return RtlCaptureStackBackTrace(0, n, array, 0);
+}
+
+#else
+
+// --------------------------------------------------------------------------
+int ctkBackTracePrivate::trace(void** /*array*/, int /*n*/) const
+{
+  return 0;
+}
+
+#endif
+
+#if defined(CTK_HAVE_DLADDR) && defined(CTK_HAVE_ABI_CXA_DEMANGLE)
+
+// --------------------------------------------------------------------------
+std::string ctkBackTracePrivate::getSymbol(void* ptr) const
+{
+  if(!ptr)
+    return std::string();
+
+  std::ostringstream res;
+  res.imbue(std::locale::classic());
+  res << ptr << ": ";
+  Dl_info info = {0,0,0,0};
+  if(dladdr(ptr,&info) == 0)
+  {
+    res << "???";
+  }
+  else
+  {
+    if(info.dli_sname)
+    {
+      int status = 0;
+      char *demangled = abi::__cxa_demangle(info.dli_sname, 0, 0, &status);
+      if(demangled)
+      {
+        res << demangled;
+        free(demangled);
+      }
+      else
+      {
+        res << info.dli_sname;
+      }
+    }
+    else
+    {
+      res << "???";
+    }
+
+    unsigned offset = reinterpret_cast<char*>(ptr) - reinterpret_cast<char*>(info.dli_saddr);
+    res << std::hex <<" + 0x" << offset ;
+
+    if(info.dli_fname)
+      res << " in " << info.dli_fname;
+  }
+  return res.str();
+}
+
+#elif defined(CTK_HAVE_EXECINFO)
+// --------------------------------------------------------------------------
+std::string ctkBackTracePrivate::getSymbol(void *address) const
+{
+  char ** ptr = backtrace_symbols(&address, 1);
+  try
+  {
+    if(ptr == 0)
+      return std::string();
+    std::string res = ptr[0];
+    free(ptr);
+    ptr = 0;
+    return res;
+  }
+  catch(...)
+  {
+    free(ptr);
+    throw;
+  }
+}
+
+#elif defined(Q_CC_MSVC)
+
+// --------------------------------------------------------------------------
+namespace {
+HANDLE hProcess = 0;
+bool syms_ready = false;
+}
+
+// --------------------------------------------------------------------------
+namespace ctk {
+bool DebugSymInitialize()
+{
+  if(hProcess == 0)
+  {
+    hProcess = GetCurrentProcess();
+    SymSetOptions(SYMOPT_DEFERRED_LOADS);
+
+    if (SymInitialize(hProcess, NULL, TRUE))
+    {
+      syms_ready = true;
+    }
+  }
+  return syms_ready;
+}
+}
+
+// --------------------------------------------------------------------------
+std::string ctkBackTracePrivate::getSymbol(void* ptr) const
+{
+  if(ptr==0)
+    return std::string();
+
+  ctk::DebugSymInitialize();
+  std::ostringstream ss;
+  ss.imbue(std::locale::classic());
+  ss << ptr;
+  if(syms_ready)
+  {
+    DWORD64  dwDisplacement = 0;
+    DWORD64  dwAddress = (DWORD64)ptr;
+
+    std::vector<char> buffer(sizeof(SYMBOL_INFO) + MAX_SYM_NAME);
+    PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)&buffer.front();
+
+    pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
+    pSymbol->MaxNameLen = MAX_SYM_NAME;
+
+    if (SymFromAddr(hProcess, dwAddress, &dwDisplacement, pSymbol))
+    {
+      ss <<": " << pSymbol->Name << std::hex << " + 0x" << dwDisplacement;
+    }
+    else
+    {
+      ss << ": ???";
+    }
+
+    std::vector<char> moduleBuffer(sizeof(IMAGEHLP_MODULE64));
+    PIMAGEHLP_MODULE64 pModuleInfo = (PIMAGEHLP_MODULE64)&moduleBuffer.front();
+    pModuleInfo->SizeOfStruct = sizeof(IMAGEHLP_MODULE64);
+    if (SymGetModuleInfo64(hProcess, pSymbol->ModBase, pModuleInfo))
+    {
+      ss << " in " << pModuleInfo->LoadedImageName;
+    }
+  }
+  return ss.str();
+}
+
+#else
+
+// --------------------------------------------------------------------------
+std::string ctkBackTracePrivate::getSymbol(void* ptr) const
+{
+  if(!ptr)
+    return std::string();
+
+  std::ostringstream res;
+  res.imbue(std::locale::classic());
+  res << ptr;
+  return res.str();
+}
+
+#endif

+ 102 - 0
Libs/Core/ctkBackTrace.h

@@ -0,0 +1,102 @@
+/*=============================================================================
+
+  Library: CTK
+
+  Copyright (c) German Cancer Research Center,
+    Division of Medical and Biological Informatics
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=============================================================================*/
+
+#ifndef __ctkBackTrace_h
+#define __ctkBackTrace_h
+
+// CTK includes
+#include <ctkCoreExport.h>
+
+// Qt includes
+#include <QScopedPointer>
+#include <QString>
+
+
+struct ctkBackTracePrivate;
+
+//---------------------------------------------------------------------------
+/**
+ * \ingroup Core
+ *
+ * \brief Obtains a back trace from the current execution context.
+ *
+ * \remarks It is generally not safe to use this class in signal handlers.
+ */
+class CTK_CORE_EXPORT ctkBackTrace
+{
+public:
+
+  static size_t const DefaultStackSize;
+
+  ctkBackTrace(const ctkBackTrace& other);
+
+  /**
+   * \brief Create a back trace.
+   * \param framesNumber The default maximum stack size.
+   */
+  ctkBackTrace(size_t framesNumber = DefaultStackSize);
+
+  virtual ~ctkBackTrace() throw();
+
+  /**
+   * @brief Get the stack size.
+   * @return The number of stack frames for this back trace.
+   */
+  size_t stackSize() const;
+
+  /**
+   * @brief Get the return address for a given stack frame.
+   * @param frameNumber The stack frame number.
+   * @return The return address for the stack frame with number <code>frameNumber</code>
+   *         or <code>NULL</code> if there is no corresponding stack frame.
+   */
+  void* returnAddress(unsigned frameNumber) const;
+
+  /**
+   * @brief Get a textual representation for a given stack frame.
+   * @param frameNumber The stack frame number.
+   * @return A string describing the stack frame with number <code>frameNumber<code>
+   *         or a null QString if there is no corresponding stack frame.
+   */
+  QString stackFrame(unsigned frameNumber) const;
+
+  /**
+   * @brief Provides programmatic access to the stack trace information.
+   *
+   * The zeroth element of the returned list (assuming the list's size is non-zero)
+   * represents the top of the stack, which is the last method invocation in the sequence.
+   *
+   * @return A list of string representations for each stack frame.
+   */
+  QList<QString> stackTrace() const;
+
+private:
+
+  QScopedPointer<ctkBackTracePrivate> d;
+};
+
+#ifdef Q_CC_MSVC
+namespace ctk {
+CTK_CORE_EXPORT bool DebugSymInitialize();
+}
+#endif
+
+#endif // __ctkBackTrace_h

+ 96 - 5
Libs/Core/ctkException.cpp

@@ -27,6 +27,20 @@
 #include <QDebug>
 
 // --------------------------------------------------------------------------
+ctkException::TraceManipulator::TraceManipulator(const ctkException* e)
+  : Exc(e)
+{
+}
+
+// --------------------------------------------------------------------------
+QDebug ctkException::TraceManipulator::print(QDebug dbg) const
+{
+  if (Exc)
+    Exc->printStackTrace(dbg);
+  return dbg.maybeSpace();
+}
+
+// --------------------------------------------------------------------------
 ctkException::ctkException(const QString& msg)
   : Msg(msg), NestedException(0)
 {
@@ -40,7 +54,7 @@ ctkException::ctkException(const QString& msg, const ctkException& cause)
 
 // --------------------------------------------------------------------------
 ctkException::ctkException(const ctkException& exc)
-  : std::exception(exc), Msg(exc.Msg)
+  : std::exception(exc), ctkBackTrace(exc), Msg(exc.Msg)
 {
   NestedException = exc.NestedException ? exc.NestedException->clone() : 0;
 }
@@ -98,14 +112,22 @@ const char* ctkException::what() const throw()
     txt += ": ";
     txt += Msg.toStdString();
   }
-  if (NestedException)
-  {
-    txt +=  std::string("\n  Caused by: ") + NestedException->what();
-  }
   return txt.c_str();
 }
 
 // --------------------------------------------------------------------------
+QString ctkException::message() const throw()
+{
+  return Msg;
+}
+
+// --------------------------------------------------------------------------
+ctkException::TraceManipulator ctkException::printStackTrace() const
+{
+  return TraceManipulator(this);
+}
+
+// --------------------------------------------------------------------------
 ctkException* ctkException::clone() const
 {
   return new ctkException(*this);
@@ -117,6 +139,69 @@ void ctkException::rethrow() const
   throw *this;
 }
 
+// --------------------------------------------------------------------------
+QDebug ctkException::printStackTrace(QDebug dbg) const
+{
+  QSet<const ctkException*> dejaVu;
+  dejaVu.insert(this);
+
+  // Print our stack trace
+  dbg.nospace() << this->what() << '\n';
+  QList<QString> trace = stackTrace();
+  foreach(QString traceElement, trace)
+  {
+    dbg.nospace() << "\tat " << qPrintable(traceElement) << '\n';
+  }
+
+  // Print cause, if any
+  if (NestedException)
+  {
+    NestedException->printEnclosedStackTrace(dbg, trace, "Caused by: ", "", dejaVu);
+  }
+  return dbg;
+}
+
+// --------------------------------------------------------------------------
+void ctkException::printEnclosedStackTrace(QDebug dbg, const QList<QString>& enclosingTrace,
+                                           const QString& caption, const QString& prefix,
+                                           QSet<const ctkException*>& dejaVu)
+{
+  if (dejaVu.contains(this))
+  {
+    dbg.nospace() << "\t[CIRCULAR REFERENCE:" << this->what() << "]\n";
+  }
+  else
+  {
+    dejaVu.insert(this);
+    // Compute number of frames in common between this and enclosing trace
+    QList<QString> trace = stackTrace();
+    int m = trace.size() - 1;
+    int n = enclosingTrace.size() - 1;
+    while (m >= 0 && n >=0 && trace[m] == enclosingTrace[n])
+    {
+      m--; n--;
+    }
+    int framesInCommon = trace.size() - 1 - m;
+
+    // Print our stack trace
+    dbg.nospace() << qPrintable(prefix) << qPrintable(caption) << this->what() << '\n';
+    for (int i = 0; i <= m; i++)
+    {
+      dbg.nospace() << qPrintable(prefix) << "\tat " << qPrintable(trace[i]) << '\n';
+    }
+    if (framesInCommon != 0)
+    {
+      dbg.nospace() << qPrintable(prefix) << "\t... " << framesInCommon << " more\n";
+    }
+
+    // Print cause, if any
+    if (NestedException)
+    {
+      NestedException->printEnclosedStackTrace(dbg, trace, "Caused by: ", prefix, dejaVu);
+    }
+  }
+}
+
 //----------------------------------------------------------------------------
 QDebug operator<<(QDebug dbg, const ctkException& exc)
 {
@@ -124,6 +209,12 @@ QDebug operator<<(QDebug dbg, const ctkException& exc)
   return dbg.maybeSpace();
 }
 
+//----------------------------------------------------------------------------
+QDebug operator<<(QDebug dbg, const ctkException::TraceManipulator& trace)
+{
+  return trace.print(dbg);
+}
+
 CTK_IMPLEMENT_EXCEPTION(ctkRuntimeException, ctkException, "ctkRuntimeException")
 CTK_IMPLEMENT_EXCEPTION(ctkInvalidArgumentException, ctkRuntimeException, "ctkInvalidArgumentException")
 CTK_IMPLEMENT_EXCEPTION(ctkIllegalStateException, ctkRuntimeException, "ctkIllegalStateException")

+ 64 - 2
Libs/Core/ctkException.h

@@ -25,9 +25,11 @@
 
 // Qt includes
 #include <QString>
+#include <QSet>
 
 // CTK includes
 #include <ctkCoreExport.h>
+#include <ctkBackTrace.h>
 
 //---------------------------------------------------------------------------
 /**
@@ -40,10 +42,23 @@
  *
  * ctkException classes can be copied, saved, and rethrown.
  */
-class CTK_CORE_EXPORT ctkException : public std::exception
+class CTK_CORE_EXPORT ctkException : public std::exception, public ctkBackTrace
 {
 public:
 
+  class TraceManipulator
+  {
+  public:
+
+    TraceManipulator(const ctkException* e);
+
+    QDebug print(QDebug dbg) const;
+
+  private:
+
+    const ctkException* Exc;
+  };
+
   /**
    * @brief Create a new ctkException.
    * @param msg The exception message.
@@ -103,6 +118,26 @@ public:
   virtual const char* what() const throw();
 
   /**
+   * @brief Returns the detail message string of this exception.
+   * @return The detail exception message.
+   */
+  QString message() const throw();
+
+  /**
+   * @brief Returns an object suitable for printing this executable
+   * and its backtrace via qDebug().
+   *
+   * Example usage:
+   * \code
+   * ctkException exc("My error");
+   * qDebug() << exc.printStackTrace();
+   * \endcode
+   *
+   * @return A helper object for streaming to qDebug().
+   */
+  TraceManipulator printStackTrace() const;
+
+  /**
    * @brief Creates a copy of this exception. Use rethrow() to throw the
    * copy again.
    * @return A copy of this exception.
@@ -112,12 +147,28 @@ public:
   /**
    * @brief (Re)Throws this exception.
    */
-  void rethrow() const;
+  virtual void rethrow() const;
+
+protected:
+
+  friend class TraceManipulator;
+
+  /**
+   * @brief Print the stack trace of this exception using the given QDebug object.
+   * @param dbg
+   * @return
+   */
+  virtual QDebug printStackTrace(QDebug dbg) const;
 
 private:
 
   QString Msg;
   ctkException* NestedException;
+
+  void printEnclosedStackTrace(QDebug dbg, const QList<QString>& enclosingTrace,
+                               const QString& caption, const QString& prefix,
+                               QSet<const ctkException*>& dejaVu);
+
 };
 
 //---------------------------------------------------------------------------
@@ -129,6 +180,12 @@ CTK_CORE_EXPORT QDebug operator<<(QDebug dbg, const ctkException& exc);
 //---------------------------------------------------------------------------
 /**
  * \ingroup Core
+ */
+CTK_CORE_EXPORT QDebug operator<<(QDebug dbg, const ctkException::TraceManipulator& trace);
+
+//---------------------------------------------------------------------------
+/**
+ * \ingroup Core
  *
  * \brief Quickly declare a ctkException sub-class.
  * \param API The export macro.
@@ -146,6 +203,7 @@ CTK_CORE_EXPORT QDebug operator<<(QDebug dbg, const ctkException& exc);
     CLS& operator = (const CLS& exc);                 \
     const char* name() const throw();                 \
     CLS* clone() const;                               \
+    void rethrow() const ;                            \
   };
 
 //---------------------------------------------------------------------------
@@ -178,6 +236,10 @@ CTK_CORE_EXPORT QDebug operator<<(QDebug dbg, const ctkException& exc);
   CLS* CLS::clone() const                                                \
   {                                                                      \
     return new CLS(*this);                                               \
+  }                                                                      \
+  void CLS::rethrow() const                                              \
+  {                                                                      \
+    throw *this;                                                         \
   }
 
 CTK_DECLARE_EXCEPTION(CTK_CORE_EXPORT, ctkRuntimeException, ctkException)

+ 6 - 0
Libs/PluginFramework/ctkPluginDatabaseException.cpp

@@ -83,6 +83,12 @@ ctkPluginDatabaseException* ctkPluginDatabaseException::clone() const
 }
 
 //----------------------------------------------------------------------------
+void ctkPluginDatabaseException::rethrow() const
+{
+  throw *this;
+}
+
+//----------------------------------------------------------------------------
 ctkPluginDatabaseException::Type ctkPluginDatabaseException::getType() const
 {
   return type;

+ 5 - 0
Libs/PluginFramework/ctkPluginDatabaseException.h

@@ -63,6 +63,11 @@ public:
    */
   ctkPluginDatabaseException* clone() const;
 
+  /**
+   * @see ctkException::rethrow()
+   */
+  void rethrow() const;
+
   Type getType() const;
 
 private:

+ 6 - 0
Libs/PluginFramework/ctkPluginException.cpp

@@ -80,6 +80,12 @@ ctkPluginException* ctkPluginException::clone() const
 }
 
 //----------------------------------------------------------------------------
+void ctkPluginException::rethrow() const
+{
+  throw *this;
+}
+
+//----------------------------------------------------------------------------
 ctkPluginException::Type ctkPluginException::getType() const
 {
   return type;

+ 5 - 0
Libs/PluginFramework/ctkPluginException.h

@@ -137,6 +137,11 @@ public:
   ctkPluginException* clone() const;
 
   /**
+   * @see ctkException::rethrow()
+   */
+  void rethrow() const;
+
+  /**
    * Returns the type for this exception or <code>UNSPECIFIED</code> if the
    * type was unspecified or unknown.
    *

+ 6 - 0
Libs/PluginFramework/ctkServiceException.cpp

@@ -82,6 +82,12 @@ ctkServiceException* ctkServiceException::clone() const
 }
 
 //----------------------------------------------------------------------------
+void ctkServiceException::rethrow() const
+{
+  throw *this;
+}
+
+//----------------------------------------------------------------------------
 ctkServiceException::Type ctkServiceException::getType() const
 {
   return type;

+ 5 - 0
Libs/PluginFramework/ctkServiceException.h

@@ -116,6 +116,11 @@ public:
   ctkServiceException* clone() const;
 
   /**
+   * @see ctkException::rethrow()
+   */
+  void rethrow() const;
+
+  /**
    * Returns the type for this exception or <code>UNSPECIFIED</code> if the
    * type was unspecified or unknown.
    *

+ 6 - 0
Libs/PluginFramework/service/cm/ctkConfigurationException.cpp

@@ -75,6 +75,12 @@ ctkConfigurationException* ctkConfigurationException::clone() const
 }
 
 //----------------------------------------------------------------------------
+void ctkConfigurationException::rethrow() const
+{
+  throw *this;
+}
+
+//----------------------------------------------------------------------------
 QString ctkConfigurationException::getProperty() const
 {
   return property;

+ 5 - 0
Libs/PluginFramework/service/cm/ctkConfigurationException.h

@@ -74,6 +74,11 @@ public:
   ctkConfigurationException* clone() const;
 
   /**
+   * @see ctkException::rethrow()
+   */
+  void rethrow() const;
+
+  /**
    * Return the property name that caused the failure or a null QString.
    *
    * @return name of property or null if no specific property caused the

+ 5 - 0
Plugins/org.commontk.eventadmin/dispatch/ctkEAInterruptedException.cpp

@@ -41,3 +41,8 @@ ctkEAInterruptedException *ctkEAInterruptedException::clone() const
 {
   return new ctkEAInterruptedException(*this);
 }
+
+void ctkEAInterruptedException::rethrow() const
+{
+  throw *this;
+}

+ 2 - 0
Plugins/org.commontk.eventadmin/dispatch/ctkEAInterruptedException_p.h

@@ -46,6 +46,8 @@ public:
   const char* name() const throw();
 
   ctkEAInterruptedException* clone() const;
+
+  void rethrow() const;
 };
 
 #endif // CTKEAINTERRUPTEDEXCEPTION_P_H

+ 5 - 0
Plugins/org.commontk.eventadmin/util/ctkEABrokenBarrierException.cpp

@@ -41,3 +41,8 @@ ctkEABrokenBarrierException *ctkEABrokenBarrierException::clone() const
 {
   return new ctkEABrokenBarrierException(*this);
 }
+
+void ctkEABrokenBarrierException::rethrow() const
+{
+  throw *this;
+}

+ 5 - 0
Plugins/org.commontk.eventadmin/util/ctkEABrokenBarrierException_p.h

@@ -56,6 +56,11 @@ public:
    * @see ctkException::clone()
    */
   ctkEABrokenBarrierException* clone() const;
+
+  /**
+   * @see ctkException::rethrow()
+   */
+  void rethrow() const;
 };
 
 #endif // CTKEABROKENBARRIEREXCEPTION_P_H

+ 5 - 0
Plugins/org.commontk.eventadmin/util/ctkEATimeoutException.cpp

@@ -41,3 +41,8 @@ ctkEATimeoutException *ctkEATimeoutException::clone() const
 {
   return new ctkEATimeoutException(*this);
 }
+
+void ctkEATimeoutException::rethrow() const
+{
+  throw *this;
+}

+ 5 - 0
Plugins/org.commontk.eventadmin/util/ctkEATimeoutException_p.h

@@ -57,6 +57,11 @@ public:
    * @see ctkException::clone()
    */
   ctkEATimeoutException* clone() const;
+
+  /**
+   * @see ctkException::rethrow()
+   */
+  void rethrow() const;
 };
 
 #endif // CTKEATIMEOUTEXCEPTION_P_H