[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [PATCH v3 4/6] qapi: Apply aliases in qobject-input-visitor
From: |
Markus Armbruster |
Subject: |
Re: [PATCH v3 4/6] qapi: Apply aliases in qobject-input-visitor |
Date: |
Mon, 06 Sep 2021 17:16:31 +0200 |
User-agent: |
Gnus/5.13 (Gnus v5.13) Emacs/27.2 (gnu/linux) |
Kevin Wolf <kwolf@redhat.com> writes:
> When looking for an object in a struct in the external representation,
> check not only the currently visited struct, but also whether an alias
> in the current StackObject matches and try to fetch the value from the
> alias then. Providing two values for the same object through different
> aliases is an error.
>
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
> qapi/qobject-input-visitor.c | 227 +++++++++++++++++++++++++++++++++--
> 1 file changed, 218 insertions(+), 9 deletions(-)
>
> diff --git a/qapi/qobject-input-visitor.c b/qapi/qobject-input-visitor.c
> index 16a75442ff..6193df28a5 100644
> --- a/qapi/qobject-input-visitor.c
> +++ b/qapi/qobject-input-visitor.c
> @@ -97,6 +97,8 @@ struct QObjectInputVisitor {
> QObject *root;
> bool keyval; /* Assume @root made with keyval_parse() */
>
> + QDict *empty_qdict; /* Used for implicit objects */
Would
/* For visiting objects where all members are from aliases */
be clearer?
> +
> /* Stack of objects being visited (all entries will be either
> * QDict or QList). */
> QSLIST_HEAD(, StackObject) stack;
> @@ -169,9 +171,190 @@ static const char *full_name(QObjectInputVisitor *qiv,
> const char *name)
> return full_name_so(qiv, name, false, tos);
> }
>
> +static bool find_object_member(QObjectInputVisitor *qiv,
> + StackObject **so, const char **name,
> + bool *is_alias_prefix, Error **errp);
According to the function's contract below, three cases:
* Input present: update *so, *name, return true.
* Input absent: zap *so, *name, set *is_alias_prefix, return false.
* Error: set *errp, leave *is_alias_prefix undefined, return false.
> +
> +/*
> + * Check whether the member @name in @so, or an alias for it, is
> + * present in the input and can be used to obtain the value.
> + */
> +static bool input_present(QObjectInputVisitor *qiv, StackObject *so,
> + const char *name)
> +{
> + /*
> + * Check whether the alias member is present in the input
> + * (possibly recursively because aliases are transitive).
> + * The QAPI generator makes sure that alises cannot form loops, so
> + * the recursion guaranteed to terminate.
> + */
> + if (!find_object_member(qiv, &so, &name, NULL, NULL)) {
* Input absent: zap @so and @name.
* Error: don't zap.
Since @so and @name aren't used anymore, the difference doesn't matter.
Okay.
> + return false;
> + }
> +
> + /*
> + * Every source can be used only once. If a value in the input
> + * would end up being used twice through aliases, we'll fail the
> + * second access.
> + */
> + if (!g_hash_table_contains(so->h, name)) {
> + return false;
> + }
> +
> + return true;
> +}
> +
> +/*
> + * Check whether the member @name in the object visited by @so can be
> + * specified in the input by using the alias described by @a (which
> + * must be an alias contained in so->aliases).
> + *
> + * If @name is only a prefix of the alias source, but doesn't match
> + * immediately, false is returned and *is_alias_prefix is set to true
> + * if it is non-NULL. In all other cases, *is_alias_prefix is left
> + * unchanged.
> + */
> +static bool alias_source_matches(QObjectInputVisitor *qiv,
> + StackObject *so, InputVisitorAlias *a,
> + const char *name, bool *is_alias_prefix)
> +{
> + if (a->src[0] == NULL) {
> + assert(a->name == NULL);
> + return true;
> + }
> +
> + if (!strcmp(a->src[0], name)) {
> + if (a->name && a->src[1] == NULL) {
> + /*
> + * We're matching an exact member, the source for this alias is
> + * immediately in @so.
> + */
> + return true;
> + } else if (is_alias_prefix) {
> + /*
> + * We're only looking at a prefix of the source path for the
> alias.
> + * If the input contains no object of the requested name, we will
> + * implicitly create an empty one so that the alias can still be
> + * used.
> + *
> + * We want to create the implicit object only if the alias is
> + * actually used, but we can't tell here for wildcard aliases
> (only
> + * a later visitor call will determine this). This means that
> + * wildcard aliases must never have optional keys in their source
> + * path. The QAPI generator checks this condition.
> + */
Double-checking: this actually ensures that we only ever create the
implicit object when it will not remain empty. Correct?
> + if (!a->name || input_present(qiv, a->alias_so, a->name)) {
> + *is_alias_prefix = true;
> + }
> + }
> + }
> +
> + return false;
> +}
> +
> +/*
> + * Find the place in the input where the value for the object member
> + * @name in @so is specified, considering applicable aliases.
> + *
> + * If a value could be found, true is returned and @so and @name are
> + * updated to identify the key name and StackObject where the value
> + * can be found in the input. (This is either unchanged or the
> + * alias_so/name of an alias.) The value of @is_alias_prefix on
> + * return is undefined in this case.
> + *
> + * If no value could be found in the input, false is returned and @so
> + * and @name are set to NULL. This is not an error and @errp remains
> + * unchanged. If @is_alias_prefix is non-NULL, it is set to true if
> + * the given name is a prefix of the source path of an alias for which
> + * a value may be present in the input. It is set to false otherwise.
> + *
> + * If an error occurs (e.g. two values are specified for the member
> + * through different names), false is returned and @errp is set. The
> + * value of @is_alias_prefix on return is undefined in this case.
> + */
> +static bool find_object_member(QObjectInputVisitor *qiv,
> + StackObject **so, const char **name,
> + bool *is_alias_prefix, Error **errp)
> +{
> + QDict *qdict = qobject_to(QDict, (*so)->obj);
> + const char *found_name = NULL;
> + StackObject *found_so = NULL;
> + bool found_is_wildcard = false;
> + InputVisitorAlias *a;
> +
> + if (is_alias_prefix) {
> + *is_alias_prefix = false;
> + }
> +
> + /* Directly present in the container */
> + if (qdict_haskey(qdict, *name)) {
> + found_name = *name;
> + found_so = *so;
> + }
> +
> + /*
> + * Find aliases whose source path matches @name in this StackObject. We
> can
> + * then get the value with the key a->name from a->alias_so.
> + */
> + QSLIST_FOREACH(a, &(*so)->aliases, next) {
> + if (a->name == NULL && found_name && !found_is_wildcard) {
> + /*
> + * Skip wildcard aliases if we already have a match. This is
> + * not a conflict that should result in an error.
> + *
> + * However, multiple wildcard aliases matching is an error
> + * and will be caught below.
> + */
> + continue;
> + }
> +
> + if (!alias_source_matches(qiv, *so, a, *name, is_alias_prefix)) {
> + continue;
> + }
According to the contract of alias_source_matches() above, three cases:
* No match: try next alias
* Partial match: set *is_alias_prefix = true if non-null
* Full match: leave it alone
> +
> + /*
> + * For single-member aliases, an alias name is specified in the
> + * alias definition. For wildcard aliases, the alias has the same
> + * name as the member in the source object, i.e. *name.
> + */
> + if (!input_present(qiv, a->alias_so, a->name ?: *name)) {
> + continue;
What if alias_source_matches() already set *is_alias_prefix = true?
I figure this can't happen, because it guards the assignment with the
exact same call of input_present(). In other words, we can get here
only for "full match". Correct?
Such repeated calls of helpers can be a sign of awkward interfaces.
Let's not worry about that now.
> + }
> +
> + /*
> + * A non-wildcard alias simply overrides a wildcard alias, but
> + * two matching non-wildcard aliases or two matching wildcard
> + * aliases conflict with each other.
> + */
> + if (found_name && (!found_is_wildcard || a->name == NULL)) {
> + error_setg(errp, "Value for parameter %s was already given "
> + "through an alias",
> + full_name_so(qiv, *name, false, *so));
> + return false;
> + } else {
> + found_name = a->name ?: *name;
> + found_so = a->alias_so;
> + found_is_wildcard = !a->name;
> + }
> + }
> +
> + /*
> + * Chained aliases: *found_so/found_name might be the source of
> + * another alias.
> + */
> + if (found_name && (found_so != *so || found_name != *name)) {
> + find_object_member(qiv, &found_so, &found_name, NULL, errp);
* Input present: update @found_so, @found_name.
* Input absent: zap @found_name, @found_name.
* Error: set *errp.
Can @found_name be non-null? If yes, we can set *errp and return
true, which would be bad.
> + }
> +
> + *so = found_so;
> + *name = found_name;
> +
> + return found_name != NULL;
> +}
> +
> static QObject *qobject_input_try_get_object(QObjectInputVisitor *qiv,
> const char *name,
> - bool consume)
> + bool consume, Error **errp)
Before the patch, two cases:
* Input present: consume input if @consume, return the input
* Input absent: return null
The patch adds
* Other error: set *errp, return null
Slightly awkward to use, as we shall see below at [1] and [2].
Observation, not demand.
> {
> StackObject *tos;
> QObject *qobj;
> @@ -189,10 +372,31 @@ static QObject
> *qobject_input_try_get_object(QObjectInputVisitor *qiv,
> assert(qobj);
>
> if (qobject_type(qobj) == QTYPE_QDICT) {
> - assert(name);
> - ret = qdict_get(qobject_to(QDict, qobj), name);
> - if (tos->h && consume && ret) {
> - bool removed = g_hash_table_remove(tos->h, name);
> + StackObject *so = tos;
> + const char *key = name;
> + bool is_alias_prefix;
> +
> + assert(key);
> + if (!find_object_member(qiv, &so, &key, &is_alias_prefix, errp)) {
* Input absent: zap @so, @key, set @is_alias_prefix.
* Error: set *errp, leave @is_alias_prefix undefined.
> + if (is_alias_prefix) {
Use of undefined @is_alias_prefix in case "Error". Bug in code or in
contract?
> + /*
> + * The member is not present in the input, but
> + * something inside of it might still be given through
> + * an alias. Pretend there was an empty object in the
> + * input.
> + */
We pretend there was an object to make the calling visitor enter the
object and visit its members. Visiting a member first looks for input
in the (empty) object, then follows aliases to look for it elsewhere.
Is "might" still correct? The comment in alias_source_matches() makes
me hope it's actually "will".
> + if (!qiv->empty_qdict) {
> + qiv->empty_qdict = qdict_new();
> + }
> + return QOBJECT(qiv->empty_qdict);
> + } else {
> + return NULL;
> + }
> + }
> + ret = qdict_get(qobject_to(QDict, so->obj), key);
> + assert(ret != NULL);
> + if (so->h && consume) {
> + bool removed = g_hash_table_remove(so->h, key);
> assert(removed);
> }
> } else {
> @@ -218,9 +422,10 @@ static QObject
> *qobject_input_get_object(QObjectInputVisitor *qiv,
> const char *name,
> bool consume, Error **errp)
> {
> - QObject *obj = qobject_input_try_get_object(qiv, name, consume);
> + ERRP_GUARD();
> + QObject *obj = qobject_input_try_get_object(qiv, name, consume, errp);
>
> - if (!obj) {
> + if (!obj && !*errp) {
> error_setg(errp, QERR_MISSING_PARAMETER, full_name(qiv, name));
> }
[1] Squash case "Input absent" into case "Error".
> return obj;
> @@ -803,13 +1008,16 @@ static bool qobject_input_type_size_keyval(Visitor *v,
> const char *name,
> static void qobject_input_optional(Visitor *v, const char *name, bool
> *present)
> {
> QObjectInputVisitor *qiv = to_qiv(v);
> - QObject *qobj = qobject_input_try_get_object(qiv, name, false);
> + Error *local_err = NULL;
> + QObject *qobj = qobject_input_try_get_object(qiv, name, false,
> &local_err);
>
> - if (!qobj) {
> + /* If there was an error, let the caller try and run into the error */
> + if (!qobj && !local_err) {
[2] Case "Input absent", and ...
> *present = false;
> return;
> }
... cases "Input present" and "Error".
>
> + error_free(local_err);
> *present = true;
> }
>
> @@ -842,6 +1050,7 @@ static void qobject_input_free(Visitor *v)
> qobject_input_stack_object_free(tos);
> }
>
> + qobject_unref(qiv->empty_qdict);
> qobject_unref(qiv->root);
> if (qiv->errname) {
> g_string_free(qiv->errname, TRUE);
- Re: [PATCH v3 4/6] qapi: Apply aliases in qobject-input-visitor,
Markus Armbruster <=