609 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			609 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
| This file is part of Telegram Desktop,
 | |
| the official desktop application for the Telegram messaging service.
 | |
| 
 | |
| For license and copyright information please follow this link:
 | |
| https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | |
| */
 | |
| #include "core/crash_reports.h"
 | |
| 
 | |
| #include "platform/platform_specific.h"
 | |
| #include "base/platform/base_platform_info.h"
 | |
| #include "core/launcher.h"
 | |
| 
 | |
| #include <signal.h>
 | |
| #include <new>
 | |
| #include <mutex>
 | |
| 
 | |
| #ifndef TDESKTOP_DISABLE_CRASH_REPORTS
 | |
| #ifdef Q_OS_WIN
 | |
| 
 | |
| #include <new.h>
 | |
| 
 | |
| #pragma warning(push)
 | |
| #pragma warning(disable:4091)
 | |
| #include <client/windows/handler/exception_handler.h>
 | |
| #pragma warning(pop)
 | |
| 
 | |
| #else // Q_OS_WIN
 | |
| 
 | |
| #include <execinfo.h>
 | |
| #include <sys/syscall.h>
 | |
| 
 | |
| #ifdef Q_OS_MAC
 | |
| 
 | |
| #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
 | |
| 
 | |
| #else // Q_OS_MAC
 | |
| 
 | |
| #include <client/linux/handler/exception_handler.h>
 | |
| 
 | |
| #endif // Q_OS_MAC
 | |
| 
 | |
| #endif // Q_OS_WIN
 | |
| #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;
 | |
| 
 | |
| 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>();
 | |
| #ifdef Q_OS_WIN
 | |
| 	_set_new_handler([](size_t requested) -> int {
 | |
| 		_set_new_handler(nullptr);
 | |
| 		ReservedMemory.reset();
 | |
| 		CrashReports::SetAnnotation("Requested", QString::number(requested));
 | |
| 		Unexpected("Could not allocate!");
 | |
| 	});
 | |
| #else // Q_OS_WIN
 | |
| 	std::set_new_handler([] {
 | |
| 		std::set_new_handler(nullptr);
 | |
| 		ReservedMemory.reset();
 | |
| 		Unexpected("Could not allocate!");
 | |
| 	});
 | |
| #endif // Q_OS_WIN
 | |
| }
 | |
| 
 | |
| 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) {
 | |
| 			CrashReports::SetAnnotation("QtFatal", message);
 | |
| 			Unexpected("Qt FATAL message was generated!");
 | |
| 		}
 | |
| 	});
 | |
| }
 | |
| 
 | |
| std::atomic<Qt::HANDLE> ReportingThreadId/* = nullptr*/;
 | |
| bool ReportingHeaderWritten/* = false*/;
 | |
| const char *BreakpadDumpPath/* = nullptr*/;
 | |
| const wchar_t *BreakpadDumpPathW/* = nullptr*/;
 | |
| 
 | |
| void WriteReportHeader() {
 | |
| 	if (ReportingHeaderWritten) {
 | |
| 		return;
 | |
| 	}
 | |
| 	ReportingHeaderWritten = true;
 | |
| 	const 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;
 | |
| 	}
 | |
| 	for (const auto &i : ProcessAnnotations) {
 | |
| 		dump() << i.first.c_str() << ": " << i.second.c_str() << "\n";
 | |
| 	}
 | |
| 	Platform::WriteCrashDumpDetails();
 | |
| 	dump() << "\n";
 | |
| }
 | |
| 
 | |
| void WriteReportInfo(int signum, const char *name) {
 | |
| 	WriteReportHeader();
 | |
| 
 | |
| 	const auto thread = ReportingThreadId.load();
 | |
| 	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";
 | |
| 	}
 | |
| }
 | |
| 
 | |
| const int HandledSignals[] = {
 | |
| 	SIGSEGV,
 | |
| 	SIGABRT,
 | |
| 	SIGFPE,
 | |
| 	SIGILL,
 | |
| #ifndef Q_OS_WIN
 | |
| 	SIGBUS,
 | |
| 	SIGTRAP,
 | |
| #endif // !Q_OS_WIN
 | |
| };
 | |
| 
 | |
| #ifdef Q_OS_WIN
 | |
| void SignalHandler(int signum) {
 | |
| #else // Q_OS_WIN
 | |
| struct sigaction OldSigActions[32]/* = { 0 }*/;
 | |
| 
 | |
| void RestoreSignalHandlers() {
 | |
| 	for (const auto signum : HandledSignals) {
 | |
| 		sigaction(signum, &OldSigActions[signum], nullptr);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void InvokeOldSignalHandler(int signum, siginfo_t *info, void *ucontext) {
 | |
| 	if (signum < 0 || signum > 31) {
 | |
| 		return;
 | |
| 	} else if (OldSigActions[signum].sa_flags & SA_SIGINFO) {
 | |
| 		if (OldSigActions[signum].sa_sigaction) {
 | |
| 			OldSigActions[signum].sa_sigaction(signum, info, ucontext);
 | |
| 		}
 | |
| 	} else {
 | |
| 		if (OldSigActions[signum].sa_handler) {
 | |
| 			OldSigActions[signum].sa_handler(signum);
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void SignalHandler(int signum, siginfo_t *info, void *ucontext) {
 | |
| 	RestoreSignalHandlers();
 | |
| 
 | |
| #endif // else for Q_OS_WIN
 | |
| 
 | |
| 	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
 | |
| 	}
 | |
| 
 | |
| 	auto expected = Qt::HANDLE(nullptr);
 | |
| 	const auto thread = QThread::currentThreadId();
 | |
| 
 | |
| 	if (ReportingThreadId.compare_exchange_strong(expected, thread)) {
 | |
| 		WriteReportInfo(signum, name);
 | |
| 		ReportingThreadId = nullptr;
 | |
| 	}
 | |
| 
 | |
| #ifndef Q_OS_WIN
 | |
| 	InvokeOldSignalHandler(signum, info, ucontext);
 | |
| #endif // !Q_OS_WIN
 | |
| }
 | |
| 
 | |
| 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)
 | |
| #else // Q_OS_MAC
 | |
| bool DumpCallback(const google_breakpad::MinidumpDescriptor &md, void *context, bool success)
 | |
| #endif // else for Q_OS_WIN || Q_OS_MAC
 | |
| {
 | |
| 	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
 | |
| 
 | |
| QString PlatformString() {
 | |
| 	if (Platform::IsWindowsStoreBuild()) {
 | |
| 		return Platform::IsWindows64Bit()
 | |
| 			? u"WinStore64Bit"_q
 | |
| 			: u"WinStore32Bit"_q;
 | |
| 	} else if (Platform::IsWindows32Bit()) {
 | |
| 		return u"Windows32Bit"_q;
 | |
| 	} else if (Platform::IsWindows64Bit()) {
 | |
| 		return u"Windows64Bit"_q;
 | |
| 	} else if (Platform::IsMacStoreBuild()) {
 | |
| 		return u"MacAppStore"_q;
 | |
| 	} else if (Platform::IsMac()) {
 | |
| 		return u"MacOS"_q;
 | |
| 	} else if (Platform::IsLinux()) {
 | |
| 		return u"Linux"_q;
 | |
| 	}
 | |
| 	Unexpected("Platform in CrashReports::PlatformString.");
 | |
| }
 | |
| 
 | |
| void StartCatching() {
 | |
| #ifndef TDESKTOP_DISABLE_CRASH_REPORTS
 | |
| 	ProcessAnnotations["Binary"] = cExeName().toUtf8().constData();
 | |
| 	ProcessAnnotations["ApiId"] = QString::number(ApiId).toUtf8().constData();
 | |
| 	ProcessAnnotations["Version"] = (cAlphaVersion()
 | |
| 		? u"%1 alpha"_q.arg(cAlphaVersion())
 | |
| 		: (AppBetaVersion
 | |
| 			? u"%1 beta"_q
 | |
| 			: u"%1"_q).arg(AppVersion)).toUtf8().constData();
 | |
| 	ProcessAnnotations["Launched"] = QDateTime::currentDateTime().toString("dd.MM.yyyy hh:mm:ss").toUtf8().constData();
 | |
| 	ProcessAnnotations["Platform"] = PlatformString().toUtf8().constData();
 | |
| 	ProcessAnnotations["UserTag"] = QString::number(Core::Launcher::Instance().installationTag(), 16).toUtf8().constData();
 | |
| 
 | |
| 	QString dumpspath = cWorkingDir() + u"tdata/dumps"_q;
 | |
| 	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() + u"/Contents/Helpers/crashpad_handler"_q).toUtf8().constData();
 | |
| 	std::string database = QFile::encodeName(dumpspath).constData();
 | |
| 	if (crashpad_client.StartHandler(
 | |
| 			base::FilePath(handler),
 | |
| 			base::FilePath(database),
 | |
| 			{}, // metrics_dir
 | |
| 			std::string(), // url
 | |
| 			ProcessAnnotations,
 | |
| 			std::vector<std::string>(), // arguments
 | |
| 			false, // restartable
 | |
| 			false)) { // asynchronous_start
 | |
| 	}
 | |
| #endif // else for MAC_USE_BREAKPAD
 | |
| #else
 | |
| 	BreakpadExceptionHandler = new google_breakpad::ExceptionHandler(
 | |
| 		google_breakpad::MinidumpDescriptor(QFile::encodeName(dumpspath).toStdString()),
 | |
| 		/*FilterCallback*/ 0,
 | |
| 		DumpCallback,
 | |
| 		/*context*/ 0,
 | |
| 		true,
 | |
| 		-1
 | |
| 	);
 | |
| #endif // else for Q_OS_WIN || Q_OS_MAC
 | |
| #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
 | |
| }
 | |
| 
 | |
| StartResult Start() {
 | |
| #ifndef TDESKTOP_DISABLE_CRASH_REPORTS
 | |
| 	ReportPath = cWorkingDir() + u"tdata/working"_q;
 | |
| 
 | |
| #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);
 | |
| 
 | |
| 		LOG(("Opened '%1' for reading, the previous "
 | |
| 			"Telegram Desktop launch was not finished properly :( "
 | |
| 			"Crash log size: %2").arg(ReportPath).arg(lastdump.size()));
 | |
| 
 | |
| 		return lastdump;
 | |
| 	}
 | |
| 
 | |
| #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;
 | |
| 
 | |
| 			for (const auto signum : HandledSignals) {
 | |
| 				sigaction(signum, &sigact, &OldSigActions[signum]);
 | |
| 			}
 | |
| #else // !Q_OS_WIN
 | |
| 			for (const auto signum : HandledSignals) {
 | |
| 				signal(signum, SignalHandler);
 | |
| 			}
 | |
| #endif // else for !Q_OS_WIN
 | |
| 		}
 | |
| 
 | |
| 		InstallOperatorNewHandler();
 | |
| 		InstallQtMessageHandler();
 | |
| 
 | |
| 		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);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 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);
 | |
| }
 | |
| 
 | |
| void SetAnnotationRef(const std::string &key, const QString *valuePtr) {
 | |
| 	static QMutex mutex;
 | |
| 	QMutexLocker lock(&mutex);
 | |
| 
 | |
| 	if (valuePtr) {
 | |
| 		ProcessAnnotationRefs[key] = valuePtr;
 | |
| 	} else {
 | |
| 		ProcessAnnotationRefs.erase(key);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| #ifndef TDESKTOP_DISABLE_CRASH_REPORTS
 | |
| 
 | |
| 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) {
 | |
| 		if (
 | |
| #if !defined(__WCHAR_UNSIGNED__)
 | |
| 			str[i] >= 0 &&
 | |
| #endif
 | |
| 			str[i] < 128) {
 | |
| 			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;
 | |
| }
 | |
| 
 | |
| #endif // TDESKTOP_DISABLE_CRASH_REPORTS
 | |
| 
 | |
| } // namespace CrashReports
 | 
