diff options
author | Cyrille Bagard <nocbos@gmail.com> | 2025-01-26 14:25:51 (GMT) |
---|---|---|
committer | Cyrille Bagard <nocbos@gmail.com> | 2025-01-26 14:25:51 (GMT) |
commit | 56c148de74ed8c78ce54ed24daa83ec2f641e054 (patch) | |
tree | d4e43da9d6c729146c77fb30de8fa3767b257afb | |
parent | b1227a2779c9a72cab1295a1419a9c990df6488e (diff) |
Define new interfaces for arch operands.
29 files changed, 2262 insertions, 669 deletions
diff --git a/plugins/pychrysalide/bindings.c b/plugins/pychrysalide/bindings.c index 99491d6..7e87e27 100644 --- a/plugins/pychrysalide/bindings.c +++ b/plugins/pychrysalide/bindings.c @@ -28,6 +28,7 @@ #include <assert.h> #include <dlfcn.h> #include <pygobject.h> +#include <stddef.h> #include <stdio.h> @@ -105,6 +106,42 @@ static bool install_metaclass_for_python_gobjects(void); /* Met en place un environnement pour l'extension Python. */ static bool setup_python_context(void); +/* Intègre les éventuelles fonctions natives des interfaces. */ +static void inherit_interface_slots(PyObject *); + +/** + * Conservation d'anciens pointeurs remplacés + */ +static initproc __old_gobject_meta_base_init = NULL; +static initproc __old_gobject_meta_init = NULL; + +/** + * La fonction unhook_pygobject_behaviour(), inversant les opérations de la fonction + * unhook_pygobject_behaviour() manipulerait volontiers les fonctions PyImport_ImportModule() + * et PyObject_GetAttrString(). + * + * Cependant, les appels à ces dernières depuis la clôture organisée par la fonction + * PyExit_pychrysalide() provoque l'erreur suivante : + * + * Fatal Python error: _PyInterpreterState_GET: the function must be called with the GIL held, but the GIL is released (the current Python thread state is NULL) + * + * Les accès nécessaires sont donc conservés ici. + */ +static PyTypeObject *__gobject_meta_base = NULL; +static PyTypeObject *__gobject_meta = NULL; + +/* Interceptionne une initialisation pour types gi._gi.GObject. */ +static int hook_gobject_meta_base_init(PyObject *, PyObject *, PyObject *); + +/* Interceptionne une initialisation pour types GObject.Object. */ +static int hook_gobject_meta_init(PyObject *, PyObject *, PyObject *); + +/* Modifie légèrement le comportement des GObjects en Python. */ +static bool hook_pygobject_behaviour(void); + +/* Restaure le comportement d'origine des GObjects en Python. */ +static void unhook_pygobject_behaviour(void); + /* Assure la définition d'un type GObject pour Python adapté. */ static void ensure_native_pygobject_type(PyTypeObject **); @@ -504,6 +541,265 @@ static bool setup_python_context(void) /****************************************************************************** * * +* Paramètres : cls = classe instanciée pour la construction d'un objet. * +* * +* Description : Intègre les éventuelles fonctions natives des interfaces. * +* * +* Retour : - * +* * +* Remarques : - * +* * +******************************************************************************/ + +static void inherit_interface_slots(PyObject *cls) +{ + GType gtype; /* Type GObject lié au Python */ + GType *ifaces; /* Interfaces implémentées */ + guint ifaces_count; /* Nombre de ces interfaces */ + guint i; /* Boucle de parcours */ + PyTypeObject *iface_type; /* Type Python pour interface */ + size_t k; /* Boucle de parcours */ + size_t offset; /* Position dans une structure */ + void *src_slot; /* Eventuelle fonction idéale */ + void *dst_slot; /* Eventuelle fonction en place*/ + + static size_t slot_offsets[] = { /* Emplacements à actualiser */ + //offsetof(PyTypeObject, tp_str), + offsetof(PyTypeObject, tp_hash), + offsetof(PyTypeObject, tp_richcompare), + }; + + /** + * Cette fonction reprend les principes de la fonction d'importation de + * PyGObject pygobject_inherit_slots(). + * + * Cependant, cette dernière n'est appelée que depuis les fonctions : + * - pygobject_register_class() (send C -> Python), qui peut écraser des + * slots existants ; + * - pygobject_new_with_interfaces() / pygobject_lookup_class(), qui ne + * remplace pas les fonctions par défaut déjà en place. + * + * Pour mémoire, les types créés dynamiquement depuis des scripts (sens + * Python -> C) passent par la fonction _wrap_pyg_type_register(). + */ + + gtype = pyg_type_from_object(cls); + assert(gtype != G_TYPE_INVALID); + + ifaces = g_type_interfaces(gtype, &ifaces_count); + + for (i = 0; i < ifaces_count; i++) + { + iface_type = pygobject_lookup_class(ifaces[i]); + +#define PYTYPE_SLOT(tp, off) \ + *(void **)(void *)(((char *)tp) + off) + + for (k = 0; k < ARRAY_SIZE(slot_offsets); k++) + { + offset = slot_offsets[k]; + + src_slot = PYTYPE_SLOT(iface_type, offset); + + if (src_slot == NULL) + continue; + + if (src_slot == PYTYPE_SLOT(&PyBaseObject_Type, offset) + || src_slot == PYTYPE_SLOT(&PyGObject_Type, offset)) + continue; + + dst_slot = PYTYPE_SLOT(cls, offset); + + if (src_slot == dst_slot) + continue; + + if (dst_slot != NULL) + { + if (dst_slot != PYTYPE_SLOT(&PyBaseObject_Type, offset) + && dst_slot != PYTYPE_SLOT(&PyGObject_Type, offset)) + continue; + } + + /** + * Usage du *(void **)(void *) + */ + PYTYPE_SLOT(cls, offset) = src_slot; + + } + + } + + g_free(ifaces); + +} + + +/****************************************************************************** +* * +* Paramètres : self = objet à initialiser (théoriquement). * +* args = arguments fournis à l'appel. * +* kwds = arguments de type key=val fournis. * +* * +* Description : Interceptionne une initialisation pour types gi._gi.GObject. * +* * +* Retour : Bilan de l'initialisation. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static int hook_gobject_meta_base_init(PyObject *self, PyObject *args, PyObject *kwds) +{ + int result; /* Bilan à retourner */ + + /** + * Le type de self (self->ob_type->tp_name) est ici _GObjectMetaBase. + */ + + result = __old_gobject_meta_base_init(self, args, kwds); + + if (result == 0) + inherit_interface_slots(self); + + return result; + + +} + + +/****************************************************************************** +* * +* Paramètres : self = objet à initialiser (théoriquement). * +* args = arguments fournis à l'appel. * +* kwds = arguments de type key=val fournis. * +* * +* Description : Interceptionne une initialisation pour types GObject.Object. * +* * +* Retour : Bilan de l'initialisation. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static int hook_gobject_meta_init(PyObject *self, PyObject *args, PyObject *kwds) +{ + int result; /* Bilan à retourner */ + + /** + * Le type de self (self->ob_type->tp_name) est ici GObjectMeta. + */ + + result = __old_gobject_meta_init(self, args, kwds); + + if (result == 0) + inherit_interface_slots(self); + + return result; + + +} + + +/****************************************************************************** +* * +* Paramètres : - * +* * +* Description : Modifie légèrement le comportement des GObjects en Python. * +* * +* Retour : Bilan de l'initialisation. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static bool hook_pygobject_behaviour(void) +{ + bool result; /* Bilan à retourner */ + PyObject *gi_types_mod; /* Module Python-GObject */ + + result = false; + + /** + * Validation des accès. + * + * Les références prises sur les attributs sont restituées dans + * unhook_pygobject_behaviour(). + */ + + gi_types_mod = PyImport_ImportModule("gi.types"); + if (gi_types_mod == NULL) goto exit; + + __gobject_meta_base = (PyTypeObject *)PyObject_GetAttrString(gi_types_mod, "_GObjectMetaBase"); + assert(__gobject_meta_base != NULL); + if (__gobject_meta_base == NULL) goto exit_with_mod; + + __gobject_meta = (PyTypeObject *)PyObject_GetAttrString(gi_types_mod, "GObjectMeta"); + assert(__gobject_meta != NULL); + if (__gobject_meta == NULL) goto exit_with_mod; + + /** + * Modification des comportements. + */ + + __old_gobject_meta_base_init = __gobject_meta_base->tp_init; + + __gobject_meta_base->tp_init = hook_gobject_meta_base_init; + + __old_gobject_meta_init = __gobject_meta->tp_init; + + __gobject_meta->tp_init = hook_gobject_meta_init; + + result = true; + + exit_with_mod: + + Py_DECREF(gi_types_mod); + + exit: + + if (!result) + PyErr_SetString(PyExc_SystemError, "unable to hook the GObject behaviour in Python."); + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : - * +* * +* Description : Restaure le comportement d'origine des GObjects en Python. * +* * +* Retour : - * +* * +* Remarques : - * +* * +******************************************************************************/ + +static void unhook_pygobject_behaviour(void) +{ + /** + * Le déclenchement de la fonction PyExit_pychrysalide() appelante est + * programmé depuis init_python_pychrysalide_module(), appelée si + * hook_pygobject_behaviour() a opéré avec réussite. + */ + assert(__gobject_meta_base != NULL); + assert(__gobject_meta != NULL); + + __gobject_meta_base->tp_init = __old_gobject_meta_base_init; + + Py_XDECREF(__gobject_meta_base); + + __gobject_meta->tp_init = __old_gobject_meta_init; + + Py_XDECREF(__gobject_meta); + +} + + +/****************************************************************************** +* * * Paramètres : namespace = module particulier à charger à partir de gi. * * version = idenfiant de la version à stipuler. * * * @@ -874,6 +1170,9 @@ PyObject *init_python_pychrysalide_module(const pyinit_details_t *details) if (!setup_python_context()) goto exit; + if (!hook_pygobject_behaviour()) + goto exit; + /** * Le chargement forcé de l'espace GLib pour Python permet d'éviter un écueil, * à savoir des types convertis de façon incomplète. Par exemple, pour une @@ -1158,6 +1457,8 @@ static bool init_python_interpreter_for_standalone_mode(const pyinit_details_t * static void PyExit_pychrysalide(void) { + unhook_pygobject_behaviour(); + exit_all_plugins(); unload_core_components(ACC_ALL_COMPONENTS); diff --git a/plugins/pychrysalide/glibext/Makefile.am b/plugins/pychrysalide/glibext/Makefile.am index 007ceee..2d45244 100644 --- a/plugins/pychrysalide/glibext/Makefile.am +++ b/plugins/pychrysalide/glibext/Makefile.am @@ -5,7 +5,6 @@ noinst_LTLIBRARIES = libpychrysaglibext.la # binarycursor.h binarycursor.c \ # buffercache.h buffercache.c \ # bufferline.h bufferline.c \ -# comparison.h comparison.c \ # configuration.h configuration.c \ # linecursor.h linecursor.c \ # linegen.h linegen.c \ @@ -21,7 +20,9 @@ noinst_LTLIBRARIES = libpychrysaglibext.la # endif libpychrysaglibext_la_SOURCES = \ + comparable.h comparable.c \ constants.h constants.c \ + hashable.h hashable.c \ module.h module.c \ objhole.h objhole.c \ portion.h portion.c \ diff --git a/plugins/pychrysalide/glibext/comparable.c b/plugins/pychrysalide/glibext/comparable.c index 548f700..e4982d7 100644 --- a/plugins/pychrysalide/glibext/comparable.c +++ b/plugins/pychrysalide/glibext/comparable.c @@ -1,8 +1,8 @@ /* Chrysalide - Outil d'analyse de fichiers binaires - * comparison.c - équivalent Python du fichier "glibext/comparison.h" + * comparable.c - équivalent Python du fichier "glibext/comparable.c" * - * Copyright (C) 2018-2019 Cyrille Bagard + * Copyright (C) 2025 Cyrille Bagard * * This file is part of Chrysalide. * @@ -22,38 +22,40 @@ */ -#include "comparison.h" +#include "comparable.h" +#include <assert.h> #include <pygobject.h> -#include <glibext/comparison-int.h> +#include <glibext/comparable-int.h> -#include "constants.h" #include "../access.h" #include "../helpers.h" -#include "../analysis/content.h" /* ------------------------ GLUE POUR CREATION DEPUIS PYTHON ------------------------ */ -/* Procède à l'initialisation de l'interface de comparaison. */ -static void py_comparable_item_interface_init(GComparableItemIface *, gpointer *); +/* Procède à l'initialisation de l'interface de détermination. */ +static void py_comparable_object_interface_init(GComparableObjectInterface *, gpointer *); -/* Réalise une comparaison entre objets selon un critère précis. */ -static bool py_comparable_item_compare_rich(const GComparableItem *, const GComparableItem *, RichCmpOperation, bool *); +/* Réalise une comparaison étendue entre objets. */ +static int py_comparable_object_compare_wrapper(const GComparableObject *, const GComparableObject *); /* ------------------------- CONNEXION AVEC L'API DE PYTHON ------------------------- */ -/* Effectue une comparaison avec un objet 'ComparableItem'. */ -static PyObject *py_comparable_item_richcompare(PyObject *, PyObject *, int); +/* Transmet le statut d'une comparaison effectuée par le parent. */ +static PyObject *py_comparable_object_parent_compare(PyObject *, PyObject *); + +/* Effectue une comparaison avec un objet 'ComparableObject'. */ +static PyObject *py_comparable_object_richcompare(PyObject *, PyObject *, int); @@ -67,7 +69,7 @@ static PyObject *py_comparable_item_richcompare(PyObject *, PyObject *, int); * Paramètres : iface = interface GLib à initialiser. * * unused = adresse non utilisée ici. * * * -* Description : Procède à l'initialisation de l'interface de comparaison. * +* Description : Procède à l'initialisation de l'interface de détermination. * * * * Retour : - * * * @@ -75,72 +77,88 @@ static PyObject *py_comparable_item_richcompare(PyObject *, PyObject *, int); * * ******************************************************************************/ -static void py_comparable_item_interface_init(GComparableItemIface *iface, gpointer *unused) +static void py_comparable_object_interface_init(GComparableObjectInterface *iface, gpointer *unused) { - -#define COMPARABLE_ITEM_DOC \ - "ComparableItem provides an interface to compare objects.\n" \ +#define COMPARABLE_OBJECT_DOC \ + "The ComparableObject class provides an interface to compare" \ + " objects.\n" \ "\n" \ "A typical class declaration for a new implementation looks like:\n" \ "\n" \ - " class NewImplem(GObject.Object, ComparableItem):\n" \ + " class NewImplem(GObject.Object, ComparableObject):\n" \ " ...\n" \ - "\n" + "\n" \ + "The following method has to be defined for new implementations:\n" \ + "* pychrysalide.glibext.ComparableObject._compare().\n" - iface->cmp_rich = py_comparable_item_compare_rich; + iface->compare = py_comparable_object_compare_wrapper; } /****************************************************************************** * * -* Paramètres : item = premier objet à cnsulter pour une comparaison. * -* other = second objet à cnsulter pour une comparaison. * -* op = opération de comparaison à réaliser. * -* status = bilan des opérations de comparaison. [OUT] * +* Paramètres : object = premier objet à consulter pour une comparaison. * +* other = second objet à consulter pour une comparaison. * * * -* Description : Réalise une comparaison entre objets selon un critère précis.* +* Description : Réalise une comparaison étendue entre objets. * * * -* Retour : true si la comparaison a pu être effectuée, false sinon. * +* Retour : Bilan de la comparaison. * * * * Remarques : - * * * ******************************************************************************/ -static bool py_comparable_item_compare_rich(const GComparableItem *item, const GComparableItem *other, RichCmpOperation op, bool *status) +static int py_comparable_object_compare_wrapper(const GComparableObject *object, const GComparableObject *other) { - bool result; /* Etat à retourner */ + int result; /* Bilan à retourner */ PyGILState_STATE gstate; /* Sauvegarde d'environnement */ - PyObject *pyitem; /* Objet Python concerné #1 */ - PyObject *pyother; /* Objet Python concerné #2 */ + PyObject *pyobj; /* Objet Python concerné */ + PyObject *args; /* Arguments pour l'appel */ PyObject *pyret; /* Bilan de consultation */ - int ret; /* Bilan d'une conversion */ - result = false; +#define COMPARABLE_OBJECT_COMPARE_WRAPPER PYTHON_WRAPPER_DEF \ +( \ + _compare, "$self, other", \ + METH_VARARGS, \ + "Abstract method allowing to compare two objects implementing" \ + " the interface. This method is used to handle rich comparisons"\ + " automatically.\n" \ + "\n" \ + "The result has to be an integer lesser than, equal to, or" \ + " greater than zero if *self* is found, respectively, to be" \ + " lesser than, to match, or to be greater than *other*.\n" \ + "\n" \ + "A *TypeError* exception is raised if the return value is not" \ + " an integer." \ +) + + result = 0; gstate = PyGILState_Ensure(); - pyitem = pygobject_new(G_OBJECT(item)); - pyother = pygobject_new(G_OBJECT(other)); + pyobj = pygobject_new(G_OBJECT(object)); - pyret = PyObject_RichCompare(pyitem, pyother, op); + args = PyTuple_New(1); + PyTuple_SetItem(args, 0, pygobject_new(G_OBJECT(other))); + + pyret = run_python_method(pyobj, "_compare", args); if (pyret != NULL) { - ret = PyBool_Check(pyret); - - if (ret) - { - *status = (pyret == Py_True); - result = true; - } + if (PyLong_Check(pyret)) + result = PyLong_AsLong(pyret); - Py_DECREF(pyret); + else + PyErr_SetString(PyExc_TypeError, _("comparison status has to be a signed integer")); } - Py_DECREF(pyother); - Py_DECREF(pyitem); + Py_XDECREF(pyret); + + Py_DECREF(args); + + Py_DECREF(pyobj); PyGILState_Release(gstate); @@ -157,11 +175,87 @@ static bool py_comparable_item_compare_rich(const GComparableItem *item, const G /****************************************************************************** * * +* Paramètres : self = objet dont l'instance se veut unique. * +* args = adresse non utilisée ici. * +* * +* Description : Transmet le statut d'une comparaison effectuée par le parent.* +* * +* Retour : Bilan de la comparaison. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static PyObject *py_comparable_object_parent_compare(PyObject *self, PyObject *args) +{ + PyObject *result; /* Valeur à retourner */ + GComparableObject *other; /* Second objet à comparer */ + int ret; /* Bilan de lecture des args. */ + GComparableObject *object; /* Mécanismes natifs */ + GComparableObjectInterface *iface; /* Interface utilisée */ + GComparableObjectInterface *parent_iface; /* Interface parente */ + int status; /* Bilan d'une comparaison */ + +#define COMPARABLE_OBJECT_PARENT_COMPARE_METHOD PYTHON_METHOD_DEF \ +( \ + parent_compare, "$sel, otherf", \ + METH_VARARGS, py_comparable_object, \ + "Provide the comparison status defined by the interface" \ + " implementation from the object native parent.\n" \ + "\n" \ + "The result is a signed integer.\n" \ + "\n" \ + "A *TypeError* exception is raised if the object parent does" \ + " not implement the pychrysalide.glibext.ComparableObject" \ + " interface.\n" \ + "\n" \ + "A *RuntimeError* exception is raised if the direct parent type"\ + " of the object has not a native implementation. For Python" \ + " implementations, the super()._compare() function has to be" \ + " used instead." \ +) + + if (!check_for_native_parent(self)) + return NULL; + + ret = PyArg_ParseTuple(args, "O&", convert_to_comparable_object, &other); + if (!ret) return NULL; + + object = G_COMPARABLE_OBJECT(pygobject_get(self)); + + iface = G_COMPARABLE_OBJECT_GET_IFACE(object); + + parent_iface = g_type_interface_peek_parent(iface); + + if (parent_iface == NULL) + { + PyErr_SetString(PyExc_TypeError, _("object parent does not implement the ComparableObject interface")); + + result = NULL; + + } + else + { + status = parent_iface->compare(object, other); + + result = PyLong_FromLong(status); + + CLEAN_RESULT_IF_RAISED_EXCEPTION(result); + + } + + return result; + +} + + +/****************************************************************************** +* * * Paramètres : a = premier object Python à consulter. * * b = second object Python à consulter. * * op = type de comparaison menée. * * * -* Description : Effectue une comparaison avec un objet 'ComparableItem'. * +* Description : Effectue une comparaison avec un objet 'ComparableObject'. * * * * Retour : Bilan de l'opération. * * * @@ -169,41 +263,71 @@ static bool py_comparable_item_compare_rich(const GComparableItem *item, const G * * ******************************************************************************/ -static PyObject *py_comparable_item_richcompare(PyObject *a, PyObject *b, int op) +static PyObject *py_comparable_object_richcompare(PyObject *a, PyObject *b, int op) { PyObject *result; /* Bilan à retourner */ int ret; /* Bilan de lecture des args. */ - GComparableItem *item_a; /* Instance à manipuler #1 */ - GComparableItem *item_b; /* Instance à manipuler #2 */ - bool valid; /* Indication de validité */ - bool status; /* Résultat d'une comparaison */ + GComparableObject *obj_a; /* Instance à manipuler #1 */ + GComparableObject *obj_b; /* Instance à manipuler #2 */ + int status; /* Bilan d'une comparaison */ - ret = PyObject_IsInstance(b, (PyObject *)get_python_comparable_item_type()); + ret = PyObject_IsInstance(b, (PyObject *)get_python_comparable_object_type()); if (!ret) { result = Py_NotImplemented; goto cmp_done; } - item_a = G_COMPARABLE_ITEM(pygobject_get(a)); - item_b = G_COMPARABLE_ITEM(pygobject_get(b)); + obj_a = G_COMPARABLE_OBJECT(pygobject_get(a)); + obj_b = G_COMPARABLE_OBJECT(pygobject_get(b)); - valid = g_comparable_item_compare_rich(item_a, item_b, op, &status); + status = g_comparable_object_compare(obj_a, obj_b); - if (valid) - result = status ? Py_True : Py_False; - else - result = Py_NotImplemented; + switch (op) + { + case Py_LT: + result = (status < 0 ? Py_True : Py_False); + break; + + case Py_LE: + result = (status <= 0 ? Py_True : Py_False); + break; + + case Py_EQ: + result = (status == 0 ? Py_True : Py_False); + break; + + case Py_NE: + result = (status != 0 ? Py_True : Py_False); + break; + + case Py_GT: + result = (status > 0 ? Py_True : Py_False); + break; + + case Py_GE: + result = (status >= 0 ? Py_True : Py_False); + break; + + default: + assert(false); + result = Py_NotImplemented; + break; + + } cmp_done: Py_INCREF(result); + CLEAN_RESULT_IF_RAISED_EXCEPTION(result); + return result; } + /****************************************************************************** * * * Paramètres : - * @@ -216,44 +340,64 @@ static PyObject *py_comparable_item_richcompare(PyObject *a, PyObject *b, int op * * ******************************************************************************/ -PyTypeObject *get_python_comparable_item_type(void) +PyTypeObject *get_python_comparable_object_type(void) { - static PyMethodDef py_comparable_item_methods[] = { + static PyMethodDef py_comparable_object_methods[] = { + COMPARABLE_OBJECT_COMPARE_WRAPPER, + COMPARABLE_OBJECT_PARENT_COMPARE_METHOD, { NULL } }; - static PyGetSetDef py_comparable_item_getseters[] = { + static PyGetSetDef py_comparable_object_getseters[] = { { NULL } }; - static PyTypeObject py_comparable_item_type = { + static PyTypeObject py_comparable_object_type = { PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "pychrysalide.glibext.ComparableItem", + .tp_name = "pychrysalide.glibext.ComparableObject", .tp_basicsize = sizeof(PyObject), + /** + * Une valeur de .tp_richcompare non nulle écarte la définition du + * champ .tp_hash à la valeur par défaut du type PyBaseObject_Type + * dans les préparatifs de la fonction Python inherit_slots(). + * + * Ces préparatifs se poursuivent avec type_ready_set_hash(), + * qui initialise .tp_hash avec PyObject_HashNotImplemented(), + * qui n'est donc pas un comportement par défaut. + * + * Côté PyGObject, la fonction pygobject_inherit_slots() y voit + * une implémentation de .tp_hash personnalisée, ce qui bloque + * la défintion d'autres personnalisations, comme celle de + * l'interface HashableObject. + * + * La valeur nominale nulle est ainsi écartée au préalable ici. + */ + .tp_hash = (hashfunc)_Py_HashPointer, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, - .tp_doc = COMPARABLE_ITEM_DOC, + .tp_doc = COMPARABLE_OBJECT_DOC, - .tp_richcompare = py_comparable_item_richcompare, + .tp_richcompare = py_comparable_object_richcompare, - .tp_methods = py_comparable_item_methods, - .tp_getset = py_comparable_item_getseters, + .tp_methods = py_comparable_object_methods, + .tp_getset = py_comparable_object_getseters }; - return &py_comparable_item_type; + return &py_comparable_object_type; } /****************************************************************************** * * -* Paramètres : module = module dont la définition est à compléter. * +* Paramètres : - * * * -* Description : Prend en charge l'objet 'pychrysalide.....ComparableItem'. * +* Description : Prend en charge l'objet 'pychrysalide.....ComparableObject'. * * * * Retour : Bilan de l'opération. * * * @@ -261,21 +405,21 @@ PyTypeObject *get_python_comparable_item_type(void) * * ******************************************************************************/ -bool ensure_python_comparable_item_is_registered(void) +bool ensure_python_comparable_object_is_registered(void) { - PyTypeObject *type; /* Type Python 'ComparableItem' */ + PyTypeObject *type; /* Type 'ComparableObject' */ PyObject *module; /* Module à recompléter */ PyObject *dict; /* Dictionnaire du module */ static GInterfaceInfo info = { /* Paramètres d'inscription */ - .interface_init = (GInterfaceInitFunc)py_comparable_item_interface_init, + .interface_init = (GInterfaceInitFunc)py_comparable_object_interface_init, .interface_finalize = NULL, .interface_data = NULL, }; - type = get_python_comparable_item_type(); + type = get_python_comparable_object_type(); if (!PyType_HasFeature(type, Py_TPFLAGS_READY)) { @@ -283,10 +427,7 @@ bool ensure_python_comparable_item_is_registered(void) dict = PyModule_GetDict(module); - if (!register_interface_for_pygobject(dict, G_TYPE_COMPARABLE_ITEM, type, &info)) - return false; - - if (!define_comparable_item_constants(type)) + if (!register_interface_for_pygobject(dict, G_TYPE_COMPARABLE_OBJECT, type, &info)) return false; } @@ -301,7 +442,7 @@ bool ensure_python_comparable_item_is_registered(void) * Paramètres : arg = argument quelconque à tenter de convertir. * * dst = destination des valeurs récupérées en cas de succès. * * * -* Description : Tente de convertir en élément comparable. * +* Description : Tente de convertir en interface d'objet comparable. * * * * Retour : Bilan de l'opération, voire indications supplémentaires. * * * @@ -309,11 +450,11 @@ bool ensure_python_comparable_item_is_registered(void) * * ******************************************************************************/ -int convert_to_comparable_item(PyObject *arg, void *dst) +int convert_to_comparable_object(PyObject *arg, void *dst) { int result; /* Bilan à retourner */ - result = PyObject_IsInstance(arg, (PyObject *)get_python_comparable_item_type()); + result = PyObject_IsInstance(arg, (PyObject *)get_python_comparable_object_type()); switch (result) { @@ -323,11 +464,11 @@ int convert_to_comparable_item(PyObject *arg, void *dst) break; case 0: - PyErr_SetString(PyExc_TypeError, "unable to convert the provided argument to comparable item"); + PyErr_SetString(PyExc_TypeError, "unable to convert the provided argument to comparable object"); break; case 1: - *((GComparableItem **)dst) = G_COMPARABLE_ITEM(pygobject_get(arg)); + *((GComparableObject **)dst) = G_COMPARABLE_OBJECT(pygobject_get(arg)); break; default: diff --git a/plugins/pychrysalide/glibext/comparable.h b/plugins/pychrysalide/glibext/comparable.h index 79f7092..d4c6ecf 100644 --- a/plugins/pychrysalide/glibext/comparable.h +++ b/plugins/pychrysalide/glibext/comparable.h @@ -1,8 +1,8 @@ /* Chrysalide - Outil d'analyse de fichiers binaires - * comparison.h - prototypes pour l'équivalent Python du fichier "glibext/comparison.h" + * comparable.h - prototypes pour l'équivalent Python du fichier "glibext/comparable.h" * - * Copyright (C) 2018 Cyrille Bagard + * Copyright (C) 2025 Cyrille Bagard * * This file is part of Chrysalide. * @@ -22,8 +22,8 @@ */ -#ifndef _PLUGINS_PYCHRYSALIDE_GLIBEXT_COMPARISON_H -#define _PLUGINS_PYCHRYSALIDE_GLIBEXT_COMPARISON_H +#ifndef _PLUGINS_PYCHRYSALIDE_GLIBEXT_COMPARABLE_H +#define _PLUGINS_PYCHRYSALIDE_GLIBEXT_COMPARABLE_H #include <Python.h> @@ -32,14 +32,14 @@ /* Fournit un accès à une définition de type à diffuser. */ -PyTypeObject *get_python_comparable_item_type(void); +PyTypeObject *get_python_comparable_object_type(void); -/* Prend en charge l'objet 'pychrysalide.glibext.ComparableItem'. */ -bool ensure_python_comparable_item_is_registered(void); +/* Prend en charge l'objet 'pychrysalide.glibext.ComparableObject'. */ +bool ensure_python_comparable_object_is_registered(void); -/* Tente de convertir en élément comparable. */ -int convert_to_comparable_item(PyObject *, void *); +/* Tente de convertir en interface d'objet comparable. */ +int convert_to_comparable_object(PyObject *, void *); -#endif /* _PLUGINS_PYCHRYSALIDE_GLIBEXT_COMPARISON_H */ +#endif /* _PLUGINS_PYCHRYSALIDE_GLIBEXT_COMPARABLE_H */ diff --git a/plugins/pychrysalide/glibext/hashable.c b/plugins/pychrysalide/glibext/hashable.c new file mode 100644 index 0000000..c870d55 --- /dev/null +++ b/plugins/pychrysalide/glibext/hashable.c @@ -0,0 +1,398 @@ + +/* Chrysalide - Outil d'analyse de fichiers binaires + * hashable.c - équivalent Python du fichier "glibext/hashable.c" + * + * Copyright (C) 2025 Cyrille Bagard + * + * This file is part of Chrysalide. + * + * Chrysalide is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Chrysalide 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include "hashable.h" + + +#include <assert.h> +#include <pygobject.h> + + +#include <glibext/hashable-int.h> + + +#include "../access.h" +#include "../helpers.h" + + + +/* ------------------------ GLUE POUR CREATION DEPUIS PYTHON ------------------------ */ + + +/* Procède à l'initialisation de l'interface de détermination. */ +static void py_hashable_object_interface_init(GHashableObjectInterface *, gpointer *); + +/* Calcule l'empreinte sur 32 bits d'un objet. */ +static guint py_hashable_object_hash_wrapper(const GHashableObject *); + + + +/* ------------------------- CONNEXION AVEC L'API DE PYTHON ------------------------- */ + + +/* Transmet l'empreinte d'un objet calculée par son parent. */ +static PyObject *py_hashable_object_parent_hash(PyObject *, PyObject *); + +/* Calcule l'empreinte sur 32 bits d'un objet. */ +static Py_hash_t py_hashable_object_hash(PyObject *); + + + +/* ---------------------------------------------------------------------------------- */ +/* GLUE POUR CREATION DEPUIS PYTHON */ +/* ---------------------------------------------------------------------------------- */ + + +/****************************************************************************** +* * +* Paramètres : iface = interface GLib à initialiser. * +* unused = adresse non utilisée ici. * +* * +* Description : Procède à l'initialisation de l'interface de détermination. * +* * +* Retour : - * +* * +* Remarques : - * +* * +******************************************************************************/ + +static void py_hashable_object_interface_init(GHashableObjectInterface *iface, gpointer *unused) +{ +#define HASHABLE_OBJECT_DOC \ + "The HashableObject class defines a interface ensuring that a" \ + " customized hashing method is available for an object.\n" \ + "\n" \ + "A typical class declaration for a new implementation looks like:\n" \ + "\n" \ + " class NewImplem(GObject.Object, HashableObject):\n" \ + " ...\n" \ + "\n" \ + "The following method has to be defined for new implementations:\n" \ + "* pychrysalide.glibext.HashableObject._hash().\n" + + iface->hash = py_hashable_object_hash_wrapper; + +} + + +/****************************************************************************** +* * +* Paramètres : object = objet dont l'instance est à consulter. * +* * +* Description : Calcule l'empreinte sur 32 bits d'un objet. * +* * +* Retour : Valeur de représentation, unique pour l'objet ou non. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static guint py_hashable_object_hash_wrapper(const GHashableObject *object) +{ + guint result; /* Valeur à retourner */ + PyGILState_STATE gstate; /* Sauvegarde d'environnement */ + PyObject *pyobj; /* Objet Python concerné */ + PyObject *pyret; /* Bilan de consultation */ + +#define HASHABLE_OBJECT_HASH_WRAPPER PYTHON_WRAPPER_DEF \ +( \ + _hash, "$self", \ + METH_NOARGS, \ + "Abstract method computing a hash from an object which is used" \ + " as the default implementation of the __hash__() method.\n" \ + "\n" \ + "The result has to be an unsigned integer.\n" \ + "\n" \ + "A *TypeError* exception is raised if the return value is not" \ + " an integer." \ +) + + result = 0; + + gstate = PyGILState_Ensure(); + + pyobj = pygobject_new(G_OBJECT(object)); + + pyret = run_python_method(pyobj, "_hash", NULL); + + if (pyret != NULL) + { + if (PyLong_Check(pyret)) + result = PyLong_AsUnsignedLong(pyret); + + else + PyErr_SetString(PyExc_TypeError, _("computed hash value has to be an unsigned integer")); + + } + + Py_XDECREF(pyret); + + Py_DECREF(pyobj); + + PyGILState_Release(gstate); + + return result; + +} + + + +/* ---------------------------------------------------------------------------------- */ +/* CONNEXION AVEC L'API DE PYTHON */ +/* ---------------------------------------------------------------------------------- */ + + +/****************************************************************************** +* * +* Paramètres : self = objet dont l'instance se veut unique. * +* args = adresse non utilisée ici. * +* * +* Description : Transmet l'empreinte d'un objet calculée par son parent. * +* * +* Retour : Valeur de représentation, unique pour l'objet ou non. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static PyObject *py_hashable_object_parent_hash(PyObject *self, PyObject *args) +{ + PyObject *result; /* Valeur à retourner */ + GHashableObject *object; /* Mécanismes natifs */ + GHashableObjectInterface *iface; /* Interface utilisée */ + GHashableObjectInterface *parent_iface; /* Interface parente */ + guint hash; /* Valeur d'empreitne */ + +#define HASHABLE_OBJECT_PARENT_HASH_METHOD PYTHON_METHOD_DEF \ +( \ + parent_hash, "$self", \ + METH_NOARGS, py_hashable_object, \ + "Provide the hash value defined by the interface implementation"\ + " from the object native parent.\n" \ + "\n" \ + "The result is an unsigned integer.\n" \ + "\n" \ + "A *TypeError* exception is raised if the object parent does" \ + " not implement the pychrysalide.glibext.HashableObject" \ + " interface.\n" \ + "\n" \ + "A *RuntimeError* exception is raised if the direct parent type"\ + " of the object has not a native implementation. For Python" \ + " implementations, the super()._hash() function has to be used" \ + " instead." \ +) + + if (!check_for_native_parent(self)) + return NULL; + + object = G_HASHABLE_OBJECT(pygobject_get(self)); + + iface = G_HASHABLE_OBJECT_GET_IFACE(object); + + parent_iface = g_type_interface_peek_parent(iface); + + if (parent_iface == NULL) + { + PyErr_SetString(PyExc_TypeError, _("object parent does not implement the HashableObject interface")); + + result = NULL; + + } + else + { + hash = parent_iface->hash(object); + + result = PyLong_FromUnsignedLong(hash); + + CLEAN_RESULT_IF_RAISED_EXCEPTION(result); + + } + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : self = objet manipulé ici. * +* * +* Description : Calcule l'empreinte sur 32 bits d'un objet. * +* * +* Retour : Valeur de représentation, unique pour l'objet ou non. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static Py_hash_t py_hashable_object_hash(PyObject *self) +{ + Py_hash_t result; /* Empreinte à retourner */ + GHashableObject *object; /* Mécanismes natifs */ + + object = G_HASHABLE_OBJECT(pygobject_get(self)); + + result = g_hashable_object_hash(object); + + UPDATE_RESULT_IF_RAISED_EXCEPTION(-1); + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : - * +* * +* Description : Fournit un accès à une définition de type à diffuser. * +* * +* Retour : Définition d'objet pour Python. * +* * +* Remarques : - * +* * +******************************************************************************/ + +PyTypeObject *get_python_hashable_object_type(void) +{ + static PyMethodDef py_hashable_object_methods[] = { + HASHABLE_OBJECT_HASH_WRAPPER, + HASHABLE_OBJECT_PARENT_HASH_METHOD, + { NULL } + }; + + static PyGetSetDef py_hashable_object_getseters[] = { + { NULL } + }; + + static PyTypeObject py_hashable_object_type = { + + PyVarObject_HEAD_INIT(NULL, 0) + + .tp_name = "pychrysalide.glibext.HashableObject", + .tp_basicsize = sizeof(PyObject), + + .tp_hash = py_hashable_object_hash, + + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + + .tp_doc = HASHABLE_OBJECT_DOC, + + .tp_methods = py_hashable_object_methods, + .tp_getset = py_hashable_object_getseters, + + }; + + return &py_hashable_object_type; + +} + + +/****************************************************************************** +* * +* Paramètres : - * +* * +* Description : Prend en charge l'objet 'pychrysalide.....HashableObject'. * +* * +* Retour : Bilan de l'opération. * +* * +* Remarques : - * +* * +******************************************************************************/ + +bool ensure_python_hashable_object_is_registered(void) +{ + PyTypeObject *type; /* Type Python 'HashableObject'*/ + PyObject *module; /* Module à recompléter */ + PyObject *dict; /* Dictionnaire du module */ + + static GInterfaceInfo info = { /* Paramètres d'inscription */ + + .interface_init = (GInterfaceInitFunc)py_hashable_object_interface_init, + .interface_finalize = NULL, + .interface_data = NULL, + + }; + + type = get_python_hashable_object_type(); + + if (!PyType_HasFeature(type, Py_TPFLAGS_READY)) + { + module = get_access_to_python_module("pychrysalide.glibext"); + + dict = PyModule_GetDict(module); + + if (!register_interface_for_pygobject(dict, G_TYPE_HASHABLE_OBJECT, type, &info)) + return false; + + } + + return true; + +} + + +/****************************************************************************** +* * +* Paramètres : arg = argument quelconque à tenter de convertir. * +* dst = destination des valeurs récupérées en cas de succès. * +* * +* Description : Tente de convertir en interface d'objet réductible. * +* * +* Retour : Bilan de l'opération, voire indications supplémentaires. * +* * +* Remarques : - * +* * +******************************************************************************/ + +int convert_to_hashable_object(PyObject *arg, void *dst) +{ + int result; /* Bilan à retourner */ + + result = PyObject_IsInstance(arg, (PyObject *)get_python_hashable_object_type()); + + switch (result) + { + case -1: + /* L'exception est déjà fixée par Python */ + result = 0; + break; + + case 0: + PyErr_SetString(PyExc_TypeError, "unable to convert the provided argument to hashable object"); + break; + + case 1: + *((GHashableObject **)dst) = G_HASHABLE_OBJECT(pygobject_get(arg)); + break; + + default: + assert(false); + break; + + } + + return result; + +} diff --git a/plugins/pychrysalide/glibext/hashable.h b/plugins/pychrysalide/glibext/hashable.h new file mode 100644 index 0000000..8583118 --- /dev/null +++ b/plugins/pychrysalide/glibext/hashable.h @@ -0,0 +1,45 @@ + +/* Chrysalide - Outil d'analyse de fichiers binaires + * hashable.h - prototypes pour l'équivalent Python du fichier "glibext/hashable.h" + * + * Copyright (C) 2025 Cyrille Bagard + * + * This file is part of Chrysalide. + * + * Chrysalide is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Chrysalide 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#ifndef _PLUGINS_PYCHRYSALIDE_GLIBEXT_HASHABLE_H +#define _PLUGINS_PYCHRYSALIDE_GLIBEXT_HASHABLE_H + + +#include <Python.h> +#include <stdbool.h> + + + +/* Fournit un accès à une définition de type à diffuser. */ +PyTypeObject *get_python_hashable_object_type(void); + +/* Prend en charge l'objet 'pychrysalide.glibext.HashableObject'. */ +bool ensure_python_hashable_object_is_registered(void); + +/* Tente de convertir en interface d'objet réductible. */ +int convert_to_hashable_object(PyObject *, void *); + + + +#endif /* _PLUGINS_PYCHRYSALIDE_GLIBEXT_HASHABLE_H */ diff --git a/plugins/pychrysalide/glibext/module.c b/plugins/pychrysalide/glibext/module.c index 6cca246..8adae07 100644 --- a/plugins/pychrysalide/glibext/module.c +++ b/plugins/pychrysalide/glibext/module.c @@ -33,13 +33,14 @@ #include "buffercache.h" #include "bufferline.h" #include "bufferview.h" -#include "comparison.h" #include "configuration.h" #include "linecursor.h" #include "linegen.h" #include "loadedpanel.h" #include "named.h" */ +#include "comparable.h" +#include "hashable.h" #include "objhole.h" #include "portion.h" #include "singleton.h" @@ -112,6 +113,8 @@ bool populate_glibext_module(void) result = true; + if (result) result = ensure_python_comparable_object_is_registered(); + if (result) result = ensure_python_hashable_object_is_registered(); if (result) result = ensure_python_singleton_candidate_is_registered(); if (result) result = ensure_python_string_builder_is_registered(); @@ -129,7 +132,6 @@ bool populate_glibext_module(void) #ifdef INCLUDE_GTK_SUPPORT if (result) result = ensure_python_buffer_view_is_registered(); #endif - if (result) result = ensure_python_comparable_item_is_registered(); if (result) result = ensure_python_config_param_is_registered(); if (result) result = ensure_python_config_param_iterator_is_registered(); if (result) result = ensure_python_generic_config_is_registered(); diff --git a/plugins/pychrysalide/glibext/singleton.c b/plugins/pychrysalide/glibext/singleton.c index ca847de..8712506 100644 --- a/plugins/pychrysalide/glibext/singleton.c +++ b/plugins/pychrysalide/glibext/singleton.c @@ -49,20 +49,20 @@ static GSingletonCandidate **py_singleton_candidate_list_inner_instances_wrapper /* Met à jour une liste de candidats embarqués par un candidat. */ static void py_singleton_candidate_update_inner_instances_wrapper(GSingletonCandidate *, GSingletonCandidate **, size_t); -/* Fournit l'empreinte d'un candidat à une centralisation. */ -static guint py_singleton_candidate___hash__wrapper(const GSingletonCandidate *); +/* Marque un candidat comme figé. */ +static void py_singleton_candidate_mark_as_read_only_wrapper(GSingletonCandidate *); -/* Détermine si deux candidats à l'unicité sont identiques. */ -static gboolean py_singleton_candidate___eq__wrapper(const GSingletonCandidate *, const GSingletonCandidate *); +/* Indique si le candidat est figé. */ +static bool py_singleton_candidate_is_read_only_wrapper(const GSingletonCandidate *); -/* Fournit l'empreinte d'un candidat à une centralisation. */ -static PyObject *py_singleton_candidate_hash(PyObject *, PyObject *); +/* Indique si le candidat est figé. */ +static PyObject *py_singleton_candidate_is_read_only(PyObject *, void *); -/* Fournit une liste de candidats embarqués par un candidat. */ -static PyObject *py_singleton_candidate_get_inner_instances(PyObject *, void *); +/* Crée une copie modifiable d'un object unique. */ +static GSingletonCandidate *py_singleton_candidate_dup_wrapper(const GSingletonCandidate *); -/* Effectue une comparaison avec un objet 'SingletonCandidate'. */ -static PyObject *py_singleton_candidate_richcompare(PyObject *, PyObject *, int); +/* Crée une copie modifiable d'un object unique. */ +static PyObject *py_singleton_candidate_dup(PyObject *, PyObject *); @@ -104,25 +104,37 @@ static void py_singleton_candidate_interface_init(GSingletonCandidateInterface * " aiming at becoming singleton instances. All shared singletons are" \ " registered within a pychrysalide.glibext.SingletonFactory object.\n" \ "\n" \ + "Implementations of the pychrysalide.glibext.HashableObject and" \ + " pychrysalide.glibext.ComparableObject interfaces are required for" \ + " types implementing the SingletonCandidate interface.\n" \ + "\n" \ "The main implemantations come with types derived from" \ - " pychrysalide.analysis.DataType.\n" \ + " pychrysalide.analysis.DataType (with possible recursivity) or from" \ + " pychrysalide.arch.ArchOperand.\n" \ "\n" \ "A typical class declaration for a new implementation looks like:\n" \ "\n" \ - " class NewImplem(GObject.Object, SingletonCandidate):\n" \ + " class NewImplem(GObject.Object, HashableObject, ComparableObject," \ + " SingletonCandidate):\n" \ " ...\n" \ "\n" \ "The following methods have to be defined for new implementations:\n" \ + "* pychrysalide.glibext.SingletonCandidate._mark_as_read_only();\n" \ + "* pychrysalide.glibext.SingletonCandidate._is_read_only();\n" \ + "* pychrysalide.glibext.SingletonCandidate._dup().\n" \ + "\n" \ + "The following methods may bbe defined for new implementations if" \ + " inner SingletonCandidate objets are carried:\n" \ "* pychrysalide.glibext.SingletonCandidate._list_inner_instances();\n" \ - "* pychrysalide.glibext.SingletonCandidate._update_inner_instances();\n"\ - "* pychrysalide.glibext.SingletonCandidate.__hash__();\n" \ - "* pychrysalide.glibext.SingletonCandidate.__eq__().\n" + "* pychrysalide.glibext.SingletonCandidate._update_inner_instances().\n" iface->update_inner = py_singleton_candidate_update_inner_instances_wrapper; iface->list_inner = py_singleton_candidate_list_inner_instances_wrapper; - iface->hash = py_singleton_candidate___hash__wrapper; - iface->is_equal = py_singleton_candidate___eq__wrapper; + iface->mark_as_ro = py_singleton_candidate_mark_as_read_only_wrapper; + iface->is_ro = py_singleton_candidate_is_read_only_wrapper; + + iface->dup = py_singleton_candidate_dup_wrapper; } @@ -297,122 +309,97 @@ static void py_singleton_candidate_update_inner_instances_wrapper(GSingletonCand * * * Paramètres : candidate = objet dont l'instance se veut unique. * * * -* Description : Fournit l'empreinte d'un candidat à une centralisation. * +* Description : Marque un candidat comme figé. * * * -* Retour : Empreinte de l'élément représenté. * +* Retour : - * * * * Remarques : - * * * ******************************************************************************/ -static guint py_singleton_candidate___hash__wrapper(const GSingletonCandidate *candidate) +static void py_singleton_candidate_mark_as_read_only_wrapper(GSingletonCandidate *candidate) { - guint result; /* Empreinte à retourner */ PyGILState_STATE gstate; /* Sauvegarde d'environnement */ PyObject *pyobj; /* Objet Python concerné */ PyObject *pyret; /* Bilan de consultation */ -#define SINGLETON_CANDIDATE_HASH_WRAPPER PYTHON_WRAPPER_DEF \ -( \ - __hash__, "$self, /", \ - METH_NOARGS, \ - "Abstract method used to produce a hash of the object.\n" \ - "\n" \ - "The result must be an integer value up to 64 bits." \ - "\n" \ - "Inner instances which are listed through the" \ - " pychrysalide.glibext.SingletonCandidate._list_inner_instances()" \ - " method do not need to get processed here as they are handled" \ - " automatically by the interface core." \ +#define SINGLETON_CANDIDATE_MARK_AS_READ_ONLY_WRAPPER PYTHON_WRAPPER_DEF \ +( \ + _mark_as_read_only, "$self", \ + METH_NOARGS, \ + "Abstract method used to seal the object as unmodifiable.\n" \ + "\n" \ + "No result is expected." \ ) - result = 0; - gstate = PyGILState_Ensure(); pyobj = pygobject_new(G_OBJECT(candidate)); - if (has_python_method(pyobj, "__hash__")) - { - pyret = run_python_method(pyobj, "__hash__", NULL); + pyret = run_python_method(pyobj, "_mark_as_read_only", NULL); - if (pyret != NULL) - { - if (PyLong_Check(pyret)) - result = PyLong_AsUnsignedLongMask(pyret); - - Py_DECREF(pyret); - - } - - } + Py_XDECREF(pyret); Py_DECREF(pyobj); PyGILState_Release(gstate); - return result; - } /****************************************************************************** * * * Paramètres : candidate = objet dont l'instance se veut unique. * -* other = second élément à analyser. * * * -* Description : Détermine si deux candidats à l'unicité sont identiques. * +* Description : Indique si le candidat est figé. * * * -* Retour : Bilan de la comparaison. * +* Retour : true si le contenu du candidat ne peut plus être modifié. * * * * Remarques : - * * * ******************************************************************************/ -static gboolean py_singleton_candidate___eq__wrapper(const GSingletonCandidate *candidate, const GSingletonCandidate *other) +static bool py_singleton_candidate_is_read_only_wrapper(const GSingletonCandidate *candidate) { - guint result; /* Empreinte à retourner */ + bool result; /* Bilan à retourner */ PyGILState_STATE gstate; /* Sauvegarde d'environnement */ PyObject *pyobj; /* Objet Python concerné */ - PyObject *args; /* Arguments pour l'appel */ PyObject *pyret; /* Bilan de consultation */ -#define SINGLETON_CANDIDATE_EQ_WRAPPER PYTHON_WRAPPER_DEF \ -( \ - __eq__, "$self, other, /", \ - METH_NOARGS, \ - "Abstract method used to provide the *__eq__* method for" \ - " rich comparison.\n" \ - "\n" \ - "The expected result is a boolean value." \ +#define SINGLETON_CANDIDATE_IS_READ_ONLY_WRAPPER PYTHON_WRAPPER_DEF \ +( \ + _is_read_only, "$self", \ + METH_NOARGS, \ + "Abstract method used to provide the state of the object: are" \ + " its properties frozen (*True*) or can it be modified" \ + " (*False*)?\n" \ + "\n" \ + "The result has to be a boolean status.\n" \ + "\n" \ + "A *TypeError* exception is raised if the return value is not" \ + " a boolean." \ ) - result = 0; + result = false; gstate = PyGILState_Ensure(); pyobj = pygobject_new(G_OBJECT(candidate)); - if (has_python_method(pyobj, "__eq__")) - { - args = PyTuple_New(1); - PyTuple_SetItem(args, 0, pygobject_new(G_OBJECT(other))); - - pyret = run_python_method(pyobj, "__eq__", args); - - if (pyret != NULL) - { - if (PyLong_Check(pyret)) - result = PyLong_AsUnsignedLong(pyret); + pyret = run_python_method(pyobj, "_is_read_only", NULL); - Py_DECREF(pyret); + if (pyret != NULL) + { + if (PyBool_Check(pyret)) + result = (pyret == Py_True); - } - - Py_DECREF(args); + else + PyErr_SetString(PyExc_TypeError, _("status has to be provided as a boolean value")); } + Py_XDECREF(pyret); + Py_DECREF(pyobj); PyGILState_Release(gstate); @@ -424,47 +411,77 @@ static gboolean py_singleton_candidate___eq__wrapper(const GSingletonCandidate * /****************************************************************************** * * -* Paramètres : self = objet dont l'instance se veut unique. * -* args = adresse non utilisée ici. * +* Paramètres : candidate = objet dont l'instance se veut unique. * * * -* Description : Fournit l'empreinte d'un candidat à une centralisation. * +* Description : Crée une copie modifiable d'un object unique. * * * -* Retour : Empreinte de l'élément représenté. * +* Retour : Nouvelle instance mise en place. * * * * Remarques : - * * * ******************************************************************************/ -static PyObject *py_singleton_candidate_hash(PyObject *self, PyObject *args) +static GSingletonCandidate *py_singleton_candidate_dup_wrapper(const GSingletonCandidate *candidate) { - PyObject *result; /* Emplacement à retourner */ - GSingletonCandidate *candidate; /* Mécanismes natifs */ - guint hash; /* Valeur d'empreitne */ + GSingletonCandidate *result; /* Instance à retourner */ + PyGILState_STATE gstate; /* Sauvegarde d'environnement */ + PyObject *pyobj; /* Objet Python concerné */ + PyObject *pyret; /* Bilan de consultation */ + PyObject *state; /* Validation du mode */ -#define SINGLETON_CANDIDATE_HASH_METHOD PYTHON_METHOD_DEF \ -( \ - hash, "$self", \ - METH_NOARGS, py_singleton_candidate, \ - "Compute the hash value of the singleton candidate.\n" \ - "\n" \ - "The method relies on the interface core to include in the" \ - " process the optional embedded instances which may become" \ - " singletons.\n" \ - "\n" \ - "The result is an integer value.\n" \ - "\n" \ - "Even if the Python *hash()* method, relying on the" \ - " pychrysalide.glibext.SingletonCandidate.__hash__()" \ - " implementation, provides values up to 64 bits, the final" \ - " hashes processed by the native GLib hash methods are" \ - " limited to 32 bits values." \ +#define SINGLETON_CANDIDATE_DUP_WRAPPER PYTHON_WRAPPER_DEF \ +( \ + _dup, "$self", \ + METH_NOARGS, \ + "Abstract method used to create a copy of the object. This" \ + " has to be able to get modified (ie. its" \ + " pychrysalide.glibext.SingletonCandidate.read_only status" \ + " has to be *False*).\n" \ + "\n" \ + "The result has to be a new intance of type(self).\n" \ + "\n" \ + "A *TypeError* exception is raised if the type of the" \ + " return value is different from the type of self.\n" \ + "\n" \ + "A *ValueError* exception is raised of the return object" \ + " is in read-only mode." \ ) - candidate = G_SINGLETON_CANDIDATE(pygobject_get(self)); + result = false; - hash = g_singleton_candidate_hash(candidate); + gstate = PyGILState_Ensure(); + + pyobj = pygobject_new(G_OBJECT(candidate)); + + pyret = run_python_method(pyobj, "_dup", NULL); + + if (pyret != NULL) + { + if (Py_TYPE(pyret) != Py_TYPE(pyobj)) + PyErr_SetString(PyExc_TypeError, _("the result type is different from the source type")); + + else + { + state = py_singleton_candidate_is_read_only(pyret, NULL); + + if (state != NULL) + { + if (state != Py_False) + PyErr_SetString(PyExc_ValueError, _("the result type can not be in read-only mode")); + + Py_DECREF(state); + + } - result = PyLong_FromUnsignedLong(hash); + } + + } + + Py_XDECREF(pyret); + + Py_DECREF(pyobj); + + PyGILState_Release(gstate); return result; @@ -473,48 +490,51 @@ static PyObject *py_singleton_candidate_hash(PyObject *self, PyObject *args) /****************************************************************************** * * -* Paramètres : self = objet Python concerné par l'appel. * -* closure = non utilisé ici. * +* Paramètres : self = objet manipulé ici. * +* args = adresse non utilisée ici. * * * -* Description : Fournit une liste de candidats embarqués par un candidat. * +* Description : Crée une copie modifiable d'un object unique. * * * -* Retour : Liste de candidats internes, vide si aucun. * +* Retour : Nouvelle instance mise en place. * * * * Remarques : - * * * ******************************************************************************/ -static PyObject *py_singleton_candidate_get_inner_instances(PyObject *self, void *closure) +static PyObject *py_singleton_candidate_dup(PyObject *self, PyObject *args) { - PyObject *result; /* Valeur à retourner */ + PyObject *result; /* Emplacement à retourner */ GSingletonCandidate *candidate; /* Mécanismes natifs */ - size_t count; /* Quantité d'objets internes */ - GSingletonCandidate **instances; /* Liste des embarqués */ - size_t i; /* Boucle de parcours */ + GSingletonCandidate *copy; /* Copie des mécanismes natifs */ -#define SINGLETON_CANDIDATE_INNER_INSTANCES_ATTRIB PYTHON_GET_DEF_FULL \ -( \ - inner_instances, py_singleton_candidate, \ - "List of optional internal singleton candidate instances.\n" \ - "\n" \ - "The result has to be a tuple containing zero or more" \ - " pychrysalide.glibext.SingletonCandidate instances." \ +#define SINGLETON_CANDIDATE_DUP_METHOD PYTHON_METHOD_DEF \ +( \ + dup, "$self", \ + METH_NOARGS, py_singleton_candidate, \ + "Create a copy of the object. This has to be able to get" \ + " modified (ie. its" \ + " pychrysalide.glibext.SingletonCandidate.read_only status" \ + " has to be *False*).\n" \ + "\n" \ + "The result has to be a new intance of type(self)." \ ) candidate = G_SINGLETON_CANDIDATE(pygobject_get(self)); - instances = g_singleton_candidate_list_inner_instances(candidate, &count); + copy = g_singleton_candidate_dup(candidate); - result = PyTuple_New(count); + if (copy == NULL) + result = NULL; - for (i = 0; i < count; i++) + else { - PyTuple_SetItem(result, i, pygobject_new(G_OBJECT(instances[i]))); - g_object_unref(G_OBJECT(instances[i])); + result = pygobject_new(G_OBJECT(candidate)); + + unref_object(copy); + } - if (instances != NULL) - free(instances); + CLEAN_RESULT_IF_RAISED_EXCEPTION(result); return result; @@ -523,50 +543,39 @@ static PyObject *py_singleton_candidate_get_inner_instances(PyObject *self, void /****************************************************************************** * * -* Paramètres : a = premier object Python à consulter. * -* b = second object Python à consulter. * -* op = type de comparaison menée. * +* Paramètres : self = objet Python concerné par l'appel. * +* closure = non utilisé ici. * * * -* Description : Effectue une comparaison avec un objet 'SingletonCandidate'. * +* Description : Indique si le candidat est figé. * * * -* Retour : Bilan de l'opération. * +* Retour : True si le contenu du candidat ne peut plus être modifié. * * * * Remarques : - * * * ******************************************************************************/ -static PyObject *py_singleton_candidate_richcompare(PyObject *a, PyObject *b, int op) +static PyObject *py_singleton_candidate_is_read_only(PyObject *self, void *closure) { - PyObject *result; /* Bilan à retourner */ - int ret; /* Bilan de lecture des args. */ - GSingletonCandidate *cand_a; /* Premier élément à traiter */ - GSingletonCandidate *cand_b; /* Second élément à traiter */ - gboolean status; /* Résultat d'une comparaison */ - - if (op != Py_EQ) - { - result = Py_NotImplemented; - goto cmp_done; - } - - ret = PyObject_IsInstance(b, (PyObject *)get_python_singleton_candidate_type()); - if (!ret) - { - result = Py_NotImplemented; - goto cmp_done; - } - - cand_a = G_SINGLETON_CANDIDATE(pygobject_get(a)); - cand_b = G_SINGLETON_CANDIDATE(pygobject_get(b)); + PyObject *result; /* Valeur à retourner */ + GSingletonCandidate *candidate; /* Mécanismes natifs */ + bool state; /* Etat de l'objet courant */ - status = g_singleton_candidate_is_equal(cand_a, cand_b); +#define SINGLETON_CANDIDATE_READ_ONLY_ATTRIB PYTHON_IS_DEF_FULL \ +( \ + read_only, py_singleton_candidate, \ + "Boolean state of the object: *True* if all its properties are" \ + " frozen, *False* if the object can be modified." \ +) - result = (status ? Py_True : Py_False); + candidate = G_SINGLETON_CANDIDATE(pygobject_get(self)); - cmp_done: + state = g_singleton_candidate_is_read_only(candidate); + result = state ? Py_True : Py_False; Py_INCREF(result); + CLEAN_RESULT_IF_RAISED_EXCEPTION(result); + return result; } @@ -589,14 +598,15 @@ PyTypeObject *get_python_singleton_candidate_type(void) static PyMethodDef py_singleton_candidate_methods[] = { SINGLETON_CANDIDATE_LIST_INNER_INSTANCES_WRAPPER, SINGLETON_CANDIDATE_UPDATE_INNER_INSTANCES_WRAPPER, - SINGLETON_CANDIDATE_HASH_WRAPPER, - SINGLETON_CANDIDATE_EQ_WRAPPER, - SINGLETON_CANDIDATE_HASH_METHOD, + SINGLETON_CANDIDATE_MARK_AS_READ_ONLY_WRAPPER, + SINGLETON_CANDIDATE_IS_READ_ONLY_WRAPPER, + SINGLETON_CANDIDATE_DUP_WRAPPER, + SINGLETON_CANDIDATE_DUP_METHOD, { NULL } }; static PyGetSetDef py_singleton_candidate_getseters[] = { - SINGLETON_CANDIDATE_INNER_INSTANCES_ATTRIB, + SINGLETON_CANDIDATE_READ_ONLY_ATTRIB, { NULL } }; @@ -611,8 +621,6 @@ PyTypeObject *get_python_singleton_candidate_type(void) .tp_doc = SINGLETON_CANDIDATE_DOC, - .tp_richcompare = py_singleton_candidate_richcompare, - .tp_methods = py_singleton_candidate_methods, .tp_getset = py_singleton_candidate_getseters diff --git a/plugins/pychrysalide/glibext/strbuilder.c b/plugins/pychrysalide/glibext/strbuilder.c index 482f7df..a6de0f0 100644 --- a/plugins/pychrysalide/glibext/strbuilder.c +++ b/plugins/pychrysalide/glibext/strbuilder.c @@ -2,7 +2,7 @@ /* Chrysalide - Outil d'analyse de fichiers binaires * strbuilder.c - équivalent Python du fichier "glibext/strbuilder.c" * - * Copyright (C) 2021 Cyrille Bagard + * Copyright (C) 2025 Cyrille Bagard * * This file is part of Chrysalide. * @@ -37,11 +37,22 @@ +/* ------------------------ GLUE POUR CREATION DEPUIS PYTHON ------------------------ */ + + /* Procède à l'initialisation de l'interface d'exportation. */ static void py_string_builder_interface_init(GStringBuilderInterface *, gpointer *); /* Exporte une chaîne de caractères à partir d'un objet. */ -bool py_string_builder_to_string_wrapper(const GStringBuilder *, unsigned int, sized_binary_t *); +static bool py_string_builder_to_string_wrapper(const GStringBuilder *, unsigned int, sized_binary_t *); + + + +/* ------------------------- CONNEXION AVEC L'API DE PYTHON ------------------------- */ + + +/* Transmet la description d'un objet définie par son parent. */ +static PyObject *py_string_builder_parent_to_string(PyObject *, PyObject *); /* Exporte une chaîne de caractères à partir d'un objet. */ static PyObject *py_string_builder_to_string(PyObject *, PyObject *); @@ -51,6 +62,11 @@ static PyObject *py_string_builder_str(PyObject *); +/* ---------------------------------------------------------------------------------- */ +/* GLUE POUR CREATION DEPUIS PYTHON */ +/* ---------------------------------------------------------------------------------- */ + + /****************************************************************************** * * * Paramètres : iface = interface GLib à initialiser. * @@ -98,9 +114,9 @@ static void py_string_builder_interface_init(GStringBuilderInterface *iface, gpo * * ******************************************************************************/ -bool py_string_builder_to_string_wrapper(const GStringBuilder *builder, unsigned int flags, sized_binary_t *out) +static bool py_string_builder_to_string_wrapper(const GStringBuilder *builder, unsigned int flags, sized_binary_t *out) { - bool result; /* Bilan à retourner */ + bool result; /* Bilan à retourner */ PyGILState_STATE gstate; /* Sauvegarde d'environnement */ PyObject *pyobj; /* Objet Python concerné */ PyObject *args; /* Arguments pour l'appel */ @@ -119,7 +135,10 @@ bool py_string_builder_to_string_wrapper(const GStringBuilder *builder, unsigned "The optional *flags* argument define hints for the operation" \ " (for instance the Intel or AT&T flavor for x86 assembly).\n" \ "\n" \ - "The result has to be a string or *None* in case of error." \ + "The result has to be a string." \ + "\n" \ + "A *TypeError* exception is raised if the return value is not" \ + " a string." \ ) result = false; @@ -128,38 +147,37 @@ bool py_string_builder_to_string_wrapper(const GStringBuilder *builder, unsigned pyobj = pygobject_new(G_OBJECT(builder)); - if (has_python_method(pyobj, "_to_string")) - { - args = PyTuple_New(1); - PyTuple_SetItem(args, 0, PyLong_FromUnsignedLong(flags)); + args = PyTuple_New(1); + PyTuple_SetItem(args, 0, PyLong_FromUnsignedLong(flags)); - pyret = run_python_method(pyobj, "_to_string", args); + pyret = run_python_method(pyobj, "_to_string", args); - if (pyret != NULL && pyret != Py_None) + if (pyret != NULL) + { + if (PyUnicode_Check(pyret)) { - if (PyUnicode_Check(pyret)) - { - utf8 = PyUnicode_AsUTF8AndSize(pyret, &size); - - if (utf8 != NULL) - { - assert(size >= 0); + utf8 = PyUnicode_AsUTF8AndSize(pyret, &size); - add_to_sized_binary(out, utf8, size); - result = true; + if (utf8 != NULL) + { + assert(size >= 0); - } + add_to_sized_binary(out, utf8, size); + result = true; } } - Py_XDECREF(pyret); - - Py_DECREF(args); + if (!result) + PyErr_SetString(PyExc_TypeError, _("object description has to get provided as an UTF-8 string value")); } + Py_XDECREF(pyret); + + Py_DECREF(args); + Py_DECREF(pyobj); PyGILState_Release(gstate); @@ -169,6 +187,108 @@ bool py_string_builder_to_string_wrapper(const GStringBuilder *builder, unsigned } + +/* ---------------------------------------------------------------------------------- */ +/* CONNEXION AVEC L'API DE PYTHON */ +/* ---------------------------------------------------------------------------------- */ + + +/****************************************************************************** +* * +* Paramètres : self = objet dont l'instance se veut unique. * +* args = adresse non utilisée ici. * +* * +* Description : Transmet la description d'un objet définie par son parent. * +* * +* Retour : Présentation de l'élément construite. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static PyObject *py_string_builder_parent_to_string(PyObject *self, PyObject *args) +{ + PyObject *result; /* Valeur à retourner */ + unsigned int flags; /* Eventuelles indications */ + int ret; /* Bilan de lecture des args. */ + GStringBuilder *builder; /* Mécanismes natifs */ + GStringBuilderInterface *iface; /* Interface utilisée */ + GStringBuilderInterface *parent_iface; /* Interface parente */ + sized_binary_t out; /* Description construite */ + bool status; /* Bilan de l'opération */ + +#define STRING_BUILDER_PARENT_TO_STRING_METHOD PYTHON_METHOD_DEF \ +( \ + parent_to_string, "$self, /, flags=0", \ + METH_VARARGS, py_string_builder, \ + "Provide a string representation defined by the interface" \ + " implementation from the object native parent.\n" \ + "\n" \ + "The result is a string.\n" \ + "\n" \ + "A *TypeError* exception is raised if the object parent does" \ + " not implement the pychrysalide.glibext.StringBuilder" \ + " interface." \ + "\n" \ + "A *RuntimeError* exception is raised if the direct parent type"\ + " of the object has not a native implementation. For Python" \ + " implementations, the super()._to_string() function has to be" \ + " used instead.\n" \ + "\n" \ + "A *BufferError* exception is raised if the description has" \ + " not been able to get created." \ +) + + if (!check_for_native_parent(self)) + return NULL; + + flags = 0; + + ret = PyArg_ParseTuple(args, "|I", &flags); + if (!ret) return NULL; + + builder = G_STRING_BUILDER(pygobject_get(self)); + + iface = G_STRING_BUILDER_GET_IFACE(builder); + + parent_iface = g_type_interface_peek_parent(iface); + + if (parent_iface == NULL) + { + PyErr_SetString(PyExc_TypeError, _("object parent does not implement the StringBuilder interface")); + + result = NULL; + + } + else + { + init_sized_binary(&out); + + status = parent_iface->to_string(builder, flags, &out); + + if (status) + result = PyUnicode_FromStringAndSize(out.data, out.size); + + else + { + result = NULL; + + if (PyErr_Occurred() == NULL) + PyErr_SetString(PyExc_BufferError, _("unable to create a description")); + + } + + exit_sized_binary(&out); + + CLEAN_RESULT_IF_RAISED_EXCEPTION(result); + + } + + return result; + +} + + /****************************************************************************** * * * Paramètres : self = objet manipulé ici. * @@ -176,7 +296,7 @@ bool py_string_builder_to_string_wrapper(const GStringBuilder *builder, unsigned * * * Description : Exporte une chaîne de caractères à partir d'un objet. * * * -* Retour : Présentation de l'élément construite ou None. * +* Retour : Présentation de l'élément construite. * * * * Remarques : - * * * @@ -198,11 +318,13 @@ static PyObject *py_string_builder_to_string(PyObject *self, PyObject *args) "Provide a string representation for the object which is used" \ " as the default implementation of the __repr__() method.\n" \ "\n" \ - "\n" \ "The optional *flags* argument define hints for the operation" \ " (for instance the Intel or AT&T flavor for x86 assembly).\n" \ "\n" \ - "The result is a string or *None* in case of error." \ + "The result is a string.\n" \ + "\n" \ + "A *BufferError* exception is raised if the description has" \ + " not been able to get created." \ ) flags = 0; @@ -221,12 +343,17 @@ static PyObject *py_string_builder_to_string(PyObject *self, PyObject *args) else { - result = Py_None; - Py_INCREF(result); + result = NULL; + + if (PyErr_Occurred() == NULL) + PyErr_SetString(PyExc_BufferError, _("unable to create a description")); + } exit_sized_binary(&out); + CLEAN_RESULT_IF_RAISED_EXCEPTION(result); + return result; } @@ -238,13 +365,13 @@ static PyObject *py_string_builder_to_string(PyObject *self, PyObject *args) * * * Description : Fournit une représentation de l'objet exportable. * * * -* Retour : Présentation de l'élément construite ou None. * +* Retour : Présentation de l'élément construite. * * * * Remarques : - * * * ******************************************************************************/ -PyObject *py_string_builder_str(PyObject *self) +static PyObject *py_string_builder_str(PyObject *self) { PyObject *result; /* Emplacement à retourner */ GStringBuilder *builder; /* Mécanismes natifs */ @@ -262,12 +389,17 @@ PyObject *py_string_builder_str(PyObject *self) else { - result = Py_None; - Py_INCREF(result); + result = NULL; + + if (PyErr_Occurred() == NULL) + PyErr_SetString(PyExc_BufferError, _("unable to create a description")); + } exit_sized_binary(&out); + CLEAN_RESULT_IF_RAISED_EXCEPTION(result); + return result; } @@ -289,6 +421,7 @@ PyTypeObject *get_python_string_builder_type(void) { static PyMethodDef py_string_builder_methods[] = { STRING_BUILDER_TO_STRING_WRAPPER, + STRING_BUILDER_PARENT_TO_STRING_METHOD, STRING_BUILDER_TO_STRING_METHOD, { NULL } }; diff --git a/plugins/pychrysalide/glibext/strbuilder.h b/plugins/pychrysalide/glibext/strbuilder.h index d6aae20..1881cae 100644 --- a/plugins/pychrysalide/glibext/strbuilder.h +++ b/plugins/pychrysalide/glibext/strbuilder.h @@ -2,7 +2,7 @@ /* Chrysalide - Outil d'analyse de fichiers binaires * strbuilder.h - prototypes pour l'équivalent Python du fichier "glibext/strbuilder.h" * - * Copyright (C) 2021 Cyrille Bagard + * Copyright (C) 2025 Cyrille Bagard * * This file is part of Chrysalide. * diff --git a/plugins/pychrysalide/helpers.c b/plugins/pychrysalide/helpers.c index c31d9f1..0c84278 100644 --- a/plugins/pychrysalide/helpers.c +++ b/plugins/pychrysalide/helpers.c @@ -1122,6 +1122,60 @@ int forward_pygobjet_init(PyObject *self) /****************************************************************************** * * +* Paramètres : type = type Python à ausculter. * +* * +* Description : Détermine si un type Python est implémenté en C ou non. * +* * +* Retour : Bilan de l'analyse. * +* * +* Remarques : - * +* * +******************************************************************************/ + +bool pytype_has_native_implementation(const PyTypeObject *type) +{ + bool result; /* Bilan à retourner */ + GType gtype; /* Type associé à la classe */ + + static GQuark pygobject_custom_key = 0; /* Clef d'accès direct */ + + /** + * Dans les sources de PyGObject, la fonction pyg_type_register() de + * gi/gimodule.c, appelée depuis gi/types.py, contient la bribe de code + * suivante : + * + * // Mark this GType as a custom python type + * g_type_set_qdata(instance_type, pygobject_custom_key, + * GINT_TO_POINTER (1)); + * + * La fonction pyi_object_register_types() de gi/pygobject-object.c indique + * la clef associée au Quark : + * + * pygobject_custom_key = g_quark_from_static_string("PyGObject::custom"); + * + * Enfin, une fonction inspirante est codée dans le fichier gi/pygi-type.c : + * + * gboolean pyg_gtype_is_custom(GType gtype) + * { + * return g_type_get_qdata (gtype, pygobject_custom_key) != NULL; + * } + * + */ + + if (pygobject_custom_key == 0) + pygobject_custom_key = g_quark_from_static_string("PyGObject::custom"); + + gtype = pyg_type_from_object((PyObject *)type); + + result = (g_type_get_qdata(gtype, pygobject_custom_key) == NULL); + + return result; + +} + + +/****************************************************************************** +* * * Paramètres : arg = argument quelconque à tenter de convertir. * * dst = destination des valeurs récupérées en cas de succès. * * * diff --git a/plugins/pychrysalide/helpers.h b/plugins/pychrysalide/helpers.h index 2808bf1..0aaf976 100644 --- a/plugins/pychrysalide/helpers.h +++ b/plugins/pychrysalide/helpers.h @@ -34,6 +34,9 @@ #endif +#include <i18n.h> + + /* ---------------------- ACCELERATEURS POUR PYTHON UNIQUEMENT ---------------------- */ @@ -225,6 +228,41 @@ PyTypeObject *define_python_dynamic_type(const PyTypeObject *); /** + * Prise en compte d'éventuelles exceptions levées dans les implémentations. + * + * Par exemple : + * - du code Python exécute une fonction implémentée en C ; + * - cette dernière fait appel à un Wrapper C qui sollicite du code + * d'implémentation Python. + * + * Cette seconde étape peut lever une exception (impletation manquante ou + * implémentation levant une exception. + * + * Les codes C des étapes 1 et 2 ne dispose pas de mécanismes pour transmettre + * le détail des éventuelles exceptions, mais Python le mémorise. + */ + +#define UPDATE_RESULT_IF_RAISED_EXCEPTION(val) \ + do \ + { \ + if (PyErr_Occurred() != NULL) \ + result = val; \ + } \ + while (0) + +#define CLEAN_RESULT_IF_RAISED_EXCEPTION(val) \ + do \ + { \ + if (PyErr_Occurred() != NULL) \ + { \ + Py_XDECREF(result); \ + result = NULL; \ + } \ + } \ + while (0) + + +/** * pygobject_new() prend en compte les références flottantes au moment de la * construction d'un objet Python. * @@ -250,11 +288,23 @@ bool register_class_for_pygobject(PyObject *, GType, PyTypeObject *); bool register_interface_for_pygobject(PyObject *, GType, PyTypeObject *, const GInterfaceInfo *); /* Enregistre un type Python dérivant d'un type GLib dynamique. */ -bool register_class_for_dynamic_pygobject(GType, PyTypeObject *); +bool register_class_for_dynamic_pygobject(GType, PyTypeObject *); // REMME /* Fait suivre à la partie GObject une initialisation nouvelle. */ int forward_pygobjet_init(PyObject *); +/* Détermine si un type Python est implémenté en C ou non. */ +bool pytype_has_native_implementation(const PyTypeObject *); + +#define check_for_native_parent(obj) \ + ({ \ + bool __result; \ + __result = pytype_has_native_implementation((obj)->ob_type->tp_base); \ + if (!__result) \ + PyErr_SetString(PyExc_RuntimeError, _("object parent is not a native type")); \ + __result; \ + }) + /* Tente de convertir en valeur GType. */ int convert_to_gtype(PyObject *, void *); diff --git a/src/glibext/Makefile.am b/src/glibext/Makefile.am index 738f5b3..2e9e86d 100644 --- a/src/glibext/Makefile.am +++ b/src/glibext/Makefile.am @@ -4,8 +4,6 @@ BUILT_SOURCES = chrysamarshal.h chrysamarshal.c resources.h resources.c noinst_LTLIBRARIES = libglibext.la libglibextui.la # libglibext_la_SOURCES = \ -# comparison-int.h \ -# comparison.h comparison.c \ # configuration-int.h \ # configuration.h configuration.c \ # gbinarycursor.h gbinarycursor.c \ @@ -40,6 +38,10 @@ noinst_LTLIBRARIES = libglibext.la libglibextui.la libglibext_la_SOURCES = \ chrysamarshal.h chrysamarshal.c \ + comparable-int.h \ + comparable.h comparable.c \ + hashable-int.h \ + hashable.h hashable.c \ helpers.h \ objhole-int.h \ objhole.h objhole.c \ diff --git a/src/glibext/comparable-int.h b/src/glibext/comparable-int.h index 446f25d..18972c2 100644 --- a/src/glibext/comparable-int.h +++ b/src/glibext/comparable-int.h @@ -1,8 +1,8 @@ /* Chrysalide - Outil d'analyse de fichiers binaires - * comparison-int.h - définitions internes propres aux opérations de comparaison d'objets + * comparable-int.h - définitions internes propres aux opérations de comparaison d'objets * - * Copyright (C) 2022 Cyrille Bagard + * Copyright (C) 2022-2025 Cyrille Bagard * * This file is part of Chrysalide. * @@ -21,38 +21,27 @@ */ -#ifndef _GLIBEXT_COMPARISON_INT_H -#define _GLIBEXT_COMPARISON_INT_H +#ifndef _GLIBEXT_COMPARABLE_INT_H +#define _GLIBEXT_COMPARABLE_INT_H -#include "comparison.h" +#include "comparable.h" -/* Réalise une comparaison entre objets selon un critère précis. */ -typedef bool (* compare_rich_fc) (const GComparableItem *, const GComparableItem *, RichCmpOperation, bool *); +/* Réalise une comparaison étendue entre objets. */ +typedef int (* compare_object_fc) (const GComparableObject *, const GComparableObject *); -/* Instance d'élément comparable (interface) */ -struct _GComparableItemIface +/* Instance d'objet comparable (interface) */ +struct _GComparableObjectInterface { GTypeInterface base_iface; /* A laisser en premier */ - compare_rich_fc cmp_rich; /* Comparaison de façon précise*/ + compare_object_fc compare; /* Comparaison étendue */ }; -/* Redéfinition */ -typedef GComparableItemIface GComparableItemInterface; - -/* Réalise une comparaison riche entre valeurs entière. */ -bool compare_rich_integer_values_signed(long long, long long, RichCmpOperation); - -/* Réalise une comparaison riche entre valeurs entière. */ -bool compare_rich_integer_values_unsigned(unsigned long long, unsigned long long, RichCmpOperation); - - - -#endif /* _GLIBEXT_COMPARISON_INT_H */ +#endif /* _GLIBEXT_COMPARABLE_INT_H */ diff --git a/src/glibext/comparable.c b/src/glibext/comparable.c index 8ce6941..95e7de7 100644 --- a/src/glibext/comparable.c +++ b/src/glibext/comparable.c @@ -1,8 +1,8 @@ /* Chrysalide - Outil d'analyse de fichiers binaires - * comparison.c - opérations de comparaison d'objets + * comparable.c - opérations de comparaison d'objets * - * Copyright (C) 2022 Cyrille Bagard + * Copyright (C) 2022-2025 Cyrille Bagard * * This file is part of Chrysalide. * @@ -21,23 +21,20 @@ */ -#include "comparison.h" +#include "comparable.h" -#include <assert.h> - - -#include "comparison-int.h" +#include "comparable-int.h" /* Procède à l'initialisation de l'interface de comparaison. */ -static void g_comparable_item_default_init(GComparableItemInterface *); +static void g_comparable_object_default_init(GComparableObjectInterface *); /* Détermine le type d'une interface pour un objet comparable. */ -G_DEFINE_INTERFACE(GComparableItem, g_comparable_item, G_TYPE_OBJECT) +G_DEFINE_INTERFACE(GComparableObject, g_comparable_object, G_TYPE_OBJECT) /****************************************************************************** @@ -52,35 +49,34 @@ G_DEFINE_INTERFACE(GComparableItem, g_comparable_item, G_TYPE_OBJECT) * * ******************************************************************************/ -static void g_comparable_item_default_init(GComparableItemInterface *iface) +static void g_comparable_object_default_init(GComparableObjectInterface *iface) { + iface->compare = NULL; } /****************************************************************************** * * -* Paramètres : item = premier objet à consulter pour une comparaison. * +* Paramètres : object = premier objet à consulter pour une comparaison. * * other = second objet à consulter pour une comparaison. * -* op = opération de comparaison à réaliser. * -* status = bilan des opérations de comparaison. [OUT] * * * -* Description : Réalise une comparaison entre objets selon un critère précis.* +* Description : Réalise une comparaison étendue entre objets. * * * -* Retour : true si la comparaison a pu être effectuée, false sinon. * +* Retour : Bilan de la comparaison. * * * * Remarques : - * * * ******************************************************************************/ -bool g_comparable_item_compare_rich(const GComparableItem *item, const GComparableItem *other, RichCmpOperation op, bool *status) +int g_comparable_object_compare(const GComparableObject *object, const GComparableObject *other) { - bool result; /* Etat à retourner */ - GComparableItemIface *iface; /* Interface utilisée */ + int result; /* Bilan à retourner */ + GComparableObjectInterface *iface; /* Interface utilisée */ - iface = G_COMPARABLE_ITEM_GET_IFACE(item); + iface = G_COMPARABLE_OBJECT_GET_IFACE(object); - result = iface->cmp_rich(item, other, op, status); + result = iface->compare(object, other); return result; @@ -89,110 +85,25 @@ bool g_comparable_item_compare_rich(const GComparableItem *item, const GComparab /****************************************************************************** * * -* Paramètres : a = premier élément à consulter pour une comparaison. * -* b = second objet à consulter pour une comparaison. * -* op = opération de comparaison à réaliser. * -* * -* Description : Réalise une comparaison riche entre valeurs entière. * -* * -* Retour : Bilan des opérations de comparaison. * -* * -* Remarques : - * -* * -******************************************************************************/ - -bool compare_rich_integer_values_signed(long long a, long long b, RichCmpOperation op) -{ - bool result; /* Bilan à retourner */ - - switch (op) - { - case RCO_LT: - result = (a < b); - break; - - case RCO_LE: - result = (a <= b); - break; - - case RCO_EQ: - result = (a == b); - break; - - case RCO_NE: - result = (a != b); - break; - - case RCO_GT: - result = (a > b); - break; - - case RCO_GE: - result = (a >= b); - break; - - default: - assert(false); - result = false; - break; - - } - - return result; - -} - - -/****************************************************************************** -* * -* Paramètres : a = premier élément à consulter pour une comparaison. * -* b = second objet à consulter pour une comparaison. * -* op = opération de comparaison à réaliser. * +* Paramètres : object = premier objet à consulter pour une comparaison. * +* other = second objet à consulter pour une comparaison. * * * -* Description : Réalise une comparaison riche entre valeurs entière. * +* Description : Détermine si deux objets sont fonctionnellement identiques. * * * -* Retour : Bilan des opérations de comparaison. * +* Retour : Bilan de la comparaison. * * * * Remarques : - * * * ******************************************************************************/ -bool compare_rich_integer_values_unsigned(unsigned long long a, unsigned long long b, RichCmpOperation op) +gboolean g_comparable_object_is_equal(const GComparableObject *object, const GComparableObject *other) { - bool result; /* Bilan à retourner */ - - switch (op) - { - case RCO_LT: - result = (a < b); - break; - - case RCO_LE: - result = (a <= b); - break; - - case RCO_EQ: - result = (a == b); - break; - - case RCO_NE: - result = (a != b); - break; - - case RCO_GT: - result = (a > b); - break; - - case RCO_GE: - result = (a >= b); - break; + gboolean result; /* Bilan à renvoyer */ + int ret; /* Bilan d'une comparaison */ - default: - assert(false); - result = false; - break; + ret = g_comparable_object_compare(object, other); - } + result = (ret == 0); return result; diff --git a/src/glibext/comparable.h b/src/glibext/comparable.h index 8d43210..7a652ff 100644 --- a/src/glibext/comparable.h +++ b/src/glibext/comparable.h @@ -1,8 +1,8 @@ /* Chrysalide - Outil d'analyse de fichiers binaires - * comparison.h - prototypes pour les opérations de comparaison d'objets + * comparable.h - prototypes pour les opérations de comparaison d'objets * - * Copyright (C) 2022 Cyrille Bagard + * Copyright (C) 2022-2025 Cyrille Bagard * * This file is part of Chrysalide. * @@ -21,60 +21,26 @@ */ -#ifndef _GLIBEXT_COMPARISON_H -#define _GLIBEXT_COMPARISON_H +#ifndef _GLIBEXT_COMPARABLE_H +#define _GLIBEXT_COMPARABLE_H -#include <glib-object.h> -#include <stdbool.h> +#include "helpers.h" -#define G_TYPE_COMPARABLE_ITEM (g_comparable_item_get_type()) -#define G_COMPARABLE_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), G_TYPE_COMPARABLE_ITEM, GComparableItem)) -#define G_COMPARABLE_ITEM_CLASS(vtable) (G_TYPE_CHECK_CLASS_CAST((vtable), G_TYPE_COMPARABLE_ITEM, GComparableItemIface)) -#define G_IS_COMPARABLE_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), G_TYPE_COMPARABLE_ITEM)) -#define G_IS_COMPARABLE_ITEM_CLASS(vtable) (G_TYPE_CHECK_CLASS_TYPE((vtable), G_TYPE_COMPARABLE_ITEM)) -#define G_COMPARABLE_ITEM_GET_IFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE((inst), G_TYPE_COMPARABLE_ITEM, GComparableItemIface)) +#define G_TYPE_COMPARABLE_OBJECT (g_comparable_object_get_type()) +DECLARE_INTERFACE(GComparableObject, g_comparable_object, G, COMPARABLE_OBJECT); -/* Instance d'élément comparable (coquille vide) */ -typedef struct _GComparableItem GComparableItem; -/* Instance d'élément comparable (interface) */ -typedef struct _GComparableItemIface GComparableItemIface; +/* Réalise une comparaison étendue entre objets. */ +int g_comparable_object_compare(const GComparableObject *, const GComparableObject *); -/* Modes de comparaison */ -typedef enum _RichCmpOperation -{ - RCO_LT, /* Equivalent de '<' */ - RCO_LE, /* Equivalent de '<=' */ - RCO_EQ, /* Equivalent de '==' */ - RCO_NE, /* Equivalent de '!=' */ - RCO_GT, /* Equivalent de '>' */ - RCO_GE, /* Equivalent de '>°' */ +/* Détermine si deux objets sont fonctionnellement identiques. */ +gboolean g_comparable_object_is_equal(const GComparableObject *, const GComparableObject *); -} RichCmpOperation; -/* Détermination d'un besoin de comparaison supplémentaire */ -#define STATUS_NOT_EQUAL(_s, _o) \ - ({ \ - bool __result; \ - if (_o == RCO_LE || _o == RCO_EQ || _o == RCO_GE) \ - __result = !_s; \ - else \ - __result = _s; \ - __result; \ - }) - -/* Détermine le type d'une interface pour un objet comparable. */ -GType g_comparable_item_get_type(void) G_GNUC_CONST; - -/* Réalise une comparaison entre objets selon un critère précis. */ -bool g_comparable_item_compare_rich(const GComparableItem *, const GComparableItem *, RichCmpOperation, bool *); - - - -#endif /* _GLIBEXT_COMPARISON_H */ +#endif /* _GLIBEXT_COMPARABLE_H */ diff --git a/src/glibext/hashable-int.h b/src/glibext/hashable-int.h new file mode 100644 index 0000000..f8a85e1 --- /dev/null +++ b/src/glibext/hashable-int.h @@ -0,0 +1,47 @@ + +/* Chrysalide - Outil d'analyse de fichiers binaires + * hashable-int.h - définitions internes propres aux calculs de l'empreinte d'un objet + * + * Copyright (C) 2025 Cyrille Bagard + * + * This file is part of Chrysalide. + * + * Chrysalide is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Chrysalide 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Chrysalide. If not, see <http://www.gnu.org/licenses/>. + */ + + +#ifndef _GLIBEXT_HASHABLE_INT_H +#define _GLIBEXT_HASHABLE_INT_H + + +#include "hashable.h" + + + +/* Calcule l'empreinte sur 32 bits d'un objet. */ +typedef guint (* hash_object_fc) (const GHashableObject *); + + +/* Instance d'objet visant à être unique (interface) */ +struct _GHashableObjectInterface +{ + GTypeInterface base_iface; /* A laisser en premier */ + + hash_object_fc hash; /* Réduction en valeur */ + +}; + + + +#endif /* _GLIBEXT_HASHABLE_INT_H */ diff --git a/src/glibext/hashable.c b/src/glibext/hashable.c new file mode 100644 index 0000000..f988a90 --- /dev/null +++ b/src/glibext/hashable.c @@ -0,0 +1,82 @@ + +/* Chrysalide - Outil d'analyse de fichiers binaires + * hashable.c - calculs de l'empreinte d'un objet + * + * Copyright (C) 2025 Cyrille Bagard + * + * This file is part of Chrysalide. + * + * Chrysalide is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Chrysalide 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Foobar. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "hashable.h" + + +#include "hashable-int.h" + + + +/* Procède à l'initialisation de l'interface de détermination. */ +static void g_hashable_object_default_init(GHashableObjectInterface *); + + + +/* Détermine le type d'une interface pour la réduction d'un objet à une valeur. */ +G_DEFINE_INTERFACE(GHashableObject, g_hashable_object, G_TYPE_OBJECT) + + +/****************************************************************************** +* * +* Paramètres : iface = interface GLib à initialiser. * +* * +* Description : Procède à l'initialisation de l'interface de détermination. * +* * +* Retour : - * +* * +* Remarques : - * +* * +******************************************************************************/ + +static void g_hashable_object_default_init(GHashableObjectInterface *iface) +{ + iface->hash = NULL; + +} + + +/****************************************************************************** +* * +* Paramètres : object = objet dont l'instance est à consulter. * +* * +* Description : Calcule l'empreinte sur 32 bits d'un objet. * +* * +* Retour : Valeur de représentation, unique pour l'objet ou non. * +* * +* Remarques : - * +* * +******************************************************************************/ + +guint g_hashable_object_hash(const GHashableObject *object) +{ + guint result; /* Valeur à retourner */ + GHashableObjectInterface *iface; /* Interface utilisée */ + + iface = G_HASHABLE_OBJECT_GET_IFACE(object); + + result = iface->hash(object); + + return result; + +} diff --git a/src/glibext/hashable.h b/src/glibext/hashable.h new file mode 100644 index 0000000..165c744 --- /dev/null +++ b/src/glibext/hashable.h @@ -0,0 +1,42 @@ + +/* Chrysalide - Outil d'analyse de fichiers binaires + * hashable.h - prototypes pour les calculs de l'empreinte d'un objet + * + * Copyright (C) 2025 Cyrille Bagard + * + * This file is part of Chrysalide. + * + * Chrysalide is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Chrysalide 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Foobar. If not, see <http://www.gnu.org/licenses/>. + */ + + +#ifndef _GLIBEXT_HASHABLE_H +#define _GLIBEXT_HASHABLE_H + + +#include "helpers.h" + + + +#define G_TYPE_HASHABLE_OBJECT (g_hashable_object_get_type()) + +DECLARE_INTERFACE(GHashableObject, g_hashable_object, G, HASHABLE_OBJECT); + + +/* Calcule l'empreinte sur 32 bits d'un objet. */ +guint g_hashable_object_hash(const GHashableObject *); + + + +#endif /* _GLIBEXT_HASHABLE_H */ diff --git a/src/glibext/singleton-int.h b/src/glibext/singleton-int.h index 9212e2e..747e64a 100644 --- a/src/glibext/singleton-int.h +++ b/src/glibext/singleton-int.h @@ -38,11 +38,14 @@ typedef GSingletonCandidate ** (* list_inner_instances_fc) (const GSingletonCand /* Met à jour une liste de candidats embarqués par un candidat. */ typedef void (* update_inner_instances_fc) (GSingletonCandidate *, GSingletonCandidate **, size_t); -/* Fournit l'empreinte d'un candidat à une centralisation. */ -typedef guint (* hash_candidate_fc) (const GSingletonCandidate *); +/* Marque un candidat comme figé. */ +typedef void (* mark_candidate_as_ro_fc) (GSingletonCandidate *); -/* Détermine si deux candidats à l'unicité sont identiques. */ -typedef gboolean (* is_candidate_equal_fc) (const GSingletonCandidate *, const GSingletonCandidate *); +/* Indique si le candidat est figé. */ +typedef bool (* is_candidate_ro_fc) (const GSingletonCandidate *); + +/* Crée une copie modifiable d'un object unique. */ +typedef GSingletonCandidate * (* dup_candidate_fc) (const GSingletonCandidate *); /* Instance d'objet visant à être unique (interface) */ @@ -53,8 +56,10 @@ struct _GSingletonCandidateInterface list_inner_instances_fc list_inner; /* Récupération d'internes */ update_inner_instances_fc update_inner; /* Mise à jour des éléments */ - hash_candidate_fc hash; /* Prise d'empreinte */ - is_candidate_equal_fc is_equal; /* Comparaison */ + mark_candidate_as_ro_fc mark_as_ro; /* Bascule en mode figé */ + is_candidate_ro_fc is_ro; /* Consultation de l'état */ + + dup_candidate_fc dup; /* Création de copie modifiable*/ }; diff --git a/src/glibext/singleton.c b/src/glibext/singleton.c index 65b83e7..4930323 100644 --- a/src/glibext/singleton.c +++ b/src/glibext/singleton.c @@ -26,9 +26,10 @@ #include <assert.h> #include <malloc.h> -#include <stdbool.h> +#include "comparable.h" +#include "hashable.h" #include "singleton-int.h" @@ -39,11 +40,14 @@ /* Procède à l'initialisation de l'interface de rassemblement. */ static void g_singleton_candidate_default_init(GSingletonCandidateInterface *); +/* Fournit une liste de candidats embarqués par un candidat. */ +static GSingletonCandidate **g_singleton_candidate_list_inner_instances(const GSingletonCandidate *, size_t *); + /* Met à jour une liste de candidats embarqués par un candidat. */ static void g_singleton_candidate_update_inner_instances(GSingletonCandidate *, GSingletonCandidate **, size_t); -/* Détermine si deux candidats à l'unicité sont identiques. */ -static gboolean _g_singleton_candidate_is_equal(GSingletonCandidate *, GSingletonCandidate *, GList **); +/* Marque un candidat comme figé. */ +static void g_singleton_candidate_mark_as_read_only(GSingletonCandidate *); @@ -70,7 +74,9 @@ static void g_singleton_factory_finalize(GSingletonFactory *); /* Détermine le type d'une interface pour la constitution d'objets uniques. */ -G_DEFINE_INTERFACE(GSingletonCandidate, g_singleton_candidate, G_TYPE_OBJECT) +G_DEFINE_INTERFACE_WITH_CODE(GSingletonCandidate, g_singleton_candidate, G_TYPE_OBJECT, + g_type_interface_add_prerequisite(g_define_type_id, G_TYPE_HASHABLE_OBJECT); + g_type_interface_add_prerequisite(g_define_type_id, G_TYPE_COMPARABLE_OBJECT)) /****************************************************************************** @@ -87,6 +93,13 @@ G_DEFINE_INTERFACE(GSingletonCandidate, g_singleton_candidate, G_TYPE_OBJECT) static void g_singleton_candidate_default_init(GSingletonCandidateInterface *iface) { + iface->list_inner = NULL; + iface->update_inner = NULL; + + iface->mark_as_ro = NULL; + iface->is_ro = NULL; + + iface->dup = NULL; } @@ -113,8 +126,11 @@ GSingletonCandidate **g_singleton_candidate_list_inner_instances(const GSingleto if (iface->list_inner == NULL) { + assert(iface->update_inner == NULL); + *count = 0; result = NULL; + } else @@ -161,17 +177,16 @@ static void g_singleton_candidate_update_inner_instances(GSingletonCandidate *ca * * * Paramètres : candidate = objet dont l'instance se veut unique. * * * -* Description : Fournit l'empreinte d'un candidat à une centralisation. * +* Description : Marque un candidat comme figé. * * * -* Retour : Empreinte de l'élément représenté. * +* Retour : - * * * * Remarques : - * * * ******************************************************************************/ -guint g_singleton_candidate_hash(GSingletonCandidate *candidate) +static void g_singleton_candidate_mark_as_read_only(GSingletonCandidate *candidate) { - guint result; /* Valeur à retourner */ GSingletonCandidateInterface *iface; /* Interface utilisée */ GSingletonCandidate **children; /* Instances internes */ size_t count; /* Quantité de ces instances */ @@ -179,103 +194,62 @@ guint g_singleton_candidate_hash(GSingletonCandidate *candidate) iface = G_SINGLETON_CANDIDATE_GET_IFACE(candidate); - result = iface->hash(candidate); + iface->mark_as_ro(candidate); children = g_singleton_candidate_list_inner_instances(candidate, &count); for (i = 0; i < count; i++) { - result ^= g_singleton_candidate_hash(children[i]); - unref_object(children[i]); + g_singleton_candidate_mark_as_read_only(children[i]); + unref_object(G_OBJECT(children[i])); } if (children != NULL) free(children); - return result; - } /****************************************************************************** * * * Paramètres : candidate = objet dont l'instance se veut unique. * -* other = second élément à analyser. * -* processed = liste de candidats déjà traités. * * * -* Description : Détermine si deux candidats à l'unicité sont identiques. * +* Description : Indique si le candidat est figé. * * * -* Retour : Bilan de la comparaison. * +* Retour : true si le contenu du candidat ne peut plus être modifié. * * * * Remarques : - * * * ******************************************************************************/ -static gboolean _g_singleton_candidate_is_equal(GSingletonCandidate *candidate, GSingletonCandidate *other, GList **processed) +bool g_singleton_candidate_is_read_only(const GSingletonCandidate *candidate) { - gboolean result; /* Bilan à renvoyer */ - GList *skip; /* Détection de boucle */ + bool result; /* Etat à retourner */ GSingletonCandidateInterface *iface; /* Interface utilisée */ - GSingletonCandidate **children[2]; /* Instances internes */ - size_t count[2]; /* Quantité de ces instances */ +#ifndef NDEBUG + GSingletonCandidate **children; /* Instances internes */ + size_t count; /* Quantité de ces instances */ size_t i; /* Boucle de parcours */ +#endif - skip = g_list_find(processed[0], candidate); - - if (skip != NULL) - result = (g_list_find(processed[1], other) != NULL); - - else - { - iface = G_SINGLETON_CANDIDATE_GET_IFACE(candidate); - - result = iface->is_equal(candidate, other); - - processed[0] = g_list_append(processed[0], candidate); - processed[1] = g_list_append(processed[1], other); - - if (!result) - goto done; - - children[0] = g_singleton_candidate_list_inner_instances(candidate, &count[0]); - children[1] = g_singleton_candidate_list_inner_instances(other, &count[1]); - - if (count[0] != count[1]) - { - for (i = 0; i < count[0]; i++) - g_object_unref(G_OBJECT(children[0][i])); - - for (i = 0; i < count[1]; i++) - g_object_unref(G_OBJECT(children[1][i])); - - } - - else - { - for (i = 0; i < count[0] && result; i++) - { - result = _g_singleton_candidate_is_equal(children[0][i], children[1][i], processed); - g_object_unref(G_OBJECT(children[0][i])); - g_object_unref(G_OBJECT(children[1][i])); - } - - for (; i < count[0]; i++) - { - g_object_unref(G_OBJECT(children[0][i])); - g_object_unref(G_OBJECT(children[1][i])); - } + iface = G_SINGLETON_CANDIDATE_GET_IFACE(candidate); - if (children[0] != NULL) - free(children[0]); + result = iface->is_ro(candidate); - if (children[1] != NULL) - free(children[1]); +#ifndef NDEBUG - } + children = g_singleton_candidate_list_inner_instances(candidate, &count); + for (i = 0; i < count; i++) + { + assert(result == g_singleton_candidate_is_read_only(children[i])); + unref_object(G_OBJECT(children[i])); } - done: + if (children != NULL) + free(children); + +#endif return result; @@ -285,31 +259,60 @@ static gboolean _g_singleton_candidate_is_equal(GSingletonCandidate *candidate, /****************************************************************************** * * * Paramètres : candidate = objet dont l'instance se veut unique. * -* other = second élément à analyser. * * * -* Description : Détermine si deux candidats à l'unicité sont identiques. * +* Description : Crée une copie modifiable d'un object unique. * * * -* Retour : Bilan de la comparaison. * +* Retour : Nouvelle instance mise en place. * * * * Remarques : - * * * ******************************************************************************/ -gboolean g_singleton_candidate_is_equal(GSingletonCandidate *candidate, GSingletonCandidate *other) +GSingletonCandidate *g_singleton_candidate_dup(const GSingletonCandidate *candidate) { - gboolean result; /* Bilan à renvoyer */ - GList *processed[2]; /* Suivi des traitements */ + GSingletonCandidate *result; /* Instance à retourner */ + GSingletonCandidateInterface *iface; /* Interface utilisée */ + size_t count; /* Quantité d'objets internes */ + GSingletonCandidate **children; /* Liste d'instances internes */ + size_t i; /* Boucle de parcours */ + GSingletonCandidate **new_children; /* Nouvelle liste d'instances */ + + iface = G_SINGLETON_CANDIDATE_GET_IFACE(candidate); + + result = iface->dup(candidate); + + assert(!g_singleton_candidate_is_read_only(result)); + + children = g_singleton_candidate_list_inner_instances(candidate, &count); + + if (count > 0) + { + new_children = malloc(count * sizeof(GSingletonCandidate *)); + + for (i = 0; i < count; i++) + { + new_children[i] = g_singleton_candidate_dup(children[i]); + + assert(!g_singleton_candidate_is_read_only(new_children[i])); + + } + + g_singleton_candidate_update_inner_instances(result, new_children, count); - processed[0] = NULL; - processed[1] = NULL; + for (i = 0; i < count; i++) + { + unref_object(G_OBJECT(new_children[i])); + unref_object(G_OBJECT(children[i])); + } + + free(new_children); - result = _g_singleton_candidate_is_equal(candidate, other, processed); + } - assert(processed[0] != NULL); - assert(processed[1] != NULL); + if (children != NULL) + free(children); - g_list_free(processed[0]); - g_list_free(processed[1]); + assert(G_OBJECT_TYPE(result) == G_OBJECT_TYPE(candidate)); return result; @@ -364,8 +367,8 @@ static void g_singleton_factory_class_init(GSingletonFactoryClass *klass) static void g_singleton_factory_init(GSingletonFactory *factory) { - factory->table = g_hash_table_new_full((GHashFunc)g_singleton_candidate_hash, - (GEqualFunc)g_singleton_candidate_is_equal, + factory->table = g_hash_table_new_full((GHashFunc)g_hashable_object_hash, + (GEqualFunc)g_comparable_object_is_equal, g_object_unref, NULL); g_mutex_init(&factory->access); @@ -524,6 +527,8 @@ GSingletonCandidate *g_singleton_factory_get_instance(GSingletonFactory *factory g_hash_table_add(factory->table, candidate); #endif + g_singleton_candidate_mark_as_read_only(candidate); + result = candidate; } diff --git a/src/glibext/singleton.h b/src/glibext/singleton.h index 36714fa..11afffd 100644 --- a/src/glibext/singleton.h +++ b/src/glibext/singleton.h @@ -25,6 +25,9 @@ #define _GLIBEXT_SINGLETON_H +#include <stdbool.h> + + #include "helpers.h" @@ -37,14 +40,11 @@ DECLARE_INTERFACE(GSingletonCandidate, g_singleton_candidate, G, SINGLETON_CANDIDATE); -/* Fournit une liste de candidats embarqués par un candidat. */ -GSingletonCandidate **g_singleton_candidate_list_inner_instances(const GSingletonCandidate *, size_t *); - -/* Fournit l'empreinte d'un candidat à une centralisation. */ -guint g_singleton_candidate_hash(GSingletonCandidate *); +/* Indique si le candidat est figé. */ +bool g_singleton_candidate_is_read_only(const GSingletonCandidate *); -/* Détermine si deux candidats à l'unicité sont identiques. */ -gboolean g_singleton_candidate_is_equal(GSingletonCandidate *, GSingletonCandidate *); +/* Crée une copie modifiable d'un object unique. */ +GSingletonCandidate *g_singleton_candidate_dup(const GSingletonCandidate *); diff --git a/src/glibext/strbuilder-int.h b/src/glibext/strbuilder-int.h index 97e0f4e..d74e65c 100644 --- a/src/glibext/strbuilder-int.h +++ b/src/glibext/strbuilder-int.h @@ -2,7 +2,7 @@ /* Chrysalide - Outil d'analyse de fichiers binaires * strbuilder-int.h - définitions internes propres aux éléments exportant une chaîne de caractères * - * Copyright (C) 2024 Cyrille Bagard + * Copyright (C) 2025 Cyrille Bagard * * This file is part of Chrysalide. * diff --git a/src/glibext/strbuilder.c b/src/glibext/strbuilder.c index 54a102b..e48674c 100644 --- a/src/glibext/strbuilder.c +++ b/src/glibext/strbuilder.c @@ -2,7 +2,7 @@ /* Chrysalide - Outil d'analyse de fichiers binaires * strbuilder.c - exportation d'une chaîne de caractères depuis un élément * - * Copyright (C) 2024 Cyrille Bagard + * Copyright (C) 2025 Cyrille Bagard * * This file is part of Chrysalide. * @@ -51,6 +51,7 @@ G_DEFINE_INTERFACE(GStringBuilder, g_string_builder, G_TYPE_OBJECT) static void g_string_builder_default_init(GStringBuilderInterface *iface) { + iface->to_string = NULL; } @@ -77,7 +78,11 @@ bool g_string_builder_to_string(const GStringBuilder *builder, unsigned int flag iface = G_STRING_BUILDER_GET_IFACE(builder); - result = iface->to_string(builder, flags, out); + if (iface->to_string == NULL) + result = false; + + else + result = iface->to_string(builder, flags, out); return result; diff --git a/src/glibext/strbuilder.h b/src/glibext/strbuilder.h index d8d0eaa..b6422f7 100644 --- a/src/glibext/strbuilder.h +++ b/src/glibext/strbuilder.h @@ -2,7 +2,7 @@ /* Chrysalide - Outil d'analyse de fichiers binaires * strbuilder.h - prototypes pour l'exportation d'une chaîne de caractères depuis un élément * - * Copyright (C) 2024 Cyrille Bagard + * Copyright (C) 2025 Cyrille Bagard * * This file is part of Chrysalide. * diff --git a/tests/glibext/comparable.py b/tests/glibext/comparable.py new file mode 100644 index 0000000..48291ca --- /dev/null +++ b/tests/glibext/comparable.py @@ -0,0 +1,132 @@ + +import gi + +from chrysacase import ChrysalideTestCase +from gi.repository import GObject +from pychrysalide.glibext import ComparableObject + + +class TestStringBuilder(ChrysalideTestCase): + """Test cases for pychrysalide.glibext.ComparableObject.""" + + + def testComparableObjectCreation(self): + """Create objects with ComparableObject interface.""" + + with self.assertRaisesRegex(NotImplementedError, 'ComparableObject can not be constructed'): + + co = ComparableObject() + + + class NewComparableObjectImplem(gi._gi.GObject, ComparableObject): + pass + + nco = NewComparableObjectImplem() + + self.assertIsNotNone(nco) + + + class NewComparableObjectImplem2(GObject.Object, ComparableObject): + pass + + nco2 = NewComparableObjectImplem() + + self.assertIsNotNone(nco2) + + + def testComparableObjectMethods(self): + """Test the ComparableObject methods.""" + + class BasicComparableObjectImplem(GObject.Object, ComparableObject): + + def __init__(self, val): + super().__init__() + self._val = val + + def _compare(self, other): + if self._val < other._val: + status = -1 + elif self._val > other._val: + status = 1 + else: + status = 0 + return status + + + a = BasicComparableObjectImplem(123) + b = BasicComparableObjectImplem(456) + + self.assertTrue(a <= b) + + # Sans l'action de inherit_interface_slots(), c'est pygobject_richcompare() qui est appelée, + # laquelle compare simplement les adresses des pointeurs + + c = BasicComparableObjectImplem(789) + d = BasicComparableObjectImplem(234) + + self.assertTrue(c > d) + + + def testComparableObjectExceptions(self): + """Raise exceptions from the ComparableObject interface as expected.""" + + + class OtherComparableObject(GObject.Object, ComparableObject): + pass + + other = OtherComparableObject() + + + class BadComparableObjectImplem(GObject.Object, ComparableObject): + pass + + obj = BadComparableObjectImplem() + + + with self.assertRaisesRegex(NotImplementedError, "method implementation is missing for '_compare'"): + + s = obj < other + + + class BadComparableObjectImplem2(GObject.Object, ComparableObject): + + def _compare(self, other): + return 'AAA' + + obj2 = BadComparableObjectImplem2() + + + with self.assertRaisesRegex(TypeError, 'comparison status has to be a signed integer'): + + s = obj2 < other + + + class BadComparableObjectImplem3a(GObject.Object, ComparableObject): + + def _compare(self, other): + return 123 + + class BadComparableObjectImplem3b(BadComparableObjectImplem3a, ComparableObject): + + def _compare(self, other): + return self.parent_compare() + + obj3 = BadComparableObjectImplem3b() + + + with self.assertRaisesRegex(RuntimeError, 'object parent is not a native type'): + + s = obj3 < other + + + class BadComparableObjectImplem4(GObject.Object, ComparableObject): + + def _compare(self, other): + raise Exception('error') + + obj4 = BadComparableObjectImplem4() + + + with self.assertRaisesRegex(Exception, 'error'): + + s = obj4 < other diff --git a/tests/glibext/hashable.py b/tests/glibext/hashable.py new file mode 100644 index 0000000..07f22e3 --- /dev/null +++ b/tests/glibext/hashable.py @@ -0,0 +1,190 @@ + +import gi + +from chrysacase import ChrysalideTestCase +from gi.repository import GObject +from pychrysalide.glibext import HashableObject + + +class TestStringBuilder(ChrysalideTestCase): + """Test cases for pychrysalide.glibext.HashableObject.""" + + + def testHashableObjectCreation(self): + """Create objects with HashableObject interface.""" + + with self.assertRaisesRegex(NotImplementedError, 'HashableObject can not be constructed'): + + ho = HashableObject() + + + class NewHashableObjectImplem(gi._gi.GObject, HashableObject): + pass + + nho = NewHashableObjectImplem() + + self.assertIsNotNone(nho) + + + class NewHashableObjectImplem2(GObject.Object, HashableObject): + pass + + nho2 = NewHashableObjectImplem() + + self.assertIsNotNone(nho2) + + + def testHashableObjectMethods(self): + """Test the HashableObject methods.""" + + class BasicHashableObjectImplem(gi._gi.GObject, HashableObject): + + def __init__(self, val): + super().__init__() + self._val = val + + def _hash(self): + return self._val + + value = 1234 + + ho = BasicHashableObjectImplem(value) + + self.assertEqual(hash(ho), value) + + + class BasicHashableObjectImplem2(GObject.Object, HashableObject): + + def __init__(self, val): + super().__init__() + self._val = val + + def _hash(self): + return self._val + + value = 5678 + + ho2 = BasicHashableObjectImplem2(value) + + self.assertEqual(hash(ho2), value) + + + def testCascadingHashableObjectImplementations(self): + """Request the hash from the object parent for a full computing.""" + + + class CascadingHashableObjectFullImplemA(GObject.Object, HashableObject): + + def _hash(self): + return 1 + + class CascadingHashableObjectFullImplemB(CascadingHashableObjectFullImplemA, HashableObject): + + def _hash(self): + return super()._hash() * 2 + + class CascadingHashableObjectFullImplemC(CascadingHashableObjectFullImplemB, HashableObject): + + def _hash(self): + return super()._hash() * 3 + + + obj_a = CascadingHashableObjectFullImplemA() + + obj_b = CascadingHashableObjectFullImplemB() + + obj_c = CascadingHashableObjectFullImplemC() + + + self.assertEqual(hash(obj_a), 1) + self.assertEqual(hash(obj_b), 2) + self.assertEqual(hash(obj_c), 6) + + + class CascadingHashableObjectPartialImplemA(GObject.Object, HashableObject): + + def _hash(self): + return 1 + + class CascadingHashableObjectPartialImplemB(CascadingHashableObjectPartialImplemA): + pass + + class CascadingHashableObjectPartialImplemC(CascadingHashableObjectPartialImplemB, HashableObject): + + def _hash(self): + return super()._hash() * 3 + + + obj_a = CascadingHashableObjectPartialImplemA() + + obj_b = CascadingHashableObjectPartialImplemB() + + obj_c = CascadingHashableObjectPartialImplemC() + + + self.assertEqual(hash(obj_a), 1) + self.assertEqual(hash(obj_b), 1) + self.assertEqual(hash(obj_c), 3) + + + def testHashableObjectExceptions(self): + """Raise exceptions from the HashableObject interface as expected.""" + + class BadHashableObjectImplem(GObject.Object, HashableObject): + pass + + obj = BadHashableObjectImplem() + + + with self.assertRaisesRegex(NotImplementedError, "method implementation is missing for '_hash'"): + + h = hash(obj) + + + with self.assertRaisesRegex(TypeError, 'object parent does not implement the HashableObject interface'): + + h = obj.parent_hash() + + + class BadHashableObjectImplem2(GObject.Object, HashableObject): + + def _hash(self): + return 'AAA' + + obj2 = BadHashableObjectImplem2() + + + with self.assertRaisesRegex(TypeError, 'computed hash value has to be an unsigned integer'): + + h = hash(obj2) + + + class BadHashableObjectImplem3a(GObject.Object, HashableObject): + + def _hash(self): + return 123 + + class BadHashableObjectImplem3b(BadHashableObjectImplem3a, HashableObject): + + def _hash(self): + return self.parent_hash() + + obj3 = BadHashableObjectImplem3b() + + + with self.assertRaisesRegex(RuntimeError, 'object parent is not a native type'): + + h = hash(obj3) + + + class BadHashableObjectImplem4(GObject.Object, HashableObject): + + def _hash(self): + raise Exception('error') + + obj4 = BadHashableObjectImplem4() + + + with self.assertRaisesRegex(Exception, 'error'): + + h = hash(obj4) diff --git a/tests/glibext/singleton.py b/tests/glibext/singleton.py index 4588ae5..712aece 100644 --- a/tests/glibext/singleton.py +++ b/tests/glibext/singleton.py @@ -1,21 +1,24 @@ +import gi + from chrysacase import ChrysalideTestCase from gi.repository import GObject -from pychrysalide.glibext import SingletonCandidate, SingletonFactory +from pychrysalide.glibext import ComparableObject, HashableObject, SingletonCandidate, SingletonFactory class TestSingleton(ChrysalideTestCase): """Test cases for pychrysalide.glibext.SingletonFactory.""" - def testSingletonCreation(self): - """Create singleton objects.""" + def testSingletonCandidateCreation(self): + """Create objects with SingletonCandidate interface.""" with self.assertRaisesRegex(NotImplementedError, 'SingletonCandidate can not be constructed'): sc = SingletonCandidate() - class NewSingletonImplem(GObject.Object, SingletonCandidate): + + class NewSingletonImplem(gi._gi.GObject, HashableObject, ComparableObject, SingletonCandidate): pass nsi = NewSingletonImplem() @@ -23,121 +26,132 @@ class TestSingleton(ChrysalideTestCase): self.assertIsNotNone(nsi) - def testFactoryCreation(self): - """Create singleton factories.""" + class NewSingletonImplem2(GObject.Object, HashableObject, ComparableObject, SingletonCandidate): + pass + + nsi2 = NewSingletonImplem2() - sf = SingletonFactory() + self.assertIsNotNone(nsi2) - self.assertIsNotNone(sf) - class MyFactory(SingletonFactory): - pass + # def testFactoryCreation(self): + # """Create singleton factories.""" + + # sf = SingletonFactory() + + # self.assertIsNotNone(sf) + + # class MyFactory(SingletonFactory): + # pass + + # msf = MyFactory() + + # self.assertIsNotNone(msf) + - msf = MyFactory() - self.assertIsNotNone(msf) - def testSingletonMethods(self): - """Test the singleton methods.""" + # def testSingletonMethods(self): + # """Test the singleton methods.""" - class IntegerCacheImplem(GObject.Object, SingletonCandidate): + # class IntegerCacheImplem(GObject.Object, SingletonCandidate): - def __init__(self, val): - super().__init__() - self._val = val + # def __init__(self, val): + # super().__init__() + # self._val = val - def _list_inner_instances(self): - return () + # def _list_inner_instances(self): + # return () - def __hash__(self): - return hash('common-key') + # def __hash__(self): + # return hash('common-key') - def __eq__(self, other): - return self._val == other._val + # def __eq__(self, other): + # return self._val == other._val - val_0 = IntegerCacheImplem(0) - val_0_bis = IntegerCacheImplem(0) - val_1 = IntegerCacheImplem(1) + # val_0 = IntegerCacheImplem(0) + # val_0_bis = IntegerCacheImplem(0) + # val_1 = IntegerCacheImplem(1) - self.assertEqual(hash(val_0), hash(val_0_bis)) - self.assertEqual(hash(val_0), hash(val_1)) + # self.assertEqual(hash(val_0), hash(val_0_bis)) + # self.assertEqual(hash(val_0), hash(val_1)) - self.assertEqual(val_0.hash(), val_0_bis.hash()) - self.assertEqual(val_0.hash(), val_1.hash()) + # self.assertEqual(val_0.hash(), val_0_bis.hash()) + # self.assertEqual(val_0.hash(), val_1.hash()) - self.assertTrue(val_0 == val_0_bis) - self.assertFalse(val_0 == val_1) + # self.assertTrue(val_0 == val_0_bis) + # self.assertFalse(val_0 == val_1) - def testSingletonFootprint(self): - """Check for singleton memory footprint.""" + # def testSingletonFootprint(self): + # """Check for singleton memory footprint.""" - sf = SingletonFactory() + # sf = SingletonFactory() - class IntegerCacheImplem(GObject.Object, SingletonCandidate): + # class IntegerCacheImplem(GObject.Object, SingletonCandidate): - def __init__(self, val): - super().__init__() - self._val = val + # def __init__(self, val): + # super().__init__() + # self._val = val - def _list_inner_instances(self): - return () + # def _list_inner_instances(self): + # return () - def __hash__(self): - return hash('common-key') + # def __hash__(self): + # return hash('common-key') - def __eq__(self, other): - return self._val == other._val + # def __eq__(self, other): + # return self._val == other._val - def _set_read_only(self): - pass + # def _set_read_only(self): + # pass - val_0 = IntegerCacheImplem(0) - val_0_bis = IntegerCacheImplem(0) - val_1 = IntegerCacheImplem(1) + # val_0 = IntegerCacheImplem(0) + # val_0_bis = IntegerCacheImplem(0) + # val_1 = IntegerCacheImplem(1) - obj = sf.get_instance(val_0) + # obj = sf.get_instance(val_0) - self.assertTrue(obj is val_0) + # self.assertTrue(obj is val_0) - obj = sf.get_instance(val_0_bis) + # obj = sf.get_instance(val_0_bis) - self.assertTrue(obj is val_0) + # self.assertTrue(obj is val_0) - obj = sf.get_instance(val_1) + # obj = sf.get_instance(val_1) - self.assertTrue(obj is val_1) + # self.assertTrue(obj is val_1) - self.assertEqual(len(obj.inner_instances), 0) + # self.assertEqual(len(obj.inner_instances), 0) - class MasterCacheImplem(GObject.Object, SingletonCandidate): + # class MasterCacheImplem(GObject.Object, SingletonCandidate): - def __init__(self, children): - super().__init__() - self._children = children + # def __init__(self, children): + # super().__init__() + # self._children = children - def _list_inner_instances(self): - return self._children + # def _list_inner_instances(self): + # return self._children - def _update_inner_instances(self, instances): - self._children = instances + # def _update_inner_instances(self, instances): + # self._children = instances - def __hash__(self): - return hash('master-key') + # def __hash__(self): + # return hash('master-key') - def __eq__(self, other): - return False + # def __eq__(self, other): + # return False - def _set_read_only(self): - pass + # def _set_read_only(self): + # pass - master = MasterCacheImplem(( val_0_bis, val_1 )) + # master = MasterCacheImplem(( val_0_bis, val_1 )) - obj = sf.get_instance(master) + # obj = sf.get_instance(master) - self.assertTrue(obj.inner_instances[0] is val_0) + # self.assertTrue(obj.inner_instances[0] is val_0) - self.assertTrue(obj.inner_instances[1] is val_1) + # self.assertTrue(obj.inner_instances[1] is val_1) diff --git a/tests/glibext/strbuilder.py b/tests/glibext/strbuilder.py index ced405e..72fd5c2 100644 --- a/tests/glibext/strbuilder.py +++ b/tests/glibext/strbuilder.py @@ -1,4 +1,6 @@ +import gi + from chrysacase import ChrysalideTestCase from gi.repository import GObject from pychrysalide.glibext import StringBuilder @@ -8,22 +10,31 @@ class TestStringBuilder(ChrysalideTestCase): """Test cases for pychrysalide.glibext.StringBuilder.""" - def testStringBuilderCreation(self): + def ZZZtestStringBuilderCreation(self): """Create objects with StringBuilder interface.""" with self.assertRaisesRegex(NotImplementedError, 'StringBuilder can not be constructed'): - sc = StringBuilder() + sb = StringBuilder() + - class NewStringBuilderImplem(GObject.Object, StringBuilder): + class NewStringBuilderImplem(gi._gi.GObject, StringBuilder): pass - nsi = NewStringBuilderImplem() + nsb = NewStringBuilderImplem() + + self.assertIsNotNone(nsb) + + + class NewStringBuilderImplem2(GObject.Object, StringBuilder): + pass - self.assertIsNotNone(nsi) + nsb2 = NewStringBuilderImplem() + self.assertIsNotNone(nsb2) - def testStringBuilderMethods(self): + + def ZZZtestStringBuilderMethods(self): """Test the StringBuilder methods.""" class BasicStringBuilderImplem(GObject.Object, StringBuilder): @@ -42,3 +53,62 @@ class TestStringBuilder(ChrysalideTestCase): self.assertEqual(sb.to_string(), desc) self.assertEqual(str(sb), desc) self.assertEqual(f'{sb}', desc) + + + def testStringBuilderExceptions(self): + """Raise exceptions from the StringBuilder interface as expected.""" + + + class BadStringBuilderImplem(GObject.Object, StringBuilder): + pass + + obj = BadStringBuilderImplem() + + + with self.assertRaisesRegex(NotImplementedError, "method implementation is missing for '_to_string'"): + + s = str(obj) + + + class BadStringBuilderImplem2(GObject.Object, StringBuilder): + + def _to_string(self, flags=0): + return 0xcafe + + obj2 = BadStringBuilderImplem2() + + + with self.assertRaisesRegex(TypeError, 'object description has to get provided as an UTF-8 string value'): + + s = str(obj2) + + + class BadStringBuilderImplem3a(GObject.Object, StringBuilder): + + def _to_string(self, flags=0): + return 'desc' + + class BadStringBuilderImplem3b(BadStringBuilderImplem3a, StringBuilder): + + def _to_string(self, flags=0): + return self.parent_to_string() + + obj3 = BadStringBuilderImplem3b() + + + with self.assertRaisesRegex(RuntimeError, 'object parent is not a native type'): + + s = str(obj3) + + + class BadStringBuilderImplem4(GObject.Object, StringBuilder): + + def _to_string(self, flags=0): + raise Exception('error') + + obj4 = BadStringBuilderImplem4() + + + with self.assertRaisesRegex(Exception, 'error'): + + s = str(obj4) |