From patchwork Thu Feb 11 18:31:13 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kevin Wolf X-Patchwork-Id: 12083857 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-13.6 required=3.0 tests=BAYES_00,DKIM_INVALID, DKIM_SIGNED,HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 1CEEFC433DB for ; Thu, 11 Feb 2021 18:48:11 +0000 (UTC) Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 5ED3964E66 for ; Thu, 11 Feb 2021 18:48:10 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 5ED3964E66 Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=redhat.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Received: from localhost ([::1]:35592 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1lAH0Z-0004ab-Jg for qemu-devel@archiver.kernel.org; Thu, 11 Feb 2021 13:48:07 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:41604) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1lAGlc-0004BD-EM for qemu-devel@nongnu.org; Thu, 11 Feb 2021 13:32:40 -0500 Received: from us-smtp-delivery-124.mimecast.com ([216.205.24.124]:24045) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.90_1) (envelope-from ) id 1lAGlM-0002aQ-7r for qemu-devel@nongnu.org; Thu, 11 Feb 2021 13:32:40 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1613068343; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=dpgg5NPwXLMnKHpc4TF8VHfAq7amSJKRbmN2mY/RkNk=; b=FHL2axVMTEg65Uopyr59IJdBDdLwpfo9j5Liw0ewy7JMCAC4Sn7Jn7oAEWX/N7h4dx7AVW 6AtuPBkhGPQ5SYHd1psL71paVLoAtZrt/g6r/jxKjgCSDDqaEG8IZOrQXs5g07YZiSm7gw 6Q5Nyiv2Pkas0SG0f9RBEMfanUQIP0M= Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-159-crQ_zjf9NWuuK7q-Pt3Uow-1; Thu, 11 Feb 2021 13:32:20 -0500 X-MC-Unique: crQ_zjf9NWuuK7q-Pt3Uow-1 Received: from smtp.corp.redhat.com (int-mx06.intmail.prod.int.phx2.redhat.com [10.5.11.16]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 2285B5224 for ; Thu, 11 Feb 2021 18:31:31 +0000 (UTC) Received: from merkur.fritz.box (ovpn-113-82.ams2.redhat.com [10.36.113.82]) by smtp.corp.redhat.com (Postfix) with ESMTP id 2A7825C6DF; Thu, 11 Feb 2021 18:31:30 +0000 (UTC) From: Kevin Wolf To: qemu-devel@nongnu.org Subject: [PATCH v2 1/6] qapi: Add interfaces for alias support to Visitor Date: Thu, 11 Feb 2021 19:31:13 +0100 Message-Id: <20210211183118.422036-2-kwolf@redhat.com> In-Reply-To: <20210211183118.422036-1-kwolf@redhat.com> References: <20210211183118.422036-1-kwolf@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.16 Authentication-Results: relay.mimecast.com; auth=pass smtp.auth=CUSA124A263 smtp.mailfrom=kwolf@redhat.com X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Received-SPF: pass client-ip=216.205.24.124; envelope-from=kwolf@redhat.com; helo=us-smtp-delivery-124.mimecast.com X-Spam_score_int: -26 X-Spam_score: -2.7 X-Spam_bar: -- X-Spam_report: (-2.7 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.569, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_MSPIKE_H3=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: kwolf@redhat.com, jsnow@redhat.com, armbru@redhat.com Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: "Qemu-devel" This adds functions to the Visitor interface that can be used to define aliases and alias scopes. Signed-off-by: Kevin Wolf --- include/qapi/visitor-impl.h | 12 ++++++++++ include/qapi/visitor.h | 44 +++++++++++++++++++++++++++++++++++++ qapi/qapi-visit-core.c | 22 +++++++++++++++++++ 3 files changed, 78 insertions(+) diff --git a/include/qapi/visitor-impl.h b/include/qapi/visitor-impl.h index 7362c043be..d9a6874528 100644 --- a/include/qapi/visitor-impl.h +++ b/include/qapi/visitor-impl.h @@ -113,6 +113,18 @@ struct Visitor The core takes care of the return type in the public interface. */ void (*optional)(Visitor *v, const char *name, bool *present); + /* + * Optional; intended for input visitors. If not given, aliases are + * ignored. + */ + void (*define_alias)(Visitor *v, const char *name, const char **source); + + /* Must be set if define_alias is set */ + void (*start_alias_scope)(Visitor *v); + + /* Must be set if define_alias is set */ + void (*end_alias_scope)(Visitor *v); + /* Must be set */ VisitorType type; diff --git a/include/qapi/visitor.h b/include/qapi/visitor.h index ebc19ede7f..2ecbc20624 100644 --- a/include/qapi/visitor.h +++ b/include/qapi/visitor.h @@ -459,6 +459,50 @@ void visit_end_alternate(Visitor *v, void **obj); */ bool visit_optional(Visitor *v, const char *name, bool *present); +/* + * Defines a new alias rule. + * + * If @name is non-NULL, the member called @name in the external + * representation of the currently visited object is defined as an + * alias for the member described by @source. It is not allowed to + * call this function when the currently visited type is not an + * object. + * + * If @name is NULL, all members of the object described by @source + * are considered to have alias members with the same key in the + * currently visited object. + * + * @source is a NULL-terminated non-empty array of names that describe + * the path to a member, starting from the currently visited object. + * All elements in @source except the last one should describe + * objects. If an intermediate element refers to a member with a + * non-object type, the alias won't work (this case can legitimately + * happen in unions where an alias only makes sense for one branch, + * but not for another). + * + * The alias stays valid until the current alias scope ends. + * visit_start/end_struct() implicitly start/end an alias scope. + * Additionally, visit_start/end_alias_scope() can be used to explicitly + * create a nested alias scope. + */ +void visit_define_alias(Visitor *v, const char *name, const char **source); + +/* + * Begins an explicit alias scope. + * + * Alias definitions after here will only stay valid until the + * corresponding visit_end_alias_scope() is called. + */ +void visit_start_alias_scope(Visitor *v); + +/* + * Ends an explicit alias scope. + * + * Alias definitions between the correspoding visit_start_alias_scope() + * call and here go out of scope and won't apply in later code any more. + */ +void visit_end_alias_scope(Visitor *v); + /* * Visit an enum value. * diff --git a/qapi/qapi-visit-core.c b/qapi/qapi-visit-core.c index 7e5f40e7f0..651dd88e02 100644 --- a/qapi/qapi-visit-core.c +++ b/qapi/qapi-visit-core.c @@ -135,6 +135,28 @@ bool visit_optional(Visitor *v, const char *name, bool *present) return *present; } +void visit_define_alias(Visitor *v, const char *name, const char **source) +{ + assert(source[0] != NULL); + if (v->define_alias) { + v->define_alias(v, name, source); + } +} + +void visit_start_alias_scope(Visitor *v) +{ + if (v->start_alias_scope) { + v->start_alias_scope(v); + } +} + +void visit_end_alias_scope(Visitor *v) +{ + if (v->end_alias_scope) { + v->end_alias_scope(v); + } +} + bool visit_is_input(Visitor *v) { return v->type == VISITOR_INPUT; From patchwork Thu Feb 11 18:31:14 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kevin Wolf X-Patchwork-Id: 12083851 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-13.6 required=3.0 tests=BAYES_00,DKIM_INVALID, DKIM_SIGNED,HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 30CC9C433DB for ; Thu, 11 Feb 2021 18:36:49 +0000 (UTC) Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id BEEED64E15 for ; Thu, 11 Feb 2021 18:36:48 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org BEEED64E15 Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=redhat.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Received: from localhost ([::1]:57454 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1lAGpb-0000zX-Qv for qemu-devel@archiver.kernel.org; Thu, 11 Feb 2021 13:36:47 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:41518) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1lAGlN-0003r2-TT for qemu-devel@nongnu.org; Thu, 11 Feb 2021 13:32:26 -0500 Received: from us-smtp-delivery-124.mimecast.com ([63.128.21.124]:28623) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.90_1) (envelope-from ) id 1lAGlG-0002Yh-VA for qemu-devel@nongnu.org; Thu, 11 Feb 2021 13:32:25 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1613068338; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=OEXr/KFAvHBwjAPUs5Het4HRC1PIKOoI7BKoe9KiT7k=; b=P6GwDwSZAZUjKVsdAQZWCdUTu8SPkXZeqezkUDj7tq90P5ITt5tZYVQhDKUFGKqV+awof4 3dHzkVzzzX2qVpuH/FRV6h7FEJNQmnWcPaQ0Y478lud3dGfZ+XCmOL2PZu7/4alZ2tRxiu jekPgB2hNwPVGryaC4u4uQfixZukMGA= Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-186-4K2O79jYNuq7drbFdzxuVA-1; Thu, 11 Feb 2021 13:32:15 -0500 X-MC-Unique: 4K2O79jYNuq7drbFdzxuVA-1 Received: from smtp.corp.redhat.com (int-mx06.intmail.prod.int.phx2.redhat.com [10.5.11.16]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 627CF814738 for ; Thu, 11 Feb 2021 18:31:33 +0000 (UTC) Received: from merkur.fritz.box (ovpn-113-82.ams2.redhat.com [10.36.113.82]) by smtp.corp.redhat.com (Postfix) with ESMTP id 6AFDD5C260; Thu, 11 Feb 2021 18:31:31 +0000 (UTC) From: Kevin Wolf To: qemu-devel@nongnu.org Subject: [PATCH v2 2/6] qapi: Remember alias definitions in qobject-input-visitor Date: Thu, 11 Feb 2021 19:31:14 +0100 Message-Id: <20210211183118.422036-3-kwolf@redhat.com> In-Reply-To: <20210211183118.422036-1-kwolf@redhat.com> References: <20210211183118.422036-1-kwolf@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.16 Authentication-Results: relay.mimecast.com; auth=pass smtp.auth=CUSA124A263 smtp.mailfrom=kwolf@redhat.com X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Received-SPF: pass client-ip=63.128.21.124; envelope-from=kwolf@redhat.com; helo=us-smtp-delivery-124.mimecast.com X-Spam_score_int: -33 X-Spam_score: -3.4 X-Spam_bar: --- X-Spam_report: (-3.4 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.569, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_LOW=-0.7, RCVD_IN_MSPIKE_H4=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: kwolf@redhat.com, jsnow@redhat.com, armbru@redhat.com Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: "Qemu-devel" This makes qobject-input-visitor remember the currently valid aliases in each StackObject. It doesn't actually allow using the aliases yet. Signed-off-by: Kevin Wolf --- qapi/qobject-input-visitor.c | 145 +++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) diff --git a/qapi/qobject-input-visitor.c b/qapi/qobject-input-visitor.c index 23843b242e..aa95cd49bd 100644 --- a/qapi/qobject-input-visitor.c +++ b/qapi/qobject-input-visitor.c @@ -29,6 +29,50 @@ #include "qemu/cutils.h" #include "qemu/option.h" +/* + * Describes an alias that is relevant for the current StackObject, + * either because it aliases a member of the currently visited object + * or because it aliases a member of a nested object. + * + * When processing a nested object, all InputVisitorAlias objects that + * are relevant for the nested object are propagated, i.e. copied with + * the name of the nested object removed from @source. + */ +typedef struct InputVisitorAlias { + /* StackObject in which the alias was defined */ + struct StackObject *alias_so; + + /* + * Alias name as defined for @alias_so. + * NULL means that this is a wildcard alias, i.e. all members of + * @src get an alias in @alias_so with the same name. + */ + const char *name; + + /* + * NULL terminated array representing a path to the source member + * that the alias refers to. + * + * Must contain at least one non-NULL element if @alias is not NULL. + * + * If it contains no non-NULL element, @alias_so must be different + * from the StackObject which contains this InputVisitorAlias in + * its aliases list. In this case, all elements in the currently + * visited object have an alias with the same name in @alias_so. + */ + const char **src; + + /* + * The alias remains valid as long as the StackObject which + * contains this InputVisitorAlias in its aliases list has + * StackObject.alias_scope_nesting >= InputVisitorAlias.scope_nesting + * or until the whole StackObject is removed. + */ + int scope_nesting; + + QSLIST_ENTRY(InputVisitorAlias) next; +} InputVisitorAlias; + typedef struct StackObject { const char *name; /* Name of @obj in its parent, if any */ QObject *obj; /* QDict or QList being visited */ @@ -38,6 +82,9 @@ typedef struct StackObject { const QListEntry *entry; /* If @obj is QList: unvisited tail */ unsigned index; /* If @obj is QList: list index of @entry */ + QSLIST_HEAD(, InputVisitorAlias) aliases; + int alias_scope_nesting; /* Number of open alias scopes */ + QSLIST_ENTRY(StackObject) node; /* parent */ } StackObject; @@ -203,6 +250,43 @@ static const char *qobject_input_get_keyval(QObjectInputVisitor *qiv, return qstring_get_str(qstr); } +/* + * Propagate aliases from the parent StackObject @src to its direct + * child StackObject @dst, which is representing the child struct @name. + * + * Every alias whose source path begins with @dst->name and which still + * applies in @dst (i.e. it is either a wildcard alias or has at least + * one more source path element) is propagated to @dst with the first + * element (i.e. @dst->name) removed from the source path. + */ +static void propagate_aliases(StackObject *dst, StackObject *src) +{ + InputVisitorAlias *a; + + QSLIST_FOREACH(a, &src->aliases, next) { + if (!a->src[0] || strcmp(a->src[0], dst->name)) { + continue; + } + + /* + * If this is not a wildcard alias, but a->src[1] is NULL, + * then it referred to dst->name in src and doesn't apply + * inside dst any more. + */ + if (a->src[1] || !a->name) { + InputVisitorAlias *alias = g_new(InputVisitorAlias, 1); + + *alias = (InputVisitorAlias) { + .name = a->name, + .alias_so = a->alias_so, + .src = &a->src[1], + }; + + QSLIST_INSERT_HEAD(&dst->aliases, alias, next); + } + } +} + static const QListEntry *qobject_input_push(QObjectInputVisitor *qiv, const char *name, QObject *obj, void *qapi) @@ -226,6 +310,9 @@ static const QListEntry *qobject_input_push(QObjectInputVisitor *qiv, g_hash_table_insert(h, (void *)qdict_entry_key(entry), NULL); } tos->h = h; + if (!QSLIST_EMPTY(&qiv->stack)) { + propagate_aliases(tos, QSLIST_FIRST(&qiv->stack)); + } } else { assert(qlist); tos->entry = qlist_first(qlist); @@ -257,10 +344,17 @@ static bool qobject_input_check_struct(Visitor *v, Error **errp) static void qobject_input_stack_object_free(StackObject *tos) { + InputVisitorAlias *a; + if (tos->h) { g_hash_table_unref(tos->h); } + while ((a = QSLIST_FIRST(&tos->aliases))) { + QSLIST_REMOVE_HEAD(&tos->aliases, next); + g_free(a); + } + g_free(tos); } @@ -274,6 +368,54 @@ static void qobject_input_pop(Visitor *v, void **obj) qobject_input_stack_object_free(tos); } +static void qobject_input_start_alias_scope(Visitor *v) +{ + QObjectInputVisitor *qiv = to_qiv(v); + StackObject *tos = QSLIST_FIRST(&qiv->stack); + + tos->alias_scope_nesting++; +} + +static void qobject_input_end_alias_scope(Visitor *v) +{ + QObjectInputVisitor *qiv = to_qiv(v); + StackObject *tos = QSLIST_FIRST(&qiv->stack); + InputVisitorAlias *a, *next; + + assert(tos->alias_scope_nesting > 0); + tos->alias_scope_nesting--; + + QSLIST_FOREACH_SAFE(a, &tos->aliases, next, next) { + if (a->scope_nesting > tos->alias_scope_nesting) { + QSLIST_REMOVE(&tos->aliases, a, InputVisitorAlias, next); + g_free(a); + } + } +} + +static void qobject_input_define_alias(Visitor *v, const char *name, + const char **source) +{ + QObjectInputVisitor *qiv = to_qiv(v); + StackObject *tos = QSLIST_FIRST(&qiv->stack); + InputVisitorAlias *alias = g_new(InputVisitorAlias, 1); + + /* + * The source path can become empty during alias propagation for + * wildcard aliases, but not when defining an alias (it would map + * all names onto themselves, which doesn't make sense). + */ + assert(source[0]); + + *alias = (InputVisitorAlias) { + .name = name, + .alias_so = tos, + .src = source, + }; + + QSLIST_INSERT_HEAD(&tos->aliases, alias, next); +} + static bool qobject_input_start_struct(Visitor *v, const char *name, void **obj, size_t size, Error **errp) { @@ -696,6 +838,9 @@ static QObjectInputVisitor *qobject_input_visitor_base_new(QObject *obj) v->visitor.end_list = qobject_input_end_list; v->visitor.start_alternate = qobject_input_start_alternate; v->visitor.optional = qobject_input_optional; + v->visitor.define_alias = qobject_input_define_alias; + v->visitor.start_alias_scope = qobject_input_start_alias_scope; + v->visitor.end_alias_scope = qobject_input_end_alias_scope; v->visitor.free = qobject_input_free; v->root = qobject_ref(obj); From patchwork Thu Feb 11 18:31:15 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kevin Wolf X-Patchwork-Id: 12083853 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-13.6 required=3.0 tests=BAYES_00,DKIM_INVALID, DKIM_SIGNED,HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 0A888C433E0 for ; Thu, 11 Feb 2021 18:41:55 +0000 (UTC) Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 36E3164E5F for ; Thu, 11 Feb 2021 18:41:54 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 36E3164E5F Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=redhat.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Received: from localhost ([::1]:60710 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1lAGuX-0002WW-1v for qemu-devel@archiver.kernel.org; Thu, 11 Feb 2021 13:41:53 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:41580) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1lAGlV-0003uB-NS for qemu-devel@nongnu.org; Thu, 11 Feb 2021 13:32:33 -0500 Received: from us-smtp-delivery-124.mimecast.com ([63.128.21.124]:27458) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.90_1) (envelope-from ) id 1lAGlM-0002aK-7m for qemu-devel@nongnu.org; Thu, 11 Feb 2021 13:32:33 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1613068341; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=YtWnpxgSynxYNfkEmIFF9qrS7ajvKamh+DoUKn1xrko=; b=Wp6qffEQB+MGShBy5D7f+4THF8mmOF7pg5F/fBGNI5ZOYgt+dG3ctZo4Fe291/24yjx9dO KSruS0PvxSdiO92SsTjNFNO/ARxfBH94yxXjjLA5oSfmvyhPgIYfJVIKf+adf+72KC2WlG GWal7IgMFGFoOaJ2xfHlNdgUIZq/8XU= Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-316-4EkLj1axPumMRC3tV1yYrA-1; Thu, 11 Feb 2021 13:32:20 -0500 X-MC-Unique: 4EkLj1axPumMRC3tV1yYrA-1 Received: from smtp.corp.redhat.com (int-mx06.intmail.prod.int.phx2.redhat.com [10.5.11.16]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id A1D5C1851B14 for ; Thu, 11 Feb 2021 18:31:34 +0000 (UTC) Received: from merkur.fritz.box (ovpn-113-82.ams2.redhat.com [10.36.113.82]) by smtp.corp.redhat.com (Postfix) with ESMTP id AB04C5C260; Thu, 11 Feb 2021 18:31:33 +0000 (UTC) From: Kevin Wolf To: qemu-devel@nongnu.org Subject: [PATCH v2 3/6] qapi: Simplify full_name_nth() in qobject-input-visitor Date: Thu, 11 Feb 2021 19:31:15 +0100 Message-Id: <20210211183118.422036-4-kwolf@redhat.com> In-Reply-To: <20210211183118.422036-1-kwolf@redhat.com> References: <20210211183118.422036-1-kwolf@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.16 Authentication-Results: relay.mimecast.com; auth=pass smtp.auth=CUSA124A263 smtp.mailfrom=kwolf@redhat.com X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Received-SPF: pass client-ip=63.128.21.124; envelope-from=kwolf@redhat.com; helo=us-smtp-delivery-124.mimecast.com X-Spam_score_int: -26 X-Spam_score: -2.7 X-Spam_bar: -- X-Spam_report: (-2.7 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.569, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_MSPIKE_H4=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_PASS=-0.001, T_SPF_HELO_TEMPERROR=0.01 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: kwolf@redhat.com, jsnow@redhat.com, armbru@redhat.com Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: "Qemu-devel" Instead of counting how many elements from the top of the stack we need to ignore until we find the thing we're interested in, we can just directly pass the StackObject pointer because all callers already know it. We only need a different way now to tell if we want to know the name of something contained in the given StackObject or of the StackObject itself. Passing name = NULL is the obvious way to request the latter. This simplifies the interface and makes it easier to use in cases where we have the StackObject, but don't know how many steps down the stack it is. Signed-off-by: Kevin Wolf --- qapi/qobject-input-visitor.c | 43 ++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/qapi/qobject-input-visitor.c b/qapi/qobject-input-visitor.c index aa95cd49bd..dd04ef0027 100644 --- a/qapi/qobject-input-visitor.c +++ b/qapi/qobject-input-visitor.c @@ -108,20 +108,20 @@ static QObjectInputVisitor *to_qiv(Visitor *v) } /* - * Find the full name of something @qiv is currently visiting. - * @qiv is visiting something named @name in the stack of containers - * @qiv->stack. - * If @n is zero, return its full name. - * If @n is positive, return the full name of the @n-th container - * counting from the top. The stack of containers must have at least - * @n elements. - * The returned string is valid until the next full_name_nth(@v) or - * destruction of @v. + * Find the full name of a member in @so which @qiv is currently + * visiting. If the currently visited thing is an object, @name is + * the (local) name of the member to describe. If it is a list, @name + * is ignored and the current index (so->index) is included. + * + * If @skip_member is true, find the full name of @so itself instead. + * @name must be NULL then. + * + * The returned string is valid until the next full_name_so(@qiv) or + * destruction of @qiv. */ -static const char *full_name_nth(QObjectInputVisitor *qiv, const char *name, - int n) +static const char *full_name_so(QObjectInputVisitor *qiv, const char *name, + bool skip_member, StackObject *so) { - StackObject *so; char buf[32]; if (qiv->errname) { @@ -130,10 +130,14 @@ static const char *full_name_nth(QObjectInputVisitor *qiv, const char *name, qiv->errname = g_string_new(""); } - QSLIST_FOREACH(so , &qiv->stack, node) { - if (n) { - n--; - } else if (qobject_type(so->obj) == QTYPE_QDICT) { + if (skip_member && so) { + assert(name == NULL); + name = so->name; + so = QSLIST_NEXT(so, node); + } + + for (; so; so = QSLIST_NEXT(so, node)) { + if (qobject_type(so->obj) == QTYPE_QDICT) { g_string_prepend(qiv->errname, name ?: ""); g_string_prepend_c(qiv->errname, '.'); } else { @@ -144,7 +148,6 @@ static const char *full_name_nth(QObjectInputVisitor *qiv, const char *name, } name = so->name; } - assert(!n); if (name) { g_string_prepend(qiv->errname, name); @@ -159,7 +162,9 @@ static const char *full_name_nth(QObjectInputVisitor *qiv, const char *name, static const char *full_name(QObjectInputVisitor *qiv, const char *name) { - return full_name_nth(qiv, name, 0); + StackObject *tos = QSLIST_FIRST(&qiv->stack); + + return full_name_so(qiv, name, false, tos); } static QObject *qobject_input_try_get_object(QObjectInputVisitor *qiv, @@ -503,7 +508,7 @@ static bool qobject_input_check_list(Visitor *v, Error **errp) if (tos->entry) { error_setg(errp, "Only %u list elements expected in %s", - tos->index + 1, full_name_nth(qiv, NULL, 1)); + tos->index + 1, full_name_so(qiv, NULL, true, tos)); return false; } return true; From patchwork Thu Feb 11 18:31:16 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kevin Wolf X-Patchwork-Id: 12083843 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-13.6 required=3.0 tests=BAYES_00,DKIM_INVALID, DKIM_SIGNED,HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 2F435C433E0 for ; Thu, 11 Feb 2021 18:34:13 +0000 (UTC) Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id B024064E15 for ; Thu, 11 Feb 2021 18:34:12 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org B024064E15 Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=redhat.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Received: from localhost ([::1]:50600 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1lAGn5-0005R2-RT for qemu-devel@archiver.kernel.org; Thu, 11 Feb 2021 13:34:11 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:41492) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1lAGlJ-0003qA-6D for qemu-devel@nongnu.org; Thu, 11 Feb 2021 13:32:21 -0500 Received: from us-smtp-delivery-124.mimecast.com ([63.128.21.124]:43827) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.90_1) (envelope-from ) id 1lAGlF-0002Ws-VZ for qemu-devel@nongnu.org; Thu, 11 Feb 2021 13:32:19 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1613068336; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=JmeOn7sJy+b4yQeBq6PnEDyH/8Fa23VxEt2DjW/7Y9o=; b=H2yVbffL4CyV71oyt2Xni8E3AP4reg8w3ZCP30OBFPM7ndOnHsmPluZOY4wruYoRCglY7I no8M0yyaK/xI7rI5ps50FpQjjGOFDrwRTY59HaKY1urIcag4fxofIpmg4gYdEEs5a6M8Y3 zJcUV8Uy2HB/mPN4DZZcvFa3CX4D1q4= Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-308-LsSKlbZyOfC4i-syljgeYw-1; Thu, 11 Feb 2021 13:32:15 -0500 X-MC-Unique: LsSKlbZyOfC4i-syljgeYw-1 Received: from smtp.corp.redhat.com (int-mx06.intmail.prod.int.phx2.redhat.com [10.5.11.16]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id E2CA8101E716 for ; Thu, 11 Feb 2021 18:31:35 +0000 (UTC) Received: from merkur.fritz.box (ovpn-113-82.ams2.redhat.com [10.36.113.82]) by smtp.corp.redhat.com (Postfix) with ESMTP id EB3B35C3E0; Thu, 11 Feb 2021 18:31:34 +0000 (UTC) From: Kevin Wolf To: qemu-devel@nongnu.org Subject: [PATCH v2 4/6] qapi: Apply aliases in qobject-input-visitor Date: Thu, 11 Feb 2021 19:31:16 +0100 Message-Id: <20210211183118.422036-5-kwolf@redhat.com> In-Reply-To: <20210211183118.422036-1-kwolf@redhat.com> References: <20210211183118.422036-1-kwolf@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.16 Authentication-Results: relay.mimecast.com; auth=pass smtp.auth=CUSA124A263 smtp.mailfrom=kwolf@redhat.com X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Received-SPF: pass client-ip=63.128.21.124; envelope-from=kwolf@redhat.com; helo=us-smtp-delivery-124.mimecast.com X-Spam_score_int: -33 X-Spam_score: -3.4 X-Spam_bar: --- X-Spam_report: (-3.4 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.569, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_LOW=-0.7, RCVD_IN_MSPIKE_H4=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: kwolf@redhat.com, jsnow@redhat.com, armbru@redhat.com Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: "Qemu-devel" When looking for an object in a struct in the external representation, check not only the currently visited struct, but also whether an alias in the current StackObject matches and try to fetch the value from the alias then. Providing two values for the same object through different aliases is an error. Signed-off-by: Kevin Wolf --- qapi/qobject-input-visitor.c | 214 +++++++++++++++++++++++++++++++++-- 1 file changed, 205 insertions(+), 9 deletions(-) diff --git a/qapi/qobject-input-visitor.c b/qapi/qobject-input-visitor.c index dd04ef0027..3ea5e5abd6 100644 --- a/qapi/qobject-input-visitor.c +++ b/qapi/qobject-input-visitor.c @@ -95,6 +95,8 @@ struct QObjectInputVisitor { QObject *root; bool keyval; /* Assume @root made with keyval_parse() */ + QDict *empty_qdict; /* Used for implicit objects */ + /* Stack of objects being visited (all entries will be either * QDict or QList). */ QSLIST_HEAD(, StackObject) stack; @@ -167,9 +169,178 @@ static const char *full_name(QObjectInputVisitor *qiv, const char *name) return full_name_so(qiv, name, false, tos); } +static bool find_object_member(QObjectInputVisitor *qiv, + StackObject **so, const char **name, + bool *implicit_object, Error **errp); + +/* + * Check whether the alias member defined by @a is present in the + * input and can be used to obtain the value for the member @name in + * the currently visited object. + */ +static bool alias_present(QObjectInputVisitor *qiv, + InputVisitorAlias *a, const char *name) +{ + StackObject *so = a->alias_so; + + /* + * The passed source @name is only relevant for wildcard aliases which + * don't have a separate name, otherwise we use the alias name. + */ + if (a->name) { + name = a->name; + } + + /* + * Check whether the alias member is present in the input + * (possibly recursively because aliases are transitive). + */ + if (!find_object_member(qiv, &so, &name, NULL, NULL)) { + return false; + } + + /* + * Every source can be used only once. If a value in the input would end up + * being used twice through aliases, we'll fail the second access. + */ + if (!g_hash_table_contains(so->h, name)) { + return false; + } + + return true; +} + +/* + * Check whether the member @name in the object visited by @so can be + * specified in the input by using the alias described by @a. + * + * If @name is only a prefix of the alias source, but doesn't match + * immediately, false is returned and @implicit_object is set to true + * if it is non-NULL. In all other cases, @implicit_object is left + * unchanged. + */ +static bool alias_source_matches(QObjectInputVisitor *qiv, + StackObject *so, InputVisitorAlias *a, + const char *name, bool *implicit_object) +{ + if (a->src[0] == NULL) { + assert(a->name == NULL); + return true; + } + + if (!strcmp(a->src[0], name)) { + if (a->name && a->src[1] == NULL) { + /* + * We're matching an exact member, the source for this alias is + * immediately in @so. + */ + return true; + } else if (implicit_object) { + /* + * We're only looking at a prefix of the source path for the alias. + * If the input contains no object of the requested name, we will + * implicitly create an empty one so that the alias can still be + * used. + * + * We want to create the implicit object only if the alias is + * actually used, but we can't tell here for wildcard aliases (only + * a later visitor call will determine this). This means that + * wildcard aliases must never have optional keys in their source + * path. + */ + if (!a->name || alias_present(qiv, a, a->name)) { + *implicit_object = true; + } + } + } + + return false; +} + +/* + * Find the place in the input where the value for the object member + * @name in @so is specified, considering applicable aliases. + * + * If a value could be found, true is returned and @so and @name are + * updated to identify the key name and StackObject where the value + * can be found in the input. (This is either unchanged or the + * alias_so/name of an alias.) The value of @implicit_object on + * return is undefined in this case. + * + * If no value could be found in the input, false is returned. This + * is not an error and @errp remains unchanged. If @implicit_object + * is non-NULL, it is set to true if the given name is a prefix of the + * source path of an alias for which a value may be present in the + * input. It is set to false otherwise. + * + * If an error occurs (e.g. two values are specified for the member + * through different names), false is returned and @errp is set. The + * value of @implicit_object on return is undefined in this case. + */ +static bool find_object_member(QObjectInputVisitor *qiv, + StackObject **so, const char **name, + bool *implicit_object, Error **errp) +{ + StackObject *cur_so = *so; + QDict *qdict = qobject_to(QDict, cur_so->obj); + const char *found = NULL; + bool found_is_wildcard = false; + InputVisitorAlias *a; + + if (implicit_object) { + *implicit_object = false; + } + + /* Directly present in the container */ + if (qdict_haskey(qdict, *name)) { + found = *name; + } + + /* + * Find aliases whose source path matches @name in this StackObject. We can + * then get the value with the key a->name from a->alias_so. + */ + QSLIST_FOREACH(a, &cur_so->aliases, next) { + if (a->name == NULL && found) { + /* + * Skip wildcard aliases if we already have a match. This is + * not a conflict that should result in an error. + */ + continue; + } + + if (!alias_source_matches(qiv, cur_so, a, *name, implicit_object)) { + continue; + } + + if (!alias_present(qiv, a, *name)) { + continue; + } + + if (found && !found_is_wildcard) { + error_setg(errp, "Value for parameter %s was already given " + "through an alias", + full_name_so(qiv, *name, false, *so)); + return false; + } else { + found = a->name ?: *name; + *so = a->alias_so; + found_is_wildcard = !a->name; + } + } + + /* Chained aliases: *so/found might be the source of another alias */ + if (found && (*so != cur_so || found != *name)) { + find_object_member(qiv, so, &found, NULL, errp); + } + + *name = found; + return found; +} + static QObject *qobject_input_try_get_object(QObjectInputVisitor *qiv, const char *name, - bool consume) + bool consume, Error **errp) { StackObject *tos; QObject *qobj; @@ -187,10 +358,30 @@ static QObject *qobject_input_try_get_object(QObjectInputVisitor *qiv, assert(qobj); if (qobject_type(qobj) == QTYPE_QDICT) { - assert(name); - ret = qdict_get(qobject_to(QDict, qobj), name); - if (tos->h && consume && ret) { - bool removed = g_hash_table_remove(tos->h, name); + StackObject *so = tos; + const char *key = name; + bool implicit_object; + + assert(key); + if (!find_object_member(qiv, &so, &key, &implicit_object, errp)) { + if (implicit_object) { + /* + * The member is not present in the input, but + * something inside of it might still be given through + * an alias. Pretend there was an empty object in the + * input. + */ + if (!qiv->empty_qdict) { + qiv->empty_qdict = qdict_new(); + } + return QOBJECT(qiv->empty_qdict); + } else { + return NULL; + } + } + ret = qdict_get(qobject_to(QDict, so->obj), key); + if (so->h && consume && ret) { + bool removed = g_hash_table_remove(so->h, key); assert(removed); } } else { @@ -216,9 +407,10 @@ static QObject *qobject_input_get_object(QObjectInputVisitor *qiv, const char *name, bool consume, Error **errp) { - QObject *obj = qobject_input_try_get_object(qiv, name, consume); + ERRP_GUARD(); + QObject *obj = qobject_input_try_get_object(qiv, name, consume, errp); - if (!obj) { + if (!obj && !*errp) { error_setg(errp, QERR_MISSING_PARAMETER, full_name(qiv, name)); } return obj; @@ -799,13 +991,16 @@ static bool qobject_input_type_size_keyval(Visitor *v, const char *name, static void qobject_input_optional(Visitor *v, const char *name, bool *present) { QObjectInputVisitor *qiv = to_qiv(v); - QObject *qobj = qobject_input_try_get_object(qiv, name, false); + Error *local_err = NULL; + QObject *qobj = qobject_input_try_get_object(qiv, name, false, &local_err); - if (!qobj) { + /* If there was an error, let the caller try and run into the error */ + if (!qobj && !local_err) { *present = false; return; } + error_free(local_err); *present = true; } @@ -820,6 +1015,7 @@ static void qobject_input_free(Visitor *v) qobject_input_stack_object_free(tos); } + qobject_unref(qiv->empty_qdict); qobject_unref(qiv->root); if (qiv->errname) { g_string_free(qiv->errname, TRUE); From patchwork Thu Feb 11 18:31:17 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kevin Wolf X-Patchwork-Id: 12083847 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-13.5 required=3.0 tests=BAYES_00,DKIM_INVALID, DKIM_SIGNED,HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id C69E7C433E6 for ; Thu, 11 Feb 2021 18:34:22 +0000 (UTC) Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 3F81964E15 for ; Thu, 11 Feb 2021 18:34:22 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 3F81964E15 Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=redhat.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Received: from localhost ([::1]:51106 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1lAGnF-0005fd-9m for qemu-devel@archiver.kernel.org; Thu, 11 Feb 2021 13:34:21 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:41582) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1lAGlW-0003ui-6R for qemu-devel@nongnu.org; Thu, 11 Feb 2021 13:32:34 -0500 Received: from us-smtp-delivery-124.mimecast.com ([216.205.24.124]:44185) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.90_1) (envelope-from ) id 1lAGlP-0002bQ-45 for qemu-devel@nongnu.org; Thu, 11 Feb 2021 13:32:33 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1613068346; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=jhYvEMH/SAJ4MJUzbOU4SzXxmFGGzBYpl/nN8QMqUm8=; b=cRqxU1VaG1wYUmgQrY2+oaQHMX0bshE3SwBIKGJ567lnIJhgbDGrqzjEJ0gUBRTRHykaTQ 2XS19V8orPiVVk+ZQ/1E+82PG9lUlz2aPVOxsChKWJxTWZy8Oq3XGtq7NlJ331xEJ6ZvOG TGGVEvvoivZ0aijQIIrnRSN/ZgRHvMk= Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-155-WznuQlasMUm49-rkNmz_6g-1; Thu, 11 Feb 2021 13:32:21 -0500 X-MC-Unique: WznuQlasMUm49-rkNmz_6g-1 Received: from smtp.corp.redhat.com (int-mx06.intmail.prod.int.phx2.redhat.com [10.5.11.16]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 694C27BA3 for ; Thu, 11 Feb 2021 18:31:37 +0000 (UTC) Received: from merkur.fritz.box (ovpn-113-82.ams2.redhat.com [10.36.113.82]) by smtp.corp.redhat.com (Postfix) with ESMTP id 4C7935C8AA; Thu, 11 Feb 2021 18:31:36 +0000 (UTC) From: Kevin Wolf To: qemu-devel@nongnu.org Subject: [PATCH v2 5/6] qapi: Add support for aliases Date: Thu, 11 Feb 2021 19:31:17 +0100 Message-Id: <20210211183118.422036-6-kwolf@redhat.com> In-Reply-To: <20210211183118.422036-1-kwolf@redhat.com> References: <20210211183118.422036-1-kwolf@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.16 Authentication-Results: relay.mimecast.com; auth=pass smtp.auth=CUSA124A263 smtp.mailfrom=kwolf@redhat.com X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Received-SPF: pass client-ip=216.205.24.124; envelope-from=kwolf@redhat.com; helo=us-smtp-delivery-124.mimecast.com X-Spam_score_int: -33 X-Spam_score: -3.4 X-Spam_bar: --- X-Spam_report: (-3.4 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.569, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_LOW=-0.7, RCVD_IN_MSPIKE_H3=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: kwolf@redhat.com, jsnow@redhat.com, armbru@redhat.com Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: "Qemu-devel" Introduce alias definitions for object types (structs and unions). This allows using the same QAPI type and visitor for many syntax variations that exist in the external representation, like between QMP and the command line. It also provides a new tool for evolving the schema while maintaining backwards compatibility during a deprecation period. Signed-off-by: Kevin Wolf --- docs/devel/qapi-code-gen.txt | 105 ++++++++++++++++++++++++- docs/sphinx/qapidoc.py | 2 +- scripts/qapi/expr.py | 36 ++++++++- scripts/qapi/schema.py | 30 +++++-- scripts/qapi/types.py | 4 +- scripts/qapi/visit.py | 34 +++++++- tests/qapi-schema/test-qapi.py | 7 +- tests/qapi-schema/double-type.err | 2 +- tests/qapi-schema/unknown-expr-key.err | 2 +- 9 files changed, 203 insertions(+), 19 deletions(-) diff --git a/docs/devel/qapi-code-gen.txt b/docs/devel/qapi-code-gen.txt index 6906a06ad2..247c4b8ef4 100644 --- a/docs/devel/qapi-code-gen.txt +++ b/docs/devel/qapi-code-gen.txt @@ -231,7 +231,8 @@ Syntax: 'data': MEMBERS, '*base': STRING, '*if': COND, - '*features': FEATURES } + '*features': FEATURES, + '*aliases': ALIASES } MEMBERS = { MEMBER, ... } MEMBER = STRING : TYPE-REF | STRING : { 'type': TYPE-REF, @@ -279,6 +280,9 @@ the schema" below for more on this. The optional 'features' member specifies features. See "Features" below for more on this. +The optional 'aliases' member specifies aliases. See "Aliases" below +for more on this. + === Union types === @@ -286,13 +290,15 @@ Syntax: UNION = { 'union': STRING, 'data': BRANCHES, '*if': COND, - '*features': FEATURES } + '*features': FEATURES, + '*aliases': ALIASES } | { 'union': STRING, 'data': BRANCHES, 'base': ( MEMBERS | STRING ), 'discriminator': STRING, '*if': COND, - '*features': FEATURES } + '*features': FEATURES, + '*aliases': ALIASES } BRANCHES = { BRANCH, ... } BRANCH = STRING : TYPE-REF | STRING : { 'type': TYPE-REF, '*if': COND } @@ -402,6 +408,9 @@ the schema" below for more on this. The optional 'features' member specifies features. See "Features" below for more on this. +The optional 'aliases' member specifies aliases. See "Aliases" below +for more on this. + === Alternate types === @@ -837,6 +846,96 @@ shows a conditional entity only when the condition is satisfied in this particular build. +=== Aliases === + +Object types, including structs and unions, can contain alias +definitions. + +Aliases define alternative member names that may be used in the +external representation to provide a value for a member in the same +object or in a nested object. + +Syntax: + ALIASES = [ ALIAS, ... ] + ALIAS = { '*name': STRING, + 'source': [ STRING, ... ] } + +If 'name' is present, then the single member referred to by 'source' +is made accessible with the name given in 'name' in the type where the +alias definition is specified. + +If 'name' is not present, then all members in the object referred to +by 'source' are made accessible in the type where the alias definition +is specified with the same name as they have in 'source'. + +'source' is a non-empty list of member names representing the path to +an object member. The first name is resolved in the same object. Each +subsequent member is resolved in the object named by the preceding +member. + +Example: Alternative name for a member in the same object (the member +"path" may be given through its alias "filename" in the external +representation): + +{ 'struct': 'File', + 'data': { 'path': 'str' }, + 'aliases': [ { 'name': 'filename', 'source': ['path'] } ] } + +Example: Alias for a member in a nested object: + +{ 'struct': 'A', + 'data': { 'zahl': 'int' } } +{ 'struct': 'B', + 'data': { 'drei': 'A' } } +{ 'struct': 'C', + 'data': { 'zwei': 'B' } } +{ 'struct': 'D', + 'data': { 'eins': 'C' }, + 'aliases': [ { 'name': 'number', + 'source': ['eins', 'zwei', 'drei', 'zahl' ] }, + { 'name': 'the_B', + 'source': ['eins','zwei'] } ] } + +With this definition, each of the following inputs mean the same: + +* { 'eins': { 'zwei': { 'drei': { 'zahl': 42 } } } } + +* { 'the_B': { 'drei': { 'zahl': 42 } } } + +* { 'number': 42 } + +Example: Flattening a union with a wildcard alias that maps all +members of 'data' to the top level: + +{ 'union': 'SocketAddress', + 'data': { + 'inet': 'InetSocketAddress', + 'unix': 'UnixSocketAddress' }, + 'aliases': [ { 'source': ['data'] } ] } + +Aliases are transitive: 'source' may refer to another alias name. In +this case, the alias is effectively an altenative name for the source +of the other alias. + +Example: Giving "the_answer" on the top level provides a value for +"zahl" in the nested object: + +{ 'struct': 'A', + 'data': { 'zahl': 'int' }, + 'aliases': [ { 'name': 'number', 'source': ['zahl'] } ] } +{ 'struct': 'B', + 'data': { 'nested': 'A' }, + 'aliases': [ { 'name': 'the_answer', + 'source': ['nested', 'number'] } ] } + +In order to accommodate unions where variants differ in structure, it +is allowed to use a path that doesn't necessarily match an existing +member in every variant or even at all; in this case, the alias +remains unused. Note that the QAPI generator does not check whether +there is at least one branch for which an alias could match. If a +source member is misspelt, the alias just won't work. + + === Documentation comments === A multi-line comment that starts and ends with a '##' line is a diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py index e03abcbb95..6c94c01148 100644 --- a/docs/sphinx/qapidoc.py +++ b/docs/sphinx/qapidoc.py @@ -310,7 +310,7 @@ class QAPISchemaGenRSTVisitor(QAPISchemaVisitor): + self._nodes_for_if_section(ifcond)) def visit_object_type(self, name, info, ifcond, features, - base, members, variants): + base, members, variants, aliases): doc = self._cur_doc if base and base.is_implicit(): base = None diff --git a/scripts/qapi/expr.py b/scripts/qapi/expr.py index 2fcaaa2497..743e23ec85 100644 --- a/scripts/qapi/expr.py +++ b/scripts/qapi/expr.py @@ -198,6 +198,34 @@ def check_features(features, info): check_if(f, info, source) +def check_aliases(aliases, info): + if aliases is None: + return + if not isinstance(aliases, list): + raise QAPISemError(info, "'aliases' must be an array") + for a in aliases: + if not isinstance(a, dict): + raise QAPISemError(info, "'aliases' members must be objects") + check_keys(a, info, "'aliases' member", ['source'], ['name']) + + if 'name' in a: + source = "alias member 'name'" + check_name_is_str(a['name'], info, source) + check_name_str(a['name'], info, source) + + if not isinstance(a['source'], list): + raise QAPISemError(info, + "alias member 'source' must be an array") + if not a['source']: + raise QAPISemError(info, + "alias member 'source' must not be empty") + + source = "member of alias member 'source'" + for s in a['source']: + check_name_is_str(s, info, source) + check_name_str(s, info, source) + + def check_enum(expr, info): name = expr['enum'] members = expr['data'] @@ -228,6 +256,7 @@ def check_struct(expr, info): check_type(members, info, "'data'", allow_dict=name) check_type(expr.get('base'), info, "'base'") + check_aliases(expr.get('aliases'), info) def check_union(expr, info): @@ -245,6 +274,8 @@ def check_union(expr, info): raise QAPISemError(info, "'discriminator' requires 'base'") check_name_is_str(discriminator, info, "'discriminator'") + check_aliases(expr.get('aliases'), info) + for (key, value) in members.items(): source = "'data' member '%s'" % key check_name_str(key, info, source) @@ -331,7 +362,7 @@ def check_exprs(exprs): elif meta == 'union': check_keys(expr, info, meta, ['union', 'data'], - ['base', 'discriminator', 'if', 'features']) + ['base', 'discriminator', 'if', 'features', 'aliases']) normalize_members(expr.get('base')) normalize_members(expr['data']) check_union(expr, info) @@ -342,7 +373,8 @@ def check_exprs(exprs): check_alternate(expr, info) elif meta == 'struct': check_keys(expr, info, meta, - ['struct', 'data'], ['base', 'if', 'features']) + ['struct', 'data'], + ['base', 'if', 'features', 'aliases']) normalize_members(expr['data']) check_struct(expr, info) elif meta == 'command': diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py index 353e8020a2..14a2b0175b 100644 --- a/scripts/qapi/schema.py +++ b/scripts/qapi/schema.py @@ -118,7 +118,7 @@ class QAPISchemaVisitor: pass def visit_object_type(self, name, info, ifcond, features, - base, members, variants): + base, members, variants, aliases): pass def visit_object_type_flat(self, name, info, ifcond, features, @@ -362,9 +362,19 @@ class QAPISchemaArrayType(QAPISchemaType): return "%s type ['%s']" % (self.meta, self._element_type_name) +class QAPISchemaAlias: + def __init__(self, name, source): + assert name is None or isinstance(name, str) + assert source + for member in source: + assert isinstance(member, str) + self.name = name + self.source = source + + class QAPISchemaObjectType(QAPISchemaType): def __init__(self, name, info, doc, ifcond, features, - base, local_members, variants): + base, local_members, variants, aliases=None): # struct has local_members, optional base, and no variants # flat union has base, variants, and no local_members # simple union has local_members, variants, and no base @@ -382,6 +392,7 @@ class QAPISchemaObjectType(QAPISchemaType): self.local_members = local_members self.variants = variants self.members = None + self.aliases = aliases or [] def check(self, schema): # This calls another type T's .check() exactly when the C @@ -474,7 +485,7 @@ class QAPISchemaObjectType(QAPISchemaType): super().visit(visitor) visitor.visit_object_type( self.name, self.info, self.ifcond, self.features, - self.base, self.local_members, self.variants) + self.base, self.local_members, self.variants, self.aliases) visitor.visit_object_type_flat( self.name, self.info, self.ifcond, self.features, self.members, self.variants) @@ -964,6 +975,12 @@ class QAPISchema: return [QAPISchemaFeature(f['name'], info, f.get('if')) for f in features] + def _make_aliases(self, aliases): + if aliases is None: + return [] + return [QAPISchemaAlias(a.get('name'), a['source']) + for a in aliases] + def _make_enum_members(self, values, info): return [QAPISchemaEnumMember(v['name'], info, v.get('if')) for v in values] @@ -1038,11 +1055,12 @@ class QAPISchema: base = expr.get('base') data = expr['data'] ifcond = expr.get('if') + aliases = self._make_aliases(expr.get('aliases')) features = self._make_features(expr.get('features'), info) self._def_entity(QAPISchemaObjectType( name, info, doc, ifcond, features, base, self._make_members(data, info), - None)) + None, aliases)) def _make_variant(self, case, typ, ifcond, info): return QAPISchemaVariant(case, info, typ, ifcond) @@ -1061,6 +1079,7 @@ class QAPISchema: data = expr['data'] base = expr.get('base') ifcond = expr.get('if') + aliases = self._make_aliases(expr.get('aliases')) features = self._make_features(expr.get('features'), info) tag_name = expr.get('discriminator') tag_member = None @@ -1085,7 +1104,8 @@ class QAPISchema: QAPISchemaObjectType(name, info, doc, ifcond, features, base, members, QAPISchemaVariants( - tag_name, info, tag_member, variants))) + tag_name, info, tag_member, variants), + aliases)) def _def_alternate_type(self, expr, info, doc): name = expr['alternate'] diff --git a/scripts/qapi/types.py b/scripts/qapi/types.py index 2bdd626847..c8306479f5 100644 --- a/scripts/qapi/types.py +++ b/scripts/qapi/types.py @@ -25,6 +25,7 @@ from .common import ( from .gen import QAPISchemaModularCVisitor, ifcontext from .schema import ( QAPISchema, + QAPISchemaAlias, QAPISchemaEnumMember, QAPISchemaFeature, QAPISchemaObjectType, @@ -332,7 +333,8 @@ class QAPISchemaGenTypeVisitor(QAPISchemaModularCVisitor): features: List[QAPISchemaFeature], base: Optional[QAPISchemaObjectType], members: List[QAPISchemaObjectTypeMember], - variants: Optional[QAPISchemaVariants]) -> None: + variants: Optional[QAPISchemaVariants], + aliases: List[QAPISchemaAlias]) -> None: # Nothing to do for the special empty builtin if name == 'q_empty': return diff --git a/scripts/qapi/visit.py b/scripts/qapi/visit.py index 22e62df901..e370485f6e 100644 --- a/scripts/qapi/visit.py +++ b/scripts/qapi/visit.py @@ -26,6 +26,7 @@ from .common import ( from .gen import QAPISchemaModularCVisitor, ifcontext from .schema import ( QAPISchema, + QAPISchemaAlias, QAPISchemaEnumMember, QAPISchemaEnumType, QAPISchemaFeature, @@ -60,7 +61,8 @@ bool visit_type_%(c_name)s_members(Visitor *v, %(c_name)s *obj, Error **errp); def gen_visit_object_members(name: str, base: Optional[QAPISchemaObjectType], members: List[QAPISchemaObjectTypeMember], - variants: Optional[QAPISchemaVariants]) -> str: + variants: Optional[QAPISchemaVariants], + aliases: List[QAPISchemaAlias]) -> str: ret = mcgen(''' bool visit_type_%(c_name)s_members(Visitor *v, %(c_name)s *obj, Error **errp) @@ -68,6 +70,24 @@ bool visit_type_%(c_name)s_members(Visitor *v, %(c_name)s *obj, Error **errp) ''', c_name=c_name(name)) + if aliases: + ret += mcgen(''' + visit_start_alias_scope(v); +''') + + for a in aliases: + if a.name: + name = '"%s"' % a.name + else: + name = "NULL" + + source = ", ".join('"%s"' % x for x in a.source) + + ret += mcgen(''' + visit_define_alias(v, %(name)s, (const char * []) { %(source)s, NULL }); +''', + name=name, source=source) + if base: ret += mcgen(''' if (!visit_type_%(c_type)s_members(v, (%(c_type)s *)obj, errp)) { @@ -133,6 +153,11 @@ bool visit_type_%(c_name)s_members(Visitor *v, %(c_name)s *obj, Error **errp) } ''') + if aliases: + ret += mcgen(''' + visit_end_alias_scope(v); +''') + ret += mcgen(''' return true; } @@ -361,14 +386,15 @@ class QAPISchemaGenVisitVisitor(QAPISchemaModularCVisitor): features: List[QAPISchemaFeature], base: Optional[QAPISchemaObjectType], members: List[QAPISchemaObjectTypeMember], - variants: Optional[QAPISchemaVariants]) -> None: + variants: Optional[QAPISchemaVariants], + aliases: List[QAPISchemaAlias]) -> None: # Nothing to do for the special empty builtin if name == 'q_empty': return with ifcontext(ifcond, self._genh, self._genc): self._genh.add(gen_visit_members_decl(name)) - self._genc.add(gen_visit_object_members(name, base, - members, variants)) + self._genc.add(gen_visit_object_members( + name, base, members, variants, aliases)) # TODO Worth changing the visitor signature, so we could # directly use rather than repeat type.is_implicit()? if not name.startswith('q_'): diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py index e8db9d09d9..1679d1b5da 100755 --- a/tests/qapi-schema/test-qapi.py +++ b/tests/qapi-schema/test-qapi.py @@ -47,7 +47,7 @@ class QAPISchemaTestVisitor(QAPISchemaVisitor): self._print_if(ifcond) def visit_object_type(self, name, info, ifcond, features, - base, members, variants): + base, members, variants, aliases): print('object %s' % name) if base: print(' base %s' % base.name) @@ -56,6 +56,11 @@ class QAPISchemaTestVisitor(QAPISchemaVisitor): % (m.name, m.type.name, m.optional)) self._print_if(m.ifcond, 8) self._print_features(m.features, indent=8) + for a in aliases: + if a.name: + print(' alias %s -> %s' % (a.name, '.'.join(a.source))) + else: + print(' alias * -> %s.*' % '.'.join(a.source)) self._print_variants(variants) self._print_if(ifcond) self._print_features(features) diff --git a/tests/qapi-schema/double-type.err b/tests/qapi-schema/double-type.err index 71fc4dbb52..5d25d7623c 100644 --- a/tests/qapi-schema/double-type.err +++ b/tests/qapi-schema/double-type.err @@ -1,3 +1,3 @@ double-type.json: In struct 'bar': double-type.json:2: struct has unknown key 'command' -Valid keys are 'base', 'data', 'features', 'if', 'struct'. +Valid keys are 'aliases', 'base', 'data', 'features', 'if', 'struct'. diff --git a/tests/qapi-schema/unknown-expr-key.err b/tests/qapi-schema/unknown-expr-key.err index c5f395bf79..7429d1ff03 100644 --- a/tests/qapi-schema/unknown-expr-key.err +++ b/tests/qapi-schema/unknown-expr-key.err @@ -1,3 +1,3 @@ unknown-expr-key.json: In struct 'bar': unknown-expr-key.json:2: struct has unknown keys 'bogus', 'phony' -Valid keys are 'base', 'data', 'features', 'if', 'struct'. +Valid keys are 'aliases', 'base', 'data', 'features', 'if', 'struct'. From patchwork Thu Feb 11 18:31:18 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kevin Wolf X-Patchwork-Id: 12083845 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-13.6 required=3.0 tests=BAYES_00,DKIM_INVALID, DKIM_SIGNED,HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 10CBCC433E0 for ; Thu, 11 Feb 2021 18:34:22 +0000 (UTC) Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 9381F64E15 for ; Thu, 11 Feb 2021 18:34:21 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 9381F64E15 Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=redhat.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Received: from localhost ([::1]:51018 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1lAGnE-0005dS-J2 for qemu-devel@archiver.kernel.org; Thu, 11 Feb 2021 13:34:20 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:41584) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1lAGlW-0003v0-9y for qemu-devel@nongnu.org; Thu, 11 Feb 2021 13:32:34 -0500 Received: from us-smtp-delivery-124.mimecast.com ([63.128.21.124]:58419) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.90_1) (envelope-from ) id 1lAGlM-0002aH-7c for qemu-devel@nongnu.org; Thu, 11 Feb 2021 13:32:34 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1613068341; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=5IrKhFJawEpCqb42VfkTcjVDpy3Wp5KY19Kfv7Eetzw=; b=QDuc73pfupaUKRULFg8lpoaSwl5orT+oEGfh0K2i/YFDVa34tR7v0NrKnmDfnA2B8aalm9 1EGbJRYsHYRoMfSc7DoTVBwNahv6ZSQIKKzjotchFehVb8HYqSGXJK2EAnzi+PDC8HZSH2 AtOYKcrhBfNAguteR1ceHFVaRFvxGQs= Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-168-zbC-bNnuM-SRM0AyWnGYHg-1; Thu, 11 Feb 2021 13:32:18 -0500 X-MC-Unique: zbC-bNnuM-SRM0AyWnGYHg-1 Received: from smtp.corp.redhat.com (int-mx06.intmail.prod.int.phx2.redhat.com [10.5.11.16]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id A9EA1801990 for ; Thu, 11 Feb 2021 18:31:38 +0000 (UTC) Received: from merkur.fritz.box (ovpn-113-82.ams2.redhat.com [10.36.113.82]) by smtp.corp.redhat.com (Postfix) with ESMTP id B2CE15C3E0; Thu, 11 Feb 2021 18:31:37 +0000 (UTC) From: Kevin Wolf To: qemu-devel@nongnu.org Subject: [PATCH v2 6/6] tests/qapi-schema: Test cases for aliases Date: Thu, 11 Feb 2021 19:31:18 +0100 Message-Id: <20210211183118.422036-7-kwolf@redhat.com> In-Reply-To: <20210211183118.422036-1-kwolf@redhat.com> References: <20210211183118.422036-1-kwolf@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.16 Authentication-Results: relay.mimecast.com; auth=pass smtp.auth=CUSA124A263 smtp.mailfrom=kwolf@redhat.com X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Received-SPF: pass client-ip=63.128.21.124; envelope-from=kwolf@redhat.com; helo=us-smtp-delivery-124.mimecast.com X-Spam_score_int: -33 X-Spam_score: -3.4 X-Spam_bar: --- X-Spam_report: (-3.4 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.569, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_LOW=-0.7, RCVD_IN_MSPIKE_H4=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: kwolf@redhat.com, jsnow@redhat.com, armbru@redhat.com Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: "Qemu-devel" Signed-off-by: Kevin Wolf --- tests/qapi-schema/alias-bad-type.err | 2 ++ tests/qapi-schema/alias-bad-type.json | 3 ++ tests/qapi-schema/alias-bad-type.out | 0 tests/qapi-schema/alias-missing-source.err | 2 ++ tests/qapi-schema/alias-missing-source.json | 3 ++ tests/qapi-schema/alias-missing-source.out | 0 tests/qapi-schema/alias-name-bad-type.err | 2 ++ tests/qapi-schema/alias-name-bad-type.json | 3 ++ tests/qapi-schema/alias-name-bad-type.out | 0 tests/qapi-schema/alias-source-bad-type.err | 2 ++ tests/qapi-schema/alias-source-bad-type.json | 3 ++ tests/qapi-schema/alias-source-bad-type.out | 0 .../alias-source-elem-bad-type.err | 2 ++ .../alias-source-elem-bad-type.json | 3 ++ .../alias-source-elem-bad-type.out | 0 tests/qapi-schema/alias-source-empty.err | 2 ++ tests/qapi-schema/alias-source-empty.json | 3 ++ tests/qapi-schema/alias-source-empty.out | 0 tests/qapi-schema/alias-unknown-key.err | 3 ++ tests/qapi-schema/alias-unknown-key.json | 3 ++ tests/qapi-schema/alias-unknown-key.out | 0 tests/qapi-schema/aliases-bad-type.err | 2 ++ tests/qapi-schema/aliases-bad-type.json | 3 ++ tests/qapi-schema/aliases-bad-type.out | 0 tests/qapi-schema/meson.build | 8 +++++ tests/qapi-schema/qapi-schema-test.json | 24 +++++++++++++++ tests/qapi-schema/qapi-schema-test.out | 29 +++++++++++++++++++ 27 files changed, 102 insertions(+) create mode 100644 tests/qapi-schema/alias-bad-type.err create mode 100644 tests/qapi-schema/alias-bad-type.json create mode 100644 tests/qapi-schema/alias-bad-type.out create mode 100644 tests/qapi-schema/alias-missing-source.err create mode 100644 tests/qapi-schema/alias-missing-source.json create mode 100644 tests/qapi-schema/alias-missing-source.out create mode 100644 tests/qapi-schema/alias-name-bad-type.err create mode 100644 tests/qapi-schema/alias-name-bad-type.json create mode 100644 tests/qapi-schema/alias-name-bad-type.out create mode 100644 tests/qapi-schema/alias-source-bad-type.err create mode 100644 tests/qapi-schema/alias-source-bad-type.json create mode 100644 tests/qapi-schema/alias-source-bad-type.out create mode 100644 tests/qapi-schema/alias-source-elem-bad-type.err create mode 100644 tests/qapi-schema/alias-source-elem-bad-type.json create mode 100644 tests/qapi-schema/alias-source-elem-bad-type.out create mode 100644 tests/qapi-schema/alias-source-empty.err create mode 100644 tests/qapi-schema/alias-source-empty.json create mode 100644 tests/qapi-schema/alias-source-empty.out create mode 100644 tests/qapi-schema/alias-unknown-key.err create mode 100644 tests/qapi-schema/alias-unknown-key.json create mode 100644 tests/qapi-schema/alias-unknown-key.out create mode 100644 tests/qapi-schema/aliases-bad-type.err create mode 100644 tests/qapi-schema/aliases-bad-type.json create mode 100644 tests/qapi-schema/aliases-bad-type.out diff --git a/tests/qapi-schema/alias-bad-type.err b/tests/qapi-schema/alias-bad-type.err new file mode 100644 index 0000000000..820e18ed9c --- /dev/null +++ b/tests/qapi-schema/alias-bad-type.err @@ -0,0 +1,2 @@ +alias-bad-type.json: In struct 'AliasStruct0': +alias-bad-type.json:1: 'aliases' members must be objects diff --git a/tests/qapi-schema/alias-bad-type.json b/tests/qapi-schema/alias-bad-type.json new file mode 100644 index 0000000000..0aa5d206fe --- /dev/null +++ b/tests/qapi-schema/alias-bad-type.json @@ -0,0 +1,3 @@ +{ 'struct': 'AliasStruct0', + 'data': { 'foo': 'int' }, + 'aliases': [ 'must be an object' ] } diff --git a/tests/qapi-schema/alias-bad-type.out b/tests/qapi-schema/alias-bad-type.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/alias-missing-source.err b/tests/qapi-schema/alias-missing-source.err new file mode 100644 index 0000000000..8b7d601fbf --- /dev/null +++ b/tests/qapi-schema/alias-missing-source.err @@ -0,0 +1,2 @@ +alias-missing-source.json: In struct 'AliasStruct0': +alias-missing-source.json:1: 'aliases' member misses key 'source' diff --git a/tests/qapi-schema/alias-missing-source.json b/tests/qapi-schema/alias-missing-source.json new file mode 100644 index 0000000000..b6c91a9488 --- /dev/null +++ b/tests/qapi-schema/alias-missing-source.json @@ -0,0 +1,3 @@ +{ 'struct': 'AliasStruct0', + 'data': { 'foo': 'int' }, + 'aliases': [ { 'name': 'bar' } ] } diff --git a/tests/qapi-schema/alias-missing-source.out b/tests/qapi-schema/alias-missing-source.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/alias-name-bad-type.err b/tests/qapi-schema/alias-name-bad-type.err new file mode 100644 index 0000000000..489f45ff9b --- /dev/null +++ b/tests/qapi-schema/alias-name-bad-type.err @@ -0,0 +1,2 @@ +alias-name-bad-type.json: In struct 'AliasStruct0': +alias-name-bad-type.json:1: alias member 'name' requires a string name diff --git a/tests/qapi-schema/alias-name-bad-type.json b/tests/qapi-schema/alias-name-bad-type.json new file mode 100644 index 0000000000..17442d5939 --- /dev/null +++ b/tests/qapi-schema/alias-name-bad-type.json @@ -0,0 +1,3 @@ +{ 'struct': 'AliasStruct0', + 'data': { 'foo': 'int' }, + 'aliases': [ { 'name': ['bar'], 'source': ['foo'] } ] } diff --git a/tests/qapi-schema/alias-name-bad-type.out b/tests/qapi-schema/alias-name-bad-type.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/alias-source-bad-type.err b/tests/qapi-schema/alias-source-bad-type.err new file mode 100644 index 0000000000..b1779cbb8e --- /dev/null +++ b/tests/qapi-schema/alias-source-bad-type.err @@ -0,0 +1,2 @@ +alias-source-bad-type.json: In struct 'AliasStruct0': +alias-source-bad-type.json:1: alias member 'source' must be an array diff --git a/tests/qapi-schema/alias-source-bad-type.json b/tests/qapi-schema/alias-source-bad-type.json new file mode 100644 index 0000000000..d6a7430ee3 --- /dev/null +++ b/tests/qapi-schema/alias-source-bad-type.json @@ -0,0 +1,3 @@ +{ 'struct': 'AliasStruct0', + 'data': { 'foo': 'int' }, + 'aliases': [ { 'name': 'bar', 'source': 'foo' } ] } diff --git a/tests/qapi-schema/alias-source-bad-type.out b/tests/qapi-schema/alias-source-bad-type.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/alias-source-elem-bad-type.err b/tests/qapi-schema/alias-source-elem-bad-type.err new file mode 100644 index 0000000000..f73fbece77 --- /dev/null +++ b/tests/qapi-schema/alias-source-elem-bad-type.err @@ -0,0 +1,2 @@ +alias-source-elem-bad-type.json: In struct 'AliasStruct0': +alias-source-elem-bad-type.json:1: member of alias member 'source' requires a string name diff --git a/tests/qapi-schema/alias-source-elem-bad-type.json b/tests/qapi-schema/alias-source-elem-bad-type.json new file mode 100644 index 0000000000..1d08f56492 --- /dev/null +++ b/tests/qapi-schema/alias-source-elem-bad-type.json @@ -0,0 +1,3 @@ +{ 'struct': 'AliasStruct0', + 'data': { 'foo': 'int' }, + 'aliases': [ { 'name': 'bar', 'source': ['foo', true] } ] } diff --git a/tests/qapi-schema/alias-source-elem-bad-type.out b/tests/qapi-schema/alias-source-elem-bad-type.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/alias-source-empty.err b/tests/qapi-schema/alias-source-empty.err new file mode 100644 index 0000000000..2848e762cb --- /dev/null +++ b/tests/qapi-schema/alias-source-empty.err @@ -0,0 +1,2 @@ +alias-source-empty.json: In struct 'AliasStruct0': +alias-source-empty.json:1: alias member 'source' must not be empty diff --git a/tests/qapi-schema/alias-source-empty.json b/tests/qapi-schema/alias-source-empty.json new file mode 100644 index 0000000000..74b529de4a --- /dev/null +++ b/tests/qapi-schema/alias-source-empty.json @@ -0,0 +1,3 @@ +{ 'struct': 'AliasStruct0', + 'data': { 'foo': 'int' }, + 'aliases': [ { 'name': 'bar', 'source': [] } ] } diff --git a/tests/qapi-schema/alias-source-empty.out b/tests/qapi-schema/alias-source-empty.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/alias-unknown-key.err b/tests/qapi-schema/alias-unknown-key.err new file mode 100644 index 0000000000..c7b8cb9498 --- /dev/null +++ b/tests/qapi-schema/alias-unknown-key.err @@ -0,0 +1,3 @@ +alias-unknown-key.json: In struct 'AliasStruct0': +alias-unknown-key.json:1: 'aliases' member has unknown key 'known' +Valid keys are 'name', 'source'. diff --git a/tests/qapi-schema/alias-unknown-key.json b/tests/qapi-schema/alias-unknown-key.json new file mode 100644 index 0000000000..cdb8fc3d07 --- /dev/null +++ b/tests/qapi-schema/alias-unknown-key.json @@ -0,0 +1,3 @@ +{ 'struct': 'AliasStruct0', + 'data': { 'foo': 'int' }, + 'aliases': [ { 'name': 'bar', 'source': ['foo'], 'known': false } ] } diff --git a/tests/qapi-schema/alias-unknown-key.out b/tests/qapi-schema/alias-unknown-key.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/aliases-bad-type.err b/tests/qapi-schema/aliases-bad-type.err new file mode 100644 index 0000000000..7ffe789ec0 --- /dev/null +++ b/tests/qapi-schema/aliases-bad-type.err @@ -0,0 +1,2 @@ +aliases-bad-type.json: In struct 'AliasStruct0': +aliases-bad-type.json:1: 'aliases' must be an array diff --git a/tests/qapi-schema/aliases-bad-type.json b/tests/qapi-schema/aliases-bad-type.json new file mode 100644 index 0000000000..4bbf6d6b20 --- /dev/null +++ b/tests/qapi-schema/aliases-bad-type.json @@ -0,0 +1,3 @@ +{ 'struct': 'AliasStruct0', + 'data': { 'foo': 'int' }, + 'aliases': 'this must be an array' } diff --git a/tests/qapi-schema/aliases-bad-type.out b/tests/qapi-schema/aliases-bad-type.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/meson.build b/tests/qapi-schema/meson.build index 304ef939bd..710cd60b61 100644 --- a/tests/qapi-schema/meson.build +++ b/tests/qapi-schema/meson.build @@ -3,6 +3,14 @@ test_env.set('PYTHONPATH', meson.source_root() / 'scripts') test_env.set('PYTHONIOENCODING', 'utf-8') schemas = [ + 'alias-bad-type.json', + 'aliases-bad-type.json', + 'alias-missing-source.json', + 'alias-name-bad-type.json', + 'alias-source-bad-type.json', + 'alias-source-elem-bad-type.json', + 'alias-source-empty.json', + 'alias-unknown-key.json', 'alternate-any.json', 'alternate-array.json', 'alternate-base.json', diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json index 63f92adf68..28cb0d34bf 100644 --- a/tests/qapi-schema/qapi-schema-test.json +++ b/tests/qapi-schema/qapi-schema-test.json @@ -325,3 +325,27 @@ { 'event': 'TEST-EVENT-FEATURES1', 'features': [ 'deprecated' ] } + +# test 'aliases' + +{ 'struct': 'AliasStruct0', + 'data': { 'foo': 'int' }, + 'aliases': [] } +{ 'struct': 'AliasStruct1', + 'data': { 'foo': 'int' }, + 'aliases': [ { 'name': 'bar', 'source': ['foo'] } ] } +{ 'struct': 'AliasStruct2', + 'data': { 'nested': 'AliasStruct1' }, + 'aliases': [ { 'name': 'bar', 'source': ['nested', 'foo'] } ] } +{ 'struct': 'AliasStruct3', + 'data': { 'nested': 'AliasStruct1' }, + 'aliases': [ { 'source': ['nested'] } ] } + +{ 'union': 'AliasFlatUnion', + 'base': { 'tag': 'FeatureEnum1' }, + 'discriminator': 'tag', + 'data': { 'eins': 'FeatureStruct1' }, + 'aliases': [ { 'name': 'bar', 'source': ['foo'] } ] } +{ 'union': 'AliasSimpleUnion', + 'data': { 'eins': 'AliasStruct1' }, + 'aliases': [ { 'source': ['data'] } ] } diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out index 3b1387d9f1..84f11e8702 100644 --- a/tests/qapi-schema/qapi-schema-test.out +++ b/tests/qapi-schema/qapi-schema-test.out @@ -443,6 +443,35 @@ command test-command-cond-features3 None -> None event TEST-EVENT-FEATURES1 None boxed=False feature deprecated +object AliasStruct0 + member foo: int optional=False +object AliasStruct1 + member foo: int optional=False + alias bar -> foo +object AliasStruct2 + member nested: AliasStruct1 optional=False + alias bar -> nested.foo +object AliasStruct3 + member nested: AliasStruct1 optional=False + alias * -> nested.* +object q_obj_AliasFlatUnion-base + member tag: FeatureEnum1 optional=False +object AliasFlatUnion + base q_obj_AliasFlatUnion-base + alias bar -> foo + tag tag + case eins: FeatureStruct1 + case zwei: q_empty + case drei: q_empty +object q_obj_AliasStruct1-wrapper + member data: AliasStruct1 optional=False +enum AliasSimpleUnionKind + member eins +object AliasSimpleUnion + member type: AliasSimpleUnionKind optional=False + alias * -> data.* + tag type + case eins: q_obj_AliasStruct1-wrapper module include/sub-module.json include sub-sub-module.json object SecondArrayRef