From 4293dfb9d679799fc46240436636fdcd431bccad Mon Sep 17 00:00:00 2001
From: Cyrille Bagard <nocbos@gmail.com>
Date: Fri, 19 May 2023 16:03:55 +0200
Subject: Install metaclass for Python GObjects, if needed.

---
 plugins/pychrysalide/core.c | 126 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 126 insertions(+)

diff --git a/plugins/pychrysalide/core.c b/plugins/pychrysalide/core.c
index 66dbdf0..d4f2292 100644
--- a/plugins/pychrysalide/core.c
+++ b/plugins/pychrysalide/core.c
@@ -98,6 +98,9 @@ static PyObject *py_chrysalide_mod_version(PyObject *, PyObject *);
 /* Détermine si l'interpréteur lancé est celui pris en compte. */
 static bool is_current_abi_suitable(void);
 
+/* Assure une pleine initialisation des objets de Python-GI. */
+static bool install_metaclass_for_python_gobjects(void);
+
 /* Définit la version attendue de GTK à charger dans Python. */
 static bool set_version_for_gtk_namespace(const char *);
 
@@ -292,6 +295,126 @@ static bool is_current_abi_suitable(void)
 
 /******************************************************************************
 *                                                                             *
+*  Paramètres  : -                                                            *
+*                                                                             *
+*  Description : Assure une pleine initialisation des objets de Python-GI.    *
+*                                                                             *
+*  Retour      : Bilan de l'opération.                                        *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static bool install_metaclass_for_python_gobjects(void)
+{
+    bool result;                            /* Bilan à retourner           */
+    PyObject *gi_types_mod;                 /* Module Python-GObject       */
+
+    /**
+     * Les extensions Python sont chargées à partir de la fonction load_python_plugins(),
+     * qui fait appel à g_python_plugin_new(). Une instance y est construite via un
+     * appel à PyObject_CallFunction() avec la classe spécifiée par l'alias AutoLoad
+     * dans le fichier __init__.py présent dans chaque module d'extension.
+     *
+     * Le constructeur py_plugin_module_new() renvoie in fine à la fonction générique
+     * python_abstract_constructor_with_dynamic_gtype(), laquelle conduit à la fonction
+     * pygobject_register_class() définie dans <python3-gi>/gi/pygobject-object.c.
+     * Le code de cette dernière comprend notamment la portion suivante :
+     *
+     *    [...]
+     *    Py_SET_TYPE(type, PyGObject_MetaType);
+     *    [...]
+     *    if (PyType_Ready(type) < 0) {
+     *        g_warning ("couldn't make the type `%s' ready", type->tp_name);
+     *        return;
+     *    }
+     *    [...]
+     *
+     * La fonction PyType_Ready() est définie dans <python3>/Objects/typeobject.c
+     * et commence par :
+     *
+     *    int PyType_Ready(PyTypeObject *type)
+     *    {
+     *        if (type->tp_flags & Py_TPFLAGS_READY) {
+     *            assert(_PyType_CheckConsistency(type));
+     *            return 0;
+     *        }
+     *        [...]
+     *    }
+     *
+     * La vérification de cohérencce commence par analyser le type et son propre
+     * type :
+     *
+     *  - cf. _PyType_CheckConsistency() dans <python3>/Objects/typeobject.c :
+     *
+     *    int _PyType_CheckConsistency(PyTypeObject *type)
+     *    {
+     *        [...]
+     *        CHECK(!_PyObject_IsFreed((PyObject *)type));
+     *        [...]
+     *    }
+     *
+     *  - cf. _PyObject_IsFreed() dans <python3>/Objects/object.c :
+     *
+     *    int _PyObject_IsFreed(PyObject *op)
+     *    {
+     *        if (_PyMem_IsPtrFreed(op) || _PyMem_IsPtrFreed(Py_TYPE(op))) {
+     *            return 1;
+     *    }
+     *
+     * La fonction _PyMem_IsPtrFreed() recherche entre autres la valeur NULL.
+     *
+     * Or le type du type est écrasé dans la fonction pygobject_register_class()
+     * avec la valeur de la variable PyGObject_MetaType. Cette variable n'est
+     * définie qu'à un seul endroit, dans <python3-gi>/gi/gimodule.c :
+     *
+     *    static PyObject *
+     *    pyg__install_metaclass(PyObject *dummy, PyTypeObject *metaclass)
+     *    {
+     *        Py_INCREF(metaclass);
+     *        PyGObject_MetaType = metaclass;
+     *        Py_INCREF(metaclass);
+     *
+     *        Py_SET_TYPE(&PyGObject_Type, metaclass);
+     *
+     *        Py_INCREF(Py_None);
+     *        return Py_None;
+     *    }
+     *
+     * Afin de valider la vérification de _PyType_CheckConsistency() pour les
+     * modules externes qui entraînent un enregistrement tout en portant le drapeau
+     * Py_TPFLAGS_READY (typiquement ceux du répertoire "plugins/python/", il faut
+     * initialiser au besoin la variable PyGObject_MetaType.
+     *
+     * Une ligne suffit donc à enregistrer le type intermédiaire :
+     *
+     *    from _gi import types
+     *
+     * On simule ici une déclaration similaire si nécessaire
+     */
+
+    result = false;
+
+    if (PyType_CheckExact(&PyGObject_Type))
+    {
+        gi_types_mod = PyImport_ImportModule("gi.types");
+
+        result = (PyErr_Occurred() == NULL);
+
+        if (result)
+            result = (PyType_CheckExact(&PyGObject_Type) == 0);
+
+        Py_XDECREF(gi_types_mod);
+
+    }
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
 *  Paramètres  : version = idenfiant de la version de GTK à stipuler.         *
 *                                                                             *
 *  Description : Définit la version attendue de GTK à charger dans Python.    *
@@ -459,6 +582,9 @@ PyMODINIT_FUNC PyInit_pychrysalide(void)
         goto exit;
     }
 
+    if (!install_metaclass_for_python_gobjects())
+        goto exit;
+
     if (!set_version_for_gtk_namespace("3.0"))
         goto exit;
 
-- 
cgit v0.11.2-87-g4458