diff --git a/.gitignore b/.gitignore index cf20bbc9c..e829a3cf2 100644 --- a/.gitignore +++ b/.gitignore @@ -76,3 +76,6 @@ test-reports/ *.pb.cc *.pb.h bin + +# AI +tmpclaude* diff --git a/DeviceAdapters/NIDAQ/NIAnalogOutputPort.cpp b/DeviceAdapters/NIDAQ/NIAnalogOutputPort.cpp index decc7d6fe..121c6722a 100644 --- a/DeviceAdapters/NIDAQ/NIAnalogOutputPort.cpp +++ b/DeviceAdapters/NIDAQ/NIAnalogOutputPort.cpp @@ -66,6 +66,28 @@ NIAnalogOutputPort::~NIAnalogOutputPort() int NIAnalogOutputPort::Initialize() +{ + // See NIDAQHub::Initialize() for why this wrapper exists. + try + { + return InitializeImpl(); + } + catch (const std::exception& e) + { + LogMessage(std::string("EXCEPTION in NIAnalogOutputPort::Initialize (port ") + + niPort_ + "): " + typeid(e).name() + ": " + e.what()); + return DEVICE_ERR; + } + catch (...) + { + LogMessage("Unknown (non-standard) C++ exception in " + "NIAnalogOutputPort::Initialize (port " + niPort_ + ")"); + return DEVICE_ERR; + } +} + + +int NIAnalogOutputPort::InitializeImpl() { if (initialized_) return DEVICE_OK; diff --git a/DeviceAdapters/NIDAQ/NIDAQ.cpp b/DeviceAdapters/NIDAQ/NIDAQ.cpp index 821d54487..dca769804 100644 --- a/DeviceAdapters/NIDAQ/NIDAQ.cpp +++ b/DeviceAdapters/NIDAQ/NIDAQ.cpp @@ -67,33 +67,52 @@ MODULE_API void InitializeModuleData() } +// Returns true if name starts with prefix, and if so stores the remainder of +// name (the part after the prefix) in suffix. +// +// Using std::string::substr(pos) directly for this throws std::out_of_range +// when pos > size(). Such an exception escaping CreateDevice() propagates +// through MMCore and the JNI boundary and terminates the JVM, so avoid it. +static bool SplitDeviceNamePrefix(const char* name, const char* prefix, + std::string& suffix) +{ + const size_t prefixLen = strlen(prefix); + const std::string nameStr(name); + if (nameStr.size() < prefixLen) + return false; + if (nameStr.compare(0, prefixLen, prefix) != 0) + return false; + suffix = nameStr.substr(prefixLen); + return true; +} + + MODULE_API MM::Device* CreateDevice(const char* deviceName) { if (deviceName == 0) return 0; - if (strcmp(deviceName, g_DeviceNameNIDAQHub) == 0) - { - return new NIDAQHub; - } - else if (std::string(deviceName). - substr(0, strlen(g_DeviceNameNIDAQAOPortPrefix)) == - g_DeviceNameNIDAQAOPortPrefix) + try { - return new NIAnalogOutputPort(std::string(deviceName). - substr(strlen(g_DeviceNameNIDAQAOPortPrefix))); - } - else if (std::string(deviceName).substr(0, strlen(g_DeviceNameNIDAQDOPortPrefix)) == - g_DeviceNameNIDAQDOPortPrefix) - { - return new DigitalOutputPort(std::string(deviceName). - substr(strlen(g_DeviceNameNIDAQDOPortPrefix))); - } - else if (std::string(deviceName).substr(0, strlen(g_DeviceNameNIDAQAIPortPrefix)) == - g_DeviceNameNIDAQAIPortPrefix) - { - return new NIAnalogInputPort(std::string(deviceName). - substr(strlen(g_DeviceNameNIDAQAIPortPrefix))); + if (strcmp(deviceName, g_DeviceNameNIDAQHub) == 0) + { + return new NIDAQHub; + } + + std::string port; + if (SplitDeviceNamePrefix(deviceName, g_DeviceNameNIDAQAOPortPrefix, port)) + return new NIAnalogOutputPort(port); + if (SplitDeviceNamePrefix(deviceName, g_DeviceNameNIDAQDOPortPrefix, port)) + return new DigitalOutputPort(port); + if (SplitDeviceNamePrefix(deviceName, g_DeviceNameNIDAQAIPortPrefix, port)) + return new NIAnalogInputPort(port); + } + catch (const std::exception&) + { + // No device object exists yet, so LogMessage() is not available here. + // Returning 0 makes MMCore report a normal "failed to instantiate + // device" error rather than letting the exception kill the process. + return 0; } return 0; @@ -176,6 +195,29 @@ NIDAQHub::~NIDAQHub() int NIDAQHub::Initialize() +{ + // Wrapper: a C++ exception escaping Initialize() propagates through MMCore + // and the JNI boundary and terminates the JVM with no usable diagnostic. + // Catch it here, log what it was, and report a normal device error instead. + try + { + return InitializeImpl(); + } + catch (const std::exception& e) + { + LogMessage(std::string("EXCEPTION in NIDAQHub::Initialize: ") + + typeid(e).name() + ": " + e.what()); + return DEVICE_ERR; + } + catch (...) + { + LogMessage("Unknown (non-standard) C++ exception in NIDAQHub::Initialize"); + return DEVICE_ERR; + } +} + + +int NIDAQHub::InitializeImpl() { if (initialized_) return DEVICE_OK; diff --git a/DeviceAdapters/NIDAQ/NIDAQ.h b/DeviceAdapters/NIDAQ/NIDAQ.h index 73c4f876c..9fb27af6a 100644 --- a/DeviceAdapters/NIDAQ/NIDAQ.h +++ b/DeviceAdapters/NIDAQ/NIDAQ.h @@ -29,6 +29,7 @@ #include #include +#include #include @@ -219,7 +220,10 @@ class NIDAQHub : public HubBase, NIDAQHub(); virtual ~NIDAQHub(); + // Initialize() is a thin exception-catching wrapper around InitializeImpl(), + // which holds the actual initialization logic. virtual int Initialize(); + int InitializeImpl(); virtual int Shutdown(); virtual void GetName(char* name) const; @@ -347,7 +351,9 @@ class NIAnalogOutputPort : public CSignalIOBase, NIAnalogOutputPort(const std::string& port); virtual ~NIAnalogOutputPort(); + // See NIDAQHub::Initialize(). virtual int Initialize(); + int InitializeImpl(); virtual int Shutdown(); virtual void GetName(char* name) const; diff --git a/DeviceAdapters/NIDAQ/NIDigitalOutputPort.cpp b/DeviceAdapters/NIDAQ/NIDigitalOutputPort.cpp index 575628568..7e97d397b 100644 --- a/DeviceAdapters/NIDAQ/NIDigitalOutputPort.cpp +++ b/DeviceAdapters/NIDAQ/NIDigitalOutputPort.cpp @@ -25,6 +25,32 @@ const int NO_INPUT_LINE = -1; +// Parse one sequence element into an unsigned long, rejecting anything that is +// not a complete valid number in [0, maxValue]. +// +// std::stoul throws std::invalid_argument for non-numeric input and +// std::out_of_range on overflow. Such an exception escaping a device adapter +// propagates through MMCore and the JNI boundary and terminates the JVM, so it +// must be caught here and reported as an ordinary device error instead. +static bool ParseSequenceValue(const std::string& s, unsigned long maxValue, + unsigned long& result) +{ + try + { + size_t pos = 0; + const unsigned long num = std::stoul(s, &pos, 0); + if (pos != s.size() || num > maxValue) + return false; + result = num; + return true; + } + catch (const std::exception&) + { + return false; + } +} + + DigitalOutputPort::DigitalOutputPort(const std::string& port) : ErrorTranslator(21000, 21999, &DigitalOutputPort::SetErrorText), niPort_(port), @@ -295,16 +321,10 @@ int DigitalOutputPort::OnState(MM::PropertyBase* pProp, MM::ActionType eAct) sequence8_.clear(); for (unsigned int i = 0; i < sequence.size(); i++) { - size_t pos; - unsigned long num = std::stoul(sequence[i], &pos, 0); - - // Check if the entire string was used for conversion and if the number fits within uint8_t range - if (pos != sequence[i].size() || num > 255) { - // "Value out of range for uint8_t" - return ERR_SEQUENCE_INVALID_NUMBER; - } - uint8_t val = static_cast(num); - sequence8_.push_back(val); + unsigned long num; + if (!ParseSequenceValue(sequence[i], 255, num)) + return ERR_SEQUENCE_INVALID_NUMBER; + sequence8_.push_back(static_cast(num)); } GetHub()->getDOHub8()->RemoveDOPortFromSequencing(niPort_); return GetHub()->getDOHub8()->AddDOPortToSequencing(niPort_, sequence8_); @@ -314,16 +334,10 @@ int DigitalOutputPort::OnState(MM::PropertyBase* pProp, MM::ActionType eAct) sequence16_.clear(); for (unsigned int i = 0; i < sequence.size(); i++) { - size_t pos; - unsigned long num = std::stoul(sequence[i], &pos, 0); - - // Check if the entire string was used for conversion and if the number fits within uint16_t range - if (pos != sequence[i].size() || num > 65535) { - // "Value out of range for uint16_t" - return ERR_SEQUENCE_INVALID_NUMBER; - } - uint16_t val = static_cast(num); - sequence16_.push_back(val); + unsigned long num; + if (!ParseSequenceValue(sequence[i], 65535, num)) + return ERR_SEQUENCE_INVALID_NUMBER; + sequence16_.push_back(static_cast(num)); } GetHub()->getDOHub16()->RemoveDOPortFromSequencing(niPort_); return GetHub()->getDOHub16()->AddDOPortToSequencing(niPort_, sequence16_); @@ -333,15 +347,10 @@ int DigitalOutputPort::OnState(MM::PropertyBase* pProp, MM::ActionType eAct) sequence32_.clear(); for (unsigned int i = 0; i < sequence.size(); i++) { - size_t pos; - unsigned long num = std::stoul(sequence[i], &pos, 0); - - // Check if the entire string was used for conversion - if (pos != sequence[i].size()) { - return ERR_SEQUENCE_INVALID_NUMBER; - } - uint32_t val = static_cast(num); - sequence32_.push_back(val); + unsigned long num; + if (!ParseSequenceValue(sequence[i], 0xFFFFFFFFUL, num)) + return ERR_SEQUENCE_INVALID_NUMBER; + sequence32_.push_back(static_cast(num)); } GetHub()->getDOHub32()->RemoveDOPortFromSequencing(niPort_); return GetHub()->getDOHub32()->AddDOPortToSequencing(niPort_, sequence32_);