qemu-devel
[Top][All Lists]
Advanced

[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: Tue, 14 Sep 2021 16:24:11 +0200
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/27.2 (gnu/linux)

Kevin Wolf <kwolf@redhat.com> writes:

> Am 14.09.2021 um 08:58 hat Markus Armbruster geschrieben:
>> Kevin Wolf <kwolf@redhat.com> writes:
>> 
>> > Am 06.09.2021 um 17:16 hat Markus Armbruster geschrieben:
>> >> 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>
>
>> >> > +/*
>> >> > + * 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?
>> >
>> > For wildcard aliases, we still can't know which keys will be visited
>> > later. Checking that we don't have optional keys only avoids the
>> > confusion between absent and present, but empty objects that you would
>> > get from the implicit objects. So it means that creating an implicit
>> > object is never wrong, either the nested object can be visited (which
>> > means we needed the implicit object) or it errors out.
>> 
>> What I'm trying to understand is whether aliases may make up an empty
>> object, and if yes, under what conditions.  Can you help me?
>> 
>> "Make up an empty object" = have an empty QDict in the result where the
>> JSON input doesn't have a {}.
>
> Well, the result is a C type, not a QDict. We never build a single QDict
> for the object including values resolved from aliases, we just fetch the
> values from different QDicts if necessary.

I managed to confuse myself.  Fortunately, it looks like I failed to
confuse you.

> But for what I think you're trying to get at: Yes, it can happen that we
> start visiting a struct which was not present in the JSON, and for which
> no members will match. This is if you have a wildcard alias for the
> members of this object because we must assume that the alias might
> provide the necessary values - but it might as well not have them.
>
> There are two cases here:
>
> 1. The nested object contains non-optional members. This is an error
>    case. The error message will change from missing struct to missing
>    member, but this isn't too bad because the missing member does in
>    fact exist on the outer level, too, as an alias. So I think the error
>    message is still good.
>
> 2. The nested object only contains optional members. Then the alias
>    allows just not specifying the empty nested object, all of the zero
>    required members are taken from the outer object.
>
>    This would be a problem if the nested object were optional itself
>    because it would turn absent into present, but empty. So this is the
>    reason why we check in the generator that you don't have optional
>    members in the path of wildcard aliases.

I'm too tired / stupid to grasp this in the abstract.  I'll try again
tomorrow, with concrete examples.

>> >> > +
>> >> > +        /*
>> >> > +         * 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?
>> >
>> > Probably, but my reasoning is much simpler: If alias_source_matches()
>> > sets *is_alias_prefix, it also returns false, so we would already have
>> > taken a different path above.
>> 
>> I see.  The contract even says so: "false is returned and
>> *is_alias_prefix is set to true".  It's actually the only way for
>> *is_alias_prefix to become true.
>> 
>> Output parameters that are set only sometimes require the caller to
>> initialize the receiving variable, or use it only under the same
>> condition it is set.  The former is easy to forget, and the latter is
>> easy to get wrong.
>> 
>> "Set sometimes" can be useful, say when you have several calls where the
>> output should "accumulate".  When you don't, I prefer to set always,
>> because it makes the function harder to misuse.  Would you mind?
>
> It does "accumulate" here. We want to return true if any of the aliases
> make it true.

You're right.

It doesn't accumulate with find_object_member() below.  There, the code
always sets, but the contract doesn't actually promise it.

>> >> > @@ -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?
>> >
>> > We should probably use ERRP_GUARD() and check for !*errp here.
>> 
>> A need to use ERRP_GUARD() often signals "awkward interface".
>> 
>> What about always setting is_alias_prefix?  Then it's false on error.
>
> Ok, I can define it as false for error cases if you prefer.
>
> I'm not sure if I find it more readable than !*errp && ..., though. It
> makes is_alias_prefix carry more information than its name suggests.
>
> Kevin




reply via email to

[Prev in Thread] Current Thread [Next in Thread]