qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
From: Anthony Liguori <anthony@codemonkey.ws>
To: Paolo Bonzini <pbonzini@redhat.com>
Cc: lcapitulino@redhat.com, eblake@redhat.com, qemu-devel@nongnu.org,
	mdroth@linux.vnet.ibm.com
Subject: Re: [Qemu-devel] [PATCH 08/10] qapi: add strict mode to input visitor
Date: Thu, 22 Mar 2012 15:25:50 -0500	[thread overview]
Message-ID: <4F6B8ACE.60504@codemonkey.ws> (raw)
In-Reply-To: <1332417072-20329-9-git-send-email-pbonzini@redhat.com>

On 03/22/2012 06:51 AM, Paolo Bonzini wrote:
> While QMP in general is designed so that it is possible to ignore
> unknown arguments, in the case of the QMP server it is better to
> reject them to detect bad clients.  In fact, we're already doing
> this at the top level in the argument checker.  To extend this to
> complex structures, add a mode to the input visitor where it checks
> for unvisited keys and raises an error if it finds one.
>
> Signed-off-by: Paolo Bonzini<pbonzini@redhat.com>

We need to document this behavior in QMP/qmp-spec.txt.

Regards,

Anthony Liguori

> ---
>   qapi/qmp-input-visitor.c |   48 +++++++++-
>   qapi/qmp-input-visitor.h |    2 +
>   test-qmp-input-strict.c  |  234 ++++++++++++++++++++++++++++++++++++++++++++++
>   tests/Makefile           |    5 +-
>   4 files changed, 285 insertions(+), 4 deletions(-)
>   create mode 100644 test-qmp-input-strict.c
>
> diff --git a/qapi/qmp-input-visitor.c b/qapi/qmp-input-visitor.c
> index 97a0186..eb09874 100644
> --- a/qapi/qmp-input-visitor.c
> +++ b/qapi/qmp-input-visitor.c
> @@ -24,6 +24,7 @@ typedef struct StackObject
>   {
>       QObject *obj;
>       const QListEntry *entry;
> +    GHashTable *h;
>   } StackObject;
>
>   struct QmpInputVisitor
> @@ -31,6 +32,7 @@ struct QmpInputVisitor
>       Visitor visitor;
>       StackObject stack[QIV_STACK_SIZE];
>       int nb_stack;
> +    bool strict;
>   };
>
>   static QmpInputVisitor *to_qiv(Visitor *v)
> @@ -45,6 +47,9 @@ static QObject *qmp_input_get_object(QmpInputVisitor *qiv,
>
>       if (qobj) {
>           if (name&&  qobject_type(qobj) == QTYPE_QDICT) {
> +            if (qiv->stack[qiv->nb_stack - 1].h) {
> +                g_hash_table_remove(qiv->stack[qiv->nb_stack - 1].h, name);
> +            }
>               return qdict_get(qobject_to_qdict(qobj), name);
>           } else if (qiv->stack[qiv->nb_stack - 1].entry) {
>               return qlist_entry_obj(qiv->stack[qiv->nb_stack - 1].entry);
> @@ -54,20 +59,47 @@ static QObject *qmp_input_get_object(QmpInputVisitor *qiv,
>       return qobj;
>   }
>
> +static void qdict_add_key(const char *key, QObject *obj, void *opaque)
> +{
> +    GHashTable *h = opaque;
> +    g_hash_table_insert(h, (gpointer) key, NULL);
> +}
> +
>   static void qmp_input_push(QmpInputVisitor *qiv, QObject *obj, Error **errp)
>   {
> -    qiv->stack[qiv->nb_stack].obj = obj;
> -    qiv->stack[qiv->nb_stack].entry = NULL;
> -    qiv->nb_stack++;
> +    GHashTable *h;
>
>       if (qiv->nb_stack>= QIV_STACK_SIZE) {
>           error_set(errp, QERR_BUFFER_OVERRUN);
>           return;
>       }
> +
> +    qiv->stack[qiv->nb_stack].obj = obj;
> +    qiv->stack[qiv->nb_stack].entry = NULL;
> +    qiv->stack[qiv->nb_stack].h = NULL;
> +
> +    if (qiv->strict&&  qobject_type(obj) == QTYPE_QDICT) {
> +        h = g_hash_table_new(g_str_hash, g_str_equal);
> +        qdict_iter(qobject_to_qdict(obj), qdict_add_key, h);
> +        qiv->stack[qiv->nb_stack].h = h;
> +    }
> +
> +    qiv->nb_stack++;
>   }
>
>   static void qmp_input_pop(QmpInputVisitor *qiv, Error **errp)
>   {
> +    GHashTableIter iter;
> +    gpointer key;
> +
> +    if (qiv->strict&&  qiv->stack[qiv->nb_stack - 1].h) {
> +        g_hash_table_iter_init(&iter, qiv->stack[qiv->nb_stack - 1].h);
> +        if (g_hash_table_iter_next(&iter,&key, NULL)) {
> +            error_set(errp, QERR_QMP_EXTRA_MEMBER, (char *) key);
> +        }
> +        g_hash_table_unref(qiv->stack[qiv->nb_stack - 1].h);
> +    }
> +
>       assert(qiv->nb_stack>  0);
>       qiv->nb_stack--;
>   }
> @@ -257,3 +289,13 @@ QmpInputVisitor *qmp_input_visitor_new(QObject *obj)
>
>       return v;
>   }
> +
> +QmpInputVisitor *qmp_input_visitor_new_strict(QObject *obj)
> +{
> +    QmpInputVisitor *v;
> +
> +    v = qmp_input_visitor_new(obj);
> +    v->strict = true;
> +
> +    return v;
> +}
> diff --git a/qapi/qmp-input-visitor.h b/qapi/qmp-input-visitor.h
> index 3f798f0..e0a48a5 100644
> --- a/qapi/qmp-input-visitor.h
> +++ b/qapi/qmp-input-visitor.h
> @@ -20,6 +20,8 @@
>   typedef struct QmpInputVisitor QmpInputVisitor;
>
>   QmpInputVisitor *qmp_input_visitor_new(QObject *obj);
> +QmpInputVisitor *qmp_input_visitor_new_strict(QObject *obj);
> +
>   void qmp_input_visitor_cleanup(QmpInputVisitor *v);
>
>   Visitor *qmp_input_get_visitor(QmpInputVisitor *v);
> diff --git a/test-qmp-input-strict.c b/test-qmp-input-strict.c
> new file mode 100644
> index 0000000..f6df8cb
> --- /dev/null
> +++ b/test-qmp-input-strict.c
> @@ -0,0 +1,234 @@
> +/*
> + * QMP Input Visitor unit-tests (strict mode).
> + *
> + * Copyright (C) 2011-2012 Red Hat Inc.
> + *
> + * Authors:
> + *  Luiz Capitulino<lcapitulino@redhat.com>
> + *  Paolo Bonzini<pbonzini@redhat.com>
> + *
> + * This work is licensed under the terms of the GNU GPL, version 2 or later.
> + * See the COPYING file in the top-level directory.
> + */
> +
> +#include<glib.h>
> +#include<stdarg.h>
> +
> +#include "qapi/qmp-input-visitor.h"
> +#include "test-qapi-types.h"
> +#include "test-qapi-visit.h"
> +#include "qemu-objects.h"
> +
> +typedef struct TestInputVisitorData {
> +    QObject *obj;
> +    QmpInputVisitor *qiv;
> +} TestInputVisitorData;
> +
> +static void validate_teardown(TestInputVisitorData *data,
> +                               const void *unused)
> +{
> +    qobject_decref(data->obj);
> +    data->obj = NULL;
> +
> +    if (data->qiv) {
> +        qmp_input_visitor_cleanup(data->qiv);
> +        data->qiv = NULL;
> +    }
> +}
> +
> +/* This is provided instead of a test setup function so that the JSON
> +   string used by the tests are kept in the test functions (and not
> +   int main()) */
> +static GCC_FMT_ATTR(2, 3)
> +Visitor *validate_test_init(TestInputVisitorData *data,
> +                             const char *json_string, ...)
> +{
> +    Visitor *v;
> +    va_list ap;
> +
> +    va_start(ap, json_string);
> +    data->obj = qobject_from_jsonv(json_string,&ap);
> +    va_end(ap);
> +
> +    g_assert(data->obj != NULL);
> +
> +    data->qiv = qmp_input_visitor_new_strict(data->obj);
> +    g_assert(data->qiv != NULL);
> +
> +    v = qmp_input_get_visitor(data->qiv);
> +    g_assert(v != NULL);
> +
> +    return v;
> +}
> +
> +typedef struct TestStruct
> +{
> +    int64_t integer;
> +    bool boolean;
> +    char *string;
> +} TestStruct;
> +
> +static void visit_type_TestStruct(Visitor *v, TestStruct **obj,
> +                                  const char *name, Error **errp)
> +{
> +    visit_start_struct(v, (void **)obj, "TestStruct", name, sizeof(TestStruct),
> +                       errp);
> +
> +    visit_type_int(v,&(*obj)->integer, "integer", errp);
> +    visit_type_bool(v,&(*obj)->boolean, "boolean", errp);
> +    visit_type_str(v,&(*obj)->string, "string", errp);
> +
> +    visit_end_struct(v, errp);
> +}
> +
> +static void test_validate_struct(TestInputVisitorData *data,
> +                                  const void *unused)
> +{
> +    TestStruct *p = NULL;
> +    Error *errp = NULL;
> +    Visitor *v;
> +
> +    v = validate_test_init(data, "{ 'integer': -42, 'boolean': true, 'string': 'foo' }");
> +
> +    visit_type_TestStruct(v,&p, NULL,&errp);
> +    g_assert(!error_is_set(&errp));
> +    g_free(p->string);
> +    g_free(p);
> +}
> +
> +static void test_validate_struct_nested(TestInputVisitorData *data,
> +                                         const void *unused)
> +{
> +    UserDefNested *udp = NULL;
> +    Error *errp = NULL;
> +    Visitor *v;
> +
> +    v = validate_test_init(data, "{ 'string0': 'string0', 'dict1': { 'string1': 'string1', 'dict2': { 'userdef1': { 'integer': 42, 'string': 'string' }, 'string2': 'string2'}}}");
> +
> +    visit_type_UserDefNested(v,&udp, NULL,&errp);
> +    g_assert(!error_is_set(&errp));
> +    qapi_free_UserDefNested(udp);
> +}
> +
> +static void test_validate_list(TestInputVisitorData *data,
> +                                const void *unused)
> +{
> +    UserDefOneList *head = NULL;
> +    Error *errp = NULL;
> +    Visitor *v;
> +
> +    v = validate_test_init(data, "[ { 'string': 'string0', 'integer': 42 }, { 'string': 'string1', 'integer': 43 }, { 'string': 'string2', 'integer': 44 } ]");
> +
> +    visit_type_UserDefOneList(v,&head, NULL,&errp);
> +    g_assert(!error_is_set(&errp));
> +    qapi_free_UserDefOneList(head);
> +}
> +
> +static void test_validate_union(TestInputVisitorData *data,
> +                                 const void *unused)
> +{
> +    UserDefUnion *tmp = NULL;
> +    Visitor *v;
> +    Error *errp = NULL;
> +
> +    v = validate_test_init(data, "{ 'type': 'b', 'data' : { 'integer': 42 } }");
> +
> +    visit_type_UserDefUnion(v,&tmp, NULL,&errp);
> +    g_assert(!error_is_set(&errp));
> +    qapi_free_UserDefUnion(tmp);
> +}
> +
> +static void test_validate_fail_struct(TestInputVisitorData *data,
> +                                       const void *unused)
> +{
> +    TestStruct *p = NULL;
> +    Error *errp = NULL;
> +    Visitor *v;
> +
> +    v = validate_test_init(data, "{ 'integer': -42, 'boolean': true, 'string': 'foo', 'extra': 42 }");
> +
> +    visit_type_TestStruct(v,&p, NULL,&errp);
> +    g_assert(error_is_set(&errp));
> +    if (p) {
> +        g_free(p->string);
> +    }
> +    g_free(p);
> +}
> +
> +static void test_validate_fail_struct_nested(TestInputVisitorData *data,
> +                                              const void *unused)
> +{
> +    UserDefNested *udp = NULL;
> +    Error *errp = NULL;
> +    Visitor *v;
> +
> +    v = validate_test_init(data, "{ 'string0': 'string0', 'dict1': { 'string1': 'string1', 'dict2': { 'userdef1': { 'integer': 42, 'string': 'string', 'extra': [42, 23, {'foo':'bar'}] }, 'string2': 'string2'}}}");
> +
> +    visit_type_UserDefNested(v,&udp, NULL,&errp);
> +    g_assert(error_is_set(&errp));
> +    qapi_free_UserDefNested(udp);
> +}
> +
> +static void test_validate_fail_list(TestInputVisitorData *data,
> +                                     const void *unused)
> +{
> +    UserDefOneList *head = NULL;
> +    Error *errp = NULL;
> +    Visitor *v;
> +
> +    v = validate_test_init(data, "[ { 'string': 'string0', 'integer': 42 }, { 'string': 'string1', 'integer': 43 }, { 'string': 'string2', 'integer': 44, 'extra': 'ggg' } ]");
> +
> +    visit_type_UserDefOneList(v,&head, NULL,&errp);
> +    g_assert(error_is_set(&errp));
> +    qapi_free_UserDefOneList(head);
> +}
> +
> +static void test_validate_fail_union(TestInputVisitorData *data,
> +                                      const void *unused)
> +{
> +    UserDefUnion *tmp = NULL;
> +    Error *errp = NULL;
> +    Visitor *v;
> +
> +    v = validate_test_init(data, "{ 'type': 'b', 'data' : { 'integer': 42 }, 'extra': 'yyy' }");
> +
> +    visit_type_UserDefUnion(v,&tmp, NULL,&errp);
> +    g_assert(error_is_set(&errp));
> +    qapi_free_UserDefUnion(tmp);
> +}
> +
> +static void validate_test_add(const char *testpath,
> +                               TestInputVisitorData *data,
> +                               void (*test_func)(TestInputVisitorData *data, const void *user_data))
> +{
> +    g_test_add(testpath, TestInputVisitorData, data, NULL, test_func,
> +               validate_teardown);
> +}
> +
> +int main(int argc, char **argv)
> +{
> +    TestInputVisitorData testdata;
> +
> +    g_test_init(&argc,&argv, NULL);
> +
> +    validate_test_add("/visitor/input-strict/pass/struct",
> +&testdata, test_validate_struct);
> +    validate_test_add("/visitor/input-strict/pass/struct-nested",
> +&testdata, test_validate_struct_nested);
> +    validate_test_add("/visitor/input-strict/pass/list",
> +&testdata, test_validate_list);
> +    validate_test_add("/visitor/input-strict/pass/union",
> +&testdata, test_validate_union);
> +    validate_test_add("/visitor/input-strict/fail/struct",
> +&testdata, test_validate_fail_struct);
> +    validate_test_add("/visitor/input-strict/fail/struct-nested",
> +&testdata, test_validate_fail_struct_nested);
> +    validate_test_add("/visitor/input-strict/fail/list",
> +&testdata, test_validate_fail_list);
> +    validate_test_add("/visitor/input-strict/fail/union",
> +&testdata, test_validate_fail_union);
> +
> +    g_test_run();
> +
> +    return 0;
> +}
> diff --git a/tests/Makefile b/tests/Makefile
> index c78ade1..31349f7 100644
> --- a/tests/Makefile
> +++ b/tests/Makefile
> @@ -15,7 +15,7 @@ check-qfloat: check-qfloat.o qfloat.o $(tools-obj-y)
>   check-qjson: check-qjson.o $(qobject-obj-y) $(tools-obj-y)
>   test-coroutine: test-coroutine.o qemu-timer-common.o async.o $(coroutine-obj-y) $(tools-obj-y)
>
> -test-qmp-input-visitor.o test-qmp-output-visitor.o \
> +test-qmp-input-visitor.o test-qmp-output-visitor.o test-qmp-input-strict.o \
>   test-string-input-visitor.o test-string-output-visitor.o \
>   	test-qmp-commands.o qemu-ga$(EXESUF): QEMU_CFLAGS += -I $(qapi-dir)
>
> @@ -36,6 +36,9 @@ test-string-output-visitor: test-string-output-visitor.o $(qobject-obj-y) $(qapi
>   test-string-input-visitor.o: $(addprefix $(qapi-dir)/, test-qapi-types.c test-qapi-types.h test-qapi-visit.c test-qapi-visit.h) $(qapi-obj-y)
>   test-string-input-visitor: test-string-input-visitor.o $(qobject-obj-y) $(qapi-obj-y) $(tools-obj-y) $(qapi-dir)/test-qapi-visit.o $(qapi-dir)/test-qapi-types.o
>
> +test-qmp-input-strict.o: $(addprefix $(qapi-dir)/, test-qapi-types.c test-qapi-types.h test-qapi-visit.c test-qapi-visit.h) $(qapi-obj-y)
> +test-qmp-input-strict: test-qmp-input-strict.o $(qobject-obj-y) $(qapi-obj-y) $(tools-obj-y) $(qapi-dir)/test-qapi-visit.o $(qapi-dir)/test-qapi-types.o
> +
>   test-qmp-output-visitor.o: $(addprefix $(qapi-dir)/, test-qapi-types.c test-qapi-types.h test-qapi-visit.c test-qapi-visit.h) $(qapi-obj-y)
>   test-qmp-output-visitor: test-qmp-output-visitor.o $(qobject-obj-y) $(qapi-obj-y) $(tools-obj-y) $(qapi-dir)/test-qapi-visit.o $(qapi-dir)/test-qapi-types.o
>

  reply	other threads:[~2012-03-22 20:26 UTC|newest]

Thread overview: 31+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2012-03-22 11:51 [Qemu-devel] [PATCH 00/10] QAPI minor fixes and strict mode Paolo Bonzini
2012-03-22 11:51 ` [Qemu-devel] [PATCH 01/10] qapi: add a test case for type errors Paolo Bonzini
2012-03-22 20:17   ` Anthony Liguori
2012-03-22 11:51 ` [Qemu-devel] [PATCH 02/10] qapi: fail hard on stack imbalance Paolo Bonzini
2012-03-22 20:17   ` Anthony Liguori
2012-03-26 14:15   ` Luiz Capitulino
2012-03-26 15:06     ` Michael Roth
2012-03-22 11:51 ` [Qemu-devel] [PATCH 03/10] qapi: fix memory leak on error Paolo Bonzini
2012-03-22 20:17   ` Anthony Liguori
2012-03-22 11:51 ` [Qemu-devel] [PATCH 04/10] qapi: shortcut visits on errors Paolo Bonzini
2012-03-22 20:17   ` Anthony Liguori
2012-03-22 11:51 ` [Qemu-devel] [PATCH 05/10] qapi: allow freeing partially-allocated objects Paolo Bonzini
2012-03-22 20:18   ` Anthony Liguori
2012-03-22 11:51 ` [Qemu-devel] [PATCH 06/10] qapi: simplify qmp_input_next_list Paolo Bonzini
2012-03-22 20:22   ` Anthony Liguori
2012-03-22 21:24     ` Paolo Bonzini
2012-03-22 21:34       ` Anthony Liguori
2012-03-22 21:38       ` [Qemu-devel] [PATCH v2 06/10] qapi: untangle next_list Paolo Bonzini
2012-03-23 16:42         ` Michael Roth
2012-03-22 11:51 ` [Qemu-devel] [PATCH 07/10] qapi: place outermost object on qiv stack Paolo Bonzini
2012-03-22 20:25   ` Anthony Liguori
2012-03-22 11:51 ` [Qemu-devel] [PATCH 08/10] qapi: add strict mode to input visitor Paolo Bonzini
2012-03-22 20:25   ` Anthony Liguori [this message]
2012-04-02 10:34   ` Laurent Desnogues
2012-04-02 11:28     ` Paolo Bonzini
2012-03-22 11:51 ` [Qemu-devel] [PATCH 09/10] qmp: add and use q type specifier Paolo Bonzini
2012-03-22 20:27   ` Anthony Liguori
2012-03-22 11:51 ` [Qemu-devel] [PATCH 10/10] qmp: parse commands in strict mode Paolo Bonzini
2012-03-22 20:28   ` Anthony Liguori
2012-03-22 20:30     ` Luiz Capitulino
2012-03-22 21:39 ` [Qemu-devel] [PATCH 11/10] qmp: document strict parsing Paolo Bonzini

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=4F6B8ACE.60504@codemonkey.ws \
    --to=anthony@codemonkey.ws \
    --cc=eblake@redhat.com \
    --cc=lcapitulino@redhat.com \
    --cc=mdroth@linux.vnet.ibm.com \
    --cc=pbonzini@redhat.com \
    --cc=qemu-devel@nongnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).