Files
binutils-gdb/libctf/testsuite/libctf-lookup/enumerator-iteration.c
Nick Alcock 6da9267482 libctf, include: add ctf_dict_set_flag: less enum dup checking by default
The recent change to detect duplicate enum values and return ECTF_DUPLICATE
when found turns out to perturb a great many callers.  In particular, the
pahole-created kernel BTF has the same problem we historically did, and
gleefully emits duplicated enum constants in profusion.  Handling the
resulting duplicate errors from BTF -> CTF converters reasonably is
unreasonably difficult (it amounts to forcing them to skip some types or
reimplement the deduplicator).

So let's step back a bit.  What we care about mostly is that the
deduplicator treat enums with conflicting enumeration constants as
conflicting types: programs that want to look up enumeration constant ->
value mappings using the new APIs to do so might well want the same checks
to apply to any ctf_add_* operations they carry out (and since they're
*using* the new APIs, added at the same time as this restriction was
imposed, there is likely to be no negative consequence of this).

So we want some way to allow processes that know about duplicate detection
to opt into it, while allowing everyone else to stay clear of it: but we
want ctf_link to get this behaviour even if its caller has opted out.

So add a new concept to the API: dict-wide CTF flags, set via
ctf_dict_set_flag, obtained via ctf_dict_get_flag.  They are not bitflags
but simple arbitrary integers and an on/off value, stored in an unspecified
manner (the one current flag, we translate into an LCTF_* flag value in the
internal ctf_dict ctf_flags word). If you pass in an invalid flag or value
you get a new ECTF_BADFLAG error, so the caller can easily tell whether
flags added in future are valid with a particular libctf or not.

We check this flag in ctf_add_enumerator, and set it around the link
(including on child per-CU dicts).  The newish enumerator-iteration test is
souped up to check the semantics of the flag as well.

The fact that the flag can be set and unset at any time has curious
consequences. You can unset the flag, insert a pile of duplicates, then set
it and expect the new duplicates to be detected, not only by
ctf_add_enumerator but also by ctf_lookup_enumerator.  This means we now
have to maintain the ctf_names and conflicting_enums enum-duplication
tracking as new enums are added, not purely as the dict is opened.
Move that code out of init_static_types_internal and into a new
ctf_track_enumerator function that addition can also call.

(None of this affects the file format or serialization machinery, which has
to be able to handle duplicate enumeration constants no matter what.)

include/
	* ctf-api.h (CTF_ERRORS) [ECTF_BADFLAG]: New.
	(ECTF_NERR): Update.
	(CTF_STRICT_NO_DUP_ENUMERATORS): New flag.
	(ctf_dict_set_flag): New function.
	(ctf_dict_get_flag): Likewise.

libctf/
	* ctf-impl.h (LCTF_STRICT_NO_DUP_ENUMERATORS): New flag.
	(ctf_track_enumerator): Declare.
	* ctf-dedup.c (ctf_dedup_emit_type): Set it.
	* ctf-link.c (ctf_create_per_cu): Likewise.
	(ctf_link_deduplicating_per_cu): Likewise.
	(ctf_link): Likewise.
	(ctf_link_write): Likewise.
	* ctf-subr.c (ctf_dict_set_flag): New function.
	(ctf_dict_get_flag): New function.
	* ctf-open.c (init_static_types_internal): Move enum tracking to...
	* ctf-create.c (ctf_track_enumerator): ... this new function.
	(ctf_add_enumerator): Call it.
	* libctf.ver: Add the new functions.
	* testsuite/libctf-lookup/enumerator-iteration.c: Test them.
2024-07-31 21:02:05 +01:00

229 lines
7.3 KiB
C

/* 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 %li\n", name,
foo = ctf_type_aname (fp, type), (long int) 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;
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_2, 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.
This also tests that you can add to enums under iteration without causing
disaster. */
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;
int dynadd2_value;
int old_dynadd2_flag;
/* Make sure that getting and setting a garbage flag, and setting one to a
garbage value, fails properly. */
if (ctf_dict_set_flag (fp, CTF_STRICT_NO_DUP_ENUMERATORS, 666) >= 0
|| ctf_errno (fp) != ECTF_BADFLAG)
fprintf (stderr, "Invalid flag value setting did not fail as it ought to\n");
if (ctf_dict_set_flag (fp, 0, 1) >= 0 || ctf_errno (fp) != ECTF_BADFLAG)
fprintf (stderr, "Invalid flag setting did not fail as it ought to\n");
if (ctf_dict_get_flag (fp, 0) >= 0 || ctf_errno (fp) != ECTF_BADFLAG)
fprintf (stderr, "Invalid flag getting did not fail as it ought to\n");
/* Set it strict for now. */
if (ctf_dict_set_flag (fp, CTF_STRICT_NO_DUP_ENUMERATORS, 1) < 0)
goto set_flag_err;
printf ("IENUMSAMPLE2_2 in %s has value %li\n",
foo = ctf_type_aname (fp, type), (long int) 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;
dynadd2_value = counter;
/* 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");
/* Make sure that it still fails if you set an enum value to the value it
already has. */
if (ctf_add_enumerator (fp, type, "DYNADD2", dynadd2_value) >= 0
|| ctf_errno (fp) != ECTF_DUPLICATE)
fprintf (stderr, "Duplicate enumerator addition did not fail as it ought to\n");
/* Flip the strict flag and try again. This time, it should succeed. */
if ((old_dynadd2_flag = ctf_dict_get_flag (fp, CTF_STRICT_NO_DUP_ENUMERATORS)) < 0)
goto get_flag_err;
if (ctf_dict_set_flag (fp, CTF_STRICT_NO_DUP_ENUMERATORS, 0) < 0)
goto set_flag_err;
if (ctf_add_enumerator (fp, type, "DYNADD2", dynadd2_value) < 0)
goto enumerator_add_err;
/* Flip it again and try *again*. This time it should fail again. */
if (ctf_dict_set_flag (fp, CTF_STRICT_NO_DUP_ENUMERATORS, old_dynadd2_flag) < 0)
goto set_flag_err;
if (ctf_add_enumerator (fp, type, "DYNADD2", dynadd2_value) >= 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. DYNADD2 should fail because
it has duplicate enumerators. */
if (ctf_lookup_enumerator (fp, "DYNADD", &val) == CTF_ERR)
goto enumerator_lookup_err;
printf ("direct lookup: DYNADD value: %i\n", (int) val);
if ((err = ctf_lookup_enumerator (fp, "DYNADD2", &val)) >= 0 ||
ctf_errno (fp) != ECTF_DUPLICATE)
fprintf (stderr, "Duplicate enumerator lookup did not fail as it ought to: %i, %s\n",
err, ctf_errmsg (ctf_errno (fp)));
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 %li in %s\n",
(long int) 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_errmsg (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;
get_flag_err:
fprintf (stderr, "ctf_dict_get_flag failed: %s\n", ctf_errmsg (ctf_errno (fp)));
return 1;
set_flag_err:
fprintf (stderr, "ctf_dict_set_flag failed: %s\n", ctf_errmsg (ctf_errno (fp)));
return 1;
}