Repository :
https://github.com/FarGroup/FarManager
On branch : master
Link :
https://github.com/FarGroup/FarManager/commit/1080d0d2bc0b098f2c4aa5fb7f4895261d920535
>---------------------------------------------------------------
commit 1080d0d2bc0b098f2c4aa5fb7f4895261d920535
Author: Alex Alabuzhev <
alab...@gmail.com>
Date: Thu Jan 8 23:34:34 2026 +0000
Exception handling improvements
>---------------------------------------------------------------
1080d0d2bc0b098f2c4aa5fb7f4895261d920535
_build/vc/config/common.mak | 1 +
_build/vc/config/common.props | 2 +-
far/common/shims_pre.hpp | 4 ++
far/exception_handler.cpp | 145 +++++++++++++++++++++++++++++++----------
far/exception_handler.hpp | 9 +++
far/exception_handler_test.cpp | 9 +++
far/far.vcxproj | 2 +-
far/main.cpp | 1 +
far/makefile_gcc | 2 +-
far/makefile_vc | 2 +-
10 files changed, 140 insertions(+), 37 deletions(-)
diff --git a/_build/vc/config/common.mak b/_build/vc/config/common.mak
index 6dabfd000..bfe825000 100644
--- a/_build/vc/config/common.mak
+++ b/_build/vc/config/common.mak
@@ -54,6 +54,7 @@ CFLAGS = $(CFLAGS)\
/volatile:iso\
/Wall\
/we4013\
+ /we4715\
/wd4464\
/wd4668\
/utf-8\
diff --git a/_build/vc/config/common.props b/_build/vc/config/common.props
index 4de110234..fe0b0e679 100644
--- a/_build/vc/config/common.props
+++ b/_build/vc/config/common.props
@@ -134,7 +134,7 @@
<ExceptionHandling>Sync</ExceptionHandling>
<WarningLevel>EnableAllWarnings</WarningLevel>
<ExternalWarningLevel>Level4</ExternalWarningLevel>
- <TreatSpecificWarningsAsErrors>4013</TreatSpecificWarningsAsErrors>
+ <TreatSpecificWarningsAsErrors>4013;4715</TreatSpecificWarningsAsErrors>
<StringPooling>true</StringPooling>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<DisableSpecificDiagnostics>869,3280,4668</DisableSpecificDiagnostics>
diff --git a/far/common/shims_pre.hpp b/far/common/shims_pre.hpp
index 58cedf289..0222b7ff3 100644
--- a/far/common/shims_pre.hpp
+++ b/far/common/shims_pre.hpp
@@ -52,6 +52,10 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#if COMPILER(GCC)
+// Disable incompatible _wassert in assert.h and redeclare, so that we can override it
+#define __ASSERT_H_
+extern "C" void _wassert(wchar_t const* Message, wchar_t const* File, unsigned Line);
+
// Current implementation of wcschr etc. in gcc removes const from the returned pointer. The issue has been opened since 2007.
// These semi-magical defines and appropriate inline overloads in shims_post.hpp are intended to fix this madness.
diff --git a/far/exception_handler.cpp b/far/exception_handler.cpp
index 2decb0c4c..9f5506608 100644
--- a/far/exception_handler.cpp
+++ b/far/exception_handler.cpp
@@ -76,6 +76,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// External:
#include "format.hpp"
+#include <crtdbg.h>
+
#if !IS_MICROSOFT_SDK()
#include <cxxabi.h>
#endif
@@ -1540,6 +1542,7 @@ static string exception_details(string_view const Module, EXCEPTION_RECORD const
return string(Message);
case STATUS_INVALID_CRUNTIME_PARAMETER:
+ case STATUS_ASSERTION_FAILURE:
return Message.empty()?
default_details() :
far::format(L"{} Expression: {}"sv, default_details(), Message);
@@ -1916,20 +1919,16 @@ static handler_result handle_generic_exception(
if (AnythingOnDisk && os::is_interactive_user_session())
OpenFolderInShell(ReportLocation);
- const auto UseDialog = !ForceStderrExceptionUI && Global && Global->WindowManager && !Global->WindowManager->ManagerIsDown();
+ const auto UseDialog = !s_ReportToStdErr && !ForceStderrExceptionUI && Global && Global->WindowManager && !Global->WindowManager->ManagerIsDown();
const auto CanContinue = !(Context.exception_record().ExceptionFlags & EXCEPTION_NONCONTINUABLE);
- const auto Result = AnythingOnDisk || ReportInClipboard?
+ const auto Result =
(UseDialog? ExcDialog : ExcConsole)(
Context.code(),
CanContinue,
- AnythingOnDisk?
- ReportLocation :
- msg(lng::MExceptionDialogClipboard),
+ AnythingOnDisk? ReportLocation : ReportInClipboard? msg(lng::MExceptionDialogClipboard) : BugReport,
PluginInfo
- ) :
- // Should never happen - neither the filesystem nor clipboard are writable, so just dump it to the screen:
- ExcConsole(Context.code(), CanContinue, BugReport, PluginInfo);
+ );
switch (Result)
{
@@ -2163,20 +2162,14 @@ void handle_unknown_exception(source_location const& Location)
std::unreachable();
}
-static void abort_handler_impl()
+static handler_result abort_handler_impl()
{
if (!HandleCppExceptions)
- {
- restore_system_exception_handler();
- return;
- }
+ return handler_result::continue_search;
static auto InsideHandler = false;
if (InsideHandler)
- {
- restore_system_exception_handler();
os::process::terminate(STATUS_FATAL_APP_EXIT);
- }
InsideHandler = true;
SCOPE_EXIT{ InsideHandler = false; };
@@ -2187,10 +2180,7 @@ static void abort_handler_impl()
if (const auto Info = os::debug::exception_information(); Info.ContextRecord && Info.ExceptionRecord && !is_fake_cpp_exception(*Info.ExceptionRecord))
{
if (handle_seh_exception(exception_context(Info), {}, Location) == handler_result::continue_search)
- {
- restore_system_exception_handler();
- return;
- }
+ return handler_result::continue_search;
}
// It's a C++ exception, implemented in some other way (GCC)
@@ -2214,11 +2204,7 @@ static void abort_handler_impl()
exception_context const Context{ os::debug::fake_exception_information(STATUS_FAR_ABORT) };
error_state_ex const LastError{ os::last_error(), {}, errno };
- if (handle_generic_exception(Context, Location, {}, {}, L"Abnormal termination"sv, LastError) == handler_result::continue_search)
- {
- restore_system_exception_handler();
- return;
- }
+ return handle_generic_exception(Context, Location, {}, {}, L"Abnormal termination"sv, LastError);
}
static LONG WINAPI unhandled_exception_filter_impl(EXCEPTION_POINTERS* const Pointers)
@@ -2258,10 +2244,12 @@ static void signal_handler_impl(int const Signal)
{
case SIGABRT:
// terminate() defaults to abort(), so this also covers various C++ runtime failures.
- return abort_handler_impl();
+ if (abort_handler_impl() == handler_result::continue_search)
+ restore_system_exception_handler();
+ break;
default:
- return;
+ break;
}
}
@@ -2292,17 +2280,11 @@ static void default_invalid_parameter_handler(const wchar_t* const Expression, c
static void invalid_parameter_handler_impl(const wchar_t* const Expression, const wchar_t* const Function, const wchar_t* const File, unsigned int const Line, uintptr_t const Reserved)
{
if (!HandleCppExceptions)
- {
- restore_system_exception_handler();
std::abort();
- }
static auto InsideHandler = false;
if (InsideHandler)
- {
- restore_system_exception_handler();
os::process::terminate(STATUS_INVALID_CRUNTIME_PARAMETER);
- }
InsideHandler = true;
SCOPE_EXIT{ InsideHandler = false; };
@@ -2345,6 +2327,103 @@ invalid_parameter_handler::~invalid_parameter_handler()
_set_invalid_parameter_handler(m_PreviousHandler);
}
+static handler_result assert_handler_impl(string_view const Message, source_location const& Location = source_location::current())
+{
+ // We only want to intercept asserts on CI (where s_ReportToStdErr is set).
+ // For normal interactive debugging the standard CRT dialog is more convenient.
+ if (!HandleCppExceptions || !s_ReportToStdErr)
+ return handler_result::continue_search;
+
+ static auto InsideHandler = false;
+ if (InsideHandler)
+ os::process::terminate(STATUS_ASSERTION_FAILURE);
+
+ InsideHandler = true;
+ SCOPE_EXIT{ InsideHandler = false; };
+
+ exception_context const Context{ os::debug::fake_exception_information(STATUS_ASSERTION_FAILURE, true) };
+ error_state_ex const LastError{ os::last_error(), {}, errno };
+
+ return handle_generic_exception(Context, Location, {}, {}, Message, LastError);
+}
+
+#ifdef _DEBUG
+#if IS_MICROSOFT_SDK()
+static int crt_report_hook_impl(int const ReportType, wchar_t* const Message, int*)
+{
+ const auto MessageStr = trim_right(string_view{ Message });
+
+ switch (ReportType)
+ {
+ case _CRT_WARN:
+ LOGWARNING(L"{}"sv, MessageStr);
+ return FALSE;
+
+ case _CRT_ERROR:
+ LOGERROR(L"{}"sv, MessageStr);
+ return FALSE;
+
+ case _CRT_ASSERT:
+ LOGERROR(L"{}"sv, MessageStr);
+
+ switch (assert_handler_impl(MessageStr))
+ {
+ case handler_result::execute_handler:
+ return FALSE;
+
+ case handler_result::continue_execution:
+ return TRUE;
+
+ case handler_result::continue_search:
+ return FALSE;
+ }
+ }
+
+ return FALSE;
+}
+#endif
+#endif
+
+crt_report_hook::crt_report_hook()
+{
+#ifdef _DEBUG
+#if IS_MICROSOFT_SDK()
+ _CrtSetReportHookW2(_CRT_RPTHOOK_INSTALL, crt_report_hook_impl);
+#endif
+#endif
+}
+
+crt_report_hook::~crt_report_hook()
+{
+#ifdef _DEBUG
+#if IS_MICROSOFT_SDK()
+ _CrtSetReportHookW2(_CRT_RPTHOOK_REMOVE, crt_report_hook_impl);
+#endif
+#endif
+}
+
+#pragma push_macro("_wassert")
+#undef _wassert
+extern "C" void _wassert(wchar_t const* Message, wchar_t const* File, unsigned Line);
+constexpr auto real_wassert = _wassert;
+#pragma pop_macro("_wassert")
+
+void far_assert(wchar_t const* const Message, wchar_t const* const File, unsigned const Line)
+{
+ switch (assert_handler_impl(Message, { encoding::utf8::get_bytes(File).c_str(), "assert", Line }))
+ {
+ case handler_result::execute_handler:
+ std::abort();
+
+ case handler_result::continue_execution:
+ return;
+
+ case handler_result::continue_search:
+ real_wassert(Message, File, Line);
+ break;
+ }
+}
+
static LONG NTAPI vectored_exception_handler_impl(EXCEPTION_POINTERS* const Pointers)
{
if (static_cast<NTSTATUS>(Pointers->ExceptionRecord->ExceptionCode) == STATUS_HEAP_CORRUPTION)
diff --git a/far/exception_handler.hpp b/far/exception_handler.hpp
index 6b14237fb..e36af376e 100644
--- a/far/exception_handler.hpp
+++ b/far/exception_handler.hpp
@@ -103,6 +103,15 @@ private:
_invalid_parameter_handler m_PreviousHandler;
};
+class crt_report_hook
+{
+public:
+ NONCOPYABLE(crt_report_hook);
+
+ crt_report_hook();
+ ~crt_report_hook();
+};
+
class vectored_exception_handler
{
public:
diff --git a/far/exception_handler_test.cpp b/far/exception_handler_test.cpp
index cdcddeba4..44b9ea44b 100644
--- a/far/exception_handler_test.cpp
+++ b/far/exception_handler_test.cpp
@@ -57,6 +57,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// External:
+#include <crtdbg.h>
+
//----------------------------------------------------------------------------
namespace tests
@@ -363,6 +365,12 @@ namespace tests
assert(!Value);
}
+ static void cpp_crt_assertion_failure()
+ {
+ if ([[maybe_unused]] volatile auto Value = true)
+ _ASSERTE(!Value);
+ }
+
static void seh_access_violation_read()
{
volatile const int* InvalidAddress = nullptr;
@@ -709,6 +717,7 @@ static bool ExceptionTestHook(Manager::Key const& key)
{ tests::cpp_pure_virtual_call, L"pure virtual call"sv },
{ tests::cpp_invalid_parameter, L"invalid parameter"sv },
{ tests::cpp_assertion_failure, L"assertion failure"sv },
+ { tests::cpp_crt_assertion_failure, L"CRT assertion failure"sv },
{ tests::cpp_memory_leak, L"memory leak"sv },
};
diff --git a/far/far.vcxproj b/far/far.vcxproj
index edd2225f7..ac97f543f 100644
--- a/far/far.vcxproj
+++ b/far/far.vcxproj
@@ -33,7 +33,7 @@ if not exist $(BootstrapDir) mkdir $(BootstrapDir)</Command>
<PrecompiledHeaderFile>headers.hpp</PrecompiledHeaderFile>
<PrecompiledHeaderOutputFile>$(IntDir)headers.pch</PrecompiledHeaderOutputFile>
<ForcedIncludeFiles>%(ForcedIncludeFiles);headers.hpp;memcheck.hpp</ForcedIncludeFiles>
- <PreprocessorDefinitions>FAR_USE_INTERNALS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <PreprocessorDefinitions>FAR_USE_INTERNALS;_wassert=far_assert;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
diff --git a/far/main.cpp b/far/main.cpp
index f15544640..fa0ed74ff 100644
--- a/far/main.cpp
+++ b/far/main.cpp
@@ -1006,6 +1006,7 @@ static int wmain_seh()
SCOPED_ACTION(vectored_exception_handler);
SCOPED_ACTION(signal_handler);
SCOPED_ACTION(invalid_parameter_handler);
+ SCOPED_ACTION(crt_report_hook);
SCOPED_ACTION(new_handler);
#ifdef ENABLE_TESTS
diff --git a/far/makefile_gcc b/far/makefile_gcc
index a774758cc..ee9528861 100644
--- a/far/makefile_gcc
+++ b/far/makefile_gcc
@@ -198,7 +198,7 @@ TESTOBJS = $(OBJDIR)api_test_c.testobj $(OBJDIR)api_test_c++.testobj
ADDINCLUDE=-I$(BOOTSTRAPDIR)..
FORCEINCLUDE=$(patsubst %, -include %, headers.hpp $(FORCEINCLUDELIST_NO_PCH))
-CPPFLAGS += $(ADDINCLUDE) -D FAR_USE_INTERNALS
+CPPFLAGS += $(ADDINCLUDE) -D FAR_USE_INTERNALS -D _wassert=far_assert
CPPFLAGS += \
-fvisibility=hidden \
diff --git a/far/makefile_vc b/far/makefile_vc
index a732604d3..3b1f57e07 100644
--- a/far/makefile_vc
+++ b/far/makefile_vc
@@ -243,7 +243,7 @@ TEST_OBJS = "$(INTDIR)\api_test_c.testobj" "$(INTDIR)\api_test_c++.testobj"
ADDINCLUDE=/I $(BOOTSTRAPDIR)..
-CPPFLAGS = $(CPPFLAGS) $(ADDINCLUDE) /D "FAR_USE_INTERNALS"
+CPPFLAGS = $(CPPFLAGS) $(ADDINCLUDE) /D "FAR_USE_INTERNALS" /D "_wassert=far_assert"
RFLAGS = $(RFLAGS) $(ADDINCLUDE)
!if ("$(ENABLE_TESTS)" == "1")