diff options
-rw-r--r-- | docs/qapi-code-gen.txt | 8 | ||||
-rw-r--r-- | include/qapi/visitor.h | 25 | ||||
-rw-r--r-- | qapi/qapi-visit-core.c | 5 | ||||
-rw-r--r-- | scripts/qapi-visit.py | 22 | ||||
-rw-r--r-- | tests/test-qmp-commands.c | 13 | ||||
-rw-r--r-- | tests/test-qmp-input-strict.c | 17 | ||||
-rw-r--r-- | tests/test-qmp-input-visitor.c | 10 |
7 files changed, 57 insertions, 43 deletions
diff --git a/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt index fe61c5c..d7d6987 100644 --- a/docs/qapi-code-gen.txt +++ b/docs/qapi-code-gen.txt @@ -905,6 +905,10 @@ Example: visit_check_struct(v, &err); out_obj: visit_end_struct(v); + if (err && visit_is_input(v)) { + qapi_free_UserDefOne(*obj); + *obj = NULL; + } out: error_propagate(errp, err); } @@ -929,6 +933,10 @@ Example: } visit_end_list(v); + if (err && visit_is_input(v)) { + qapi_free_UserDefOneList(*obj); + *obj = NULL; + } out: error_propagate(errp, err); } diff --git a/include/qapi/visitor.h b/include/qapi/visitor.h index 8de6b43..4d12167 100644 --- a/include/qapi/visitor.h +++ b/include/qapi/visitor.h @@ -66,12 +66,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 @@ -106,7 +108,6 @@ * v = ...obtain input visitor... * visit_type_Foo(v, NULL, &f, &err); * if (err) { - * qapi_free_Foo(f); * ...handle error... * } else { * ...use f... @@ -124,7 +125,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) { @@ -154,7 +154,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 @@ -405,6 +407,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/qapi/qapi-visit-core.c b/qapi/qapi-visit-core.c index d6bf4bd..eada467 100644 --- a/qapi/qapi-visit-core.c +++ b/qapi/qapi-visit-core.c @@ -104,6 +104,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/scripts/qapi-visit.py b/scripts/qapi-visit.py index 8b7efcc..70ea8ca 100644 --- a/scripts/qapi-visit.py +++ b/scripts/qapi-visit.py @@ -108,10 +108,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 } 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); } diff --git a/tests/test-qmp-commands.c b/tests/test-qmp-commands.c index 597fb44..5c3edd7 100644 --- a/tests/test-qmp-commands.c +++ b/tests/test-qmp-commands.c @@ -228,14 +228,13 @@ static void test_dealloc_partial(void) QDECREF(ud2_dict); } - /* verify partial success */ - assert(ud2 != NULL); - assert(ud2->string0 != NULL); - assert(strcmp(ud2->string0, text) == 0); - assert(ud2->dict1 == NULL); - - /* confirm & release construction error */ + /* verify that visit_type_XXX() cleans up properly on error */ error_free_or_abort(&err); + assert(!ud2); + + /* Manually create a partial object, leaving ud2->dict1 at NULL */ + ud2 = g_new0(UserDefTwo, 1); + ud2->string0 = g_strdup(text); /* tear down partial object */ qapi_free_UserDefTwo(ud2); diff --git a/tests/test-qmp-input-strict.c b/tests/test-qmp-input-strict.c index 2b053a2..4602529 100644 --- a/tests/test-qmp-input-strict.c +++ b/tests/test-qmp-input-strict.c @@ -182,10 +182,7 @@ static void test_validate_fail_struct(TestInputVisitorData *data, visit_type_TestStruct(v, NULL, &p, &err); error_free_or_abort(&err); - if (p) { - g_free(p->string); - } - g_free(p); + g_assert(!p); } static void test_validate_fail_struct_nested(TestInputVisitorData *data, @@ -199,7 +196,7 @@ static void test_validate_fail_struct_nested(TestInputVisitorData *data, visit_type_UserDefTwo(v, NULL, &udp, &err); error_free_or_abort(&err); - qapi_free_UserDefTwo(udp); + g_assert(!udp); } static void test_validate_fail_list(TestInputVisitorData *data, @@ -213,7 +210,7 @@ static void test_validate_fail_list(TestInputVisitorData *data, visit_type_UserDefOneList(v, NULL, &head, &err); error_free_or_abort(&err); - qapi_free_UserDefOneList(head); + g_assert(!head); } static void test_validate_fail_union_native_list(TestInputVisitorData *data, @@ -228,7 +225,7 @@ static void test_validate_fail_union_native_list(TestInputVisitorData *data, visit_type_UserDefNativeListUnion(v, NULL, &tmp, &err); error_free_or_abort(&err); - qapi_free_UserDefNativeListUnion(tmp); + g_assert(!tmp); } static void test_validate_fail_union_flat(TestInputVisitorData *data, @@ -242,7 +239,7 @@ static void test_validate_fail_union_flat(TestInputVisitorData *data, visit_type_UserDefFlatUnion(v, NULL, &tmp, &err); error_free_or_abort(&err); - qapi_free_UserDefFlatUnion(tmp); + g_assert(!tmp); } static void test_validate_fail_union_flat_no_discrim(TestInputVisitorData *data, @@ -257,7 +254,7 @@ static void test_validate_fail_union_flat_no_discrim(TestInputVisitorData *data, visit_type_UserDefFlatUnion2(v, NULL, &tmp, &err); error_free_or_abort(&err); - qapi_free_UserDefFlatUnion2(tmp); + g_assert(!tmp); } static void test_validate_fail_alternate(TestInputVisitorData *data, @@ -271,7 +268,7 @@ static void test_validate_fail_alternate(TestInputVisitorData *data, visit_type_UserDefAlternate(v, NULL, &tmp, &err); error_free_or_abort(&err); - qapi_free_UserDefAlternate(tmp); + g_assert(!tmp); } static void do_test_validate_qmp_introspect(TestInputVisitorData *data, diff --git a/tests/test-qmp-input-visitor.c b/tests/test-qmp-input-visitor.c index 6617276..cee07ce 100644 --- a/tests/test-qmp-input-visitor.c +++ b/tests/test-qmp-input-visitor.c @@ -773,18 +773,12 @@ static void test_visitor_in_errors(TestInputVisitorData *data, visit_type_TestStruct(v, NULL, &p, &err); error_free_or_abort(&err); - /* FIXME - a failed parse should not leave a partially-allocated p - * for us to clean up; this could cause callers to leak memory. */ - g_assert(p->string == NULL); - - g_free(p->string); - g_free(p); + g_assert(!p); v = visitor_input_test_init(data, "[ '1', '2', false, '3' ]"); visit_type_strList(v, NULL, &q, &err); error_free_or_abort(&err); - assert(q); - qapi_free_strList(q); + assert(!q); } static void test_visitor_in_wrong_type(TestInputVisitorData *data, |