Repository: ninia/jep (language: python)

Issue:
Python isinstance does not work with imported Java classes
e.g.

```python
>>> from ghidra.program.database.data import *
...
>>> type(dt)
<class 'ghidra.program.database.data.FunctionDefinitionDB'>
>>> print(FunctionDefinitionDB)
class ghidra.program.database.data.FunctionDefinitionDB
>>> type(FunctionDefinitionDB)
<class 'jep.PyJClass'>
>>> isinstance(dt, FunctionDefinitionDB)
Traceback (most recent call last):
  File "/Users/pieceofsummer/.ghidra/.ghidra_10.1.5_PUBLIC/Extensions/Ghidrathon-1.0.0/data/python/jepeval.py", line 66, in jepeval
    more_input_needed = _jepeval(line)
  File "/Users/pieceofsummer/.ghidra/.ghidra_10.1.5_PUBLIC/Extensions/Ghidrathon-1.0.0/data/python/jepeval.py", line 49, in _jepeval
    exec(compile(line, "<string>", "single"), globals(), globals())
  File "<string>", line 1, in <module>
TypeError: isinstance() arg 2 must be a type, a tuple of types, or a union
```

I see a workaround based on jep code found [here](https://github.com/ninia/jep/blob/b926ca4af1df7a496ea0a8cb7d030bbe310de898/src/test/python/test_import.py#L58-L59) that requires users to pass `__pytype__` e.g. `FunctionDefinitionDB.__pytype__`. Is there a way to use `isinstance` without a workaround?

See https://github.com/mandiant/Ghidrathon/issues/13 for more details.


Discussion / hints:

Fix patch:
diff --git a/src/main/c/Objects/pyjclass.c b/src/main/c/Objects/pyjclass.c
index 14b69627..aed78b9b 100644
--- a/src/main/c/Objects/pyjclass.c
+++ b/src/main/c/Objects/pyjclass.c
@@ -392,13 +392,58 @@ static int pyjclass_setattro(PyObject *obj, PyObject *name, PyObject *v)
     return pyjfield_set((PyJFieldObject *) cur, pyjobj, v);
 }
 
-
 static PyTypeObject* pyjclass_GetPyType(PyJClassObject* self)
 {
     JNIEnv* env = pyembed_get_env();
     return PyJType_Get(env, self->clazz);
 }
 
+static PyObject* pyjclass_IsInstance(PyJClassObject* self, PyObject* inst)
+{
+    PyObject* type = (PyObject*) pyjclass_GetPyType(self);
+    if (!type) {
+        return NULL;
+    }
+    int result = PyObject_IsInstance(inst, type);
+    Py_DECREF(type);
+    return PyBool_FromLong(result);
+}
+
+static PyObject* pyjclass_IsSubclass(PyJClassObject* self, PyObject* sub)
+{
+    PyObject* type = (PyObject*) pyjclass_GetPyType(self);
+    if (!type) {
+        return NULL;
+    }
+    int result;
+    if (PyJClass_Check(sub)) {
+        PyObject* subtype = (PyObject*) pyjclass_GetPyType((PyJClassObject*) sub);
+        if (!subtype) {
+            Py_DECREF(type);
+            return NULL;
+        }
+        result = PyObject_IsSubclass(subtype, type);
+        Py_DECREF(subtype);
+    } else {
+        result = PyObject_IsSubclass(sub, type);
+    }
+    Py_DECREF(type);
+    return PyBool_FromLong(result);
+}
+
+PyMethodDef pyjclass_methods[] = {
+    {
+        "__instancecheck__",
+        (PyCFunction) pyjclass_IsInstance, METH_O, NULL
+    },
+    {
+        "__subclasscheck__",
+        (PyCFunction) pyjclass_IsSubclass, METH_O, NULL
+    },
+    { NULL, NULL }
+};
+
+
 static PyMemberDef pyjclass_members[] = {
     {"__dict__", T_OBJECT, offsetof(PyJClassObject, attr), READONLY},
     {0}
@@ -437,7 +482,7 @@ PyTypeObject PyJClass_Type = {
     0,                                        /* tp_weaklistoffset */
     0,                                        /* tp_iter */
     0,                                        /* tp_iternext */
-    0,                                        /* tp_methods */
+    pyjclass_methods,                         /* tp_methods */
     pyjclass_members,                         /* tp_members */
     pyjclass_getset,                          /* tp_getset */
     0, // &PyJObject_Type                     /* tp_base */


Test patch:
diff --git a/src/test/python/test_import.py b/src/test/python/test_import.py
index 4734f03e..33ef0246 100644
--- a/src/test/python/test_import.py
+++ b/src/test/python/test_import.py
@@ -57,4 +57,10 @@ def test_type_inheritance(self):
         from java.util import Date
         self.assertTrue(isinstance(Date(), Object.__pytype__))
         self.assertTrue(isinstance(Date(), Serializable.__pytype__))
+        self.assertTrue(issubclass(Date.__pytype__, Object.__pytype__))
+        self.assertTrue(issubclass(Date.__pytype__, Serializable.__pytype__))
+        self.assertTrue(isinstance(Date(), Object))
+        self.assertTrue(isinstance(Date(), Serializable))
+        self.assertTrue(issubclass(Date, Object))
+        self.assertTrue(issubclass(Date, Serializable))