On 04/15/2016 08:49 AM, Markus Armbruster wrote: > Eric Blake <[email protected]> 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), so callers >> can now unconditionally use qapi_free_FOO() to clean up regardless >> of whether an error occurred. > > Hmm, wasn't the "assign null on error" part done in a prior patch? > Checking... no, only half of it, in PATCH 03: there, we went from "may > store an incomplete object on error" to "store either an incomplete > object or null on error". Now we go on to just "store null on error". > Correct?
Yes. I'll tweak the wording to make it clearer.
>
>> The decision is done by enhancing qapi-visit-core to return true
>> for input visitors (the callbacks themselves do not need
>> modification); since we've documented that visit_end_* must be
>> called after any successful visit_start_*, that is a sufficient
>> point for knowing that something was allocated during start.
>
> I find this sentence a bit confusing. Let me try:
>
> To help visitor-agnostic code, such as the generated qapi-visit.c,
> make the visit_end_FOO() return true when something was allocated.
> Easily done in the visitor core, no need to change the callbacks.
>
> But see my comments on the visit_end_FOO() inline.
Reply below, where your comments are indeed worth thinking about.
>
>> 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).
>
> Should this note be in PATCH 03?
>
> The inconsistency isn't pretty, but tolerable if it simplifies things.
No. Patch 03 fixed visit_start_struct, not visit_type_FOO. Since it is
this patch that is tweaking visit_type_FOO, I have to explain why
visit_type_ENUM preserves values, while visit_type_OBJ sets to NULL.
>> *
>> - * FIXME: At present, input visitors may allocate an incomplete *@obj
>> - * even when visit_type_FOO() reports an error. Using an output
>> - * visitor with an incomplete object has undefined behavior; callers
>> - * must call qapi_free_FOO() (which uses the dealloc visitor, and
>> - * safely handles an incomplete object) to avoid a memory leak.
>> + * 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.
>
> Okay.
And this matches the commit message explaining the difference between
scalar and object (and also applies to visit_type_int being a scalar
that leaves the value unchanged on error).
>
>> + * 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.
>
> Where do the incomplete objects come from now? I thought this patch
> gets rid of them.
Still possible to create one by manual means, just no longer possible
from a QAPI input visitor. I'll tweak the wording.
>> -void visit_end_struct(Visitor *v);
>> +bool visit_end_struct(Visitor *v);
>
> I generally like functions to return something useful, but not in this
> case, because the function name gives you no clue about its value.
> Consider:
>
> if (visit_end_struct(v) && err) {
> qapi_free_FOO(*obj);
> *obj = NULL;
> }
>
> To find out what this means, a reader not familiar with visitors almost
> certainly needs to refer to visit_end_struct()'s contract or code.
>
> Compare:
>
> visit_end_struct(v);
> if (err && v->type == VISITOR_INPUT) {
v->type is a layering violation...
> qapi_free_FOO(*obj);
> *obj = NULL;
> }
>
> Or:
>
> visit_end_struct(v);
> if (err && visit_is_input(v)) {
...but this is doable by exporting visit_is_input().
> qapi_free_FOO(*obj);
> *obj = NULL;
> }
Makes the generated code have more lines, but who really cares. So
consider it done in v15.
>> +++ b/qapi/qapi-visit-core.c
>> @@ -23,11 +23,17 @@
>> void visit_start_struct(Visitor *v, const char *name, void **obj,
>> size_t size, Error **errp)
>> {
>> + Error *err = NULL;
>> +
>> if (obj) {
>> assert(size);
>> assert(v->type != VISITOR_OUTPUT || *obj);
>> }
>> - v->start_struct(v, name, obj, size, errp);
>> + v->start_struct(v, name, obj, size, &err);
>> + if (obj && v->type == VISITOR_INPUT) {
>> + assert(err || *obj);
>> + }
>> + error_propagate(errp, err);
>
> Sure this belongs to this patch? More of the same below.
Hmm. Patch 3 was the one that tightened semantics on visit_start, so I
can certainly try to hoist the added assertions there.
>> static void test_validate_fail_alternate(TestInputVisitorData *data,
>> const void *unused)
>> {
>> - UserDefAlternate *tmp = NULL;
>> + UserDefAlternate *tmp;
>
> Did this initialization become dead in PATCH 03 already?
Probably :)
--
Eric Blake eblake redhat com +1-919-301-3266
Libvirt virtualization library http://libvirt.org
signature.asc
Description: OpenPGP digital signature
