1 /*
2  * This file is part of the Colobot: Gold Edition source code
3  * Copyright (C) 2001-2020, Daniel Roux, EPSITEC SA & TerranovaTeam
4  * http://epsitec.ch; http://colobot.info; http://github.com/colobot
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14  * See the GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program. If not, see http://gnu.org/licenses
18  */
19 
20 #include "app/signal_handlers.h"
21 
22 #include "common/stringutils.h"
23 #include "common/version.h"
24 
25 #include "common/resources/resourcemanager.h"
26 
27 #include "common/system/system.h"
28 
29 #include "level/robotmain.h"
30 
31 #include <csignal>
32 #include <sstream>
33 #include <iostream>
34 
35 CSystemUtils* CSignalHandlers::m_systemUtils = nullptr;
36 
Init(CSystemUtils * systemUtils)37 void CSignalHandlers::Init(CSystemUtils* systemUtils)
38 {
39     m_systemUtils = systemUtils;
40     signal(SIGSEGV, SignalHandler);
41     signal(SIGABRT, SignalHandler);
42     signal(SIGFPE,  SignalHandler);
43     signal(SIGILL,  SignalHandler);
44     std::set_terminate(UnhandledExceptionHandler);
45 }
46 
SignalHandler(int sig)47 void CSignalHandlers::SignalHandler(int sig)
48 {
49     std::string signalStr = StrUtils::ToString(sig);
50     switch(sig)
51     {
52         case SIGSEGV: signalStr = "SIGSEGV, segmentation fault"; break;
53         case SIGABRT: signalStr = "SIGABRT, abort"; break;
54         case SIGFPE:  signalStr = "SIGFPE, arithmetic exception"; break;
55         case SIGILL:  signalStr = "SIGILL, illegal instruction"; break;
56     }
57     ReportError(signalStr);
58 }
59 
60 #if HAVE_DEMANGLE
61 // For gcc and clang
62 #include <cstdlib>
63 #include <memory>
64 #include <cxxabi.h>
demangle(const char * name)65 static std::string demangle(const char* name)
66 {
67     int status;
68     std::unique_ptr<char[], void(*)(void*)> result {
69         abi::__cxa_demangle(name, nullptr, nullptr, &status),
70         std::free
71     };
72 
73     return (result != nullptr && status == 0) ? result.get() : name;
74 }
75 #else
76 // For MSVC and others
77 // In MSVC typeinfo(e).name() should be already demangled
demangle(const char * name)78 static std::string demangle(const char* name)
79 {
80     return name;
81 }
82 #endif
83 
UnhandledExceptionHandler()84 void CSignalHandlers::UnhandledExceptionHandler()
85 {
86     std::exception_ptr exptr = std::current_exception();
87     if (!exptr)
88     {
89         std::stringstream ss;
90         ss << "std::terminate called without an exception";
91         #ifdef HAS_MSVC_EXCEPTION_BUG
92         ss << " [this is a known bug in MSVC]";
93         // see https://connect.microsoft.com/VisualStudio/feedback/details/988432/std-current-exception-returns-null-when-called-from-terminate-handler
94         #endif
95         ReportError(ss.str());
96         return;
97     }
98 
99     try
100     {
101         std::rethrow_exception(exptr);
102     }
103     catch (const std::exception& e)
104     {
105         std::stringstream ss;
106         ss << "Type: " << demangle(typeid(e).name()) << std::endl;
107         ss << "Message: " << e.what();
108         ReportError(ss.str());
109     }
110     catch (...)
111     {
112         ReportError("Unknown unhandled exception (not inherited from std::exception)");
113     }
114 }
115 
ReportError(const std::string & errorMessage)116 void CSignalHandlers::ReportError(const std::string& errorMessage)
117 {
118     static bool triedSaving = false;
119 
120     if (SDL_WasInit(SDL_INIT_VIDEO))
121     {
122         // Close the SDL window on crash, because otherwise the error doesn't show on in fullscreen mode and the game appears to freeze
123         SDL_Quit();
124     }
125 
126     std::stringstream msg;
127     msg << "Unhandled exception occurred!" << std::endl;
128     msg << "==============================" << std::endl;
129     msg << errorMessage << std::endl;
130     msg << "==============================" << std::endl;
131     msg << std::endl;
132     msg << "This is usually caused by a bug. Please report this on http://github.com/colobot/colobot/issues" << std::endl;
133     msg << "including information on what you were doing before this happened and all the information below." << std::endl;
134     msg << "==============================" << std::endl;
135     #if BUILD_NUMBER == 0
136         #ifdef OFFICIAL_COLOBOT_BUILD
137             msg << "You are running official " << COLOBOT_VERSION_DISPLAY << " build." << std::endl;
138         #else
139             // COLOBOT_VERSION_DISPLAY doesn't update if you don't run CMake after "git pull"
140             msg << "You seem to be running a custom compilation of version " << COLOBOT_VERSION_DISPLAY << ", but please verify that." << std::endl;
141         #endif
142     #else
143         msg << "You are running version " << COLOBOT_VERSION_DISPLAY << " from CI build #" << BUILD_NUMBER << std::endl;
144     #endif
145     msg << std::endl;
146     bool canSave = false;
147     CRobotMain* robotMain = nullptr;
148     if (!CRobotMain::IsCreated())
149     {
150         msg << "CRobotMain instance does not seem to exist" << std::endl;
151     }
152     else
153     {
154         robotMain = CRobotMain::GetInstancePointer();
155         msg << "The game was in phase " << PhaseToString(robotMain->GetPhase()) << " (ID=" << robotMain->GetPhase() << ")" << std::endl;
156         msg << "Last started level was: category=" << GetLevelCategoryDir(robotMain->GetLevelCategory()) << " chap=" << robotMain->GetLevelChap() << " rank=" << robotMain->GetLevelRank() << std::endl;
157         canSave = (robotMain->GetPhase() == PHASE_SIMUL || IsInSimulationConfigPhase(robotMain->GetPhase())) && !robotMain->IsLoading();
158     }
159     msg << "==============================" << std::endl;
160     msg << std::endl;
161     msg << "Sorry for inconvenience!";
162 
163     std::cerr << std::endl << msg.str() << std::endl;
164 
165     m_systemUtils->SystemDialog(SDT_ERROR, "Unhandled exception occurred!", msg.str());
166 
167     if (canSave && !triedSaving)
168     {
169         msg.str("");
170         msg << "You can try saving the game at the moment of a crash. Keep in mind, the game engine is in" << std::endl;
171         msg << "an unstable state so the saved game may be corrupted or even cause another crash." << std::endl;
172         msg << std::endl;
173         msg << "Do you want to try saving now?";
174 
175         SystemDialogResult result = m_systemUtils->SystemDialog(SDT_YES_NO, "Try to save?", msg.str());
176         if (result == SDR_YES)
177         {
178             triedSaving = true;
179             CResourceManager::CreateDirectory("crashsave");
180             robotMain->IOWriteScene("crashsave/data.sav", "crashsave/cbot.run", "crashsave/screen.png", "Backup at the moment of a crash", true);
181             m_systemUtils->SystemDialog(SDT_INFO, "Try to save?", "Saving finished.\nPlease restart the game now");
182         }
183     }
184 
185     exit(1);
186 }
187