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
No known key found for this signature in database
9 changed files with 520 additions and 9 deletions

View File

@ -25,6 +25,7 @@
#define _CTF_API_H
#include <sys/types.h>
#include <inttypes.h>
#include <ctf.h>
#include <zlib.h>
@ -538,6 +539,16 @@ extern ctf_id_t ctf_lookup_by_name (ctf_dict_t *, const char *);
extern ctf_id_t ctf_lookup_variable (ctf_dict_t *, const char *);
/* 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. */
extern ctf_id_t ctf_lookup_enumerator (ctf_dict_t *, const char *,
int64_t *enum_value);
/* Type lookup functions. */
/* Strip qualifiers and typedefs off a type, returning the base type.
@ -669,6 +680,33 @@ 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 *);
/* 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 non-root
types *are* returned. This operation internally iterates over all types in
the dict, so is relatively expensive in large dictionaries.
There is nothing preventing NAME from being changed by the caller in the
middle of iteration: the results might be slightly confusing, but they are
well-defined. */
extern ctf_id_t ctf_lookup_enumerator_next (ctf_dict_t *, const char *name,
ctf_next_t **, int64_t *enum_value);
/* Likewise, across all dicts in an archive (parent first). The DICT and ERRP
arguments are not optional: without the forer you can't tell which dict the
returned type is in, and without the latter you can't distinguish real errors
from end-of-iteration. DICT should be NULL before the first call and is set
to NULL after the last and on error: on successful call it is set to the dict
containing the returned enum, and it is the caller's responsibility to
ctf_dict_close() it. The caller should otherwise pass it back in unchanged
(do not reassign it during iteration, just as with the ctf_next_t iterator
itself). */
extern ctf_id_t ctf_arc_lookup_enumerator_next (ctf_archive_t *, const char *name,
ctf_next_t **, int64_t *enum_value,
ctf_dict_t **dict, int *errp);
/* Iterate over all types in a dict. ctf_type_iter_all recurses over all types:
ctf_type_iter recurses only over types with user-visible names (for which
CTF_ADD_ROOT was passed). All such types are returned, even if they are

View File

@ -1011,6 +1011,113 @@ ctf_arc_lookup_symbol_name (ctf_archive_t *wrapper, const char *symname,
return ctf_arc_lookup_sym_or_name (wrapper, 0, symname, typep, errp);
}
/* Return all enumeration constants with a given NAME across all dicts in an
archive, similar to ctf_lookup_enumerator_next. The DICT is cached, so
opening costs are paid only once, but (unlike ctf_arc_lookup_symbol*
above) the results of the iterations are not cached. dict and errp are
not optional. */
ctf_id_t
ctf_arc_lookup_enumerator_next (ctf_archive_t *arc, const char *name,
ctf_next_t **it, int64_t *enum_value,
ctf_dict_t **dict, int *errp)
{
ctf_next_t *i = *it;
ctf_id_t type;
int opened_this_time = 0;
int err;
/* We have two nested iterators in here: ctn_next tracks archives, while
within it ctn_next_inner tracks enumerators within an archive. We
keep track of the dict by simply reusing the passed-in arg: if it's
changed by the caller, the caller will get an ECTF_WRONGFP error,
so this is quite safe and means we don't have to track the arc and fp
simultaneously in the ctf_next_t. */
if (!i)
{
if ((i = ctf_next_create ()) == NULL)
{
err = ENOMEM;
goto err;
}
i->ctn_iter_fun = (void (*) (void)) ctf_arc_lookup_enumerator_next;
i->cu.ctn_arc = arc;
*it = i;
}
if ((void (*) (void)) ctf_arc_lookup_enumerator_next != i->ctn_iter_fun)
{
err = ECTF_NEXT_WRONGFUN;
goto err;
}
if (arc != i->cu.ctn_arc)
{
err = ECTF_NEXT_WRONGFP;
goto err;
}
/* Prevent any earlier end-of-iteration on this dict from confusing the
test below. */
if (i->ctn_next != NULL)
ctf_set_errno (*dict, 0);
do
{
/* At end of one dict, or not started any iterations yet?
Traverse to next dict. If we never returned this dict to the
caller, close it ourselves: the caller will never see it and cannot
do so. */
if (i->ctn_next == NULL || ctf_errno (*dict) == ECTF_NEXT_END)
{
if (opened_this_time)
{
ctf_dict_close (*dict);
*dict = NULL;
opened_this_time = 0;
}
*dict = ctf_archive_next (arc, &i->ctn_next, NULL, 0, &err);
if (!*dict)
goto err;
opened_this_time = 1;
}
type = ctf_lookup_enumerator_next (*dict, name, &i->ctn_next_inner,
enum_value);
}
while (type == CTF_ERR && ctf_errno (*dict) == ECTF_NEXT_END);
if (type == CTF_ERR)
{
err = ctf_errno (*dict);
goto err;
}
/* If this dict is being reused from the previous iteration, bump its
refcnt: the caller is going to close it and has no idea that we didn't
open it this time round. */
if (!opened_this_time)
ctf_ref (*dict);
return type;
err: /* Also ECTF_NEXT_END. */
if (opened_this_time)
{
ctf_dict_close (*dict);
*dict = NULL;
}
ctf_next_destroy (i);
*it = NULL;
if (errp)
*errp = err;
return CTF_ERR;
}
/* Raw iteration over all CTF files in an archive. We pass the raw data for all
CTF files in turn to the specified callback function. */
static int

View File

@ -544,13 +544,15 @@ struct ctf_next
uint32_t ctn_n;
/* Some iterators contain other iterators, in addition to their other
state. */
state. We allow for inner and outer iterators, for two-layer nested loops
like those found in ctf_arc_lookup_enumerator_next. */
ctf_next_t *ctn_next;
ctf_next_t *ctn_next_inner;
/* 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. */
/* We can save space on this side of things by noting that a type 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. */
union
{
unsigned char *ctn_vlen;

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;

View File

@ -262,6 +262,8 @@ ctf_next_destroy (ctf_next_t *i)
free (i->u.ctn_sorted_hkv);
if (i->ctn_next)
ctf_next_destroy (i->ctn_next);
if (i->ctn_next_inner)
ctf_next_destroy (i->ctn_next_inner);
free (i);
}
@ -276,16 +278,35 @@ ctf_next_copy (ctf_next_t *i)
return NULL;
memcpy (i2, i, sizeof (struct ctf_next));
if (i2->ctn_next)
{
i2->ctn_next = ctf_next_copy (i2->ctn_next);
if (i2->ctn_next == NULL)
goto err_next;
}
if (i2->ctn_next_inner)
{
i2->ctn_next_inner = ctf_next_copy (i2->ctn_next_inner);
if (i2->ctn_next_inner == NULL)
goto err_next_inner;
}
if (i2->ctn_iter_fun == (void (*) (void)) ctf_dynhash_next_sorted)
{
size_t els = ctf_dynhash_elements ((ctf_dynhash_t *) i->cu.ctn_h);
if ((i2->u.ctn_sorted_hkv = calloc (els, sizeof (ctf_next_hkv_t))) == NULL)
{
free (i2);
return NULL;
}
goto err_sorted_hkv;
memcpy (i2->u.ctn_sorted_hkv, i->u.ctn_sorted_hkv,
els * sizeof (ctf_next_hkv_t));
}
return i2;
err_sorted_hkv:
ctf_next_destroy (i2->ctn_next_inner);
err_next_inner:
ctf_next_destroy (i2->ctn_next);
err_next:
ctf_next_destroy (i2);
return NULL;
}

View File

@ -198,3 +198,10 @@ LIBCTF_1.2 {
ctf_arc_lookup_symbol_name;
ctf_add_unknown;
} LIBCTF_1.1;
LIBCTF_1.3 {
global:
ctf_lookup_enumerator;
ctf_lookup_enumerator_next;
ctf_arc_lookup_enumerator_next;
} LIBCTF_1.2;

View File

@ -0,0 +1,6 @@
enum e { ENUMSAMPLE_1 = 6, ENUMSAMPLE_2 = 7 };
enum ie2 { IENUMSAMPLE2_1 = -10, IENUMSAMPLE2_2 };
enum e baz;
enum ie2 quux;

View File

@ -0,0 +1,168 @@
/* Test enumerator iteration and querying. Because
ctf_arc_lookup_enumerator_next uses ctf_lookup_enumerator_next internally, we
only need to test the former. */
#include "config.h"
#include <ctf-api.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void
print_constants (ctf_archive_t *ctf, const char *name)
{
ctf_next_t *i = NULL;
int err;
ctf_dict_t *fp;
ctf_id_t type;
int64_t val;
while ((type = ctf_arc_lookup_enumerator_next (ctf, name, &i,
&val, &fp, &err)) != CTF_ERR)
{
char *foo;
printf ("%s in %s has value %i\n", name,
foo = ctf_type_aname (fp, type), val);
free (foo);
ctf_dict_close (fp);
}
if (err != ECTF_NEXT_END)
{
fprintf (stderr, "iteration failed: %s\n", ctf_errmsg (err));
exit (1);
}
}
int
main (int argc, char *argv[])
{
ctf_archive_t *ctf;
ctf_dict_t *fp;
int err;
ctf_id_t type;
ctf_next_t *i = NULL;
const char *name;
int64_t val;
int counter = 0;
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;
/* Look for all instances of ENUMSAMPLE2_1, and add some new enums to all
dicts found, to test dynamic enum iteration as well as static.
Add two enums with a different name and constants to any that should
already be there (one hidden), and one with the same constants, but hidden,
to test ctf_lookup_enumerator_next()'s multiple-lookup functionality and
ctf_lookup_enumerator() in the presence of hidden types. */
printf ("First iteration: addition of enums.\n");
while ((type = ctf_arc_lookup_enumerator_next (ctf, "IENUMSAMPLE2_2", &i,
&val, &fp, &err)) != CTF_ERR)
{
char *foo;
printf ("IENUMSAMPLE2_2 in %s has value %i\n",
foo = ctf_type_aname (fp, type), val);
free (foo);
if ((type = ctf_add_enum (fp, CTF_ADD_ROOT, "ie3")) == CTF_ERR)
goto enum_add_err;
if (ctf_add_enumerator (fp, type, "DYNADD", counter += 10) < 0)
goto enumerator_add_err;
if (ctf_add_enumerator (fp, type, "DYNADD2", counter += 10) < 0)
goto enumerator_add_err;
/* Make sure that overlapping enumerator addition fails as it should. */
if (ctf_add_enumerator (fp, type, "IENUMSAMPLE2_2", 666) >= 0
|| ctf_errno (fp) != ECTF_DUPLICATE)
fprintf (stderr, "Duplicate enumerator addition did not fail as it ought to\n");
if ((type = ctf_add_enum (fp, CTF_ADD_NONROOT, "ie4_hidden")) == CTF_ERR)
goto enum_add_err;
if (ctf_add_enumerator (fp, type, "DYNADD3", counter += 10) < 0)
goto enumerator_add_err;
if (ctf_add_enumerator (fp, type, "DYNADD4", counter += 10) < 0)
goto enumerator_add_err;
if ((type = ctf_add_enum (fp, CTF_ADD_NONROOT, "ie3_hidden")) == CTF_ERR)
goto enum_add_err;
if (ctf_add_enumerator (fp, type, "DYNADD", counter += 10) < 0)
goto enumerator_add_err;
if (ctf_add_enumerator (fp, type, "DYNADD2", counter += 10) < 0)
goto enumerator_add_err;
/* Look them up via ctf_lookup_enumerator. */
if (ctf_lookup_enumerator (fp, "DYNADD", &val) == CTF_ERR)
goto enumerator_lookup_err;
printf ("direct lookup: DYNADD value: %i\n", (int) val);
if ((type = ctf_lookup_enumerator (fp, "DYNADD3", &val) != CTF_ERR) ||
ctf_errno (fp) != ECTF_NOENUMNAM)
{
if (type != CTF_ERR)
{
char *foo;
printf ("direct lookup: hidden lookup did not return ECTF_NOENUMNAM but rather %i in %s\n",
val, foo = ctf_type_aname (fp, type));
free (foo);
}
else
printf ("direct lookup: hidden lookup did not return ECTF_NOENUMNAM but rather %s\n",
ctf_errno (fp));
}
ctf_dict_close (fp);
}
if (err != ECTF_NEXT_END)
{
fprintf (stderr, "iteration failed: %s\n", ctf_errmsg (err));
return 1;
}
/* Look for (and print out) some enumeration constants. */
printf ("Second iteration: printing of enums.\n");
print_constants (ctf, "ENUMSAMPLE_1");
print_constants (ctf, "IENUMSAMPLE_1");
print_constants (ctf, "ENUMSAMPLE_2");
print_constants (ctf, "DYNADD");
print_constants (ctf, "DYNADD3");
ctf_close (ctf);
printf ("All done.\n");
return 0;
open_err:
fprintf (stderr, "%s: cannot open: %s\n", argv[0], ctf_errmsg (err));
return 1;
enum_add_err:
fprintf (stderr, "Cannot add enum to dict \"%s\": %s\n",
ctf_cuname (fp) ? ctf_cuname (fp) : "(null: parent)", ctf_errmsg (ctf_errno (fp)));
return 1;
enumerator_add_err:
fprintf (stderr, "Cannot add enumerator to dict \"%s\": %s\n",
ctf_cuname (fp) ? ctf_cuname (fp) : "(null: parent)", ctf_errmsg (ctf_errno (fp)));
return 1;
enumerator_lookup_err:
fprintf (stderr, "Cannot look up enumerator in dict \"%s\": %s\n",
ctf_cuname (fp) ? ctf_cuname (fp) : "(null: parent)", ctf_errmsg (ctf_errno (fp)));
return 1;
}

View File

@ -0,0 +1,17 @@
# lookup: enumerator-iteration.c
# source: enum-ctf.c
# source: enum-ctf-2.c
# link: on
First iteration: addition of enums.
IENUMSAMPLE2_2 in enum ie2 has value -9
direct lookup: DYNADD value: 10
Second iteration: printing of enums.
ENUMSAMPLE_1 in enum e has value 6
ENUMSAMPLE_1 in enum e has value 0
IENUMSAMPLE_1 in enum ie has value -10
ENUMSAMPLE_2 in enum e has value 7
ENUMSAMPLE_2 in enum e has value 1
DYNADD in enum ie3 has value 10
DYNADD in enum ie3_hidden has value 50
DYNADD3 in enum ie4_hidden has value 30
All done.