Files
ResInsight/ApplicationExeCode/RiaMain.cpp
T
Kristian Bendiksen 42d8a4b14a #14270 Capture uncaught exception throw site in crash reports
Add a std::set_terminate handler (manageTerminate) that runs before the
stack unwinds when a C++ exception escapes the Qt event loop, so the
captured stack trace points at the original throw site instead of the
crash-logging plumbing reached via the SIGABRT signal handler. The
handler also recovers the exception type and message via
std::current_exception and reports them as crash attributes.

Remove the catch-and-rethrow around the event loop in RiaMain: that
handler unwound the stack to main before anything captured it, which
defeated the new terminate handler. The SIGABRT signal handler remains
as a fallback for direct abort()/assert() failures that do not go
through std::terminate.
2026-06-22 14:18:05 +02:00

279 lines
10 KiB
C++

/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2011-2012 Statoil ASA, Ceetron AS
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RiaApplication.h"
#include "RiaArgumentParser.h"
#include "RiaMainTools.h"
#include "RiaOpenTelemetryManager.h"
#include "RiaPreferences.h"
#include "RiaQuantityInfoTools.h"
#include "RiaStdStringTools.h"
#ifdef ENABLE_GRPC
#include "RiaGrpcConsoleApplication.h"
#include "RiaGrpcGuiApplication.h"
#else
#include "RiaConsoleApplication.h"
#include "RiaGuiApplication.h"
#endif
#include "cafUiAppearanceSettings.h"
#include "cvfProgramOptions.h"
#include "cvfqtUtils.h"
#include <QFile>
#include <QtGlobal>
#ifndef WIN32
#include <sys/types.h>
#include <unistd.h>
#endif
#include <signal.h>
#include <exception>
void manageSegFailure( int signalCode );
void manageTerminate();
#ifndef WIN32
void manageSegFailureSA( int signalCode, siginfo_t* info, void* ucontext );
#endif
namespace
{
QtMessageHandler g_previousMessageHandler = nullptr;
//--------------------------------------------------------------------------------------------------
/// Filter out a harmless Qt6 warning seen on some Linux platforms (e.g. RHEL8) where Qt validates an
/// empty (all-zero) QColorSpacePrimaries and prints:
/// "QColorSpace attempted constructed from invalid primaries: QPointF(0,0) ..."
/// The message has no functional impact, so suppress it while forwarding everything else unchanged.
/// See https://github.com/OPM/ResInsight/issues/11802
//--------------------------------------------------------------------------------------------------
void riaFilteredMessageHandler( QtMsgType type, const QMessageLogContext& context, const QString& msg )
{
if ( msg.startsWith( QLatin1String( "QColorSpace attempted constructed from invalid primaries" ) ) )
{
return;
}
if ( g_previousMessageHandler )
{
g_previousMessageHandler( type, context, msg );
}
}
} // namespace
RiaApplication* createApplication( int& argc, char* argv[] )
{
for ( int i = 1; i < argc; ++i )
{
if ( !qstrcmp( argv[i], "--console" ) || !qstrcmp( argv[i], "--unittest" ) )
{
#ifdef ENABLE_GRPC
return new RiaGrpcConsoleApplication( argc, argv );
#else
return new RiaConsoleApplication( argc, argv );
#endif
}
}
#ifdef ENABLE_GRPC
return new RiaGrpcGuiApplication( argc, argv );
#else
return new RiaGuiApplication( argc, argv );
#endif
}
int main( int argc, char* argv[] )
{
#ifndef WIN32
// From Qt 5.3 and onwards Qt has a mechanism for checking this automatically
// But it only checks user id not group id, so better to do it ourselves.
if ( getuid() != geteuid() || getgid() != getegid() )
{
std::cerr << "FATAL: The application binary appears to be running setuid or setgid, this is a security hole."
<< std::endl;
return 1;
}
#endif
// Install a message handler to filter out a harmless Qt6 color-space warning (see #11802)
g_previousMessageHandler = qInstallMessageHandler( riaFilteredMessageHandler );
RiaMainTools::deleteStaleSettingsLockFiles();
// The Qt::AA_ShareOpenGLContexts setting is needed when we have multiple viz widgets in flight
// and we have a setup where these widgets belong to different top-level windows, or end up
// belonging to different top-level windows through re-parenting.
// See test application QtTestBenchOpenGLWidget
QApplication::setAttribute( Qt::AA_ShareOpenGLContexts );
// Create feature manager before the application object is created
RiaMainTools::initializeSingletons();
RiaQuantityInfoTools::initializeSummaryKeywords();
// https://www.w3.org/wiki/CSS/Properties/color/keywords
caf::UiAppearanceSettings::instance()->setAutoValueEditorColor( "moccasin" );
std::unique_ptr<RiaApplication> app( createApplication( argc, argv ) );
cvf::ProgramOptions progOpt;
bool result = RiaArgumentParser::parseArguments( &progOpt );
const cvf::String usageText = progOpt.usageText( 110, 30 );
app->initialize();
app->setCommandLineHelpText( cvfqt::Utils::toQString( usageText ) );
// Report application startup to OpenTelemetry
{
std::map<std::string, std::string> attributes;
bool isConsoleMode = ( dynamic_cast<RiaGuiApplication*>( app.get() ) == nullptr );
attributes["app.console_mode"] = RiaStdStringTools::boolToString( isConsoleMode );
attributes["app.version"] = RiaApplication::getVersionStringApp( false );
RiaOpenTelemetryManager::instance().reportEventAsync( "app.started", attributes );
}
if ( !result )
{
std::vector<cvf::String> unknownOptions = progOpt.unknownOptions();
QString unknownOptionsText;
for ( cvf::String option : unknownOptions )
{
unknownOptionsText += QString( "\tUnknown option: %1\n" ).arg( cvfqt::Utils::toQString( option ) );
}
app->showFormattedTextInMessageBoxOrConsole(
"ERROR: Unknown command line options detected ! \n" + unknownOptionsText + "\n\n" +
"The current command line options in ResInsight are:\n" + app->commandLineParameterHelp() );
if ( dynamic_cast<RiaGuiApplication*>( app.get() ) == nullptr )
{
return 1;
}
}
QLocale::setDefault( QLocale( QLocale::English, QLocale::UnitedStates ) );
setlocale( LC_NUMERIC, "C" );
// Set up signal handlers for genuine fault/abort signals only.
// SIGINT and SIGTERM are graceful termination requests (Ctrl-C, kill, or a
// controlling process such as the Python rips client). They must NOT be routed
// through the crash handler: doing so captures the stack trace of wherever the
// main thread happens to be parked - e.g. inside the multi-second gRPC server
// EventEngine teardown during normal shutdown - and reports it as a spurious
// "Crash". Leaving them with their default disposition lets the process
// terminate cleanly.
#ifndef WIN32
// Use SA_SIGINFO on Linux to capture fault address, signal code, and program counter
struct sigaction sa{};
sa.sa_sigaction = manageSegFailureSA;
sa.sa_flags = SA_SIGINFO;
sigemptyset( &sa.sa_mask );
sigaction( SIGILL, &sa, nullptr );
sigaction( SIGFPE, &sa, nullptr );
sigaction( SIGSEGV, &sa, nullptr );
sigaction( SIGABRT, &sa, nullptr );
#else
signal( SIGILL, manageSegFailure );
signal( SIGFPE, manageSegFailure );
signal( SIGSEGV, manageSegFailure );
signal( SIGABRT, manageSegFailure );
#endif
// Capture uncaught C++ exceptions at the throw site (before the stack unwinds) with full type,
// message and stack trace. The SIGABRT signal handler above remains a fallback for direct
// abort()/assert() failures that do not go through std::terminate.
std::set_terminate( manageTerminate );
// Handle the command line arguments.
// Todo: Move to a one-shot timer, delaying the execution until we are inside the event loop.
// The complete handling of the resulting ApplicationStatus must be moved along.
// The reason for this is: deleteLater() does not work outside the event loop
// Make execution of command line stuff operate in identical conditions as interactive operation.
RiaApplication::ApplicationStatus status = app->handleArguments( &progOpt );
if ( status == RiaApplication::ApplicationStatus::EXIT_COMPLETED )
{
// Make sure project is closed to avoid assert and crash in destruction of widgets
app->closeProject();
app.reset();
RiaMainTools::releaseSingletonAndFactoryObjects();
return 0;
}
else if ( status == RiaApplication::ApplicationStatus::EXIT_WITH_ERROR )
{
// Make sure project is closed to avoid assert and crash in destruction of widgets
app->closeProject();
return 2;
}
else if ( status == RiaApplication::ApplicationStatus::KEEP_GOING )
{
int exitCode = 0;
// No try/catch around the event loop: an uncaught exception must reach std::set_terminate
// (manageTerminate) with the stack still intact so the crash report captures the throw site.
{
#ifdef ENABLE_GRPC
auto grpcInterface = dynamic_cast<RiaGrpcApplicationInterface*>( app.get() );
if ( grpcInterface && grpcInterface->initializeGrpcServer( progOpt ) )
{
grpcInterface->launchGrpcServer();
if ( cvf::Option o = progOpt.option( "portnumberfile" ) )
{
if ( o.valueCount() == 1 )
{
int portNumber = grpcInterface->portNumber();
QString fileName = QString::fromStdString( o.value( 0 ).toStdString() );
// Write port number to the file given file.
// Temp file is used to avoid incomplete reads.
QString tempFilePath = fileName + ".tmp";
QFile file( tempFilePath );
if ( file.open( QIODevice::WriteOnly | QIODevice::Text ) )
{
QTextStream out( &file );
out << portNumber << "\n";
}
file.close();
QFile::rename( tempFilePath, fileName );
}
}
}
#endif
// Check if max thread count is set either from command line or preferences
app->setThreadCount();
exitCode = QCoreApplication::instance()->exec();
}
app.reset();
RiaMainTools::releaseSingletonAndFactoryObjects();
return exitCode;
}
CVF_ASSERT( false && "Unknown ApplicationStatus" );
return -1;
}