All of lore.kernel.org
 help / color / mirror / Atom feed
From: Eric Blake <eblake@redhat.com>
To: Markus Armbruster <armbru@redhat.com>
Cc: marcandre.lureau@redhat.com, qemu-devel@nongnu.org,
	Michael Roth <mdroth@linux.vnet.ibm.com>
Subject: Re: [Qemu-devel] [PATCH v9 35/37] qapi: Change visit_type_FOO() to no longer return partial objects
Date: Thu, 28 Jan 2016 10:05:14 -0700	[thread overview]
Message-ID: <56AA4A4A.60703@redhat.com> (raw)
In-Reply-To: <87h9hxiv66.fsf@blackfin.pond.sub.org>

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

On 01/28/2016 08:24 AM, Markus Armbruster wrote:
> Eric Blake <eblake@redhat.com> writes:
> 
>> Returning a partial object on error is an invitation for a careless
>> caller to leak memory.  As no one outside the testsuite was actually
>> relying on these semantics, it is cleaner to just document and
>> guarantee that ALL pointer-based visit_type_FOO() functions always
>> leave a safe value in *obj during an input visitor (either the new
>> object on success, or NULL if an error is encountered).
>>
>> Since input visitors have blind assignment semantics, we have to
>> track the result of whether an assignment is made all the way down
>> to each visitor callback implementation, to avoid making decisions
>> based on potentially uninitialized storage.
> 
> I'm not sure I get this paragraph.  What's "blind assignment semantics"?

The caller does:

{
    Foo *bar; /* uninit */
    visit_type_Foo(&bar);
    if (no error) {
        /* use bar */
    }
}

Which means the visitor core can't do 'if (*obj)', because obj may be
uninitialized on entry; if it dereferences obj at all, it must be via
'*obj = ...' which I'm terming a blind assignment.

But I can try and come up with better wording.

> 
>> Note that we still leave *obj unchanged after a scalar-based
>> visit_type_FOO(); I did not feel like auditing all uses of
>> visit_type_Enum() to see if the callers would tolerate a specific
>> sentinel value (not to mention having to decide whether it would
>> be better to use 0 or ENUM__MAX as that sentinel).
> 
> The assigning input visitor functions (core and generated) all assign
> either a pointer to a newly allocated object, or a non-pointer scalar
> value.
> 
> Possible behaviors on error:
> 
> (0) What we have now: assign something that must be cleaned up with the
>     dealloc visitor if it's a pointer, but is otherwise useless
> 
>     CON: callers have to clean up
>     CON: exposes careless callers to useless values
> 
> (1) Don't assign anything
> 
>     PRO: consistent
>     CON: exposes careless callers to uninitialized values

Half-PRO: Caller can pre-initialize a default, and rely on that value on
error.  In fact, I think we have callers doing that when visiting an
enum, and I didn't feel up to auditing them all when first writing the
patch.

But a small audit right now shows:

qom/object.c:object_property_get_enum() starts with uninitialized 'int
ret;', hardcodes 'return 0;' on some failures, but otherwise passes it
to visit_type_enum() then blindly returns that value even if errp is
set.  Yuck.  Callers HAVE to check errp rather than relying on the
return value to flag errors; although it looks like the lone caller is
in numa.c and passes &error_abort.

Maybe I should just bite the bullet, and audit ALL uses of visitor for
their behavior of what to expect in *obj on error.

> 
> (2) Assign zero bits
> 
>     PRO: consistent
>     CON: exposes careless callers to bogus zero values

Half-CON: Caller cannot pre-initialize a default

> 
> (3) Assign null pointer, else don't assign anything
> 
>     CON: inconsistent
>     CON: mix of (1)'s and (2)'s CON

Which I think is what I did in this patch.

> 
> (4) Other ideas?

Store the caller's initial value (often all zero, but not necessarily),
and restore THAT value on error (preserves a pre-initialized default,
but leaves uninitialized garbage in place to bite careless callers)

> 
> Note that *obj is almost always null on entry, because we allocate
> objects zero-initialized.  Only root visits can expose their caller to
> uninitialized values.
> 
>> Signed-off-by: Eric Blake <eblake@redhat.com>
>>

>> +/* All qapi types have a corresponding function with a signature
>> + * roughly compatible with this:
>> + *
>> + * void visit_type_FOO(Visitor *v, void *obj, const char *name, Error **errp);
>> + *
>> + * where *@obj is itself a pointer or a scalar.  The visit functions for
>> + * built-in types are declared here, while the functions for qapi-defined
>> + * struct, union, enum, and list types are generated (see qapi-visit.h).
>> + * Input visitors may receive an uninitialized *@obj, and guarantee a
>> + * safe value is assigned (a new object on success, or NULL on failure).
> 
> This specifies the safe value: NULL.  Makes no sense for non-pointer
> scalars.
> 
> May input visitors really receive uninitialized *@obj?  As far as I can
> see, we routinely store a newly allocated object to *@obj on success,
> and store nothing on failure.  To be able to pass this to the dealloc
> visitor, *@obj must have been null initially, mustn't it?

Correct.  Pre-patch: either the caller pre-initialized obj to NULL (and
can then blindly pass it to the dealloc visitor regardless of whether
visit_start_struct() succeeded), or it did not (in which case the
dealloc visitor must not be called if *obj remains uninitialized because
visit_start_struct() failed, but MUST be called if visit_start_struct()
succeeded to clean up the partial object).

Post-patch: calling visit_start_struct() always assigns to *obj, and the
dealloc visitor can be safely called.

> 
>> + * Output visitors expect *@obj to be properly initialized on entry.
> 
> What about the dealloc visitor?

Can be passed NULL, a partial object, or a complete object.  But
spelling that out would indeed be useful.

> 
>> + *
>> + * Additionally, all qapi structs have a generated function compatible
>> + * with this:
>> + *
>> + * void qapi_free_FOO(void *obj);
>> + *
>> + * which behaves like free(), even if @obj is NULL or was only partially
>> + * allocated before encountering an error.
> 
> Will partially allocated QAPI objects remain visible outside input
> visitors?

A user can still hand-initialize a QAPI struct into partial state,
although you are correct that this patch is what is changing things to
no longer leak a partial object outside of the visit_type_FOO() calls.

>> + * Returns true if *@obj was allocated; if that happens, and an error
>> + * occurs any time before the matching visit_end_struct(), then the
>> + * caller (usually a visit_type_FOO() function) knows to undo the
>> + * allocation before returning control further.
>>   */
>> -void visit_start_struct(Visitor *v, const char *name, void **obj,
>> +bool visit_start_struct(Visitor *v, const char *name, void **obj,
>>                          size_t size, Error **errp);
> 
> Need to see how this is used before I can judge whether I like it :)
> 
>> @@ -152,6 +154,7 @@ opts_start_struct(Visitor *v, const char *name, void **obj,
>>          ov->fake_id_opt->str = g_strdup(ov->opts_root->id);
>>          opts_visitor_insert(ov->unprocessed_opts, ov->fake_id_opt);
>>      }
>> +    return result;
>>  }
> 
> Stores the newly allocated object in *@obj on success, doesn't store
> anything on failure.
> 
> To make cleanup possible, *@obj must be null initially.  Then the return
> value is true iff *@obj is non-null on return.

If we want, I can change these to all store *obj = NULL on failure.
Thinking about it more: for any given visit_type_FOO(), if
visit_start_struct() succeeds but something else later fails, *obj will
be NULL; so it also makes sense that if visit_start_struct() fails than
*obj should be NULL.

>> -void visit_start_struct(Visitor *v, const char *name, void **obj,
>> +bool visit_start_struct(Visitor *v, const char *name, void **obj,
>>                          size_t size, Error **errp)
>>  {
>> +    bool result;
>> +
>>      assert(obj ? size : !size);
>>      if (obj && visit_is_output(v)) {
>>          assert(*obj);
>>      }
>> -    v->start_struct(v, name, obj, size, errp);
>> +    result = v->start_struct(v, name, obj, size, errp);
>> +    if (result) {
>> +        assert(obj && *obj);
>> +    }
> 
> Roundabout way to write assert(!result || (obj && *obj));
> 
> Heh, you even assert one half of what I'm trying to prove.
> 
> Can we make it assert(result == (visit_is_input(v) && obj && *obj) ?

Missing a ); guessing that you meant:

assert(result == (visit_is_input(v) && obj && *obj));

Yes, I think we can, but we'd need a visit_is_input() helper.

>> @@ -236,7 +253,11 @@ void visit_type_str(Visitor *v, const char *name, char **obj, Error **errp)
>>          assert(*obj);
>>      }
>>       */
>> -    v->type_str(v, name, obj, errp);
>> +    v->type_str(v, name, obj, &err);
>> +    if (!visit_is_output(v) && err) {
>> +        *obj = NULL;
> 
> Shouldn't storing null on error be left to v->type_str(), like we do for
> structs and lists?  Not least because it begs the question whether this
> leaks a string stored by it.

May be worthwhile to mandate that tighter semantics on each visitor.

>> +++ b/scripts/qapi-visit.py

>> @@ -301,10 +316,15 @@ void visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s **obj, Error
>>      visit_check_struct(v, &err);
>>  out_obj:
>>      visit_end_struct(v);
>> +    if (allocated && err) {
>> +        qapi_free_%(c_name)s(*obj);
>> +        *obj = NULL;
>> +    }
>>  out:
>>      error_propagate(errp, err);
>>  }
>> -''')
>> +''',
>> +                 c_name=c_name(name))
>>
>>      return ret
>>
> 
> Hmm.
> 
> This grows qapi-visit.c by 14% for me.
> 
> Can we do the deallocation in the visitor core somehow?  We'd have to
> pass "how to deallocate" information: the appropriate qapi_free_FOO()
> function.  We already pass in "how to allocate" information: size.

I thought about that; do we really want to be type-punning functions
like that?  But it does sound smaller; I can play with the idea.


> 
> Now I've seen the complete patch, two more remarks:
> 
> * I think all the allocating methods should behave the same.  Right now,
>   some store null on failure, and some don't.  For the latter to work,
>   the value must be null initially (or else the dealloc visitor runs
>   amok).

Agree; I'm leaning towards ALL allocating methods must store into *obj
(either NULL on failure, or object on success).

> 
> * Do we really need the new return value?  It looks like it's always
>   equal to visit_is_input(v) && obj && *obj.

Except we don't (yet) have a visit_is_input() function, let alone one
visible from within the generated qapi-visit.c code.  Maybe doing that
first would help.

-- 
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 --]

  reply	other threads:[~2016-01-28 17:05 UTC|newest]

Thread overview: 128+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2016-01-19 16:10 [Qemu-devel] [PATCH v9 00/37] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
2016-01-19 16:10 ` [Qemu-devel] [PATCH v9 01/37] qobject: Document more shortcomings in our number handling Eric Blake
2016-01-20  9:02   ` Markus Armbruster
2016-01-20 15:55     ` Eric Blake
2016-01-21  6:21       ` Markus Armbruster
2016-01-21 17:12         ` Eric Blake
2016-01-21 17:29         ` Daniel P. Berrange
2016-01-19 16:10 ` [Qemu-devel] [PATCH v9 02/37] qapi: Avoid use of misnamed DO_UPCAST() Eric Blake
2016-01-20 10:04   ` Markus Armbruster
2016-01-20 15:59     ` Eric Blake
2016-01-21  6:22       ` Markus Armbruster
2016-01-19 16:10 ` [Qemu-devel] [PATCH v9 03/37] qapi: Drop dead dealloc visitor variable Eric Blake
2016-01-19 16:10 ` [Qemu-devel] [PATCH v9 04/37] hmp: Improve use of qapi visitor Eric Blake
2016-01-20 13:05   ` Markus Armbruster
2016-01-19 16:10 ` [Qemu-devel] [PATCH v9 05/37] vl: " Eric Blake
2016-01-20 13:43   ` Markus Armbruster
2016-01-20 16:18     ` Eric Blake
2016-01-21  6:45       ` Markus Armbruster
2016-01-19 16:10 ` [Qemu-devel] [PATCH v9 06/37] balloon: " Eric Blake
2016-01-20 14:09   ` Markus Armbruster
2016-01-19 16:10 ` [Qemu-devel] [PATCH v9 07/37] qapi: Improve generated event " Eric Blake
2016-01-20 15:19   ` Markus Armbruster
2016-01-20 17:10     ` Eric Blake
2016-01-21  7:16       ` Markus Armbruster
2016-01-26 23:40       ` Eric Blake
2016-01-28 22:51     ` Eric Blake
2016-01-19 16:10 ` [Qemu-devel] [PATCH v9 08/37] qapi: Track all failures between visit_start/stop Eric Blake
2016-01-20 16:03   ` Markus Armbruster
2016-01-20 17:15     ` Eric Blake
2016-01-21  7:19       ` Markus Armbruster
2016-01-19 16:10 ` [Qemu-devel] [PATCH v9 09/37] qapi: Prefer type_int64 over type_int in visitors Eric Blake
2016-01-20 17:07   ` Markus Armbruster
2016-01-19 16:10 ` [Qemu-devel] [PATCH v9 10/37] qapi: Make all visitors supply uint64 callbacks Eric Blake
2016-01-20 17:29   ` Markus Armbruster
2016-01-20 18:10     ` Eric Blake
2016-01-21  8:56       ` Markus Armbruster
2016-01-21 17:22         ` Eric Blake
2016-01-19 16:10 ` [Qemu-devel] [PATCH v9 11/37] qapi: Consolidate visitor small integer callbacks Eric Blake
2016-01-20 17:34   ` Markus Armbruster
2016-01-20 18:17     ` Eric Blake
2016-01-21  9:05       ` Markus Armbruster
2016-01-19 16:10 ` [Qemu-devel] [PATCH v9 12/37] qapi: Don't cast Enum* to int* Eric Blake
2016-01-20 18:08   ` Markus Armbruster
2016-01-20 19:58     ` Eric Blake
2016-01-21  9:08       ` Markus Armbruster
2016-01-19 16:10 ` [Qemu-devel] [PATCH v9 13/37] qom: Use typedef for Visitor Eric Blake
2016-01-19 16:10 ` [Qemu-devel] [PATCH v9 14/37] qapi: Swap visit_* arguments for consistent 'name' placement Eric Blake
2016-01-20 18:28   ` Markus Armbruster
2016-01-19 16:10 ` [Qemu-devel] [PATCH v9 15/37] qom: Swap 'name' next to visitor in ObjectPropertyAccessor Eric Blake
2016-01-20 18:49   ` Markus Armbruster
2016-01-20 20:54     ` Eric Blake
2016-01-21  9:18       ` Markus Armbruster
2016-01-21  9:46         ` Kevin Wolf
2016-01-21 10:04           ` Markus Armbruster
2016-01-19 16:10 ` [Qemu-devel] [PATCH v9 16/37] qapi: Swap 'name' in visit_* callbacks to match public API Eric Blake
2016-01-20 18:55   ` Markus Armbruster
2016-01-20 21:01     ` Eric Blake
2016-01-21  9:19       ` Markus Armbruster
2016-01-19 16:10 ` [Qemu-devel] [PATCH v9 17/37] qapi: Drop unused 'kind' for struct/enum visit Eric Blake
2016-01-20 18:59   ` Markus Armbruster
2016-01-19 16:10 ` [Qemu-devel] [PATCH v9 18/37] qapi: Drop unused error argument for list and implicit struct Eric Blake
2016-01-20 19:03   ` Markus Armbruster
2016-01-20 21:58     ` Eric Blake
2016-01-21  9:47       ` Markus Armbruster
2016-01-19 16:10 ` [Qemu-devel] [PATCH v9 19/37] qmp: Fix reference-counting of qnull on empty output visit Eric Blake
2016-01-21 10:27   ` Markus Armbruster
2016-01-21 13:19     ` Markus Armbruster
2016-01-19 16:10 ` [Qemu-devel] [PATCH v9 20/37] qmp: Don't abuse stack to track qmp-output root Eric Blake
2016-01-21 13:58   ` Markus Armbruster
2016-01-29  3:06     ` Eric Blake
2016-01-19 16:10 ` [Qemu-devel] [PATCH v9 21/37] qapi: Document visitor interfaces, add assertions Eric Blake
2016-01-21 20:08   ` Markus Armbruster
2016-01-22  0:30     ` Eric Blake
2016-01-22 12:18       ` Markus Armbruster
2016-02-10  0:23         ` Eric Blake
2016-02-10  7:38           ` Markus Armbruster
2016-01-19 16:10 ` [Qemu-devel] [PATCH v9 22/37] qapi: Add visit_type_null() visitor Eric Blake
2016-01-22 17:00   ` Markus Armbruster
2016-01-19 16:10 ` [Qemu-devel] [PATCH v9 23/37] qmp: Support explicit null during input visit Eric Blake
2016-01-22 17:12   ` Markus Armbruster
2016-02-01 23:52     ` Eric Blake
2016-01-19 16:10 ` [Qemu-devel] [PATCH v9 24/37] qmp: Tighten output visitor rules Eric Blake
2016-01-22 19:11   ` Markus Armbruster
2016-01-19 16:10 ` [Qemu-devel] [PATCH v9 25/37] spapr_drc: Expose 'null' in qom-get when there is no fdt Eric Blake
2016-01-22 19:15   ` Markus Armbruster
2016-01-19 16:10 ` [Qemu-devel] [PATCH v9 26/37] qapi: Simplify excess input reporting in input visitors Eric Blake
2016-01-22 19:24   ` Markus Armbruster
2016-01-22 19:37     ` Eric Blake
2016-01-25  9:27       ` Markus Armbruster
2016-01-25 10:43         ` Laszlo Ersek
2016-01-27 13:54   ` [Qemu-devel] [PATCH 0/3] qapi-visit: Unify struct and union visit Markus Armbruster
2016-01-27 13:54     ` [Qemu-devel] [PATCH 1/3] qapi-visit: Simplify how we visit common union members Markus Armbruster
2016-01-27 21:48       ` Eric Blake
2016-01-27 13:54     ` [Qemu-devel] [PATCH 2/3] qapi-visit: Clean up code generated around visit_end_union() Markus Armbruster
2016-01-27 14:02       ` Eric Blake
2016-01-27 14:46         ` Markus Armbruster
2016-01-27 13:54     ` [Qemu-devel] [PATCH 3/3] qapi-visit: Unify struct and union visit Markus Armbruster
2016-01-19 16:10 ` [Qemu-devel] [PATCH v9 27/37] qapi: Add type.is_empty() helper Eric Blake
2016-01-25 14:15   ` Markus Armbruster
2016-01-19 16:10 ` [Qemu-devel] [PATCH v9 28/37] qapi: Fix command with named empty argument type Eric Blake
2016-01-25 15:03   ` Markus Armbruster
2016-01-19 16:10 ` [Qemu-devel] [PATCH v9 29/37] qapi: Eliminate empty visit_type_FOO_fields Eric Blake
2016-01-25 17:04   ` Markus Armbruster
2016-02-17  4:57     ` Eric Blake
2016-01-19 16:10 ` [Qemu-devel] [PATCH v9 30/37] qapi: Canonicalize missing object to :empty Eric Blake
2016-01-25 19:04   ` Markus Armbruster
2016-01-26 16:29     ` Markus Armbruster
2016-01-27  8:00       ` Markus Armbruster
2016-01-19 16:10 ` [Qemu-devel] [PATCH v9 31/37] qapi-visit: Unify struct and union visit Eric Blake
2016-01-27 14:12   ` Markus Armbruster
2016-01-19 16:10 ` [Qemu-devel] [PATCH v9 32/37] qapi: Rework deallocation of partial struct Eric Blake
2016-01-27 16:41   ` Markus Armbruster
2016-01-19 16:10 ` [Qemu-devel] [PATCH v9 33/37] qapi: Split visit_end_struct() into pieces Eric Blake
2016-01-27 17:20   ` Markus Armbruster
2016-01-19 16:10 ` [Qemu-devel] [PATCH v9 34/37] qapi: Simplify semantics of visit_next_list() Eric Blake
2016-01-28 13:37   ` Markus Armbruster
2016-01-19 16:10 ` [Qemu-devel] [PATCH v9 35/37] qapi: Change visit_type_FOO() to no longer return partial objects Eric Blake
2016-01-28 15:24   ` Markus Armbruster
2016-01-28 17:05     ` Eric Blake [this message]
2016-01-29 12:03       ` Markus Armbruster
2016-01-29 15:13         ` Eric Blake
2016-01-19 16:10 ` [Qemu-devel] [PATCH v9 36/37] RFC: qapi: Adjust layout of FooList types Eric Blake
2016-01-28 15:34   ` Markus Armbruster
2016-01-28 17:23     ` Eric Blake
2016-01-29  8:19       ` Markus Armbruster
2016-01-19 16:10 ` [Qemu-devel] [PATCH v9 37/37] qapi: Update docs to match recent generator changes Eric Blake
2016-01-28 15:37   ` Markus Armbruster
2016-01-28 17:56 ` [Qemu-devel] [PATCH v9 00/37] qapi visitor cleanups (post-introspection cleanups subset E) 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=56AA4A4A.60703@redhat.com \
    --to=eblake@redhat.com \
    --cc=armbru@redhat.com \
    --cc=marcandre.lureau@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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.