From 90e683a16ec1f267d3efd1b3fd1bff0b9ac9691e Mon Sep 17 00:00:00 2001
From: dnpwwo <kendel.boul@gmail.com>
Date: Tue, 1 Mar 2022 22:01:14 +1100
Subject: [PATCH] BugFix: Prevent crash processing Python exceptions on Linux.
 Uplift: Create Device objects using Python rather than C++

---
 main/EventsPythonDevice.h   |  2 +-
 main/EventsPythonModule.cpp | 92 ++++++++++++++++---------------------
 2 files changed, 40 insertions(+), 54 deletions(-)

--- a/main/EventsPythonDevice.h
+++ b/main/EventsPythonDevice.h
@@ -55,6 +55,6 @@
 					   nullptr,
 					   nullptr };
 
-	  static PyObject* PDeviceType;
+	  static PyTypeObject* PDeviceType;
     }
 #endif
--- a/main/EventsPythonModule.cpp
+++ b/main/EventsPythonModule.cpp
@@ -16,11 +16,11 @@ namespace Plugins
 {
 #define GETSTATE(m) ((struct eventModule_state*)PyModule_GetState(m))
 
-	void*   m_PyInterpreter;
-    bool	ModuleInitialized = false;
+	PyThreadState*	m_PyInterpreter;
+    bool			m_ModuleInitialized = false;
 
     struct eventModule_state {
-        PyObject*	error;
+		PyObject*	error;
     };
 
 	static PyMethodDef DomoticzEventsMethods[] = { 
@@ -116,7 +116,7 @@ namespace Plugins
 		PyType_Spec PDeviceSpec = { "DomoticzEvents.PDevice", sizeof(PDevice), 0,
 							  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, PDeviceSlots };
 
-		PDeviceType = PyType_FromSpec(&PDeviceSpec);
+		PDeviceType = (PyTypeObject*)PyType_FromSpec(&PDeviceSpec);
 		PyModule_AddObject(pModule, "PDevice", (PyObject*)PDeviceType);
 
 		return pModule;
@@ -169,7 +169,7 @@ namespace Plugins
                 _log.Log(LOG_ERROR, "EventSystem - Python: Failed to initialize module.");
                 return false;
             }
-            ModuleInitialized = true;
+            m_ModuleInitialized = true;
             return true;
 	}
 
@@ -232,12 +232,6 @@ namespace Plugins
 		else
 		{
 			std::string	sTypeText("Unknown");
-			if (pExcept)
-			{
-				PyTypeObject* TypeName = (PyTypeObject*)pExcept;
-				PyNewRef	pName = PyObject_GetAttrString((PyObject*)TypeName, "__name__");
-				sTypeText = (std::string)pName;
-			}
 
 			/* See if we can get a full traceback */
 			PyNewRef	pModule = PyImport_ImportModule("traceback");
@@ -245,7 +239,7 @@ namespace Plugins
 			{
 				PyNewRef	pFunc = PyObject_GetAttrString(pModule, "format_exception");
 				if (pFunc && PyCallable_Check(pFunc)) {
-					PyNewRef	pList = PyObject_CallFunctionObjArgs(pFunc, pExcept, pValue, pTraceback, NULL);
+					PyNewRef	pList = PyObject_CallFunctionObjArgs(pFunc, (PyObject*)pExcept, (PyObject*)pValue, (PyObject*)pTraceback, NULL);
 					if (pList)
 					{
 						for (Py_ssize_t i = 0; i < PyList_Size(pList); i++)
@@ -263,16 +257,19 @@ namespace Plugins
 					}
 					else
 					{
+						if (pExcept) sTypeText = pExcept.Attribute("__name__");
 						_log.Log(LOG_ERROR, "Exception: '%s'.  No traceback available.", sTypeText.c_str());
 					}
 				}
 				else
 				{
+					if (pExcept) sTypeText = pExcept.Attribute("__name__");
 					_log.Log(LOG_ERROR, "'format_exception' lookup failed, exception: '%s'.  No traceback available.", sTypeText.c_str());
 				}
 			}
 			else
 			{
+				if (pExcept) sTypeText = pExcept.Attribute("__name__");
 				_log.Log(LOG_ERROR, "'Traceback' module import failed, exception: '%s'.  No traceback available.", sTypeText.c_str());
 			}
 		}
@@ -280,11 +277,11 @@ namespace Plugins
 	}
 
 	void PythonEventsProcessPython(const std::string &reason, const std::string &filename, const std::string &PyString,
-				       const uint64_t DeviceID, std::map<uint64_t, CEventSystem::_tDeviceStatus> m_devicestates,
-				       std::map<uint64_t, CEventSystem::_tUserVariable> m_uservariables, int intSunRise, int intSunSet)
+				       const uint64_t DeviceID, std::map<uint64_t, CEventSystem::_tDeviceStatus> deviceStates,
+				       std::map<uint64_t, CEventSystem::_tUserVariable> userVariables, int intSunRise, int intSunSet)
 	{
 
-		if (!ModuleInitialized)
+		if (!m_ModuleInitialized)
 		{
 			return;
 		}
@@ -292,7 +289,7 @@ namespace Plugins
 		if (Py_IsInitialized())
 		{
 			if (m_PyInterpreter)
-				PyEval_RestoreThread((PyThreadState *)m_PyInterpreter);
+				PyEval_RestoreThread(m_PyInterpreter);
 
 			PyBorrowedRef	pModule = PythonEventsGetModule();
 			if (pModule)
@@ -305,7 +302,7 @@ namespace Plugins
 					return;
 				}
 
-				PyNewRef	pStrVal = PyUnicode_FromString(m_devicestates[DeviceID].deviceName.c_str());
+				PyNewRef	pStrVal = PyUnicode_FromString(deviceStates[DeviceID].deviceName.c_str());
 				if (PyDict_SetItemString(pModuleDict, "changed_device_name", pStrVal) == -1)
 				{
 					_log.Log(LOG_ERROR, "Python EventSystem: Failed to set changed_device_name.");
@@ -327,22 +324,34 @@ namespace Plugins
 					return;
 				}
 
-				// Mutex
-				// boost::shared_lock<boost::shared_mutex> devicestatesMutexLock1(m_devicestatesMutex);
-
-				for (auto it_type = m_devicestates.begin(); it_type != m_devicestates.end(); ++it_type)
+				for (auto it_type = deviceStates.begin(); it_type != deviceStates.end(); ++it_type)
 				{
 					CEventSystem::_tDeviceStatus sitem = it_type->second;
-					// object deviceStatus = domoticz_module.attr("Device")(sitem.ID, sitem.deviceName, sitem.devType,
-					// sitem.subType, sitem.switchtype, sitem.nValue, sitem.nValueWording, sitem.sValue,
-					// sitem.lastUpdate); devices[sitem.deviceName] = deviceStatus;
 
-					PDevice *aDevice = (PDevice *)PDevice_new((PyTypeObject*)PDeviceType, (PyObject *)nullptr, (PyObject *)nullptr);
-					PyNewRef	pKey = PyUnicode_FromString(sitem.deviceName.c_str());
+					PyNewRef nrArgList = Py_BuildValue("(iOiiiOiOO)",	static_cast<int>(sitem.ID),
+																		PyUnicode_FromString(sitem.deviceName.c_str()),
+																		sitem.devType,
+																		sitem.subType,
+																		sitem.switchtype,
+																		PyUnicode_FromString(sitem.sValue.c_str()),
+																		sitem.nValue,
+																		PyUnicode_FromString(sitem.nValueWording.c_str()),
+																		PyUnicode_FromString(sitem.lastUpdate.c_str()));
+					if (!nrArgList)
+					{
+						_log.Log(LOG_ERROR, "Python EventSystem: Building device argument list failed for key %s.", sitem.deviceName.c_str());
+						continue;
+					}
+					PyNewRef pDevice = PyObject_CallObject((PyObject*)PDeviceType, nrArgList);
+					if (!pDevice)
+					{
+						_log.Log(LOG_ERROR, "Python EventSystem: Event Device object creation failed for key %s.", sitem.deviceName.c_str());
+						continue;
+					}
 
 					if (sitem.ID == DeviceID)
 					{
-						if (PyDict_SetItemString(pModuleDict, "changed_device", (PyObject *)aDevice) == -1)
+						if (PyDict_SetItemString(pModuleDict, "changed_device", (PyObject *)pDevice) == -1)
 						{
 							_log.Log(LOG_ERROR,
 								 "Python EventSystem: Failed to add device '%s' as changed_device.",
@@ -350,36 +359,13 @@ namespace Plugins
 						}
 					}
 
-					if (PyDict_SetItem(pDeviceDict, pKey, (PyObject *)aDevice) == -1)
+					PyNewRef	pKey = PyUnicode_FromString(sitem.deviceName.c_str());
+					if (PyDict_SetItem(pDeviceDict, pKey, (PyObject *)pDevice) == -1)
 					{
 						_log.Log(LOG_ERROR, "Python EventSystem: Failed to add device '%s' to device dictionary.",
 							 sitem.deviceName.c_str());
 					}
-					else
-					{
-
-						// _log.Log(LOG_ERROR, "Python EventSystem: nValueWording '%s' - done. ",
-						// sitem.nValueWording.c_str());
-
-						std::string temp_n_value_string = sitem.nValueWording;
-
-						// If nValueWording contains %, unicode fails?
-
-						aDevice->id = static_cast<int>(sitem.ID);
-						aDevice->name = PyUnicode_FromString(sitem.deviceName.c_str());
-						aDevice->type = sitem.devType;
-						aDevice->sub_type = sitem.subType;
-						aDevice->switch_type = sitem.switchtype;
-						aDevice->n_value = sitem.nValue;
-						aDevice->n_value_string = PyUnicode_FromString(temp_n_value_string.c_str());
-						aDevice->s_value = Plugins::PyUnicode_FromString(sitem.sValue.c_str());
-						aDevice->last_update_string = PyUnicode_FromString(sitem.lastUpdate.c_str());
-						// _log.Log(LOG_STATUS, "Python EventSystem: deviceName %s added to device dictionary",
-						// sitem.deviceName.c_str());
-					}
-					Py_DECREF(aDevice);
 				}
-				// devicestatesMutexLock1.unlock();
 
 				// Time related
 
@@ -440,7 +426,7 @@ namespace Plugins
 					return;
 				}
 
-				for (auto it_var = m_uservariables.begin(); it_var != m_uservariables.end(); ++it_var)
+				for (auto it_var = userVariables.begin(); it_var != userVariables.end(); ++it_var)
 				{
 					CEventSystem::_tUserVariable uvitem = it_var->second;
 					PyDict_SetItemString(userVariablesDict, uvitem.variableName.c_str(),
