From 2f42c2c195047f312eded443adbaadceab294bd9 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Fri, 23 Feb 2018 19:53:12 -0500 Subject: [PATCH] qapi: Change visit_type_FOO() to no longer return partial objects Returning a partial object on error is an invitation for a careless caller to leak memory. We already fixed things in an earlier patch to guarantee NULL if visit_start fails ("qapi: Guarantee NULL obj on input visitor callback error"), but that does not help the case where visit_start succeeds but some other failure happens before visit_end, such that we leak a partially constructed object outside visit_type_FOO(). 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. The decision is done by adding visit_is_input(), then updating the generated code to check if additional cleanup is needed based on the type of visitor in use. 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). Backports commit 68ab47e4b4ecc1c4649362b8cc1e49794d1a6537 from qemu --- qemu/include/qapi/visitor.h | 25 ++++++++++++++++--------- qemu/qapi/qapi-visit-core.c | 5 +++++ qemu/scripts/qapi-visit.py | 22 +++++++++++++--------- 3 files changed, 34 insertions(+), 18 deletions(-) diff --git a/qemu/include/qapi/visitor.h b/qemu/include/qapi/visitor.h index e947f672..304f476f 100644 --- a/qemu/include/qapi/visitor.h +++ b/qemu/include/qapi/visitor.h @@ -68,12 +68,14 @@ * member @name is not present, or is present but not the specified * type). * - * FIXME: At present, visit_type_FOO() is an awkward interface: input - * visitors may allocate an incomplete *@obj even when reporting an - * error, but using an output visitor with an incomplete object has - * undefined behavior. To avoid a memory leak, callers must use - * qapi_free_FOO() even on error (this uses the dealloc visitor, and - * safely handles an incomplete object). + * 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. * * For the QAPI object types (structs, unions, and alternates), there * is an additional generated function in qapi-visit.h compatible @@ -108,7 +110,6 @@ * v = ...obtain input visitor... * visit_type_Foo(v, NULL, &f, &err); * if (err) { - * qapi_free_Foo(f); * ...handle error... * } else { * ...use f... @@ -126,7 +127,6 @@ * v = ...obtain input visitor... * visit_type_FooList(v, NULL, &l, &err); * if (err) { - * qapi_free_FooList(l); * ...handle error... * } else { * for ( ; l; l = l->next) { @@ -156,7 +156,9 @@ * helpers that rely on in-tree information to control the walk: * visit_optional() for the 'has_member' field associated with * optional 'member' in the C struct; and visit_next_list() for - * advancing through a FooList linked list. Only the generated + * advancing through a FooList linked list. Similarly, the + * visit_is_input() helper makes it possible to write code that is + * visitor-agnostic everywhere except for cleanup. Only the generated * visit_type functions need to use these helpers. * * It is also possible to use the visitors to do a virtual walk, where @@ -411,6 +413,11 @@ bool visit_optional(Visitor *v, const char *name, bool *present); void visit_type_enum(Visitor *v, const char *name, int *obj, const char *const strings[], Error **errp); +/* + * Check if visitor is an input visitor. + */ +bool visit_is_input(Visitor *v); + /*** Visiting built-in types ***/ /* diff --git a/qemu/qapi/qapi-visit-core.c b/qemu/qapi/qapi-visit-core.c index a1475715..a7025872 100644 --- a/qemu/qapi/qapi-visit-core.c +++ b/qemu/qapi/qapi-visit-core.c @@ -99,6 +99,11 @@ bool visit_optional(Visitor *v, const char *name, bool *present) return *present; } +bool visit_is_input(Visitor *v) +{ + return v->type == VISITOR_INPUT; +} + void visit_type_int(Visitor *v, const char *name, int64_t *obj, Error **errp) { assert(obj); diff --git a/qemu/scripts/qapi-visit.py b/qemu/scripts/qapi-visit.py index e89e672e..53e551ff 100644 --- a/qemu/scripts/qapi-visit.py +++ b/qemu/scripts/qapi-visit.py @@ -106,10 +106,6 @@ out: def gen_visit_list(name, element_type): - # FIXME: if *obj is NULL on entry, and the first visit_next_list() - # assigns to *obj, while a later one fails, we should clean up *obj - # rather than leaving it non-NULL. As currently written, the caller must - # call qapi_free_FOOList() to avoid a memory leak of the partial FOOList. return mcgen(''' void visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s **obj, Error **errp) @@ -134,6 +130,10 @@ void visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s **obj, Error error_propagate(errp, err); err = NULL; visit_end_list(v); + if (err && visit_is_input(v)) { + qapi_free_%(c_name)s(*obj); + *obj = NULL; + } out: error_propagate(errp, err); } @@ -211,20 +211,20 @@ void visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s **obj, Error "%(name)s"); } visit_end_alternate(v); + if (err && visit_is_input(v)) { + qapi_free_%(c_name)s(*obj); + *obj = NULL; + } out: error_propagate(errp, err); } ''', - name=name) + name=name, c_name=c_name(name)) return ret def gen_visit_object(name, base, members, variants): - # FIXME: if *obj is NULL on entry, and visit_start_struct() assigns to - # *obj, but then visit_type_FOO_members() fails, we should clean up *obj - # rather than leaving it non-NULL. As currently written, the caller must - # call qapi_free_FOO() to avoid a memory leak of the partial FOO. return mcgen(''' void visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s **obj, Error **errp) @@ -245,6 +245,10 @@ 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 (err && visit_is_input(v)) { + qapi_free_%(c_name)s(*obj); + *obj = NULL; + } out: error_propagate(errp, err); }