qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Qemu-devel] [PATCH v4 13/28] qapi: Add new clone visitor


From: Eric Blake
Subject: Re: [Qemu-devel] [PATCH v4 13/28] qapi: Add new clone visitor
Date: Wed, 8 Jun 2016 22:15:35 -0600
User-agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101 Thunderbird/38.3.0

On 06/02/2016 07:43 AM, Markus Armbruster wrote:
> Eric Blake <address@hidden> writes:
> 
>> We have a couple places in the code base that want to deep-clone
>> one QAPI object into another, and they were resorting to serializing
>> the struct out to QObject then reparsing it.  A much more efficient
>> version can be done by adding a new clone visitor.
>>

> [...]
>     * If an error is detected during visit_type_FOO() with an input
>     * visitor, then address@hidden will be NULL for pointer types, and left
>     * unchanged for scalar types.  Using an output visitor with an
>     * incomplete object has undefined behavior (other than a special case
>     * for visit_type_str() treating NULL like ""), while the dealloc
>     * visitor safely handles incomplete objects.  Since input visitors
>     * never produce an incomplete object, such an object is possible only
>     * by manual construction.
> 
> What about the clone visitor?

Probably safest to document it as undefined on incomplete objects.

>    /*
>     * Start visiting an object @obj (struct or union).
>     *
>     * @name expresses the relationship of this object to its parent
>     * container; see the general description of @name above.
>     *
>     * @obj must be non-NULL for a real walk, in which case @size
>     * determines how much memory an input visitor will allocate into
>     * address@hidden  @obj may also be NULL for a virtual walk, in which case
>     * @size is ignored.
> 
> What about the clone visitor?

Yes, clone visitors also use size.

> 
>     *
>     * @errp obeys typical error usage, and reports failures such as a
>     * member @name is not present, or present but not an object.  On
>     * error, input visitors set address@hidden to NULL.
> 
> What about the clone visitor?

Never sets an error (ie. it can't fail on a complete source object, if
you don't include abort-due-to-OOM scenarios), so I'm not sure I need to
word anything differently here.

>     * Start visiting a list.
>     *
>     * @name expresses the relationship of this list to its parent
>     * container; see the general description of @name above.
>     *
>     * @list must be non-NULL for a real walk, in which case @size
>     * determines how much memory an input visitor will allocate into
>     * address@hidden (at least sizeof(GenericList)).  Some visitors also allow
>     * @list to be NULL for a virtual walk, in which case @size is
>     * ignored.
> 
> What about the clone visitor?
> 
>     *
>     * @errp obeys typical error usage, and reports failures such as a
>     * member @name is not present, or present but not a list.  On error,
>     * input visitors set address@hidden to NULL.
> 
> What about the clone visitor?

Same as for start_struct.

>    /*
>     * Does optional struct member @name need visiting?
>     *
>     * @name must not be NULL.  This function is only useful between
>     * visit_start_struct() and visit_end_struct(), since only objects
>     * have optional keys.
>     *
>     * @present points to the address of the optional member's has_ flag.
>     *
>     * Input visitors set address@hidden according to input; other visitors
>     * leave it unchanged.  In either case, return address@hidden for
>     * convenience.
> 
> I guess this is correct for the clone visitor.

Clone visitor leaves it alone (it is reading address@hidden on the dest,
which was already set earlier during the g_memdup() of visit_start_*).

>    /*
>     * Visit an enum value.
>     *
>     * @name expresses the relationship of this enum to its parent
>     * container; see the general description of @name above.
>     *
>     * @obj must be non-NULL.  Input visitors parse input and set 
> address@hidden to
>     * the enumeration value, leaving @obj unchanged on error; other
>     * visitors use address@hidden but leave it unchanged.
> 
> I guess this is correct for the clone visitor.

It's a bit of a stretch, but "use address@hidden" can certainly mean "do nothing
with it, because it is a scalar that was already set earlier during the
g_memdup() of visit_start_*".  So yes, the clone visitor wants
visit_type_enum() to be a no-op.


> 
>    /*
>     * Check if visitor is an input visitor.
> 
> Does the clone visitor count as input visitor here?  Should it?

No, and probably no.  A clone visitor never sets errp, and therefore
there is no reason to clean up after a failed clone; and our current use
of visit_is_input() is only for cleaning up after failures in an input
visitor.

> 
>     */
>    bool visit_is_input(Visitor *v);
> 
>    /*** Visiting built-in types ***/
> 
>    /*
>     * Visit an integer value.
>     *
>     * @name expresses the relationship of this integer to its parent
>     * container; see the general description of @name above.
>     *
>     * @obj must be non-NULL.  Input visitors set address@hidden to the value;
>     * other visitors will leave address@hidden unchanged.
> 
> I guess this is correct for the clone visitor.

Again correct - the clone visitor doesn't set anything at this point,
because the integer was already copied earlier during the g_memdup() of
visit_start_*().


>    /*
>     * Visit a string value.
>     *
>     * @name expresses the relationship of this string to its parent
>     * container; see the general description of @name above.
>     *
>     * @obj must be non-NULL.  Input visitors set address@hidden to the value
>     * (never NULL).  Other visitors leave address@hidden unchanged, and 
> commonly
>     * treat NULL like "".
> 
> I guess this is correct for the clone visitor.

The clone visitor could morph NULL into "" (I didn't code it that way,
though).  Here, the clone visitor DOES set address@hidden, in order to dedupe 
the
pointer from the source object, so maybe a third sentence is needed?


>    /*
>     * Visit an arbitrary value.
>     *
>     * @name expresses the relationship of this value to its parent
>     * container; see the general description of @name above.
>     *
>     * @obj must be non-NULL.  Input visitors set address@hidden to the value;
>     * other visitors will leave address@hidden unchanged.  address@hidden 
> must be non-NULL
>     * for output visitors.
> 
> Fine, as the clone visitor doesn't support any.

It could, if we use the JSON output visitor code later in the series to
create a QObject deep-cloner, but I'd rather not do it unless we find an
actual need (keeping 'any' out of clone does simplify the number of
corner cases to think about).

>> +++ b/qapi/qapi-visit-core.c
> 
> As we'll see further down, @obj points into the clone, except at the
> root, where it points to qapi_clone()'s local variable @dst.  A
> pointer-valued address@hidden still points into the source.
> 
> Now let's go through the v->type checks real slow.
> 

>> @@ -44,10 +44,10 @@ void visit_start_struct(Visitor *v, const char *name, 
>> void **obj,
>>
>>      if (obj) {
>>          assert(size);
>> -        assert(v->type != VISITOR_OUTPUT || *obj);
>> +        assert(!(v->type & VISITOR_OUTPUT) || *obj);
>>      }
> 
> For real walks (obj != NULL):
> 
> * Input visitors write *obj, and don't care for the old value.
> 
> * Output visitors read *obj, and a struct can't be null.
> 
> * The dealloc visitor reads *obj, but null is fine (partially
>   constructed object).
> 
> * The clone visitor reads like an output visitor (except at the root)
>   and writes like an input visitor.
> 
> Before the patch, we assert "if output visitor, then *obj isn't null".
> 
> After the patch, we do the same for the clone visitor.  Correct, except
> at the root.  There, @obj points to qapi_clone()'s @dst, which is
> uninitialized.  I'm afraid this assertion fails if @dst happens to be
> null.
> 
> Can we fix this by removing the "except at the root" special case?
> Change qapi_clone to initialize dst = src, drop QapiCloneVisitor member
> @root and qapi_clone_visitor_new() parameter @src.

Cool idea!  And it avoids the crash (I was indeed compiling without
optimization, and getting lucky that the uninit value wasn't crashing my
tests; wonder why valgrind wasn't flagging it).


> [...]
>    bool visit_is_input(Visitor *v)
>    {
>        return v->type == VISITOR_INPUT;
>    }
> 
> This answers my question "Does the clone visitor count as input visitor
> here?"  Remaining question: "Should it?"

I'm still not convinced, again on the grounds that this is used for
cleanup after a failed visit, but clone visits don't fail.

> 
>> @@ -252,9 +252,10 @@ void visit_type_str(Visitor *v, const char *name, char 
>> **obj, Error **errp)
>>      assert(obj);
>>      /* TODO: Fix callers to not pass NULL when they mean "", so that we
>>       * can enable:
>> -    assert(v->type != VISITOR_OUTPUT || *obj);
>> +    assert(!(v->type & VISITOR_OUTPUT) || *obj);
>>       */
>>      v->type_str(v, name, obj, &err);
>> +    /* Likewise, use of NULL means we can't do (v->type & VISITOR_INPUT) */
>>      if (v->type == VISITOR_INPUT) {
>>          assert(!err != !*obj);
>>      }
> 
> If your head doesn't hurt by know, you either wrote this, or you're not
> reading closely :)

And there's my idea of making the clone visitor auto-magically clone
NULL into "", at which point the conditions in the assertions would change.

> 
> If the TODOs were already addressed, we'd again get the same analysis as
> for visit_start_struct(), except for the arguments about the root, which
> don't apply here, because the clone visitor doesn't accept scalar roots.
> 
> In the current state, the analysis needs to be modified as follows.
> 
> First assertion:
> 
> Before the patch, we'd like to assert "if output or clone visitor, then
> *obj isn't null".  We can't as long as we need to treat null as the
> empty string.
> 
> After the patch, the situation is the same for the clone visitor.  Okay.
> 
> Second assertion:
> 
> Before the patch, we assert "input visitor must either fail or create
> *obj for a real walk."  The TODO doesn't apply; we create "", not null.
> 
> After the patch, we'd like to assert the same for the clone visitor, but
> we can't: the clone of null is null.  Okay.
> 
>> @@ -273,9 +274,9 @@ void visit_type_any(Visitor *v, const char *name, 
>> QObject **obj, Error **errp)
>>      Error *err = NULL;
>>
>>      assert(obj);
>> -    assert(v->type != VISITOR_OUTPUT || *obj);
>> +    assert(!(v->type & VISITOR_OUTPUT) || *obj);
>>      v->type_any(v, name, obj, &err);
>> -    if (v->type == VISITOR_INPUT) {
>> +    if (v->type & VISITOR_INPUT) {
>>          assert(!err != !*obj);
>>      }
>>      error_propagate(errp, err);
> 
> v->type_any() will crash for the clone visitor, so these changes aren't.
> necessary.  If you want them to future-proof the code, I need to
> convince myself the changes make sense, similar to what I did for the
> other ones in this file.

Okay, I can leave this hunk out for now.

> 
>> @@ -342,4 +343,5 @@ void visit_type_enum(Visitor *v, const char *name, int 
>> *obj,
>>      } else if (v->type == VISITOR_OUTPUT) {
>>          output_type_enum(v, name, obj, strings, errp);
>>      }
>> +    /* dealloc and clone visitors have nothing to do */
>>  }
> 
> I'm upgrade my verdict from "subtle" to "scarily subtle" %-}
> 

Any comments I can add to make it more obvious to the next reader?

>> +
>> +static void qapi_clone_start_struct(Visitor *v, const char *name, void 
>> **obj,
>> +                                    size_t size, Error **errp)
>> +{
>> +    QapiCloneVisitor *qcv = to_qcv(v);
>> +
>> +    if (!obj) {
>> +        /* Only possible when visiting an alternate's object
>> +         * branch. Nothing to do here, since the earlier
>> +         * visit_start_alternate() already copied memory. */
> 
> Should visitor-impl.h explain how method start_struct() is used with
> alternates?  I once again forgot how this works...  Hmm, you explained
> it to me during review of v3.
> 
> Despite there's "nothing to do here", you found something to do:
> 
>> +        assert(qcv->depth);

That's really only asserting that the clone itself is a real visit; we
don't allow cloning on a virtual visit, even though the real visit of an
alternate also involves the virtual visit of an object, if the
alternate's object branch is selected.


> [Skipping the tests for now to get this review out today...]

Did you want to review the tests in any further detail?

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org

Attachment: signature.asc
Description: OpenPGP digital signature


reply via email to

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