This is the mail archive of the gdb-patches@sourceware.org mailing list for the GDB project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Python: add field access by name and standard python mapping methods to gdb.Type


The attached proposed patch adds access to struct and enum fields by their name, just as field names can be used on gdb.Value objects.  It also adds the standard Python methods for mappings (dictionary-like objects), so that the kind of things that Python programmers would expect to work on objects that can be indexed by name indeed do work.  For example, you can iterate through the fields, or query if a field name exists.  

For a change like this that adds a new feature which uses a whole pile of new functions, is the sort of ChangeLog entry shown below the right way to document the change, or should I write this in a different way?

	paul

ChangeLog:

2011-09-15  Paul Koning  <paul_koning@dell.com>

	* python/py-type.c: Add standard Python mapping methods to
	gdb.Type object
	(make_fielditem, typy_field_names, typy_items, typy_length,
	typy_get, typy_has_key, typy_make_iter, typy_iterkeys,
	typy_iteritems, typy_itervalues, typy_iter, typy_iterator_iter,
	typy_iterator_iternext, typy_iterator_dealloc): New.
	(gdb.TypeIterator): New Python type.
	* python/python-internal.h (gdbpy_iter_kind): New.
	* doc/gdb.texinfo (gdb.Type): Document field access by dictionary
	key syntax.

testsuite/ChangeLog:

2011-09-15  Paul Koning  <pkoning@equallogic.com>

	* gdb.python/py-type.c (enum E): New.
	* gdb.python/py-type.exp (test_fields): Add tests for Python
	mapping access to fields.
	(test_enums): New test for field access on enums.

Index: doc/gdb.texinfo
===================================================================
RCS file: /cvs/src/src/gdb/doc/gdb.texinfo,v
retrieving revision 1.861
diff -u -r1.861 gdb.texinfo
--- doc/gdb.texinfo	15 Sep 2011 16:09:42 -0000	1.861
+++ doc/gdb.texinfo	15 Sep 2011 18:34:41 -0000
@@ -21537,6 +21537,19 @@
 If the named type cannot be found, it will throw an exception.
 @end defun
 
+If the type is a structure or class type, or an enum type, the fields
+of that type can be accessed using the Python @dfn{dictionary syntax}.
+For example, if @code{some_type} is a @code{gdb.Type} instance holding
+a structure type, you can access its @code{foo} element with:
+
+@smallexample
+bar = some_type['foo']
+@end smallexample
+
+@code{bar} will be a @code{gdb.Field} object; see below under the
+description of the @code{fields()} method for a description of the
+gdb.Field class.
+
 An instance of @code{Type} has the following attributes:
 
 @table @code
@@ -21570,7 +21583,7 @@
 represented as fields.  If the type has no fields, or does not fit
 into one of these categories, an empty sequence will be returned.
 
-Each field is an object, with some pre-defined attributes:
+Each field is an @code{gdb.Field} object, with some pre-defined attributes:
 @table @code
 @item bitpos
 This attribute is not available for @code{static} fields (as in
Index: python/py-type.c
===================================================================
RCS file: /cvs/src/src/gdb/python/py-type.c,v
retrieving revision 1.21
diff -u -r1.21 py-type.c
--- python/py-type.c	15 Sep 2011 18:33:15 -0000	1.21
+++ python/py-type.c	15 Sep 2011 18:34:41 -0000
@@ -55,6 +55,19 @@
 
 static PyTypeObject field_object_type;
 
+/* A type iterator object.  */
+typedef struct {
+  PyObject_HEAD
+  /* The current field index.  */
+  int field;
+  /* What to return.  */
+  enum gdbpy_iter_kind kind;
+  /* Pointer back to the original source type object.  */
+  struct pyty_type_object *source;
+} typy_iterator_object;
+
+static PyTypeObject type_iterator_object_type;
+
 /* This is used to initialize various gdb.TYPE_ constants.  */
 struct pyty_code
 {
@@ -210,12 +223,64 @@
   return NULL;
 }
 
-/* Return a sequence of all fields.  Each field is a dictionary with
-   some pre-defined keys.  */
+/* Helper function to return the name of a field, as a Python object.  */
 static PyObject *
-typy_fields (PyObject *self, PyObject *args)
+field_name (struct type *type, int field)
 {
   PyObject *result;
+
+  if (TYPE_FIELD_NAME (type, field))
+    result = PyString_FromString (TYPE_FIELD_NAME (type, field));
+  else
+    {
+      result = Py_None;
+      Py_INCREF (result);
+    }
+  return result;
+}
+
+static PyObject *
+make_fielditem (struct type *type, int i, enum gdbpy_iter_kind kind)
+{
+  PyObject *item = NULL, *key = NULL, *value = NULL;
+
+  switch (kind)
+    {
+    case iter_items:
+      key = field_name (type, i);
+      if (key == NULL)
+	goto fail;
+      value = convert_field (type, i);
+      if (value == NULL)
+	goto fail;
+      item = PyTuple_New (2);
+      if (item == NULL)
+	goto fail;
+      PyTuple_SET_ITEM (item, 0, key);
+      PyTuple_SET_ITEM (item, 1, value);
+      break;
+    case iter_keys:
+      item = field_name (type, i);
+      break;
+    case iter_values:
+      item =  convert_field (type, i);
+      break;
+    }
+  return item;
+  
+ fail:
+  Py_XDECREF (key);
+  Py_XDECREF (value);
+  Py_XDECREF (item);
+  return NULL;
+}
+
+/* Return a sequence of all field names, fields, or (name, field) pairs.
+   Each field is a dictionary with some pre-defined keys.  */
+static PyObject *
+typy_fields_items (PyObject *self, enum gdbpy_iter_kind kind)
+{
+  PyObject *result = NULL, *item = NULL;
   int i;
   struct type *type = ((type_object *) self)->type;
   volatile struct gdb_exception except;
@@ -230,26 +295,49 @@
      then memoize the result (and perhaps make Field.type() lazy).
      However, that can lead to cycles.  */
   result = PyList_New (0);
-
+  if (result == NULL)
+    return NULL;
+  
   for (i = 0; i < TYPE_NFIELDS (type); ++i)
     {
-      PyObject *dict = convert_field (type, i);
-
-      if (!dict)
-	{
-	  Py_DECREF (result);
-	  return NULL;
-	}
-      if (PyList_Append (result, dict))
-	{
-	  Py_DECREF (dict);
-	  Py_DECREF (result);
-	  return NULL;
-	}
-      Py_DECREF (dict);
+      item = make_fielditem (type, i, kind);
+      if (!item)
+	goto fail;
+      if (PyList_Append (result, item))
+	goto fail;
+      Py_DECREF (item);
     }
 
   return result;
+
+ fail:
+  Py_XDECREF (item);
+  Py_XDECREF (result);
+  return NULL;
+}
+
+/* Return a sequence of all fields.  Each field is a dictionary with
+   some pre-defined keys.  */
+static PyObject *
+typy_fields (PyObject *self, PyObject *args)
+{
+  return typy_fields_items (self, iter_values);
+}
+
+/* Return a sequence of all field names.  Each field is a dictionary with
+   some pre-defined keys.  */
+static PyObject *
+typy_field_names (PyObject *self, PyObject *args)
+{
+  return typy_fields_items (self, iter_keys);
+}
+
+/* Return a sequence of all (name, fields) pairs.  Each field is a 
+   dictionary with some pre-defined keys.  */
+static PyObject *
+typy_items (PyObject *self, PyObject *args)
+{
+  return typy_fields_items (self, iter_items);
 }
 
 /* Return the type's tag, or None.  */
@@ -1000,6 +1088,194 @@
   type->ob_type->tp_free (type);
 }
 
+/* Return number of fields ("length" of the field dictionary).  */
+static Py_ssize_t
+typy_length (PyObject *self)
+{
+  struct type *type = ((type_object *) self)->type;
+
+  return TYPE_NFIELDS (type);
+}
+
+/* Return a field object for the field named by the argument.  */
+static PyObject *
+typy_getitem (PyObject *self, PyObject *key)
+{
+  struct type *type = ((type_object *) self)->type;
+  char *field;
+  int i;
+  
+  field = python_string_to_host_string (key);
+  if (field == NULL)
+    return NULL;
+
+  /* We want just fields of this type, not of base types, so instead of 
+     using lookup_struct_elt_type, portions of that function are
+     copied here.  */
+
+  for (;;)
+    {
+      CHECK_TYPEDEF (type);
+      if (TYPE_CODE (type) != TYPE_CODE_PTR
+	  && TYPE_CODE (type) != TYPE_CODE_REF)
+	break;
+      type = TYPE_TARGET_TYPE (type);
+    }
+
+  for (i = 0; i < TYPE_NFIELDS (type); i++)
+    {
+      char *t_field_name = TYPE_FIELD_NAME (type, i);
+
+      if (t_field_name && (strcmp_iw (t_field_name, field) == 0))
+	{
+	  return convert_field (type, i);
+	}
+    }
+  PyErr_SetObject (PyExc_KeyError, key);
+  return NULL;
+}
+
+/* Implement the "get" method on the type object.  This is the 
+   same as getitem if the key is present, but returns the supplied
+   default value or None if the key is not found.  */
+static PyObject *
+typy_get (PyObject *self, PyObject *args)
+{
+  PyObject *key, *defval = Py_None, *result;
+  
+  if (!PyArg_UnpackTuple (args, "get", 1, 2, &key, &defval))
+    return NULL;
+  
+  result = typy_getitem (self, key);
+  if (result != NULL)
+    return result;
+  
+  /* getitem returned error, clear the exception status and
+     return the defval instead.  */
+  PyErr_Clear ();
+  Py_INCREF (defval);
+  return defval;
+}
+
+/* Implement the "has_key" method on the type object.  */
+static PyObject *
+typy_has_key (PyObject *self, PyObject *args)
+{
+  struct type *type = ((type_object *) self)->type;
+  char *field;
+  int i;
+  
+  if (!PyArg_ParseTuple (args, "s", &field))
+    return NULL;
+
+  /* We want just fields of this type, not of base types, so instead of 
+     using lookup_struct_elt_type, portions of that function are
+     copied here.  */
+
+  for (;;)
+    {
+      CHECK_TYPEDEF (type);
+      if (TYPE_CODE (type) != TYPE_CODE_PTR
+	  && TYPE_CODE (type) != TYPE_CODE_REF)
+	break;
+      type = TYPE_TARGET_TYPE (type);
+    }
+
+  for (i = 0; i < TYPE_NFIELDS (type); i++)
+    {
+      char *t_field_name = TYPE_FIELD_NAME (type, i);
+
+      if (t_field_name && (strcmp_iw (t_field_name, field) == 0))
+	Py_RETURN_TRUE;
+    }
+  Py_RETURN_FALSE;
+}
+
+/* Make an iterator object to iterate over keys, values, or items.  */
+static PyObject *
+typy_make_iter (PyObject *self, enum gdbpy_iter_kind kind)
+{
+  typy_iterator_object *typy_iter_obj;
+
+  typy_iter_obj = PyObject_New (typy_iterator_object,
+				&type_iterator_object_type);
+  if (typy_iter_obj == NULL)
+      return NULL;
+
+  typy_iter_obj->field = 0;
+  typy_iter_obj->kind = kind;
+  Py_INCREF (self);
+  typy_iter_obj->source = (type_object *) self;
+
+  return (PyObject *) typy_iter_obj;
+}
+
+/* iteritems() method.  */
+static PyObject *
+typy_iteritems (PyObject *self, PyObject *args)
+{
+  return typy_make_iter (self, iter_items);
+}
+
+/* iterkeys() method.  */
+static PyObject *
+typy_iterkeys (PyObject *self, PyObject *args)
+{
+  return typy_make_iter (self, iter_keys);
+}
+
+/* Iterating over the class, same as iterkeys
+   except for the function signature.  */
+static PyObject *
+typy_iter (PyObject *self)
+{
+  return typy_make_iter (self, iter_keys);
+}
+
+/* itervalues() method.  */
+static PyObject *
+typy_itervalues (PyObject *self, PyObject *args)
+{
+  return typy_make_iter (self, iter_values);
+}
+
+/* Return a reference to the type iterator.  */
+static PyObject *
+typy_iterator_iter (PyObject *self)
+{
+  Py_INCREF (self);
+  return self;
+}
+
+/* Return the next field in the iteration through the list of fields
+   of the type.  */
+static PyObject *
+typy_iterator_iternext (PyObject *self)
+{
+  typy_iterator_object *iter_obj = (typy_iterator_object *) self;
+  struct type *type = iter_obj->source->type;
+  int i;
+  PyObject *result;
+  
+  if (iter_obj->field < TYPE_NFIELDS (type))
+    {
+      result = make_fielditem (type, iter_obj->field, iter_obj->kind);
+      if (result != NULL)
+	iter_obj->field++;
+      return result;
+    }
+
+  return NULL;
+}
+
+static void
+typy_iterator_dealloc (PyObject *obj)
+{
+  typy_iterator_object *iter_obj = (typy_iterator_object *) obj;
+
+  Py_DECREF (iter_obj->source);
+}
+
 /* Create a new Type referring to TYPE.  */
 PyObject *
 type_to_type_object (struct type *type)
@@ -1067,6 +1343,8 @@
     return;
   if (PyType_Ready (&field_object_type) < 0)
     return;
+  if (PyType_Ready (&type_iterator_object_type) < 0)
+    return;
 
   for (i = 0; pyty_codes[i].name; ++i)
     {
@@ -1080,6 +1358,10 @@
   Py_INCREF (&type_object_type);
   PyModule_AddObject (gdb_module, "Type", (PyObject *) &type_object_type);
 
+  Py_INCREF (&type_iterator_object_type);
+  PyModule_AddObject (gdb_module, "TypeIterator",
+		      (PyObject *) &type_iterator_object_type);
+
   Py_INCREF (&field_object_type);
   PyModule_AddObject (gdb_module, "Field", (PyObject *) &field_object_type);
 }
@@ -1102,13 +1384,35 @@
   { "array", typy_array, METH_VARARGS,
     "array (N) -> Type\n\
 Return a type which represents an array of N objects of this type." },
+   { "__contains__", typy_has_key, METH_VARARGS,
+     "T.__contains__(k) -> True if T has a field named k, else False" },
   { "const", typy_const, METH_NOARGS,
     "const () -> Type\n\
 Return a const variant of this type." },
   { "fields", typy_fields, METH_NOARGS,
-    "field () -> list\n\
-Return a sequence holding all the fields of this type.\n\
-Each field is a dictionary." },
+    "fields () -> list\n\
+Return a list holding all the fields of this type.\n\
+Each field is a gdb.Field object." },
+  { "get", typy_get, METH_VARARGS,
+    "T.get(k[,default]) -> returns field named k in T, if it exists;\n\
+otherwise returns default, if supplied, or None if not." },
+  { "has_key", typy_has_key, METH_VARARGS,
+    "T.has_key(k) -> True if T has a field named k, else False" },
+  { "items", typy_items, METH_NOARGS,
+    "items () -> list\n\
+Return a list of (name, field) pairs of this type.\n\
+Each field is a gdb.Field object." },
+  { "iteritems", typy_iteritems, METH_NOARGS,
+    "iteritems () -> an iterator over the (name, field)\n\
+pairs of this type.  Each field is a gdb.Field object." },
+  { "iterkeys", typy_iterkeys, METH_NOARGS,
+    "iterkeys () -> an iterator over the field names of this type." },
+  { "itervalues", typy_itervalues, METH_NOARGS,
+    "itervalues () -> an iterator over the fields of this type.\n\
+Each field is a gdb.Field object." },
+  { "keys", typy_field_names, METH_NOARGS,
+    "keys () -> list\n\
+Return a list holding all the fields names of this type." },
   { "pointer", typy_pointer, METH_NOARGS,
     "pointer () -> Type\n\
 Return a type of pointer to this type." },
@@ -1130,12 +1434,22 @@
   { "unqualified", typy_unqualified, METH_NOARGS,
     "unqualified () -> Type\n\
 Return a variant of this type without const or volatile attributes." },
+  { "values", typy_fields, METH_NOARGS,
+    "values () -> list\n\
+Return a list holding all the fields of this type.\n\
+Each field is a gdb.Field object." },
   { "volatile", typy_volatile, METH_NOARGS,
     "volatile () -> Type\n\
 Return a volatile variant of this type" },
   { NULL }
 };
 
+static PyMappingMethods typy_mapping = {
+  typy_length,
+  typy_getitem,
+  NULL				  /* no "set" method */
+};
+
 static PyTypeObject type_object_type =
 {
   PyObject_HEAD_INIT (NULL)
@@ -1151,7 +1465,7 @@
   0,				  /*tp_repr*/
   0,				  /*tp_as_number*/
   0,				  /*tp_as_sequence*/
-  0,				  /*tp_as_mapping*/
+  &typy_mapping,		  /*tp_as_mapping*/
   0,				  /*tp_hash */
   0,				  /*tp_call*/
   typy_str,			  /*tp_str*/
@@ -1164,7 +1478,7 @@
   0,				  /* tp_clear */
   typy_richcompare,		  /* tp_richcompare */
   0,				  /* tp_weaklistoffset */
-  0,				  /* tp_iter */
+  typy_iter,			  /* tp_iter */
   0,				  /* tp_iternext */
   type_object_methods,		  /* tp_methods */
   0,				  /* tp_members */
@@ -1221,3 +1535,35 @@
   0,				  /* tp_alloc */
   0,				  /* tp_new */
 };
+
+static PyTypeObject type_iterator_object_type = {
+  PyObject_HEAD_INIT (NULL)
+  0,				  /*ob_size*/
+  "gdb.TypeIterator",		  /*tp_name*/
+  sizeof (typy_iterator_object),  /*tp_basicsize*/
+  0,				  /*tp_itemsize*/
+  typy_iterator_dealloc,	  /*tp_dealloc*/
+  0,				  /*tp_print*/
+  0,				  /*tp_getattr*/
+  0,				  /*tp_setattr*/
+  0,				  /*tp_compare*/
+  0,				  /*tp_repr*/
+  0,				  /*tp_as_number*/
+  0,				  /*tp_as_sequence*/
+  0,				  /*tp_as_mapping*/
+  0,				  /*tp_hash */
+  0,				  /*tp_call*/
+  0,				  /*tp_str*/
+  0,				  /*tp_getattro*/
+  0,				  /*tp_setattro*/
+  0,				  /*tp_as_buffer*/
+  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_ITER,  /*tp_flags*/
+  "GDB type iterator object",	  /*tp_doc */
+  0,				  /*tp_traverse */
+  0,				  /*tp_clear */
+  0,				  /*tp_richcompare */
+  0,				  /*tp_weaklistoffset */
+  typy_iterator_iter,             /*tp_iter */
+  typy_iterator_iternext,	  /*tp_iternext */
+  0				  /*tp_methods */
+};
Index: python/python-internal.h
===================================================================
RCS file: /cvs/src/src/gdb/python/python-internal.h,v
retrieving revision 1.48
diff -u -r1.48 python-internal.h
--- python/python-internal.h	15 Sep 2011 12:42:30 -0000	1.48
+++ python/python-internal.h	15 Sep 2011 18:34:41 -0000
@@ -104,6 +104,8 @@
 
 #include "exceptions.h"
 
+enum gdbpy_iter_kind { iter_keys, iter_values, iter_items };
+
 struct block;
 struct value;
 struct language_defn;
Index: testsuite/gdb.python/py-type.c
===================================================================
RCS file: /cvs/src/src/gdb/testsuite/gdb.python/py-type.c,v
retrieving revision 1.4
diff -u -r1.4 py-type.c
--- testsuite/gdb.python/py-type.c	1 Jan 2011 15:33:49 -0000	1.4
+++ testsuite/gdb.python/py-type.c	15 Sep 2011 18:34:42 -0000
@@ -43,6 +43,10 @@
 
 #endif
 
+enum E
+{ v1, v2, v3
+};
+
 int
 main ()
 {
@@ -56,9 +60,12 @@
   d.e = 3;
   d.f = 4;
 #endif
-
+  enum E e;
+  
   st.a = 3;
   st.b = 5;
 
+  e = v2;
+  
   return 0;      /* break to inspect struct and array.  */
 }
Index: testsuite/gdb.python/py-type.exp
===================================================================
RCS file: /cvs/src/src/gdb/testsuite/gdb.python/py-type.exp,v
retrieving revision 1.13
diff -u -r1.13 py-type.exp
--- testsuite/gdb.python/py-type.exp	26 Jul 2011 18:38:55 -0000	1.13
+++ testsuite/gdb.python/py-type.exp	15 Sep 2011 18:34:42 -0000
@@ -86,6 +86,14 @@
   gdb_test "python print fields\[0\].name" "a" "Check structure field a name"
   gdb_test "python print fields\[1\].name" "b" "Check structure field b name"
 
+  # Test Python mapping behavior of gdb.Type for structs/classes
+  gdb_test "python print len(st.type)" "2" "Check number of fields"
+  gdb_test "python print st.type\['a'\].name" "a" "Check fields lookup by name"
+    gdb_test "python print \[v.bitpos for v in st.type.itervalues()\]" {\[0L, 32L\]} "Check fields iteration over values"
+    gdb_test "python print \[(n, v.bitpos) for (n, v) in st.type.items()\]" {\[\('a', 0L\), \('b', 32L\)\]} "Check fields items list"
+  gdb_test "python print 'a' in st.type" "True" "Check field name exists test"
+  gdb_test "python print 'nosuch' in st.type" "False" "Check field name nonexists test"
+  
   # Test regression PR python/10805
   gdb_py_test_silent_cmd "print ar" "print value" 1
   gdb_py_test_silent_cmd "python ar = gdb.history (0)" "get value from  history" 1
@@ -101,6 +109,21 @@
   gdb_test "python print ar\[0\].type == ar\[0\].type" "True"
 }
 
+proc test_enums {} {
+  gdb_py_test_silent_cmd "print e" "print value" 1
+  gdb_py_test_silent_cmd "python e = gdb.history (0)" "get value from  history" 1
+  gdb_py_test_silent_cmd "python fields = e.type.fields()" "get value from history" 1
+  gdb_test "python print len(fields)" "3" "Check the number of enum fields"
+  gdb_test "python print fields\[0\].name" "v1" "Check enum field name"
+  gdb_test "python print fields\[1\].name" "v2" "Check enum field name"
+
+  # Ditto but by mapping operations
+  gdb_test "python print len(e.type)" "3" "Check the number of enum fields"
+  gdb_test "python print e.type\['v1'\].name" "v1" "Check enum field lookup by name"
+  gdb_test "python print e.type\['v3'\].name" "v3" "Check enum field lookup by name"
+    gdb_test "python print \[v.bitpos for v in e.type.itervalues()\]" {\[0L, 1L, 2L\]} "Check num fields iteration over values"
+    gdb_test "python print \[(n, v.bitpos) for (n, v) in e.type.items()\]" {\[\('v1', 0L\), \('v2', 1L\), \('v3', 2L\)\]} "Check enum fields items list"
+}
 proc test_base_class {} {
   gdb_py_test_silent_cmd "print d" "print value" 1
   gdb_py_test_silent_cmd "python d = gdb.history (0)" "get value from  history" 1
@@ -169,6 +192,7 @@
 
 runto_bp "break to inspect struct and array."
 test_fields "c"
+test_enums
 
 # Perform C++ Tests.
 build_inferior "${binfile}-cxx" "c++"
@@ -178,3 +202,4 @@
 test_base_class
 test_range
 test_template
+test_enums


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]