2017-12-11 14:45:29 +00:00
/*
This file is part of Telegram Desktop ,
2018-01-03 10:23:14 +00:00
the official desktop application for the Telegram messaging service .
2017-12-11 14:45:29 +00:00
2018-01-03 10:23:14 +00:00
For license and copyright information please follow this link :
https : //github.com/telegramdesktop/tdesktop/blob/master/LEGAL
2017-12-11 14:45:29 +00:00
*/
# include "core/crash_reports.h"
# include "platform/platform_specific.h"
# include <signal.h>
# include <new>
# include <mutex>
# ifndef TDESKTOP_DISABLE_CRASH_REPORTS
// see https://blog.inventic.eu/2012/08/qt-and-google-breakpad/
# ifdef Q_OS_WIN
# pragma warning(push)
# pragma warning(disable:4091)
# include "client/windows/handler/exception_handler.h"
# pragma warning(pop)
# elif defined Q_OS_MAC // Q_OS_WIN
# include <execinfo.h>
# include <signal.h>
# include <sys/syscall.h>
# include <dlfcn.h>
# include <unistd.h>
# ifdef MAC_USE_BREAKPAD
# include "client/mac/handler/exception_handler.h"
# else // MAC_USE_BREAKPAD
# include "client/crashpad_client.h"
# endif // else for MAC_USE_BREAKPAD
# elif defined Q_OS_LINUX64 || defined Q_OS_LINUX32 // Q_OS_MAC
# include <execinfo.h>
# include <signal.h>
# include <sys/syscall.h>
# include "client/linux/handler/exception_handler.h"
# endif // Q_OS_LINUX64 || Q_OS_LINUX32
# endif // !TDESKTOP_DISABLE_CRASH_REPORTS
namespace CrashReports {
namespace {
using Annotations = std : : map < std : : string , std : : string > ;
using AnnotationRefs = std : : map < std : : string , const QString * > ;
Annotations ProcessAnnotations ;
AnnotationRefs ProcessAnnotationRefs ;
# ifndef TDESKTOP_DISABLE_CRASH_REPORTS
QString ReportPath ;
FILE * ReportFile = nullptr ;
int ReportFileNo = 0 ;
char LaunchedDateTimeStr [ 32 ] = { 0 } ;
char LaunchedBinaryName [ 256 ] = { 0 } ;
void SafeWriteChar ( char ch ) {
fwrite ( & ch , 1 , 1 , ReportFile ) ;
}
template < bool Unsigned , typename Type >
struct writeNumberSignAndRemoveIt {
static void call ( Type & number ) {
if ( number < 0 ) {
SafeWriteChar ( ' - ' ) ;
number = - number ;
}
}
} ;
template < typename Type >
struct writeNumberSignAndRemoveIt < true , Type > {
static void call ( Type & number ) {
}
} ;
template < typename Type >
const dump & SafeWriteNumber ( const dump & stream , Type number ) {
if ( ! ReportFile ) return stream ;
writeNumberSignAndRemoveIt < ( Type ( - 1 ) > Type ( 0 ) ) , Type > : : call ( number ) ;
Type upper = 1 , prev = number / 10 ;
while ( prev > = upper ) {
upper * = 10 ;
}
while ( upper > 0 ) {
int digit = ( number / upper ) ;
SafeWriteChar ( ' 0 ' + digit ) ;
number - = digit * upper ;
upper / = 10 ;
}
return stream ;
}
using ReservedMemoryChunk = std : : array < gsl : : byte , 1024 * 1024 > ;
std : : unique_ptr < ReservedMemoryChunk > ReservedMemory ;
void InstallOperatorNewHandler ( ) {
ReservedMemory = std : : make_unique < ReservedMemoryChunk > ( ) ;
std : : set_new_handler ( [ ] {
std : : set_new_handler ( nullptr ) ;
ReservedMemory . reset ( ) ;
Unexpected ( " Could not allocate! " ) ;
} ) ;
}
2018-11-16 15:27:02 +00:00
void InstallQtMessageHandler ( ) {
static QtMessageHandler original = nullptr ;
original = qInstallMessageHandler ( [ ] (
QtMsgType type ,
const QMessageLogContext & context ,
const QString & message ) {
if ( original ) {
original ( type , context , message ) ;
}
if ( type = = QtFatalMsg ) {
2018-12-05 06:41:43 +00:00
CrashReports : : SetAnnotation ( " QtFatal " , message ) ;
2018-11-16 15:27:02 +00:00
Unexpected ( " Qt FATAL message was generated! " ) ;
}
} ) ;
}
2017-12-11 14:45:29 +00:00
Qt : : HANDLE ReportingThreadId = nullptr ;
bool ReportingHeaderWritten = false ;
QMutex ReportingMutex ;
const char * BreakpadDumpPath = nullptr ;
const wchar_t * BreakpadDumpPathW = nullptr ;
# if defined Q_OS_MAC || defined Q_OS_LINUX32 || defined Q_OS_LINUX64
struct sigaction SIG_def [ 32 ] ;
void SignalHandler ( int signum , siginfo_t * info , void * ucontext ) {
if ( signum > 0 ) {
sigaction ( signum , & SIG_def [ signum ] , 0 ) ;
}
# else // Q_OS_MAC || Q_OS_LINUX32 || Q_OS_LINUX64
void SignalHandler ( int signum ) {
# endif // else for Q_OS_MAC || Q_OS_LINUX || Q_OS_LINUX64
const char * name = 0 ;
switch ( signum ) {
case SIGABRT : name = " SIGABRT " ; break ;
case SIGSEGV : name = " SIGSEGV " ; break ;
case SIGILL : name = " SIGILL " ; break ;
case SIGFPE : name = " SIGFPE " ; break ;
# ifndef Q_OS_WIN
case SIGBUS : name = " SIGBUS " ; break ;
case SIGSYS : name = " SIGSYS " ; break ;
# endif // !Q_OS_WIN
}
Qt : : HANDLE thread = QThread : : currentThreadId ( ) ;
if ( thread = = ReportingThreadId ) return ;
QMutexLocker lock ( & ReportingMutex ) ;
ReportingThreadId = thread ;
if ( ! ReportingHeaderWritten ) {
ReportingHeaderWritten = true ;
auto dec2hex = [ ] ( int value ) - > char {
if ( value > = 0 & & value < 10 ) {
return ' 0 ' + value ;
} else if ( value > = 10 & & value < 16 ) {
return ' a ' + ( value - 10 ) ;
}
return ' # ' ;
} ;
for ( const auto & i : ProcessAnnotationRefs ) {
QByteArray utf8 = i . second - > toUtf8 ( ) ;
std : : string wrapped ;
wrapped . reserve ( 4 * utf8 . size ( ) ) ;
for ( auto ch : utf8 ) {
auto uch = static_cast < uchar > ( ch ) ;
wrapped . append ( " \\ x " , 2 ) . append ( 1 , dec2hex ( uch > > 4 ) ) . append ( 1 , dec2hex ( uch & 0x0F ) ) ;
}
ProcessAnnotations [ i . first ] = wrapped ;
}
2018-11-12 07:50:40 +00:00
for ( const auto & i : ProcessAnnotations ) {
2017-12-11 14:45:29 +00:00
dump ( ) < < i . first . c_str ( ) < < " : " < < i . second . c_str ( ) < < " \n " ;
}
psWriteDump ( ) ;
dump ( ) < < " \n " ;
}
if ( name ) {
dump ( ) < < " Caught signal " < < signum < < " ( " < < name < < " ) in thread " < < uint64 ( thread ) < < " \n " ;
} else if ( signum = = - 1 ) {
dump ( ) < < " Google Breakpad caught a crash, minidump written in thread " < < uint64 ( thread ) < < " \n " ;
if ( BreakpadDumpPath ) {
dump ( ) < < " Minidump: " < < BreakpadDumpPath < < " \n " ;
} else if ( BreakpadDumpPathW ) {
dump ( ) < < " Minidump: " < < BreakpadDumpPathW < < " \n " ;
}
} else {
dump ( ) < < " Caught signal " < < signum < < " in thread " < < uint64 ( thread ) < < " \n " ;
}
// see https://github.com/benbjohnson/bandicoot
# if defined Q_OS_MAC || defined Q_OS_LINUX32 || defined Q_OS_LINUX64
ucontext_t * uc = ( ucontext_t * ) ucontext ;
void * caller = 0 ;
if ( uc ) {
# if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_6)
/* OSX < 10.6 */
# if defined(__x86_64__)
caller = ( void * ) uc - > uc_mcontext - > __ss . __rip ;
# elif defined(__i386__)
caller = ( void * ) uc - > uc_mcontext - > __ss . __eip ;
# else
caller = ( void * ) uc - > uc_mcontext - > __ss . __srr0 ;
# endif
# elif defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_6)
/* OSX >= 10.6 */
# if defined(_STRUCT_X86_THREAD_STATE64) && !defined(__i386__)
caller = ( void * ) uc - > uc_mcontext - > __ss . __rip ;
# else
caller = ( void * ) uc - > uc_mcontext - > __ss . __eip ;
# endif
# elif defined(__linux__)
/* Linux */
# if defined(__i386__)
caller = ( void * ) uc - > uc_mcontext . gregs [ 14 ] ; /* Linux 32 */
# elif defined(__X86_64__) || defined(__x86_64__)
caller = ( void * ) uc - > uc_mcontext . gregs [ 16 ] ; /* Linux 64 */
# elif defined(__ia64__) /* Linux IA64 */
caller = ( void * ) uc - > uc_mcontext . sc_ip ;
# endif
# endif
}
void * addresses [ 132 ] = { 0 } ;
size_t size = backtrace ( addresses , 128 ) ;
/* overwrite sigaction with caller's address */
if ( caller ) {
for ( int i = size ; i > 1 ; - - i ) {
addresses [ i + 3 ] = addresses [ i ] ;
}
addresses [ 2 ] = ( void * ) 0x1 ;
addresses [ 3 ] = caller ;
addresses [ 4 ] = ( void * ) 0x1 ;
}
# ifdef Q_OS_MAC
dump ( ) < < " \n Base image addresses: \n " ;
for ( size_t i = 0 ; i < size ; + + i ) {
Dl_info info ;
dump ( ) < < i < < " " ;
if ( dladdr ( addresses [ i ] , & info ) ) {
dump ( ) < < uint64 ( info . dli_fbase ) < < " ( " < < info . dli_fname < < " ) \n " ;
} else {
dump ( ) < < " _unknown_module_ \n " ;
}
}
# endif // Q_OS_MAC
dump ( ) < < " \n Backtrace: \n " ;
backtrace_symbols_fd ( addresses , size , ReportFileNo ) ;
# else // Q_OS_MAC || Q_OS_LINUX32 || Q_OS_LINUX64
2018-11-12 07:50:40 +00:00
dump ( ) < < " \n Backtrace omitted. \n " ;
2017-12-11 14:45:29 +00:00
# endif // else for Q_OS_MAC || Q_OS_LINUX32 || Q_OS_LINUX64
dump ( ) < < " \n " ;
ReportingThreadId = nullptr ;
}
bool SetSignalHandlers = true ;
bool CrashLogged = false ;
# if !defined Q_OS_MAC || defined MAC_USE_BREAKPAD
google_breakpad : : ExceptionHandler * BreakpadExceptionHandler = 0 ;
# ifdef Q_OS_WIN
bool DumpCallback ( const wchar_t * _dump_dir , const wchar_t * _minidump_id , void * context , EXCEPTION_POINTERS * exinfo , MDRawAssertionInfo * assertion , bool success )
# elif defined Q_OS_MAC // Q_OS_WIN
bool DumpCallback ( const char * _dump_dir , const char * _minidump_id , void * context , bool success )
# elif defined Q_OS_LINUX64 || defined Q_OS_LINUX32 // Q_OS_MAC
bool DumpCallback ( const google_breakpad : : MinidumpDescriptor & md , void * context , bool success )
# endif // Q_OS_LINUX64 || Q_OS_LINUX32
{
if ( CrashLogged ) return success ;
CrashLogged = true ;
# ifdef Q_OS_WIN
BreakpadDumpPathW = _minidump_id ;
SignalHandler ( - 1 ) ;
# else // Q_OS_WIN
# ifdef Q_OS_MAC
BreakpadDumpPath = _minidump_id ;
# else // Q_OS_MAC
BreakpadDumpPath = md . path ( ) ;
# endif // else for Q_OS_MAC
SignalHandler ( - 1 , 0 , 0 ) ;
# endif // else for Q_OS_WIN
return success ;
}
# endif // !Q_OS_MAC || MAC_USE_BREAKPAD
# endif // !TDESKTOP_DISABLE_CRASH_REPORTS
} // namespace
void StartCatching ( ) {
# ifndef TDESKTOP_DISABLE_CRASH_REPORTS
ProcessAnnotations [ " Binary " ] = cExeName ( ) . toUtf8 ( ) . constData ( ) ;
ProcessAnnotations [ " ApiId " ] = QString : : number ( ApiId ) . toUtf8 ( ) . constData ( ) ;
2018-09-26 15:11:16 +00:00
ProcessAnnotations [ " Version " ] = ( cAlphaVersion ( ) ? qsl ( " %1 alpha " ) . arg ( cAlphaVersion ( ) ) : ( AppBetaVersion ? qsl ( " %1 beta " ) : qsl ( " %1 " ) ) . arg ( AppVersion ) ) . toUtf8 ( ) . constData ( ) ;
2017-12-11 14:45:29 +00:00
ProcessAnnotations [ " Launched " ] = QDateTime : : currentDateTime ( ) . toString ( " dd.MM.yyyy hh:mm:ss " ) . toUtf8 ( ) . constData ( ) ;
ProcessAnnotations [ " Platform " ] = cPlatformString ( ) . toUtf8 ( ) . constData ( ) ;
ProcessAnnotations [ " UserTag " ] = QString : : number ( Sandbox : : UserTag ( ) , 16 ) . toUtf8 ( ) . constData ( ) ;
QString dumpspath = cWorkingDir ( ) + qsl ( " tdata/dumps " ) ;
QDir ( ) . mkpath ( dumpspath ) ;
# ifdef Q_OS_WIN
BreakpadExceptionHandler = new google_breakpad : : ExceptionHandler (
dumpspath . toStdWString ( ) ,
google_breakpad : : ExceptionHandler : : FilterCallback ( nullptr ) ,
DumpCallback ,
( void * ) nullptr , // callback_context
google_breakpad : : ExceptionHandler : : HANDLER_ALL ,
MINIDUMP_TYPE ( MiniDumpNormal ) ,
// MINIDUMP_TYPE(MiniDumpWithFullMemory | MiniDumpWithHandleData | MiniDumpWithThreadInfo | MiniDumpWithProcessThreadData | MiniDumpWithFullMemoryInfo | MiniDumpWithUnloadedModules | MiniDumpWithFullAuxiliaryState | MiniDumpIgnoreInaccessibleMemory | MiniDumpWithTokenInformation),
( const wchar_t * ) nullptr , // pipe_name
( const google_breakpad : : CustomClientInfo * ) nullptr
) ;
# elif defined Q_OS_MAC // Q_OS_WIN
# ifdef MAC_USE_BREAKPAD
# ifndef _DEBUG
BreakpadExceptionHandler = new google_breakpad : : ExceptionHandler (
QFile : : encodeName ( dumpspath ) . toStdString ( ) ,
/*FilterCallback*/ 0 ,
DumpCallback ,
/*context*/ 0 ,
true ,
0
) ;
# endif // !_DEBUG
SetSignalHandlers = false ;
# else // MAC_USE_BREAKPAD
crashpad : : CrashpadClient crashpad_client ;
std : : string handler = ( cExeDir ( ) + cExeName ( ) + qsl ( " /Contents/Helpers/crashpad_handler " ) ) . toUtf8 ( ) . constData ( ) ;
std : : string database = QFile : : encodeName ( dumpspath ) . constData ( ) ;
if ( crashpad_client . StartHandler ( base : : FilePath ( handler ) ,
base : : FilePath ( database ) ,
std : : string ( ) ,
ProcessAnnotations ,
std : : vector < std : : string > ( ) ,
false ) ) {
crashpad_client . UseHandler ( ) ;
}
# endif // else for MAC_USE_BREAKPAD
# elif defined Q_OS_LINUX64 || defined Q_OS_LINUX32
BreakpadExceptionHandler = new google_breakpad : : ExceptionHandler (
google_breakpad : : MinidumpDescriptor ( QFile : : encodeName ( dumpspath ) . toStdString ( ) ) ,
/*FilterCallback*/ 0 ,
DumpCallback ,
/*context*/ 0 ,
true ,
- 1
) ;
# endif // Q_OS_LINUX64 || Q_OS_LINUX32
# endif // !TDESKTOP_DISABLE_CRASH_REPORTS
}
void FinishCatching ( ) {
# ifndef TDESKTOP_DISABLE_CRASH_REPORTS
# if !defined Q_OS_MAC || defined MAC_USE_BREAKPAD
delete base : : take ( BreakpadExceptionHandler ) ;
# endif // !Q_OS_MAC || MAC_USE_BREAKPAD
# endif // !TDESKTOP_DISABLE_CRASH_REPORTS
}
Status Start ( ) {
# ifndef TDESKTOP_DISABLE_CRASH_REPORTS
ReportPath = cWorkingDir ( ) + qsl ( " tdata/working " ) ;
# ifdef Q_OS_WIN
FILE * f = nullptr ;
if ( _wfopen_s ( & f , ReportPath . toStdWString ( ) . c_str ( ) , L " rb " ) ! = 0 ) {
f = nullptr ;
} else {
# else // !Q_OS_WIN
if ( FILE * f = fopen ( QFile : : encodeName ( ReportPath ) . constData ( ) , " rb " ) ) {
# endif // else for !Q_OS_WIN
QByteArray lastdump ;
char buffer [ 256 * 1024 ] = { 0 } ;
int32 read = fread ( buffer , 1 , 256 * 1024 , f ) ;
if ( read > 0 ) {
lastdump . append ( buffer , read ) ;
}
fclose ( f ) ;
Sandbox : : SetLastCrashDump ( lastdump ) ;
LOG ( ( " Opened '%1' for reading, the previous Telegram Desktop launch was not finished properly :( Crash log size: %2 " ) . arg ( ReportPath ) . arg ( lastdump . size ( ) ) ) ;
return LastCrashed ;
}
# endif // !TDESKTOP_DISABLE_CRASH_REPORTS
return Restart ( ) ;
}
Status Restart ( ) {
# ifndef TDESKTOP_DISABLE_CRASH_REPORTS
if ( ReportFile ) {
return Started ;
}
# ifdef Q_OS_WIN
if ( _wfopen_s ( & ReportFile , ReportPath . toStdWString ( ) . c_str ( ) , L " wb " ) ! = 0 ) {
ReportFile = nullptr ;
}
# else // Q_OS_WIN
ReportFile = fopen ( QFile : : encodeName ( ReportPath ) . constData ( ) , " wb " ) ;
# endif // else for Q_OS_WIN
if ( ReportFile ) {
# ifdef Q_OS_WIN
ReportFileNo = _fileno ( ReportFile ) ;
# else // Q_OS_WIN
ReportFileNo = fileno ( ReportFile ) ;
# endif // else for Q_OS_WIN
if ( SetSignalHandlers ) {
# ifndef Q_OS_WIN
struct sigaction sigact ;
sigact . sa_sigaction = SignalHandler ;
sigemptyset ( & sigact . sa_mask ) ;
sigact . sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO ;
sigaction ( SIGABRT , & sigact , & SIG_def [ SIGABRT ] ) ;
sigaction ( SIGSEGV , & sigact , & SIG_def [ SIGSEGV ] ) ;
sigaction ( SIGILL , & sigact , & SIG_def [ SIGILL ] ) ;
sigaction ( SIGFPE , & sigact , & SIG_def [ SIGFPE ] ) ;
sigaction ( SIGBUS , & sigact , & SIG_def [ SIGBUS ] ) ;
sigaction ( SIGSYS , & sigact , & SIG_def [ SIGSYS ] ) ;
# else // !Q_OS_WIN
signal ( SIGABRT , SignalHandler ) ;
signal ( SIGSEGV , SignalHandler ) ;
signal ( SIGILL , SignalHandler ) ;
signal ( SIGFPE , SignalHandler ) ;
# endif // else for !Q_OS_WIN
}
InstallOperatorNewHandler ( ) ;
2018-11-16 15:27:02 +00:00
InstallQtMessageHandler ( ) ;
2017-12-11 14:45:29 +00:00
return Started ;
}
LOG ( ( " FATAL: Could not open '%1' for writing! " ) . arg ( ReportPath ) ) ;
return CantOpen ;
# else // !TDESKTOP_DISABLE_CRASH_REPORTS
return Started ;
# endif // else for !TDESKTOP_DISABLE_CRASH_REPORTS
}
void Finish ( ) {
# ifndef TDESKTOP_DISABLE_CRASH_REPORTS
FinishCatching ( ) ;
if ( ReportFile ) {
fclose ( ReportFile ) ;
ReportFile = nullptr ;
# ifdef Q_OS_WIN
_wunlink ( ReportPath . toStdWString ( ) . c_str ( ) ) ;
# else // Q_OS_WIN
unlink ( ReportPath . toUtf8 ( ) . constData ( ) ) ;
# endif // else for Q_OS_WIN
}
# endif // !TDESKTOP_DISABLE_CRASH_REPORTS
}
void SetAnnotation ( const std : : string & key , const QString & value ) {
static QMutex mutex ;
QMutexLocker lock ( & mutex ) ;
if ( ! value . trimmed ( ) . isEmpty ( ) ) {
ProcessAnnotations [ key ] = value . toUtf8 ( ) . constData ( ) ;
} else {
ProcessAnnotations . erase ( key ) ;
}
}
2018-01-18 11:04:25 +00:00
void SetAnnotationHex ( const std : : string & key , const QString & value ) {
if ( value . isEmpty ( ) ) {
return SetAnnotation ( key , value ) ;
}
const auto utf = value . toUtf8 ( ) ;
auto buffer = std : : string ( ) ;
buffer . reserve ( 4 * utf . size ( ) ) ;
const auto hexDigit = [ ] ( std : : uint8_t value ) {
if ( value > = 10 ) {
return ' A ' + ( value - 10 ) ;
}
return ' 0 ' + value ;
} ;
const auto appendHex = [ & ] ( std : : uint8_t value ) {
buffer . push_back ( ' \\ ' ) ;
buffer . push_back ( ' x ' ) ;
buffer . push_back ( hexDigit ( value / 16 ) ) ;
buffer . push_back ( hexDigit ( value % 16 ) ) ;
} ;
for ( const auto ch : utf ) {
appendHex ( ch ) ;
}
ProcessAnnotations [ key ] = std : : move ( buffer ) ;
}
2017-12-11 14:45:29 +00:00
void SetAnnotationRef ( const std : : string & key , const QString * valuePtr ) {
2018-07-19 14:58:40 +00:00
static QMutex mutex ;
QMutexLocker lock ( & mutex ) ;
2017-12-11 14:45:29 +00:00
if ( valuePtr ) {
ProcessAnnotationRefs [ key ] = valuePtr ;
} else {
ProcessAnnotationRefs . erase ( key ) ;
}
}
2017-12-12 20:15:58 +00:00
# ifndef TDESKTOP_DISABLE_CRASH_REPORTS
2017-12-11 14:45:29 +00:00
dump : : ~ dump ( ) {
if ( ReportFile ) {
fflush ( ReportFile ) ;
}
}
const dump & operator < < ( const dump & stream , const char * str ) {
if ( ! ReportFile ) return stream ;
fwrite ( str , 1 , strlen ( str ) , ReportFile ) ;
return stream ;
}
const dump & operator < < ( const dump & stream , const wchar_t * str ) {
if ( ! ReportFile ) return stream ;
for ( int i = 0 , l = wcslen ( str ) ; i < l ; + + i ) {
2018-03-09 20:48:47 +00:00
if (
# if !defined(__WCHAR_UNSIGNED__)
str [ i ] > = 0 & &
# endif
str [ i ] < 128 ) {
2017-12-11 14:45:29 +00:00
SafeWriteChar ( char ( str [ i ] ) ) ;
} else {
SafeWriteChar ( ' ? ' ) ;
}
}
return stream ;
}
const dump & operator < < ( const dump & stream , int num ) {
return SafeWriteNumber ( stream , num ) ;
}
const dump & operator < < ( const dump & stream , unsigned int num ) {
return SafeWriteNumber ( stream , num ) ;
}
const dump & operator < < ( const dump & stream , unsigned long num ) {
return SafeWriteNumber ( stream , num ) ;
}
const dump & operator < < ( const dump & stream , unsigned long long num ) {
return SafeWriteNumber ( stream , num ) ;
}
const dump & operator < < ( const dump & stream , double num ) {
if ( num < 0 ) {
SafeWriteChar ( ' - ' ) ;
num = - num ;
}
SafeWriteNumber ( stream , uint64 ( floor ( num ) ) ) ;
SafeWriteChar ( ' . ' ) ;
num - = floor ( num ) ;
for ( int i = 0 ; i < 4 ; + + i ) {
num * = 10 ;
int digit = int ( floor ( num ) ) ;
SafeWriteChar ( ' 0 ' + digit ) ;
num - = digit ;
}
return stream ;
}
2017-12-12 20:15:58 +00:00
# endif // TDESKTOP_DISABLE_CRASH_REPORTS
2017-12-11 14:45:29 +00:00
} // namespace CrashReports