mirror of
https://github.com/yuzu-emu/unicorn.git
synced 2025-03-30 14:07:00 +00:00
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
This commit is contained in:
parent
0d52542da2
commit
2f42c2c195
|
@ -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 ***/
|
||||
|
||||
/*
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue