libdrgn/python: add proper type for enumerators

Currently, type members, enumerators, and parameters are all represented
by tuples in the Python bindings. This is awkward to document and
implement. Instead, let's replace these tuples with proper types,
starting with the easiest one, TypeEnumerator. This one still makes
sense to treat as a sequence so that it can be unpacked as (name,
value).
This commit is contained in:
Omar Sandoval 2020-02-11 11:33:22 -08:00
parent bda1426ae9
commit 7c70a1a384
10 changed files with 301 additions and 107 deletions

View File

@ -1136,9 +1136,7 @@ Types
List of enumeration constants of this type, or ``None`` if this is an
incomplete type. This is only present for enumerated types.
Each enumeration constant is a (name, value) tuple.
:vartype: list[tuple(str, int)] or None
:vartype: list[TypeEnumerator] or None
.. attribute:: parameters
@ -1189,6 +1187,29 @@ Types
:rtype: Type
.. class:: TypeEnumerator(name, value)
A ``TypeEnumerator`` represents a constant in an enumerated type.
Its name and value may be accessed as attributes or unpacked:
>>> prog.type('enum pid_type').enumerators[0].name
'PIDTYPE_PID'
>>> name, value = prog.type('enum pid_type').enumerators[0]
>>> value
0
:param str name: Enumerator name.
:param int value: Enumerator value.
.. attribute:: name
:vartype: str
.. attribute:: value
:vartype: int
.. class:: TypeKind
``TypeKind`` is an :class:`enum.Enum` of the different kinds of types.
@ -1401,7 +1422,7 @@ can be used just like types obtained from :meth:`Program.type()`.
:param type: The compatible integer type (:attr:`Type.type`)
:type type: Type or None
:param enumerators: :attr:`Type.enumerators`
:type enumerators: list[tuple] or None
:type enumerators: list[TypeEnumerator] or None
:param qualifiers: :attr:`Type.qualifiers`
:type qualifiers: Qualifiers or None
:rtype: Type

View File

@ -64,6 +64,7 @@ from _drgn import (
StackTrace,
Symbol,
Type,
TypeEnumerator,
TypeKind,
_with_libkdumpfile,
array_type,
@ -110,6 +111,7 @@ __all__ = (
"StackTrace",
"Symbol",
"Type",
"TypeEnumerator",
"TypeKind",
"array_type",
"bool_type",

View File

@ -101,6 +101,12 @@ typedef struct {
struct drgn_symbol *sym;
} Symbol;
typedef struct {
PyObject_HEAD
PyObject *name;
PyObject *value;
} TypeEnumerator;
extern PyObject *Architecture_class;
extern PyObject *FindObjectFlags_class;
extern PyObject *PlatformFlags_class;
@ -119,6 +125,7 @@ extern PyTypeObject Register_type;
extern PyTypeObject StackFrame_type;
extern PyTypeObject StackTrace_type;
extern PyTypeObject Symbol_type;
extern PyTypeObject TypeEnumerator_type;
extern PyObject *MissingDebugInfoError;
extern PyObject *OutOfBoundsError;

View File

@ -216,6 +216,12 @@ DRGNPY_PUBLIC PyMODINIT_FUNC PyInit__drgn(void)
Py_INCREF(&DrgnType_type);
PyModule_AddObject(m, "Type", (PyObject *)&DrgnType_type);
if (PyType_Ready(&TypeEnumerator_type) < 0)
goto err;
Py_INCREF(&TypeEnumerator_type);
PyModule_AddObject(m, "TypeEnumerator",
(PyObject *)&TypeEnumerator_type);
host_platform_obj = Platform_wrap(&drgn_host_platform);
if (!host_platform_obj)
goto err;

View File

@ -336,11 +336,13 @@ static PyObject *DrgnType_get_enumerators(DrgnType *self)
PyObject *item;
if (is_signed) {
item = Py_BuildValue("(sL)", enumerators[i].name,
(long long)enumerators[i].svalue);
item = PyObject_CallFunction((PyObject *)&TypeEnumerator_type,
"sL", enumerators[i].name,
(long long)enumerators[i].svalue);
} else {
item = Py_BuildValue("(sK)", enumerators[i].name,
(unsigned long long)enumerators[i].uvalue);
item = PyObject_CallFunction((PyObject *)&TypeEnumerator_type,
"sK", enumerators[i].name,
(unsigned long long)enumerators[i].uvalue);
}
if (!item) {
Py_DECREF(enumerators_obj);
@ -957,6 +959,107 @@ PyTypeObject DrgnType_type = {
.tp_getset = DrgnType_getset,
};
static TypeEnumerator *TypeEnumerator_new(PyTypeObject *subtype, PyObject *args,
PyObject *kwds)
{
static char *keywords[] = {"name", "value", NULL};
PyObject *name, *value;
TypeEnumerator *enumerator;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!O!:TypeEnumerator",
keywords, &PyUnicode_Type, &name,
&PyLong_Type, &value))
return NULL;
enumerator = (TypeEnumerator *)subtype->tp_alloc(subtype, 0);
if (enumerator) {
Py_INCREF(name);
enumerator->name = name;
Py_INCREF(value);
enumerator->value = value;
}
return enumerator;
}
static void TypeEnumerator_dealloc(TypeEnumerator *self)
{
Py_XDECREF(self->value);
Py_XDECREF(self->name);
Py_TYPE(self)->tp_free((PyObject *)self);
}
static PyObject *TypeEnumerator_repr(TypeEnumerator *self)
{
return PyUnicode_FromFormat("TypeEnumerator(%R, %R)", self->name,
self->value);
}
Py_ssize_t TypeEnumerator_length(PyObject *self)
{
return 2;
}
PyObject *TypeEnumerator_item(TypeEnumerator *self, Py_ssize_t i)
{
switch (i) {
case 0:
Py_INCREF(self->name);
return self->name;
case 1:
Py_INCREF(self->value);
return self->value;
default:
PyErr_SetString(PyExc_IndexError,
"TypeEnumerator index out of range");
return NULL;
}
}
static PyObject *TypeEnumerator_richcompare(TypeEnumerator *self,
TypeEnumerator *other,
int op)
{
int ret;
if ((op != Py_EQ && op != Py_NE) ||
!PyObject_TypeCheck((PyObject *)other, &TypeEnumerator_type))
Py_RETURN_NOTIMPLEMENTED;
ret = PyUnicode_Compare(self->name, other->name);
if (ret == -1 && PyErr_Occurred())
return NULL;
if (ret != 0)
Py_RETURN_RICHCOMPARE(ret, 0, op);
return PyObject_RichCompare(self->value, other->value, op);
}
static PySequenceMethods TypeEnumerator_as_sequence = {
.sq_length = TypeEnumerator_length,
.sq_item = (ssizeargfunc)TypeEnumerator_item,
};
static PyMemberDef TypeEnumerator_members[] = {
{"name", T_OBJECT, offsetof(TypeEnumerator, name), READONLY,
drgn_TypeEnumerator_name_DOC},
{"value", T_OBJECT, offsetof(TypeEnumerator, value), READONLY,
drgn_TypeEnumerator_value_DOC},
{},
};
PyTypeObject TypeEnumerator_type = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "_drgn.TypeEnumerator",
.tp_basicsize = sizeof(TypeEnumerator),
.tp_dealloc = (destructor)TypeEnumerator_dealloc,
.tp_repr = (reprfunc)TypeEnumerator_repr,
.tp_as_sequence = &TypeEnumerator_as_sequence,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = drgn_TypeEnumerator_DOC,
.tp_richcompare = (richcmpfunc)TypeEnumerator_richcompare,
.tp_members = TypeEnumerator_members,
.tp_new = (newfunc)TypeEnumerator_new,
};
DrgnType *void_type(PyObject *self, PyObject *args, PyObject *kwds)
{
static char *keywords[] = { "qualifiers", NULL, };
@ -1429,67 +1532,41 @@ DrgnType *class_type(PyObject *self, PyObject *args, PyObject *kwds)
DRGN_TYPE_CLASS);
}
static int unpack_enumerator(DrgnType *type_obj, PyObject *enumerators_seq,
PyObject *cached_enumerators_obj, size_t i,
bool is_signed)
static int unpack_enumerator(DrgnType *type_obj, PyObject *cached_enumerators_obj,
size_t i, bool is_signed)
{
static const char *msg = "enumerator must be (name, value) sequence";
PyObject *seq, *tuple, *name_obj, *value_obj;
TypeEnumerator *item;
const char *name;
int ret = -1;
seq = PySequence_Fast(PySequence_Fast_GET_ITEM(enumerators_seq, i),
msg);
if (!seq)
item = (TypeEnumerator *)PyTuple_GET_ITEM(cached_enumerators_obj, i);
if (!PyObject_TypeCheck((PyObject *)item, &TypeEnumerator_type)) {
PyErr_SetString(PyExc_TypeError,
"enumerator must be TypeEnumerator");
return -1;
}
name = PyUnicode_AsUTF8(item->name);
if (!name)
return -1;
if (PySequence_Fast_GET_SIZE(seq) != 2) {
PyErr_SetString(PyExc_ValueError, msg);
goto out;
}
name_obj = PySequence_Fast_GET_ITEM(seq, 0);
if (!PyUnicode_Check(name_obj)) {
PyErr_SetString(PyExc_TypeError,
"enumerator name must be string");
goto out;
}
name = PyUnicode_AsUTF8(name_obj);
if (!name)
goto out;
value_obj = PySequence_Fast_GET_ITEM(seq, 1);
if (!PyLong_Check(value_obj)) {
PyErr_SetString(PyExc_TypeError,
"enumerator value must be integer");
goto out;
}
if (is_signed) {
long long svalue;
svalue = PyLong_AsLongLong(value_obj);
svalue = PyLong_AsLongLong(item->value);
if (svalue == -1 && PyErr_Occurred())
goto out;
return -1;
drgn_type_enumerator_init_signed(type_obj->type, i, name,
svalue);
} else {
unsigned long long uvalue;
uvalue = PyLong_AsUnsignedLongLong(value_obj);
uvalue = PyLong_AsUnsignedLongLong(item->value);
if (uvalue == (unsigned long long)-1 && PyErr_Occurred())
goto out;
return -1;
drgn_type_enumerator_init_unsigned(type_obj->type, i, name,
uvalue);
}
tuple = PySequence_Tuple(seq);
if (!tuple)
goto out;
PyTuple_SET_ITEM(cached_enumerators_obj, i, tuple);
ret = 0;
out:
Py_DECREF(seq);
return ret;
return 0;
}
DrgnType *enum_type(PyObject *self, PyObject *args, PyObject *kwds)
@ -1502,7 +1579,6 @@ DrgnType *enum_type(PyObject *self, PyObject *args, PyObject *kwds)
struct drgn_type *compatible_type;
PyObject *enumerators_obj = Py_None;
unsigned char qualifiers = 0;
PyObject *enumerators_seq = NULL;
PyObject *cached_enumerators_obj = NULL;
size_t num_enumerators;
@ -1567,14 +1643,15 @@ DrgnType *enum_type(PyObject *self, PyObject *args, PyObject *kwds)
"enum type must have compatible type");
return NULL;
}
enumerators_seq = PySequence_Fast(enumerators_obj,
"enumerators must be sequence or None");
if (!enumerators_seq)
if (!PySequence_Check(enumerators_obj)) {
PyErr_SetString(PyExc_TypeError,
"enumerators must be sequence or None");
return NULL;
num_enumerators = PySequence_Fast_GET_SIZE(enumerators_seq);
cached_enumerators_obj = PyTuple_New(num_enumerators);
}
cached_enumerators_obj = PySequence_Tuple(enumerators_obj);
if (!cached_enumerators_obj)
goto err;
return NULL;
num_enumerators = PyTuple_GET_SIZE(cached_enumerators_obj);
is_signed = drgn_type_is_signed(compatible_type);
type_obj = DrgnType_new(qualifiers, num_enumerators,
@ -1582,12 +1659,10 @@ DrgnType *enum_type(PyObject *self, PyObject *args, PyObject *kwds)
if (!type_obj)
goto err;
for (i = 0; i < num_enumerators; i++) {
if (unpack_enumerator(type_obj, enumerators_seq,
cached_enumerators_obj, i,
is_signed) == -1)
if (unpack_enumerator(type_obj, cached_enumerators_obj,
i, is_signed) == -1)
goto err;
}
Py_CLEAR(enumerators_seq);
if (_PyDict_SetItemId(type_obj->attr_cache,
&DrgnType_attr_enumerators.id,
@ -1614,7 +1689,6 @@ DrgnType *enum_type(PyObject *self, PyObject *args, PyObject *kwds)
err:
Py_XDECREF(type_obj);
Py_XDECREF(cached_enumerators_obj);
Py_XDECREF(enumerators_seq);
return NULL;
}

View File

@ -10,6 +10,7 @@ from drgn import (
PlatformFlags,
Program,
Type,
TypeEnumerator,
TypeKind,
class_type,
enum_type,
@ -42,7 +43,9 @@ option_type = union_type(
"option", 4, ((int_type("int", 4, True), "i"), (float_type("float", 4), "f"),)
)
color_type = enum_type(
"color", int_type("unsigned int", 4, False), (("RED", 0), ("GREEN", 1), ("BLUE", 2))
"color",
int_type("unsigned int", 4, False),
(TypeEnumerator("RED", 0), TypeEnumerator("GREEN", 1), TypeEnumerator("BLUE", 2)),
)
pid_type = typedef_type("pid_t", int_type("int", 4, True))

View File

@ -7,6 +7,7 @@ from drgn import (
Object,
Program,
Qualifiers,
TypeEnumerator,
array_type,
class_type,
complex_type,
@ -1022,7 +1023,11 @@ class TestTypes(unittest.TestCase):
enum_type(
"color",
int_type("<unknown>", 4, True),
[("RED", 0), ("GREEN", -1), ("BLUE", -2)],
(
TypeEnumerator("RED", 0),
TypeEnumerator("GREEN", -1),
TypeEnumerator("BLUE", -2),
),
),
)
@ -1874,7 +1879,13 @@ class TestObjects(ObjectTestCase):
]
type_ = enum_type(
"color", int_type("int", 4, True), [("RED", 0), ("GREEN", 1), ("BLUE", 2)]
"color",
int_type("int", 4, True),
(
TypeEnumerator("RED", 0),
TypeEnumerator("GREEN", 1),
TypeEnumerator("BLUE", 2),
),
)
prog = dwarf_program(dies)
self.assertEqual(prog["BLUE"], Object(prog, type_, value=2))
@ -1883,7 +1894,11 @@ class TestObjects(ObjectTestCase):
type_ = enum_type(
"color",
int_type("unsigned int", 4, False),
[("RED", 0), ("GREEN", 1), ("BLUE", 2)],
(
TypeEnumerator("RED", 0),
TypeEnumerator("GREEN", 1),
TypeEnumerator("BLUE", 2),
),
)
prog = dwarf_program(dies)
self.assertEqual(prog["GREEN"], Object(prog, type_, value=1))
@ -1892,7 +1907,11 @@ class TestObjects(ObjectTestCase):
type_ = enum_type(
None,
int_type("unsigned int", 4, False),
[("RED", 0), ("GREEN", 1), ("BLUE", 2)],
(
TypeEnumerator("RED", 0),
TypeEnumerator("GREEN", 1),
TypeEnumerator("BLUE", 2),
),
)
prog = dwarf_program(dies)
self.assertEqual(

View File

@ -4,6 +4,7 @@ import unittest
from drgn import (
Qualifiers,
TypeEnumerator,
array_type,
bool_type,
class_type,
@ -407,7 +408,11 @@ class coord {
t = enum_type(
"color",
int_type("unsigned int", 4, False),
(("RED", 0), ("GREEN", 1), ("BLUE", 2),),
(
TypeEnumerator("RED", 0),
TypeEnumerator("GREEN", 1),
TypeEnumerator("BLUE", 2),
),
)
self.assertPrettyPrint(
t,
@ -422,7 +427,11 @@ enum color {
t = enum_type(
"color",
int_type("unsigned int", 4, False),
(("RED", 0), ("GREEN", 1), ("BLUE", 2),),
(
TypeEnumerator("RED", 0),
TypeEnumerator("GREEN", 1),
TypeEnumerator("BLUE", 2),
),
Qualifiers.CONST,
)
self.assertPrettyPrint(
@ -436,7 +445,13 @@ const enum color {
)
t = enum_type(
None, int_type("int", 4, True), (("RED", 0), ("GREEN", -1), ("BLUE", -2),)
None,
int_type("int", 4, True),
(
TypeEnumerator("RED", 0),
TypeEnumerator("GREEN", -1),
TypeEnumerator("BLUE", -2),
),
)
self.assertPrettyPrint(
t,

View File

@ -8,6 +8,7 @@ from drgn import (
OutOfBoundsError,
Qualifiers,
Type,
TypeEnumerator,
array_type,
cast,
container_of,
@ -996,7 +997,11 @@ class TestCIntegerPromotion(ObjectTestCase):
type_ = enum_type(
"color",
self.prog.type("unsigned long long"),
(("RED", 0), ("GREEN", 1), ("BLUE", 2)),
(
TypeEnumerator("RED", 0),
TypeEnumerator("GREEN", 1),
TypeEnumerator("BLUE", 2),
),
)
self.assertEqual(
+Object(self.prog, type_, value=1),
@ -1004,7 +1009,13 @@ class TestCIntegerPromotion(ObjectTestCase):
)
type_ = enum_type(
"color", self.prog.type("char"), (("RED", 0), ("GREEN", 1), ("BLUE", 2))
"color",
self.prog.type("char"),
(
TypeEnumerator("RED", 0),
TypeEnumerator("GREEN", 1),
TypeEnumerator("BLUE", 2),
),
)
self.assertEqual(
+Object(self.prog, type_, value=1), Object(self.prog, "int", value=1)

View File

@ -3,6 +3,7 @@ import unittest
from drgn import (
PrimitiveType,
Qualifiers,
TypeEnumerator,
TypeKind,
array_type,
bool_type,
@ -790,13 +791,24 @@ class TestType(unittest.TestCase):
t = enum_type(
"color",
int_type("unsigned int", 4, False),
(("RED", 0), ("GREEN", 1), ("BLUE", 2)),
(
TypeEnumerator("RED", 0),
TypeEnumerator("GREEN", 1),
TypeEnumerator("BLUE", 2),
),
)
self.assertEqual(t.kind, TypeKind.ENUM)
self.assertIsNone(t.primitive)
self.assertEqual(t.tag, "color")
self.assertEqual(t.type, int_type("unsigned int", 4, False))
self.assertEqual(t.enumerators, (("RED", 0), ("GREEN", 1), ("BLUE", 2)))
self.assertEqual(
t.enumerators,
(
TypeEnumerator("RED", 0),
TypeEnumerator("GREEN", 1),
TypeEnumerator("BLUE", 2),
),
)
self.assertTrue(t.is_complete())
self.assertEqual(
@ -804,7 +816,11 @@ class TestType(unittest.TestCase):
enum_type(
"color",
int_type("unsigned int", 4, False),
(("RED", 0), ("GREEN", 1), ("BLUE", 2)),
(
TypeEnumerator("RED", 0),
TypeEnumerator("GREEN", 1),
TypeEnumerator("BLUE", 2),
),
),
)
# Different tag.
@ -813,7 +829,11 @@ class TestType(unittest.TestCase):
enum_type(
"COLOR",
int_type("unsigned int", 4, False),
(("RED", 0), ("GREEN", 1), ("BLUE", 2)),
(
TypeEnumerator("RED", 0),
TypeEnumerator("GREEN", 1),
TypeEnumerator("BLUE", 2),
),
),
)
# One is anonymous.
@ -822,7 +842,11 @@ class TestType(unittest.TestCase):
enum_type(
None,
int_type("unsigned int", 4, False),
(("RED", 0), ("GREEN", 1), ("BLUE", 2)),
(
TypeEnumerator("RED", 0),
TypeEnumerator("GREEN", 1),
TypeEnumerator("BLUE", 2),
),
),
)
# Different compatible type.
@ -831,7 +855,11 @@ class TestType(unittest.TestCase):
enum_type(
"color",
int_type("int", 4, True),
(("RED", 0), ("GREEN", 1), ("BLUE", 2)),
(
TypeEnumerator("RED", 0),
TypeEnumerator("GREEN", 1),
TypeEnumerator("BLUE", 2),
),
),
)
# Different enumerators.
@ -840,14 +868,20 @@ class TestType(unittest.TestCase):
enum_type(
"color",
int_type("unsigned int", 4, False),
(("RED", 0), ("YELLOW", 1), ("BLUE", 2)),
(
TypeEnumerator("RED", 0),
TypeEnumerator("YELLOW", 1),
TypeEnumerator("BLUE", 2),
),
),
)
# Different number of enumerators.
self.assertNotEqual(
t,
enum_type(
"color", int_type("unsigned int", 4, False), (("RED", 0), ("GREEN", 1))
"color",
int_type("unsigned int", 4, False),
(TypeEnumerator("RED", 0), TypeEnumerator("GREEN", 1)),
),
)
# One is incomplete.
@ -855,7 +889,7 @@ class TestType(unittest.TestCase):
self.assertEqual(
repr(t),
"enum_type(tag='color', type=int_type(name='unsigned int', size=4, is_signed=False), enumerators=(('RED', 0), ('GREEN', 1), ('BLUE', 2)))",
"enum_type(tag='color', type=int_type(name='unsigned int', size=4, is_signed=False), enumerators=(TypeEnumerator('RED', 0), TypeEnumerator('GREEN', 1), TypeEnumerator('BLUE', 2)))",
)
self.assertEqual(sizeof(t), 4)
@ -916,36 +950,12 @@ class TestType(unittest.TestCase):
)
self.assertRaisesRegex(
TypeError,
"must be.*sequence",
"must be TypeEnumerator",
enum_type,
"color",
int_type("unsigned int", 4, False),
(4,),
)
self.assertRaisesRegex(
ValueError,
"must be.*sequence",
enum_type,
"color",
int_type("unsigned int", 4, False),
((),),
)
self.assertRaisesRegex(
TypeError,
"must be string",
enum_type,
"color",
int_type("unsigned int", 4, False),
((None, 0),),
)
self.assertRaisesRegex(
TypeError,
"must be integer",
enum_type,
"color",
int_type("unsigned int", 4, False),
(("RED", None),),
)
def test_typedef(self):
t = typedef_type("INT", int_type("int", 4, True))
@ -1201,3 +1211,29 @@ class TestType(unittest.TestCase):
self.assertNotEqual(void_type(), int_type("int", 4, True))
self.assertNotEqual(void_type(), 1)
self.assertNotEqual(1, void_type())
class TestTypeEnumerator(unittest.TestCase):
def test_init(self):
e = TypeEnumerator("a", 1)
self.assertEqual(e.name, "a")
self.assertEqual(e.value, 1)
self.assertRaises(TypeError, TypeEnumerator, "a", None)
self.assertRaises(TypeError, TypeEnumerator, None, 1)
def test_repr(self):
e = TypeEnumerator("a", 1)
self.assertEqual(repr(e), "TypeEnumerator('a', 1)")
def test_sequence(self):
e = TypeEnumerator("a", 1)
name, value = e
self.assertEqual(name, "a")
self.assertEqual(value, 1)
self.assertEqual(list(e), ["a", 1])
def test_cmp(self):
self.assertEqual(TypeEnumerator("a", 1), TypeEnumerator(name="a", value=1))
self.assertNotEqual(TypeEnumerator("a", 1), TypeEnumerator("a", 2))
self.assertNotEqual(TypeEnumerator("b", 1), TypeEnumerator("a", 1))