mirror of
https://github.com/yuzu-emu/unicorn.git
synced 2025-08-01 13:01:03 +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
|
* member @name is not present, or is present but not the specified
|
||||||
* type).
|
* type).
|
||||||
*
|
*
|
||||||
* FIXME: At present, visit_type_FOO() is an awkward interface: input
|
* If an error is detected during visit_type_FOO() with an input
|
||||||
* visitors may allocate an incomplete *@obj even when reporting an
|
* visitor, then *@obj will be NULL for pointer types, and left
|
||||||
* error, but using an output visitor with an incomplete object has
|
* unchanged for scalar types. Using an output visitor with an
|
||||||
* undefined behavior. To avoid a memory leak, callers must use
|
* incomplete object has undefined behavior (other than a special case
|
||||||
* qapi_free_FOO() even on error (this uses the dealloc visitor, and
|
* for visit_type_str() treating NULL like ""), while the dealloc
|
||||||
* safely handles an incomplete object).
|
* 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
|
* For the QAPI object types (structs, unions, and alternates), there
|
||||||
* is an additional generated function in qapi-visit.h compatible
|
* is an additional generated function in qapi-visit.h compatible
|
||||||
|
@ -108,7 +110,6 @@
|
||||||
* v = ...obtain input visitor...
|
* v = ...obtain input visitor...
|
||||||
* visit_type_Foo(v, NULL, &f, &err);
|
* visit_type_Foo(v, NULL, &f, &err);
|
||||||
* if (err) {
|
* if (err) {
|
||||||
* qapi_free_Foo(f);
|
|
||||||
* ...handle error...
|
* ...handle error...
|
||||||
* } else {
|
* } else {
|
||||||
* ...use f...
|
* ...use f...
|
||||||
|
@ -126,7 +127,6 @@
|
||||||
* v = ...obtain input visitor...
|
* v = ...obtain input visitor...
|
||||||
* visit_type_FooList(v, NULL, &l, &err);
|
* visit_type_FooList(v, NULL, &l, &err);
|
||||||
* if (err) {
|
* if (err) {
|
||||||
* qapi_free_FooList(l);
|
|
||||||
* ...handle error...
|
* ...handle error...
|
||||||
* } else {
|
* } else {
|
||||||
* for ( ; l; l = l->next) {
|
* for ( ; l; l = l->next) {
|
||||||
|
@ -156,7 +156,9 @@
|
||||||
* helpers that rely on in-tree information to control the walk:
|
* helpers that rely on in-tree information to control the walk:
|
||||||
* visit_optional() for the 'has_member' field associated with
|
* visit_optional() for the 'has_member' field associated with
|
||||||
* optional 'member' in the C struct; and visit_next_list() for
|
* 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.
|
* visit_type functions need to use these helpers.
|
||||||
*
|
*
|
||||||
* It is also possible to use the visitors to do a virtual walk, where
|
* 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,
|
void visit_type_enum(Visitor *v, const char *name, int *obj,
|
||||||
const char *const strings[], Error **errp);
|
const char *const strings[], Error **errp);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check if visitor is an input visitor.
|
||||||
|
*/
|
||||||
|
bool visit_is_input(Visitor *v);
|
||||||
|
|
||||||
/*** Visiting built-in types ***/
|
/*** Visiting built-in types ***/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -99,6 +99,11 @@ bool visit_optional(Visitor *v, const char *name, bool *present)
|
||||||
return *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)
|
void visit_type_int(Visitor *v, const char *name, int64_t *obj, Error **errp)
|
||||||
{
|
{
|
||||||
assert(obj);
|
assert(obj);
|
||||||
|
|
|
@ -106,10 +106,6 @@ out:
|
||||||
|
|
||||||
|
|
||||||
def gen_visit_list(name, element_type):
|
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('''
|
return mcgen('''
|
||||||
|
|
||||||
void visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s **obj, Error **errp)
|
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);
|
error_propagate(errp, err);
|
||||||
err = NULL;
|
err = NULL;
|
||||||
visit_end_list(v);
|
visit_end_list(v);
|
||||||
|
if (err && visit_is_input(v)) {
|
||||||
|
qapi_free_%(c_name)s(*obj);
|
||||||
|
*obj = NULL;
|
||||||
|
}
|
||||||
out:
|
out:
|
||||||
error_propagate(errp, err);
|
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");
|
"%(name)s");
|
||||||
}
|
}
|
||||||
visit_end_alternate(v);
|
visit_end_alternate(v);
|
||||||
|
if (err && visit_is_input(v)) {
|
||||||
|
qapi_free_%(c_name)s(*obj);
|
||||||
|
*obj = NULL;
|
||||||
|
}
|
||||||
out:
|
out:
|
||||||
error_propagate(errp, err);
|
error_propagate(errp, err);
|
||||||
}
|
}
|
||||||
''',
|
''',
|
||||||
name=name)
|
name=name, c_name=c_name(name))
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
def gen_visit_object(name, base, members, variants):
|
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('''
|
return mcgen('''
|
||||||
|
|
||||||
void visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s **obj, Error **errp)
|
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);
|
visit_check_struct(v, &err);
|
||||||
out_obj:
|
out_obj:
|
||||||
visit_end_struct(v);
|
visit_end_struct(v);
|
||||||
|
if (err && visit_is_input(v)) {
|
||||||
|
qapi_free_%(c_name)s(*obj);
|
||||||
|
*obj = NULL;
|
||||||
|
}
|
||||||
out:
|
out:
|
||||||
error_propagate(errp, err);
|
error_propagate(errp, err);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue