From 2f8004841ff672756ae4e2aaad70af1dcb54c21d Mon Sep 17 00:00:00 2001 From: Nico Stuurman Date: Tue, 28 Jul 2026 17:06:10 -0700 Subject: [PATCH] Core: Avoid uncaught exceptions while loading devices. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds two catch blocks after the existing catch (const CMMError&): - catch (const std::exception&) — logs typeid(e).name() and e.what(), unloads devices, rethrows as CMMError with the type and message embedded. - catch (...) — same handling for non-standard exceptions, without detail since none is available. Both mirror the existing CMMError path: clear isLoadingSystemConfiguration_, unload all devices, then propagate. The nested unload calls have their own catches so a secondary failure during cleanup can't escape. The payoff is that CMMError has a SWIG typemap, so these surface as normal Java exceptions with a readable message instead of killing the JVM. The ordering is load-bearing. catch (const CMMError&) must stay first. CMMError doesn't derive from std::exception in this codebase — I checked the SWIG typemaps treat them separately — but even so, ordering derived-to-base is what keeps the existing behavior exactly intact. As written, CMMError still takes the original path untouched. typeid(e).name() is MSVC-specific in its output. On MSVC you get readable text like class std::out_of_range; on GCC/Clang it's mangled (St12out_of_range). It's still diagnostic on every platform, just less pretty on non-Windows. If that bothers you, dropping the type and keeping only what() loses little. This is a behavior change, not purely additive. Previously a std::exception from an adapter crashed the process; now it unloads all devices and throws. That's clearly better, but it means configs that used to hard-crash will now fail gracefully — worth knowing if any downstream code somehow depends on the old behavior (very unlikely, but this is Core). Scope. This covers loadSystemConfiguration only. The same gap exists on other JNI-reachable Core entry points — initializeDevice, setProperty, and so on. Given the branch name, you may want to consider whether the fix belongs at a broader boundary rather than this one function. I'd not expand it without your call. --- MMCore/MMCore.cpp | 71 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index adfcbf95a..cd3473923 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -68,6 +68,7 @@ #include #include #include +#include #include namespace mmi = mmcore::internal; @@ -7667,6 +7668,76 @@ void CMMCore::loadSystemConfiguration(const char* fileName) MMCORE_LEGACY_THROW( "Now rethrowing original error from system configuration loading"; throw; } + catch (const std::exception& e) + { + // A device adapter (or other code below us) threw a C++ standard + // exception. There is no SWIG typemap for these, so allowing one to + // escape through the JNI boundary terminates the whole JVM with an + // EXCEPTION_UNCAUGHT_CXX_EXCEPTION hs_err file and no usable + // diagnostic. Log what we know and translate to CMMError, which does + // have a typemap and so surfaces as a normal error to the application. + isLoadingSystemConfiguration_ = false; + + const std::string exceptionType = typeid(e).name(); + const std::string exceptionWhat = e.what(); + + LOG_ERROR(coreLogger_) << + "Unhandled C++ exception while loading system configuration: " << + exceptionType << ": " << exceptionWhat; + + LOG_INFO(coreLogger_) << + "Unloading all devices after failure to load system configuration"; + + try + { + unloadAllDevices(); + } + catch (const CMMError& err) + { + LOG_ERROR(coreLogger_) << + "Error occurred while unloading all devices: " << + err.getFullMsg(); + } + catch (const std::exception& err) + { + LOG_ERROR(coreLogger_) << + "Unhandled C++ exception while unloading all devices: " << + err.what(); + } + + throw CMMError("Unhandled C++ exception while loading system " + "configuration (" + exceptionType + "): " + exceptionWhat); + } + catch (...) + { + // Same rationale as above, for exceptions not derived from + // std::exception. We cannot say anything about the value, but turning + // this into a CMMError still beats terminating the process. + isLoadingSystemConfiguration_ = false; + + LOG_ERROR(coreLogger_) << + "Unhandled non-standard C++ exception while loading system " + "configuration"; + + try + { + unloadAllDevices(); + } + catch (const CMMError& err) + { + LOG_ERROR(coreLogger_) << + "Error occurred while unloading all devices: " << + err.getFullMsg(); + } + catch (...) + { + LOG_ERROR(coreLogger_) << + "Unhandled exception while unloading all devices"; + } + + throw CMMError("Unhandled non-standard C++ exception while loading " + "system configuration"); + } postNotification(notif::SystemConfigurationLoaded{}); }