qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
From: Eric Blake <eblake@redhat.com>
To: Markus Armbruster <armbru@redhat.com>
Cc: qemu-devel@nongnu.org, Michael Roth <mdroth@linux.vnet.ibm.com>
Subject: Re: [Qemu-devel] [PATCH v4 13/28] qapi: Add new clone visitor
Date: Wed, 8 Jun 2016 22:15:35 -0600	[thread overview]
Message-ID: <5758ED67.1050304@redhat.com> (raw)
In-Reply-To: <8737oviu3s.fsf@dusky.pond.sub.org>

[-- Attachment #1: Type: text/plain, Size: 12626 bytes --]

On 06/02/2016 07:43 AM, Markus Armbruster wrote:
> Eric Blake <eblake@redhat.com> 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 *@obj 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
>     * *@obj.  @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 *@obj 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
>     * *@list (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 *@list 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 *@present according to input; other visitors
>     * leave it unchanged.  In either case, return *@present for
>     * convenience.
> 
> I guess this is correct for the clone visitor.

Clone visitor leaves it alone (it is reading *@present 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 *@obj to
>     * the enumeration value, leaving @obj unchanged on error; other
>     * visitors use *@obj but leave it unchanged.
> 
> I guess this is correct for the clone visitor.

It's a bit of a stretch, but "use *@obj" 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 *@obj to the value;
>     * other visitors will leave *@obj 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 *@obj to the value
>     * (never NULL).  Other visitors leave *@obj 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 *@obj, 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 *@obj to the value;
>     * other visitors will leave *@obj unchanged.  *@obj 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 *@obj 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 #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

  parent reply	other threads:[~2016-06-09  4:15 UTC|newest]

Thread overview: 71+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2016-05-19  4:40 [Qemu-devel] [PATCH v4 00/28] Add qapi-to-JSON and clone visitors Eric Blake
2016-05-19  4:40 ` [Qemu-devel] [PATCH v4 01/28] qapi: Rename (one) qjson.h to qobject-json.h Eric Blake
2016-06-01 15:09   ` Markus Armbruster
2016-05-19  4:40 ` [Qemu-devel] [PATCH v4 02/28] qapi: Improve use of qmp/types.h Eric Blake
2016-05-19  4:40 ` [Qemu-devel] [PATCH v4 03/28] qemu-img: Don't leak errors when outputting JSON Eric Blake
2016-06-01 15:25   ` Markus Armbruster
2016-05-19  4:40 ` [Qemu-devel] [PATCH v4 04/28] qapi: Add parameter to visit_end_* Eric Blake
2016-06-01 15:36   ` Markus Armbruster
2016-06-07 23:20     ` Eric Blake
2016-05-19  4:40 ` [Qemu-devel] [PATCH v4 05/28] qapi: Add new visit_free() function Eric Blake
2016-06-01 16:03   ` Markus Armbruster
2016-06-03 11:46     ` Markus Armbruster
2016-05-19  4:40 ` [Qemu-devel] [PATCH v4 06/28] opts-visitor: Favor " Eric Blake
2016-06-01 16:06   ` Markus Armbruster
2016-05-19  4:40 ` [Qemu-devel] [PATCH v4 07/28] string-input-visitor: " Eric Blake
2016-06-01 16:13   ` Markus Armbruster
2016-05-19  4:40 ` [Qemu-devel] [PATCH v4 08/28] qmp-input-visitor: " Eric Blake
2016-06-01 16:19   ` Markus Armbruster
2016-05-19  4:40 ` [Qemu-devel] [PATCH v4 09/28] string-output-visitor: " Eric Blake
2016-05-19  4:40 ` [Qemu-devel] [PATCH v4 10/28] qmp-output-visitor: " Eric Blake
2016-05-19  4:40 ` [Qemu-devel] [PATCH v4 11/28] tests: Factor out common code in qapi output tests Eric Blake
2016-06-01 16:33   ` Markus Armbruster
2016-05-19  4:40 ` [Qemu-devel] [PATCH v4 12/28] qapi: Add new visit_complete() function Eric Blake
2016-06-01 17:02   ` Markus Armbruster
2016-05-19  4:40 ` [Qemu-devel] [PATCH v4 13/28] qapi: Add new clone visitor Eric Blake
2016-06-02 13:43   ` Markus Armbruster
2016-06-03 14:04     ` Markus Armbruster
2016-06-09  4:15     ` Eric Blake [this message]
2016-05-19  4:41 ` [Qemu-devel] [PATCH v4 14/28] sockets: Use new QAPI cloning Eric Blake
2016-05-19  4:41 ` [Qemu-devel] [PATCH v4 15/28] replay: " Eric Blake
2016-05-19  4:41 ` [Qemu-devel] [PATCH v4 16/28] qapi: Factor out JSON string escaping Eric Blake
2016-06-02 14:53   ` Markus Armbruster
2016-05-19  4:41 ` [Qemu-devel] [PATCH v4 17/28] qapi: Factor out JSON number formatting Eric Blake
2016-06-02 15:02   ` Markus Armbruster
2016-06-02 15:06     ` Eric Blake
2016-06-03  9:02       ` Markus Armbruster
2016-06-09 16:07         ` Eric Blake
2016-06-13  8:22           ` Markus Armbruster
2016-06-13 12:34             ` Eric Blake
2016-06-13 14:41               ` Markus Armbruster
2016-05-19  4:41 ` [Qemu-devel] [PATCH v4 18/28] qapi: Add qstring_append_printf() Eric Blake
2016-05-19  4:41 ` [Qemu-devel] [PATCH v4 19/28] qapi: Use qstring_append_chr() where appropriate Eric Blake
2016-05-19  4:41 ` [Qemu-devel] [PATCH v4 20/28] qstring: Add qstring_consume_str() Eric Blake
2016-05-19  4:41 ` [Qemu-devel] [PATCH v4 21/28] qstring: Add qstring_wrap_str() Eric Blake
2016-06-02 15:21   ` Markus Armbruster
2016-06-09 16:31     ` Eric Blake
2016-05-19  4:41 ` [Qemu-devel] [PATCH v4 22/28] qobject: Consolidate qobject_to_json() calls Eric Blake
2016-06-02 15:32   ` Markus Armbruster
2016-05-19  4:41 ` [Qemu-devel] [PATCH v4 23/28] tests: Test qobject_to_json() pretty formatting Eric Blake
2016-05-19  4:41 ` [Qemu-devel] [PATCH v4 24/28] qapi: Add JSON output visitor Eric Blake
2016-06-03  7:39   ` Markus Armbruster
2016-06-03 12:53     ` Eric Blake
2016-06-03 14:09       ` Markus Armbruster
2016-05-19  4:41 ` [Qemu-devel] [PATCH v4 25/28] qapi: Support pretty printing in " Eric Blake
2016-06-03  7:56   ` Markus Armbruster
2016-06-03 12:55     ` Eric Blake
2016-06-03 14:08       ` Markus Armbruster
2016-05-19  4:41 ` [Qemu-devel] [PATCH v4 26/28] qobject: Implement qobject_to_json() atop JSON visitor Eric Blake
2016-06-03  8:25   ` Markus Armbruster
2016-05-19  4:41 ` [Qemu-devel] [PATCH v4 27/28] qapi: Add 'any' support to JSON output Eric Blake
2016-06-03  8:29   ` Markus Armbruster
2016-05-19  4:41 ` [Qemu-devel] [PATCH v4 28/28] qemu-img: Use new JSON output formatter Eric Blake
2016-05-19 14:58 ` [Qemu-devel] [PATCH v4 00/28] Add qapi-to-JSON and clone visitors Eric Blake
2016-05-19 16:52 ` [Qemu-devel] [PATCH v4 29/28] qapi: Add strict mode to JSON output visitor Eric Blake
2016-05-19 20:18   ` Eric Blake
2016-06-03  8:36     ` Markus Armbruster
2016-06-03  9:21   ` Markus Armbruster
2016-05-19 17:05 ` [Qemu-devel] [PATCH v4 00/28] Add qapi-to-JSON and clone visitors Markus Armbruster
2016-06-03 12:09 ` Markus Armbruster
2016-06-09 16:16   ` Eric Blake
2016-06-13  8:26     ` Markus Armbruster

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=5758ED67.1050304@redhat.com \
    --to=eblake@redhat.com \
    --cc=armbru@redhat.com \
    --cc=mdroth@linux.vnet.ibm.com \
    --cc=qemu-devel@nongnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).