From nobody Sun May 19 13:07:49 2024 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=fail; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=fail(p=none dis=none) header.from=redhat.com Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1628784790936516.5167166130054; Thu, 12 Aug 2021 09:13:10 -0700 (PDT) Received: from localhost ([::1]:43344 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1mEDKP-000777-Cc for importer@patchew.org; Thu, 12 Aug 2021 12:13:09 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:40714) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1mEDJ6-0005Av-Bd for qemu-devel@nongnu.org; Thu, 12 Aug 2021 12:11:48 -0400 Received: from us-smtp-delivery-124.mimecast.com ([216.205.24.124]:47937) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1mEDJ2-0001M5-KS for qemu-devel@nongnu.org; Thu, 12 Aug 2021 12:11:47 -0400 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-125--Ln3EkukMs2cl2_20iNdAg-1; Thu, 12 Aug 2021 12:11:41 -0400 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 1C4F11008061 for ; Thu, 12 Aug 2021 16:11:40 +0000 (UTC) Received: from merkur.fritz.box (unknown [10.39.194.67]) by smtp.corp.redhat.com (Postfix) with ESMTP id 2BA355FC22; Thu, 12 Aug 2021 16:11:39 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1628784702; 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=NTznk6WVjB6j4tC5d3KmZYtmFaLYYucUp8s3eIaVkXI=; b=HlzuMtmoIyF2567vz10MY839wxjpph8cKWKPeXikVWhihN6I/HvA+tiKfUC9lXEFlBlepi KqXOFV1vMypHsc9yj+vTYCo4Du72lAf7lEdyAejJ6Vn7JAxRb0cK0/05AOMpK0vOazqIxC irZaNY/6QQfXf3uA7OLI6yhCmkFmNsc= X-MC-Unique: -Ln3EkukMs2cl2_20iNdAg-1 From: Kevin Wolf To: qemu-devel@nongnu.org Subject: [PATCH v3 1/6] qapi: Add interfaces for alias support to Visitor Date: Thu, 12 Aug 2021 18:11:26 +0200 Message-Id: <20210812161131.92017-2-kwolf@redhat.com> In-Reply-To: <20210812161131.92017-1-kwolf@redhat.com> References: <20210812161131.92017-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 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; 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: -34 X-Spam_score: -3.5 X-Spam_bar: --- X-Spam_report: (-3.5 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.701, 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_H2=-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+importer=patchew.org@nongnu.org Sender: "Qemu-devel" X-ZohoMail-DKIM: fail (Header signature does not verify) X-ZM-MESSAGEID: 1628784792238100002 Content-Type: text/plain; charset="utf-8" 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 | 59 ++++++++++++++++++++++++++++++++++--- qapi/qapi-visit-core.c | 22 ++++++++++++++ 3 files changed, 89 insertions(+), 4 deletions(-) diff --git a/include/qapi/visitor-impl.h b/include/qapi/visitor-impl.h index 3b950f6e3d..704c5ad2d9 100644 --- a/include/qapi/visitor-impl.h +++ b/include/qapi/visitor-impl.h @@ -119,6 +119,18 @@ struct Visitor /* Optional */ bool (*deprecated)(Visitor *v, const char *name); =20 + /* + * 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; =20 diff --git a/include/qapi/visitor.h b/include/qapi/visitor.h index b3c9ef7a81..3bf0f4dad2 100644 --- a/include/qapi/visitor.h +++ b/include/qapi/visitor.h @@ -220,10 +220,17 @@ * * * This file provides helpers for use by the generated - * visit_type_FOO(): visit_optional() for the 'has_member' field - * associated with optional 'member' in the C struct, - * visit_next_list() for advancing through a FooList linked list, and - * visit_is_input() for cleaning up on failure. + * visit_type_FOO(): + * + * - visit_optional() for the 'has_member' field associated with + * optional 'member' in the C struct, + * - visit_next_list() for advancing through a FooList linked list + * - visit_is_input() for cleaning up on failure + * - visit_define_alias() for defining alternative names for object + * members in input visitors + * - visit_start/end_alias_scope() to limit the scope of aliases + * within a single input object (e.g. aliases defined in the base + * struct should not provide values for the parent struct) */ =20 /*** Useful types ***/ @@ -477,6 +484,50 @@ bool visit_deprecated_accept(Visitor *v, const char *n= ame, Error **errp); */ bool visit_deprecated(Visitor *v, const char *name); =20 +/* + * 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 a641adec51..79df6901ae 100644 --- a/qapi/qapi-visit-core.c +++ b/qapi/qapi-visit-core.c @@ -153,6 +153,28 @@ bool visit_deprecated(Visitor *v, const char *name) return true; } =20 +void visit_define_alias(Visitor *v, const char *name, const char **source) +{ + assert(source[0] !=3D 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 =3D=3D VISITOR_INPUT; --=20 2.31.1 From nobody Sun May 19 13:07:49 2024 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=fail; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=fail(p=none dis=none) header.from=redhat.com Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1628784793551603.3973645238423; Thu, 12 Aug 2021 09:13:13 -0700 (PDT) Received: from localhost ([::1]:43636 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1mEDKS-0007LB-FP for importer@patchew.org; Thu, 12 Aug 2021 12:13:12 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:40716) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1mEDJ6-0005Aw-Bb for qemu-devel@nongnu.org; Thu, 12 Aug 2021 12:11:48 -0400 Received: from us-smtp-delivery-124.mimecast.com ([216.205.24.124]:33555) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1mEDJ2-0001Ms-OW for qemu-devel@nongnu.org; Thu, 12 Aug 2021 12:11:47 -0400 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-401-64yBGazXMfmz9NiESRAjCA-1; Thu, 12 Aug 2021 12:11:42 -0400 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 51F8118C8C16 for ; Thu, 12 Aug 2021 16:11:41 +0000 (UTC) Received: from merkur.fritz.box (unknown [10.39.194.67]) by smtp.corp.redhat.com (Postfix) with ESMTP id 61F545C3E0; Thu, 12 Aug 2021 16:11:40 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1628784703; 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=ClZ2V82vIuah89XudutsMjf4+R0o17xvTWdNNTPgtHY=; b=QOR1xTF5W1n54QOGDgFZogODRr8Tc0jI8jYHx3NX8gBiEljQH1UNkDfVAsM+Vv7YVo+zyn URPOKZI3b0oFsjiJovFwrBl6bdgLwXyaufQYCOe5PW7izSGsZytqK+JBJJLmqIbXKa4OZD o2io4/0bi0s3mhf7u2hM1o5B3+rHAAE= X-MC-Unique: 64yBGazXMfmz9NiESRAjCA-1 From: Kevin Wolf To: qemu-devel@nongnu.org Subject: [PATCH v3 2/6] qapi: Remember alias definitions in qobject-input-visitor Date: Thu, 12 Aug 2021 18:11:27 +0200 Message-Id: <20210812161131.92017-3-kwolf@redhat.com> In-Reply-To: <20210812161131.92017-1-kwolf@redhat.com> References: <20210812161131.92017-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 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; 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: -34 X-Spam_score: -3.5 X-Spam_bar: --- X-Spam_report: (-3.5 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.701, 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_H2=-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+importer=patchew.org@nongnu.org Sender: "Qemu-devel" X-ZohoMail-DKIM: fail (Header signature does not verify) X-ZM-MESSAGEID: 1628784794374100005 Content-Type: text/plain; charset="utf-8" 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 | 147 +++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/qapi/qobject-input-visitor.c b/qapi/qobject-input-visitor.c index 04b790412e..d0061d33d6 100644 --- a/qapi/qobject-input-visitor.c +++ b/qapi/qobject-input-visitor.c @@ -30,6 +30,50 @@ #include "qemu/cutils.h" #include "qemu/option.h" =20 +/* + * 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 >=3D 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 */ @@ -39,6 +83,9 @@ typedef struct StackObject { const QListEntry *entry; /* If @obj is QList: unvisited tail */ unsigned index; /* If @obj is QList: list index of @entry = */ =20 + QSLIST_HEAD(, InputVisitorAlias) aliases; + int alias_scope_nesting; /* Number of open alias scopes */ + QSLIST_ENTRY(StackObject) node; /* parent */ } StackObject; =20 @@ -205,6 +252,45 @@ static const char *qobject_input_get_keyval(QObjectInp= utVisitor *qiv, return qstring_get_str(qstr); } =20 +/* + * 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; + InputVisitorAlias *propagated_alias; + + 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 a->name in src and doesn't apply inside + * dst any more. + */ + if (a->name && !a->src[1]) { + continue; + } + + propagated_alias =3D g_new(InputVisitorAlias, 1); + *propagated_alias =3D (InputVisitorAlias) { + .name =3D a->name, + .alias_so =3D a->alias_so, + .src =3D &a->src[1], + }; + + QSLIST_INSERT_HEAD(&dst->aliases, propagated_alias, next); + } +} + static const QListEntry *qobject_input_push(QObjectInputVisitor *qiv, const char *name, QObject *obj, void *qapi) @@ -228,6 +314,9 @@ static const QListEntry *qobject_input_push(QObjectInpu= tVisitor *qiv, g_hash_table_insert(h, (void *)qdict_entry_key(entry), NULL); } tos->h =3D h; + if (!QSLIST_EMPTY(&qiv->stack)) { + propagate_aliases(tos, QSLIST_FIRST(&qiv->stack)); + } } else { assert(qlist); tos->entry =3D qlist_first(qlist); @@ -259,10 +348,17 @@ static bool qobject_input_check_struct(Visitor *v, Er= ror **errp) =20 static void qobject_input_stack_object_free(StackObject *tos) { + InputVisitorAlias *a; + if (tos->h) { g_hash_table_unref(tos->h); } =20 + while ((a =3D QSLIST_FIRST(&tos->aliases))) { + QSLIST_REMOVE_HEAD(&tos->aliases, next); + g_free(a); + } + g_free(tos); } =20 @@ -276,6 +372,54 @@ static void qobject_input_pop(Visitor *v, void **obj) qobject_input_stack_object_free(tos); } =20 +static void qobject_input_start_alias_scope(Visitor *v) +{ + QObjectInputVisitor *qiv =3D to_qiv(v); + StackObject *tos =3D QSLIST_FIRST(&qiv->stack); + + tos->alias_scope_nesting++; +} + +static void qobject_input_end_alias_scope(Visitor *v) +{ + QObjectInputVisitor *qiv =3D to_qiv(v); + StackObject *tos =3D 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 =3D to_qiv(v); + StackObject *tos =3D QSLIST_FIRST(&qiv->stack); + InputVisitorAlias *alias =3D 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 =3D (InputVisitorAlias) { + .name =3D name, + .alias_so =3D tos, + .src =3D 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) { @@ -717,6 +861,9 @@ static QObjectInputVisitor *qobject_input_visitor_base_= new(QObject *obj) v->visitor.start_alternate =3D qobject_input_start_alternate; v->visitor.optional =3D qobject_input_optional; v->visitor.deprecated_accept =3D qobject_input_deprecated_accept; + v->visitor.define_alias =3D qobject_input_define_alias; + v->visitor.start_alias_scope =3D qobject_input_start_alias_scope; + v->visitor.end_alias_scope =3D qobject_input_end_alias_scope; v->visitor.free =3D qobject_input_free; =20 v->root =3D qobject_ref(obj); --=20 2.31.1 From nobody Sun May 19 13:07:49 2024 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=fail; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=fail(p=none dis=none) header.from=redhat.com Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1628784790309340.09730131597337; Thu, 12 Aug 2021 09:13:10 -0700 (PDT) Received: from localhost ([::1]:43432 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1mEDKO-0007BM-Hk for importer@patchew.org; Thu, 12 Aug 2021 12:13:08 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:40742) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1mEDJ7-0005BB-8b for qemu-devel@nongnu.org; Thu, 12 Aug 2021 12:11:49 -0400 Received: from us-smtp-delivery-124.mimecast.com ([170.10.133.124]:56835) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1mEDJ3-0001NN-CO for qemu-devel@nongnu.org; Thu, 12 Aug 2021 12:11:49 -0400 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-442-pNNSTi2BPN-M36nD4GDT3g-1; Thu, 12 Aug 2021 12:11:43 -0400 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 8A4B01012D9E for ; Thu, 12 Aug 2021 16:11:42 +0000 (UTC) Received: from merkur.fritz.box (unknown [10.39.194.67]) by smtp.corp.redhat.com (Postfix) with ESMTP id 991045C25A; Thu, 12 Aug 2021 16:11:41 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1628784704; 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=H76Y1c/QXnY6CPybEgO8I6XEVH5Nn00/hcbCGywgZA8=; b=fAXIjnkSak7F2/l0Cud9i785YrL8uk4kSm6+EKODub84CfqU/PFA5oF5KPJWm5v9k18IPi KsGDQvmN54lypDrR3D4vNyIgNoTyPF1lKfTqiikazZf+ammSHllmfA/XQFurOrwLWDKJNS 5p0cs6Z6wUBV8pBq7tEG/rYfSMBM9o0= X-MC-Unique: pNNSTi2BPN-M36nD4GDT3g-1 From: Kevin Wolf To: qemu-devel@nongnu.org Subject: [PATCH v3 3/6] qapi: Simplify full_name_nth() in qobject-input-visitor Date: Thu, 12 Aug 2021 18:11:28 +0200 Message-Id: <20210812161131.92017-4-kwolf@redhat.com> In-Reply-To: <20210812161131.92017-1-kwolf@redhat.com> References: <20210812161131.92017-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 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=170.10.133.124; envelope-from=kwolf@redhat.com; helo=us-smtp-delivery-124.mimecast.com X-Spam_score_int: -34 X-Spam_score: -3.5 X-Spam_bar: --- X-Spam_report: (-3.5 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.701, 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_H2=-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+importer=patchew.org@nongnu.org Sender: "Qemu-devel" X-ZohoMail-DKIM: fail (Header signature does not verify) X-ZM-MESSAGEID: 1628784792226100001 Content-Type: text/plain; charset="utf-8" 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. Make this explicit with a new boolean parameter. This makes the function easier to use in cases where we have the StackObject, but don't know how many steps down the stack it is. The following patches will introduce such a caller. 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 d0061d33d6..16a75442ff 100644 --- a/qapi/qobject-input-visitor.c +++ b/qapi/qobject-input-visitor.c @@ -110,20 +110,20 @@ static QObjectInputVisitor *to_qiv(Visitor *v) } =20 /* - * 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 *nam= e, - int n) +static const char *full_name_so(QObjectInputVisitor *qiv, const char *name, + bool skip_member, StackObject *so) { - StackObject *so; char buf[32]; =20 if (qiv->errname) { @@ -132,10 +132,14 @@ static const char *full_name_nth(QObjectInputVisitor = *qiv, const char *name, qiv->errname =3D g_string_new(""); } =20 - QSLIST_FOREACH(so , &qiv->stack, node) { - if (n) { - n--; - } else if (qobject_type(so->obj) =3D=3D QTYPE_QDICT) { + if (skip_member && so) { + assert(name =3D=3D NULL); + name =3D so->name; + so =3D QSLIST_NEXT(so, node); + } + + for (; so; so =3D QSLIST_NEXT(so, node)) { + if (qobject_type(so->obj) =3D=3D QTYPE_QDICT) { g_string_prepend(qiv->errname, name ?: ""); g_string_prepend_c(qiv->errname, '.'); } else { @@ -146,7 +150,6 @@ static const char *full_name_nth(QObjectInputVisitor *q= iv, const char *name, } name =3D so->name; } - assert(!n); =20 if (name) { g_string_prepend(qiv->errname, name); @@ -161,7 +164,9 @@ static const char *full_name_nth(QObjectInputVisitor *q= iv, const char *name, =20 static const char *full_name(QObjectInputVisitor *qiv, const char *name) { - return full_name_nth(qiv, name, 0); + StackObject *tos =3D QSLIST_FIRST(&qiv->stack); + + return full_name_so(qiv, name, false, tos); } =20 static QObject *qobject_input_try_get_object(QObjectInputVisitor *qiv, @@ -507,7 +512,7 @@ static bool qobject_input_check_list(Visitor *v, Error = **errp) =20 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; --=20 2.31.1 From nobody Sun May 19 13:07:49 2024 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=fail; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=fail(p=none dis=none) header.from=redhat.com Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1628784902007391.8898048277814; Thu, 12 Aug 2021 09:15:02 -0700 (PDT) Received: from localhost ([::1]:48816 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1mEDMC-0002L8-V8 for importer@patchew.org; Thu, 12 Aug 2021 12:15:00 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:40744) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1mEDJ7-0005C3-JN for qemu-devel@nongnu.org; Thu, 12 Aug 2021 12:11:49 -0400 Received: from us-smtp-delivery-124.mimecast.com ([170.10.133.124]:40575) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1mEDJ4-0001O4-Ji for qemu-devel@nongnu.org; Thu, 12 Aug 2021 12:11:49 -0400 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-80-Nry8fZfKOmys49UjqZiiLw-1; Thu, 12 Aug 2021 12:11:44 -0400 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 C363D800493 for ; Thu, 12 Aug 2021 16:11:43 +0000 (UTC) Received: from merkur.fritz.box (unknown [10.39.194.67]) by smtp.corp.redhat.com (Postfix) with ESMTP id D23EE5FC22; Thu, 12 Aug 2021 16:11:42 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1628784706; 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=LWymCUA+oKy+rAWEfR+PC7qlzYe7fMXznqGfg/fQHZw=; b=fbnVXZUFklHooSemaLmYnqoT8pFkMsEwi5PrX5pZaM62eL6gin+Oq5ccnSKHLr0WJiBdPY B5vJ3ADLYG0FOatVbt36roNSUiIR0xXh0amHeX+eUxZu9a1reCFRv5LNBoHAbMUTGUMYfr AmHvGti1Wk+8DZDi3XKIzTi0HxTViJg= X-MC-Unique: Nry8fZfKOmys49UjqZiiLw-1 From: Kevin Wolf To: qemu-devel@nongnu.org Subject: [PATCH v3 4/6] qapi: Apply aliases in qobject-input-visitor Date: Thu, 12 Aug 2021 18:11:29 +0200 Message-Id: <20210812161131.92017-5-kwolf@redhat.com> In-Reply-To: <20210812161131.92017-1-kwolf@redhat.com> References: <20210812161131.92017-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 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=170.10.133.124; envelope-from=kwolf@redhat.com; helo=us-smtp-delivery-124.mimecast.com X-Spam_score_int: -34 X-Spam_score: -3.5 X-Spam_bar: --- X-Spam_report: (-3.5 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.701, 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_H2=-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+importer=patchew.org@nongnu.org Sender: "Qemu-devel" X-ZohoMail-DKIM: fail (Header signature does not verify) X-ZM-MESSAGEID: 1628784904327100001 Content-Type: text/plain; charset="utf-8" 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 | 227 +++++++++++++++++++++++++++++++++-- 1 file changed, 218 insertions(+), 9 deletions(-) diff --git a/qapi/qobject-input-visitor.c b/qapi/qobject-input-visitor.c index 16a75442ff..6193df28a5 100644 --- a/qapi/qobject-input-visitor.c +++ b/qapi/qobject-input-visitor.c @@ -97,6 +97,8 @@ struct QObjectInputVisitor { QObject *root; bool keyval; /* Assume @root made with keyval_parse() */ =20 + QDict *empty_qdict; /* Used for implicit objects */ + /* Stack of objects being visited (all entries will be either * QDict or QList). */ QSLIST_HEAD(, StackObject) stack; @@ -169,9 +171,190 @@ static const char *full_name(QObjectInputVisitor *qiv= , const char *name) return full_name_so(qiv, name, false, tos); } =20 +static bool find_object_member(QObjectInputVisitor *qiv, + StackObject **so, const char **name, + bool *is_alias_prefix, Error **errp); + +/* + * Check whether the member @name in @so, or an alias for it, is + * present in the input and can be used to obtain the value. + */ +static bool input_present(QObjectInputVisitor *qiv, StackObject *so, + const char *name) +{ + /* + * Check whether the alias member is present in the input + * (possibly recursively because aliases are transitive). + * The QAPI generator makes sure that alises cannot form loops, so + * the recursion guaranteed to terminate. + */ + 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 (which + * must be an alias contained in so->aliases). + * + * If @name is only a prefix of the alias source, but doesn't match + * immediately, false is returned and *is_alias_prefix is set to true + * if it is non-NULL. In all other cases, *is_alias_prefix is left + * unchanged. + */ +static bool alias_source_matches(QObjectInputVisitor *qiv, + StackObject *so, InputVisitorAlias *a, + const char *name, bool *is_alias_prefix) +{ + if (a->src[0] =3D=3D NULL) { + assert(a->name =3D=3D NULL); + return true; + } + + if (!strcmp(a->src[0], name)) { + if (a->name && a->src[1] =3D=3D NULL) { + /* + * We're matching an exact member, the source for this alias is + * immediately in @so. + */ + return true; + } else if (is_alias_prefix) { + /* + * We're only looking at a prefix of the source path for the a= lias. + * If the input contains no object of the requested name, we w= ill + * 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 sou= rce + * path. The QAPI generator checks this condition. + */ + if (!a->name || input_present(qiv, a->alias_so, a->name)) { + *is_alias_prefix =3D 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 @is_alias_prefix on + * return is undefined in this case. + * + * If no value could be found in the input, false is returned and @so + * and @name are set to NULL. This is not an error and @errp remains + * unchanged. If @is_alias_prefix 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 @is_alias_prefix on return is undefined in this case. + */ +static bool find_object_member(QObjectInputVisitor *qiv, + StackObject **so, const char **name, + bool *is_alias_prefix, Error **errp) +{ + QDict *qdict =3D qobject_to(QDict, (*so)->obj); + const char *found_name =3D NULL; + StackObject *found_so =3D NULL; + bool found_is_wildcard =3D false; + InputVisitorAlias *a; + + if (is_alias_prefix) { + *is_alias_prefix =3D false; + } + + /* Directly present in the container */ + if (qdict_haskey(qdict, *name)) { + found_name =3D *name; + found_so =3D *so; + } + + /* + * Find aliases whose source path matches @name in this StackObject. W= e can + * then get the value with the key a->name from a->alias_so. + */ + QSLIST_FOREACH(a, &(*so)->aliases, next) { + if (a->name =3D=3D NULL && found_name && !found_is_wildcard) { + /* + * Skip wildcard aliases if we already have a match. This is + * not a conflict that should result in an error. + * + * However, multiple wildcard aliases matching is an error + * and will be caught below. + */ + continue; + } + + if (!alias_source_matches(qiv, *so, a, *name, is_alias_prefix)) { + continue; + } + + /* + * For single-member aliases, an alias name is specified in the + * alias definition. For wildcard aliases, the alias has the same + * name as the member in the source object, i.e. *name. + */ + if (!input_present(qiv, a->alias_so, a->name ?: *name)) { + continue; + } + + /* + * A non-wildcard alias simply overrides a wildcard alias, but + * two matching non-wildcard aliases or two matching wildcard + * aliases conflict with each other. + */ + if (found_name && (!found_is_wildcard || a->name =3D=3D NULL)) { + error_setg(errp, "Value for parameter %s was already given " + "through an alias", + full_name_so(qiv, *name, false, *so)); + return false; + } else { + found_name =3D a->name ?: *name; + found_so =3D a->alias_so; + found_is_wildcard =3D !a->name; + } + } + + /* + * Chained aliases: *found_so/found_name might be the source of + * another alias. + */ + if (found_name && (found_so !=3D *so || found_name !=3D *name)) { + find_object_member(qiv, &found_so, &found_name, NULL, errp); + } + + *so =3D found_so; + *name =3D found_name; + + return found_name !=3D NULL; +} + static QObject *qobject_input_try_get_object(QObjectInputVisitor *qiv, const char *name, - bool consume) + bool consume, Error **errp) { StackObject *tos; QObject *qobj; @@ -189,10 +372,31 @@ static QObject *qobject_input_try_get_object(QObjectI= nputVisitor *qiv, assert(qobj); =20 if (qobject_type(qobj) =3D=3D QTYPE_QDICT) { - assert(name); - ret =3D qdict_get(qobject_to(QDict, qobj), name); - if (tos->h && consume && ret) { - bool removed =3D g_hash_table_remove(tos->h, name); + StackObject *so =3D tos; + const char *key =3D name; + bool is_alias_prefix; + + assert(key); + if (!find_object_member(qiv, &so, &key, &is_alias_prefix, errp)) { + if (is_alias_prefix) { + /* + * 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 =3D qdict_new(); + } + return QOBJECT(qiv->empty_qdict); + } else { + return NULL; + } + } + ret =3D qdict_get(qobject_to(QDict, so->obj), key); + assert(ret !=3D NULL); + if (so->h && consume) { + bool removed =3D g_hash_table_remove(so->h, key); assert(removed); } } else { @@ -218,9 +422,10 @@ static QObject *qobject_input_get_object(QObjectInputV= isitor *qiv, const char *name, bool consume, Error **errp) { - QObject *obj =3D qobject_input_try_get_object(qiv, name, consume); + ERRP_GUARD(); + QObject *obj =3D qobject_input_try_get_object(qiv, name, consume, errp= ); =20 - if (!obj) { + if (!obj && !*errp) { error_setg(errp, QERR_MISSING_PARAMETER, full_name(qiv, name)); } return obj; @@ -803,13 +1008,16 @@ static bool qobject_input_type_size_keyval(Visitor *= v, const char *name, static void qobject_input_optional(Visitor *v, const char *name, bool *pre= sent) { QObjectInputVisitor *qiv =3D to_qiv(v); - QObject *qobj =3D qobject_input_try_get_object(qiv, name, false); + Error *local_err =3D NULL; + QObject *qobj =3D qobject_input_try_get_object(qiv, name, false, &loca= l_err); =20 - if (!qobj) { + /* If there was an error, let the caller try and run into the error */ + if (!qobj && !local_err) { *present =3D false; return; } =20 + error_free(local_err); *present =3D true; } =20 @@ -842,6 +1050,7 @@ static void qobject_input_free(Visitor *v) qobject_input_stack_object_free(tos); } =20 + qobject_unref(qiv->empty_qdict); qobject_unref(qiv->root); if (qiv->errname) { g_string_free(qiv->errname, TRUE); --=20 2.31.1 From nobody Sun May 19 13:07:49 2024 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=fail; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=fail(p=none dis=none) header.from=redhat.com Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 162878503242495.22171764056066; Thu, 12 Aug 2021 09:17:12 -0700 (PDT) Received: from localhost ([::1]:53142 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1mEDOI-0005Fw-Qo for importer@patchew.org; Thu, 12 Aug 2021 12:17:10 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:40758) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1mEDJB-0005IV-3Q for qemu-devel@nongnu.org; Thu, 12 Aug 2021 12:11:53 -0400 Received: from us-smtp-delivery-124.mimecast.com ([216.205.24.124]:59370) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1mEDJ8-0001QG-6R for qemu-devel@nongnu.org; Thu, 12 Aug 2021 12:11:52 -0400 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-237-tjSoT7H7PPmUKyp0rqs1tw-1; Thu, 12 Aug 2021 12:11:46 -0400 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 4D4C518C8C02 for ; Thu, 12 Aug 2021 16:11:45 +0000 (UTC) Received: from merkur.fritz.box (unknown [10.39.194.67]) by smtp.corp.redhat.com (Postfix) with ESMTP id 1715E5C3E0; Thu, 12 Aug 2021 16:11:43 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1628784709; 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=2cfv/7q83Oz25H5/HugtmXhTybxw9g50tkQa824YGGw=; b=efwxNwgGzhV6fAQX3vuQCk3KZwmip4es40LgIzCsg8NzUrf5MseEYmErKIavOh3qGNvcd/ 4tgPVlcDC0ifPDuTWg8haD+7BwyZp3DBskTtEsPmGBl3JHWDFPkRpMD/T/wfxbyDgvVIhu 0crFUVPe8JN6I6koM3HTF2cs+gRBcEg= X-MC-Unique: tjSoT7H7PPmUKyp0rqs1tw-1 From: Kevin Wolf To: qemu-devel@nongnu.org Subject: [PATCH v3 5/6] qapi: Add support for aliases Date: Thu, 12 Aug 2021 18:11:30 +0200 Message-Id: <20210812161131.92017-6-kwolf@redhat.com> In-Reply-To: <20210812161131.92017-1-kwolf@redhat.com> References: <20210812161131.92017-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 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; 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: -34 X-Spam_score: -3.5 X-Spam_bar: --- X-Spam_report: (-3.5 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.701, 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_H2=-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+importer=patchew.org@nongnu.org Sender: "Qemu-devel" X-ZohoMail-DKIM: fail (Header signature does not verify) X-ZM-MESSAGEID: 1628785033419100001 Content-Type: text/plain; charset="utf-8" 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.rst | 104 +++++++++++++++++++++- docs/sphinx/qapidoc.py | 2 +- scripts/qapi/expr.py | 47 +++++++++- scripts/qapi/schema.py | 116 +++++++++++++++++++++++-- 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, 297 insertions(+), 21 deletions(-) diff --git a/docs/devel/qapi-code-gen.rst b/docs/devel/qapi-code-gen.rst index 26c62b0e7b..c0883507a8 100644 --- a/docs/devel/qapi-code-gen.rst +++ b/docs/devel/qapi-code-gen.rst @@ -262,7 +262,8 @@ Syntax:: 'data': MEMBERS, '*base': STRING, '*if': COND, - '*features': FEATURES } + '*features': FEATURES, + '*aliases': ALIASES } MEMBERS =3D { MEMBER, ... } MEMBER =3D STRING : TYPE-REF | STRING : { 'type': TYPE-REF, @@ -312,6 +313,9 @@ the schema`_ below for more on this. The optional 'features' member specifies features. See Features_ below for more on this. =20 +The optional 'aliases' member specifies aliases. See Aliases_ below +for more on this. + =20 Union types ----------- @@ -321,13 +325,15 @@ Syntax:: UNION =3D { '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 =3D { BRANCH, ... } BRANCH =3D STRING : TYPE-REF | STRING : { 'type': TYPE-REF, '*if': COND } @@ -437,6 +443,9 @@ the schema`_ below for more on this. The optional 'features' member specifies features. See Features_ below for more on this. =20 +The optional 'aliases' member specifies aliases. See Aliases_ below +for more on this. + =20 Alternate types --------------- @@ -888,6 +897,95 @@ shows a conditional entity only when the condition is = satisfied in this particular build. =20 =20 +Aliases +------- + +Object types, including structs and unions, can contain alias +definitions. + +Aliases define alternative member names that may be used in wire input +to provide a value for a member in the same object or in a nested +object. + +Syntax:: + + ALIASES =3D [ ALIAS, ... ] + ALIAS =3D { '*name': STRING, + 'source': [ STRING, ... ] } + +If ``name`` is present, then the single member referred to by ``source`` +is made accessible with the name given by ``name`` in the type where the +alias definition is specified. + +If ``name`` is not present, then this is a wildcard alias and 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. + +Do not use optional objects in the path of a wildcard alias unless there +is no semantic difference between an empty object and an absent object. +Absent objects are implicitly turned into empty ones if an alias could +apply and provide a value in the nested object, which is always the case +for wildcard aliases. + +Example: Alternative name for a member in the same object :: + + { 'struct': 'File', + 'data': { 'path': 'str' }, + 'aliases': [ { 'name': 'filename', 'source': ['path'] } ] } + +The member ``path`` may instead be given through its alias ``filename`` +in input. + +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 for ``D`` mean the +same:: + + { 'eins': { 'zwei': { 'drei': { 'zahl': 42 } } } } + + { 'the_B': { 'drei': { 'zahl': 42 } } } + + { 'number': 42 } + +Example: Flattening a simple 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 alternative name for the source +of the other alias. + +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; in this case, the alias remains unused. The +QAPI generator checks that there is at least one branch for which an +alias could match. + + Documentation comments ---------------------- =20 diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py index 87c67ab23f..68340b8529 100644 --- a/docs/sphinx/qapidoc.py +++ b/docs/sphinx/qapidoc.py @@ -313,7 +313,7 @@ def visit_enum_type(self, name, info, ifcond, features,= members, prefix): + self._nodes_for_if_section(ifcond)) =20 def visit_object_type(self, name, info, ifcond, features, - base, members, variants): + base, members, variants, aliases): doc =3D self._cur_doc if base and base.is_implicit(): base =3D None diff --git a/scripts/qapi/expr.py b/scripts/qapi/expr.py index cf98923fa6..054fef8d8e 100644 --- a/scripts/qapi/expr.py +++ b/scripts/qapi/expr.py @@ -430,6 +430,45 @@ def check_features(features: Optional[object], check_if(feat, info, source) =20 =20 +def check_aliases(aliases: Optional[object], + info: QAPISourceInfo) -> None: + """ + Normalize and validate the ``aliases`` member. + + :param aliases: The aliases member value to validate. + :param info: QAPI schema source file information. + + :raise QAPISemError: When ``aliases`` fails validation. + :return: None, ``aliases`` is normalized in-place as needed. + """ + + 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 =3D "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 =3D "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: _JSONObject, info: QAPISourceInfo) -> None: """ Normalize and validate this expression as an ``enum`` definition. @@ -483,6 +522,7 @@ def check_struct(expr: _JSONObject, info: QAPISourceInf= o) -> None: =20 check_type(members, info, "'data'", allow_dict=3Dname) check_type(expr.get('base'), info, "'base'") + check_aliases(expr.get('aliases'), info) =20 =20 def check_union(expr: _JSONObject, info: QAPISourceInfo) -> None: @@ -509,6 +549,8 @@ def check_union(expr: _JSONObject, info: QAPISourceInfo= ) -> None: raise QAPISemError(info, "'discriminator' requires 'base'") check_name_is_str(discriminator, info, "'discriminator'") =20 + check_aliases(expr.get('aliases'), info) + if not isinstance(members, dict): raise QAPISemError(info, "'data' must be an object") =20 @@ -653,7 +695,7 @@ def check_exprs(exprs: List[_JSONObject]) -> List[_JSON= Object]: elif meta =3D=3D 'union': check_keys(expr, info, meta, ['union', 'data'], - ['base', 'discriminator', 'if', 'features']) + ['base', 'discriminator', 'if', 'features', 'aliase= s']) normalize_members(expr.get('base')) normalize_members(expr['data']) check_union(expr, info) @@ -664,7 +706,8 @@ def check_exprs(exprs: List[_JSONObject]) -> List[_JSON= Object]: check_alternate(expr, info) elif meta =3D=3D '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 =3D=3D 'command': diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py index d1d27ff7ee..fc75635f4e 100644 --- a/scripts/qapi/schema.py +++ b/scripts/qapi/schema.py @@ -118,7 +118,7 @@ def visit_array_type(self, name, info, ifcond, element_= type): pass =20 def visit_object_type(self, name, info, ifcond, features, - base, members, variants): + base, members, variants, aliases): pass =20 def visit_object_type_flat(self, name, info, ifcond, features, @@ -364,7 +364,7 @@ def describe(self): =20 class QAPISchemaObjectType(QAPISchemaType): def __init__(self, name, info, doc, ifcond, features, - base, local_members, variants): + base, local_members, variants, aliases=3DNone): # 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 +382,9 @@ def __init__(self, name, info, doc, ifcond, features, self.local_members =3D local_members self.variants =3D variants self.members =3D None + self.aliases =3D aliases or [] + for a in self.aliases: + a.set_defined_in(name) =20 def check(self, schema): # This calls another type T's .check() exactly when the C @@ -413,12 +416,16 @@ def check(self, schema): for m in self.local_members: m.check(schema) m.check_clash(self.info, seen) - members =3D seen.values() + members =3D list(seen.values()) =20 if self.variants: self.variants.check(schema, seen) self.variants.check_clash(self.info, seen) =20 + for a in self.aliases: + a.check_clash(self.info, seen) + self.check_path(a, list(a.source), members) + self.members =3D members # mark completed =20 # Check that the members of this type do not cause duplicate JSON memb= ers, @@ -430,6 +437,68 @@ def check_clash(self, info, seen): for m in self.members: m.check_clash(info, seen) =20 + # Deletes elements from path, so pass a copy if you still need them + def check_path(self, alias, path, members=3DNone, local_aliases_seen= =3D()): + assert isinstance(path, list) + + if not path: + return + first =3D path.pop(0) + + for a in self.aliases: + if a.name =3D=3D first: + if a in local_aliases_seen: + raise QAPISemError( + self.info, + "%s resolving to '%s' makes '%s' an alias for itse= lf" + % (a.describe(self.info), a.source[0], a.source[0]= )) + + path =3D a.source + path + return self.check_path(alias, path, members, + (*local_aliases_seen, a)) + + if members is None: + assert self.members is not None + members =3D self.members + else: + assert isinstance(members, list) + + for m in members: + if m.name =3D=3D first: + # Wildcard aliases can only accept object types in the who= le + # path; for single-member aliases, the last element can be + # any type + need_obj =3D (alias.name is None) or path + if need_obj and not isinstance(m.type, QAPISchemaObjectTyp= e): + raise QAPISemError( + self.info, + "%s has non-object '%s' in its source path" + % (alias.describe(self.info), m.name)) + if alias.name is None and m.optional: + raise QAPISemError( + self.info, + "%s has optional object %s in its source path" + % (alias.describe(self.info), m.describe(self.info= ))) + if path: + m.type.check_path(alias, path) + return + + # It is sufficient that the path is valid in at least one variant + if self.variants: + for v in self.variants.variants: + try: + return v.type.check_path(alias, [first, *path]) + except QAPISemError: + pass + raise QAPISemError( + self.info, + "%s has a source path that does not exist in any variant o= f %s" + % (alias.describe(self.info), self.describe())) + + raise QAPISemError( + self.info, + "%s has inexistent source" % alias.describe(self.info)) + def connect_doc(self, doc=3DNone): super().connect_doc(doc) doc =3D doc or self.doc @@ -474,7 +543,7 @@ def visit(self, visitor): 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) @@ -639,7 +708,7 @@ def check_clash(self, info, seen): =20 =20 class QAPISchemaMember: - """ Represents object members, enum members and features """ + """ Represents object members, enum members, features and aliases """ role =3D 'member' =20 def __init__(self, name, info, ifcond=3DNone): @@ -705,6 +774,30 @@ class QAPISchemaFeature(QAPISchemaMember): role =3D 'feature' =20 =20 +class QAPISchemaAlias(QAPISchemaMember): + role =3D 'alias' + + def __init__(self, name, info, source): + assert name is None or isinstance(name, str) + assert source + for member in source: + assert isinstance(member, str) + + super().__init__(name or '*', info) + self.name =3D name + self.source =3D source + + def check_clash(self, info, seen): + if self.name: + super().check_clash(info, seen) + + def describe(self, info): + if self.name: + return super().describe(info) + else: + return "wildcard alias" + + class QAPISchemaObjectTypeMember(QAPISchemaMember): def __init__(self, name, info, typ, optional, ifcond=3DNone, features= =3DNone): super().__init__(name, info, ifcond) @@ -971,6 +1064,12 @@ def _make_features(self, features, info): return [QAPISchemaFeature(f['name'], info, f.get('if')) for f in features] =20 + def _make_aliases(self, aliases, info): + if aliases is None: + return [] + return [QAPISchemaAlias(a.get('name'), info, 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] @@ -1045,11 +1144,12 @@ def _def_struct_type(self, expr, info, doc): base =3D expr.get('base') data =3D expr['data'] ifcond =3D expr.get('if') + aliases =3D self._make_aliases(expr.get('aliases'), info) features =3D 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)) =20 def _make_variant(self, case, typ, ifcond, info): return QAPISchemaVariant(case, info, typ, ifcond) @@ -1068,6 +1168,7 @@ def _def_union_type(self, expr, info, doc): data =3D expr['data'] base =3D expr.get('base') ifcond =3D expr.get('if') + aliases =3D self._make_aliases(expr.get('aliases'), info) features =3D self._make_features(expr.get('features'), info) tag_name =3D expr.get('discriminator') tag_member =3D None @@ -1092,7 +1193,8 @@ def _def_union_type(self, expr, info, doc): QAPISchemaObjectType(name, info, doc, ifcond, features, base, members, QAPISchemaVariants( - tag_name, info, tag_member, variants)= )) + tag_name, info, tag_member, variants), + aliases)) =20 def _def_alternate_type(self, expr, info, doc): name =3D expr['alternate'] diff --git a/scripts/qapi/types.py b/scripts/qapi/types.py index 20d572a23a..3bc451baa9 100644 --- a/scripts/qapi/types.py +++ b/scripts/qapi/types.py @@ -25,6 +25,7 @@ from .gen import QAPISchemaModularCVisitor, ifcontext from .schema import ( QAPISchema, + QAPISchemaAlias, QAPISchemaEnumMember, QAPISchemaFeature, QAPISchemaObjectType, @@ -332,7 +333,8 @@ def visit_object_type(self, 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 =3D=3D 'q_empty': return diff --git a/scripts/qapi/visit.py b/scripts/qapi/visit.py index 9e96f3c566..0aa0764755 100644 --- a/scripts/qapi/visit.py +++ b/scripts/qapi/visit.py @@ -26,6 +26,7 @@ from .gen import QAPISchemaModularCVisitor, ifcontext from .schema import ( QAPISchema, + QAPISchemaAlias, QAPISchemaEnumMember, QAPISchemaEnumType, QAPISchemaFeature, @@ -60,7 +61,8 @@ def gen_visit_members_decl(name: str) -> str: def gen_visit_object_members(name: str, base: Optional[QAPISchemaObjectType], members: List[QAPISchemaObjectTypeMember], - variants: Optional[QAPISchemaVariants]) -> st= r: + variants: Optional[QAPISchemaVariants], + aliases: List[QAPISchemaAlias]) -> str: ret =3D mcgen(''' =20 bool visit_type_%(c_name)s_members(Visitor *v, %(c_name)s *obj, Error **er= rp) @@ -68,6 +70,24 @@ def gen_visit_object_members(name: str, ''', c_name=3Dc_name(name)) =20 + if aliases: + ret +=3D mcgen(''' + visit_start_alias_scope(v); +''') + + for a in aliases: + if a.name: + name =3D '"%s"' % a.name + else: + name =3D "NULL" + + source =3D ", ".join('"%s"' % x for x in a.source) + + ret +=3D mcgen(''' + visit_define_alias(v, %(name)s, (const char * []) { %(source)s, NULL }= ); +''', + name=3Dname, source=3Dsource) + if base: ret +=3D mcgen(''' if (!visit_type_%(c_type)s_members(v, (%(c_type)s *)obj, errp)) { @@ -148,6 +168,11 @@ def gen_visit_object_members(name: str, } ''') =20 + if aliases: + ret +=3D mcgen(''' + visit_end_alias_scope(v); +''') + ret +=3D mcgen(''' return true; } @@ -376,14 +401,15 @@ def visit_object_type(self, 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 =3D=3D '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 f1c4deb9a5..376630901b 100755 --- a/tests/qapi-schema/test-qapi.py +++ b/tests/qapi-schema/test-qapi.py @@ -47,7 +47,7 @@ def visit_array_type(self, name, info, ifcond, element_ty= pe): self._print_if(ifcond) =20 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 @@ def visit_object_type(self, name, info, ifcond, features, % (m.name, m.type.name, m.optional)) self._print_if(m.ifcond, 8) self._print_features(m.features, indent=3D8) + 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-t= ype.err index 576e716197..c382e61d88 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/unk= nown-expr-key.err index f2538e3ce7..354916968f 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'. --=20 2.31.1 From nobody Sun May 19 13:07:49 2024 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=fail; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=fail(p=none dis=none) header.from=redhat.com Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1628785036078548.0237205387745; Thu, 12 Aug 2021 09:17:16 -0700 (PDT) Received: from localhost ([::1]:53268 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1mEDOM-0005M7-Vo for importer@patchew.org; Thu, 12 Aug 2021 12:17:15 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:40778) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1mEDJE-0005Qm-QL for qemu-devel@nongnu.org; Thu, 12 Aug 2021 12:11:56 -0400 Received: from us-smtp-delivery-124.mimecast.com ([170.10.133.124]:49985) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1mEDJB-0001RI-4E for qemu-devel@nongnu.org; Thu, 12 Aug 2021 12:11:56 -0400 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-394-C5mr1ltzPgayVHHajVA9hg-1; Thu, 12 Aug 2021 12:11:47 -0400 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 9466A1012D97 for ; Thu, 12 Aug 2021 16:11:46 +0000 (UTC) Received: from merkur.fritz.box (unknown [10.39.194.67]) by smtp.corp.redhat.com (Postfix) with ESMTP id 78568171FF; Thu, 12 Aug 2021 16:11:45 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1628784712; 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=9WKM20IX1xMFA/pfUweQC5saQedSBmHf8PhfJLbpdYk=; b=Z0E3FkVng1UiOrEoReVD5HGZGgy0jre+giu0Pbw1UnLArTR2Amx8uJlu/HW/nvcO5PdQdT 7koRlCkvwZQhyW37M3eJSShnsfv7QYHJ3Yiv01e7YCk5M0DaX4a+y2rDDL0+wDxPMEZ+Wq Vh0rxCzEpHEYY0Ajp3ujoc4KAmVtetM= X-MC-Unique: C5mr1ltzPgayVHHajVA9hg-1 From: Kevin Wolf To: qemu-devel@nongnu.org Subject: [PATCH v3 6/6] tests/qapi-schema: Test cases for aliases Date: Thu, 12 Aug 2021 18:11:31 +0200 Message-Id: <20210812161131.92017-7-kwolf@redhat.com> In-Reply-To: <20210812161131.92017-1-kwolf@redhat.com> References: <20210812161131.92017-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 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=170.10.133.124; envelope-from=kwolf@redhat.com; helo=us-smtp-delivery-124.mimecast.com X-Spam_score_int: -34 X-Spam_score: -3.5 X-Spam_bar: --- X-Spam_report: (-3.5 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.701, 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_H2=-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+importer=patchew.org@nongnu.org Sender: "Qemu-devel" X-ZohoMail-DKIM: fail (Header signature does not verify) X-ZM-MESSAGEID: 1628785037646100001 Content-Type: text/plain; charset="utf-8" Signed-off-by: Kevin Wolf --- tests/unit/test-qobject-input-visitor.c | 218 ++++++++++++++++++ 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-name-conflict.err | 2 + tests/qapi-schema/alias-name-conflict.json | 4 + tests/qapi-schema/alias-name-conflict.out | 0 tests/qapi-schema/alias-recursive.err | 2 + tests/qapi-schema/alias-recursive.json | 4 + tests/qapi-schema/alias-recursive.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 .../alias-source-inexistent-variants.err | 2 + .../alias-source-inexistent-variants.json | 12 + .../alias-source-inexistent-variants.out | 0 tests/qapi-schema/alias-source-inexistent.err | 2 + .../qapi-schema/alias-source-inexistent.json | 3 + tests/qapi-schema/alias-source-inexistent.out | 0 .../alias-source-non-object-path.err | 2 + .../alias-source-non-object-path.json | 3 + .../alias-source-non-object-path.out | 0 .../alias-source-non-object-wildcard.err | 2 + .../alias-source-non-object-wildcard.json | 3 + .../alias-source-non-object-wildcard.out | 0 ...lias-source-optional-wildcard-indirect.err | 2 + ...ias-source-optional-wildcard-indirect.json | 6 + ...lias-source-optional-wildcard-indirect.out | 0 .../alias-source-optional-wildcard.err | 2 + .../alias-source-optional-wildcard.json | 5 + .../alias-source-optional-wildcard.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 | 16 ++ tests/qapi-schema/qapi-schema-test.json | 26 +++ tests/qapi-schema/qapi-schema-test.out | 31 +++ 52 files changed, 388 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-name-conflict.err create mode 100644 tests/qapi-schema/alias-name-conflict.json create mode 100644 tests/qapi-schema/alias-name-conflict.out create mode 100644 tests/qapi-schema/alias-recursive.err create mode 100644 tests/qapi-schema/alias-recursive.json create mode 100644 tests/qapi-schema/alias-recursive.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-source-inexistent-variants.err create mode 100644 tests/qapi-schema/alias-source-inexistent-variants.json create mode 100644 tests/qapi-schema/alias-source-inexistent-variants.out create mode 100644 tests/qapi-schema/alias-source-inexistent.err create mode 100644 tests/qapi-schema/alias-source-inexistent.json create mode 100644 tests/qapi-schema/alias-source-inexistent.out create mode 100644 tests/qapi-schema/alias-source-non-object-path.err create mode 100644 tests/qapi-schema/alias-source-non-object-path.json create mode 100644 tests/qapi-schema/alias-source-non-object-path.out create mode 100644 tests/qapi-schema/alias-source-non-object-wildcard.err create mode 100644 tests/qapi-schema/alias-source-non-object-wildcard.json create mode 100644 tests/qapi-schema/alias-source-non-object-wildcard.out create mode 100644 tests/qapi-schema/alias-source-optional-wildcard-indire= ct.err create mode 100644 tests/qapi-schema/alias-source-optional-wildcard-indire= ct.json create mode 100644 tests/qapi-schema/alias-source-optional-wildcard-indire= ct.out create mode 100644 tests/qapi-schema/alias-source-optional-wildcard.err create mode 100644 tests/qapi-schema/alias-source-optional-wildcard.json create mode 100644 tests/qapi-schema/alias-source-optional-wildcard.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/unit/test-qobject-input-visitor.c b/tests/unit/test-qobj= ect-input-visitor.c index e41b91a2a6..f2891b6f5d 100644 --- a/tests/unit/test-qobject-input-visitor.c +++ b/tests/unit/test-qobject-input-visitor.c @@ -952,6 +952,214 @@ static void test_visitor_in_list_union_number(TestInp= utVisitorData *data, g_string_free(gstr_list, true); } =20 +static void test_visitor_in_alias_struct_local(TestInputVisitorData *data, + const void *unused) +{ + AliasStruct1 *tmp =3D NULL; + Error *err =3D NULL; + Visitor *v; + + /* Can still specify the real member name with alias support */ + v =3D visitor_input_test_init(data, "{ 'foo': 42 }"); + visit_type_AliasStruct1(v, NULL, &tmp, &error_abort); + g_assert_cmpint(tmp->foo, =3D=3D, 42); + qapi_free_AliasStruct1(tmp); + + /* The alias is a working alternative */ + v =3D visitor_input_test_init(data, "{ 'bar': 42 }"); + visit_type_AliasStruct1(v, NULL, &tmp, &error_abort); + g_assert_cmpint(tmp->foo, =3D=3D, 42); + qapi_free_AliasStruct1(tmp); + + /* But you can't use both at the same time */ + v =3D visitor_input_test_init(data, "{ 'foo': 5, 'bar': 42 }"); + visit_type_AliasStruct1(v, NULL, &tmp, &err); + error_free_or_abort(&err); +} + +static void test_visitor_in_alias_struct_nested(TestInputVisitorData *data, + const void *unused) +{ + AliasStruct2 *tmp =3D NULL; + Error *err =3D NULL; + Visitor *v; + + /* Can still specify the real member names with alias support */ + v =3D visitor_input_test_init(data, "{ 'nested': { 'foo': 42 } }"); + visit_type_AliasStruct2(v, NULL, &tmp, &error_abort); + g_assert_cmpint(tmp->nested->foo, =3D=3D, 42); + qapi_free_AliasStruct2(tmp); + + /* The inner alias is a working alternative */ + v =3D visitor_input_test_init(data, "{ 'nested': { 'bar': 42 } }"); + visit_type_AliasStruct2(v, NULL, &tmp, &error_abort); + g_assert_cmpint(tmp->nested->foo, =3D=3D, 42); + qapi_free_AliasStruct2(tmp); + + /* So is the outer alias */ + v =3D visitor_input_test_init(data, "{ 'bar': 42 }"); + visit_type_AliasStruct2(v, NULL, &tmp, &error_abort); + g_assert_cmpint(tmp->nested->foo, =3D=3D, 42); + qapi_free_AliasStruct2(tmp); + + /* You can't use more than one option at the same time */ + v =3D visitor_input_test_init(data, "{ 'bar': 5, 'nested': { 'foo': 42= } }"); + visit_type_AliasStruct2(v, NULL, &tmp, &err); + error_free_or_abort(&err); + + v =3D visitor_input_test_init(data, "{ 'bar': 5, 'nested': { 'bar': 42= } }"); + visit_type_AliasStruct2(v, NULL, &tmp, &err); + error_free_or_abort(&err); + + v =3D visitor_input_test_init(data, "{ 'nested': { 'foo': 42, 'bar': 4= 2 } }"); + visit_type_AliasStruct2(v, NULL, &tmp, &err); + error_free_or_abort(&err); + + v =3D visitor_input_test_init(data, "{ 'bar': 5, " + " 'nested': { 'foo': 42, 'bar': 42 = } }"); + visit_type_AliasStruct2(v, NULL, &tmp, &err); + error_free_or_abort(&err); +} + +static void test_visitor_in_alias_wildcard(TestInputVisitorData *data, + const void *unused) +{ + AliasStruct3 *tmp =3D NULL; + Error *err =3D NULL; + Visitor *v; + + /* Can still specify the real member names with alias support */ + v =3D visitor_input_test_init(data, "{ 'nested': { 'foo': 42 } }"); + visit_type_AliasStruct3(v, NULL, &tmp, &error_abort); + g_assert_cmpint(tmp->nested->foo, =3D=3D, 42); + qapi_free_AliasStruct3(tmp); + + /* The wildcard alias makes it work on the top level */ + v =3D visitor_input_test_init(data, "{ 'foo': 42 }"); + visit_type_AliasStruct3(v, NULL, &tmp, &error_abort); + g_assert_cmpint(tmp->nested->foo, =3D=3D, 42); + qapi_free_AliasStruct3(tmp); + + /* It makes the inner alias available, too */ + v =3D visitor_input_test_init(data, "{ 'bar': 42 }"); + visit_type_AliasStruct3(v, NULL, &tmp, &error_abort); + g_assert_cmpint(tmp->nested->foo, =3D=3D, 42); + qapi_free_AliasStruct3(tmp); + + /* You can't use more than one option at the same time */ + v =3D visitor_input_test_init(data, "{ 'foo': 42, 'nested': { 'foo': 4= 2 } }"); + visit_type_AliasStruct3(v, NULL, &tmp, &err); + error_free_or_abort(&err); + + v =3D visitor_input_test_init(data, "{ 'bar': 42, 'nested': { 'foo': 4= 2 } }"); + visit_type_AliasStruct3(v, NULL, &tmp, &err); + error_free_or_abort(&err); + + v =3D visitor_input_test_init(data, "{ 'foo': 42, 'nested': { 'bar': 4= 2 } }"); + visit_type_AliasStruct3(v, NULL, &tmp, &err); + error_free_or_abort(&err); + + v =3D visitor_input_test_init(data, "{ 'bar': 42, 'nested': { 'bar': 4= 2 } }"); + visit_type_AliasStruct3(v, NULL, &tmp, &err); + error_free_or_abort(&err); + + v =3D visitor_input_test_init(data, "{ 'foo': 42, 'bar': 42 }"); + visit_type_AliasStruct3(v, NULL, &tmp, &err); + error_free_or_abort(&err); +} + +static void test_visitor_in_alias_flat_union(TestInputVisitorData *data, + const void *unused) +{ + AliasFlatUnion *tmp =3D NULL; + Error *err =3D NULL; + Visitor *v; + + /* Can still specify the real member name with alias support */ + v =3D visitor_input_test_init(data, "{ 'tag': 'drei' }"); + visit_type_AliasFlatUnion(v, NULL, &tmp, &error_abort); + g_assert_cmpint(tmp->tag, =3D=3D, FEATURE_ENUM1_DREI); + qapi_free_AliasFlatUnion(tmp); + + /* Use alias for a base member (the discriminator even) */ + v =3D visitor_input_test_init(data, "{ 'variant': 'zwei' }"); + visit_type_AliasFlatUnion(v, NULL, &tmp, &error_abort); + g_assert_cmpint(tmp->tag, =3D=3D, FEATURE_ENUM1_ZWEI); + qapi_free_AliasFlatUnion(tmp); + + /* Use alias for a variant member */ + v =3D visitor_input_test_init(data, "{ 'tag': 'eins', 'bar': 42 }"); + visit_type_AliasFlatUnion(v, NULL, &tmp, &error_abort); + g_assert_cmpint(tmp->tag, =3D=3D, FEATURE_ENUM1_EINS); + g_assert_cmpint(tmp->u.eins.foo, =3D=3D, 42); + qapi_free_AliasFlatUnion(tmp); + + /* Both together */ + v =3D visitor_input_test_init(data, "{ 'variant': 'eins', 'bar': 42 }"= ); + visit_type_AliasFlatUnion(v, NULL, &tmp, &error_abort); + g_assert_cmpint(tmp->tag, =3D=3D, FEATURE_ENUM1_EINS); + g_assert_cmpint(tmp->u.eins.foo, =3D=3D, 42); + qapi_free_AliasFlatUnion(tmp); + + /* You can't use more than one option at the same time for each alias = */ + v =3D visitor_input_test_init(data, "{ 'variant': 'zwei', 'tag': 'drei= ' }"); + visit_type_AliasFlatUnion(v, NULL, &tmp, &err); + error_free_or_abort(&err); + + v =3D visitor_input_test_init(data, "{ 'tag': 'eins', 'foo': 6, 'bar':= 9 }"); + visit_type_AliasFlatUnion(v, NULL, &tmp, &err); + error_free_or_abort(&err); +} + +static void test_visitor_in_alias_simple_union(TestInputVisitorData *data, + const void *unused) +{ + AliasSimpleUnion *tmp =3D NULL; + Error *err =3D NULL; + Visitor *v; + + /* Can still specify the real member name with alias support */ + v =3D visitor_input_test_init(data, "{ 'type': 'eins', " + " 'data': { 'foo': 42 } }"); + visit_type_AliasSimpleUnion(v, NULL, &tmp, &error_abort); + g_assert_cmpint(tmp->type, =3D=3D, ALIAS_SIMPLE_UNION_KIND_EINS); + g_assert_cmpint(tmp->u.eins.data->foo, =3D=3D, 42); + qapi_free_AliasSimpleUnion(tmp); + + /* 'type' can be aliased */ + v =3D visitor_input_test_init(data, "{ 'tag': 'eins', " + " 'data': { 'foo': 42 } }"); + visit_type_AliasSimpleUnion(v, NULL, &tmp, &error_abort); + g_assert_cmpint(tmp->type, =3D=3D, ALIAS_SIMPLE_UNION_KIND_EINS); + g_assert_cmpint(tmp->u.eins.data->foo, =3D=3D, 42); + qapi_free_AliasSimpleUnion(tmp); + + /* The wildcard alias makes it work on the top level */ + v =3D visitor_input_test_init(data, "{ 'type': 'eins', 'foo': 42 }"); + visit_type_AliasSimpleUnion(v, NULL, &tmp, &error_abort); + g_assert_cmpint(tmp->type, =3D=3D, ALIAS_SIMPLE_UNION_KIND_EINS); + g_assert_cmpint(tmp->u.eins.data->foo, =3D=3D, 42); + qapi_free_AliasSimpleUnion(tmp); + + /* It makes the inner alias available, too */ + v =3D visitor_input_test_init(data, "{ 'type': 'eins', 'bar': 42 }"); + visit_type_AliasSimpleUnion(v, NULL, &tmp, &error_abort); + g_assert_cmpint(tmp->type, =3D=3D, ALIAS_SIMPLE_UNION_KIND_EINS); + g_assert_cmpint(tmp->u.eins.data->foo, =3D=3D, 42); + qapi_free_AliasSimpleUnion(tmp); + + /* You can't use more than one option at the same time for each alias = */ + v =3D visitor_input_test_init(data, "{ 'type': 'eins', 'tag': 'eins' }= "); + visit_type_AliasSimpleUnion(v, NULL, &tmp, &err); + error_free_or_abort(&err); + + v =3D visitor_input_test_init(data, "{ 'type': 'eins', " + " 'bar': 123, " + " 'data': { 'foo': 312 } }"); + visit_type_AliasSimpleUnion(v, NULL, &tmp, &err); + error_free_or_abort(&err); +} + static void input_visitor_test_add(const char *testpath, const void *user_data, void (*test_func)(TestInputVisitorData = *data, @@ -1350,6 +1558,16 @@ int main(int argc, char **argv) NULL, test_visitor_in_list_union_string); input_visitor_test_add("/visitor/input/list_union/number", NULL, test_visitor_in_list_union_number); + input_visitor_test_add("/visitor/input/alias/struct-local", + NULL, test_visitor_in_alias_struct_local); + input_visitor_test_add("/visitor/input/alias/struct-nested", + NULL, test_visitor_in_alias_struct_nested); + input_visitor_test_add("/visitor/input/alias/wildcard", + NULL, test_visitor_in_alias_wildcard); + input_visitor_test_add("/visitor/input/alias/flat-union", + NULL, test_visitor_in_alias_flat_union); + input_visitor_test_add("/visitor/input/alias/simple-union", + NULL, test_visitor_in_alias_simple_union); input_visitor_test_add("/visitor/input/fail/struct", NULL, test_visitor_in_fail_struct); input_visitor_test_add("/visitor/input/fail/struct-nested", 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/alia= s-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-schem= a/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-name-conflict.err b/tests/qapi-schema/= alias-name-conflict.err new file mode 100644 index 0000000000..d5825a0285 --- /dev/null +++ b/tests/qapi-schema/alias-name-conflict.err @@ -0,0 +1,2 @@ +alias-name-conflict.json: In struct 'AliasStruct0': +alias-name-conflict.json:1: alias 'bar' collides with member 'bar' diff --git a/tests/qapi-schema/alias-name-conflict.json b/tests/qapi-schema= /alias-name-conflict.json new file mode 100644 index 0000000000..bdb5bd4eab --- /dev/null +++ b/tests/qapi-schema/alias-name-conflict.json @@ -0,0 +1,4 @@ +{ 'struct': 'AliasStruct0', + 'data': { 'foo': 'int', + 'bar': 'int' }, + 'aliases': [ { 'name': 'bar', 'source': ['foo'] } ] } diff --git a/tests/qapi-schema/alias-name-conflict.out b/tests/qapi-schema/= alias-name-conflict.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/alias-recursive.err b/tests/qapi-schema/alia= s-recursive.err new file mode 100644 index 0000000000..127ce019a8 --- /dev/null +++ b/tests/qapi-schema/alias-recursive.err @@ -0,0 +1,2 @@ +alias-recursive.json: In struct 'AliasStruct0': +alias-recursive.json:1: alias 'baz' resolving to 'bar' makes 'bar' an alia= s for itself diff --git a/tests/qapi-schema/alias-recursive.json b/tests/qapi-schema/ali= as-recursive.json new file mode 100644 index 0000000000..e25b935324 --- /dev/null +++ b/tests/qapi-schema/alias-recursive.json @@ -0,0 +1,4 @@ +{ 'struct': 'AliasStruct0', + 'data': { 'foo': 'int' }, + 'aliases': [ { 'name': 'bar', 'source': ['baz'] }, + { 'name': 'baz', 'source': ['bar'] } ] } diff --git a/tests/qapi-schema/alias-recursive.out b/tests/qapi-schema/alia= s-recursive.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/alias-source-bad-type.err b/tests/qapi-schem= a/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-sche= ma/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-schem= a/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' require= s 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/a= lias-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/a= lias-source-empty.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/alias-source-inexistent-variants.err b/tests= /qapi-schema/alias-source-inexistent-variants.err new file mode 100644 index 0000000000..a5d4a4c334 --- /dev/null +++ b/tests/qapi-schema/alias-source-inexistent-variants.err @@ -0,0 +1,2 @@ +alias-source-inexistent-variants.json: In union 'AliasStruct0': +alias-source-inexistent-variants.json:7: alias 'test' has a source path th= at does not exist in any variant of union type 'AliasStruct0' diff --git a/tests/qapi-schema/alias-source-inexistent-variants.json b/test= s/qapi-schema/alias-source-inexistent-variants.json new file mode 100644 index 0000000000..6328095b86 --- /dev/null +++ b/tests/qapi-schema/alias-source-inexistent-variants.json @@ -0,0 +1,12 @@ +{ 'enum': 'Variants', + 'data': [ 'a', 'b' ] } +{ 'struct': 'Variant0', + 'data': { 'foo': 'int' } } +{ 'struct': 'Variant1', + 'data': { 'bar': 'int' } } +{ 'union': 'AliasStruct0', + 'base': { 'type': 'Variants' }, + 'discriminator': 'type', + 'data': { 'a': 'Variant0', + 'b': 'Variant1' }, + 'aliases': [ { 'name': 'test', 'source': ['baz'] } ] } diff --git a/tests/qapi-schema/alias-source-inexistent-variants.out b/tests= /qapi-schema/alias-source-inexistent-variants.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/alias-source-inexistent.err b/tests/qapi-sch= ema/alias-source-inexistent.err new file mode 100644 index 0000000000..2d65d3f588 --- /dev/null +++ b/tests/qapi-schema/alias-source-inexistent.err @@ -0,0 +1,2 @@ +alias-source-inexistent.json: In struct 'AliasStruct0': +alias-source-inexistent.json:1: alias 'bar' has inexistent source diff --git a/tests/qapi-schema/alias-source-inexistent.json b/tests/qapi-sc= hema/alias-source-inexistent.json new file mode 100644 index 0000000000..5106d3609f --- /dev/null +++ b/tests/qapi-schema/alias-source-inexistent.json @@ -0,0 +1,3 @@ +{ 'struct': 'AliasStruct0', + 'data': { 'foo': 'int' }, + 'aliases': [ { 'name': 'bar', 'source': ['baz'] } ] } diff --git a/tests/qapi-schema/alias-source-inexistent.out b/tests/qapi-sch= ema/alias-source-inexistent.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/alias-source-non-object-path.err b/tests/qap= i-schema/alias-source-non-object-path.err new file mode 100644 index 0000000000..b3c748350f --- /dev/null +++ b/tests/qapi-schema/alias-source-non-object-path.err @@ -0,0 +1,2 @@ +alias-source-non-object-path.json: In struct 'AliasStruct0': +alias-source-non-object-path.json:1: alias 'bar' has non-object 'foo' in i= ts source path diff --git a/tests/qapi-schema/alias-source-non-object-path.json b/tests/qa= pi-schema/alias-source-non-object-path.json new file mode 100644 index 0000000000..808a3e6281 --- /dev/null +++ b/tests/qapi-schema/alias-source-non-object-path.json @@ -0,0 +1,3 @@ +{ 'struct': 'AliasStruct0', + 'data': { 'foo': 'int' }, + 'aliases': [ { 'name': 'bar', 'source': ['foo', 'baz'] } ] } diff --git a/tests/qapi-schema/alias-source-non-object-path.out b/tests/qap= i-schema/alias-source-non-object-path.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/alias-source-non-object-wildcard.err b/tests= /qapi-schema/alias-source-non-object-wildcard.err new file mode 100644 index 0000000000..4adc0d2281 --- /dev/null +++ b/tests/qapi-schema/alias-source-non-object-wildcard.err @@ -0,0 +1,2 @@ +alias-source-non-object-wildcard.json: In struct 'AliasStruct0': +alias-source-non-object-wildcard.json:1: wildcard alias has non-object 'fo= o' in its source path diff --git a/tests/qapi-schema/alias-source-non-object-wildcard.json b/test= s/qapi-schema/alias-source-non-object-wildcard.json new file mode 100644 index 0000000000..59ce1081ef --- /dev/null +++ b/tests/qapi-schema/alias-source-non-object-wildcard.json @@ -0,0 +1,3 @@ +{ 'struct': 'AliasStruct0', + 'data': { 'foo': 'int' }, + 'aliases': [ { 'source': ['foo'] } ] } diff --git a/tests/qapi-schema/alias-source-non-object-wildcard.out b/tests= /qapi-schema/alias-source-non-object-wildcard.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/alias-source-optional-wildcard-indirect.err = b/tests/qapi-schema/alias-source-optional-wildcard-indirect.err new file mode 100644 index 0000000000..b58b8ff00f --- /dev/null +++ b/tests/qapi-schema/alias-source-optional-wildcard-indirect.err @@ -0,0 +1,2 @@ +alias-source-optional-wildcard-indirect.json: In struct 'AliasStruct0': +alias-source-optional-wildcard-indirect.json:3: wildcard alias has optiona= l object member 'nested' in its source path diff --git a/tests/qapi-schema/alias-source-optional-wildcard-indirect.json= b/tests/qapi-schema/alias-source-optional-wildcard-indirect.json new file mode 100644 index 0000000000..fcf04969dc --- /dev/null +++ b/tests/qapi-schema/alias-source-optional-wildcard-indirect.json @@ -0,0 +1,6 @@ +{ 'struct': 'Nested', + 'data': { 'foo': 'int' } } +{ 'struct': 'AliasStruct0', + 'data': { '*nested': 'Nested' }, + 'aliases': [ { 'name': 'nested-alias', 'source': ['nested'] }, + { 'source': ['nested-alias'] } ] } diff --git a/tests/qapi-schema/alias-source-optional-wildcard-indirect.out = b/tests/qapi-schema/alias-source-optional-wildcard-indirect.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/alias-source-optional-wildcard.err b/tests/q= api-schema/alias-source-optional-wildcard.err new file mode 100644 index 0000000000..e39200bd3d --- /dev/null +++ b/tests/qapi-schema/alias-source-optional-wildcard.err @@ -0,0 +1,2 @@ +alias-source-optional-wildcard.json: In struct 'AliasStruct0': +alias-source-optional-wildcard.json:3: wildcard alias has optional object = member 'nested' in its source path diff --git a/tests/qapi-schema/alias-source-optional-wildcard.json b/tests/= qapi-schema/alias-source-optional-wildcard.json new file mode 100644 index 0000000000..1a315f2ae0 --- /dev/null +++ b/tests/qapi-schema/alias-source-optional-wildcard.json @@ -0,0 +1,5 @@ +{ 'struct': 'Nested', + 'data': { 'foo': 'int' } } +{ 'struct': 'AliasStruct0', + 'data': { '*nested': 'Nested' }, + 'aliases': [ { 'source': ['nested'] } ] } diff --git a/tests/qapi-schema/alias-source-optional-wildcard.out b/tests/q= api-schema/alias-source-optional-wildcard.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/alias-unknown-key.err b/tests/qapi-schema/al= ias-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/a= lias-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/al= ias-unknown-key.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/aliases-bad-type.err b/tests/qapi-schema/ali= ases-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/al= iases-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/ali= ases-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 b8de58116a..f937de1c35 100644 --- a/tests/qapi-schema/meson.build +++ b/tests/qapi-schema/meson.build @@ -3,6 +3,22 @@ test_env.set('PYTHONPATH', meson.source_root() / 'scripts') test_env.set('PYTHONIOENCODING', 'utf-8') =20 schemas =3D [ + 'alias-bad-type.json', + 'aliases-bad-type.json', + 'alias-missing-source.json', + 'alias-name-bad-type.json', + 'alias-name-conflict.json', + 'alias-recursive.json', + 'alias-source-bad-type.json', + 'alias-source-elem-bad-type.json', + 'alias-source-empty.json', + 'alias-source-inexistent.json', + 'alias-source-inexistent-variants.json', + 'alias-source-non-object-path.json', + 'alias-source-non-object-wildcard.json', + 'alias-source-optional-wildcard.json', + 'alias-source-optional-wildcard-indirect.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/qa= pi-schema-test.json index 84b9d41f15..c5e81a883c 100644 --- a/tests/qapi-schema/qapi-schema-test.json +++ b/tests/qapi-schema/qapi-schema-test.json @@ -336,3 +336,29 @@ =20 { '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': 'variant', 'source': ['tag'] }, + { 'name': 'bar', 'source': ['foo'] } ] } +{ 'union': 'AliasSimpleUnion', + 'data': { 'eins': 'AliasStruct1' }, + 'aliases': [ { 'source': ['data'] }, + { 'name': 'tag', 'source': ['type'] } ] } diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qap= i-schema-test.out index e0b8a5f0b6..f6b8a98b7c 100644 --- a/tests/qapi-schema/qapi-schema-test.out +++ b/tests/qapi-schema/qapi-schema-test.out @@ -445,6 +445,37 @@ event TEST_EVENT_FEATURES0 FeatureStruct1 event TEST_EVENT_FEATURES1 None boxed=3DFalse feature deprecated +object AliasStruct0 + member foo: int optional=3DFalse +object AliasStruct1 + member foo: int optional=3DFalse + alias bar -> foo +object AliasStruct2 + member nested: AliasStruct1 optional=3DFalse + alias bar -> nested.foo +object AliasStruct3 + member nested: AliasStruct1 optional=3DFalse + alias * -> nested.* +object q_obj_AliasFlatUnion-base + member tag: FeatureEnum1 optional=3DFalse +object AliasFlatUnion + base q_obj_AliasFlatUnion-base + alias variant -> tag + 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=3DFalse +enum AliasSimpleUnionKind + member eins +object AliasSimpleUnion + member type: AliasSimpleUnionKind optional=3DFalse + alias * -> data.* + alias tag -> type + tag type + case eins: q_obj_AliasStruct1-wrapper module include/sub-module.json include sub-sub-module.json object SecondArrayRef --=20 2.31.1