diff --git a/cpp/include/Ice/Exception.h b/cpp/include/Ice/Exception.h index d0a773194a9..5456d0cbb26 100644 --- a/cpp/include/Ice/Exception.h +++ b/cpp/include/Ice/Exception.h @@ -1,112 +1,49 @@ -// -// Copyright (c) ZeroC, Inc. All rights reserved. -// +// Copyright (c) ZeroC, Inc. #ifndef ICE_EXCEPTION_H #define ICE_EXCEPTION_H #include "Config.h" -#include "ValueF.h" #include -#include #include -#include -#include namespace Ice { - /** - * Abstract base class for all Ice exceptions. It has only two derived classes: LocalException and UserException. - * \headerfile Ice/Ice.h - */ + /// Abstract base class for all Ice exceptions. It has only two derived classes: LocalException and UserException. + /// \headerfile Ice/Ice.h class ICE_API Exception : public std::exception { public: - /** - * Constructs an exception with a default message. - * @param file The file where this exception is constructed. This C string is not copied. - * @param line The line where this exception is constructed. - */ - Exception(const char* file, int line) noexcept; - - /** - * Constructs an exception. - * @param file The file where this exception is constructed. This C string is not copied. - * @param line The line where this exception is constructed. - * @param message The error message adopted by this exception and returned by what(). - */ - Exception(const char* file, int line, std::string message); - - /** - * Copy constructor. - * @param other The exception to copy. - */ - Exception(const Exception& other) noexcept = default; - - /** - * Assignment operator. - * @param rhs The exception to assign. - */ - Exception& operator=(const Exception& rhs) noexcept = default; - - /** - * Returns the error message of this exception. - * @return The error message. - */ - [[nodiscard]] const char* what() const noexcept override; - - /** - * Returns the type ID of this exception. This corresponds to the Slice type ID for Slice-defined exceptions, - * and to a similar fully scoped name for other exceptions. For example "::Ice::CommunicatorDestroyedException". - * @return The type ID of this exception - */ + Exception() noexcept = default; + Exception(const Exception&) noexcept = default; + Exception& operator=(const Exception&) noexcept = default; + + // Need out-of-line virtual function to avoid weak vtable, which in turn requires the default constructor, + // copy constructor, and copy assignment operator to be declared explicitly. + ~Exception() override; + + /// Returns the type ID of this exception. This corresponds to the Slice type ID for Slice-defined exceptions, + /// and to a similar fully scoped name for other exceptions. For example + /// "::Ice::CommunicatorDestroyedException". + /// @return The type ID of this exception [[nodiscard]] virtual const char* ice_id() const noexcept = 0; - /** - * Outputs a description of this exception to a stream. This function is called by operator<<(std::ostream&, - * const Ice::Exception&). The default implementation outputs ice_id(). The application can override the - * ice_print of a user exception to produce a more detailed description, with typically the ice_id() plus - * additional information. - * @param os The output stream. - */ - virtual void ice_print(std::ostream& os) const { os << ice_id(); } - - /** - * Returns the name of the file where this exception was constructed. - * @return The file name. - */ - [[nodiscard]] const char* ice_file() const noexcept; - - /** - * Returns the line number where this exception was constructed. - * @return The line number. - */ - [[nodiscard]] int ice_line() const noexcept; - - /** - * Returns the stack trace at the point this exception was constructed. - * @return The stack trace as a string, or an empty string if stack trace collection is not enabled. - */ - [[nodiscard]] std::string ice_stackTrace() const; - - /** - * Enables the collection of stack traces for exceptions. On Windows, calling this function more than once is - * useful to refresh the symbol module list; on other platforms, the second and subsequent calls have no effect. - */ - static void ice_enableStackTraceCollection(); - - private: - friend ICE_API std::ostream& operator<<(std::ostream&, const Exception&); - - const char* _file; // can be nullptr - int _line; // not used when _file is nullptr - std::shared_ptr _whatString; // shared storage for custom _what message. - const char* _what; // can be nullptr - std::shared_ptr> _stackFrames; // shared storage for stack frames. + /// Outputs a description of this exception to a stream. + /// @param os The output stream. + virtual void ice_print(std::ostream& os) const = 0; }; - ICE_API std::ostream& operator<<(std::ostream&, const Exception&); + /// Outputs a description of an Ice exception to a stream by calling the ice_print member function of this + /// exception. + /// @param os The output stream. + /// @param exception The exception to describe. + /// @return The output stream. + inline std::ostream& operator<<(std::ostream& os, const Exception& exception) + { + exception.ice_print(os); + return os; + } } #endif diff --git a/cpp/include/Ice/LocalException.h b/cpp/include/Ice/LocalException.h index 0e46852dc47..1e7655d12c0 100644 --- a/cpp/include/Ice/LocalException.h +++ b/cpp/include/Ice/LocalException.h @@ -1,30 +1,57 @@ -// -// Copyright (c) ZeroC, Inc. All rights reserved. -// +// Copyright (c) ZeroC, Inc. #ifndef ICE_LOCAL_EXCEPTION_H #define ICE_LOCAL_EXCEPTION_H #include "Exception.h" +#include +#include +#include + namespace Ice { - /** - * Base class for all Ice exceptions not defined in Slice. - * \headerfile Ice/Ice.h - */ + /// Base class for all Ice exceptions not defined in Slice. + /// \headerfile Ice/Ice.h class ICE_API LocalException : public Exception { public: - /** - * Constructs a local exception. - * @param file The file where this exception is constructed. This C string is not copied. - * @param line The line where this exception is constructed. - * @param message The error message adopted by this exception and returned by what(). - */ - LocalException(const char* file, int line, std::string message) : Exception(file, line, std::move(message)) {} + /// Constructs a local exception. + /// @param file The file where this exception is constructed. This C string is not copied. + /// @param line The line where this exception is constructed. + /// @param message The error message adopted by this exception and returned by what(). + LocalException(const char* file, int line, std::string message); + + /// Gets the error message of this local Ice exception. + /// @return The error message. + [[nodiscard]] const char* what() const noexcept final; + + void ice_print(std::ostream& os) const final; [[nodiscard]] const char* ice_id() const noexcept override; + + /// Gets the name of the file where this exception was constructed. + /// @return The file name. + [[nodiscard]] const char* ice_file() const noexcept; + + /// Gets the line number where this exception was constructed. + /// @return The line number. + [[nodiscard]] int ice_line() const noexcept; + + /// Gets the stack trace at the point this exception was constructed. + /// @return The stack trace as a string, or an empty string if stack trace collection is not enabled. + [[nodiscard]] std::string ice_stackTrace() const; + + /// Enables the collection of stack traces for exceptions. On Windows, calling this function more than once is + /// useful to refresh the symbol module list; on other platforms, the second and subsequent calls have no + /// effect. + static void ice_enableStackTraceCollection(); + + private: + const char* _file; + int _line; + std::shared_ptr _whatString; // shared storage for custom _what message. + std::shared_ptr> _stackFrames; // shared storage for stack frames. }; } diff --git a/cpp/include/Ice/LocalExceptions.h b/cpp/include/Ice/LocalExceptions.h index 9e56de91f8c..8f58b3a324b 100644 --- a/cpp/include/Ice/LocalExceptions.h +++ b/cpp/include/Ice/LocalExceptions.h @@ -82,6 +82,10 @@ namespace Ice { } + RequestFailedException(const RequestFailedException&) noexcept = default; + RequestFailedException& operator=(const RequestFailedException&) noexcept = default; + ~RequestFailedException() override; // to avoid weak vtable + private: std::shared_ptr _id; std::shared_ptr _facet; diff --git a/cpp/include/Ice/UserException.h b/cpp/include/Ice/UserException.h index 992ce1452ea..179a2dc621c 100644 --- a/cpp/include/Ice/UserException.h +++ b/cpp/include/Ice/UserException.h @@ -1,36 +1,29 @@ -// -// Copyright (c) ZeroC, Inc. All rights reserved. -// +// Copyright (c) ZeroC, Inc. #ifndef ICE_USER_EXCEPTION_H #define ICE_USER_EXCEPTION_H #include "Exception.h" -#include - namespace Ice { class InputStream; class OutputStream; - /** - * Abstract base class for all Ice exceptions defined in Slice. - * \headerfile Ice/Ice.h - */ + /// Abstract base class for all Ice exceptions defined in Slice. + /// \headerfile Ice/Ice.h class ICE_API UserException : public Exception { public: - /** - * Default constructor. The file, line and what message are never set for user exceptions. - */ - UserException() : Exception(nullptr, 0) {} - - /** - * Throws this exception. - */ + /// Throws this exception. virtual void ice_throw() const = 0; + /// Gets the Slice type ID of this user exception. + /// @return The Slice type ID. + [[nodiscard]] const char* what() const noexcept final; + + void ice_print(std::ostream& os) const override; + /// \cond STREAM // _write and _read are virtual for the Python, Ruby etc. mappings. virtual void _write(OutputStream*) const; diff --git a/cpp/src/DataStorm/SessionI.cpp b/cpp/src/DataStorm/SessionI.cpp index 799a1a64c19..11591d0d71c 100644 --- a/cpp/src/DataStorm/SessionI.cpp +++ b/cpp/src/DataStorm/SessionI.cpp @@ -766,7 +766,7 @@ SessionI::destroyImpl(const exception_ptr& ex) { rethrow_exception(ex); } - catch (const Exception& e) + catch (const LocalException& e) { out << "\n:" << e.what() << "\n" << e.ice_stackTrace(); } diff --git a/cpp/src/Ice/Exception.cpp b/cpp/src/Ice/Exception.cpp index 6eecdc31c37..0f3d3d12106 100644 --- a/cpp/src/Ice/Exception.cpp +++ b/cpp/src/Ice/Exception.cpp @@ -1,524 +1,5 @@ -// -// Copyright (c) ZeroC, Inc. All rights reserved. -// - -#if defined(_MSC_VER) -# ifndef UNICODE -# define UNICODE -# endif -# ifndef _UNICODE -# define _UNICODE -# endif -#endif - -// -// For UINTPTR_MAX on Ubuntu Precise -// -#ifndef __STDC_LIMIT_MACROS -# define __STDC_LIMIT_MACROS // NOLINT -#endif +// Copyright (c) ZeroC, Inc. #include "Ice/Exception.h" -#include "Ice/Config.h" -#include "Ice/Demangle.h" -#include "Ice/StringUtil.h" - -#ifdef _WIN32 -# include -#endif - -#include -#include -#include -#include -#include -#include - -#ifdef __GNUC__ -# if defined(ICE_LIBBACKTRACE) -# include -# include -# if BACKTRACE_SUPPORTED && BACKTRACE_SUPPORTS_THREADS -# include -# else -// It's available but we can't use it - shouldn't happen -# undef ICE_LIBBACKTRACE -# endif -# endif - -# if !defined(__FreeBSD__) -# include -# include -# define ICE_BACKTRACE -# endif -#endif - -// -// The Slice compilers don't retrieve the exception stack traces so we don't need the DbgHelp calls. -// -#if defined(_WIN32) && !defined(ICE_BUILDING_SLICE_COMPILERS) -# define ICE_DBGHELP -# define DBGHELP_TRANSLATE_TCHAR -# include "Ice/StringConverter.h" -# include -# include -#endif - -using namespace std; - -namespace -{ - std::mutex globalMutex; -#if defined(ICE_DBGHELP) - HANDLE process = nullptr; -#elif defined(ICE_LIBBACKTRACE) - backtrace_state* bstate = nullptr; - - void ignoreErrorCallback(void*, const char* /*msg*/, int /*errnum*/) - { - // cerr << "Error callback: " << msg << ", errnum = " << errnum << endl; - } -#elif defined(ICE_BACKTRACE) - bool backTraceEnabled = false; -#endif - -#ifdef ICE_DBGHELP - class Init - { - public: - ~Init() - { - if (process) - { - SymCleanup(process); - CloseHandle(process); - process = nullptr; - } - } - }; - - Init init; -#endif - - inline bool collectStackTraces() noexcept - { - lock_guard lock(globalMutex); -#if defined(ICE_DBGHELP) - return process != nullptr; -#elif defined(ICE_LIBBACKTRACE) - return bstate != nullptr; -#elif defined(ICE_BACKTRACE) - return backTraceEnabled; -#else - return false; -#endif - } - -#if defined(ICE_LIBBACKTRACE) || defined(ICE_BACKTRACE) - - struct FrameInfo - { - FrameInfo(int i, uintptr_t p) : index(i), pc(p), fallback(nullptr), setByErrorCb(false) {} - - int index; - uintptr_t pc; - const char* fallback; - bool setByErrorCb; - string output; - }; - - void decode(const string& line, string& function, string& filename) - { - string::size_type openParen = line.find_first_of('('); - if (openParen != string::npos) - { - // - // Format: "/opt/Ice/lib/libIceUtil.so.33(_ZN7IceUtil9ExceptionC2EPKci+0x51) [0x73b267]" - // - string::size_type closeParen = line.find_first_of(')', openParen); - if (closeParen != string::npos) - { - string tmp = line.substr(openParen + 1, closeParen - openParen - 1); - string::size_type plus = tmp.find_last_of('+'); - if (plus != string::npos) - { - function = tmp.substr(0, plus); - filename = line.substr(0, openParen); - } - } - } - else - { - // - // Format: "1 libIce.3.3.1.dylib 0x000933a1 _ZN7IceUtil9ExceptionC2EPKci + 71" - // - string::size_type plus = line.find_last_of('+'); - if (plus != string::npos) - { - string tmp = line.substr(0, plus - 1); - string::size_type space = tmp.find_last_of(" \t"); - if (space != string::npos) - { - tmp = tmp.substr(space + 1, tmp.size() - space); - - string::size_type start = line.find_first_not_of(" \t", 3); - if (start != string::npos) - { - string::size_type finish = line.find_first_of(" \t", start); - if (finish != string::npos) - { - function = tmp; - filename = line.substr(start, finish - start); - } - } - } - } - } - } - - int printFrame(void* data, uintptr_t pc, const char* filename, int lineno, const char* function) - { - FrameInfo& frameInfo = *reinterpret_cast(data); - - ostringstream os; - os << setw(3) << frameInfo.index << " "; - - string functionHolder, filenameHolder; - - if (!function && frameInfo.fallback) - { - // Extract function and filename from fallback - decode(frameInfo.fallback, functionHolder, filenameHolder); - if (!functionHolder.empty()) - { - function = functionHolder.c_str(); - } - if (!filename && !filenameHolder.empty()) - { - filename = filenameHolder.c_str(); - } - } - - int ret = 0; - - if (function) - { - os << IceInternal::demangle(function); - - if (filename && lineno > 0) - { - os << " at " << filename << ":" << lineno; - } - else if (filename) - { - os << " in " << filename; - } - } - else if (frameInfo.fallback) - { - // decode was not able to parse this string - os << frameInfo.fallback; - ret = 1; - } - else - { - os << hex << setw(sizeof(uintptr_t) * 2) << setfill('0') << pc; - ret = 2; - } - os << "\n"; - frameInfo.output = os.str(); - return ret; - } -#endif - -#ifdef ICE_LIBBACKTRACE - - void handlePcInfoError(void* data, const char* /*msg*/, int /*errnum*/) - { - FrameInfo& frameInfo = *reinterpret_cast(data); - printFrame(&frameInfo, frameInfo.pc, 0, 0, 0); - frameInfo.setByErrorCb = true; - } - - int addFrame(void* sf, uintptr_t pc) - { - if (pc != UINTPTR_MAX) - { - vector* stackFrames = reinterpret_cast*>(sf); - stackFrames->push_back(reinterpret_cast(pc)); - return 0; - } - else - { - return 1; - } - } -#endif - - vector getStackFrames() noexcept - { - vector stackFrames; - - if (!collectStackTraces()) - { - return stackFrames; - } - -#if defined(ICE_DBGHELP) - - stackFrames.resize(61); - // - // 1: skip the first frame (the call to getStackFrames) - // 1 + stackSize < 63 on Windows XP according to the documentation for CaptureStackBackTrace - // - USHORT frameCount = CaptureStackBackTrace(1, static_cast(stackFrames.size()), &stackFrames.front(), 0); - - stackFrames.resize(frameCount); - -#elif defined(ICE_LIBBACKTRACE) - - backtrace_simple(bstate, 1, addFrame, ignoreErrorCallback, &stackFrames); - -#elif defined(ICE_BACKTRACE) - - stackFrames.resize(100); - int stackDepth = backtrace(&stackFrames.front(), static_cast(stackFrames.size())); - stackFrames.resize(static_cast(stackDepth)); - if (!stackFrames.empty()) - { - stackFrames.erase(stackFrames.begin()); // drop the first frame - } -#endif - - return stackFrames; - } - - string getStackTrace(const vector& stackFrames) - { - if (stackFrames.empty()) - { - return ""; - } - - assert(collectStackTraces()); - string stackTrace; - -#if defined(ICE_DBGHELP) - static_assert(sizeof(TCHAR) == sizeof(wchar_t), "Bad TCHAR - should be wchar_t"); - - char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; - - SYMBOL_INFO* symbol = reinterpret_cast(buffer); - symbol->SizeOfStruct = sizeof(SYMBOL_INFO); - symbol->MaxNameLen = MAX_SYM_NAME; - - IMAGEHLP_LINE64 line = {}; - line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); - DWORD displacement = 0; - - lock_guard lock(globalMutex); - const Ice::StringConverterPtr converter = Ice::getProcessStringConverter(); - for (size_t i = 0; i < stackFrames.size(); i++) - { - ostringstream s; - s << setw(3) << i << " "; - - DWORD64 address = reinterpret_cast(stackFrames[i]); - - BOOL ok = SymFromAddr(process, address, 0, symbol); - if (ok) - { - s << wstringToString(symbol->Name, converter); - ok = SymGetLineFromAddr64(process, address, &displacement, &line); - if (ok) - { - s << " at " << wstringToString(line.FileName, converter) << ":" << line.LineNumber; - } - } - else - { - s << hex << setw(sizeof(DWORD64) * 2) << setfill('0') << address; - } - s << "\n"; - stackTrace += s.str(); - } - -#elif defined(ICE_LIBBACKTRACE) || defined(ICE_BACKTRACE) - - auto p = stackFrames.begin(); - int frameIndex = 0; - int offset = 0; - char** backtraceStrings = nullptr; - -# if defined(ICE_LIBBACKTRACE) && defined(ICE_BACKTRACE) - bool backtraceStringsInitialized = false; -# endif -# if !defined(ICE_LIBBACKTRACE) - // Initialize backtraceStrings immediately - if (p != stackFrames.end()) - { - backtraceStrings = backtrace_symbols(&*p, static_cast(stackFrames.size())); - } -# endif - - do - { - FrameInfo frameInfo(frameIndex, reinterpret_cast(*p)); - bool retry = false; - - if (backtraceStrings) - { - frameInfo.fallback = backtraceStrings[frameIndex - offset]; - } - -# if defined(ICE_LIBBACKTRACE) - bool ok = backtrace_pcinfo(bstate, frameInfo.pc, printFrame, handlePcInfoError, &frameInfo) == 0; - - // When error callback is called, pcinfo returns 0 - if (!ok || frameInfo.setByErrorCb) - { -# if defined(ICE_BACKTRACE) - if (!backtraceStringsInitialized) - { - offset = frameIndex; - // Initialize backtraceStrings as fallback - backtraceStrings = backtrace_symbols(&*p, stackFrames.size() - offset); - backtraceStringsInitialized = true; - retry = true; - } -# endif - } -# else // not using libbacktrace: - printFrame(&frameInfo, frameInfo.pc, nullptr, 0, nullptr); -# endif - if (!retry) - { - stackTrace += frameInfo.output; - ++p; - ++frameIndex; - } - } while (p != stackFrames.end()); - - if (backtraceStrings) - { - free(backtraceStrings); - } - -#endif - return stackTrace; - } -} - -// TODO: make_shared is not noexcept. -Ice::Exception::Exception(const char* file, int line) noexcept - : _file(file), - _line(line), - _what(nullptr), - _stackFrames(make_shared>(getStackFrames())) -{ -} - -Ice::Exception::Exception(const char* file, int line, string message) - : _file(file), - _line(line), - _whatString(make_shared(std::move(message))), - _what(_whatString->c_str()), - _stackFrames(make_shared>(getStackFrames())) -{ -} - -const char* -Ice::Exception::what() const noexcept -{ - return _what ? _what : ice_id(); -} - -const char* -Ice::Exception::ice_file() const noexcept -{ - return _file; -} - -int -Ice::Exception::ice_line() const noexcept -{ - return _line; -} - -string -Ice::Exception::ice_stackTrace() const -{ - return getStackTrace(*_stackFrames); -} - -void -Ice::Exception::ice_enableStackTraceCollection() -{ - lock_guard lock(globalMutex); -#if defined(ICE_DBGHELP) - if (process) - { - // Already initialized, just refresh. - if (!SymRefreshModuleList(process)) - { - // TODO: SymRefreshModuleList occasionally fails with error code 3221225476; we retry once in this case. - // Note that calling GetLastError() does not reset the last error. - if (GetLastError() != 3221225476 || !SymRefreshModuleList(process)) - { - throw std::runtime_error{ - "SymRefreshModuleList failed with " + IceInternal::errorToString(GetLastError())}; - } - } - } - else - { - HANDLE currentProcess = GetCurrentProcess(); - // duplicate handle as per https://learn.microsoft.com/en-us/windows/win32/debug/initializing-the-symbol-handler - if (!DuplicateHandle(currentProcess, currentProcess, currentProcess, &process, 0, FALSE, DUPLICATE_SAME_ACCESS)) - { - throw std::runtime_error{ - "DuplicateHandle on current process failed with " + IceInternal::errorToString(GetLastError())}; - } - - SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_DEFERRED_LOADS | SYMOPT_EXACT_SYMBOLS | SYMOPT_UNDNAME); - if (!SymInitialize(process, nullptr, TRUE)) - { - DWORD initializeError = GetLastError(); - CloseHandle(process); - process = nullptr; - throw std::runtime_error{"SymInitialize failed with " + IceInternal::errorToString(initializeError)}; - } - } -#elif defined(ICE_LIBBACKTRACE) - if (!bstate) - { - // Leaked, as libbacktrace does not provide an API to free this state. - bstate = backtrace_create_state(0, 1, ignoreErrorCallback, 0); - } -#elif defined(ICE_BACKTRACE) - backTraceEnabled = true; -#endif -} - -ostream& -Ice::operator<<(ostream& out, const Ice::Exception& ex) -{ - if (ex.ice_file() && ex.ice_line() > 0) - { - out << ex.ice_file() << ':' << ex.ice_line() << ' '; - } - ex.ice_print(out); - // We don't override ice_print when we have a custom _what message. And a custom what message typically does not - // repeat the Slice type ID. - if (ex._what) - { - out << ' ' << ex._what; - } - string stack = ex.ice_stackTrace(); - if (!stack.empty()) - { - out << "\nstack trace:\n" << stack; - } - return out; -} +Ice::Exception::~Exception() = default; // avoid weak vtable diff --git a/cpp/src/Ice/Instance.cpp b/cpp/src/Ice/Instance.cpp index f382a8d6163..47928105125 100644 --- a/cpp/src/Ice/Instance.cpp +++ b/cpp/src/Ice/Instance.cpp @@ -113,7 +113,7 @@ namespace { try { - Exception::ice_enableStackTraceCollection(); + LocalException::ice_enableStackTraceCollection(); } catch (const std::exception& ex) { diff --git a/cpp/src/Ice/LocalException.cpp b/cpp/src/Ice/LocalException.cpp index ed0e40b4346..1e374f77530 100644 --- a/cpp/src/Ice/LocalException.cpp +++ b/cpp/src/Ice/LocalException.cpp @@ -1,9 +1,514 @@ // Copyright (c) ZeroC, Inc. +#if defined(_MSC_VER) +# ifndef UNICODE +# define UNICODE +# endif +# ifndef _UNICODE +# define _UNICODE +# endif +#endif + +// +// For UINTPTR_MAX on Ubuntu Precise +// +#ifndef __STDC_LIMIT_MACROS +# define __STDC_LIMIT_MACROS // NOLINT +#endif + #include "Ice/LocalException.h" +#include "Ice/Config.h" +#include "Ice/Demangle.h" +#include "Ice/StringUtil.h" + +#ifdef _WIN32 +# include +#endif + +#include +#include +#include +#include +#include +#include + +#ifdef __GNUC__ +# if defined(ICE_LIBBACKTRACE) +# include +# include +# if BACKTRACE_SUPPORTED && BACKTRACE_SUPPORTS_THREADS +# include +# else +// It's available but we can't use it - shouldn't happen +# undef ICE_LIBBACKTRACE +# endif +# endif + +# if !defined(__FreeBSD__) +# include +# include +# define ICE_BACKTRACE +# endif +#endif + +// +// The Slice compilers don't retrieve the exception stack traces so we don't need the DbgHelp calls. +// +#if defined(_WIN32) && !defined(ICE_BUILDING_SLICE_COMPILERS) +# define ICE_DBGHELP +# define DBGHELP_TRANSLATE_TCHAR +# include "Ice/StringConverter.h" +# include +# include +#endif + +using namespace std; + +namespace +{ + std::mutex globalMutex; +#if defined(ICE_DBGHELP) + HANDLE process = nullptr; +#elif defined(ICE_LIBBACKTRACE) + backtrace_state* bstate = nullptr; + + void ignoreErrorCallback(void*, const char* /*msg*/, int /*errnum*/) + { + // cerr << "Error callback: " << msg << ", errnum = " << errnum << endl; + } +#elif defined(ICE_BACKTRACE) + bool backTraceEnabled = false; +#endif + +#ifdef ICE_DBGHELP + class Init + { + public: + ~Init() + { + if (process) + { + SymCleanup(process); + CloseHandle(process); + process = nullptr; + } + } + }; + + Init init; +#endif + + inline bool collectStackTraces() noexcept + { + lock_guard lock(globalMutex); +#if defined(ICE_DBGHELP) + return process != nullptr; +#elif defined(ICE_LIBBACKTRACE) + return bstate != nullptr; +#elif defined(ICE_BACKTRACE) + return backTraceEnabled; +#else + return false; +#endif + } + +#if defined(ICE_LIBBACKTRACE) || defined(ICE_BACKTRACE) + + struct FrameInfo + { + FrameInfo(int i, uintptr_t p) : index(i), pc(p), fallback(nullptr), setByErrorCb(false) {} + + int index; + uintptr_t pc; + const char* fallback; + bool setByErrorCb; + string output; + }; + + void decode(const string& line, string& function, string& filename) + { + string::size_type openParen = line.find_first_of('('); + if (openParen != string::npos) + { + // + // Format: "/opt/Ice/lib/libIceUtil.so.33(_ZN7IceUtil9ExceptionC2EPKci+0x51) [0x73b267]" + // + string::size_type closeParen = line.find_first_of(')', openParen); + if (closeParen != string::npos) + { + string tmp = line.substr(openParen + 1, closeParen - openParen - 1); + string::size_type plus = tmp.find_last_of('+'); + if (plus != string::npos) + { + function = tmp.substr(0, plus); + filename = line.substr(0, openParen); + } + } + } + else + { + // + // Format: "1 libIce.3.3.1.dylib 0x000933a1 _ZN7IceUtil9ExceptionC2EPKci + 71" + // + string::size_type plus = line.find_last_of('+'); + if (plus != string::npos) + { + string tmp = line.substr(0, plus - 1); + string::size_type space = tmp.find_last_of(" \t"); + if (space != string::npos) + { + tmp = tmp.substr(space + 1, tmp.size() - space); + + string::size_type start = line.find_first_not_of(" \t", 3); + if (start != string::npos) + { + string::size_type finish = line.find_first_of(" \t", start); + if (finish != string::npos) + { + function = tmp; + filename = line.substr(start, finish - start); + } + } + } + } + } + } + + int printFrame(void* data, uintptr_t pc, const char* filename, int lineno, const char* function) + { + FrameInfo& frameInfo = *reinterpret_cast(data); + + ostringstream os; + os << setw(3) << frameInfo.index << " "; + + string functionHolder, filenameHolder; + + if (!function && frameInfo.fallback) + { + // Extract function and filename from fallback + decode(frameInfo.fallback, functionHolder, filenameHolder); + if (!functionHolder.empty()) + { + function = functionHolder.c_str(); + } + if (!filename && !filenameHolder.empty()) + { + filename = filenameHolder.c_str(); + } + } + + int ret = 0; + + if (function) + { + os << IceInternal::demangle(function); + + if (filename && lineno > 0) + { + os << " at " << filename << ":" << lineno; + } + else if (filename) + { + os << " in " << filename; + } + } + else if (frameInfo.fallback) + { + // decode was not able to parse this string + os << frameInfo.fallback; + ret = 1; + } + else + { + os << hex << setw(sizeof(uintptr_t) * 2) << setfill('0') << pc; + ret = 2; + } + os << "\n"; + frameInfo.output = os.str(); + return ret; + } +#endif + +#ifdef ICE_LIBBACKTRACE + + void handlePcInfoError(void* data, const char* /*msg*/, int /*errnum*/) + { + FrameInfo& frameInfo = *reinterpret_cast(data); + printFrame(&frameInfo, frameInfo.pc, 0, 0, 0); + frameInfo.setByErrorCb = true; + } + + int addFrame(void* sf, uintptr_t pc) + { + if (pc != UINTPTR_MAX) + { + vector* stackFrames = reinterpret_cast*>(sf); + stackFrames->push_back(reinterpret_cast(pc)); + return 0; + } + else + { + return 1; + } + } +#endif + + vector getStackFrames() noexcept + { + vector stackFrames; + + if (!collectStackTraces()) + { + return stackFrames; + } + +#if defined(ICE_DBGHELP) + + stackFrames.resize(61); + // + // 1: skip the first frame (the call to getStackFrames) + // 1 + stackSize < 63 on Windows XP according to the documentation for CaptureStackBackTrace + // + USHORT frameCount = CaptureStackBackTrace(1, static_cast(stackFrames.size()), &stackFrames.front(), 0); + + stackFrames.resize(frameCount); + +#elif defined(ICE_LIBBACKTRACE) + + backtrace_simple(bstate, 1, addFrame, ignoreErrorCallback, &stackFrames); + +#elif defined(ICE_BACKTRACE) + + stackFrames.resize(100); + int stackDepth = backtrace(&stackFrames.front(), static_cast(stackFrames.size())); + stackFrames.resize(static_cast(stackDepth)); + if (!stackFrames.empty()) + { + stackFrames.erase(stackFrames.begin()); // drop the first frame + } +#endif + + return stackFrames; + } + + string getStackTrace(const vector& stackFrames) + { + if (stackFrames.empty()) + { + return ""; + } + + assert(collectStackTraces()); + string stackTrace; + +#if defined(ICE_DBGHELP) + static_assert(sizeof(TCHAR) == sizeof(wchar_t), "Bad TCHAR - should be wchar_t"); + + char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; + + SYMBOL_INFO* symbol = reinterpret_cast(buffer); + symbol->SizeOfStruct = sizeof(SYMBOL_INFO); + symbol->MaxNameLen = MAX_SYM_NAME; + + IMAGEHLP_LINE64 line = {}; + line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + DWORD displacement = 0; + + lock_guard lock(globalMutex); + const Ice::StringConverterPtr converter = Ice::getProcessStringConverter(); + for (size_t i = 0; i < stackFrames.size(); i++) + { + ostringstream s; + s << setw(3) << i << " "; + + DWORD64 address = reinterpret_cast(stackFrames[i]); + + BOOL ok = SymFromAddr(process, address, 0, symbol); + if (ok) + { + s << wstringToString(symbol->Name, converter); + ok = SymGetLineFromAddr64(process, address, &displacement, &line); + if (ok) + { + s << " at " << wstringToString(line.FileName, converter) << ":" << line.LineNumber; + } + } + else + { + s << hex << setw(sizeof(DWORD64) * 2) << setfill('0') << address; + } + s << "\n"; + stackTrace += s.str(); + } + +#elif defined(ICE_LIBBACKTRACE) || defined(ICE_BACKTRACE) + + auto p = stackFrames.begin(); + int frameIndex = 0; + int offset = 0; + char** backtraceStrings = nullptr; + +# if defined(ICE_LIBBACKTRACE) && defined(ICE_BACKTRACE) + bool backtraceStringsInitialized = false; +# endif +# if !defined(ICE_LIBBACKTRACE) + // Initialize backtraceStrings immediately + if (p != stackFrames.end()) + { + backtraceStrings = backtrace_symbols(&*p, static_cast(stackFrames.size())); + } +# endif + + do + { + FrameInfo frameInfo(frameIndex, reinterpret_cast(*p)); + bool retry = false; + + if (backtraceStrings) + { + frameInfo.fallback = backtraceStrings[frameIndex - offset]; + } + +# if defined(ICE_LIBBACKTRACE) + bool ok = backtrace_pcinfo(bstate, frameInfo.pc, printFrame, handlePcInfoError, &frameInfo) == 0; + + // When error callback is called, pcinfo returns 0 + if (!ok || frameInfo.setByErrorCb) + { +# if defined(ICE_BACKTRACE) + if (!backtraceStringsInitialized) + { + offset = frameIndex; + // Initialize backtraceStrings as fallback + backtraceStrings = backtrace_symbols(&*p, stackFrames.size() - offset); + backtraceStringsInitialized = true; + retry = true; + } +# endif + } +# else // not using libbacktrace: + printFrame(&frameInfo, frameInfo.pc, nullptr, 0, nullptr); +# endif + if (!retry) + { + stackTrace += frameInfo.output; + ++p; + ++frameIndex; + } + } while (p != stackFrames.end()); + + if (backtraceStrings) + { + free(backtraceStrings); + } + +#endif + return stackTrace; + } +} + +Ice::LocalException::LocalException(const char* file, int line, string message) + : _file(file), + _line(line), + _whatString(make_shared(std::move(message))), + _stackFrames(make_shared>(getStackFrames())) +{ +} + +void +Ice::LocalException::ice_print(ostream& os) const +{ + // We tolerate a null file / 0 line, even though the thrower should always set them with __FILE__, __LINE__. + if (_file && _line > 0) + { + os << _file << ':' << _line << ' '; + } + + // +2 to remove the leading "::" from the type ID. + os << ice_id() + 2 << ' ' << what(); + + string stack = ice_stackTrace(); + if (!stack.empty()) + { + os << "\nstack trace:\n" << stack; + } +} + +const char* +Ice::LocalException::what() const noexcept +{ + return _whatString->c_str(); +} const char* Ice::LocalException::ice_id() const noexcept { return "::Ice::LocalException"; } + +const char* +Ice::LocalException::ice_file() const noexcept +{ + return _file; +} + +int +Ice::LocalException::ice_line() const noexcept +{ + return _line; +} + +string +Ice::LocalException::ice_stackTrace() const +{ + return getStackTrace(*_stackFrames); +} + +void +Ice::LocalException::ice_enableStackTraceCollection() +{ + lock_guard lock(globalMutex); +#if defined(ICE_DBGHELP) + if (process) + { + // Already initialized, just refresh. + if (!SymRefreshModuleList(process)) + { + // TODO: SymRefreshModuleList occasionally fails with error code 3221225476; we retry once in this case. + // Note that calling GetLastError() does not reset the last error. + if (GetLastError() != 3221225476 || !SymRefreshModuleList(process)) + { + throw std::runtime_error{ + "SymRefreshModuleList failed with " + IceInternal::errorToString(GetLastError())}; + } + } + } + else + { + HANDLE currentProcess = GetCurrentProcess(); + // duplicate handle as per https://learn.microsoft.com/en-us/windows/win32/debug/initializing-the-symbol-handler + if (!DuplicateHandle(currentProcess, currentProcess, currentProcess, &process, 0, FALSE, DUPLICATE_SAME_ACCESS)) + { + throw std::runtime_error{ + "DuplicateHandle on current process failed with " + IceInternal::errorToString(GetLastError())}; + } + + SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_DEFERRED_LOADS | SYMOPT_EXACT_SYMBOLS | SYMOPT_UNDNAME); + if (!SymInitialize(process, nullptr, TRUE)) + { + DWORD initializeError = GetLastError(); + CloseHandle(process); + process = nullptr; + throw std::runtime_error{"SymInitialize failed with " + IceInternal::errorToString(initializeError)}; + } + } +#elif defined(ICE_LIBBACKTRACE) + if (!bstate) + { + // Leaked, as libbacktrace does not provide an API to free this state. + bstate = backtrace_create_state(0, 1, ignoreErrorCallback, 0); + } +#elif defined(ICE_BACKTRACE) + backTraceEnabled = true; +#endif +} diff --git a/cpp/src/Ice/LocalExceptions.cpp b/cpp/src/Ice/LocalExceptions.cpp index 9d51b4a0e3e..54ed9a23e1e 100644 --- a/cpp/src/Ice/LocalExceptions.cpp +++ b/cpp/src/Ice/LocalExceptions.cpp @@ -27,6 +27,8 @@ namespace } } +Ice::RequestFailedException::~RequestFailedException() = default; // avoid weak vtable. + // Can't move id/facet/operation because the evaluation order is unspecified. // https://en.cppreference.com/w/cpp/language/eval_order Ice::ObjectNotExistException::ObjectNotExistException( diff --git a/cpp/src/Ice/OutgoingResponse.cpp b/cpp/src/Ice/OutgoingResponse.cpp index d1ea779d558..bac148ed5a5 100644 --- a/cpp/src/Ice/OutgoingResponse.cpp +++ b/cpp/src/Ice/OutgoingResponse.cpp @@ -20,9 +20,9 @@ namespace { inline string toString(const Exception& ex) { - // Includes the stack trace when available. + // Includes the stack trace when available (local exceptions only). ostringstream os; - os << ex; + ex.ice_print(os); return os.str(); } @@ -152,13 +152,6 @@ namespace unknownExceptionMessage = createUnknownExceptionMessage(exceptionId, ex.what()); replyStatus = ReplyStatus::UnknownLocalException; } - catch (const Exception& ex) - { - exceptionId = ex.ice_id(); - exceptionDetails = toString(ex); - unknownExceptionMessage = createUnknownExceptionMessage(exceptionId, ex.what()); - replyStatus = ReplyStatus::UnknownException; - } catch (const std::exception& ex) { exceptionId = demangle(typeid(ex).name()); diff --git a/cpp/src/Ice/Timer.cpp b/cpp/src/Ice/Timer.cpp index a47cbfa811b..c4c64ce0b83 100644 --- a/cpp/src/Ice/Timer.cpp +++ b/cpp/src/Ice/Timer.cpp @@ -122,11 +122,7 @@ Timer::run() } catch (const Ice::Exception& e) { - consoleErr << "Ice::Timer::run(): uncaught exception:\n" << e.what(); -#ifdef __GNUC__ - consoleErr << "\n" << e.ice_stackTrace(); -#endif - consoleErr << endl; + consoleErr << "Ice::Timer::run(): uncaught exception:\n" << e << endl; } catch (const std::exception& e) { diff --git a/cpp/src/Ice/UserException.cpp b/cpp/src/Ice/UserException.cpp index 4bbe0c2fac8..c1091083e60 100644 --- a/cpp/src/Ice/UserException.cpp +++ b/cpp/src/Ice/UserException.cpp @@ -9,6 +9,18 @@ using namespace std; using namespace Ice; +const char* +Ice::UserException::what() const noexcept +{ + return ice_id(); +} + +void +Ice::UserException::ice_print(ostream& os) const +{ + os << ice_id(); +} + void Ice::UserException::_write(OutputStream* os) const { diff --git a/cpp/src/IceBox/ServiceManagerI.cpp b/cpp/src/IceBox/ServiceManagerI.cpp index 163832166e7..5c0146e8239 100644 --- a/cpp/src/IceBox/ServiceManagerI.cpp +++ b/cpp/src/IceBox/ServiceManagerI.cpp @@ -441,7 +441,7 @@ IceBox::ServiceManagerI::start() { try { - Exception::ice_enableStackTraceCollection(); + LocalException::ice_enableStackTraceCollection(); } catch (const std::exception& ex) { diff --git a/cpp/src/slice2cpp/Gen.cpp b/cpp/src/slice2cpp/Gen.cpp index 93de0cacf8b..87ab986f0bb 100644 --- a/cpp/src/slice2cpp/Gen.cpp +++ b/cpp/src/slice2cpp/Gen.cpp @@ -2049,8 +2049,9 @@ Slice::Gen::DataDefVisitor::visitExceptionStart(const ExceptionPtr& p) H << sb; H << eb; - // We generate a noexcept copy constructor all the time. A C++ exception must have noexcept copy constructor - // but the default constructor is not always noexcept (e.g. if the exception has a string field). + // We generate a noexcept copy constructor all the time. A C++ exception must have a noexcept copy + // constructor but the default constructor is not always noexcept (e.g. if the exception has a string + // field). H << sp; H << nl << "/// Copy constructor."; H << nl << name << "(const " << name << "&) noexcept = default;"; diff --git a/cpp/test/Ice/exceptions/AllTests.cpp b/cpp/test/Ice/exceptions/AllTests.cpp index d520007a18b..e596d5c72c3 100644 --- a/cpp/test/Ice/exceptions/AllTests.cpp +++ b/cpp/test/Ice/exceptions/AllTests.cpp @@ -47,52 +47,35 @@ allTests(Test::TestHelper* helper) A a; string aMsg = "::Test::A"; - Ice::OperationNotExistException opNotExist("thisFile", 99); + Ice::OperationNotExistException opNotExist{"thisFile", 99}; string opNotExistWhat = "dispatch failed with OperationNotExistException"; - string opNotExistPrint = opNotExist.ice_id(); - string opNotExistStream = "thisFile:99 " + opNotExistPrint + " " + opNotExistWhat; + string opNotExistPrint = + "thisFile:99 Ice::OperationNotExistException " + opNotExistWhat; // + stack trace in debug builds string customMessage = "custom message"; - Ice::UnknownLocalException customUle("thisFile", 199, customMessage); - string customUlePrint = customUle.ice_id(); - string customUleStream = "thisFile:199 " + customUlePrint + " " + customMessage; + Ice::UnknownLocalException customUle{"thisFile", 199, customMessage}; + string customUlePrint = + "thisFile:199 Ice::UnknownLocalException " + customMessage; // + stack trace in debug builds // // Test ice_print(). // { - stringstream str; + ostringstream str; a.ice_print(str); test(str.str() == aMsg); } { - stringstream str; + ostringstream str; opNotExist.ice_print(str); - test(str.str() == opNotExistPrint); + string result = str.str(); + test(result.find(opNotExistPrint) == 0); } { - stringstream str; + ostringstream str; customUle.ice_print(str); - test(str.str() == customUlePrint); - } - - // - // Test operator<<(). - // - { - stringstream str; - str << a; - test(str.str().substr(0, aMsg.size()) == aMsg); - } - { - stringstream str; - str << opNotExist; - test(str.str().substr(0, opNotExistStream.size()) == opNotExistStream); - } - { - stringstream str; - str << customUle; - test(str.str().substr(0, customUleStream.size()) == customUleStream); + string result = str.str(); + test(result.find(customUlePrint) == 0); } // @@ -100,7 +83,7 @@ allTests(Test::TestHelper* helper) // test(aMsg == a.what()); test(opNotExistWhat == opNotExist.what()); - test(string{customMessage} == customUle.what()); + test(customMessage == customUle.what()); { E ex("E"); diff --git a/cpp/test/Ice/exceptions/ExceptionsI.cpp b/cpp/test/Ice/exceptions/ExceptionsI.cpp index e6af823b55c..276a46ce80a 100644 --- a/cpp/test/Ice/exceptions/ExceptionsI.cpp +++ b/cpp/test/Ice/exceptions/ExceptionsI.cpp @@ -13,7 +13,7 @@ using namespace std; void Test::F::ice_print(ostream& out) const { - Exception::ice_print(out); + UserException::ice_print(out); if (!data.empty()) { out << " data:'F'"; diff --git a/cpp/test/IceUtil/stacktrace/Client.cpp b/cpp/test/IceUtil/stacktrace/Client.cpp index 87534a26e8a..3bf5cc760ce 100644 --- a/cpp/test/IceUtil/stacktrace/Client.cpp +++ b/cpp/test/IceUtil/stacktrace/Client.cpp @@ -74,7 +74,7 @@ class Client final : public Test::TestHelper void Client::run(int, char*[]) { - Ice::Exception::ice_enableStackTraceCollection(); + Ice::LocalException::ice_enableStackTraceCollection(); // We assume this test is executed only on platforms/builds with support for stack trace collection. @@ -85,7 +85,7 @@ Client::run(int, char*[]) { thrower->first(); } - catch (const Ice::Exception& ex) + catch (const Ice::LocalException& ex) { // cerr << ex << endl; test(splitLines(ex.ice_stackTrace()).size() >= 3);