See More

/* -*- mode: c++; indent-tabs-mode: nil -*- */ /** @file QorePythonProgram.cpp defines the QorePythonProgram class */ /* QorePythonProgram.qpp Qore Programming Language Copyright 2020 - 2026 Qore Technologies, s.r.o. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "QC_PythonProgram.h" #include "QorePythonPrivateData.h" #include "QorePythonClass.h" #include "QoreLoader.h" #include "PythonQoreClass.h" #include "QoreMetaPathFinder.h" #include "PythonCallableCallReferenceNode.h" #include "PythonQoreCallable.h" #include "ModuleNamespace.h" #include "QorePythonStackLocationHelper.h" #include #include #include #include #include static const char* this_file = q_basenameptr(__FILE__); // type used for imported Qore namespaces in Python; a plan type with only a dictionary struct PythonQoreNs { PyObject_HEAD PyObject* dict; }; static strvec_t get_dot_path_list(const std::string str) { strvec_t rv; size_t start = 0; while (true) { size_t pos = str.find('.', start); rv.push_back(str.substr(start, pos - start)); if (pos == std::string::npos) { break; } start = pos + 1; } return rv; } #if defined(DEBUG) && !defined(Py_GIL_DISABLED) // from Python internal code static bool _qore_PyThreadState_IsCurrent(PyThreadState* tstate) { // Must be the tstate for this thread return tstate == _qore_PyRuntimeGILState_GetThreadState(); } #endif // static member declarations QorePythonProgram::py_thr_map_t QorePythonProgram::py_thr_map; QorePythonProgram::py_global_tid_map_t QorePythonProgram::py_global_tid_map; QoreThreadLock QorePythonProgram::py_thr_lck; QoreThreadLock QorePythonProgram::main_ts_lck; unsigned QorePythonProgram::pgm_count = 0; QorePythonProgram::QorePythonProgram() : save_object_callback(nullptr) { printd(5, "QorePythonProgram::QorePythonProgram() this: %p\n", this); qpy_global_register(this); #if PY_VERSION_HEX >= 0x030D0000 // Python 3.13+: PyGILState_Check() and PyGILState_GetThisThreadState() check Python's // Thread Specific Storage (TSS/autoTSSkey). When external modules like JNI are loaded, // they can clear or corrupt Python's TSS without affecting actual GIL ownership. // Use our own tracking exclusively - it's maintained independently and remains reliable. assert(_qore_PyCeval_GetGilLockedStatus()); PyThreadState* python = _qore_PyRuntimeGILState_GetThreadState(); assert(python); interpreter = python->interp; #else assert(PyGILState_Check()); bool have_gil = PyGILState_Check(); PyThreadState* python; if (have_gil) { assert(_qore_PyRuntimeGILState_GetThreadState() == PyGILState_GetThisThreadState()); python = PyGILState_GetThisThreadState(); interpreter = python->interp; } else { python = nullptr; interpreter = _PyGILState_GetInterpreterStateUnsafe(); } #endif owns_interpreter = false; createQoreProgram(); // insert thread state into thread map AutoLocker al(py_thr_lck); assert(py_thr_map.find(this) == py_thr_map.end()); int tid = q_gettid(); py_thr_map[this] = {{tid, {python, false}}}; py_global_tid_map[tid].insert(python); ++pgm_count; needs_deregistration = qpy_register(this); } QorePythonProgram::QorePythonProgram(QoreProgram* qpgm, QoreNamespace* pyns) : qpgm(qpgm), pyns(pyns), save_object_callback(nullptr) { qpy_global_register(this); // Two-phase locking to avoid deadlocks: // Phase 1: Use main_ts_lck to serialize mainThreadState access during GIL acquisition // Phase 2: Use py_thr_lck for thread map updates (after we have our own thread state) // This avoids deadlock because: // - Constructor: main_ts_lck -> GIL -> py_thr_lck (acquires in this order) // - setContext/releaseContext: py_thr_lck (brief) -> GIL (no conflict with main_ts_lck) AutoLocker mts_al(main_ts_lck); QorePythonGilHelper qpgh; ExceptionSink xsink; if (createInterpreter(qpgh, &xsink)) { valid = false; return; } // ensure that the __main__ module is created // returns a borrowed reference module = PyImport_AddModule("__main__"); module.py_ref(); import(&xsink, "builtins"); if (xsink) { valid = false; return; } //assert(!xsink); // returns a borrowed reference setGlobalDictionary(*module); assert(!PyErr_Occurred()); // import qoreloader module QorePythonReferenceHolder qoreloader(PyImport_ImportModule("qoreloader")); if (!qoreloader) { if (!checkPythonException(&xsink)) { xsink.raiseException("PYTHON-COMPILE-ERROR", "cannot load the 'qoreloader' module"); } return; } PyDict_SetItemString(module_dict, "qoreloader", *qoreloader); needs_deregistration = qpy_register(this); } QorePythonProgram::QorePythonProgram(const QoreString& source_code, const QoreString& source_label, int start, ExceptionSink* xsink) : save_object_callback(nullptr) { printd(5, "QorePythonProgram::QorePythonProgram() this: %p\n", this); qpy_global_register(this); TempEncodingHelper src_code(source_code, QCS_UTF8, xsink); if (*xsink) { xsink->appendLastDescription(" (while processing the \"source_code\" argument)"); return; } TempEncodingHelper src_label(source_label, QCS_UTF8, xsink); if (*xsink) { xsink->appendLastDescription(" (while processing the \"source_label\" argument)"); return; } //printd(5, "QorePythonProgram::QorePythonProgram() GIL thread state: %p\n", PyGILState_GetThisThreadState()); // Two-phase locking to avoid deadlocks: // Phase 1: Use main_ts_lck to serialize mainThreadState access during GIL acquisition // Phase 2: Use py_thr_lck for thread map updates (inside createInterpreter, after we have our own thread state) // This avoids deadlock because: // - Constructor: main_ts_lck -> GIL -> py_thr_lck (acquires in this order) // - setContext/releaseContext: py_thr_lck (brief) -> GIL (no conflict with main_ts_lck) AutoLocker mts_al(main_ts_lck); QorePythonGilHelper qpgh; //printd(5, "QorePythonProgram::QorePythonProgram() GIL thread state: %p\n", PyGILState_GetThisThreadState()); if (createInterpreter(qpgh, xsink)) { return; } // import qoreloader module QorePythonReferenceHolder qoreloader(PyImport_ImportModule("qoreloader")); if (!qoreloader) { if (!checkPythonException(xsink)) { xsink->raiseException("PYTHON-COMPILE-ERROR", "cannot load the 'qoreloader' module"); } return; } //printd(5, "QorePythonProgram::QorePythonProgram() loaded qoreloader: %p\n", *qoreloader); // parse and compile code python_code = (PyObject*)Py_CompileString(src_code->c_str(), src_label->c_str(), start); if (!python_code) { if (!checkPythonException(xsink)) { xsink->raiseException("PYTHON-COMPILE-ERROR", "parsing and compilation failed"); } return; } assert(!module); // create module for code QorePythonReferenceHolder new_module(PyImport_ExecCodeModule(src_label->c_str(), *python_code)); if (!new_module) { if (!checkPythonException(xsink)) { xsink->raiseException("PYTHON-COMPILE-ERROR", "compile failed"); } return; } module = new_module.release(); // returns a borrowed reference setGlobalDictionary(*module); PyDict_SetItemString(module_dict, "qoreloader", *qoreloader); // use the parent Program object as the source for importing qpgm = getProgram(); owns_qore_program_ref = false; pyns = qpgm->findNamespace(QORE_PYTHON_NS_NAME); assert(pyns); //printd(5, "QorePythonProgram::QorePythonProgram() this: %p pgm: %p rootns: %p\n", this, qpgm, // qpgm->getRootNS()); // create Qore program object with the same restrictions as the parent //createQoreProgram(); needs_deregistration = qpy_register(this); } int QorePythonProgram::setGlobalDictionary(PyObject* mod) { module_dict = PyModule_GetDict(mod); assert(module_dict); // returns a borrowed reference builtin_dict = PyDict_GetItemString(module_dict, "__builtins__"); assert(builtin_dict); return 0; } void QorePythonProgram::createQoreProgram() { // create Qore program object with the same restrictions as the parent QoreProgram* pgm = getProgram(); QoreParseOptions parse_options = pgm ? pgm->getParseOptions() : QoreParseOptions(); qpgm = new QoreProgram(parse_options); owns_qore_program_ref = true; pyns = PNS->copy(); qpgm->getRootNS()->addNamespace(pyns); qpgm->setExternalData(QORE_PYTHON_MODULE_NAME, this); // Inherit sandbox manager from parent program if (pgm) { QoreSandboxManagerHelper smh(pgm); if (smh) { qpgm->setSandboxManager(smh.get()); } } //printd(5, "QorePythonProgram::createQoreProgram() this: %p pgm: %p rootns: %p\n", this, qpgm, // qpgm->getRootNS()); } QorePythonProgram* QorePythonProgram::getExecutionContext() { QorePythonProgram* pypgm = reinterpret_cast(q_get_thread_local_data(python_u_tld_key)); if (pypgm && pypgm->qpgm) { return pypgm; } //printd(5, "QorePythonProgram::getExecutionContext() current pypgm context %p has no Qore Program context\n", // pypgm); return getContext(); } QorePythonProgram* QorePythonProgram::getContext() { QorePythonProgram* pypgm; // first try to get the actual Program context QoreProgram* pgm = getProgram(); if (pgm) { pypgm = static_cast(pgm->getExternalData(QORE_PYTHON_MODULE_NAME)); if (pypgm) { //printd(5, "QorePythonProgram::getContext() got local program context pgm: %p pypgm: %p\n", pgm, pypgm); return pypgm; } } pgm = qore_get_call_program_context(); if (pgm) { pypgm = static_cast(pgm->getExternalData(QORE_PYTHON_MODULE_NAME)); if (pypgm) { //printd(5, "QorePythonProgram::getContext() got local call context pgm: %p pypgm: %p\n", pgm, pypgm); return pypgm; } } //printd(5, "QorePythonProgram::getContext() got global program context pypgm: %p\n", pypgm); return qore_python_pgm; } int QorePythonProgram::staticInit() { PyDateTime_IMPORT; assert(PyDateTimeAPI); return 0; } void QorePythonProgram::waitForThreadsIntern() { while (pgm_thr_cnt) { ++pgm_thr_waiting; pgm_thr_cond.wait(py_thr_lck); --pgm_thr_waiting; } } void QorePythonProgram::deleteIntern(ExceptionSink* xsink) { if (destroyed) { return; } destroyed = true; // Deregister from global validity tracking FIRST, before anything else qpy_global_deregister(this); if (needs_deregistration) { qpy_deregister(this); } printd(5, "QorePythonProgram::deleteIntern() this: %p i: %p oi: %d\n", this, interpreter, owns_interpreter); if (q_libqore_exiting()) { #ifdef DEBUG qpgm = nullptr; #endif return; } if (qpgm && owns_qore_program_ref) { // remove the external data before dereferencing qpgm->removeExternalData(QORE_PYTHON_MODULE_NAME); qpgm->waitForTerminationAndDeref(xsink); } qpgm = nullptr; // remove all thread states; the objects will be deleted by Python when the interpreter is destroyed { AutoLocker al(py_thr_lck); if (interpreter) { // wait for threads to complete before deleting entries waitForThreadsIntern(); py_thr_map_t::iterator i = py_thr_map.find(this); assert(i != py_thr_map.end()); //printd(5, "QorePythonProgram::deleteIntern() this: %p removing all thread states for pgm (delta: %d)\n", // this, (int)i->second.size()); for (auto& ti : i->second) { //printd(5, "QorePythonProgram::deleteIntern() this: %p removing TID %d\n", this, ti.first); py_global_tid_map_t::iterator gi = py_global_tid_map.find(ti.first); assert(gi != py_global_tid_map.end()); py_thr_set_t::iterator thr_i = gi->second.find(ti.second.state); if (thr_i != gi->second.end()) { gi->second.erase(thr_i); } } py_thr_map.erase(i); assert(pgm_count > 0); --pgm_count; } } if ((interpreter && owns_interpreter)) { #ifdef Py_GIL_DISABLED // In free-threading mode, we need a thread state for this interpreter during cleanup // Create one before any cleanup operations PyThreadState* cleanup_tstate = PyThreadState_New(interpreter); if (cleanup_tstate) { PyThreadState_Swap(cleanup_tstate); } // Clean up Python objects with proper thread state active for (auto& i : obj_sink) { Py_DECREF(i); } obj_sink.clear(); for (auto& i : meth_vec) { delete i; } meth_vec.clear(); module.purge(); python_code.purge(); for (auto& i : py_cls_map) { delete i.second; } py_cls_map.clear(); valid = false; // Properly clean up thread state before interpreter deletion // Must detach and delete thread state before clearing/deleting interpreter PyThreadState_Swap(nullptr); PyThreadState_Clear(cleanup_tstate); PyThreadState_Delete(cleanup_tstate); // Use PyInterpreterState_Clear and PyInterpreterState_Delete // Py_EndInterpreter causes issues with weak references PyInterpreterState_Clear(interpreter); PyInterpreterState_Delete(interpreter); // Signal that an interpreter has been destroyed - must be AFTER deletion qpy_interpreter_destroyed(); interpreter = nullptr; owns_interpreter = false; #else { QorePythonHelper qph(this); for (auto& i : obj_sink) { Py_DECREF(i); } // NOTE: Do NOT delete meth_vec items here - Python function objects still reference them // They will be deleted after PyInterpreterState_Clear below module.purge(); python_code.purge(); // CRITICAL: Clear method objects from type dictionaries BEFORE releasing py_type. // Method objects reference PyMethodDef structures that will be freed after cleanup. // If method objects survive (e.g., due to references from the main interpreter), // they'll be traversed during GC and cause use-after-free crashes. for (auto& i : py_cls_map) { i.second->clearMethods(); } // Release Python references from PythonQoreClass objects BEFORE interpreter cleanup. // We must do this while the interpreter is still valid. // The actual deletion of PythonQoreClass objects (and their PyMethodDef structures) // happens AFTER PyInterpreterState_Clear to avoid use-after-free during GC. for (auto& i : py_cls_map) { i.second->release(); } valid = false; } if (interpreter && owns_interpreter) { #if PY_VERSION_HEX >= 0x030D0000 // Python 3.13+ requires that when calling PyInterpreterState_Clear on a sub-interpreter, // the current thread must have a thread state from that interpreter attached. // Create a temporary thread state for the sub-interpreter. { // Acquire the GIL with main interpreter - keep it held through entire cleanup QorePythonGilHelper pgh; // CRITICAL: If Python is shutting down, pgh won't acquire the GIL. // Skip all Python API cleanup in this case to avoid crashes. if (!pgh.isInitialized()) { printd(5, "QorePythonProgram::deleteIntern() skipping cleanup - Python is shutting down\n"); // Clear the interpreter pointer to prevent double-cleanup attempts interpreter = nullptr; return; } PyThreadState* sub_tstate = PyThreadState_New(interpreter); assert(sub_tstate); // Swap to the sub-interpreter's thread state PyThreadState* old_tstate = PyThreadState_Swap(sub_tstate); { // enforce serialization AutoLocker al(py_thr_lck); // CRITICAL: Clear our thread state cache BEFORE PyInterpreterState_Clear/Delete. // PyInterpreterState_Delete() calls zapthreads() which frees all thread states // attached to this interpreter. If we don't clear our cache, we'll have // dangling pointers to freed memory. py_thr_map.erase(this); PyInterpreterState_Clear(interpreter); } // CRITICAL: Before calling PyInterpreterState_Delete, we must clear bound_gilstate // for ALL thread states of this interpreter (including sub_tstate). // PyInterpreterState_Delete calls zapthreads which deletes remaining thread states, // and each delete will call unbind_gilstate_tstate which asserts TSS == tstate. // Since we'll swap TSS to main interpreter before delete, these assertions will fail. // // The _status.bound_gilstate field is in the public cpython/pystate.h header, // so this works without internal Python headers. PyThreadState* tstate = PyInterpreterState_ThreadHead(interpreter); while (tstate) { qore_py_set_bound_gilstate(tstate, 0); tstate = PyThreadState_Next(tstate); } // Now swap back to the main interpreter's thread state PyThreadState_Swap(old_tstate); // Delete sub_tstate - its bound_gilstate is already 0 so no TSS assertion PyThreadState_Delete(sub_tstate); // Now delete the interpreter - all thread states have bound_gilstate=0 PyInterpreterState_Delete(interpreter); interpreter = nullptr; // Clear pointer to prevent dangling reference } // QorePythonGilHelper destructor releases the GIL here #else // grab the GIL with the main thread lock QorePythonGilHelper pgh; // CRITICAL: If Python is shutting down, pgh won't acquire the GIL. // Skip all Python API cleanup in this case to avoid crashes. if (!pgh.isInitialized()) { printd(5, "QorePythonProgram::deleteIntern() skipping cleanup - Python is shutting down\n"); // Clear the interpreter pointer to prevent double-cleanup attempts interpreter = nullptr; return; } { // enforce serialization AutoLocker al(py_thr_lck); // CRITICAL: Clear our thread state cache BEFORE PyInterpreterState_Clear/Delete. // PyInterpreterState_Delete() calls zapthreads() which frees all thread states // attached to this interpreter. If we don't clear our cache, we'll have // dangling pointers to freed memory. py_thr_map.erase(this); // Clear bound_gilstate for all thread states to avoid TSS assertions in 3.12+ PyThreadState* tstate = PyInterpreterState_ThreadHead(interpreter); while (tstate) { qore_py_set_bound_gilstate(tstate, 0); tstate = PyThreadState_Next(tstate); } assert(_qore_PyRuntimeGILState_GetThreadState()); PyInterpreterState_Clear(interpreter); } PyInterpreterState_Delete(interpreter); interpreter = nullptr; // Clear pointer to prevent dangling reference #endif // Signal that an interpreter has been destroyed - must be AFTER deletion qpy_interpreter_destroyed(); // Now it's safe to delete PyMethodDef structures since the interpreter is gone // and Python function objects no longer reference them for (auto& i : meth_vec) { delete i; } meth_vec.clear(); // Now it's safe to delete PythonQoreClass objects since the interpreter is gone // and Python method objects no longer reference their PyMethodDef structures for (auto& i : py_cls_map) { delete i.second; } py_cls_map.clear(); interpreter = nullptr; owns_interpreter = false; } else { // If we don't own the interpreter, still need to clean up for (auto& i : meth_vec) { delete i; } meth_vec.clear(); for (auto& i : py_cls_map) { delete i.second; } py_cls_map.clear(); } #endif } if (save_object_callback) { save_object_callback = nullptr; } //printd(5, "QorePythonProgram::deleteIntern() this: %p\n", this); } QoreValue QorePythonProgram::eval(ExceptionSink* xsink, const QoreString& source_code, const QoreString& source_label, int input, bool encapsulate) { TempEncodingHelper src_code(source_code, QCS_UTF8, xsink); if (*xsink) { xsink->appendLastDescription(" (while processing the \"source_code\" argument)"); return QoreValue(); } TempEncodingHelper src_label(source_label, QCS_UTF8, xsink); if (*xsink) { xsink->appendLastDescription(" (while processing the \"source_label\" argument)"); return QoreValue(); } // ensure atomic access to the Python interpreter (GIL) and manage the Python thread state QorePythonHelper qph(this, xsink); if (*xsink || checkValid(xsink)) { return QoreValue(); } QorePythonReferenceHolder return_value; QorePythonReferenceHolder python_code; { //printd(5, "QorePythonProgram::QorePythonProgram() GIL thread state: %p\n", PyGILState_GetThisThreadState()); // parse and compile code python_code = (PyObject*)Py_CompileString(src_code->c_str(), src_label->c_str(), input); if (!python_code) { if (!checkPythonException(xsink)) { xsink->raiseException("PYTHON-COMPILE-ERROR", "parsing and compilation failed"); } return QoreValue(); } } PyObject* main_dict; if (encapsulate) { // returns a borrowed reference PyObject* main = PyImport_AddModule("__main__"); // returns a borrowed reference main_dict = PyModule_GetDict(main); } else { assert(module); // returns a borrowed reference main_dict = PyModule_GetDict(*module); } return_value = PyEval_EvalCode(*python_code, main_dict, main_dict); // check for Python exceptions if (checkPythonException(xsink)) { return QoreValue(); } return getQoreValue(xsink, return_value); } void QorePythonProgram::pythonThreadCleanup(void*) { int tid = q_gettid(); //printd(5, "QorePythonProgram::pythonThreadCleanup()\n"); // issue #4651: when called when the Qore library and the python module are initialized from Java during static // process destruction, exit handlers may have already destroyed the static objects in this module if (py_thr_lck.trylock()) { return; } // delete all thread states for the tid for (auto& i : py_thr_map) { py_tid_map_t::iterator ti = i.second.find(tid); if (ti != i.second.end()) { //printd(5, "QorePythonProgram::pythonThreadCleanup() deleting state %p for TID %d", ti->second, tid); i.second.erase(ti); } } // delete reverse lookups py_global_tid_map_t::iterator gi = py_global_tid_map.find(tid); if (gi != py_global_tid_map.end()) { //printd(5, "QorePythonProgram::pythonThreadCleanup() deleting global info for TID %d", tid); py_global_tid_map.erase(gi); } //printd(5, "QorePythonProgram::pythonThreadCleanup() done\n"); py_thr_lck.unlock(); } bool QorePythonProgram::haveGil() { if (!_qore_PyCeval_GetGilLockedStatus()) { return false; } PyThreadState* tstate = _qore_PyCeval_GetThreadState(); if (!tstate) { return false; } int tid = q_gettid(); AutoLocker al(py_thr_lck); py_global_tid_map_t::iterator i = py_global_tid_map.find(tid); if (i != py_global_tid_map.end()) { return i->second.find(tstate) != i->second.end(); } return false; } bool QorePythonProgram::haveGilUnlocked(PyThreadState* check_tstate) { if (!_qore_PyCeval_GetGilLockedStatus()) { return false; } PyThreadState* tstate = _qore_PyCeval_GetThreadState(); return tstate == check_tstate; } int QorePythonProgram::createInterpreter(QorePythonGilHelper& qpgh, ExceptionSink* xsink) { #ifndef Py_GIL_DISABLED #if PY_VERSION_HEX >= 0x030D0000 // Python 3.13+: Use our own tracking since Python's TSS can be cleared by external modules // (like JNI) without affecting our actual GIL ownership. PyGILState_Check() is unreliable. assert(_qore_PyCeval_GetGilLockedStatus()); #else assert(PyGILState_Check()); #endif #endif PyThreadState* python; // NOTE: py_thr_lck must be held by caller to serialize interpreter creation // and prevent multiple threads from using mainThreadState concurrently. { #ifdef Py_GIL_DISABLED // In free-threading mode, use Py_NewInterpreterFromConfig with appropriate settings // Share main obmalloc to avoid per-interpreter heap issues // Require multi-phase init extensions for sub-interpreter compatibility PyInterpreterConfig config = { .use_main_obmalloc = 1, // Share main allocator .allow_fork = 0, .allow_exec = 0, .allow_threads = 1, .allow_daemon_threads = 0, .check_multi_interp_extensions = 1, // Require multi-phase init extensions .gil = PyInterpreterConfig_SHARED_GIL, // Shared GIL (no real GIL in free-threading) }; // CRITICAL: Release the GIL state before creating the sub-interpreter. // PyGILState_Ensure() (called in QorePythonGilHelper constructor) initializes thread-local // mimalloc heap data for the main interpreter. If we don't release it, the new sub-interpreter's // thread state will have a NULL mimalloc heap, causing crashes on any memory allocation. // This releases the main interpreter's thread state so that Py_NewInterpreterFromConfig can // properly initialize mimalloc for the new sub-interpreter. qpgh.releaseBeforeSubInterpreter(); PyStatus status = Py_NewInterpreterFromConfig(&python, &config); if (PyStatus_Exception(status)) { if (xsink) { xsink->raiseException("PYTHON-COMPILE-ERROR", "error creating the Python subinterpreter: %s", status.err_msg ? status.err_msg : "unknown error"); } return -1; } #else python = Py_NewInterpreter(); #endif if (!python) { if (xsink) { xsink->raiseException("PYTHON-COMPILE-ERROR", "error creating the Python subinterpreter"); } return -1; } _QORE_GILSTATE_COUNTER_ASSERT_ONE(python); //printd(5, "QorePythonProgram::createInterpreter() created thead state: %p\n", python); // NOTE: we have to reenable PyGILState_Check() here _QORE_PYTHON_REENABLE_GIL_CHECK qpgh.set(python); // Ensure the sub-interpreter has a usable sys.path (stdlib + extension modules). const char* pyhome = getenv("PYTHONHOME"); const char* pypath = getenv("PYTHONPATH"); if ((pyhome && *pyhome) || (pypath && *pypath)) { #ifdef _Q_WINDOWS const char path_sep = ';'; #else const char path_sep = ':'; #endif std::string path; if (pypath) { path += pypath; } if (pyhome && *pyhome) { if (!path.empty()) { path += path_sep; } path += pyhome; path += "/Lib"; path += path_sep; path += pyhome; path += "/Modules"; } PyObject* sys_path = PySys_GetObject("path"); // borrowed if (!sys_path || !PyList_Check(sys_path)) { sys_path = PyList_New(0); if (sys_path) { PySys_SetObject("path", sys_path); Py_DECREF(sys_path); } } if (sys_path && PyList_Check(sys_path)) { size_t start = 0; // Preserve empty entries to keep CWD semantics (ex: leading/trailing separators). while (start <= path.size()) { size_t end = path.find(path_sep, start); if (end == std::string::npos) { end = path.size(); } std::string entry = path.substr(start, end - start); PyObject* py_entry = PyUnicode_DecodeFSDefault(entry.c_str()); if (py_entry) { PyList_Append(sys_path, py_entry); Py_DECREF(py_entry); } start = end + 1; } } } // Ensure PyDateTimeAPI is initialized for this interpreter; the macro only runs if the // global pointer is null, so clear it to avoid stale main-interpreter state. PyDateTimeAPI = nullptr; PyDateTime_IMPORT; if (!PyDateTimeAPI) { PyErr_Clear(); } } interpreter = python->interp; owns_interpreter = true; printd(5, "QorePythonProgram::createInterpreter() interpreter: %p\n", interpreter); if (!PyDateTimeAPI) { PyDateTime_IMPORT; if (!PyDateTimeAPI) { PyErr_Clear(); PyDateTimeAPI = nullptr; } } // save thread state // NOTE: Acquire py_thr_lck here for thread map updates. // The caller holds main_ts_lck (not py_thr_lck) to serialize mainThreadState access. // This allows setContext/releaseContext to use py_thr_lck without conflicting with main_ts_lck. int tid = q_gettid(); { AutoLocker al(py_thr_lck); { py_thr_map_t::iterator ti = py_thr_map.lower_bound(this); if (ti == py_thr_map.end() || ti->first != this) { py_thr_map.insert(ti, {this, {{tid, {python, true}}}}); } else { ti->second[tid] = {python, true}; } } { py_global_tid_map_t::iterator i = py_global_tid_map.lower_bound(tid); if (i == py_global_tid_map.end() || i->first != tid) { py_global_tid_map.insert(i, {tid, {python}}); } else { i->second.insert(python); } //printd(5, "QorePythonProgram::createInterpreter() inserted TID %d -> %p\n", tid, python); } ++pgm_count; } //printd(5, "QorePythonProgram::createInterpreter() this: %p\n", this); return setRecursionLimit(xsink); } int QorePythonProgram::getRecursionLimit() { #if QORE_VERSION_CODE > 10007 //printd(5, "QorePythonProgram::getRecursionLimit() stack remaining: %lld total size: %lld\n", // q_thread_stack_remaining(), q_thread_get_this_stack_size()); // for some reason the recusion depth needs to be calculated smaller with smaller stack sizes // testing results in using an estimated 6K python stack frame size for stacks 1M and smaller // and 10K for larger stacks int64 ss = (int64)q_thread_stack_remaining(); int64 lim; if (ss > (1024 * 1024)) { lim = (int64)q_thread_stack_remaining() / PYTHON_LARGE_STACK_FACTOR; } else { lim = (int64)q_thread_stack_remaining() / PYTHON_SMALL_STACK_FACTOR; } #else // q_thread_stack_remaining() was broken in Qore < 1.0.8 int64 lim = (int64)q_thread_get_stack_size() / PYTHON_SMALL_STACK_FACTOR; #endif return lim; } int QorePythonProgram::setRecursionLimit(ExceptionSink* xsink) { QorePythonReferenceHolder sys(PyImport_ImportModule("sys")); if (!sys) { if (!checkPythonException(xsink)) { xsink->raiseException("PYTHON-ERROR", "cannot load the 'sys' module"); } return -1; } if (!PyObject_HasAttrString(*sys, "setrecursionlimit")) { xsink->raiseException("PYTHON-ERROR", "'sys.setrecursionlimit' not found"); return -1; } QorePythonReferenceHolder func(PyObject_GetAttrString(*sys, "setrecursionlimit")); assert(PyCFunction_Check(*func)); //printd(5, "QorePythonProgram::setRecursionLimit() setrecursionlimit: %s\n", Py_TYPE(*func)->tp_name); // use the q_thread_stack_remaining() API if Qore >= 1.0.8 QorePythonReferenceHolder py_args(PyTuple_New(1)); PyTuple_SET_ITEM(*py_args, 0, PyLong_FromLongLong(getRecursionLimit())); QorePythonReferenceHolder return_value(PyCFunction_Call(*func, *py_args, nullptr)); return checkPythonException(xsink); } // setContext() - Acquire the Python execution context for the current thread // // This function manages thread state and GIL acquisition to allow safe Python API calls. // It handles multiple complex scenarios: // 1. Creating new thread states for threads that don't have one yet // 2. Reusing cached thread states for threads that have called Python before // 3. Cross-interpreter thread state switching when a thread moves between PythonPrograms // 4. Detection and handling of Python-created threads (e.g., threading.Thread callbacks) // 5. Stale TSS (Thread-Specific Storage) cleanup when interpreters are deleted // // Returns QorePythonThreadInfo containing saved state for releaseContext() to restore. // The caller MUST call releaseContext() when done with Python operations. // // Thread safety: Uses py_thr_lck for thread map access, but GIL acquisition happens outside // the lock to avoid deadlocks. See design/python-module.md for detailed documentation. QorePythonThreadInfo QorePythonProgram::setContext(bool check_interrupt) const { // CRITICAL: Check if Python is shutting down before any Python API calls. // After Py_FinalizeEx(), calling PyGILState_Ensure() or PyEval_RestoreThread() will crash. if (python_shutdown || !Py_IsInitialized()) { return {nullptr, nullptr, nullptr, nullptr, PyGILState_UNLOCKED, 0, false}; } if (!valid) { return {nullptr, nullptr, nullptr, nullptr, PyGILState_UNLOCKED, 0, false}; } // Check for interrupt before acquiring any state (only if requested) if (check_interrupt) { if (qore_check_cancel(nullptr, "Python execution")) { return {nullptr, nullptr, nullptr, nullptr, PyGILState_UNLOCKED, 0, false}; } } // Safety check: verify our interpreter is still in the global list of interpreters. // This can happen when another PythonProgram that owned the shared interpreter // has been destroyed, deleting the interpreter out from under us. if (!interpreter) { return {nullptr, nullptr, nullptr, nullptr, PyGILState_UNLOCKED, 0, false}; } // Check if interpreter is still valid by verifying it's in the global list { PyInterpreterState* interp = PyInterpreterState_Head(); bool found = false; while (interp) { if (interp == interpreter) { found = true; break; } interp = PyInterpreterState_Next(interp); } if (!found) { // Interpreter has been deleted - mark ourselves as invalid and return const_cast(this)->valid = false; return {nullptr, nullptr, nullptr, nullptr, PyGILState_UNLOCKED, 0, false}; } } assert(interpreter); PyThreadState* python = getAcquireThreadState(); // CRITICAL: Verify the cached thread state's interpreter matches ours. // The thread state's interp pointer can become stale if: // 1. PyInterpreterState_Clear was called (sets tstate->interp = NULL) // 2. The thread state was somehow detached from the interpreter // If the cached thread state isn't in the interpreter's thread list, it's stale/invalid. if (python) { bool found = false; PyThreadState* ts = PyInterpreterState_ThreadHead(interpreter); while (ts) { if (ts == python) { found = true; break; } ts = PyThreadState_Next(ts); } if (!found) { printd(5, "QorePythonProgram::setContext() cached thread state %p not found in interpreter %p " "thread list - will create new\n", python, interpreter); // Remove this thread's entry from the cache - it's invalid { int tid = q_gettid(); AutoLocker al(py_thr_lck); py_thr_map_t::iterator i = py_thr_map.find(this); if (i != py_thr_map.end()) { i->second.erase(tid); } py_global_tid_map_t::iterator gi = py_global_tid_map.find(tid); if (gi != py_global_tid_map.end()) { gi->second.erase(python); if (gi->second.empty()) { py_global_tid_map.erase(gi); } } // Don't decrement pgm_thr_cnt here - getAcquireThreadState already incremented it } python = nullptr; // Force creation of new thread state below } } // CRITICAL: Also check if TSS points to a stale thread state (from a deleted interpreter). // This can happen when an interpreter is deleted but TSS still points to one of its thread states. // When we create a new thread state with PyThreadState_New(), it will try to bind to TSS, // but if TSS already has a stale thread state with bound_gilstate=1, tstate_activate will fail. #if PY_VERSION_HEX >= 0x030D0000 // CRITICAL: Check for stale TSS from deleted interpreters BEFORE creating new thread states. // This can happen when an interpreter is deleted but TSS still points to one of its thread states. { PyThreadState* tss_tstate = PyGILState_GetThisThreadState(); if (tss_tstate && tss_tstate != python) { // TSS has a thread state different from our cached/expected one // Check if the TSS thread state's interpreter is valid bool tss_interp_valid = false; PyInterpreterState* interp = PyInterpreterState_Head(); while (interp) { if (interp == tss_tstate->interp) { tss_interp_valid = true; break; } interp = PyInterpreterState_Next(interp); } if (!tss_interp_valid || tss_tstate->interp == nullptr) { // TSS points to a thread state with a deleted/invalid interpreter // Clear its bound_gilstate so it won't interfere with new thread state activation qore_py_set_bound_gilstate(tss_tstate, 0); // CRITICAL: Also clear TSS. PyThreadState_New() only sets TSS if it's currently NULL. // If we don't clear TSS, PyThreadState_New() won't update it, and when we activate // our new thread state, TSS won't match. _qore_PyGILState_SetThisThreadState(nullptr); PyThreadState_Swap(nullptr); } } } // Handle cached thread state that might have TSS mismatch if (python) { PyThreadState* tss_for_cached = PyGILState_GetThisThreadState(); // If the cached thread state has bound_gilstate=1 but TSS doesn't match, clear it if (qore_py_get_bound_gilstate(python) && tss_for_cached != python) { qore_py_set_bound_gilstate(python, 0); } } #endif printd(5, "QorePythonProgram::setContext() ENTRY this: %p owns_interpreter: %d python: %p " "_qore_tss_tstate: %p tid: %d\n", this, owns_interpreter, python, _qore_PyCeval_GetThreadState(), q_gettid()); // Flag to track if this is a Python-created thread (threading.Thread calling back into Qore) bool is_python_created_thread = false; // create new thread state if necessary if (!python) { // IMPORTANT - Python-Created Thread Detection (Python 3.12+) // Before creating a new thread state, we must check if this is a Python-created thread // (e.g., a threading.Thread) that already has a thread state and holds the GIL. // // For Python-created threads: // - Python already created a thread state when the thread was spawned // - The thread already has the GIL (from Python's thread startup) // - We should USE that existing thread state, not create a new one // // For Qore-created threads (background threads calling into Python): // - No thread state exists yet // - We need to create one with PyThreadState_New() // - Then acquire the GIL with PyEval_RestoreThread() // // The _qore_check_python_created_thread_gil() function checks Python's TSS to detect // Python-created threads. We MUST call it BEFORE PyThreadState_New() because in Python 3.12, // PyThreadState_New() modifies TSS (see bind_tstate/bind_gilstate_tstate in pystate.c), // which would corrupt our detection. // NOTE: This detection is also needed for Python 3.11 - Python's threading.Thread creates // threads that already have thread states and the GIL. Without this check, we'd create a // duplicate thread state and deadlock trying to acquire an already-held GIL. #if !defined(QORE_PYTHON_INCLUDE_INTERNALS) PyThreadState* python_thread_state = _qore_check_python_created_thread_gil(); if (python_thread_state != nullptr) { // This is a Python-created thread - use the existing thread state // The thread already has the GIL, so we don't need to acquire it python = python_thread_state; is_python_created_thread = true; printd(5, "QorePythonProgram::setContext() detected Python-created thread, using existing " "thread state: %p\n", python); } #endif if (!python) { // This is a Qore-created thread - create a new thread state printd(5, "QorePythonProgram::setContext() creating new thread state for interpreter: %p\n", interpreter); python = PyThreadState_New(interpreter); printd(5, "QorePythonProgram::setContext() this: %p created new thread context: %p (py_thr_map: %p " "size: %d)\n", this, python, &py_thr_map, (int)py_thr_map.size()); assert(python); _QORE_GILSTATE_COUNTER_ASSERT_ONE(python); #if PY_VERSION_HEX >= 0x030D0000 // Python 3.14+: PyThreadState_New() tries to bind the new thread state to TSS. // However, if TSS already had a stale thread state (from a deleted interpreter), // the binding may have failed or TSS may still point to the stale thread state. // In either case, if TSS doesn't match our new thread state and our new thread // state has bound_gilstate=1, the tstate_activate assertion will fail. // Solution: Check if TSS matches, and if not, clear bound_gilstate on our new // thread state so the activation won't expect a TSS match. PyThreadState* tss_after_new = PyGILState_GetThisThreadState(); if (tss_after_new != python && qore_py_get_bound_gilstate(python)) { // TSS doesn't match our new thread state but it has bound_gilstate=1 // Clear it to prevent tstate_activate assertion failure qore_py_set_bound_gilstate(python, 0); } #endif // IMPORTANT - Python 3.12 PyThreadState_New() Behavior Change: // In Python 3.12, PyThreadState_New() automatically binds the newly created thread state // to the current thread via TSS (see Python/pystate.c bind_tstate/bind_gilstate_tstate). // This is different from older Python versions where PyThreadState_New() did not modify TSS. // // After PyThreadState_New() returns: // - PyGILState_GetThisThreadState() returns the new thread state // - PyGILState_Check() returns 1 (true) // Even though we HAVEN'T ACQUIRED THE GIL yet! // // The fix: immediately mark our tracking as initialized (_qore_tss_initialized = true) // but WITHOUT setting _qore_tss_tstate. This tells _qore_check_python_created_thread_gil() // that this is a Qore-managed thread (not Python-created) that doesn't have the GIL yet. // The GIL will be properly acquired later via PyEval_RestoreThread(). #if PY_VERSION_HEX >= 0x030C0000 && PY_VERSION_HEX < 0x030D0000 && !defined(QORE_PYTHON_INCLUDE_INTERNALS) _qore_tss_initialized = true; // Keep _qore_tss_tstate = nullptr to indicate we don't have the GIL printd(5, "QorePythonProgram::setContext() Python 3.12: marked _qore_tss_initialized=true " "after PyThreadState_New() to prevent false Python-created thread detection\n"); #endif } // the thread state will be deleted when the thread terminates or the interpreter is deleted int tid = q_gettid(); AutoLocker al(py_thr_lck); { py_thr_map_t::iterator i = py_thr_map.find(this); if (i == py_thr_map.end()) { py_thr_map[this] = {{tid, {python, owns_interpreter}}}; } else { assert(i->second.find(tid) == i->second.end()); i->second[tid] = {python, owns_interpreter}; } assert(py_thr_map.find(this) != py_thr_map.end()); } { py_global_tid_map_t::iterator i = py_global_tid_map.lower_bound(tid); if (i == py_global_tid_map.end() || i->first != tid) { py_global_tid_map.insert(i, {tid, py_thr_set_t {python,}}); } else { i->second.insert(python); } //printd(5, "QorePythonProgram::setContext() inserted TID %d -> %p\n", tid, python); } //printd(5, "QorePythonProgram::setContext() this: %p\n", this); } printd(5, "QorePythonProgram::setContext() got thread context: %p (GIL: %d hG: %d) refs: %d\n", python, PyGILState_Check(), haveGil(), _QORE_GILSTATE_COUNTER_GET(python)); PyGILState_STATE g_state; // the TSS state needs to be restored in any case PyThreadState* tss_state = PyGILState_GetThisThreadState(); PyThreadState* t_state, * ceval_state; PyThreadState* released_other_interp_tstate = nullptr; // Track if we released another interpreter's thread state #ifdef Py_GIL_DISABLED // In free-threading mode only: set new TSS thread state before thread state operations. // This is needed because free-threading requires thread state setup before attachment. // NOTE: For GIL-enabled Python (3.12, 3.13, 3.14), we set TSS AFTER acquiring the GIL // to avoid confusing _qore_has_gil() which uses our tracking (_qore_tss_tstate). // Setting TSS before the GIL check could cause _qore_has_gil() to incorrectly return true // when we don't actually hold the GIL. if (tss_state != python) { _qore_PyGILState_SetThisThreadState(python); } #endif // are we currently holding the GIL? #ifdef Py_GIL_DISABLED // In free-threading mode, there's no GIL to hold, but we need a valid attached thread state // for Python operations to work ceval_state = nullptr; t_state = PyGILState_GetThisThreadState(); //printd(5, "QorePythonProgram::setContext() free-threading: t_state: %p python: %p\n", t_state, python); if (t_state == nullptr) { // No thread state attached - we need to attach our specific interpreter's thread state // PyThreadState_Swap will call _PyThreadState_Attach internally PyThreadState_Swap(python); t_state = python; g_state = PyGILState_UNLOCKED; // Mark that we need to detach in releaseContext } else if (t_state != python) { // Different thread state is attached - swap to ours // NOTE: In free-threading, we need to detach current and attach new PyThreadState_Swap(python); g_state = PyGILState_UNLOCKED; // Mark that we need to restore in releaseContext } else { // Our thread state is already attached g_state = PyGILState_LOCKED; // Mark as "locked" since we don't need to change it } //printd(5, "QorePythonProgram::setContext() free-threading: after swap, current: %p g_state: %d\n", // PyGILState_GetThisThreadState(), (int)g_state); #else #if PY_VERSION_HEX >= 0x030D0000 // Python 3.13+ changed thread state management - use PyEval_AcquireThread/ReleaseThread // which properly handle TSS and fast TLS synchronization t_state = PyGILState_GetThisThreadState(); // IMPORTANT: If is_python_created_thread is true (set earlier when we detected a Python-created // thread like threading.Thread), we already have the GIL and should not try to acquire it. bool have_gil = is_python_created_thread || _qore_has_gil(t_state); printd(5, "QorePythonProgram::setContext() Python 3.13+ is_python_created_thread: %d have_gil: %d " "t_state: %p tid: %d\n", is_python_created_thread, have_gil, t_state, q_gettid()); if (have_gil) { // Already have the GIL - just swap thread states if needed ceval_state = t_state; if (t_state != python) { PyThreadState_Swap(python); } g_state = PyGILState_LOCKED; } else { // Don't have the GIL - need to acquire it // CRITICAL: Save the python thread state we're acquiring with, so releaseContext can // use the SAME thread state for PyEval_ReleaseThread. Using _qore_PyCeval_GetThreadState() // in releaseContext can return a different thread state after nested context switches. ceval_state = python; // CRITICAL: Check for stale TSS before acquiring the GIL. // If TSS points to a thread state with a deleted interpreter, we need to: // 1. Clear its bound_gilstate to prevent tstate_activate assertion failure // 2. Clear our python's bound_gilstate too - when tstate_activate runs, // if bound_gilstate=0, it will bind python to TSS properly { PyThreadState* tss_before_acquire = PyGILState_GetThisThreadState(); if (tss_before_acquire && tss_before_acquire != python) { bool tss_interp_valid = false; PyInterpreterState* interp = PyInterpreterState_Head(); while (interp) { if (interp == tss_before_acquire->interp) { tss_interp_valid = true; break; } interp = PyInterpreterState_Next(interp); } if (!tss_interp_valid) { // Stale TSS - clear bound_gilstate on our python thread state qore_py_set_bound_gilstate(python, 0); // Update our internal tracking _qore_PyGILState_SetThisThreadState(nullptr); } } } // WORKAROUND: Python 3.14's tstate_activate has strict assertions about TSS state. // When there's a stale TSS from a deleted interpreter, these assertions can fail // even when bound_gilstate=0 (which should make the assertion pass via !bound_gilstate). // The workaround is to first use PyGILState_Ensure to get a valid GIL context, // then release it, which clears any stale TSS state. Then we can safely use // PyEval_RestoreThread with our specific thread state. PyThreadState* tss_check = PyGILState_GetThisThreadState(); if (tss_check && tss_check != python) { // Check if TSS has a stale thread state bool tss_interp_valid = false; PyInterpreterState* interp = PyInterpreterState_Head(); while (interp) { if (interp == tss_check->interp) { tss_interp_valid = true; break; } interp = PyInterpreterState_Next(interp); } if (!tss_interp_valid) { // Stale TSS - clear the stale thread state's bound_gilstate flag to prevent // assertion failures. We CANNOT call PyGILState_Ensure() here because it will // try to use the stale thread state (whose interpreter is freed) and crash. qore_py_set_bound_gilstate(tss_check, 0); // Clear our internal tracking _qore_PyGILState_SetThisThreadState(nullptr); // Clear python's bound_gilstate so tstate_activate will rebind it to TSS qore_py_set_bound_gilstate(python, 0); } } // CRITICAL: In Python 3.14, PyEval_RestoreThread requires no thread state currently attached. // If there's a thread state from a different interpreter still attached, we need to save and // release it first. We'll restore it later in releaseContext. PyThreadState* currently_attached = PyGILState_GetThisThreadState(); if (currently_attached && currently_attached->interp != python->interp) { // Verify the currently attached thread state's interpreter is still valid bool currently_attached_interp_valid = false; PyInterpreterState* interp = PyInterpreterState_Head(); while (interp) { if (interp == currently_attached->interp) { currently_attached_interp_valid = true; break; } interp = PyInterpreterState_Next(interp); } if (currently_attached_interp_valid) { // Verify that the currently attached thread state is actually the current one PyThreadState* fast_current = PyThreadState_GetUnchecked(); if (fast_current != currently_attached) { // Avoid PyThreadState_Swap without the GIL; clear bound_gilstate instead qore_py_set_bound_gilstate(currently_attached, 0); } else { // Save the current thread state to restore later released_other_interp_tstate = PyEval_SaveThread(); // Release GIL and detach } } else { // The interpreter is gone - just clear the thread state qore_py_set_bound_gilstate(currently_attached, 0); } } PyEval_RestoreThread(python); g_state = PyGILState_UNLOCKED; _qore_gil_held = true; // Track that we now hold the GIL } // Update our tracking _qore_PyGILState_SetThisThreadState(python); #elif PY_VERSION_HEX >= 0x030C0000 // Python 3.12 with bundled internals // NOTE: Don't use _qore_has_gil(tss_state) here because tss_state (from Python's TSS) // can be stale in Python 3.12 after PyEval_ReleaseThread(). Instead, check our own // tracking to determine if we have the GIL. // // IMPORTANT: If is_python_created_thread is true (set earlier when we detected a Python-created // thread like threading.Thread), we already have the GIL and should not try to acquire it. bool have_gil = is_python_created_thread || _qore_PyCeval_GetGilLockedStatus(); printd(5, "QorePythonProgram::setContext() Python 3.12 is_python_created_thread: %d have_gil: %d " "_qore_tss_tstate: %p _qore_tss_initialized: %d tid: %d\n", is_python_created_thread, have_gil, _qore_PyCeval_GetThreadState(), _qore_tss_initialized, q_gettid()); printd(5, "QorePythonProgram::setContext() final have_gil: %d\n", have_gil); if (have_gil) { // If bound_gilstate is stale, clear it to avoid tstate_activate assertions. if (qore_py_get_bound_gilstate(python) && PyGILState_GetThisThreadState() != python) { qore_py_set_bound_gilstate(python, 0); } // We already have the GIL - swap thread states ceval_state = _qore_PyCeval_SwapThreadState(python); g_state = PyGILState_LOCKED; // Also update Python's TSS via PyThreadState_Swap // NOTE: Our tracking is updated above, but Python's autoTSSkey is not. // PyThreadState_Swap updates both the ceval thread state and autoTSSkey. if (PyGILState_GetThisThreadState() != python) { PyThreadState_Swap(python); } } else { // We don't have the GIL - acquire it // In Python 3.12, TSS state can be stale; clear it before activating the new // thread state so bind_gilstate_tstate can safely rebind. PyThreadState* tss_before_acquire = PyGILState_GetThisThreadState(); if (tss_before_acquire && tss_before_acquire != python) { // Only clear the old bound_gilstate if the thread state is still valid. bool found = false; PyInterpreterState* interp = PyInterpreterState_Head(); while (interp && !found) { PyThreadState* ts = PyInterpreterState_ThreadHead(interp); while (ts) { if (ts == tss_before_acquire) { found = true; break; } ts = PyThreadState_Next(ts); } interp = PyInterpreterState_Next(interp); } if (found) { qore_py_set_bound_gilstate(tss_before_acquire, 0); } } // Bind our thread state to TSS directly to avoid tstate_activate assertions. _qore_PyGILState_SetTSS(python); PyThreadState* tss_after_set = PyGILState_GetThisThreadState(); if (tss_after_set == python) { qore_py_set_bound_gilstate(python, 1); } else { // If TSS didn't update, clear it to avoid dereferencing stale thread states. if (tss_after_set) { bool tss_valid = false; PyInterpreterState* interp = PyInterpreterState_Head(); while (interp && !tss_valid) { PyThreadState* ts = PyInterpreterState_ThreadHead(interp); while (ts) { if (ts == tss_after_set) { tss_valid = true; break; } ts = PyThreadState_Next(ts); } interp = PyInterpreterState_Next(interp); } if (!tss_valid) { _qore_PyGILState_ClearTSS(); } } qore_py_set_bound_gilstate(python, 0); } ceval_state = nullptr; PyEval_RestoreThread(python); // Set our tracking now that we have the GIL _qore_PyGILState_SetThisThreadState(python); _qore_gil_held = true; // Track that we now hold the GIL g_state = PyGILState_UNLOCKED; } t_state = _qore_PyRuntimeGILState_GetThreadState(); if (t_state != python) { _QORE_PYTHREAD_STATE_SWAP(python); } #else // Python < 3.12 (3.11 and earlier) // In these versions, PyGILState_* functions are mostly reliable, but with sub-interpreters // on the same thread, PyGILState_Check() can return false even when our thread holds the GIL // with a different thread state. Use _qore_PyCeval_GetGilLockedStatus() which checks the // actual thread ID instead of relying on TSS matching. t_state = PyGILState_GetThisThreadState(); // IMPORTANT: If is_python_created_thread is true (set earlier when we detected a Python-created // thread like threading.Thread), we already have the GIL and should not try to acquire it. // Use _qore_PyCeval_GetGilLockedStatus() which checks if tstate_current->thread_id matches // our thread ID, which correctly handles sub-interpreters on the same thread. bool have_gil = is_python_created_thread || _qore_PyCeval_GetGilLockedStatus(); printd(5, "QorePythonProgram::setContext() Python <3.12 is_python_created_thread: %d have_gil: %d " "t_state: %p tid: %d\n", is_python_created_thread, have_gil, t_state, q_gettid()); if (have_gil) { // We already have the GIL - swap thread states if needed ceval_state = t_state; if (t_state != python) { PyThreadState_Swap(python); } g_state = PyGILState_LOCKED; } else { // We don't have the GIL - acquire it ceval_state = nullptr; PyEval_RestoreThread(python); g_state = PyGILState_UNLOCKED; _qore_gil_held = true; // Track that we now hold the GIL } // Update tracking for consistency _qore_PyGILState_SetThisThreadState(python); #endif #endif #ifndef Py_GIL_DISABLED // Now we have the GIL - verify our state is consistent #if PY_VERSION_HEX >= 0x030D0000 // Python 3.13+: PyGILState_Check() and PyGILState_GetThisThreadState() rely on Python's // internal TSS (autoTSSkey) which can become stale or cleared when external modules like // JNI interact with Python. Our thread-local tracking (_qore_tss_tstate) is authoritative. assert(_qore_PyCeval_GetGilLockedStatus()); #else assert(PyGILState_Check()); // TSS state assertions - only reliable in Python < 3.13 assert(PyGILState_GetThisThreadState() == python); #endif assert(haveGilUnlocked(python)); assert(_qore_PyCeval_GetThreadState() == python); assert(_qore_PyRuntimeGILState_GetThreadState() == python); #endif //printd(5, "QorePythonProgram::setContext() old thread context: %p\n", t_state); _QORE_GILSTATE_COUNTER_INC(python); #if defined(Py_GIL_DISABLED) || PY_VERSION_HEX >= 0x030D0000 // In free-threading mode and Python 3.13+, skip recursion limit handling // - Free-threading: requires an attached interpreter which may not be available // - Python 3.13+: uses global limits instead of per-thread, and Py_GetRecursionLimit() // may not be safe to call in all contexts (e.g., during cleanup) int recursion_depth = 1000; // Use a default value #else // calculate new recursion depth int recursion_depth = PyThreadState_GetRecursionLimit(python); // be conservative when calculating the new recursion depth based on remaining stack space int new_recursion_depth = q_thread_stack_remaining() / PYTHON_SMALL_STACK_FACTOR; //printd(5, "QorePythonProgram::setContext() recursion_depth: %d -> %d\n", python->recursion_depth, // new_recursion_depth); PyThreadState_UpdateRecursionLimit(python, new_recursion_depth); #endif return {tss_state, t_state, ceval_state, released_other_interp_tstate, g_state, recursion_depth, true}; } // releaseContext() - Release the Python execution context acquired by setContext() // // This function restores the previous thread state and releases the GIL if it was acquired. // It handles: // 1. Releasing the GIL when g_state == PyGILState_UNLOCKED (we acquired it in setContext) // 2. Restoring thread states when we swapped them for cross-interpreter calls // 3. Restoring our tracking variables (_qore_tss_tstate, _qore_gil_held, etc.) // 4. Decrementing thread reference counts for thread map management // // The oldstate parameter contains the saved state from setContext() that must be restored. // See design/python-module.md for detailed documentation. void QorePythonProgram::releaseContext(const QorePythonThreadInfo& oldstate) const { // CRITICAL: Check if Python is shutting down before any Python API calls. // After Py_FinalizeEx(), calling Python APIs will crash. if (python_shutdown || !Py_IsInitialized()) { return; } if (!oldstate.valid) { return; } // CRITICAL: Get thread state from Python's internal tracking WITHOUT acquiring py_thr_lck. // This avoids a deadlock: if we acquire py_thr_lck while holding the GIL, and another // thread is in a constructor holding py_thr_lck while waiting for the GIL, we deadlock. // Lock ordering: py_thr_lck -> GIL (constructor acquires lock first, then GIL) // Here we release GIL first, then acquire py_thr_lck to decrement thread count. PyThreadState* python = _qore_PyCeval_GetThreadState(); assert(python); #ifndef Py_GIL_DISABLED assert(_qore_PyThreadState_IsCurrent(python)); #endif //printd(5, "QorePythonProgram::releaseContext() rd: %d -> ord: %d\n", PyThreadState_GetRecursionLimit(python), // oldstate.recursion_depth); #if !defined(Py_GIL_DISABLED) && PY_VERSION_HEX < 0x030D0000 // restore recursion depth // Skip for Python 3.13+ which uses global limits PyThreadState_UpdateRecursionLimit(python, oldstate.recursion_depth); #endif // restore the old state #ifndef Py_GIL_DISABLED // In GIL mode, restore the ceval thread state if it was different if (oldstate.ceval_state != python) { // set GIL context _qore_PyCeval_SwapThreadState(oldstate.ceval_state); } #endif _QORE_GILSTATE_COUNTER_DEC(python); //printd(5, "QorePythonProgram::releaseContext() t_state: %p g_state: %d\n", oldstate.t_state, oldstate.g_state); #ifdef Py_GIL_DISABLED // In free-threading mode, restore the previous thread state if we swapped if (oldstate.g_state == PyGILState_UNLOCKED) { // We swapped in setContext, so swap back to the original (or nullptr) PyThreadState_Swap(oldstate.t_state); } #elif PY_VERSION_HEX >= 0x030D0000 // Python 3.13+ - restore original state and release GIL if needed if (oldstate.g_state == PyGILState_UNLOCKED) { // We acquired the GIL with PyEval_RestoreThread(ceval_state) in setContext. // Use the SAME thread state we saved for release to avoid TSS mismatch. PyThreadState* release_tstate = oldstate.ceval_state; assert(release_tstate); // CRITICAL: PyEval_ReleaseThread expects Python's TSS and fast TLS to match the thread state. // Ensure they're set to release_tstate before releasing. PyThreadState* current_tss = PyGILState_GetThisThreadState(); if (current_tss != release_tstate) { PyThreadState_Swap(release_tstate); } // Use PyEval_ReleaseThread which properly clears both TSS and fast TLS PyEval_ReleaseThread(release_tstate); _qore_PyGILState_SetThisThreadState(nullptr); _qore_gil_held = false; // Track that we no longer hold the GIL // If we released another interpreter's thread state in setContext, restore it now if (oldstate.released_other_interp_tstate) { PyEval_RestoreThread(oldstate.released_other_interp_tstate); _qore_PyGILState_SetThisThreadState(oldstate.released_other_interp_tstate); _qore_gil_held = true; } } else { // We already had the GIL - swap back to original thread state if needed // For LOCKED case, ceval_state holds the original thread state we swapped FROM if (oldstate.ceval_state != python && oldstate.ceval_state != nullptr) { PyThreadState_Swap(oldstate.ceval_state); } // Restore tracking to original state _qore_PyGILState_SetThisThreadState(oldstate.t_state); } #else if (oldstate.g_state == PyGILState_UNLOCKED) { // We acquired the GIL in setContext() with PyEval_RestoreThread(python). // During our context, nested setContext calls might have changed the ceval thread state. // Ensure we release the correct thread state by swapping back to python first. // Use _qore_PyCeval_GetThreadState() instead of PyThreadState_Get() - the latter crashes // in Python 3.11 if the GIL is not held properly (e.g., after PyInterpreterState_Delete). PyThreadState* current = _qore_PyCeval_GetThreadState(); if (current != python) { PyThreadState_Swap(python); } _qore_release_thread_state(python); // NOTE we cannot assert !PyGILState_Check() here, as we have released the GIL, and another thread may have // created a new interpreter, which will temporarily disbale the GIL check, which would cause // PyGILState_Check() to return 1 // NOTE: Do NOT restore tss_state here - we've released the GIL and _qore_release_thread_state already // cleared our tracking. Restoring stale tss_state would incorrectly indicate we still have the GIL. } else { if (python != oldstate.t_state) { _QORE_PYTHREAD_STATE_SWAP(oldstate.t_state); } // Restore our tracking to the original ceval_state (which was saved from our tracking). // NOTE: Don't use oldstate.tss_state here - in Python 3.12, Python's TSS can be stale // after PyEval_ReleaseThread(). Use oldstate.ceval_state which came from our tracking. // The tracking was already restored at lines 886-889 above via _qore_PyCeval_SwapThreadState, // so we don't need to do it again here. } #endif // Decrement thread count. // In the nested case (g_state == LOCKED), we still have the GIL but this is safe because: // - Constructors use main_ts_lck (not py_thr_lck) for GIL acquisition // - getAcquireThreadState() in setContext only briefly holds py_thr_lck, releasing before GIL acquisition // So no thread holds py_thr_lck while waiting for the GIL, avoiding deadlocks. { AutoLocker al(py_thr_lck); if (!--pgm_thr_cnt && pgm_thr_waiting) { pgm_thr_cond.signal(); } } } PythonQoreClass* QorePythonProgram::findCreatePythonClass(const QoreClass& cls, const char* mod_name) { printd(5, "QorePythonProgram::findCreatePythonClass() %s.%s\n", mod_name, cls.getName()); py_cls_map_t::iterator i = py_cls_map.lower_bound(&cls); if (i != py_cls_map.end() && i->first == &cls) { printd(5, "QorePythonProgram::findCreatePythonClass() returning existing %s.%s\n", mod_name, cls.getName()); return i->second; } std::unique_ptr py_cls(new PythonQoreClass(this, mod_name, cls, i)); PyTypeObject* t = py_cls->getPythonType(); printd(5, "QorePythonProgram::findCreatePythonClass() returning new %s.%s type: %p (%s)\n", mod_name, cls.getName(), t, t->tp_name); //py_cls_map.insert(i, py_cls_map_t::value_type(&cls, py_cls.get())); return py_cls.release(); } struct func_capsule_t { const QoreExternalFunction& func; QorePythonProgram* py_pgm; DLLLOCAL func_capsule_t(const QoreExternalFunction& func, QorePythonProgram* py_pgm) : func(func), py_pgm(py_pgm) { } }; static void func_capsule_destructor(PyObject* func_capsule) { func_capsule_t* fc = reinterpret_cast(PyCapsule_GetPointer(func_capsule, nullptr)); delete fc; } int QorePythonProgram::importQoreFunctionToPython(PyObject* mod, const QoreExternalFunction& func) { printd(5, "QorePythonProgram::importQoreFunctionToPython() %s()\n", func.getName()); std::unique_ptr fc(new func_capsule_t(func, this)); QorePythonReferenceHolder capsule(PyCapsule_New((void*)fc.release(), nullptr, func_capsule_destructor)); std::unique_ptr funcdef(new PyMethodDef); funcdef->ml_name = func.getName(); funcdef->ml_meth = callQoreFunction; funcdef->ml_flags = METH_VARARGS; funcdef->ml_doc = nullptr; meth_vec.push_back(funcdef.get()); QorePythonReferenceHolder pyfunc(PyCFunction_New(funcdef.release(), *capsule)); assert(pyfunc); if (PyObject_SetAttrString(mod, func.getName(), *pyfunc)) { assert(PyErr_Occurred()); printd(5, "QorePythonProgram::importQoreFunctionToPython() error setting function %s in %p (%s)\n", func.getName(), mod, Py_TYPE(mod)->tp_name); return -1; } printd(5, "QorePythonProgram::importQoreFunctionToPython() ns: %p (%s) %s = %p\n", mod, Py_TYPE(mod)->tp_name, func.getName(), &func); return 0; } int QorePythonProgram::saveQoreObjectFromPython(const QoreValue& rv, ExceptionSink& xsink) { // save object in thread-local data if relevant if (rv.getType() != NT_OBJECT) { return 0; } //printd(5, "QorePythonProgram::saveQoreObjectFromPython() this: %p val: %s soc: %p\n", this, // rv.getFullTypeName(), *save_object_callback); if (save_object_callback) { ReferenceHolder args(new QoreListNode(autoTypeInfo), &xsink); args->push(rv.refSelf(), &xsink); save_object_callback->execValue(*args, &xsink); if (xsink) { raisePythonException(xsink); return -1; } return 0; } return saveQoreObjectFromPythonDefault(rv, xsink); } int QorePythonProgram::saveQoreObjectFromPythonDefault(const QoreValue& rv, ExceptionSink& xsink) { QoreHashNode* data = qpgm->getThreadData(); assert(data); std::string domain_name; // get key name where to save the data if possible QoreValue v = data->getKeyValue("_python_save"); if (v.getType() != NT_STRING) { domain_name = "_python_save"; } else { QoreStringValueHelper str(v); domain_name = str->c_str(); } QoreValue kv = data->getKeyValue(domain_name.c_str()); // ignore operation if domain exists but is not a list if (!kv || kv.getType() == NT_LIST) { QoreListNode* list; ReferenceHolder list_holder(&xsink); if (!kv) { // we need to assign list in data *after* we prepend the object to the list // in order to manage object counts list = new QoreListNode(autoTypeInfo); list_holder = list; } else { list = kv.get(); } // prepend to list to ensure FILO destruction order list->splice(0, 0, rv, &xsink); if (!xsink && list_holder) { data->setKeyValue(domain_name, list_holder.release(), &xsink); } if (xsink) { raisePythonException(xsink); return -1; } //printd(5, "saveQoreObjectFromPythonDefault() domain: '%s' obj: %p %s\n", domain_name, rv.get(), // rv.get()->getClassName()); } else { //printd(5, "saveQoreObjectFromPythonDefault() NOT SAVING domain: '%s' HAS KEY v: %s (kv: %s)\n", domain_name, // rv.getFullTypeName(), kv.getFullTypeName()); } return 0; } void QorePythonProgram::raisePythonException(ExceptionSink& xsink) { assert(xsink); QoreValue err(xsink.getExceptionErr()); QoreValue desc(xsink.getExceptionDesc()); QoreValue arg(xsink.getExceptionArg()); if (err.getType() == NT_STRING) { QoreStringValueHelper err_str(err); printd(5, "QorePythonProgram::raisePythonException() this: %p owns_interpreter: %d err: %s\n", this, owns_interpreter, err_str->c_str()); } else { printd(5, "QorePythonProgram::raisePythonException() this: %p owns_interpreter: %d err: N/A\n", this, owns_interpreter); } // In sub-interpreters (owns_interpreter == true), we cannot use PythonQoreException_Type // because it's a custom type initialized in the main interpreter. Using it in a sub-interpreter // causes crashes in Python 3.12+ when the exception handling machinery tries to access // type-specific data structures that aren't properly set up for the sub-interpreter. // // IMPORTANT: In Python 3.12+, each interpreter has its own builtin types. Using the global // PyExc_RuntimeError (which is the main interpreter's type) in a sub-interpreter can cause // crashes when Python's internal machinery tries to process the exception. // We must get the RuntimeError type from the current interpreter's builtins. if (owns_interpreter) { // Convert values to Python objects for the exception args // This matches the PythonQoreException behavior where args[0] is err, args[1] is desc, etc. // // NOTE: "err", "desc", and "arg" are owned by the exception in "xsink", so the exception must not be // cleared until all of them have been converted; the error string is used in the fallback path below // after the exception has been cleared, so it is copied here ExceptionSink xsink2; QorePythonReferenceHolder py_err(getPythonValue(err, &xsink2)); QorePythonReferenceHolder py_desc(getPythonValue(desc, &xsink2)); std::string err_msg; { QoreStringValueHelper err_str(err); err_msg = err_str->c_str(); } // Create args tuple: (err, desc) or (err, desc, arg) QorePythonReferenceHolder tuple(PyTuple_New(arg ? 3 : 2)); if (py_err) { PyTuple_SET_ITEM(*tuple, 0, py_err.release()); } else { Py_INCREF(Py_None); PyTuple_SET_ITEM(*tuple, 0, Py_None); } if (py_desc) { PyTuple_SET_ITEM(*tuple, 1, py_desc.release()); } else { Py_INCREF(Py_None); PyTuple_SET_ITEM(*tuple, 1, Py_None); } if (arg) { QorePythonReferenceHolder py_arg(getPythonValue(arg, &xsink2)); if (py_arg) { PyTuple_SET_ITEM(*tuple, 2, py_arg.release()); } else { Py_INCREF(Py_None); PyTuple_SET_ITEM(*tuple, 2, Py_None); } } // all values owned by the exception have been converted; "err", "desc", and "arg" are invalid after this xsink.clear(); // Get RuntimeError from the current interpreter's builtins PyObject* builtins = PyEval_GetBuiltins(); if (builtins) { PyObject* runtime_error = PyDict_GetItemString(builtins, "RuntimeError"); if (runtime_error && PyType_Check(runtime_error)) { // Create the exception with args tuple QorePythonReferenceHolder exc(PyObject_Call(runtime_error, *tuple, nullptr)); if (exc) { PyErr_SetObject(runtime_error, *exc); return; } } } // Fallback - use error string as message PyErr_SetString(PyExc_RuntimeError, err_msg.c_str()); return; } ExceptionSink xsink2; QorePythonReferenceHolder tuple(PyTuple_New(arg ? 3 : 2)); QorePythonReferenceHolder ex_arg(getPythonValue(err, &xsink2)); if (ex_arg) { PyTuple_SET_ITEM(*tuple, 0, ex_arg.release()); } else { Py_INCREF(Py_None); PyTuple_SET_ITEM(*tuple, 0, Py_None); } ex_arg = getPythonValue(desc, &xsink2); if (ex_arg) { PyTuple_SET_ITEM(*tuple, 1, ex_arg.release()); } else { Py_INCREF(Py_None); PyTuple_SET_ITEM(*tuple, 1, Py_None); } if (arg) { ex_arg = getPythonValue(arg, &xsink2); if (ex_arg) { PyTuple_SET_ITEM(*tuple, 2, ex_arg.release()); } else { Py_INCREF(Py_None); PyTuple_SET_ITEM(*tuple, 2, Py_None); } } xsink.clear(); ex_arg = PyObject_CallObject((PyObject*)&PythonQoreException_Type, *tuple); //printd(5, "QorePythonProgram::raisePythonException() py_ex: %p %s\n", *ex_arg, Py_TYPE(*ex_arg)->tp_name); PyErr_SetObject((PyObject*)&PythonQoreException_Type, *ex_arg); } // import Qore definitions to Python void QorePythonProgram::importQoreToPython(PyObject* mod, const QoreNamespace& ns, const char* mod_name) { //printd(5, "QorePythonProgram::importQoreToPython() mod: %p (%d) ns: %p '%s' mod name: '%s'\n", mod, // Py_REFCNT(mod), &ns, ns.getName(), mod_name); // import all functions QoreNamespaceFunctionIterator fi(ns); while (fi.next()) { const QoreExternalFunction& func = fi.get(); // do not import deprecated functions if (func.getCodeFlags() & QCF_DEPRECATED) { continue; } if (importQoreFunctionToPython(mod, func)) { return; } } // import all constants QoreNamespaceConstantIterator consti(ns); while (consti.next()) { if (importQoreConstantToPython(mod, consti.get())) { return; } } // import all classes QoreNamespaceClassIterator clsi(ns); while (clsi.next()) { if (importQoreClassToPython(mod, clsi.get(), mod_name)) { return; } } // import all subnamespaces as modules QoreNamespaceNamespaceIterator ni(ns); while (ni.next()) { if (importQoreNamespaceToPython(mod, ni.get())) { return; } } } int QorePythonProgram::importQoreConstantToPython(PyObject* mod, const QoreExternalConstant& constant) { //printd(5, "QorePythonProgram::importQoreConstantToPython() %s\n", constant.getName()); ExceptionSink xsink; ValueHolder qoreval(constant.getReferencedValue(), &xsink); if (!xsink) { QorePythonReferenceHolder val(qore_python_pgm->getPythonValue(*qoreval, &xsink)); if (!xsink) { if (!PyObject_SetAttrString(mod, constant.getName(), *val)) { return 0; } } } raisePythonException(xsink); return -1; } int QorePythonProgram::importQoreClassToPython(PyObject* mod, const QoreClass& cls, const char* mod_name) { PyTypeObject* py_cls = findCreatePythonClass(cls, mod_name)->getPythonType(); //printd(5, "QorePythonProgram::importQoreClassToPython() py_cls: %p ('%s') cn: '%s' mod_name: '%s'\n", py_cls, // py_cls->tp_name, cls.getName(), mod_name); PyObject* clsobj = (PyObject*)py_cls; if (PyObject_SetAttrString(mod, cls.getName(), clsobj)) { return -1; } return 0; } int QorePythonProgram::importQoreNamespaceToPython(PyObject* mod, const QoreNamespace& ns) { //printd(5, "QorePythonProgram::importQoreNamespaceToPython() %s\n", ns.getName()); assert(PyModule_Check(mod)); QoreStringMaker nsname("%s.%s", PyModule_GetName(mod), ns.getName()); // create a submodule QorePythonReferenceHolder new_mod(newModule(nsname.c_str(), &ns)); printd(5, "QorePythonProgram::importQoreNamespaceToPython() (mod) created new module '%s'\n", nsname.c_str()); importQoreToPython(*new_mod, ns, nsname.c_str()); if (PyObject_SetAttrString(mod, ns.getName(), *new_mod)) { return -1; } return 0; } PyObject* QorePythonProgram::newModule(const char* name, const QoreNamespace* ns_pkg) { //QorePythonReferenceHolder new_mod(PyModule_New(name)); QorePythonReferenceHolder new_mod(ModuleNamespace_New(name, ns_pkg)); if (ns_pkg) { std::string nspath = ns_pkg->getPath(); QorePythonReferenceHolder path(PyUnicode_FromStringAndSize(nspath.c_str(), nspath.size())); PyObject_SetAttrString(*new_mod, "__path__", *path); } saveModule(name, *new_mod); return new_mod.release(); } PyObject* QorePythonProgram::newModule(const char* name, const char* path) { QorePythonReferenceHolder new_mod(PyModule_New(name)); QorePythonReferenceHolder py_path(PyUnicode_FromStringAndSize(path, strlen(path))); PyObject_SetAttrString(*new_mod, "__path__", *py_path); saveModule(name, *new_mod); return new_mod.release(); } void QorePythonProgram::importQoreNamespaceToPython(const QoreNamespace& ns, const QoreString& py_mod_path, ExceptionSink* xsink) { //printd(5, "QorePythonProgram::getCreateModule() '%s'\n", path); assert(!py_mod_path.empty()); QorePythonHelper qph(this, xsink); if (*xsink || checkValid(xsink)) { return; } assert(module); // start with the root module module.py_ref(); QorePythonReferenceHolder mod(*module); strvec_t strpath = get_dot_path_list(py_mod_path.c_str()); assert(!strpath.empty()); QoreString nspath; for (const std::string& str : strpath) { bool store = nspath.empty(); if (!store) { nspath.concat('.'); } nspath.concat(str); //printd(5, "QorePythonProgram::importQoreNamespaceToPython() '%s' str: '%s' mod: %p ('%s')\n", // py_mod_path.c_str(), str.c_str(), *mod, Py_TYPE(*mod)->tp_name); if (PyObject_HasAttrString(*mod, str.c_str())) { QorePythonReferenceHolder new_mod(PyObject_GetAttrString(*mod, str.c_str())); if (PyModule_Check(*new_mod) || PyDict_Check(*new_mod)) { mod = new_mod.release(); continue; } // WARNING: any existing attribute will be replaced with the new module } QorePythonReferenceHolder new_mod(newModule(nspath.c_str(), &ns)); //printd(5, "QorePythonProgram::importQoreNamespaceToPython() created new module '%s' (store: %d)\n", // nspath.c_str(), store); assert(new_mod); if (PyObject_SetAttrString(*mod, str.c_str(), *new_mod)) { if (!checkPythonException(xsink)) { xsink->raiseException("IMPORT-NS-ERROR", "could not set element '%s' when creating path '%s'", str.c_str(), py_mod_path.c_str()); } return; } mod = new_mod.release(); } printd(5, "QorePythonProgram::importQoreNamespaceToPython() %s => %p ('%s': %s) mr: %d\n", py_mod_path.c_str(), *mod, Py_TYPE(*mod)->tp_name, ns.getName(), Py_REFCNT(*mod)); assert(mod); importQoreToPython(*mod, ns, strpath.back().c_str()); checkPythonException(xsink); } void QorePythonProgram::aliasDefinition(const QoreString& source_path, const QoreString& target_path) { ExceptionSink xsink; QorePythonHelper qph(this, &xsink); if (xsink || checkValid(&xsink)) { throw QoreXSinkException(xsink); } if (!module) { throw QoreStandardException("PYTHON-ALIAS-ERROR", "source path '%s' cannot be found as there is no code " "context", source_path.c_str()); } // start with the root Dict module.py_ref(); QorePythonReferenceHolder source_obj(*module); strvec_t strpath = get_dot_path_list(source_path.c_str()); assert(!strpath.empty()); for (size_t i = 0, e = strpath.size(); i < e; ++i) { const std::string& str = strpath[i]; if (!PyObject_HasAttrString(*source_obj, str.c_str())) { throw QoreStandardException("PYTHON-ALIAS-ERROR", "source path '%s': element '%s' not found", source_path.c_str(), str.c_str()); } source_obj = PyObject_GetAttrString(*source_obj, str.c_str()); assert(source_obj); } // get / create target module.py_ref(); QorePythonReferenceHolder obj(*module); strpath = get_dot_path_list(target_path.c_str()); assert(!strpath.empty()); QoreString nspath; for (size_t i = 0, e = strpath.size(); i < e; ++i) { const std::string& str = strpath[i]; if (!nspath.empty()) { nspath.concat('.'); } nspath.concat(str); //printd(5, "QorePythonProgram::aliasDefinition() '%s' str: '%s' obj: %p ('%s')\n", target_path.c_str(), // str.c_str(), *obj, Py_TYPE(*obj)->tp_name); QorePythonReferenceHolder new_elem; if (i < (e - 1)) { if (PyObject_HasAttrString(*obj, str.c_str())) { obj = PyObject_GetAttrString(*obj, str.c_str()); continue; } // create a new module, if the parent is a module, or a dictionary QoreStringMaker path("alias:%s", source_path.c_str()); new_elem = newModule(nspath.c_str(), path.c_str()); //printd(5, "QorePythonProgram::aliasDefinition() created module '%s'\n", nspath.c_str()); } else { new_elem = source_obj.release(); //printd(5, "QorePythonProgram::aliasDefinition() got elem %p %s (path %s)\n", *new_elem, // Py_TYPE(*new_elem)->tp_name, nspath.c_str()); // if aliasing a module, insert the new alias in sys.modules if (PyModule_Check(*new_elem)) { saveModule(nspath.c_str(), *new_elem); } } if (PyObject_SetAttrString(*obj, str.c_str(), *new_elem) < 0) { ExceptionSink xsink; if (!checkPythonException(&xsink)) { new QoreStandardException("PYTHON-ALIAS-ERROR", "could not set element '%s' when creating " \ "target path '%s' while aliasing source path '%s'", str.c_str(), target_path.c_str(), source_path.c_str()); } throw QoreXSinkException(xsink); } if (i == (e - 1)) { break; } obj = new_elem.release(); } } void QorePythonProgram::exportClass(ExceptionSink* xsink, QoreString& arg) { QorePythonHelper qph(this, xsink); if (*xsink || checkValid(xsink)) { return; } // start with the root module module.py_ref(); QorePythonReferenceHolder obj(*module); strvec_t strpath = get_dot_path_list(arg.c_str()); assert(!strpath.empty()); for (const std::string& str : strpath) { //printd(5, "QorePythonProgram::exportClass() '%s' str: '%s' mod: %p ('%s')\n", arg.c_str(), str.c_str(), mod, // Py_TYPE(mod)->tp_name); if (!PyObject_HasAttrString(*obj, str.c_str())) { xsink->raiseException("EXPORT-CLASS-ERROR", "could find component '%s' in path '%s'", str.c_str(), arg.c_str()); return; } obj = PyObject_GetAttrString(*obj, str.c_str()); } if (!PyType_Check(*obj)) { xsink->raiseException("EXPORT-CLASS-ERROR", "path '%s' is not a class; got type '%s' instead", arg.c_str(), Py_TYPE(*obj)->tp_name); return; } PyTypeObject* type = reinterpret_cast(*obj); clmap_t::iterator i = clmap.lower_bound(type); if (i != clmap.end() && i->first == type) { xsink->raiseException("EXPORT-CLASS-ERROR", "Qore class for Python path '%s' already exists", arg.c_str()); return; } qore_offset_t ci = arg.rfind('.'); //QoreExternalProgramContextHelper pch(xsink, qpgm); // grab current Program's parse lock before manipulating namespaces CurrentProgramRuntimeExternalParseContextHelper pch; QoreNamespace* ns = qpgm->getRootNS(); if (ci > 0) { QoreString ns_str(arg); ns_str.terminate(ci); ns_str.replaceAll(".", "::"); ns = ns->findCreateNamespacePathAll(ns_str.c_str()); } strset_t nsset; addClassToNamespaceIntern(xsink, ns, (PyTypeObject*)*obj, strpath.back().c_str(), i, nsset); } void QorePythonProgram::addModulePath(ExceptionSink* xsink, QoreString& arg) { q_env_subst(arg); printd(5, "QorePythonProgram::addModulePath() this: %p arg: '%s'\n", this, arg.c_str()); QorePythonHelper qph(this, xsink); if (*xsink || checkValid(xsink)) { return; } QorePythonReferenceHolder sys(PyImport_ImportModule("sys")); if (!sys) { if (!checkPythonException(xsink)) { throw QoreStandardException("PYTHON-ERROR", "cannot load 'sys' module"); } return; } QorePythonReferenceHolder path; if (!PyObject_HasAttrString(*sys, "path")) { path = PyList_New(0); } else { path = PyObject_GetAttrString(*sys, "path"); if (!PyList_Check(*path)) { throw QoreStandardException("PYTHON-ERROR", "'sys.path' is not a list; got type '%s' instead", Py_TYPE(*path)->tp_name); } } QorePythonReferenceHolder item(PyUnicode_FromStringAndSize(arg.c_str(), arg.size())); PyList_Append(*path, *item); } void QorePythonProgram::exportFunction(ExceptionSink* xsink, QoreString& arg) { QorePythonHelper qph(this, xsink); if (*xsink || checkValid(xsink)) { return; } // start with the root module module.py_ref(); QorePythonReferenceHolder obj(*module); strvec_t strpath = get_dot_path_list(arg.c_str()); assert(!strpath.empty()); for (const std::string& str : strpath) { //printd(5, "QorePythonProgram::exportClass() '%s' str: '%s' mod: %p ('%s')\n", arg.c_str(), str.c_str(), mod, // Py_TYPE(mod)->tp_name); if (!PyObject_HasAttrString(*obj, str.c_str())) { xsink->raiseException("EXPORT-FUNCTION-ERROR", "could find component '%s' in path '%s'", str.c_str(), arg.c_str()); return; } obj = PyObject_GetAttrString(*obj, str.c_str()); } q_external_func_t qore_func = nullptr; if (PyFunction_Check(*obj)) { qore_func = (q_external_func_t)QorePythonProgram::execPythonFunction; } else if (PyCFunction_Check(*obj)) { qore_func = (q_external_func_t)QorePythonProgram::execPythonCFunction; } else { xsink->raiseException("EXPORT-FUNCTION-ERROR", "path '%s' is not a function; got type '%s' instead", arg.c_str(), Py_TYPE(*obj)->tp_name); return; } flmap_t::iterator i = flmap.lower_bound(*obj); if (i != flmap.end() && i->first == *obj) { xsink->raiseException("EXPORT-FUNCTION-ERROR", "Qore function for Python path '%s' already exists", arg.c_str()); return; } qore_offset_t ci = arg.rfind('.'); //QoreExternalProgramContextHelper pch(xsink, qpgm); // grab current Program's parse lock before manipulating namespaces CurrentProgramRuntimeExternalParseContextHelper pch; QoreNamespace* ns = qpgm->getRootNS(); if (ci > 0) { QoreString ns_str(arg); ns_str.terminate(ci); ns_str.replaceAll(".", "::"); ns = ns->findCreateNamespacePathAll(ns_str.c_str()); } const char* func_name = strpath.back().c_str(); if (findCreateQoreFunction(*obj, func_name, qore_func)) { xsink->raiseException("EXPORT-FUNCTION-ERROR", "Qore function for Python path '%s' already exists", arg.c_str()); } } QoreListNode* QorePythonProgram::getQoreListFromList(ExceptionSink* xsink, PyObject* val) { pyobj_set_t rset; return getQoreListFromList(xsink, val, rset); } QoreListNode* QorePythonProgram::getQoreListFromList(ExceptionSink* xsink, PyObject* val, pyobj_set_t& rset) { assert(PyList_Check(val)); ReferenceHolder rv(new QoreListNode(autoTypeInfo), xsink); Py_ssize_t len = PyList_Size(val); for (Py_ssize_t i = 0; i < len; ++i) { ValueHolder qval(getQoreValue(xsink, PyList_GetItem(val, i), rset), xsink); if (*xsink) { return nullptr; } rv->push(qval.release(), xsink); assert(!*xsink); } return rv.release(); } QoreListNode* QorePythonProgram::getQoreListFromTuple(ExceptionSink* xsink, PyObject* val, size_t offset, bool for_args) { pyobj_set_t rset; return getQoreListFromTuple(xsink, val, rset, offset); } QoreListNode* QorePythonProgram::getQoreListFromTuple(ExceptionSink* xsink, PyObject* val, pyobj_set_t& rset, size_t offset, bool for_args) { assert(PyTuple_Check(val)); Py_ssize_t len = PyTuple_Size(val); if (for_args && !len) { return nullptr; } ReferenceHolder rv(new QoreListNode(autoTypeInfo), xsink); for (Py_ssize_t i = offset; i < len; ++i) { ValueHolder qval(getQoreValue(xsink, PyTuple_GetItem(val, i), rset), xsink); if (*xsink || checkPythonException(xsink)) { return nullptr; } rv->push(qval.release(), xsink); assert(!*xsink); } return rv.release(); } QoreHashNode* QorePythonProgram::getQoreHashFromDict(ExceptionSink* xsink, PyObject* val, pyobj_set_t& rset) { assert(PyDict_Check(val)); assert(rset.find(val) == rset.end()); rset.insert(val); ReferenceHolder rv(new QoreHashNode(autoTypeInfo), xsink); PyObject* key, * value; Py_ssize_t pos = 0; while (PyDict_Next(val, &pos, &key, &value)) { const char* keystr; QorePythonReferenceHolder tkey; if (Py_TYPE(key) == &PyUnicode_Type) { keystr = PyUnicode_AsUTF8(key); } else { tkey = PyObject_Repr(key); assert(Py_TYPE(*tkey) == &PyUnicode_Type); keystr = PyUnicode_AsUTF8(*tkey); } // skip recursive refs if (PyDict_Check(value) && (rset.find(value) != rset.end())) { continue; } ValueHolder qval(getQoreValue(xsink, value, rset), xsink); if (*xsink) { return nullptr; } rv->setKeyValue(keystr, qval.release(), xsink); } return rv.release(); } BinaryNode* QorePythonProgram::getQoreBinaryFromBytes(PyObject* val) { assert(PyBytes_Check(val)); SimpleRefHolder rv(new BinaryNode); rv->append(PyBytes_AS_STRING(val), PyBytes_GET_SIZE(val)); return rv.release(); } BinaryNode* QorePythonProgram::getQoreBinaryFromByteArray(PyObject* val) { assert(PyByteArray_Check(val)); SimpleRefHolder rv(new BinaryNode); rv->append(PyByteArray_AsString(val), PyByteArray_Size(val)); return rv.release(); } DateTimeNode* QorePythonProgram::getQoreDateTimeFromDelta(PyObject* val) { assert(PyDelta_Check(val)); return DateTimeNode::makeRelative(0, 0, PyDateTime_DELTA_GET_DAYS(val), 0, 0, PyDateTime_DELTA_GET_SECONDS(val), PyDateTime_DELTA_GET_MICROSECONDS(val)); } DateTimeNode* QorePythonProgram::getQoreDateTimeFromDateTime(ExceptionSink* xsink, PyObject* val) { assert(PyDateTime_Check(val)); const AbstractQoreZoneInfo* zone = nullptr; #if PY_VERSION_HEX >= 0x030A0000 // Python 3.10+: Use PyDateTime_DATE_GET_TZINFO() macro to directly access the tzinfo field. // // CRITICAL: In Python 3.13+ with sub-interpreters, PyObject_HasAttrString() can SIGSEGV because: // 1. PyDateTimeAPI is a per-interpreter global pointer initialized by PyDateTime_IMPORT // 2. When JNI or other external modules are loaded, they may initialize their own Python // sub-interpreter context, leaving our PyDateTimeAPI pointing to stale type objects // 3. PyObject_HasAttrString() internally does type lookups using the stale PyDateTimeAPI // 4. This causes segfaults when accessing freed memory // // The PyDateTime_DATE_GET_TZINFO() macro directly accesses the datetime struct's tzinfo // field without using PyDateTimeAPI, making it safe across interpreter boundaries. PyObject* tzinfo = PyDateTime_DATE_GET_TZINFO(val); if (tzinfo && tzinfo != Py_None && PyTZInfo_Check(tzinfo)) { // Get UTC offset using PyObject_CallMethod which is safer than attribute access QorePythonReferenceHolder delta(PyObject_CallMethod(tzinfo, "utcoffset", "O", val)); if (!delta) { // utcoffset() raised an exception - propagate to caller if xsink provided if (xsink) { // Fetch and format the Python exception QorePythonReferenceHolder ex_type, ex_value, traceback; PyErr_Fetch(ex_type.getRef(), ex_value.getRef(), traceback.getRef()); QoreStringNode* desc = new QoreStringNode("tzinfo.utcoffset() failed"); if (ex_value) { QorePythonReferenceHolder str(PyObject_Str(*ex_value)); if (str) { desc->concat(": "); desc->concat(PyUnicode_AsUTF8(*str)); } } xsink->raiseException("PYTHON-DATETIME-ERROR", desc); return nullptr; } // No xsink provided - clear exception and proceed without timezone PyErr_Clear(); } else if (*delta != Py_None && PyDelta_Check(*delta)) { zone = findCreateOffsetZone(PyDateTime_DELTA_GET_SECONDS(*delta)); } } #else // Python 3.7-3.9: Use attribute-based access (PyDateTime_DATE_GET_TZINFO not available) if (PyObject_HasAttrString(val, "tzinfo")) { QorePythonReferenceHolder tzinfo(PyObject_GetAttrString(val, "tzinfo")); if (tzinfo && PyTZInfo_Check(*tzinfo)) { QorePythonReferenceHolder utcoffset_func(PyObject_GetAttrString(*tzinfo, "utcoffset")); if (utcoffset_func && PyCallable_Check(*utcoffset_func)) { QorePythonReferenceHolder args(PyTuple_New(1)); Py_INCREF(val); PyTuple_SET_ITEM(*args, 0, val); QorePythonReferenceHolder delta(PyEval_CallObject(*utcoffset_func, *args)); if (!delta) { // utcoffset() raised an exception - propagate to caller if xsink provided if (xsink) { QorePythonReferenceHolder ex_type, ex_value, traceback; PyErr_Fetch(ex_type.getRef(), ex_value.getRef(), traceback.getRef()); QoreStringNode* desc = new QoreStringNode("tzinfo.utcoffset() failed"); if (ex_value) { QorePythonReferenceHolder str(PyObject_Str(*ex_value)); if (str) { desc->concat(": "); desc->concat(PyUnicode_AsUTF8(*str)); } } xsink->raiseException("PYTHON-DATETIME-ERROR", desc); return nullptr; } PyErr_Clear(); } else if (PyDelta_Check(*delta)) { zone = findCreateOffsetZone(PyDateTime_DELTA_GET_SECONDS(*delta)); } } } } #endif return DateTimeNode::makeAbsolute(zone ? zone : currentTZ(), PyDateTime_GET_YEAR(val), PyDateTime_GET_MONTH(val), PyDateTime_GET_DAY(val), PyDateTime_DATE_GET_HOUR(val), PyDateTime_DATE_GET_MINUTE(val), PyDateTime_DATE_GET_SECOND(val), PyDateTime_DATE_GET_MICROSECOND(val)); } DateTimeNode* QorePythonProgram::getQoreDateTimeFromDate(PyObject* val) { assert(PyDate_Check(val)); return DateTimeNode::makeAbsolute(currentTZ(), PyDateTime_GET_YEAR(val), PyDateTime_GET_MONTH(val), PyDateTime_GET_DAY(val), 0, 0, 0, 0); } DateTimeNode* QorePythonProgram::getQoreDateTimeFromTime(PyObject* val) { assert(Py_TYPE(val) == PyDateTimeAPI->TimeType); return DateTimeNode::makeAbsolute(currentTZ(), 0, 0, 0, PyDateTime_TIME_GET_HOUR(val), PyDateTime_TIME_GET_MINUTE(val), PyDateTime_TIME_GET_SECOND(val), PyDateTime_TIME_GET_MICROSECOND(val)); } ResolvedCallReferenceNode* QorePythonProgram::getQoreCallRefFromFunc(ExceptionSink* xsink, PyObject* val) { assert(PyFunction_Check(val)); Py_INCREF(val); weakRef(); return new PythonCallableCallReferenceNode(this, val); } ResolvedCallReferenceNode* QorePythonProgram::getQoreCallRefFromMethod(ExceptionSink* xsink, PyObject* val) { assert(PyMethod_Check(val)); PyMethodObject* m = reinterpret_cast(val); Py_INCREF(m->im_func); Py_INCREF(m->im_self); weakRef(); // NOTE: classmethods will have their "self" arg = the class / type return new PythonCallableCallReferenceNode(this, m->im_func, PyType_Check(m->im_self) ? nullptr : m->im_self); } QoreValue QorePythonProgram::getQoreValue(ExceptionSink* xsink, QorePythonReferenceHolder& val) { return getQoreValue(xsink, *val); } QoreValue QorePythonProgram::getQoreValue(ExceptionSink* xsink, QorePythonReferenceHolder& val, pyobj_set_t& rset) { return getQoreValue(xsink, *val, rset); } QoreValue QorePythonProgram::getQoreValue(ExceptionSink* xsink, PyObject* val) { pyobj_set_t rset; return getQoreValue(xsink, val, rset); } QoreValue QorePythonProgram::getQoreValue(ExceptionSink* xsink, PyObject* val, pyobj_set_t& rset) { if (!val || val == Py_None) { return QoreValue(); } PyTypeObject* type = Py_TYPE(val); if (!type) { if (xsink) { xsink->raiseException("PYTHON-IMPORT-ERROR", "Python object has null type"); } return QoreValue(); } printd(5, "QorePythonBase::getQoreValue() this: %p PyDateTimeAPI: %p val: %p '%s'\n", this, PyDateTimeAPI, val, type->tp_name); if (!PyDateTimeAPI) { PyDateTime_IMPORT; if (!PyDateTimeAPI) { PyErr_Clear(); PyDateTimeAPI = nullptr; } } bool have_datetime_api = (PyDateTimeAPI != nullptr); // if this is already a Qore object, then return it if (PyQoreObject_Check(val)) { PyQoreObject* pyobj = reinterpret_cast(val); return pyobj->qobj ? pyobj->qobj->refSelf() : QoreValue(); } if (type == &PyBool_Type) { return QoreValue(val == Py_True); } if (type == &PyLong_Type) { // Python 3+ implements "long" as an arbitrary-precision number (= Qore "number") QorePythonReferenceHolder longval(PyObject_Repr(val)); assert(Py_TYPE(*longval) == &PyUnicode_Type); const char* longstr = PyUnicode_AsUTF8(*longval); // see if we can convert to a Qore integer bool sign = longstr[0] == '-'; size_t len = strlen(longstr); if (len < 19 || (len == 19 && ((!sign && strcmp(longstr, "9223372036854775807") <= 0) || (sign && strcmp(longstr, "-9223372036854775808") <= 0)))) { return strtoll(longstr, 0, 10); } return new QoreNumberNode(longstr); } if (type == &PyFloat_Type) { return QoreValue(PyFloat_AS_DOUBLE(val)); } if (type == &PyUnicode_Type) { Py_ssize_t size; const char* str = PyUnicode_AsUTF8AndSize(val, &size); return new QoreStringNode(str, size, QCS_UTF8); } if (type == &PyList_Type) { return getQoreListFromList(xsink, val, rset); } if (type == &PyTuple_Type) { return getQoreListFromTuple(xsink, val, rset); } if (type == &PyBytes_Type) { return getQoreBinaryFromBytes(val); } if (type == &PyByteArray_Type) { return getQoreBinaryFromByteArray(val); } if (have_datetime_api && type == PyDateTimeAPI->DateType) { return getQoreDateTimeFromDate(val); } if (have_datetime_api && type == PyDateTimeAPI->TimeType) { return getQoreDateTimeFromTime(val); } if (have_datetime_api && type == PyDateTimeAPI->DateTimeType) { return getQoreDateTimeFromDateTime(xsink, val); } if (have_datetime_api && type == PyDateTimeAPI->DeltaType) { return getQoreDateTimeFromDelta(val); } if (type == &PyDict_Type) { return getQoreHashFromDict(xsink, val, rset); } if (PyFunction_Check(val)) { return getQoreCallRefFromFunc(xsink, val); } if (PyMethod_Check(val)) { return getQoreCallRefFromMethod(xsink, val); } QoreClass* cls = getCreateQorePythonClass(xsink, type); if (!cls) { assert(*xsink); return QoreValue(); } Py_INCREF(val); QoreObject* obj = new QoreObject(cls, qpgm, new QorePythonPrivateData(val)); //printd(5, "QorePythonProgram::getQoreValue() obj: %p cls: %p '%s' id: %d\n", obj, cls, cls->getName(), // cls->getID()); return obj; } QoreValue QorePythonProgram::getQoreAttr(PyObject* obj, const char* attr, ExceptionSink* xsink) { QorePythonReferenceHolder return_value(PyObject_GetAttrString(obj, attr)); // check for Python exceptions if (!return_value && checkPythonException(xsink)) { return QoreValue(); } return getQoreValue(xsink, *return_value); } PyObject* QorePythonProgram::getPythonList(ExceptionSink* xsink, const QoreListNode* l) { QorePythonReferenceHolder list(PyList_New(l->size())); ConstListIterator i(l); while (i.next()) { QorePythonReferenceHolder val(getPythonValue(i.getValue(), xsink)); if (*xsink) { raisePythonException(*xsink); return nullptr; } PyList_SetItem(*list, i.index(), val.release()); } return list.release(); } PyObject* QorePythonProgram::getPythonTupleValue(ExceptionSink* xsink, const QoreListNode* l, size_t arg_offset, PyObject* first) { //printd(5, "QorePythonProgram::getPythonTupleValue() l: %d first: %p\n", l ? (int)l->size() : 0, first); bool has_list = (l && l->size() >= arg_offset); if (!first && !has_list) { return PyTuple_New(0); } Py_ssize_t size = has_list ? (l->size() - arg_offset) : 0; if (first) { ++size; } QorePythonReferenceHolder tuple(PyTuple_New(size)); size_t offset = 0; if (first) { Py_INCREF(first); PyTuple_SET_ITEM(*tuple, 0, first); offset = 1; } if (has_list) { ConstListIterator i(l, arg_offset - 1); while (i.next()) { QorePythonReferenceHolder val(getPythonValue(i.getValue(), xsink)); if (*xsink) { raisePythonException(*xsink); return nullptr; } PyTuple_SET_ITEM(*tuple, i.index() - arg_offset + offset, val.release()); } } return tuple.release(); } PyObject* QorePythonProgram::getPythonDict(ExceptionSink* xsink, const QoreHashNode* h) { QorePythonReferenceHolder dict(PyDict_New()); ConstHashIterator i(h); while (i.next()) { // Use getKey() which returns const char* (borrowed reference) instead of // getKeyString() which returns a newly allocated QoreString* that must be freed QorePythonReferenceHolder key(PyUnicode_FromString(i.getKey())); if (!key) { checkPythonException(xsink); return nullptr; } QorePythonReferenceHolder val(getPythonValue(i.get(), xsink)); if (*xsink) { raisePythonException(*xsink); return nullptr; } assert(val); PyDict_SetItem(*dict, *key, *val); } return dict.release(); } PyObject* QorePythonProgram::getPythonString(ExceptionSink* xsink, const QoreString* str) { TempEncodingHelper py_str(str, QCS_UTF8, xsink); if (*xsink) { qore_python_pgm->raisePythonException(*xsink); return nullptr; } return PyUnicode_FromStringAndSize(py_str->c_str(), py_str->size()); } PyObject* QorePythonProgram::getPythonByteArray(ExceptionSink* xsink, const BinaryNode* b) { return PyByteArray_FromStringAndSize(reinterpret_cast(b->getPtr()), b->size()); } PyObject* QorePythonProgram::getPythonDelta(ExceptionSink* xsink, const DateTime* dt) { assert(dt->isRelative()); // WARNING: years are converted to 365 days; months are converted to 30 days if (PyDateTimeAPI) { return PyDelta_FromDSU(dt->getYear() * 365 + dt->getMonth() * 30 + dt->getDay(), dt->getHour() * 3600 + dt->getMinute() * 60 + dt->getSecond(), dt->getMicrosecond()); } // Fallback: create datetime.timedelta via Python when PyDateTimeAPI is unavailable. QorePythonReferenceHolder datetime_mod(PyImport_ImportModule("datetime")); if (!datetime_mod) { if (xsink) { QorePythonProgram* ctx = QorePythonProgram::getContext(); if (!ctx || !ctx->checkPythonException(xsink)) { xsink->raiseException("PYTHON-DATETIME-ERROR", "failed to import Python datetime module"); } } return nullptr; } QorePythonReferenceHolder timedelta_cls(PyObject_GetAttrString(*datetime_mod, "timedelta")); if (!timedelta_cls) { if (xsink) { QorePythonProgram* ctx = QorePythonProgram::getContext(); if (!ctx || !ctx->checkPythonException(xsink)) { xsink->raiseException("PYTHON-DATETIME-ERROR", "failed to resolve datetime.timedelta"); } } return nullptr; } PyObject* args = Py_BuildValue("(iii)", dt->getYear() * 365 + dt->getMonth() * 30 + dt->getDay(), dt->getHour() * 3600 + dt->getMinute() * 60 + dt->getSecond(), dt->getMicrosecond()); if (!args) { if (xsink) { QorePythonProgram* ctx = QorePythonProgram::getContext(); if (!ctx || !ctx->checkPythonException(xsink)) { xsink->raiseException("PYTHON-DATETIME-ERROR", "failed to build timedelta arguments"); } } return nullptr; } PyObject* rv = PyObject_CallObject(*timedelta_cls, args); Py_DECREF(args); if (!rv) { if (xsink) { QorePythonProgram* ctx = QorePythonProgram::getContext(); if (!ctx || !ctx->checkPythonException(xsink)) { xsink->raiseException("PYTHON-DATETIME-ERROR", "failed to create datetime.timedelta"); } } return nullptr; } return rv; } PyObject* QorePythonProgram::getPythonDateTime(ExceptionSink* xsink, const DateTime* dt) { assert(dt->isAbsolute()); if (PyDateTimeAPI) { return PyDateTime_FromDateAndTime(dt->getYear(), dt->getMonth(), dt->getDay(), dt->getHour(), dt->getMinute(), dt->getSecond(), dt->getMicrosecond()); } // Fallback: create datetime.datetime via Python when PyDateTimeAPI is unavailable. QorePythonReferenceHolder datetime_mod(PyImport_ImportModule("datetime")); if (!datetime_mod) { if (xsink) { QorePythonProgram* ctx = QorePythonProgram::getContext(); if (!ctx || !ctx->checkPythonException(xsink)) { xsink->raiseException("PYTHON-DATETIME-ERROR", "failed to import Python datetime module"); } } return nullptr; } QorePythonReferenceHolder datetime_cls(PyObject_GetAttrString(*datetime_mod, "datetime")); if (!datetime_cls) { if (xsink) { QorePythonProgram* ctx = QorePythonProgram::getContext(); if (!ctx || !ctx->checkPythonException(xsink)) { xsink->raiseException("PYTHON-DATETIME-ERROR", "failed to resolve datetime.datetime"); } } return nullptr; } PyObject* args = Py_BuildValue("(iiiiiii)", dt->getYear(), dt->getMonth(), dt->getDay(), dt->getHour(), dt->getMinute(), dt->getSecond(), dt->getMicrosecond()); if (!args) { if (xsink) { QorePythonProgram* ctx = QorePythonProgram::getContext(); if (!ctx || !ctx->checkPythonException(xsink)) { xsink->raiseException("PYTHON-DATETIME-ERROR", "failed to build datetime arguments"); } } return nullptr; } PyObject* rv = PyObject_CallObject(*datetime_cls, args); Py_DECREF(args); if (!rv) { if (xsink) { QorePythonProgram* ctx = QorePythonProgram::getContext(); if (!ctx || !ctx->checkPythonException(xsink)) { xsink->raiseException("PYTHON-DATETIME-ERROR", "failed to create datetime.datetime"); } } return nullptr; } return rv; } PyObject* QorePythonProgram::getPythonCallable(ExceptionSink* xsink, const ResolvedCallReferenceNode* call) { QorePythonImplicitQoreArgHelper qpiqoh((void*)call); return PyObject_CallObject((PyObject*)&PythonQoreCallable_Type, nullptr); } PyObject* QorePythonProgram::getPythonValue(QoreValue val, ExceptionSink* xsink) { //printd(5, "QorePythonProgram::getPythonValue() type '%s'\n", val.getFullTypeName()); switch (val.getType()) { case NT_NOTHING: case NT_NULL: Py_INCREF(Py_None); return Py_None; case NT_BOOLEAN: { PyObject* rv = val.getAsBool() ? Py_True : Py_False; Py_INCREF(rv); return rv; } case NT_INT: return PyLong_FromLongLong(val.getAsBigInt()); case NT_FLOAT: return PyFloat_FromDouble(val.getAsFloat()); case NT_STRING: { QoreStringNodeValueHelper str(val); return getPythonString(xsink, *str); } case NT_LIST: return getPythonList(xsink, val.get()); case NT_HASH: return getPythonDict(xsink, val.get()); case NT_BINARY: return getPythonByteArray(xsink, val.get()); case NT_DATE: { const DateTimeNode* dt = val.get(); return dt->isRelative() ? getPythonDelta(xsink, dt) : getPythonDateTime(xsink, dt); } case NT_RUNTIME_CLOSURE: case NT_FUNCREF: { return getPythonCallable(xsink, val.get()); } case NT_OBJECT: { QoreObject* o = const_cast(val.get()); if (!o->isValid()) { Py_INCREF(Py_None); return Py_None; } TryPrivateDataRefHolder pypd(o, CID_PYTHONBASEOBJECT, xsink); if (pypd) { PyObject* rv = pypd->get(); Py_XINCREF(rv); return rv; } PythonQoreClass* py_cls = findCreatePythonClass(*o->getClass(), "qore"); return py_cls->wrap(o); } } // ignore types that cannot be converted to a Python value and return None Py_INCREF(Py_None); return Py_None; } QoreValue QorePythonProgram::callFunction(ExceptionSink* xsink, const QoreString& func_name, const QoreListNode* args, size_t arg_offset) { assert(!haveGil()); //printd(5, "QorePythonProgram::callFunction() this: %p func: '%s' module: %p module_dict: %p valid: %d\n", // this, func_name.c_str(), *module, module_dict, (int)valid); TempEncodingHelper fname(func_name, QCS_UTF8, xsink); if (*xsink) { xsink->appendLastDescription(" (while processing the \"func_name\" argument)"); return QoreValue(); } // set Qore program context for Qore APIs QoreExternalProgramContextHelper pch(xsink, qpgm); if (*xsink) { return QoreValue(); } ValueHolder rv(xsink); { QorePythonHelper qph(this, xsink); //printd(5, "QorePythonProgram::callFunction() after qph: module: %p module_dict: %p\n", *module, module_dict); if (*xsink || checkValid(xsink)) { return QoreValue(); } //printd(5, "QorePythonProgram::callFunction() calling PyDict_GetItemString(module_dict: %p, fname: '%s')\n", // module_dict, fname->c_str()); // returns a borrowed reference PyObject* py_func = PyDict_GetItemString(module_dict, fname->c_str()); if (!py_func || !PyFunction_Check(py_func)) { xsink->raiseException("NO-FUNCTION", "cannot find function '%s'", fname->c_str()); return QoreValue(); } //printd(5, "QorePythonProgram::callFunction() this: %p %s()\n", this, func_name.c_str()); rv = callInternal(xsink, py_func, args, arg_offset); } assert(!haveGil()); return rv.release(); } QoreValue QorePythonProgram::callMethod(ExceptionSink* xsink, const QoreString& class_name, const QoreString& method_name, const QoreListNode* args, size_t arg_offset) { TempEncodingHelper cname(class_name, QCS_UTF8, xsink); if (*xsink) { xsink->appendLastDescription(" (while processing the \"class_name\" argument)"); return QoreValue(); } TempEncodingHelper mname(method_name, QCS_UTF8, xsink); if (*xsink) { xsink->appendLastDescription(" (while processing the \"method_name\" argument)"); return QoreValue(); } return callMethod(xsink, cname->c_str(), mname->c_str(), args, arg_offset); } QoreValue QorePythonProgram::callMethod(ExceptionSink* xsink, const char* cname, const char* mname, const QoreListNode* args, size_t arg_offset, PyObject* first) { // set Qore program context for Qore APIs QoreExternalProgramContextHelper pch(xsink, qpgm); if (*xsink) { return QoreValue(); } QorePythonHelper qph(this, xsink); if (*xsink || checkValid(xsink)) { return QoreValue(); } assert(module_dict); assert(builtin_dict); // returns a borrowed reference PyObject* py_class = PyDict_GetItemString(module_dict, cname); if (!py_class || !PyType_Check(py_class)) { // returns a borrowed reference py_class = PyDict_GetItemString(builtin_dict, cname); if (!py_class || !PyType_Check(py_class)) { xsink->raiseException("NO-CLASS", "cannot find class '%s'", cname); return QoreValue(); } } // returns a borrowed reference QorePythonReferenceHolder py_method; if (PyObject_HasAttrString(py_class, mname)) { py_method = PyObject_GetAttrString(py_class, mname); } if (!py_method || (!PyFunction_Check(*py_method) && (Py_TYPE(*py_method) != &PyMethodDescr_Type))) { xsink->raiseException("NO-METHOD", "cannot find method '%s.%s()'", cname, mname); return QoreValue(); } //printd(5, "QorePythonProgram::callMethod() %s::%s() args: %d\n", cname, mname, args ? (int)args->size() : 0); return callInternal(xsink, *py_method, args, arg_offset); } QoreValue QorePythonProgram::callInternal(ExceptionSink* xsink, PyObject* callable, const QoreListNode* args, size_t arg_offset, PyObject* first) { // set Qore program context for Qore APIs QoreExternalProgramContextHelper pch(xsink, qpgm); if (*xsink) { return QoreValue(); } QorePythonHelper qph(this, xsink); if (*xsink || checkValid(xsink)) { return QoreValue(); } //printd(5, "QorePythonProgram::callInternal() f: %p args: %d (%d) self: %p\n", callable, // args ? (int)args->size() : 0, (int)arg_offset, first); QorePythonReferenceHolder rv(callPythonInternal(xsink, callable, args, arg_offset, first)); return *xsink ? QoreValue() : getQoreValue(xsink, rv.release()); } PyObject* QorePythonProgram::callPythonInternal(ExceptionSink* xsink, PyObject* callable, const QoreListNode* args, size_t arg_offset, PyObject* first, PyObject* kwargs) { QorePythonReferenceHolder py_args; py_args = getPythonTupleValue(xsink, args, arg_offset, first); if (*xsink) { return nullptr; } //printd(5, "QorePythonProgram::callPythonInternal(): this: %p valid: %d argcount: %d (first: %p)\n", this, valid, // (args && args->size() > arg_offset) ? args->size() - arg_offset : 0, first); QorePythonReferenceHolder return_value(PyEval_CallObjectWithKeywords(callable, *py_args, kwargs)); // check for Python exceptions if (!return_value && checkPythonException(xsink)) { return nullptr; } return return_value.release(); } QoreValue QorePythonProgram::callFunctionObject(ExceptionSink* xsink, PyObject* func, const QoreListNode* args, size_t arg_offset, PyObject* first) { // set Qore program context for Qore APIs QoreExternalProgramContextHelper pch(xsink, qpgm); if (*xsink) { return QoreValue(); } QorePythonHelper qph(this, xsink); if (*xsink || checkValid(xsink)) { return QoreValue(); } QorePythonReferenceHolder py_args(getPythonTupleValue(xsink, args, arg_offset, first)); if (*xsink) { return QoreValue(); } //printd(5, "QorePythonProgram::callFunctionObject(): this: %p valid: %d argcount: %d\n", this, valid, // (args && args->size() > arg_offset) ? args->size() - arg_offset : 0); QorePythonReferenceHolder return_value(PyFunction_Type.tp_call(func, *py_args, nullptr)); // check for Python exceptions if (!return_value && checkPythonException(xsink)) { return QoreValue(); } return getQoreValue(xsink, return_value.release()); } void QorePythonProgram::clearPythonException() { QorePythonReferenceHolder ex_type, ex_value, traceback; PyErr_Fetch(ex_type.getRef(), ex_value.getRef(), traceback.getRef()); } int QorePythonProgram::checkPythonException(ExceptionSink* xsink) { // returns a borrowed reference PyObject* ex = PyErr_Occurred(); if (!ex) { //printd(5, "QorePythonProgram::checkPythonException() no error\n"); return 0; } QorePythonReferenceHolder ex_type, ex_value, traceback; PyErr_Fetch(ex_type.getRef(), ex_value.getRef(), traceback.getRef()); assert(ex_type); // get location QoreExternalProgramLocationWrapper loc; QoreCallStack callstack; printd(5, "QorePythonProgram::checkPythonException() type: %s val: %s (%p) traceback: %s\n", Py_TYPE(*ex_type)->tp_name, ex_value ? Py_TYPE(*ex_value)->tp_name : "(null)", *ex_value, traceback ? Py_TYPE(*traceback)->tp_name : "(null)"); if (!ex_value) { ex_value = Py_None; Py_INCREF(Py_None); } if (!traceback) { traceback = Py_None; Py_INCREF(Py_None); } PyErr_NormalizeException(ex_type.getRef(), ex_value.getRef(), traceback.getRef()); printd(5, "QorePythonProgram::checkPythonException() type: %s val: %s (%p) traceback: %s\n", Py_TYPE(*ex_type)->tp_name, Py_TYPE(*ex_value)->tp_name, *ex_value, Py_TYPE(*traceback)->tp_name); bool use_loc; if (PyTraceBack_Check(*traceback)) { PyTracebackObject* tb = reinterpret_cast(*traceback); int depth = 0; PyTracebackObject* tb1 = tb; while (tb1) { ++depth; tb1 = tb1->tb_next; } while (tb->tb_next && depth > 0) { --depth; tb = tb->tb_next; } PyFrameObject* frame = tb->tb_frame; const char* funcname = nullptr; #if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION > 10 while (frame) { // get line number QorePythonReferenceHolder code((PyObject*)PyFrame_GetCode(frame)); int line = PyCode_Addr2Line((PyCodeObject*)*code, PyFrame_GetLasti(frame)); QorePythonReferenceHolder filename_obj(PyObject_GetAttrString(*code, "co_filename")); const char* filename; Py_INCREF(*filename_obj); QorePythonReferenceHolder np_obj(QorePythonStackLocationHelper::normalizePath(*filename_obj)); if (np_obj) { filename = getCString(*np_obj); } else { filename = PyUnicode_AsUTF8(*filename_obj); } if (frame == tb->tb_frame) { loc.set(filename, line, line, nullptr, 0, QORE_PYTHON_LANG_NAME); } else { callstack.add(CT_USER, filename, line, line, funcname, QORE_PYTHON_LANG_NAME); } QorePythonReferenceHolder funcname_obj(PyObject_GetAttrString(*code, "co_qualname")); funcname = PyUnicode_AsUTF8(*funcname_obj); frame = PyFrame_GetBack(frame); } #else while (frame) { int line = PyCode_Addr2Line(frame->f_code, frame->f_lasti); const char* filename; Py_INCREF(frame->f_code->co_filename); QorePythonReferenceHolder np_obj(QorePythonStackLocationHelper::normalizePath( frame->f_code->co_filename )); if (np_obj) { filename = getCString(*np_obj); } else { filename = getCString(frame->f_code->co_filename); } if (frame == tb->tb_frame) { loc.set(filename, line, line, nullptr, 0, QORE_PYTHON_LANG_NAME); } else { callstack.add(CT_USER, filename, line, line, funcname, QORE_PYTHON_LANG_NAME); } funcname = getCString(frame->f_code->co_name); frame = frame->f_back; } #endif if (funcname) { callstack.add(CT_BUILTIN, this_file, __LINE__, __LINE__, funcname, "c++"); } use_loc = true; } else { use_loc = false; } // check if it's a QoreException (either PythonQoreException or RuntimeError with our tuple format) bool is_qore_exception = false; if (*ex_type == (PyObject*)&PythonQoreException_Type) { is_qore_exception = true; } else if (PyExceptionInstance_Check(*ex_value) && PyErr_GivenExceptionMatches(*ex_type, PyExc_RuntimeError)) { // Check if it's a RuntimeError with our tuple format (err, desc[, arg]) // This is used for exceptions from sub-interpreters. // NOTE: Only check RuntimeError specifically, not all exceptions, because other exceptions // like SyntaxError also have 2 args but in a different format. // // Qore exception format: (err, desc[, arg]) where: // - err: can be int or string (error code like 1 or "ERROR-NAME") // - desc: can be None or string (description) // - arg: optional, any type // // SyntaxError format: ('invalid syntax', ('', 1, 14, ...)) // - Second arg is a tuple (location info), not None or string // // So we validate that the second element is NOT a tuple to distinguish from SyntaxError. QorePythonReferenceHolder args(PyObject_GetAttrString(*ex_value, "args")); if (args && PyTuple_Check(*args)) { Py_ssize_t size = PyTuple_Size(*args); if (size >= 2 && size <= 3) { // Validate: second element should be None or string, not a tuple PyObject* py_desc = PyTuple_GetItem(*args, 1); // borrowed ref if (!PyTuple_Check(py_desc)) { // Looks like our tuple format - treat as a Qore exception is_qore_exception = true; } } } } if (is_qore_exception) { QorePythonReferenceHolder pyval; ValueHolder err(xsink); ValueHolder desc(xsink); ValueHolder arg(xsink); if (*ex_type == (PyObject*)&PythonQoreException_Type) { // PythonQoreException has err, desc, arg attributes assert(PyObject_HasAttrString(*ex_value, "err")); pyval = PyObject_GetAttrString(*ex_value, "err"); err = getQoreValue(xsink, *pyval); if (!*xsink && PyObject_HasAttrString(*ex_value, "desc")) { pyval = PyObject_GetAttrString(*ex_value, "desc"); desc = getQoreValue(xsink, *pyval); } if (!*xsink && PyObject_HasAttrString(*ex_value, "arg")) { pyval = PyObject_GetAttrString(*ex_value, "arg"); arg = getQoreValue(xsink, *pyval); } } else { // RuntimeError with tuple args: (err, desc[, arg]) QorePythonReferenceHolder args(PyObject_GetAttrString(*ex_value, "args")); if (args && PyTuple_Check(*args)) { Py_ssize_t size = PyTuple_Size(*args); if (size >= 1) { PyObject* py_err = PyTuple_GetItem(*args, 0); // borrowed ref err = getQoreValue(xsink, py_err); } if (!*xsink && size >= 2) { PyObject* py_desc = PyTuple_GetItem(*args, 1); // borrowed ref desc = getQoreValue(xsink, py_desc); } if (!*xsink && size >= 3) { PyObject* py_arg = PyTuple_GetItem(*args, 2); // borrowed ref arg = getQoreValue(xsink, py_arg); } } } if (!*xsink) { QoreStringValueHelper errstr(*err); QoreStringNodeValueHelper descstr(*desc); if (use_loc) { xsink->raiseExceptionArg(loc.get(), errstr->c_str(), arg->refSelf(), descstr.getReferencedValue(), callstack); } else { xsink->raiseExceptionArg(errstr->c_str(), arg->refSelf(), descstr.getReferencedValue(), callstack); } return -1; } } if (!*xsink) { // get full exception class name // NOTE: Use ex_type directly instead of Py_TYPE(*ex_value) because after PyErr_NormalizeException, // ex_value might still be the exception class itself (not an instance) in some cases like SyntaxError. // In such cases, Py_TYPE(*ex_value) returns 'type' (the metaclass), not the actual exception class. PyTypeObject* py_cls = PyExceptionInstance_Check(*ex_value) ? Py_TYPE(*ex_value) : reinterpret_cast(*ex_type); QoreString ex_name(py_cls->tp_name); if (PyObject_HasAttrString(reinterpret_cast(py_cls), "__module__")) { QorePythonReferenceHolder ex_mod(PyObject_GetAttrString(reinterpret_cast(py_cls), "__module__")); if (PyUnicode_Check(*ex_mod)) { ex_name.prepend("."); ex_name.prepend(PyUnicode_AsUTF8(*ex_mod)); } } // get description QorePythonReferenceHolder desc(PyObject_Str(*ex_value)); ValueHolder qore_desc(getQoreValue(xsink, *desc), xsink); if (!*xsink) { QoreStringNodeValueHelper descstr(*qore_desc); if (use_loc) { // check if we have a Qore exception xsink->raiseExceptionArg(loc.get(), ex_name.c_str(), QoreValue(), descstr.getReferencedValue(), callstack); } else { // check if we have a Qore exception xsink->raiseExceptionArg(ex_name.c_str(), QoreValue(), descstr.getReferencedValue(), callstack); } return -1; } } xsink->appendLastDescription(" (while trying to convert Python exception arguments to Qore)"); return -1; } QoreValue QorePythonProgram::callPythonMethod(ExceptionSink* xsink, PyObject* attr, PyObject* obj, const QoreListNode* args, size_t arg_offset) { PyTypeObject* mtype = Py_TYPE(attr); // check for static method if (mtype == &PyStaticMethod_Type) { // get callable from static method QorePythonReferenceHolder py_method(PyStaticMethod_Type.tp_descr_get(attr, nullptr, nullptr)); assert(py_method); return callInternal(xsink, *py_method, args, arg_offset); } // check for wrapper descriptors -> normal method if (mtype == &PyWrapperDescr_Type) { return callWrapperDescriptorMethod(xsink, obj, attr, args, arg_offset); } if (mtype == &PyMethodDescr_Type) { return callMethodDescriptorMethod(xsink, obj, attr, args, arg_offset); } if (mtype == &PyClassMethodDescr_Type) { return callClassMethodDescriptorMethod(xsink, obj, attr, args, arg_offset); } if (PyFunction_Check(attr)) { return callFunctionObject(xsink, attr, args, arg_offset, obj); } if (PyCFunction_Check(attr)) { return callCFunctionMethod(xsink, attr, args, arg_offset); } xsink->raiseException("PYTHON-ERROR", "cannot make a call with Python type '%s'", mtype->tp_name); return QoreValue(); } QoreClass* QorePythonProgram::getCreateQorePythonClass(ExceptionSink* xsink, PyTypeObject* type, int flags) { // grab current Program's parse lock before manipulating namespaces CurrentProgramRuntimeExternalParseContextHelper pch; if (!pch) { xsink->raiseException("PROGRAM-ERROR", "cannot process Python type '%s' in deleted Program object", type->tp_name); return nullptr; } strset_t nsset; return getCreateQorePythonClassIntern(xsink, type, nsset, nullptr, flags); } QoreNamespace* QorePythonProgram::getNamespaceForObject(PyObject* obj) { //printd(5, "QorePythonProgram::getNamespaceForObject() obj: %p (%s)\n", obj, Py_TYPE(obj)->tp_name); QoreString ns_path; // use the __name__ attribute to derive the namespace path if possible if (PyObject_HasAttrString(obj, "__name__")) { QorePythonReferenceHolder name(PyObject_GetAttrString(obj, "__name__")); const char* name_str = PyUnicode_AsUTF8(*name); //printd(5, "QorePythonProgram::getNamespaceForObject() obj %p __name__ '%s'\n", obj, name_str); const char* p = strrchr(name_str, '.'); if (p) { ns_path = name_str; ns_path.terminate(p - name_str); ns_path.replaceAll(".", "::"); } } // otherwise use "module_context", if not available, use the __module__ attribute, if available if (!module_context && ns_path.empty() && PyObject_HasAttrString(obj, "__module__")) { QorePythonReferenceHolder mod(PyObject_GetAttrString(obj, "__module__")); if (PyUnicode_Check(*mod)) { const char* mod_str = PyUnicode_AsUTF8(*mod); //printd(5, "QorePythonProgram::getNamespaceForObject() obj %p __module__ '%s'\n", obj, mod_str); ns_path = mod_str; ns_path.replaceAll(".", "::"); } } if (ns_path.empty()) { //printd(5, "QorePythonProgram::getNamespaceForObject() obj %p (%s) -> ns: Python\n", obj, // Py_TYPE(obj)->tp_name); if (!module_context) { return pyns; } ns_path = module_context; ns_path.replaceAll(".", "::"); } //printd(5, "QorePythonProgram::getNamespaceForObject() obj %p (%s) -> ns: Python::%s\n", obj, // Py_TYPE(obj)->tp_name, ns_path.c_str()); QoreNamespace* ns = pyns->findCreateNamespacePathAll(ns_path.c_str()); #if QORE_VERSION_CODE >= 10013 if (module_context) { if (ns->setKeyValueIfNotSet("python_module", module_context)) { printd(5, "set python_module: '%s' in ns: '%s'\n", module_context, ns->getPath(true).c_str()); } } #endif return ns; } QoreClass* QorePythonProgram::getCreateQorePythonClassIntern(ExceptionSink* xsink, PyTypeObject* type, strset_t& nsset, const char* cname, int flags) { //printd(5, "QorePythonProgram::getCreateQorePythonClassIntern() class: '%s'\n", type->tp_name); // see if the Python type already represents a Qore class if (PyQoreObjectType_Check(type)) { //printd(5, "QorePythonProgram::getCreateQorePythonClassIntern() class: '%s' is Qore\n", type->tp_name); return const_cast(PythonQoreClass::getQoreClass(type)); } //printd(5, "QorePythonProgram::getCreateQorePythonClassIntern() creating Qore class for Python class: '%s' \n", // type->tp_name); clmap_t::iterator i = clmap.lower_bound(type); if (i != clmap.end() && i->first == type) { return i->second; } // get relative path to class and class name QoreString rpath_str; if (!cname) { const char* p = strrchr(type->tp_name, '.'); if (p) { rpath_str.set(type->tp_name, p - type->tp_name); cname = p + 1; } else { cname = type->tp_name; } } // create new QorePythonClass QoreNamespace* ns = getNamespaceForObject(reinterpret_cast(type)); if (!rpath_str.empty() && rpath_str.startsWith(ns->getName())) { size_t len = strlen(ns->getName()); if (rpath_str[len] == '.' && rpath_str.size() > len) { rpath_str.replace(0, len + 1, nullptr); rpath_str.replaceAll(".", "::"); ns = ns->findCreateNamespacePathAll(rpath_str.c_str()); } } return addClassToNamespaceIntern(xsink, ns, type, cname, i, nsset, flags); } QorePythonClass* QorePythonProgram::addClassToNamespaceIntern(ExceptionSink* xsink, QoreNamespace* ns, PyTypeObject* type, const char* cname, clmap_t::iterator i, strset_t& nsset, int flags) { // get a unique name for the class QoreString cname_str = cname; // namespace path std::string ns_path = ns->getPath(true); // full path of class with ns std::string full_path; strset_t::iterator nsi; { int base = 0; while (true) { full_path = ns_path + "::" + cname_str.c_str(); nsi = nsset.find(full_path); if (nsi == nsset.end() && !ns->findLocalClass(cname_str.c_str())) { break; } cname_str.clear(); cname_str.sprintf("%s_base_%d", cname, base++); } } cname = cname_str.c_str(); // create new class std::unique_ptr cls(new QorePythonClass(this, cname, full_path.c_str())); // add to nsset nsset.insert(nsi, full_path); // insert into map clmap.insert(i, clmap_t::value_type(type, cls.get())); //printd(5, "QorePythonProgram::addClassToNamespaceIntern() ns: '%s' cls: '%s' (%s id: %d)\n", ns->getName(), // cls->getName(), type->tp_name, cls->getID()); return setupQorePythonClass(xsink, ns, type, cls, nsset, flags); } static constexpr int static_meth_flags = QCF_USES_EXTRA_ARGS; static constexpr int normal_meth_flags = static_meth_flags | QCF_ABSTRACT_OVERRIDE_ALL; QorePythonClass* QorePythonProgram::setupQorePythonClass(ExceptionSink* xsink, QoreNamespace* ns, PyTypeObject* type, std::unique_ptr& cls, strset_t& nsset, int flags) { //printd(5, "QorePythonProgram::setupQorePythonClass() ns: '%s' cls: '%s' (%s) flags: %d\n", ns->getName(), // cls->getName(), type->tp_name, flags); cls->addConstructor((void*)type, (q_external_constructor_t)execPythonConstructor, Public, QCF_USES_EXTRA_ARGS, QDOM_UNCONTROLLED_API); cls->setDestructor((void*)type, (q_external_destructor_t)execPythonDestructor); // create Python mapping for QoreClass if necessary if (!PyQoreObjectType_Check(type)) { new PythonQoreClass(this, type, *cls.get()); } // add single base class if (type->tp_base) { QoreClass* bclass = getCreateQorePythonClassIntern(xsink, type->tp_base, nsset); if (!bclass) { assert(*xsink); return nullptr; } printd(5, "QorePythonProgram::setupQorePythonClass() %s (id: %d) parent: %s (bclass: %p id: %d)\n", type->tp_name, cls->getID(), type->tp_base->tp_name, bclass, bclass->getID()); cls->addBaseClass(bclass, true); } cls->addBuiltinVirtualBaseClass(QC_PYTHONBASEOBJECT); ns->addSystemClass(cls.get()); printd(5, "QorePythonProgram::setupQorePythonClass() %s methods: %p\n", type->tp_name, type->tp_methods); // process dict if (type->tp_dict) { PyObject* key, * value; Py_ssize_t pos = 0; while (PyDict_Next(type->tp_dict, &pos, &key, &value)) { assert(Py_TYPE(key) == &PyUnicode_Type); const char* keystr = PyUnicode_AsUTF8(key); PyTypeObject* var_type = Py_TYPE(value); // check for static method if (var_type == &PyStaticMethod_Type) { // get callable from static method // returns a new reference PyObject* py_method = PyStaticMethod_Type.tp_descr_get(value, nullptr, nullptr); assert(py_method); cls->addObj(py_method); cls->addStaticMethod((void*)py_method, keystr, (q_external_static_method_t)QorePythonProgram::execPythonStaticMethod, Public, static_meth_flags, QDOM_UNCONTROLLED_API, autoTypeInfo); printd(5, "QorePythonProgram::setupQorePythonClass() added static method " \ "%s.%s() (%s)\n", type->tp_name, keystr, Py_TYPE(value)->tp_name); continue; } // check for wrapper descriptors -> normal method if (var_type == &PyWrapperDescr_Type) { Py_INCREF(value); cls->addObj(value); if (!strcmp(keystr, "copy")) { keystr = "_copy"; } cls->addMethod((void*)value, keystr, (q_external_method_t)QorePythonProgram::execPythonNormalWrapperDescriptorMethod, Public, normal_meth_flags, QDOM_UNCONTROLLED_API, autoTypeInfo); printd(5, "QorePythonProgram::setupQorePythonClass() added normal wrapper " \ "descriptor method %s.%s() (%s) %p: %zd\n", type->tp_name, keystr, Py_TYPE(value)->tp_name, value, (Py_ssize_t)Py_REFCNT(value)); continue; } // check for method descriptors -> normal method if (var_type == &PyMethodDescr_Type) { Py_INCREF(value); cls->addObj(value); if (!strcmp(keystr, "copy")) { keystr = "_copy"; } cls->addMethod((void*)value, keystr, (q_external_method_t)QorePythonProgram::execPythonNormalMethodDescriptorMethod, Public, normal_meth_flags, QDOM_UNCONTROLLED_API, autoTypeInfo); printd(5, "QorePythonProgram::setupQorePythonClass() added normal method " \ "descriptor method %s.%s() (%s) %p: %zd\n", type->tp_name, keystr, Py_TYPE(value)->tp_name, value, (Py_ssize_t)Py_REFCNT(value)); continue; } // check for classmethod descriptors -> normal method if (var_type == &PyClassMethodDescr_Type) { // do not need to save reference here if (!strcmp(keystr, "copy")) { keystr = "_copy"; } cls->addMethod((void*)value, keystr, (q_external_method_t)QorePythonProgram::execPythonNormalClassMethodDescriptorMethod, Public, normal_meth_flags, QDOM_UNCONTROLLED_API, autoTypeInfo); printd(5, "QorePythonProgram::setupQorePythonClass() added normal " "classmethod descriptor method %s.%s() (%s)\n", type->tp_name, keystr, Py_TYPE(value)->tp_name); continue; } // check for normal user methods if (PyFunction_Check(value)) { Py_INCREF(value); cls->addObj(value); if (!strcmp(keystr, "copy")) { keystr = "_copy"; } cls->addMethod((void*)value, keystr, (q_external_method_t)QorePythonProgram::execPythonNormalMethod, Public, normal_meth_flags, QDOM_UNCONTROLLED_API, autoTypeInfo); printd(5, "QorePythonProgram::setupQorePythonClass() added normal method " \ "%s.%s() (%s)\n", type->tp_name, keystr, Py_TYPE(value)->tp_name); continue; } // check for builtin functions -> static method if (PyCFunction_Check(value)) { // do not need to save reference here cls->addStaticMethod((void*)value, keystr, (q_external_static_method_t)QorePythonProgram::execPythonStaticCFunctionMethod, Public, static_meth_flags, QDOM_UNCONTROLLED_API, autoTypeInfo); printd(5, "QorePythonProgram::setupQorePythonClass() added static C function method " \ "%s.%s() (%s)\n", type->tp_name, keystr, Py_TYPE(value)->tp_name); continue; } // check for member descriptors if (var_type == &PyMemberDescr_Type) { cls->addPythonMember(keystr, reinterpret_cast(value)->d_member); continue; } printd(5, "QorePythonProgram::setupQorePythonClass() %s: member '%s': %s\n", type->tp_name, keystr, Py_TYPE(value)->tp_name); } } return cls.release(); } QoreValue QorePythonProgram::execPythonStaticCFunctionMethod(const QoreMethod& meth, PyObject* func, const QoreListNode* args, q_rt_flags_t rtflags, ExceptionSink* xsink) { //printd(5, "QorePythonProgram::execPythonStaticCFunctionMethod() m: %s::%s() argcount: %d\n", meth.getClassName(), // meth.getName(), args ? (int)args->size() : 0); QorePythonProgram* pypgm = QorePythonProgram::getPythonProgramFromMethod(meth, xsink); return pypgm->callCFunctionMethod(xsink, func, args); } QoreValue QorePythonProgram::execPythonCFunction(PyObject* func, const QoreListNode* args, q_rt_flags_t rtflags, ExceptionSink* xsink) { //printd(5, "QorePythonProgram::execPythonCFunction() argcount: %d\n", args ? (int)args->size() : 0); QorePythonProgram* pypgm = QorePythonProgram::getContext(); return pypgm->callCFunctionMethod(xsink, func, args); } QoreValue QorePythonProgram::execPythonFunction(PyObject* func, const QoreListNode* args, q_rt_flags_t rtflags, ExceptionSink* xsink) { //printd(5, "QorePythonProgram::execPythonFunction() argcount: %d\n", args ? (int)args->size() : 0); QorePythonProgram* pypgm = QorePythonProgram::getContext(); return pypgm->callFunctionObject(xsink, func, args); } QoreValue QorePythonProgram::callCFunctionMethod(ExceptionSink* xsink, PyObject* func, const QoreListNode* args, size_t arg_offset) { // set Qore program context for Qore APIs QoreExternalProgramContextHelper pch(xsink, qpgm); if (*xsink) { return QoreValue(); } QorePythonHelper qph(this, xsink); QorePythonReferenceHolder py_args; if (*xsink || checkValid(xsink)) { return QoreValue(); } py_args = getPythonTupleValue(xsink, args, arg_offset); if (*xsink) { return QoreValue(); } //printd(5, "QorePythonProgram::callCMethod(): argcount: %d\n", // (args && args->size() > arg_offset) ? args->size() - arg_offset : 0); QorePythonReferenceHolder return_value(PyCFunction_Call(func, *py_args, nullptr)); // check for Python exceptions if (!return_value && checkPythonException(xsink)) { return QoreValue(); } return getQoreValue(xsink, *return_value); } void QorePythonProgram::execPythonConstructor(const QoreMethod& meth, PyObject* pycls, QoreObject* self, const QoreListNode* args, q_rt_flags_t rtflags, ExceptionSink* xsink) { QorePythonProgram* pypgm = QorePythonProgram::getPythonProgramFromMethod(meth, xsink); // set Qore program context for Qore APIs QoreExternalProgramContextHelper pch(xsink, pypgm->qpgm); if (*xsink) { return; } QorePythonHelper qph(pypgm, xsink); if (*xsink || pypgm->checkValid(xsink)) { return; } assert(PyType_Check(pycls)); //printd(5, "QorePythonProgram::execPythonConstructor() m: %s args: %d self: %s\n", meth.getName(), // args ? (int)args->size() : 0, self->getClassName()); // save Qore object for any Python class that needs it QorePythonImplicitQoreArgHelper qpiqoh(self); QorePythonReferenceHolder pyobj(pypgm->callPythonInternal(xsink, pycls, args)); if (*xsink) { return; } // check base class initialization self->setPrivate(meth.getClass()->getID(), new QorePythonPrivateData(pyobj.release())); } void QorePythonProgram::execPythonDestructor(const QorePythonClass& thisclass, PyObject* pycls, QoreObject* self, QorePythonPrivateData* pd, ExceptionSink* xsink) { if (q_libqore_exiting()) { return; } QorePythonProgram* pypgm = thisclass.getPythonProgram(); // Check if the Python program pointer is still valid - it may have been destroyed // during program cleanup, leaving a dangling pointer in the QorePythonClass if (!qpy_is_valid(pypgm)) { return; } // Check the destroyed flag (safe now that we know pypgm is a valid pointer) // The destroyed flag is set at the very start of deleteIntern(), before any cleanup if (pypgm->isDestroyed()) { return; } // Also check the valid flag for good measure if (!pypgm->isValid()) { return; } // Check if any sub-interpreters have been destroyed // When sub-interpreters are destroyed in Python 3.12, the GIL state can become // corrupted, making it unsafe to acquire the GIL for the main interpreter. // Skip the destructor in this case - the Python object will be cleaned up // when the interpreter is eventually destroyed anyway. if (qpy_get_destroyed_count() > 0) { return; } QorePythonHelper qph(pypgm); if (!qph.isValid()) { return; } if (pypgm->isValid()) { pd->deref(xsink); } } QoreValue QorePythonProgram::execPythonStaticMethod(const QoreMethod& meth, PyObject* m, const QoreListNode* args, q_rt_flags_t rtflags, ExceptionSink* xsink) { QorePythonProgram* pypgm = QorePythonProgram::getPythonProgramFromMethod(meth, xsink); return pypgm->callInternal(xsink, m, args); } QoreValue QorePythonProgram::execPythonNormalMethod(const QoreMethod& meth, PyObject* m, QoreObject* self, QorePythonPrivateData* pd, const QoreListNode* args, q_rt_flags_t rtflags, ExceptionSink* xsink) { //printd(5, "QorePythonProgram::execPythonNormalMethod() %s::%s() pyobj: %p: %d\n", meth.getClassName(), // meth.getName(), m, m->ob_refcnt); QorePythonProgram* pypgm = QorePythonProgram::getPythonProgramFromMethod(meth, xsink); return pypgm->callInternal(xsink, m, args, 0, pd->get()); } QoreValue QorePythonProgram::execPythonNormalWrapperDescriptorMethod(const QoreMethod& meth, PyObject* m, QoreObject* self, QorePythonPrivateData* pd, const QoreListNode* args, q_rt_flags_t rtflags, ExceptionSink* xsink) { //printd(5, "QorePythonProgram::execPythonNormalWrapperDescriptorMethod() %s::%s() pyobj: %p: %zd\n", // meth.getClassName(), meth.getName(), m, (Py_ssize_t)Py_REFCNT(m)); assert(Py_REFCNT(m) > 0); QorePythonProgram* pypgm = QorePythonProgram::getPythonProgramFromMethod(meth, xsink); return pypgm->callWrapperDescriptorMethod(xsink, pd->get(), m, args); } QoreValue QorePythonProgram::execPythonNormalMethodDescriptorMethod(const QoreMethod& meth, PyObject* m, QoreObject* self, QorePythonPrivateData* pd, const QoreListNode* args, q_rt_flags_t rtflags, ExceptionSink* xsink) { //printd(5, "QorePythonProgram::execPythonNormalMethodDescriptorMethod() %s::%s() pyobj: %p: %zd\n", // meth.getClassName(), meth.getName(), m, (Py_ssize_t)Py_REFCNT(m)); QorePythonProgram* pypgm = QorePythonProgram::getPythonProgramFromMethod(meth, xsink); return pypgm->callMethodDescriptorMethod(xsink, pd->get(), m, args); } QoreValue QorePythonProgram::execPythonNormalClassMethodDescriptorMethod(const QoreMethod& meth, PyObject* m, QoreObject* self, QorePythonPrivateData* pd, const QoreListNode* args, q_rt_flags_t rtflags, ExceptionSink* xsink) { //printd(5, "QorePythonProgram::execPythonNormalClassMethodDescriptorMethod() %s::%s() pyobj: %p: %d\n", // meth.getClassName(), meth.getName(), m, m->ob_refcnt); QorePythonProgram* pypgm = QorePythonProgram::getPythonProgramFromMethod(meth, xsink); return pypgm->callClassMethodDescriptorMethod(xsink, pd->get(), m, args); } QoreValue QorePythonProgram::callWrapperDescriptorMethod(ExceptionSink* xsink, PyObject* self, PyObject* obj, const QoreListNode* args, size_t arg_offset) { QorePythonHelper qph(this, xsink); if (*xsink || checkValid(xsink)) { return QoreValue(); } QorePythonReferenceHolder py_args; py_args = getPythonTupleValue(xsink, args, arg_offset, self); if (*xsink) { return QoreValue(); } //printd(5, "QorePythonProgram::callWrapperDescriptorMethod() argcount: %d\n", // (args && args->size() > arg_offset) ? args->size() - arg_offset : 0); QorePythonReferenceHolder return_value(PyWrapperDescr_Type.tp_call(obj, *py_args, nullptr)); // check for Python exceptions if (!return_value && checkPythonException(xsink)) { return QoreValue(); } return getQoreValue(xsink, *return_value); } QoreValue QorePythonProgram::callMethodDescriptorMethod(ExceptionSink* xsink, PyObject* self, PyObject* obj, const QoreListNode* args, size_t arg_offset) { QorePythonHelper qph(this, xsink); if (*xsink || checkValid(xsink)) { return QoreValue(); } QorePythonReferenceHolder py_args; py_args = getPythonTupleValue(xsink, args, arg_offset, self); if (*xsink) { return QoreValue(); } //printd(5, "QorePythonProgram::callMethodDescriptorMethod() argcount: %d\n", // (args && args->size() > arg_offset) ? args->size() - arg_offset : 0); QorePythonReferenceHolder return_value(PyMethodDescr_Type.tp_call(obj, *py_args, nullptr)); // check for Python exceptions if (!return_value && checkPythonException(xsink)) { return QoreValue(); } return getQoreValue(xsink, *return_value); } QoreValue QorePythonProgram::callClassMethodDescriptorMethod(ExceptionSink* xsink, PyObject* self, PyObject* obj, const QoreListNode* args, size_t arg_offset) { QorePythonHelper qph(this, xsink); if (*xsink || checkValid(xsink)) { return QoreValue(); } // get class from self QorePythonReferenceHolder cls(PyObject_GetAttrString(self, "__class__")); QorePythonReferenceHolder py_args; py_args = getPythonTupleValue(xsink, args, arg_offset, *cls); if (*xsink) { return QoreValue(); } //printd(5, "QorePythonProgram::callClassMethodDescriptorMethod(): argcount: %d\n", // (args && args->size() > arg_offset) ? args->size() - arg_offset : 0); QorePythonReferenceHolder return_value(PyClassMethodDescr_Type.tp_call(obj, *py_args, nullptr)); // check for Python exceptions if (!return_value && checkPythonException(xsink)) { return QoreValue(); } return getQoreValue(xsink, *return_value); } int QorePythonProgram::saveModule(const char* name, PyObject* mod) { QorePythonReferenceHolder sys(PyImport_ImportModule("sys")); if (!sys) { return -1; } if (!PyObject_HasAttrString(*sys, "modules")) { return -1; } QorePythonReferenceHolder modules(PyObject_GetAttrString(*sys, "modules")); printd(5, "QorePythonProgram::saveModule() modules: %p '%s' %s\n", *modules, name, Py_TYPE(*modules)->tp_name); if (!PyDict_Check(*modules)) { return -1; } return PyDict_SetItemString(*modules, name, mod); } int QorePythonProgram::import(ExceptionSink* xsink, const char* module, const char* symbol) { //printd(5, "QorePythonProgram::import() module: '%s' symbol: '%s'\n", module, symbol ? symbol : "n/a"); QoreString mod_name(module); QorePythonReferenceHolder mod; if (symbol) { QoreString sym(symbol); qore_offset_t i = sym.rfind('.'); if (i > 0 && (size_t)i != (sym.size() - 1)) { mod_name.concat('.'); mod_name.concat(sym, 0, i, xsink); //printd(5, "trying to import '%s'\n", mod_name.c_str()); mod = PyImport_ImportModule(mod_name.c_str()); if (!mod) { if (!checkPythonException(xsink)) { throw QoreStandardException("PYTHON-IMPORT-ERROR", "Python could not load module '%s'", module); } return -1; } // if the module has already been imported, then ignore if (mod_set.find(*mod) != mod_set.end()) { return 0; } QoreString ns_path(mod_name); ns_path.replaceAll(".", "::"); symbol = sym.c_str() + i + 1; //printd(5, "imported module: '%s' importing symbol: '%s' (ns_path: '%s')\n", mod_name.c_str(), symbol, // ns_path.c_str()); PythonModuleContextHelper mch(this, ns_path.c_str()); // https://docs.python.org/3/reference/import.html: // any module that contains a __path__ attribute is considered a package return checkImportSymbol(xsink, mod_name.c_str(), *mod, PyObject_HasAttrString(*mod, "__path__"), symbol, IF_ALL, false); } } mod = PyImport_ImportModule(module); if (!mod) { if (!checkPythonException(xsink)) { throw QoreStandardException("PYTHON-IMPORT-ERROR", "Python could not load module '%s'", module); } return -1; } // if the module has already been imported, then ignore if (mod_set.find(*mod) != mod_set.end()) { return 0; } return importModule(xsink, *mod, module, IF_ALL); } int QorePythonProgram::importModule(ExceptionSink* xsink, PyObject* mod, const char* module, int filter) { PythonModuleContextHelper mch(this, module); // if the module has already been imported, then ignore if (mod_set.find(mod) != mod_set.end()) { return 0; } mod_set.insert(mod); PyObject* main = PyImport_AddModule("__main__"); assert(main); Py_INCREF(mod); if (PyModule_AddObject(main, module, mod) < 0) { Py_DECREF(mod); if (!checkPythonException(xsink)) { xsink->raiseException("PYTHON-IMPORT-ERROR", "module '%s' could not be added to the main module", module); } return -1; } // returns a borrowed reference PyObject* mod_dict = PyModule_GetDict(mod); if (!mod_dict) { // no dictionary; cannot import module throw QoreStandardException("PYTHON-IMPORT-ERROR", "module '%s' has no dictionary", module); } // https://docs.python.org/3/reference/import.html: // any module that contains a __path__ attribute is considered a package bool is_package = (bool)PyDict_GetItemString(mod_dict, "__path__"); //printd(5, "QorePythonProgram::importModule() '%s' mod: %p (%d) pkg: %d (def: %p)\n", module, mod, filter, // is_package, PyModule_GetDef(mod)); // check the dictionary for __all__, giving a list of strings as public symbols // returns a borrowed reference { PyObject* all = PyDict_GetItemString(mod_dict, "__all__"); if (all && PyTuple_Check(all)) { Py_ssize_t len = PyTuple_Size(all); for (Py_ssize_t i = 0; i < len; ++i) { // returns a borrowed reference PyObject* sv = PyTuple_GetItem(all, i); if (!sv || !PyUnicode_Check(sv)) { throw QoreStandardException("PYTHON-IMPORT-ERROR", "module '%s' __all__ has an invalid " \ "element with type '%s'; expecting 'str'", module, sv ? Py_TYPE(sv)->tp_name : "null"); } if (checkImportSymbol(xsink, module, mod, is_package, PyUnicode_AsUTF8(sv), filter, true)) { return -1; } } printd(5, "QorePythonProgram::importModule() '%s' mod: %p (%d) pkg: %d imported __all__: %d\n", module, mod, filter, is_package, len); return 0; } } QorePythonReferenceHolder dir(PyObject_Dir(mod)); if (dir && PyList_Check(*dir)) { Py_ssize_t len = PyList_Size(*dir); for (Py_ssize_t i = 0; i < len; ++i) { // returns a borrowed reference PyObject* sv = PyList_GetItem(*dir, i); if (!sv || !PyUnicode_Check(sv)) { throw QoreStandardException("PYTHON-IMPORT-ERROR", "module '%s' __all__ has an invalid " \ "element with type '%s'; expecting 'str'", module, sv ? Py_TYPE(sv)->tp_name : "null"); } if (checkImportSymbol(xsink, module, mod, is_package, PyUnicode_AsUTF8(sv), filter, true)) { return -1; } } printd(5, "QorePythonProgram::importModule() '%s' mod: %p (%d) pkg: %d imported dir: %d\n", module, mod, filter, is_package, len); return 0; } throw QoreStandardException("PYTHON-IMPORT-ERROR", "module '%s' has no symbol directory", module); } int QorePythonProgram::checkImportSymbol(ExceptionSink* xsink, const char* module, PyObject* mod, bool is_package, const char* symbol, int filter, bool ignore_missing) { if (!PyObject_HasAttrString(mod, symbol)) { if (ignore_missing) { return 0; } throw QoreStandardException("PYTHON-IMPORT-ERROR", "module '%s' references unknown symbol '%s'", module, symbol); } QorePythonReferenceHolder value(PyObject_GetAttrString(mod, symbol)); if (!value) { if (!checkPythonException(xsink)) { throw QoreStandardException("PYTHON-IMPORT-ERROR", "module '%s' failed to get symbol '%s'", module, symbol); } return -1; } bool is_class = PyType_Check(*value); if (is_class) { if (!(filter & IF_CLASS)) { return 0; } } else { bool is_module = PyModule_Check(*value); if (is_module) { if (!is_package) { return 0; } } else if (!(filter & IF_OTHER)) { return 0; } } return importSymbol(xsink, *value, module, symbol, filter); } int QorePythonProgram::findCreateQoreFunction(PyObject* value, const char* symbol, q_external_func_t func) { QoreNamespace* ns = getNamespaceForObject(value); if (!ns->findLocalFunction(symbol)) { // do not need to save reference here ns->addBuiltinVariant((void*)value, symbol, func, QCF_USES_EXTRA_ARGS, QDOM_UNCONTROLLED_API, autoTypeInfo); printd(5, "QorePythonProgram::findCreateQoreFunction() added function %s::%s() (%s)\n", ns->getName(), symbol, Py_TYPE(value)->tp_name); return 0; } return -1; } int QorePythonProgram::importSymbol(ExceptionSink* xsink, PyObject* value, const char* module, const char* symbol, int filter) { if (!value) { if (!checkPythonException(xsink)) { xsink->raiseException("PYTHON-IMPORT-ERROR", "module '%s' returned null for symbol '%s'", module, symbol); } return -1; } PyTypeObject* value_type = Py_TYPE(value); if (!value_type) { xsink->raiseException("PYTHON-IMPORT-ERROR", "module '%s' symbol '%s' has null type", module, symbol); return -1; } printd(5, "QorePythonProgram::importSymbol() %s.%s (type %s)\n", module, symbol, value_type->tp_name); // check for builtin functions -> static method if (PyCFunction_Check(value)) { // ignore errors findCreateQoreFunction(value, symbol, (q_external_func_t)QorePythonProgram::execPythonCFunction); return 0; } if (PyFunction_Check(value)) { // ignore errors findCreateQoreFunction(value, symbol, (q_external_func_t)QorePythonProgram::execPythonFunction); return 0; } if (PyType_Check(value)) { //printd(5, "QorePythonProgram::importSymbol() class sym: '%s' -> '%s' (%p)\n", symbol, // reinterpret_cast(value)->tp_name, value); strset_t nsset; getCreateQorePythonClassIntern(xsink, reinterpret_cast(value), nsset); if (*xsink) { return -1; } //printd(5, "QorePythonProgram::importSymbol() added class %s.%s (%s)\n", module, symbol, cls->getName()); return 0; } if (PyModule_Check(value)) { QoreStringMaker sub_module("%s::%s", module, symbol); return importModule(xsink, value, sub_module.c_str(), filter); } //printd(5, "QorePythonProgram::importSymbol() adding const %s.%s = '%s'\n", module, symbol, // Py_TYPE(value)->tp_name); ValueHolder v(getQoreValue(xsink, value), xsink); if (*xsink) { return -1; } // skip empty values if (!v) { return 0; } const QoreTypeInfo* typeInfo = v->getFullTypeInfo(); QoreString ns_path = module_context; ns_path.replaceAll(".", "::"); QoreNamespace* ns = pyns->findCreateNamespacePathAll(ns_path.c_str()); if (ns->findLocalConstant(symbol)) { return 0; } //std::string path = ns->getPath(); //printd(5, "QorePythonProgram::importSymbol() adding const %s.%s = '%s' (ns: %s)\n", module_context, symbol, // qore_type_get_name(typeInfo), path.c_str()); ns->addConstant(symbol, v.release(), typeInfo); return 0; } // Python integration PyObject* QorePythonProgram::callQoreFunction(PyObject* self, PyObject* args) { /* QorePythonReferenceHolder selfstr(PyObject_Repr(self)); assert(PyUnicode_Check(*selfstr)); QorePythonReferenceHolder argstr(PyObject_Repr(args)); assert(PyUnicode_Check(*argstr)); printd(5, "QorePythonProgram::callQoreFunction() self: %p (%s) args: %s\n", self, PyUnicode_AsUTF8(*selfstr), PyUnicode_AsUTF8(*argstr)); */ assert(PyCapsule_CheckExact(self)); func_capsule_t* fc = reinterpret_cast(PyCapsule_GetPointer(self, nullptr)); assert(&fc->func); assert(fc->py_pgm); // get Qore arguments ExceptionSink xsink; assert(PyTuple_Check(args)); ReferenceHolder qargs(fc->py_pgm->getQoreListFromTuple(&xsink, args), &xsink); if (!xsink) { ValueHolder rv(&xsink); { QorePythonReleaseGilHelper prgh; rv = fc->func.evalFunction(nullptr, *qargs, fc->py_pgm->getQoreProgram(), &xsink); } if (!xsink) { QorePythonReferenceHolder py_rv(fc->py_pgm->getPythonValue(*rv, &xsink)); if (!xsink) { assert(py_rv); return py_rv.release(); } } } fc->py_pgm->raisePythonException(xsink); assert(PyErr_Occurred()); return nullptr; }