[10/13] libctf, include: support unnamed structure members better

Message ID 20201218195147.378720-11-nick.alcock@oracle.com
State New
Headers show
Series
  • CTF dumper improvements, a lookup testsuite, and bugfixes
Related show

Commit Message

Andrea Corallo via Binutils Dec. 18, 2020, 7:51 p.m.
libctf has no intrinsic support for the GCC unnamed structure member
extension.  This principally means that you can't look up named members
inside unnamed struct or union members via ctf_member_info: you have to
tiresomely find out the type ID of the unnamed members via iteration,
then look in each of these.

This is ridiculous.  Fix it by extending ctf_member_info so that it
recurses into unnamed members for you: this is still unambiguous because
GCC won't let you create ambiguously-named members even in the presence
of this extension.

For consistency, and because the release hasn't happened and we can
still do this, break the ctf_member_next API and add flags: we specify
one flag, CTF_MN_RECURSE, which if set causes ctf_member_next to
automatically recurse into unnamed members for you, returning not only
the members themselves but all their contained members, so that you can
use ctf_member_next to identify every member that it would be valid to
call ctf_member_info with.

New lookup tests are added for all of this.

include/ChangeLog
2020-12-16  Nick Alcock  <nick.alcock@oracle.com>

	* ctf-api.h (CTF_MN_RECURSE): New.
	(ctf_member_next): Add flags argument.

libctf/ChangeLog
2020-12-15  Nick Alcock  <nick.alcock@oracle.com>

	* ctf-impl.h (struct ctf_next) <u.ctn_next>: Move to...
	<ctn_next>: ... here.
	* ctf-util.c (ctf_next_destroy): Unconditionally destroy it.
	* ctf-lookup.c (ctf_symbol_next): Adjust accordingly.
	* ctf-types.c (ctf_member_iter): Reimplement in terms of...
	(ctf_member_next): ... this.  Support recursive unnamed member
	iteration (off by default).
	(ctf_member_info): Look up members in unnamed sub-structs.
	* ctf-dedup.c (ctf_dedup_rhash_type): Adjust ctf_member_next call.
	(ctf_dedup_emit_struct_members): Likewise.
	* testsuite/libctf-lookup/struct-iteration-ctf.c: Test empty unnamed
	members, and a normal member after the end.
	* testsuite/libctf-lookup/struct-iteration.c: Verify that
	ctf_member_count is consistent with the number of successful returns
	from a non-recursive ctf_member_next.
	* testsuite/libctf-lookup/struct-iteration-*: New, test iteration
	over struct members.
	* testsuite/libctf-lookup/struct-lookup.c: New test.
	* testsuite/libctf-lookup/struct-lookup.lk: New test.
---
 include/ctf-api.h                             |   7 +-
 libctf/ctf-dedup.c                            |   6 +-
 libctf/ctf-impl.h                             |  12 +-
 libctf/ctf-lookup.c                           |   2 +-
 libctf/ctf-types.c                            | 227 +++++++++++-------
 libctf/ctf-util.c                             |   5 +-
 .../libctf-lookup/struct-iteration-ctf.c      |  28 +++
 .../libctf-lookup/struct-iteration.c          |  92 +++++++
 .../libctf-lookup/struct-iteration.lk         |  24 ++
 .../testsuite/libctf-lookup/struct-lookup.c   |  60 +++++
 .../testsuite/libctf-lookup/struct-lookup.lk  |   4 +
 11 files changed, 365 insertions(+), 102 deletions(-)
 create mode 100644 libctf/testsuite/libctf-lookup/struct-iteration-ctf.c
 create mode 100644 libctf/testsuite/libctf-lookup/struct-iteration.c
 create mode 100644 libctf/testsuite/libctf-lookup/struct-iteration.lk
 create mode 100644 libctf/testsuite/libctf-lookup/struct-lookup.c
 create mode 100644 libctf/testsuite/libctf-lookup/struct-lookup.lk

-- 
2.29.2.250.g8336e49d6f.dirty

Patch

diff --git a/include/ctf-api.h b/include/ctf-api.h
index 16567ef3ab6..112b8a42f89 100644
--- a/include/ctf-api.h
+++ b/include/ctf-api.h
@@ -264,6 +264,10 @@  _CTF_ERRORS
 #define	CTF_ADD_NONROOT	0	/* Type only visible in nested scope.  */
 #define	CTF_ADD_ROOT	1	/* Type visible at top-level scope.  */
 
+/* Flags for ctf_member_next.  */
+
+#define CTF_MN_RECURSE 0x1	/* Recurse into unnamed members.  */
+
 /* These typedefs are used to define the signature for callback functions that
    can be used with the iteration and visit functions below.  There is also a
    family of iteration functions that do not require callbacks.  */
@@ -411,7 +415,8 @@  extern int ctf_label_info (ctf_dict_t *, const char *, ctf_lblinfo_t *);
 extern int ctf_member_count (ctf_dict_t *, ctf_id_t);
 extern int ctf_member_iter (ctf_dict_t *, ctf_id_t, ctf_member_f *, void *);
 extern ssize_t ctf_member_next (ctf_dict_t *, ctf_id_t, ctf_next_t **,
-				const char **name, ctf_id_t *membtype);
+				const char **name, ctf_id_t *membtype,
+				int flags);
 extern int ctf_enum_iter (ctf_dict_t *, ctf_id_t, ctf_enum_f *, void *);
 extern const char *ctf_enum_next (ctf_dict_t *, ctf_id_t, ctf_next_t **,
 				  int *);
diff --git a/libctf/ctf-dedup.c b/libctf/ctf-dedup.c
index b0be5a7044f..b77acc58a3e 100644
--- a/libctf/ctf-dedup.c
+++ b/libctf/ctf-dedup.c
@@ -887,8 +887,8 @@  ctf_dedup_rhash_type (ctf_dict_t *fp, ctf_dict_t *input, ctf_dict_t **inputs,
 	ctf_dedup_sha1_add (&hash, &size, sizeof (ssize_t), "struct size",
 			    depth);
 
-	while ((offset = ctf_member_next (input, type, &i, &mname,
-					  &membtype)) >= 0)
+	while ((offset = ctf_member_next (input, type, &i, &mname, &membtype,
+					  0)) >= 0)
 	  {
 	    if (mname == NULL)
 	      mname = "";
@@ -2956,7 +2956,7 @@  ctf_dedup_emit_struct_members (ctf_dict_t *output, ctf_dict_t **inputs,
       target_type = CTF_DEDUP_GID_TO_TYPE (target_id);
 
       while ((offset = ctf_member_next (input_fp, input_type, &j, &name,
-					&membtype)) >= 0)
+					&membtype, 0)) >= 0)
 	{
 	  err_fp = target;
 	  err_type = target_type;
diff --git a/libctf/ctf-impl.h b/libctf/ctf-impl.h
index 84460ea2da4..df0d2ef8a1b 100644
--- a/libctf/ctf-impl.h
+++ b/libctf/ctf-impl.h
@@ -532,13 +532,15 @@  struct ctf_next
   ssize_t ctn_size;
   ssize_t ctn_increment;
   uint32_t ctn_n;
+
+  /* Some iterators contain other iterators, in addition to their other
+     state.  */
+  ctf_next_t *ctn_next;
+
   /* We can save space on this side of things by noting that a dictionary is
      either dynamic or not, as a whole, and a given iterator can only iterate
      over one kind of thing at once: so we can overlap the DTD and non-DTD
-     members, and the structure, variable and enum members, etc.
-
-     Some of the _next iterators actually thunk down to another _next iterator
-     themselves, so one of the options in here is a _next iterator!  */
+     members, and the structure, variable and enum members, etc.  */
   union
   {
     const ctf_member_t *ctn_mp;
@@ -546,10 +548,10 @@  struct ctf_next
     const ctf_dmdef_t *ctn_dmd;
     const ctf_enum_t *ctn_en;
     const ctf_dvdef_t *ctn_dvd;
-    ctf_next_t *ctn_next;
     ctf_next_hkv_t *ctn_sorted_hkv;
     void **ctn_hash_slot;
   } u;
+
   /* This union is of various sorts of dict we can iterate over:
      currently dictionaries and archives, dynhashes, and dynsets.  */
   union
diff --git a/libctf/ctf-lookup.c b/libctf/ctf-lookup.c
index a862b407bcb..9bbd9178356 100644
--- a/libctf/ctf-lookup.c
+++ b/libctf/ctf-lookup.c
@@ -442,7 +442,7 @@  ctf_symbol_next (ctf_dict_t *fp, ctf_next_t **it, const char **name,
 	  return (ctf_set_errno (fp, ECTF_NEXT_END));
 	}
 
-      err = ctf_dynhash_next (dynh, &i->u.ctn_next, &dyn_name, &dyn_value);
+      err = ctf_dynhash_next (dynh, &i->ctn_next, &dyn_name, &dyn_value);
       /* This covers errors and also end-of-iteration.  */
       if (err != 0)
 	{
diff --git a/libctf/ctf-types.c b/libctf/ctf-types.c
index 6f40f40b086..1b808f6d487 100644
--- a/libctf/ctf-types.c
+++ b/libctf/ctf-types.c
@@ -41,76 +41,33 @@  ctf_type_ischild (ctf_dict_t * fp, ctf_id_t id)
 int
 ctf_member_iter (ctf_dict_t *fp, ctf_id_t type, ctf_member_f *func, void *arg)
 {
-  ctf_dict_t *ofp = fp;
-  const ctf_type_t *tp;
-  ctf_dtdef_t *dtd;
-  ssize_t size, increment;
-  uint32_t kind, n;
+  ctf_next_t *i = NULL;
+  ssize_t offset;
+  const char *name;
+  ctf_id_t membtype;
   int rc;
 
-  if ((type = ctf_type_resolve (fp, type)) == CTF_ERR)
-    return -1;			/* errno is set for us.  */
-
-  if ((tp = ctf_lookup_by_id (&fp, type)) == NULL)
-    return -1;			/* errno is set for us.  */
-
-  (void) ctf_get_ctt_size (fp, tp, &size, &increment);
-  kind = LCTF_INFO_KIND (fp, tp->ctt_info);
-
-  if (kind != CTF_K_STRUCT && kind != CTF_K_UNION)
-    return (ctf_set_errno (ofp, ECTF_NOTSOU));
-
-  if ((dtd = ctf_dynamic_type (fp, type)) == NULL)
+  while ((offset = ctf_member_next (fp, type, &i, &name, &membtype, 0)) >= 0)
     {
-      if (size < CTF_LSTRUCT_THRESH)
-	{
-	  const ctf_member_t *mp = (const ctf_member_t *) ((uintptr_t) tp +
-							   increment);
-
-	  for (n = LCTF_INFO_VLEN (fp, tp->ctt_info); n != 0; n--, mp++)
-	    {
-	      const char *name = ctf_strptr (fp, mp->ctm_name);
-	      if ((rc = func (name, mp->ctm_type, mp->ctm_offset, arg)) != 0)
-	    return rc;
-	    }
-	}
-      else
-	{
-	  const ctf_lmember_t *lmp = (const ctf_lmember_t *) ((uintptr_t) tp +
-							      increment);
-
-	  for (n = LCTF_INFO_VLEN (fp, tp->ctt_info); n != 0; n--, lmp++)
-	    {
-	      const char *name = ctf_strptr (fp, lmp->ctlm_name);
-	      if ((rc = func (name, lmp->ctlm_type,
-			      (unsigned long) CTF_LMEM_OFFSET (lmp), arg)) != 0)
-		return rc;
-	    }
-	}
-    }
-  else
-    {
-      ctf_dmdef_t *dmd;
-
-      for (dmd = ctf_list_next (&dtd->dtd_u.dtu_members);
-	   dmd != NULL; dmd = ctf_list_next (dmd))
+      if ((rc = func (name, membtype, offset, arg)) != 0)
 	{
-	  if ((rc = func (dmd->dmd_name, dmd->dmd_type,
-			  dmd->dmd_offset, arg)) != 0)
-	    return rc;
+	  ctf_next_destroy (i);
+	  return rc;
 	}
     }
+  if (ctf_errno (fp) != ECTF_NEXT_END)
+    return -1;					/* errno is set for us.  */
 
   return 0;
 }
 
 /* Iterate over the members of a STRUCT or UNION, returning each member's
    offset and optionally name and member type in turn.  On end-of-iteration,
-   returns -1.  */
+   returns -1.  If FLAGS is CTF_MN_RECURSE, recurse into unnamed members.  */
 
 ssize_t
 ctf_member_next (ctf_dict_t *fp, ctf_id_t type, ctf_next_t **it,
-		 const char **name, ctf_id_t *membtype)
+		 const char **name, ctf_id_t *membtype, int flags)
 {
   ctf_dict_t *ofp = fp;
   uint32_t kind;
@@ -121,6 +78,7 @@  ctf_member_next (ctf_dict_t *fp, ctf_id_t type, ctf_next_t **it,
     {
       const ctf_type_t *tp;
       ctf_dtdef_t *dtd;
+      ssize_t increment;
 
       if ((type = ctf_type_resolve (fp, type)) == CTF_ERR)
 	return -1;			/* errno is set for us.  */
@@ -132,8 +90,7 @@  ctf_member_next (ctf_dict_t *fp, ctf_id_t type, ctf_next_t **it,
 	return ctf_set_errno (ofp, ENOMEM);
       i->cu.ctn_fp = ofp;
 
-      (void) ctf_get_ctt_size (fp, tp, &i->ctn_size,
-			       &i->ctn_increment);
+      (void) ctf_get_ctt_size (fp, tp, &i->ctn_size, &increment);
       kind = LCTF_INFO_KIND (fp, tp->ctt_info);
 
       if (kind != CTF_K_STRUCT && kind != CTF_K_UNION)
@@ -156,11 +113,9 @@  ctf_member_next (ctf_dict_t *fp, ctf_id_t type, ctf_next_t **it,
 	  i->ctn_n = LCTF_INFO_VLEN (fp, tp->ctt_info);
 
 	  if (i->ctn_size < CTF_LSTRUCT_THRESH)
-	    i->u.ctn_mp = (const ctf_member_t *) ((uintptr_t) tp +
-						  i->ctn_increment);
+	    i->u.ctn_mp = (const ctf_member_t *) ((uintptr_t) tp + increment);
 	  else
-	    i->u.ctn_lmp = (const ctf_lmember_t *) ((uintptr_t) tp +
-						    i->ctn_increment);
+	    i->u.ctn_lmp = (const ctf_lmember_t *) ((uintptr_t) tp + increment);
 	}
       else
 	i->u.ctn_dmd = ctf_list_next (&dtd->dtd_u.dtu_members);
@@ -178,41 +133,112 @@  ctf_member_next (ctf_dict_t *fp, ctf_id_t type, ctf_next_t **it,
   if ((fp = ctf_get_dict (ofp, type)) == NULL)
     return (ctf_set_errno (ofp, ECTF_NOPARENT));
 
-  if (!(fp->ctf_flags & LCTF_RDWR))
-    {
-      if (i->ctn_n == 0)
-	goto end_iter;
+  /* When we hit an unnamed struct/union member, we set ctn_type to indicate
+     that we are inside one, then return the unnamed member: on the next call,
+     we must skip over top-level member iteration in favour of iteration within
+     the sub-struct until it later turns out that that iteration has ended.  */
 
-      if (i->ctn_size < CTF_LSTRUCT_THRESH)
+ retry:
+  if (!i->ctn_type)
+    {
+      if (!(fp->ctf_flags & LCTF_RDWR))
 	{
-	  if (name)
-	    *name = ctf_strptr (fp, i->u.ctn_mp->ctm_name);
-	  if (membtype)
-	    *membtype = i->u.ctn_mp->ctm_type;
-	  offset = i->u.ctn_mp->ctm_offset;
-	  i->u.ctn_mp++;
+	  if (i->ctn_n == 0)
+	    goto end_iter;
+
+	  if (i->ctn_size < CTF_LSTRUCT_THRESH)
+	    {
+	      const char *membname = ctf_strptr (fp, i->u.ctn_mp->ctm_name);
+
+	      if (name)
+		*name = membname;
+	      if (membtype)
+		*membtype = i->u.ctn_mp->ctm_type;
+	      offset = i->u.ctn_mp->ctm_offset;
+
+	      if (membname[0] == 0
+		  && (ctf_type_kind (fp, i->u.ctn_mp->ctm_type) == CTF_K_STRUCT
+		      || ctf_type_kind (fp, i->u.ctn_mp->ctm_type) == CTF_K_UNION))
+		i->ctn_type = i->u.ctn_mp->ctm_type;
+
+	      i->u.ctn_mp++;
+	    }
+	  else
+	    {
+	      const char *membname = ctf_strptr (fp, i->u.ctn_lmp->ctlm_name);
+
+	      if (name)
+		*name = membname;
+	      if (membtype)
+		*membtype = i->u.ctn_lmp->ctlm_type;
+	      offset = (unsigned long) CTF_LMEM_OFFSET (i->u.ctn_lmp);
+
+	      if (membname[0] == 0
+		  && (ctf_type_kind (fp, i->u.ctn_lmp->ctlm_type) == CTF_K_STRUCT
+		      || ctf_type_kind (fp, i->u.ctn_lmp->ctlm_type) == CTF_K_UNION))
+		i->ctn_type = i->u.ctn_lmp->ctlm_type;
+
+	      i->u.ctn_lmp++;
+	    }
+	  i->ctn_n--;
 	}
       else
 	{
+	  if (i->u.ctn_dmd == NULL)
+	    goto end_iter;
+	  /* The dmd contains a NULL for unnamed dynamic members.  Don't inflict
+	     this on our callers.  */
 	  if (name)
-	    *name = ctf_strptr (fp, i->u.ctn_lmp->ctlm_name);
+	    {
+	      if (i->u.ctn_dmd->dmd_name)
+		*name = i->u.ctn_dmd->dmd_name;
+	      else
+		*name = "";
+	    }
 	  if (membtype)
-	    *membtype = i->u.ctn_lmp->ctlm_type;
-	  offset = (unsigned long) CTF_LMEM_OFFSET (i->u.ctn_lmp);
-	  i->u.ctn_lmp++;
+	    *membtype = i->u.ctn_dmd->dmd_type;
+	  offset = i->u.ctn_dmd->dmd_offset;
+
+	  if (i->u.ctn_dmd->dmd_name == NULL
+	      && (ctf_type_kind (fp, i->u.ctn_dmd->dmd_type) == CTF_K_STRUCT
+		  || ctf_type_kind (fp, i->u.ctn_dmd->dmd_type) == CTF_K_UNION))
+	    i->ctn_type = i->u.ctn_dmd->dmd_type;
+
+	  i->u.ctn_dmd = ctf_list_next (i->u.ctn_dmd);
 	}
-      i->ctn_n--;
+
+      /* The callers might want automatic recursive sub-struct traversal.  */
+      if (!(flags & CTF_MN_RECURSE))
+	i->ctn_type = 0;
+
+      /* Sub-struct traversal starting?  Take note of the offset of this member,
+	 for later boosting of sub-struct members' offsets.  */
+      if (i->ctn_type)
+	i->ctn_increment = offset;
     }
+  /* Traversing a sub-struct?  Just return it, with the offset adjusted.  */
   else
     {
-      if (i->u.ctn_dmd == NULL)
-	goto end_iter;
-      if (name)
-	*name = i->u.ctn_dmd->dmd_name;
-      if (membtype)
-	*membtype = i->u.ctn_dmd->dmd_type;
-      offset = i->u.ctn_dmd->dmd_offset;
-      i->u.ctn_dmd = ctf_list_next (i->u.ctn_dmd);
+      ssize_t ret = ctf_member_next (fp, i->ctn_type, &i->ctn_next, name,
+				     membtype, flags);
+
+      if (ret >= 0)
+	return ret + i->ctn_increment;
+
+      if (ctf_errno (fp) != ECTF_NEXT_END)
+	{
+	  ctf_next_destroy (i);
+	  *it = NULL;
+	  i->ctn_type = 0;
+	  return ret;				/* errno is set for us.  */
+	}
+
+      if (!ctf_assert (fp, (i->ctn_next == NULL)))
+	return -1;				/* errno is set for us.  */
+
+      i->ctn_type = 0;
+      /* This sub-struct has ended: on to the next real member.  */
+      goto retry;
     }
 
   return offset;
@@ -1376,7 +1402,7 @@  ctf_type_compat (ctf_dict_t *lfp, ctf_id_t ltype,
 }
 
 /* Return the number of members in a STRUCT or UNION, or the number of
-   enumerators in an ENUM.  */
+   enumerators in an ENUM.  The count does not include unnamed sub-members.  */
 
 int
 ctf_member_count (ctf_dict_t *fp, ctf_id_t type)
@@ -1432,7 +1458,15 @@  ctf_member_info (ctf_dict_t *fp, ctf_id_t type, const char *name,
 
 	  for (n = LCTF_INFO_VLEN (fp, tp->ctt_info); n != 0; n--, mp++)
 	    {
-	      if (strcmp (ctf_strptr (fp, mp->ctm_name), name) == 0)
+	      const char *membname = ctf_strptr (fp, mp->ctm_name);
+
+	      if (membname[0] == 0
+		  && (ctf_type_kind (fp, mp->ctm_type) == CTF_K_STRUCT
+		      || ctf_type_kind (fp, mp->ctm_type) == CTF_K_UNION)
+		  && (ctf_member_info (fp, mp->ctm_type, name, mip) == 0))
+		return 0;
+
+	      if (strcmp (membname, name) == 0)
 		{
 		  mip->ctm_type = mp->ctm_type;
 		  mip->ctm_offset = mp->ctm_offset;
@@ -1447,7 +1481,15 @@  ctf_member_info (ctf_dict_t *fp, ctf_id_t type, const char *name,
 
 	  for (n = LCTF_INFO_VLEN (fp, tp->ctt_info); n != 0; n--, lmp++)
 	    {
-	      if (strcmp (ctf_strptr (fp, lmp->ctlm_name), name) == 0)
+	      const char *membname = ctf_strptr (fp, lmp->ctlm_name);
+
+	      if (membname[0] == 0
+		  && (ctf_type_kind (fp, lmp->ctlm_type) == CTF_K_STRUCT
+		      || ctf_type_kind (fp, lmp->ctlm_type) == CTF_K_UNION)
+		  && (ctf_member_info (fp, lmp->ctlm_type, name, mip) == 0))
+		return 0;
+
+	      if (strcmp (membname, name) == 0)
 		{
 		  mip->ctm_type = lmp->ctlm_type;
 		  mip->ctm_offset = (unsigned long) CTF_LMEM_OFFSET (lmp);
@@ -1463,7 +1505,14 @@  ctf_member_info (ctf_dict_t *fp, ctf_id_t type, const char *name,
       for (dmd = ctf_list_next (&dtd->dtd_u.dtu_members);
 	   dmd != NULL; dmd = ctf_list_next (dmd))
 	{
-	  if (strcmp (dmd->dmd_name, name) == 0)
+	  if (dmd->dmd_name == NULL
+	      && (ctf_type_kind (fp, dmd->dmd_type) == CTF_K_STRUCT
+		  || ctf_type_kind (fp, dmd->dmd_type) == CTF_K_UNION)
+	      && (ctf_member_info (fp, dmd->dmd_type, name, mip) == 0))
+	    return 0;
+
+	  if (dmd->dmd_name != NULL
+	      && strcmp (dmd->dmd_name, name) == 0)
 	    {
 	      mip->ctm_type = dmd->dmd_type;
 	      mip->ctm_offset = dmd->dmd_offset;
diff --git a/libctf/ctf-util.c b/libctf/ctf-util.c
index ab34bc0732e..799e35ad22f 100644
--- a/libctf/ctf-util.c
+++ b/libctf/ctf-util.c
@@ -283,9 +283,8 @@  ctf_next_destroy (ctf_next_t *i)
 
   if (i->ctn_iter_fun == (void (*) (void)) ctf_dynhash_next_sorted)
     free (i->u.ctn_sorted_hkv);
-  if (i->ctn_iter_fun == (void (*) (void)) ctf_symbol_next
-      && i->cu.ctn_fp->ctf_flags & LCTF_RDWR)
-    ctf_next_destroy (i->u.ctn_next);
+  if (i->ctn_next)
+    ctf_next_destroy (i->ctn_next);
   free (i);
 }
 
diff --git a/libctf/testsuite/libctf-lookup/struct-iteration-ctf.c b/libctf/testsuite/libctf-lookup/struct-iteration-ctf.c
new file mode 100644
index 00000000000..7df67adaad4
--- /dev/null
+++ b/libctf/testsuite/libctf-lookup/struct-iteration-ctf.c
@@ -0,0 +1,28 @@ 
+#include <unistd.h>
+
+struct foo_t
+{
+  int foo;
+  size_t bar;
+  const char *baz;
+  struct foo_t *self;
+  union
+  {
+    double should_not_appear;
+    char *nor_should_this;
+  } named;
+  struct
+  {
+    long unnamed_sub_member;
+    union
+    {
+      double one_more_level;
+      long yes_really_one_more;
+    };
+  };
+  struct {};		/* Empty ones */
+  union {};
+  int after_the_end;
+};
+
+struct foo_t used;
diff --git a/libctf/testsuite/libctf-lookup/struct-iteration.c b/libctf/testsuite/libctf-lookup/struct-iteration.c
new file mode 100644
index 00000000000..03750604ecf
--- /dev/null
+++ b/libctf/testsuite/libctf-lookup/struct-iteration.c
@@ -0,0 +1,92 @@ 
+#include <ctf-api.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+static int
+print_struct (const char *name, ctf_id_t membtype, unsigned long offset,
+              void *fp_)
+{
+  ctf_dict_t *fp = (ctf_dict_t *) fp_;
+  char *type_name = ctf_type_aname (fp, membtype);
+
+  printf ("iter test: %s, offset %lx, has type %lx/%s\n",
+          name, offset, membtype, type_name);
+  free (type_name);
+
+  return 0;
+}
+
+int
+main (int argc, char *argv[])
+{
+  ctf_dict_t *fp;
+  ctf_archive_t *ctf;
+  ctf_id_t type;
+  ctf_next_t *i = NULL;
+  const char *name;
+  ctf_id_t membtype;
+  ssize_t offset;
+  ssize_t icount = 0;
+  int err;
+
+  if (argc != 2)
+    {
+      fprintf (stderr, "Syntax: %s PROGRAM\n", argv[0]);
+      exit(1);
+    }
+
+  if ((ctf = ctf_open (argv[1], NULL, &err)) == NULL)
+    goto open_err;
+  if ((fp = ctf_dict_open (ctf, NULL, &err)) == NULL)
+    goto open_err;
+
+  /* Iterate over the structure members with each iterator type in turn.  */
+
+  if ((type = ctf_lookup_by_name (fp, "struct foo_t") ) == CTF_ERR)
+    goto err;
+
+  if (ctf_member_iter (fp, type, print_struct, fp) < 0)
+    goto ierr;
+
+  while ((offset = ctf_member_next (fp, type, &i, &name, &membtype,
+				    CTF_MN_RECURSE)) >= 0)
+    {
+      char *type_name = ctf_type_aname (fp, membtype);
+
+      printf ("next test: %s, offset %lx, has type %lx/%s\n",
+              name, offset, membtype, type_name);
+      free (type_name);
+    }
+  if (ctf_errno (fp) != ECTF_NEXT_END)
+    goto nerr;
+
+  /* Now make sure the count of members does not include any recursive
+     members.  */
+  while ((offset = ctf_member_next (fp, type, &i, &name, &membtype, 0)) >= 0)
+    icount++;
+
+  if (ctf_errno (fp) != ECTF_NEXT_END)
+    goto nerr;
+
+  if (icount != ctf_member_count (fp, type))
+    printf ("member counts differ: %li by direct iteration, "
+	    "%li by ctf_member_count\n", icount, ctf_member_count (fp, type));
+
+  ctf_dict_close (fp);
+  ctf_close (ctf);
+
+  return 0;
+
+ open_err:
+  fprintf (stderr, "%s: cannot open: %s\n", argv[0], ctf_errmsg (err));
+  return 1;
+ err:
+  fprintf (stderr, "Lookup failed: %s\n", ctf_errmsg (ctf_errno (fp)));
+  return 1;
+ ierr:
+  fprintf (stderr, "_iter iteration failed: %s\n", ctf_errmsg (ctf_errno (fp)));
+  return 1;
+ nerr:
+  fprintf (stderr, "_next iteration failed: %s\n", ctf_errmsg (ctf_errno (fp)));
+  return 1;
+}
diff --git a/libctf/testsuite/libctf-lookup/struct-iteration.lk b/libctf/testsuite/libctf-lookup/struct-iteration.lk
new file mode 100644
index 00000000000..fd644547f20
--- /dev/null
+++ b/libctf/testsuite/libctf-lookup/struct-iteration.lk
@@ -0,0 +1,24 @@ 
+# source: struct-iteration-ctf.c
+# link: on
+iter test: foo, offset [0-9a-f]*, has type [0-9a-f]*/int
+iter test: bar, offset [0-9a-f]*, has type [0-9a-f]*/size_t
+iter test: baz, offset [0-9a-f]*, has type [0-9a-f]*/const char \*
+iter test: self, offset [0-9a-f]*, has type [0-9a-f]*/struct foo_t \*
+iter test: named, offset [0-9a-f]*, has type [0-9a-f]*/union 
+iter test: , offset [0-9a-f]*, has type [0-9a-f]*/struct 
+iter test: , offset [0-9a-f]*, has type [0-9a-f]*/struct 
+iter test: , offset [0-9a-f]*, has type [0-9a-f]*/union 
+iter test: after_the_end, offset [0-9a-f]*, has type [0-9a-f]*/int
+next test: foo, offset [0-9a-f]*, has type [0-9a-f]*/int
+next test: bar, offset [0-9a-f]*, has type [0-9a-f]*/size_t
+next test: baz, offset [0-9a-f]*, has type [0-9a-f]*/const char \*
+next test: self, offset [0-9a-f]*, has type [0-9a-f]*/struct foo_t \*
+next test: named, offset [0-9a-f]*, has type [0-9a-f]*/union 
+next test: , offset [0-9a-f]*, has type [0-9a-f]*/struct 
+next test: unnamed_sub_member, offset [0-9a-f]*, has type [0-9a-f]*/long int
+next test: , offset [0-9a-f]*, has type [0-9a-f]*/union 
+next test: one_more_level, offset [0-9a-f]*, has type [0-9a-f]*/double
+next test: yes_really_one_more, offset [0-9a-f]*, has type [0-9a-f]*/long int
+next test: , offset [0-9a-f]*, has type [0-9a-f]*/struct 
+next test: , offset [0-9a-f]*, has type [0-9a-f]*/union 
+next test: after_the_end, offset [0-9a-f]*, has type [0-9a-f]*/int
diff --git a/libctf/testsuite/libctf-lookup/struct-lookup.c b/libctf/testsuite/libctf-lookup/struct-lookup.c
new file mode 100644
index 00000000000..9b95317bfa0
--- /dev/null
+++ b/libctf/testsuite/libctf-lookup/struct-lookup.c
@@ -0,0 +1,60 @@ 
+#include <ctf-api.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+int
+main (int argc, char *argv[])
+{
+  ctf_dict_t *fp;
+  ctf_archive_t *ctf;
+  ctf_id_t type;
+  char *type_name;
+  ctf_membinfo_t mi;
+  int err;
+
+  if (argc != 2)
+    {
+      fprintf (stderr, "Syntax: %s PROGRAM\n", argv[0]);
+      exit(1);
+    }
+
+  if ((ctf = ctf_open (argv[1], NULL, &err)) == NULL)
+    goto open_err;
+  if ((fp = ctf_dict_open (ctf, NULL, &err)) == NULL)
+    goto open_err;
+
+  /* Dig out some strucutre members by name.  */
+
+  if ((type = ctf_lookup_by_name (fp, "struct foo_t") ) == CTF_ERR)
+    goto err;
+
+  if (ctf_member_info (fp, type, "baz", &mi) < 0)
+    goto err;
+
+  type_name = ctf_type_aname (fp, mi.ctm_type);
+  printf ("baz is of type %s, at offset %lx\n", type_name, mi.ctm_offset);
+  free (type_name);
+
+  if (ctf_member_info (fp, type, "one_more_level", &mi) < 0)
+    goto err;
+
+  type_name = ctf_type_aname (fp, mi.ctm_type);
+  printf ("one_more_level is of type %s, at offset %lx\n", type_name, mi.ctm_offset);
+  free (type_name);
+
+  if (ctf_member_info (fp, type, "should_not_appear", &mi) >= 0
+      || ctf_errno (fp) != ECTF_NOMEMBNAM)
+    fprintf (stderr, "should_not_appear appeared.\n");
+
+  ctf_dict_close (fp);
+  ctf_close (ctf);
+
+  return 0;
+
+ open_err:
+  fprintf (stderr, "%s: cannot open: %s\n", argv[0], ctf_errmsg (err));
+  return 1;
+ err:
+  fprintf (stderr, "Lookup failed: %s\n", ctf_errmsg (ctf_errno (fp)));
+  return 1;
+}
diff --git a/libctf/testsuite/libctf-lookup/struct-lookup.lk b/libctf/testsuite/libctf-lookup/struct-lookup.lk
new file mode 100644
index 00000000000..b8488237489
--- /dev/null
+++ b/libctf/testsuite/libctf-lookup/struct-lookup.lk
@@ -0,0 +1,4 @@ 
+# source: struct-iteration-ctf.c
+# link: on
+baz is of type const char \*, at offset [0-9a-z]*
+one_more_level is of type double, at offset [0-9a-z]*