diff --git a/src/core/co_initialize/co_stub.cc b/src/core/co_initialize/co_stub.cc index 64c82d2e..8e567a91 100644 --- a/src/core/co_initialize/co_stub.cc +++ b/src/core/co_initialize/co_stub.cc @@ -7,6 +7,7 @@ #include #include #include +#include const std::string GetBootstrapModule(const std::string scriptModules, const uint16_t port) { @@ -156,13 +157,27 @@ void AddSitePackagesDirectory(std::filesystem::path customPath) /// @brief initializes the current plugin. creates a plugin instance and calls _load() /// @param global_dict -void StartPluginBackend(PyObject* global_dict) +void StartPluginBackend(PyObject* global_dict, std::string pluginName) { + const auto PrintError = [&pluginName]() + { + const auto [errorMessage, traceback] = Python::GetExceptionInformaton(); + PyErr_Clear(); + + if (errorMessage == "name 'plugin' is not defined") + { + Logger.PrintMessage(" FFI-ERROR ", fmt::format("Millennium failed to call _load on {}", pluginName), COL_RED); + return; + } + + Logger.PrintMessage(" FFI-ERROR ", fmt::format("Millennium failed to call _load on {}: {}\n{}{}", pluginName, COL_RED, traceback, COL_RESET), COL_RED); + }; + PyObject *pluginComponent = PyDict_GetItemString(global_dict, "Plugin"); if (!pluginComponent || !PyCallable_Check(pluginComponent)) { - PyErr_Print(); + PrintError(); return; } @@ -170,7 +185,7 @@ void StartPluginBackend(PyObject* global_dict) if (!pluginComponentInstance) { - PyErr_Print(); + PrintError(); return; } @@ -179,7 +194,7 @@ void StartPluginBackend(PyObject* global_dict) if (!loadMethodAttribute || !PyCallable_Check(loadMethodAttribute)) { - PyErr_Print(); + PrintError(); return; } @@ -237,15 +252,33 @@ const void CoInitializer::BackendStartCallback(SettingsStore::PluginTypeSchema p return; } - if (PyRun_SimpleFile(mainModuleFilePtr, backendMainModule.c_str()) != 0) + PyObject* mainModule = PyImport_AddModule("__main__"); + PyObject* mainModuleDict = PyModule_GetDict(mainModule); + + if (!mainModule || !mainModuleDict) { + Logger.Warn("Millennium failed to initialize the main module."); + backendHandler.BackendLoaded({ plugin.pluginName, CoInitializer::BackendCallbacks::BACKEND_LOAD_FAILED }); + fclose(mainModuleFilePtr); + return; + } + + PyObject* result = PyRun_File(mainModuleFilePtr, backendMainModule.c_str(), Py_file_input, mainModuleDict, mainModuleDict); + fclose(mainModuleFilePtr); + + if (!result) { + const auto [errorMessage, traceback] = Python::GetExceptionInformaton(); + + Logger.PrintMessage(" PY-MAN ", fmt::format("Millennium failed to start {}: {}\n{}{}", plugin.pluginName, COL_RED, traceback, COL_RESET), COL_RED); + + PyErr_Print(); // Print the Python error to stderr Logger.Warn("Millennium failed to start '{}'. This is likely due to failing module side effects, unrelated to Millennium.", plugin.pluginName); backendHandler.BackendLoaded({ plugin.pluginName, CoInitializer::BackendCallbacks::BACKEND_LOAD_FAILED }); - return; } - StartPluginBackend(globalDictionary); + Py_DECREF(result); + StartPluginBackend(globalDictionary, plugin.pluginName); } const std::string ConstructScriptElement(std::string filename) diff --git a/src/core/ffi/c_python.cc b/src/core/ffi/c_python.cc index fd2aca98..fc99c8fe 100644 --- a/src/core/ffi/c_python.cc +++ b/src/core/ffi/c_python.cc @@ -1,6 +1,7 @@ #include "ffi.h" #include #include +#include std::string Python::ConstructFunctionCall(nlohmann::basic_json<> data) { @@ -35,6 +36,57 @@ std::string Python::ConstructFunctionCall(nlohmann::basic_json<> data) return strFunctionCall; } +std::tuple Python::GetExceptionInformaton() +{ + PyObject* typeObj = nullptr; + PyObject* valueObj = nullptr; + PyObject* traceBackObj = nullptr; + + PyErr_Fetch(&typeObj, &valueObj, &traceBackObj); + PyErr_NormalizeException(&typeObj, &valueObj, &traceBackObj); + + if (!valueObj) + { + return { "Unknown Error.", "Unknown Error." }; + } + + PyObject* pStrErrorMessage = PyObject_Str(valueObj); + const char* errorMessage = pStrErrorMessage ? PyUnicode_AsUTF8(pStrErrorMessage) : "Unknown Error."; + + std::string tracebackText; + if (traceBackObj) + { + PyObject* tracebackModule = PyImport_ImportModule("traceback"); + if (tracebackModule) + { + PyObject* formatExceptionFunc = PyObject_GetAttrString(tracebackModule, "format_exception"); + if (formatExceptionFunc && PyCallable_Check(formatExceptionFunc)) + { + PyObject* tracebackList = PyObject_CallFunctionObjArgs(formatExceptionFunc, typeObj, valueObj, traceBackObj, NULL); + if (tracebackList) + { + PyObject* tracebackStr = PyUnicode_Join(PyUnicode_FromString(""), tracebackList); + if (tracebackStr) + { + tracebackText = PyUnicode_AsUTF8(tracebackStr); + Py_DECREF(tracebackStr); + } + Py_DECREF(tracebackList); + } + Py_DECREF(formatExceptionFunc); + } + Py_DECREF(tracebackModule); + } + } + + Py_XDECREF(typeObj); + Py_XDECREF(valueObj); + Py_XDECREF(traceBackObj); + Py_XDECREF(pStrErrorMessage); + + return { errorMessage, tracebackText }; +} + const Python::EvalResult EvaluatePython(std::string pluginName, std::string script) { PyObject* globalDictionaryObj = PyModule_GetDict(PyImport_AddModule("__main__")); @@ -42,21 +94,9 @@ const Python::EvalResult EvaluatePython(std::string pluginName, std::string scri if (!EvaluatedObj && PyErr_Occurred()) { - PyObject* typeObj, *valueObj, *traceBackObj; - PyErr_Fetch(&typeObj, &valueObj, &traceBackObj); - PyErr_NormalizeException(&typeObj, &valueObj, &traceBackObj); - - PyObject* pStrErrorMessage = PyObject_Str(valueObj); - if (pStrErrorMessage) - { - const char* errorMessage = PyUnicode_AsUTF8(pStrErrorMessage); - Python::EvalResult exceptionResult = { errorMessage ? errorMessage : "Unknown Error.", Python::Types::Error }; + const auto [errorMessage, traceback] = Python::GetExceptionInformaton(); - Logger.PrintMessage(" FFI-ERR ", fmt::format("An error occurred while calling a backend method from [{}].", pluginName), COL_RED); - Logger.PrintMessage(" TRACE ", fmt::format("{} -> {}", script, exceptionResult.plain), COL_RED); - - return exceptionResult; - } + Logger.PrintMessage(" FFI-ERROR ", fmt::format("Failed to call {}: {}\n{}{}", script, COL_RED, traceback, COL_RESET), COL_RED); } if (EvaluatedObj == nullptr || EvaluatedObj == Py_None) @@ -124,7 +164,23 @@ void Python::LockGILAndDiscardEvaluate(std::string pluginName, std::string scrip std::shared_ptr pythonGilLock = std::make_shared(); pythonGilLock->HoldAndLockGILOnThread(threadState); { - PyRun_SimpleString(script.c_str()); + PyObject* globalDictionaryObj = PyModule_GetDict(PyImport_AddModule("__main__")); + PyObject* EvaluatedObj = PyRun_String(script.c_str(), Py_eval_input, globalDictionaryObj, globalDictionaryObj); + + if (!EvaluatedObj && PyErr_Occurred()) + { + const auto [errorMessage, traceback] = Python::GetExceptionInformaton(); + PyErr_Clear(); + + if (errorMessage == "name 'plugin' is not defined") + { + Logger.PrintMessage(" FFI-ERROR ", fmt::format("Millennium failed to call {} on {} as the function " + "does not exist, or the interpreter crashed before it was loaded.", script, pluginName), COL_RED); + return; + } + + Logger.PrintMessage(" FFI-ERROR ", fmt::format("Millennium failed to call {} on {}: {}\n{}{}", script, pluginName, COL_RED, traceback, COL_RESET), COL_RED); + } } pythonGilLock->ReleaseAndUnLockGIL(); } diff --git a/src/core/ffi/ffi.h b/src/core/ffi/ffi.h index 69d2681c..8b640044 100644 --- a/src/core/ffi/ffi.h +++ b/src/core/ffi/ffi.h @@ -38,6 +38,7 @@ namespace Python { }; std::string ConstructFunctionCall(nlohmann::basic_json<> data); + std::tuple GetExceptionInformaton(); EvalResult LockGILAndEvaluate(std::string pluginName, std::string script); void LockGILAndDiscardEvaluate(std::string pluginName, std::string script);