From patchwork Tue Mar 1 05:14:32 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Eric Blake X-Patchwork-Id: 8461971 Return-Path: X-Original-To: patchwork-qemu-devel@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork1.web.kernel.org (Postfix) with ESMTP id 8A8949F372 for ; Tue, 1 Mar 2016 05:19:23 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 41C1E20304 for ; Tue, 1 Mar 2016 05:19:22 +0000 (UTC) Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id A2C30202FF for ; Tue, 1 Mar 2016 05:19:20 +0000 (UTC) Received: from localhost ([::1]:40913 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1aaciN-000894-VC for patchwork-qemu-devel@patchwork.kernel.org; Tue, 01 Mar 2016 00:19:19 -0500 Received: from eggs.gnu.org ([2001:4830:134:3::10]:59849) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1aace3-0000W1-Lp for qemu-devel@nongnu.org; Tue, 01 Mar 2016 00:14:53 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1aacdz-0006St-AJ for qemu-devel@nongnu.org; Tue, 01 Mar 2016 00:14:51 -0500 Received: from mx1.redhat.com ([209.132.183.28]:45184) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1aacdz-0006Sk-2f for qemu-devel@nongnu.org; Tue, 01 Mar 2016 00:14:47 -0500 Received: from int-mx14.intmail.prod.int.phx2.redhat.com (int-mx14.intmail.prod.int.phx2.redhat.com [10.5.11.27]) by mx1.redhat.com (Postfix) with ESMTPS id C4FA96314F; Tue, 1 Mar 2016 05:14:46 +0000 (UTC) Received: from red.redhat.com (ovpn-113-165.phx2.redhat.com [10.3.113.165]) by int-mx14.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id u215EZ9c018132; Tue, 1 Mar 2016 00:14:46 -0500 From: Eric Blake To: qemu-devel@nongnu.org Date: Mon, 29 Feb 2016 22:14:32 -0700 Message-Id: <1456809272-14184-19-git-send-email-eblake@redhat.com> In-Reply-To: <1456809272-14184-1-git-send-email-eblake@redhat.com> References: <1456809272-14184-1-git-send-email-eblake@redhat.com> X-Scanned-By: MIMEDefang 2.68 on 10.5.11.27 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.38]); Tue, 01 Mar 2016 05:14:46 +0000 (UTC) X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 209.132.183.28 Cc: armbru@redhat.com, Michael Roth Subject: [Qemu-devel] [PATCH v12 18/18] qapi: Change visit_type_FOO() to no longer return partial objects X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org Sender: qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org X-Spam-Status: No, score=-6.9 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_HI, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP 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. 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. 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). Signed-off-by: Eric Blake --- v12: rebase to latest, don't modify callback signatures, use newer approach for detecting input visitors, avoid 'allocated' boolean [no v10, v11] v9: fix bug in use of errp v8: rebase to earlier changes v7: rebase to earlier changes, enhance commit message, also fix visit_type_str() and visit_type_any() v6: rebase on top of earlier doc and formatting improvements, mention that *obj can be uninitialized on entry to an input visitor, rework semantics to keep valgrind happy on uninitialized input, break some long lines --- include/qapi/visitor.h | 40 ++++++++++++++++++++++++++++++---------- include/qapi/visitor-impl.h | 8 +++++--- scripts/qapi-visit.py | 25 +++++++++++++------------ qapi/qapi-visit-core.c | 41 ++++++++++++++++++++++++++++++++++------- tests/test-qmp-commands.c | 13 ++++++------- tests/test-qmp-input-strict.c | 19 ++++++++----------- tests/test-qmp-input-visitor.c | 10 ++-------- docs/qapi-code-gen.txt | 10 ++++++++-- 8 files changed, 106 insertions(+), 60 deletions(-) diff --git a/include/qapi/visitor.h b/include/qapi/visitor.h index a81878d..f676991 100644 --- a/include/qapi/visitor.h +++ b/include/qapi/visitor.h @@ -66,14 +66,16 @@ * }' if an error is encountered on "value" (or to have the visitor * core auto-generate the nicer name). * - * 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. 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. * - * Likewise, the QAPI object types (structs, unions, and alternates) - * have a generated function in qapi-visit.h compatible with: + * Additionally, the QAPI object types (structs, unions, and + * alternates) have a generated function in qapi-visit.h compatible + * with: * * void visit_type_FOO_members(Visitor *v, FOO *obj, Error **errp); * @@ -256,8 +258,14 @@ void visit_check_struct(Visitor *v, Error **errp); * even if intermediate processing was skipped due to errors, to allow * the backend to release any resources. Destroying the visitor may * behave as if this was implicitly called. + * + * Returns true if this is an input visitor (that is, an allocation + * occurred during visit_start_struct() if obj was non-NULL). The + * caller can use this, along with tracking whether a local error + * occurred in the meantime, to decide when to undo allocation before + * returning control from a visit_type_FOO() function. */ -void visit_end_struct(Visitor *v); +bool visit_end_struct(Visitor *v); /* === Visiting lists */ @@ -313,8 +321,14 @@ GenericList *visit_next_list(Visitor *v, GenericList *tail, size_t size); * even if intermediate processing was skipped due to errors, to allow * the backend to release any resources. Destroying the visitor may * behave as if this was implicitly called. + * + * Returns true if this is an input visitor (that is, an allocation + * occurred during visit_start_list() if list was non-NULL). The + * caller can use this, along with tracking whether a local error + * occurred in the meantime, to decide when to undo allocation before + * returning control from a visit_type_FOO() function. */ -void visit_end_list(Visitor *v); +bool visit_end_list(Visitor *v); /* === Visiting alternates */ @@ -347,10 +361,16 @@ void visit_start_alternate(Visitor *v, const char *name, * the backend to release any resources. Destroying the visitor may * behave as if this was implicitly called. * + * Returns true if this is an input visitor (that is, an allocation + * occurred during visit_start_alternate() if obj was non-NULL). The + * caller can use this, along with tracking whether a local error + * occurred in the meantime, to decide when to undo allocation before + * returning control from a visit_type_FOO() function. + * * TODO: Should all the visit_end_* interfaces take obj parameter, so * that dealloc visitor need not track what was passed in visit_start? */ -void visit_end_alternate(Visitor *v); +bool visit_end_alternate(Visitor *v); /* === Other helpers */ diff --git a/include/qapi/visitor-impl.h b/include/qapi/visitor-impl.h index 0471465..f113869 100644 --- a/include/qapi/visitor-impl.h +++ b/include/qapi/visitor-impl.h @@ -42,7 +42,8 @@ struct Visitor /* Optional; intended for input visitors. */ void (*check_struct)(Visitor *v, Error **errp); - /* Must be set to visit structs. */ + /* Must be set to visit structs. The core takes care of the + * return value. */ void (*end_struct)(Visitor *v); /* Must be set; document if @list may not be NULL. */ @@ -52,7 +53,7 @@ struct Visitor /* Must be set. */ GenericList *(*next_list)(Visitor *v, GenericList *tail, size_t size); - /* Must be set. */ + /* Must be set. The core takes care of the return value. */ void (*end_list)(Visitor *v); /* Must be set by input and dealloc visitors to visit alternates; @@ -61,7 +62,8 @@ struct Visitor GenericAlternate **obj, size_t size, bool promote_int, Error **errp); - /* Optional, needed for dealloc visitor. */ + /* Optional, needed for dealloc visitor. The core takes care of + * the return value. */ void (*end_alternate)(Visitor *v); /* Must be set. */ diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py index 5921bad..fab00b9 100644 --- a/scripts/qapi-visit.py +++ b/scripts/qapi-visit.py @@ -110,10 +110,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) @@ -133,7 +129,10 @@ void visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s **obj, Error break; } } - visit_end_list(v); + if (visit_end_list(v) && err) { + qapi_free_%(c_name)s(*obj); + *obj = NULL; + } out: error_propagate(errp, err); } @@ -210,12 +209,15 @@ void visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s **obj, Error error_setg(&err, QERR_INVALID_PARAMETER_TYPE, name ? name : "null", "%(name)s"); } - visit_end_alternate(v); + if (visit_end_alternate(v) && err) { + qapi_free_%(c_name)s(*obj); + *obj = NULL; + } out: error_propagate(errp, err); } ''', - name=name) + name=name, c_name=c_name(name)) return ret @@ -223,10 +225,6 @@ out: def gen_visit_object(name, base, members, variants): ret = gen_visit_object_members(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. ret += mcgen(''' void visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s **obj, Error **errp) @@ -246,7 +244,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 (visit_end_struct(v) && err) { + qapi_free_%(c_name)s(*obj); + *obj = NULL; + } out: error_propagate(errp, err); } diff --git a/qapi/qapi-visit-core.c b/qapi/qapi-visit-core.c index bbcedb1..7c36b24 100644 --- a/qapi/qapi-visit-core.c +++ b/qapi/qapi-visit-core.c @@ -22,11 +22,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); } void visit_check_struct(Visitor *v, Error **errp) @@ -36,11 +42,13 @@ void visit_check_struct(Visitor *v, Error **errp) } } -void visit_end_struct(Visitor *v) +bool visit_end_struct(Visitor *v) { v->end_struct(v); + return v->type == VISITOR_INPUT; } + void visit_start_list(Visitor *v, const char *name, GenericList **list, size_t size, Error **errp) { @@ -54,26 +62,34 @@ GenericList *visit_next_list(Visitor *v, GenericList *tail, size_t size) return v->next_list(v, tail, size); } -void visit_end_list(Visitor *v) +bool visit_end_list(Visitor *v) { v->end_list(v); + return v->type == VISITOR_INPUT; } void visit_start_alternate(Visitor *v, const char *name, GenericAlternate **obj, size_t size, bool promote_int, Error **errp) { + Error *err = NULL; + assert(obj && size >= sizeof(GenericAlternate)); if (v->start_alternate) { - v->start_alternate(v, name, obj, size, promote_int, errp); + v->start_alternate(v, name, obj, size, promote_int, &err); + if (v->type == VISITOR_INPUT) { + assert(err || *obj); + } + error_propagate(errp, err); } } -void visit_end_alternate(Visitor *v) +bool visit_end_alternate(Visitor *v) { if (v->end_alternate) { v->end_alternate(v); } + return v->type == VISITOR_INPUT; } bool visit_optional(Visitor *v, const char *name, bool *present) @@ -205,12 +221,17 @@ void visit_type_bool(Visitor *v, const char *name, bool *obj, Error **errp) void visit_type_str(Visitor *v, const char *name, char **obj, Error **errp) { + Error *err = NULL; assert(obj); /* TODO: Fix callers to not pass NULL when they mean "", so that we * can enable: assert(v->type != VISITOR_OUTPUT || *obj); */ - v->type_str(v, name, obj, errp); + v->type_str(v, name, obj, &err); + if (v->type == VISITOR_INPUT) { + assert(err || *obj); + } + error_propagate(errp, err); } void visit_type_number(Visitor *v, const char *name, double *obj, @@ -222,9 +243,15 @@ void visit_type_number(Visitor *v, const char *name, double *obj, void visit_type_any(Visitor *v, const char *name, QObject **obj, Error **errp) { + Error *err = NULL; + assert(obj); assert(v->type != VISITOR_OUTPUT || *obj); - v->type_any(v, name, obj, errp); + v->type_any(v, name, obj, &err); + if (v->type == VISITOR_INPUT) { + assert(err || *obj); + } + error_propagate(errp, err); } void visit_type_null(Visitor *v, const char *name, Error **errp) diff --git a/tests/test-qmp-commands.c b/tests/test-qmp-commands.c index 14a9ebb..d6c494d 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 6a33aa4..9587b14 100644 --- a/tests/test-qmp-input-strict.c +++ b/tests/test-qmp-input-strict.c @@ -181,10 +181,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, @@ -198,7 +195,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, @@ -212,7 +209,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, @@ -227,7 +224,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, @@ -241,7 +238,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, @@ -256,13 +253,13 @@ 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, const void *unused) { - UserDefAlternate *tmp = NULL; + UserDefAlternate *tmp; Visitor *v; Error *err = NULL; @@ -270,7 +267,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 a62c2b1..19bed0a 100644 --- a/tests/test-qmp-input-visitor.c +++ b/tests/test-qmp-input-visitor.c @@ -759,18 +759,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, diff --git a/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt index 06f22b7..f7956b8 100644 --- a/docs/qapi-code-gen.txt +++ b/docs/qapi-code-gen.txt @@ -903,7 +903,10 @@ Example: } visit_check_struct(v, &err); out_obj: - visit_end_struct(v); + if (visit_end_struct(v) && err) { + qapi_free_UserDefOne(*obj); + *obj = NULL; + } out: error_propagate(errp, err); } @@ -926,7 +929,10 @@ Example: } } - visit_end_list(v); + if (visit_end_list(v) && err) { + qapi_free_UserDefOneList(*obj); + *obj = NULL; + } out: error_propagate(errp, err); }