libctf, include: new functions for looking up enumerators

Three new functions for looking up the enum type containing a given
enumeration constant, and optionally that constant's value.

The simplest, ctf_lookup_enumerator, looks up a root-visible enumerator by
name in one dict: if the dict contains multiple such constants (which is
possible for dicts created by older versions of the libctf deduplicator),
ECTF_DUPLICATE is returned.

The next simplest, ctf_lookup_enumerator_next, is an iterator which returns
all enumerators with a given name in a given dict, whether root-visible or
not.

The most elaborate, ctf_arc_lookup_enumerator_next, finds all
enumerators with a given name across all dicts in an entire CTF archive,
whether root-visible or not, starting looking in the shared parent dict;
opened dicts are cached (as with all other ctf_arc_*lookup functions) so
that repeated use does not incur repeated opening costs.

All three of these return enumerator values as int64_t: unfortunately, API
compatibility concerns prevent us from doing the same with the other older
enum-related functions, which all return enumerator constant values as ints.
We may be forced to add symbol-versioning compatibility aliases that fix the
other functions in due course, bumping the soname for platforms that do not
support such things.

ctf_arc_lookup_enumerator_next is implemented as a nested ctf_archive_next
iterator, and inside that, a nested ctf_lookup_enumerator_next iterator
within each dict.  To aid in this, add support to ctf_next_t iterators for
iterators that are implemented in terms of two simultaneous nested iterators
at once.  (It has always been possible for callers to use as many nested or
semi-overlapping ctf_next_t iterators as they need, which is one of the
advantages of this style over the _iter style that calls a function for each
thing iterated over: the iterator change here permits *ctf_next_t iterators
themselves* to be implemented by iterating using multiple other iterators as
part of their internal operation, transparently to the caller.)

Also add a testcase that tests all these functions (which is fairly easy
because ctf_arc_lookup_enumerator_next is implemented in terms of
ctf_lookup_enumerator_next) in addition to enumeration addition in
ctf_open()ed dicts, ctf_add_enumerator duplicate enumerator addition, and
conflicting enumerator constant deduplication.

include/
	* ctf-api.h (ctf_lookup_enumerator): New.
	(ctf_lookup_enumerator_next): Likewise.
	(ctf_arc_lookup_enumerator_next): Likewise.

libctf/
	* libctf.ver: Add them.
	* ctf-impl.h (ctf_next_t) <ctn_next_inner>: New.
	* ctf-util.c (ctf_next_copy): Copy it.
        (ctf_next_destroy): Destroy it.
	* ctf-lookup.c (ctf_lookup_enumerator): New.
	(ctf_lookup_enumerator_next): New.
	* ctf-archive.c (ctf_arc_lookup_enumerator_next): New.
	* testsuite/libctf-lookup/enumerator-iteration.*: New test.
	* testsuite/libctf-lookup/enum-ctf-2.c: New test CTF, used by the
	  above.
This commit is contained in:
Nick Alcock
2024-06-11 20:58:00 +01:00
parent 1f62f2a9b5
commit 2fa4b6e6df
9 changed files with 520 additions and 9 deletions

View File

@@ -413,6 +413,151 @@ ctf_lookup_variable (ctf_dict_t *fp, const char *name)
return type;
}
/* Look up a single enumerator by enumeration constant name. Returns the ID of
the enum it is contained within and optionally its value. Error out with
ECTF_DUPLICATE if multiple exist (which can happen in some older dicts). See
ctf_lookup_enumerator_next in that case. Enumeration constants in non-root
types are not returned, but constants in parents are, if not overridden by
an enum in the child.. */
ctf_id_t
ctf_lookup_enumerator (ctf_dict_t *fp, const char *name, int64_t *enum_value)
{
ctf_id_t type;
int enum_int_value;
if (ctf_dynset_lookup (fp->ctf_conflicting_enums, name))
return (ctf_set_typed_errno (fp, ECTF_DUPLICATE));
/* CTF_K_UNKNOWN suffices for things like enumeration constants that aren't
actually types at all (ending up in the global name table). */
type = ctf_lookup_by_rawname (fp, CTF_K_UNKNOWN, name);
/* Nonexistent type? It may be in the parent. */
if (type == 0 && fp->ctf_parent)
{
if ((type = ctf_lookup_enumerator (fp->ctf_parent, name, enum_value)) == 0)
return ctf_set_typed_errno (fp, ECTF_NOENUMNAM);
return type;
}
/* Nothing more to do if this type didn't exist or we don't have to look up
the enum value. */
if (type == 0)
return ctf_set_typed_errno (fp, ECTF_NOENUMNAM);
if (enum_value == NULL)
return type;
if (ctf_enum_value (fp, type, name, &enum_int_value) < 0)
return CTF_ERR;
*enum_value = enum_int_value;
return type;
}
/* Return all enumeration constants with a given name in a given dict, similar
to ctf_lookup_enumerator above but capable of returning multiple values.
Enumerators in parent dictionaries are not returned: enumerators in
hidden types *are* returned. */
ctf_id_t
ctf_lookup_enumerator_next (ctf_dict_t *fp, const char *name,
ctf_next_t **it, int64_t *val)
{
ctf_next_t *i = *it;
int found = 0;
/* We use ctf_type_next() to iterate across all types, but then traverse each
enumerator found by hand: traversing enumerators is very easy, and it would
probably be more confusing to use two nested iterators than to do it this
way. We use ctn_next to work over enums, then ctn_en and ctn_n to work
over enumerators within each enum. */
if (!i)
{
if ((i = ctf_next_create ()) == NULL)
return ctf_set_typed_errno (fp, ENOMEM);
i->cu.ctn_fp = fp;
i->ctn_iter_fun = (void (*) (void)) ctf_lookup_enumerator_next;
i->ctn_increment = 0;
i->ctn_tp = NULL;
i->u.ctn_en = NULL;
i->ctn_n = 0;
*it = i;
}
if ((void (*) (void)) ctf_lookup_enumerator_next != i->ctn_iter_fun)
return (ctf_set_typed_errno (fp, ECTF_NEXT_WRONGFUN));
if (fp != i->cu.ctn_fp)
return (ctf_set_typed_errno (fp, ECTF_NEXT_WRONGFP));
do
{
const char *this_name;
/* At end of enum? Traverse to next one, if any are left. */
if (i->u.ctn_en == NULL || i->ctn_n == 0)
{
const ctf_type_t *tp;
ctf_dtdef_t *dtd;
do
i->ctn_type = ctf_type_next (i->cu.ctn_fp, &i->ctn_next, NULL, 1);
while (i->ctn_type != CTF_ERR
&& ctf_type_kind_unsliced (i->cu.ctn_fp, i->ctn_type)
!= CTF_K_ENUM);
if (i->ctn_type == CTF_ERR)
{
/* Conveniently, when the iterator over all types is done, so is the
iteration as a whole: so we can just pass all errors from the
internal iterator straight back out.. */
ctf_next_destroy (i);
*it = NULL;
return CTF_ERR; /* errno is set for us. */
}
if ((tp = ctf_lookup_by_id (&fp, i->ctn_type)) == NULL)
return CTF_ERR; /* errno is set for us. */
i->ctn_n = LCTF_INFO_VLEN (fp, tp->ctt_info);
dtd = ctf_dynamic_type (fp, i->ctn_type);
if (dtd == NULL)
{
(void) ctf_get_ctt_size (fp, tp, NULL, &i->ctn_increment);
i->u.ctn_en = (const ctf_enum_t *) ((uintptr_t) tp +
i->ctn_increment);
}
else
i->u.ctn_en = (const ctf_enum_t *) dtd->dtd_vlen;
}
this_name = ctf_strptr (fp, i->u.ctn_en->cte_name);
i->ctn_n--;
if (strcmp (name, this_name) == 0)
{
if (val)
*val = i->u.ctn_en->cte_value;
found = 1;
/* Constant found in this enum: try the next one. (Constant names
cannot be duplicated within a given enum.) */
i->ctn_n = 0;
}
i->u.ctn_en++;
}
while (!found);
return i->ctn_type;
}
typedef struct ctf_symidx_sort_arg_cb
{
ctf_dict_t *fp;