Files
binutils-gdb/libctf/testsuite/libctf-writable/error-propagation.c
Nick Alcock a480362d88 libctf: string: refs rework
This commit moves provisional (not-yet-serialized) string refs towards the
scheme to be used for CTF IDs in the future.  In particular

 - provisional string offsets now count downwards from just under the
   external string offset space (all bits on but the high bit).  This makes
   it possible to detect an overflowing strtab, and also makes it trivial to
   determine whether any string offset (ref) updates were missed -- where
   before we might get a slightly corrupted or incorrect string, we now get
   a huge high strtab offset corresponding to no string, and an error is
   emitted at read time.

 - refs are emitted at serialization time during the pass through the types.
   They are strictly associated with the newly-written-out buffer: the
   existing opened CTF dict is not changed, though it does still get the new
   strtab so that new refs to the same string can just refer directly to it.
   The provisional strtab hash table that contains these strings is not
   deleted after serialization (because we might serialize again): instead,
   we keep track in the parent of the lowest-yet-used ("latest") provisional
   strtab offset, and any strtab offset above that, but not external
   (high-bit-on) is considered provisional.

   This is sort-of-enforced by moving most of the ref-addition function
   declarations (including ctf_str_add_ref) to a new ctf-ref.h, which is
   not included by ctf-create.c or ctf-open.c.

 - because we don't add refs when adding types, we don't need to handle the
   case where we add things to expanding vlens (enums, struct members) and
   have to realloc() them.  So the entire painful movable refs system can
   just be deleted, along with the ability to remove refs piecemeal at all
   (purging all of them is still possible).  Strings added during type
   addition are added via ctf_str_add(), which adds no refs: the strings are
   picked up at serialization time and refs to their final, serialized
   resting place added.  The DTDs never have any refs in them, and their
   provisional strtab offsets are never updated by the ref system.

This caused several bugs to fall out of the earlier work and get fixed.
In particular, attempts to look up a string in a child dict now search
the parent's provisional strtab too: we add some extra special casing
for the null string so we don't need to worry about deduplication
moving it somewhere other than offset zero.

Finally, the optimization that removes an unreferenced synthetic external
strtab (the record of the strings the linker has told us about, kept around
internally for lookup during late serialization) is faulty: references to a
strtab entry will only produce CTF-level refs if their value might change,
and an external string's offset won't change, so it produces no refs: worse
yet, even if we did get a ref (say, if the string was originally believed
to be internal and only later were we told that the linker knew about it
too), when we serialize a strtab, all its refs are dropped (since they've
been updated and can no longer change); so if we serialized it a second
time, its synthetic external strtab would be considered empty and dropped,
even though the same external strings as before still exist, referencing
it.  We must keep the synthetic external strtab around as long as external
strings exist that reference it, i.e. for the life of the dict.

One benefit of all this: now we're emitting provisional string offsets at
a really high value, it's out of the way of the consecutive, deduplicated
string offsets in child dicts.  So we can drop the constraint that you
cannot add strings to a dict with children, which allows us to add types
freely to parent dicts again.  What you can't do is write that dict out
again: when we serialize, we currently update the dict being serialized
with the updated strtabs: when you write a dict out, its provisional
strings become real strings, and suddenly the offsets would overlap once
more.  But opening a dict and its children, adding to it, and then
writing it out again is rare indeed, and we have a workaround: anyone
wanting to do this can just use ctf_link instead.
2025-02-28 15:13:24 +00:00

198 lines
6.0 KiB
C

/* Make sure that errors are propagated properly from parent dicts to children
when errors are encountered in child functions that can recurse to parents.
We check specifically a subset of known-buggy functions.
Functions that require a buggy linker to expose, or that only fail on
assertion-failure-incurring corrupted dicts, are not tested. */
#include <ctf-api.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static const char *desc;
static void
check_prop_err (ctf_dict_t *child, ctf_dict_t *parent, int expected)
{
if (ctf_errno (child) == expected)
return;
if (ctf_errno (parent) == expected)
fprintf (stderr, "%s: error propagation failure: error \"%s\" not seen on child, "
"but instead on parent\n", desc, ctf_errmsg (ctf_errno (parent)));
else
fprintf (stderr, "%s: expected error is entirely lost: "
"\"%s\" seen on parent, \"%s\" on child\n", desc,
ctf_errmsg (ctf_errno (parent)),
ctf_errmsg (ctf_errno (child)));
}
static void
no_prop_err (void)
{
fprintf (stderr, "%s: expected error return not observed.\n", desc);
}
int main (void)
{
ctf_dict_t *parent;
ctf_dict_t *child;
ctf_dict_t *wrong;
ctf_id_t void_id;
ctf_id_t wrong_id;
ctf_id_t base;
ctf_id_t slice;
ctf_id_t function;
ctf_id_t ptr;
ctf_encoding_t long_encoding = { CTF_INT_SIGNED, 0, sizeof (long) };
ctf_encoding_t void_encoding = { CTF_INT_SIGNED, 0, 0 };
ctf_encoding_t foo;
ctf_funcinfo_t fi;
ctf_id_t bar;
int err;
if ((parent = ctf_create (&err)) == NULL
|| (child = ctf_create (&err)) == NULL
|| (wrong = ctf_create (&err)) == NULL)
{
fprintf (stderr, "Cannot create dicts: %s\n", ctf_errmsg (err));
return 1;
}
if ((ctf_import (child, parent)) < 0)
{
fprintf (stderr, "cannot import: %s\n", ctf_errmsg (ctf_errno (child)));
return 1;
}
/* Populate two dicts, one with the same types in a different order. This
passes all ctf_import checks (type and strtab count), but will still
induce errors due to type mismatches with the child. In particular, base
in the right parent is a non-integral type (a pointer) in the wrong one,
and "void" in the parent is an unknown type in the wrong one. */
if ((ctf_add_unknown (parent, CTF_ADD_ROOT, "spacer")) /* 1 */
== CTF_ERR)
goto parent_err;
if ((ctf_add_unknown (parent, CTF_ADD_ROOT, "spacer2")) /* 2 */
== CTF_ERR)
goto parent_err;
if ((void_id = ctf_add_integer (parent, CTF_ADD_ROOT, "void", &void_encoding)) /* 3 */
== CTF_ERR)
goto parent_err;
if ((base = ctf_add_integer (parent, CTF_ADD_ROOT, "long int", &long_encoding)) /* 4 */
== CTF_ERR)
goto parent_err;
if ((ptr = ctf_add_pointer (parent, CTF_ADD_ROOT, void_id)) /* 5 */
== CTF_ERR)
goto parent_err;
if ((ctf_add_integer (wrong, CTF_ADD_ROOT, "long int", &long_encoding)) /* 1 */
== CTF_ERR)
goto parent_err;
if ((wrong_id = ctf_add_integer (wrong, CTF_ADD_ROOT, "void", &void_encoding)) /* 2 */
== CTF_ERR)
goto parent_err;
if ((ctf_add_unknown (parent, CTF_ADD_ROOT, "spacer")) /* 3 */
== CTF_ERR)
goto parent_err;
if ((ptr = ctf_add_pointer (wrong, CTF_ADD_ROOT, wrong_id)) /* 4 */
== CTF_ERR)
goto parent_err;
if ((ctf_add_unknown (wrong, CTF_ADD_ROOT, "spacer2")) /* 5 */
== CTF_ERR)
goto parent_err;
foo.cte_format = 0;
foo.cte_bits = 4;
foo.cte_offset = 4;
if ((slice = ctf_add_slice (child, CTF_ADD_ROOT, base, &foo)) == CTF_ERR)
goto parent_err;
/* Same name as a type: no change in strtab.strlen. */
if (ctf_add_variable (parent, "base", base) < 0)
goto child_err;
fi.ctc_return = void_id;
fi.ctc_argc = 0;
fi.ctc_flags = 0;
if ((function = ctf_add_function (child, CTF_ADD_ROOT, &fi, NULL)) == CTF_ERR)
goto child_err;
desc = "func info lookup of non-function";
if ((ctf_func_type_info (child, base, &fi)) != CTF_ERR)
no_prop_err ();
check_prop_err (child, parent, ECTF_NOTFUNC);
desc = "func args lookup of non-function";
if ((ctf_func_type_args (child, base, 0, &bar)) != CTF_ERR)
no_prop_err ();
check_prop_err (child, parent, ECTF_NOTFUNC);
/* Write out and reopen to get a child with no parent. */
if ((ctf_import (child, wrong)) < 0)
{
fprintf (stderr, "cannot reimport: %s\n", ctf_errmsg (ctf_errno (child)));
return 1;
}
/* This is testing ctf_type_resolve_unsliced(), which is called by the enum
functions (which are not themselves buggy). This type isn't an enum, but
that's OK: we're after an error, after all, and the type we're slicing is
not visible any longer, so nothing can tell it's not an enum. */
desc = "child slice resolution";
if ((ctf_enum_value (child, slice, "foo", NULL)) != CTF_ERR)
no_prop_err ();
check_prop_err (child, wrong, ECTF_NONREPRESENTABLE);
desc = "child slice encoding lookup";
if ((ctf_type_encoding (child, slice, &foo)) != CTF_ERR)
no_prop_err ();
check_prop_err (child, wrong, ECTF_BADID);
desc = "func info lookup of nonrepresentable function";
if ((ctf_func_type_info (child, base, &fi)) != CTF_ERR)
no_prop_err ();
check_prop_err (child, wrong, ECTF_NONREPRESENTABLE);
desc = "func args lookup of nonrepresentable function";
if ((ctf_func_type_args (child, base, 0, &bar)) != CTF_ERR)
no_prop_err ();
check_prop_err (child, wrong, ECTF_NONREPRESENTABLE);
desc = "child slice addition";
if ((slice = ctf_add_slice (child, CTF_ADD_ROOT, base, &foo)) != CTF_ERR)
no_prop_err ();
check_prop_err (child, wrong, ECTF_NOTINTFP);
desc = "variable lookup";
if (ctf_lookup_variable (child, "base") != CTF_ERR)
no_prop_err ();
check_prop_err (child, wrong, ECTF_NOTYPEDAT);
ctf_dict_close (child);
ctf_dict_close (parent);
ctf_dict_close (wrong);
fprintf (stderr, "All done.\n");
return 0;
parent_err:
fprintf (stderr, "cannot populate parent: %s\n", ctf_errmsg (ctf_errno (parent)));
return 1;
child_err:
fprintf (stderr, "cannot populate child: %s\n", ctf_errmsg (ctf_errno (parent)));
return 1;
}