All of lore.kernel.org
 help / color / mirror / Atom feed
* [Qemu-devel] [PATCH v15 00/23] qapi visitor cleanups (post-introspection cleanups subset E)
@ 2016-04-28  0:01 Eric Blake
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 01/23] qapi-visit: Add visitor.type classification Eric Blake
                   ` (23 more replies)
  0 siblings, 24 replies; 49+ messages in thread
From: Eric Blake @ 2016-04-28  0:01 UTC (permalink / raw)
  To: qemu-devel; +Cc: armbru

2.7 material; hopefully this iteration is close enough for
Markus to stick it in his qapi-next staging branch, so we
can move on to my other pending series.

Based on master, with no prerequisite patches.

Also available as a tag at this location:
git fetch git://repo.or.cz/qemu/ericb.git qapi-cleanupv15e

and will soon be part of my branch with the rest of the v5 series, at:
http://repo.or.cz/qemu/ericb.git/shortlog/refs/heads/qapi

v14 was:
https://lists.gnu.org/archive/html/qemu-devel/2016-04/msg01486.html

Since then, I've rearranged several patches (including moving hunks
between patches), added a couple of new ones, and in general
addressed a lot of Markus' findings.  The comparison to the previous
posting looks rather big, but a lot of it is due to comment changes
or rebase artifacts from shuffling things around, where a lot of
the end results are still the same.

001/23:[0024] [FC] 'qapi-visit: Add visitor.type classification'
002/23:[0036] [FC] 'qapi: Guarantee NULL obj on input visitor callback error'
003/23:[----] [--] 'qmp: Drop dead command->type'
004/23:[0019] [FC] 'qmp-input: Clean up stack handling'
005/23:[down] 'qapi: Use strict QMP input visitor in more places'
006/23:[0007] [FC] 'qmp-input: Don't consume input when checking has_member'
007/23:[0014] [FC] 'qapi-commands: Wrap argument visit in visit_start_struct'
008/23:[down] 'monitor: Let generated code validate arguments'
009/23:[0005] [FC] 'qom: Wrap prop visit in visit_start_struct'
010/23:[0052] [FC] 'qmp-input: Require struct push to visit members of top dict'
011/23:[0032] [FC] 'qmp-input: Refactor when list is advanced'
012/23:[0253] [FC] 'qapi: Document visitor interfaces, add assertions'
013/23:[0006] [FC] 'tests: Add check-qnull'
014/23:[0026] [FC] 'qapi: Add visit_type_null() visitor'
015/23:[0046] [FC] 'qmp: Support explicit null during visits'
016/23:[----] [--] 'spapr_drc: Expose 'null' in qom-get when there is no fdt'
017/23:[down] 'qmp: Add qmp_output_visitor_reset()'
018/23:[0024] [FC] 'qmp: Tighten output visitor rules'
019/23:[0040] [FC] 'qapi: Split visit_end_struct() into pieces'
020/23:[down] 'tests/string-input-visitor: Add negative integer tests'
021/23:[down] 'qapi: Fix string input visitor handling of invalid list'
022/23:[0082] [FC] 'qapi: Simplify semantics of visit_next_list()'
023/23:[0108] [FC] 'qapi: Change visit_type_FOO() to no longer return partial objects'

Eric Blake (22):
  qapi-visit: Add visitor.type classification
  qapi: Guarantee NULL obj on input visitor callback error
  qmp: Drop dead command->type
  qmp-input: Clean up stack handling
  qapi: Use strict QMP input visitor in more places
  qmp-input: Don't consume input when checking has_member
  qapi-commands: Wrap argument visit in visit_start_struct
  monitor: Let generated code validate arguments
  qom: Wrap prop visit in visit_start_struct
  qmp-input: Require struct push to visit members of top dict
  qmp-input: Refactor when list is advanced
  qapi: Document visitor interfaces, add assertions
  tests: Add check-qnull
  qapi: Add visit_type_null() visitor
  qmp: Support explicit null during visits
  spapr_drc: Expose 'null' in qom-get when there is no fdt
  qmp: Add qmp_output_visitor_reset()
  qmp: Tighten output visitor rules
  qapi: Split visit_end_struct() into pieces
  qapi: Fix string input visitor handling of invalid list
  qapi: Simplify semantics of visit_next_list()
  qapi: Change visit_type_FOO() to no longer return partial objects

Markus Armbruster (1):
  tests/string-input-visitor: Add negative integer tests

 include/qapi/visitor.h               | 492 +++++++++++++++++++++++++++++++++--
 include/qapi/visitor-impl.h          |  81 ++++--
 scripts/qapi-commands.py             |  20 +-
 scripts/qapi-event.py                |   5 +-
 scripts/qapi-visit.py                |  53 ++--
 include/qapi/dealloc-visitor.h       |   5 +
 include/qapi/opts-visitor.h          |   5 +
 include/qapi/qmp-input-visitor.h     |   9 +-
 include/qapi/qmp-output-visitor.h    |   1 +
 include/qapi/qmp/dispatch.h          |   6 -
 include/qapi/string-input-visitor.h  |   5 +
 include/qapi/string-output-visitor.h |   5 +
 qapi/qapi-visit-core.c               | 106 ++++++--
 block/crypto.c                       |  14 +-
 hw/ppc/spapr_drc.c                   |  11 +-
 hw/virtio/virtio-balloon.c           |  15 +-
 monitor.c                            |   4 +
 qapi/opts-visitor.c                  |  70 ++---
 qapi/qapi-dealloc-visitor.c          |  43 +--
 qapi/qmp-dispatch.c                  |  18 +-
 qapi/qmp-input-visitor.c             | 189 ++++++++------
 qapi/qmp-output-visitor.c            |  71 ++---
 qapi/qmp-registry.c                  |   1 -
 qapi/string-input-visitor.c          |  51 ++--
 qapi/string-output-visitor.c         |  43 ++-
 qmp.c                                |   2 +-
 qom/object.c                         |   5 +-
 qom/object_interfaces.c              |  33 ++-
 qom/qom-qobject.c                    |   3 +-
 replay/replay-input.c                |   2 +-
 tests/check-qnull.c                  |  75 ++++++
 tests/test-qmp-commands.c            |  15 +-
 tests/test-qmp-input-strict.c        |  21 +-
 tests/test-qmp-input-visitor.c       |  39 ++-
 tests/test-qmp-output-visitor.c      |  29 ++-
 tests/test-string-input-visitor.c    |  23 +-
 tests/test-visitor-serialization.c   |   2 +-
 util/qemu-sockets.c                  |   2 +-
 docs/qapi-code-gen.txt               |  44 +++-
 qmp-commands.hx                      | 148 +----------
 tests/.gitignore                     |   1 +
 tests/Makefile                       |   6 +-
 42 files changed, 1198 insertions(+), 575 deletions(-)
 create mode 100644 tests/check-qnull.c

-- 
2.5.5

^ permalink raw reply	[flat|nested] 49+ messages in thread

* [Qemu-devel] [PATCH v15 01/23] qapi-visit: Add visitor.type classification
  2016-04-28  0:01 [Qemu-devel] [PATCH v15 00/23] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
@ 2016-04-28  0:01 ` Eric Blake
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 02/23] qapi: Guarantee NULL obj on input visitor callback error Eric Blake
                   ` (22 subsequent siblings)
  23 siblings, 0 replies; 49+ messages in thread
From: Eric Blake @ 2016-04-28  0:01 UTC (permalink / raw)
  To: qemu-devel; +Cc: armbru, Michael Roth

We have three classes of QAPI visitors: input, output, and dealloc.
Currently, all implementations of these visitors have one thing in
common based on their visitor type: the implementation used for the
visit_type_enum() callback.  But since we plan to add more such
common behavior, in relation to documenting and further refining
the semantics, it makes more sense to have the visitor
implementations advertise which class they belong to, so the common
qapi-visit-core code can use that information in multiple places.

A later patch will better document the types of visitors directly
in visitor.h.

For this patch, knowing the class of a visitor implementation lets
us make input_type_enum() and output_type_enum() become static
functions, by replacing the callback function Visitor.type_enum()
with the simpler enum member Visitor.type.  Share a common
assertion in qapi-visit-core as part of the refactoring.

Move comments in opts-visitor.c to match the refactored layout.

Signed-off-by: Eric Blake <eblake@redhat.com>

---
v15: comment improvements
v14: no change
v13: no change
v12: new patch
---
 include/qapi/visitor.h       | 11 +++++++++++
 include/qapi/visitor-impl.h  | 23 ++++++++++++++---------
 qapi/qapi-visit-core.c       | 28 +++++++++++++++-------------
 qapi/opts-visitor.c          | 17 +++++++----------
 qapi/qapi-dealloc-visitor.c  |  7 +------
 qapi/qmp-input-visitor.c     |  2 +-
 qapi/qmp-output-visitor.c    |  2 +-
 qapi/string-input-visitor.c  |  2 +-
 qapi/string-output-visitor.c |  2 +-
 9 files changed, 52 insertions(+), 42 deletions(-)

diff --git a/include/qapi/visitor.h b/include/qapi/visitor.h
index 9a8d010..690589f 100644
--- a/include/qapi/visitor.h
+++ b/include/qapi/visitor.h
@@ -78,8 +78,19 @@ void visit_end_alternate(Visitor *v);
  */
 bool visit_optional(Visitor *v, const char *name, bool *present);

+/*
+ * Visit an enum value.
+ *
+ * @strings expresses the mapping between C enum values and QAPI enum
+ * names; it should be the ENUM_lookup array from visit-types.h.
+ *
+ * May call visit_type_str() under the hood, and the enum visit may
+ * fail even if the corresponding string visit succeeded; this implies
+ * that visit_type_str() must have no unwelcome side effects.
+ */
 void visit_type_enum(Visitor *v, const char *name, int *obj,
                      const char *const strings[], Error **errp);
+
 void visit_type_int(Visitor *v, const char *name, int64_t *obj, Error **errp);
 void visit_type_uint8(Visitor *v, const char *name, uint8_t *obj,
                       Error **errp);
diff --git a/include/qapi/visitor-impl.h b/include/qapi/visitor-impl.h
index 2bd8f29..51c338a 100644
--- a/include/qapi/visitor-impl.h
+++ b/include/qapi/visitor-impl.h
@@ -14,6 +14,17 @@

 #include "qapi/visitor.h"

+/*
+ * There are three classes of visitors; setting the class determines
+ * how QAPI enums are visited, as well as what additional restrictions
+ * can be asserted.
+ */
+typedef enum VisitorType {
+    VISITOR_INPUT,
+    VISITOR_OUTPUT,
+    VISITOR_DEALLOC,
+} VisitorType;
+
 struct Visitor
 {
     /* Must be set */
@@ -36,10 +47,6 @@ struct Visitor
     void (*end_alternate)(Visitor *v);

     /* Must be set. */
-    void (*type_enum)(Visitor *v, const char *name, int *obj,
-                      const char *const strings[], Error **errp);
-
-    /* Must be set. */
     void (*type_int64)(Visitor *v, const char *name, int64_t *obj,
                        Error **errp);
     /* Must be set. */
@@ -58,11 +65,9 @@ struct Visitor

     /* May be NULL; most useful for input visitors. */
     void (*optional)(Visitor *v, const char *name, bool *present);
+
+    /* Must be set */
+    VisitorType type;
 };

-void input_type_enum(Visitor *v, const char *name, int *obj,
-                     const char *const strings[], Error **errp);
-void output_type_enum(Visitor *v, const char *name, int *obj,
-                      const char *const strings[], Error **errp);
-
 #endif
diff --git a/qapi/qapi-visit-core.c b/qapi/qapi-visit-core.c
index fa680c9..3cd7edc 100644
--- a/qapi/qapi-visit-core.c
+++ b/qapi/qapi-visit-core.c
@@ -72,12 +72,6 @@ bool visit_optional(Visitor *v, const char *name, bool *present)
     return *present;
 }

-void visit_type_enum(Visitor *v, const char *name, int *obj,
-                     const char *const strings[], Error **errp)
-{
-    v->type_enum(v, name, obj, strings, errp);
-}
-
 void visit_type_int(Visitor *v, const char *name, int64_t *obj, Error **errp)
 {
     v->type_int64(v, name, obj, errp);
@@ -208,14 +202,13 @@ void visit_type_any(Visitor *v, const char *name, QObject **obj, Error **errp)
     v->type_any(v, name, obj, errp);
 }

-void output_type_enum(Visitor *v, const char *name, int *obj,
-                      const char *const strings[], Error **errp)
+static void output_type_enum(Visitor *v, const char *name, int *obj,
+                             const char *const strings[], Error **errp)
 {
     int i = 0;
     int value = *obj;
     char *enum_str;

-    assert(strings);
     while (strings[i++] != NULL);
     if (value < 0 || value >= i - 1) {
         error_setg(errp, QERR_INVALID_PARAMETER, name ? name : "null");
@@ -226,15 +219,13 @@ void output_type_enum(Visitor *v, const char *name, int *obj,
     visit_type_str(v, name, &enum_str, errp);
 }

-void input_type_enum(Visitor *v, const char *name, int *obj,
-                     const char *const strings[], Error **errp)
+static void input_type_enum(Visitor *v, const char *name, int *obj,
+                            const char *const strings[], Error **errp)
 {
     Error *local_err = NULL;
     int64_t value = 0;
     char *enum_str;

-    assert(strings);
-
     visit_type_str(v, name, &enum_str, &local_err);
     if (local_err) {
         error_propagate(errp, local_err);
@@ -257,3 +248,14 @@ void input_type_enum(Visitor *v, const char *name, int *obj,
     g_free(enum_str);
     *obj = value;
 }
+
+void visit_type_enum(Visitor *v, const char *name, int *obj,
+                     const char *const strings[], Error **errp)
+{
+    assert(strings);
+    if (v->type == VISITOR_INPUT) {
+        input_type_enum(v, name, obj, strings, errp);
+    } else if (v->type == VISITOR_OUTPUT) {
+        output_type_enum(v, name, obj, strings, errp);
+    }
+}
diff --git a/qapi/opts-visitor.c b/qapi/opts-visitor.c
index 602f260..66aeaed 100644
--- a/qapi/opts-visitor.c
+++ b/qapi/opts-visitor.c
@@ -317,6 +317,11 @@ opts_type_str(Visitor *v, const char *name, char **obj, Error **errp)
         return;
     }
     *obj = g_strdup(opt->str ? opt->str : "");
+    /* Note that we consume a string even if this is called as part of
+     * an enum visit that later fails because the string is not a
+     * valid enum value; this is harmless because tracking what gets
+     * consumed only matters to visit_end_struct() as the final error
+     * check if there were no other failures during the visit.  */
     processed(ov, name);
 }

@@ -507,6 +512,8 @@ opts_visitor_new(const QemuOpts *opts)

     ov = g_malloc0(sizeof *ov);

+    ov->visitor.type = VISITOR_INPUT;
+
     ov->visitor.start_struct = &opts_start_struct;
     ov->visitor.end_struct   = &opts_end_struct;

@@ -514,16 +521,6 @@ opts_visitor_new(const QemuOpts *opts)
     ov->visitor.next_list  = &opts_next_list;
     ov->visitor.end_list   = &opts_end_list;

-    /* input_type_enum() covers both "normal" enums and union discriminators.
-     * The union discriminator field is always generated as "type"; it should
-     * match the "type" QemuOpt child of any QemuOpts.
-     *
-     * input_type_enum() will remove the looked-up key from the
-     * "unprocessed_opts" hash even if the lookup fails, because the removal is
-     * done earlier in opts_type_str(). This should be harmless.
-     */
-    ov->visitor.type_enum = &input_type_enum;
-
     ov->visitor.type_int64  = &opts_type_int64;
     ov->visitor.type_uint64 = &opts_type_uint64;
     ov->visitor.type_size   = &opts_type_size;
diff --git a/qapi/qapi-dealloc-visitor.c b/qapi/qapi-dealloc-visitor.c
index 6922179..c19a459 100644
--- a/qapi/qapi-dealloc-visitor.c
+++ b/qapi/qapi-dealloc-visitor.c
@@ -163,11 +163,6 @@ static void qapi_dealloc_type_anything(Visitor *v, const char *name,
     }
 }

-static void qapi_dealloc_type_enum(Visitor *v, const char *name, int *obj,
-                                   const char * const strings[], Error **errp)
-{
-}
-
 Visitor *qapi_dealloc_get_visitor(QapiDeallocVisitor *v)
 {
     return &v->visitor;
@@ -184,6 +179,7 @@ QapiDeallocVisitor *qapi_dealloc_visitor_new(void)

     v = g_malloc0(sizeof(*v));

+    v->visitor.type = VISITOR_DEALLOC;
     v->visitor.start_struct = qapi_dealloc_start_struct;
     v->visitor.end_struct = qapi_dealloc_end_struct;
     v->visitor.start_alternate = qapi_dealloc_start_alternate;
@@ -191,7 +187,6 @@ QapiDeallocVisitor *qapi_dealloc_visitor_new(void)
     v->visitor.start_list = qapi_dealloc_start_list;
     v->visitor.next_list = qapi_dealloc_next_list;
     v->visitor.end_list = qapi_dealloc_end_list;
-    v->visitor.type_enum = qapi_dealloc_type_enum;
     v->visitor.type_int64 = qapi_dealloc_type_int64;
     v->visitor.type_uint64 = qapi_dealloc_type_uint64;
     v->visitor.type_bool = qapi_dealloc_type_bool;
diff --git a/qapi/qmp-input-visitor.c b/qapi/qmp-input-visitor.c
index 7cd1b77..02d4233 100644
--- a/qapi/qmp-input-visitor.c
+++ b/qapi/qmp-input-visitor.c
@@ -339,13 +339,13 @@ QmpInputVisitor *qmp_input_visitor_new(QObject *obj)

     v = g_malloc0(sizeof(*v));

+    v->visitor.type = VISITOR_INPUT;
     v->visitor.start_struct = qmp_input_start_struct;
     v->visitor.end_struct = qmp_input_end_struct;
     v->visitor.start_list = qmp_input_start_list;
     v->visitor.next_list = qmp_input_next_list;
     v->visitor.end_list = qmp_input_end_list;
     v->visitor.start_alternate = qmp_input_start_alternate;
-    v->visitor.type_enum = input_type_enum;
     v->visitor.type_int64 = qmp_input_type_int64;
     v->visitor.type_uint64 = qmp_input_type_uint64;
     v->visitor.type_bool = qmp_input_type_bool;
diff --git a/qapi/qmp-output-visitor.c b/qapi/qmp-output-visitor.c
index d44c676..1f2a7ba 100644
--- a/qapi/qmp-output-visitor.c
+++ b/qapi/qmp-output-visitor.c
@@ -234,12 +234,12 @@ QmpOutputVisitor *qmp_output_visitor_new(void)

     v = g_malloc0(sizeof(*v));

+    v->visitor.type = VISITOR_OUTPUT;
     v->visitor.start_struct = qmp_output_start_struct;
     v->visitor.end_struct = qmp_output_end_struct;
     v->visitor.start_list = qmp_output_start_list;
     v->visitor.next_list = qmp_output_next_list;
     v->visitor.end_list = qmp_output_end_list;
-    v->visitor.type_enum = output_type_enum;
     v->visitor.type_int64 = qmp_output_type_int64;
     v->visitor.type_uint64 = qmp_output_type_uint64;
     v->visitor.type_bool = qmp_output_type_bool;
diff --git a/qapi/string-input-visitor.c b/qapi/string-input-visitor.c
index ab12953..d604575 100644
--- a/qapi/string-input-visitor.c
+++ b/qapi/string-input-visitor.c
@@ -348,7 +348,7 @@ StringInputVisitor *string_input_visitor_new(const char *str)

     v = g_malloc0(sizeof(*v));

-    v->visitor.type_enum = input_type_enum;
+    v->visitor.type = VISITOR_INPUT;
     v->visitor.type_int64 = parse_type_int64;
     v->visitor.type_uint64 = parse_type_uint64;
     v->visitor.type_size = parse_type_size;
diff --git a/qapi/string-output-visitor.c b/qapi/string-output-visitor.c
index c2e5c5b..0d44d7e 100644
--- a/qapi/string-output-visitor.c
+++ b/qapi/string-output-visitor.c
@@ -351,7 +351,7 @@ StringOutputVisitor *string_output_visitor_new(bool human)

     v->string = g_string_new(NULL);
     v->human = human;
-    v->visitor.type_enum = output_type_enum;
+    v->visitor.type = VISITOR_OUTPUT;
     v->visitor.type_int64 = print_type_int64;
     v->visitor.type_uint64 = print_type_uint64;
     v->visitor.type_size = print_type_size;
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [Qemu-devel] [PATCH v15 02/23] qapi: Guarantee NULL obj on input visitor callback error
  2016-04-28  0:01 [Qemu-devel] [PATCH v15 00/23] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 01/23] qapi-visit: Add visitor.type classification Eric Blake
@ 2016-04-28  0:01 ` Eric Blake
  2016-04-28 12:24   ` Markus Armbruster
  2016-04-28 16:02   ` [Qemu-devel] [PATCH v15 02A/23] fixup! " Eric Blake
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 03/23] qmp: Drop dead command->type Eric Blake
                   ` (21 subsequent siblings)
  23 siblings, 2 replies; 49+ messages in thread
From: Eric Blake @ 2016-04-28  0:01 UTC (permalink / raw)
  To: qemu-devel; +Cc: armbru, Michael Roth

Our existing input visitors were not very consistent on errors
in a function taking 'TYPE **obj' (that is, start_struct(),
start_alternate(), next_list(), type_str(), and type_any()).
While all of them set '*obj' to allocated storage on success,
it was not obvious whether '*obj' was guaranteed safe on failure,
or whether it was left uninitialized.  But a future patch wants
to guarantee that visit_type_FOO() does not leak a partially-
constructed obj back to the caller; it is easier to implement
this if we can reliably state that '*obj' is assigned on exit,
even on failures.  Add assertions to enforce it.

The opts-visitor start_struct() doesn't set an error, but it
also was doing a weird check for 0 size; all callers pass in
non-zero size if obj is non-NULL.

The testsuite has at least one spot where we no longer need
to pre-initialize a variable prior to a visit; valgrind confirms
that the test is still fine with the cleanup.

A later patch will document the design constraint implemented
here.

Signed-off-by: Eric Blake <eblake@redhat.com>

---
v15: enhance commit message, hoist assertions from later in series
v14: no change
v13: no change
v12: new patch
---
 qapi/qapi-visit-core.c        | 34 ++++++++++++++++++++++++++++++----
 qapi/opts-visitor.c           |  3 ++-
 qapi/qmp-input-visitor.c      |  4 ++++
 qapi/string-input-visitor.c   |  1 +
 tests/test-qmp-input-strict.c |  2 +-
 5 files changed, 38 insertions(+), 6 deletions(-)

diff --git a/qapi/qapi-visit-core.c b/qapi/qapi-visit-core.c
index 3cd7edc..3a131ce 100644
--- a/qapi/qapi-visit-core.c
+++ b/qapi/qapi-visit-core.c
@@ -23,7 +23,13 @@
 void visit_start_struct(Visitor *v, const char *name, void **obj,
                         size_t size, Error **errp)
 {
-    v->start_struct(v, name, obj, size, errp);
+    Error *err = NULL;
+
+    v->start_struct(v, name, obj, size, &err);
+    if (obj && v->type == VISITOR_INPUT) {
+        assert(err || *obj);
+    }
+    error_propagate(errp, err);
 }

 void visit_end_struct(Visitor *v, Error **errp)
@@ -51,9 +57,15 @@ void visit_start_alternate(Visitor *v, const char *name,
                            GenericAlternate **obj, size_t size,
                            bool promote_int, Error **errp)
 {
+    Error *err = NULL;
+
     assert(obj && size >= sizeof(GenericAlternate));
     if (v->start_alternate) {
-        v->start_alternate(v, name, obj, size, promote_int, errp);
+        v->start_alternate(v, name, obj, size, promote_int, &err);
+        if (v->type == VISITOR_INPUT) {
+            assert(err || *obj);
+        }
+        error_propagate(errp, err);
     }
 }

@@ -188,7 +200,14 @@ void visit_type_bool(Visitor *v, const char *name, bool *obj, Error **errp)

 void visit_type_str(Visitor *v, const char *name, char **obj, Error **errp)
 {
-    v->type_str(v, name, obj, errp);
+    Error *err = NULL;
+
+    assert(obj);
+    v->type_str(v, name, obj, &err);
+    if (v->type == VISITOR_INPUT) {
+        assert(err || *obj);
+    }
+    error_propagate(errp, err);
 }

 void visit_type_number(Visitor *v, const char *name, double *obj,
@@ -199,7 +218,14 @@ void visit_type_number(Visitor *v, const char *name, double *obj,

 void visit_type_any(Visitor *v, const char *name, QObject **obj, Error **errp)
 {
-    v->type_any(v, name, obj, errp);
+    Error *err = NULL;
+
+    assert(obj);
+    v->type_any(v, name, obj, &err);
+    if (v->type == VISITOR_INPUT) {
+        assert(err || *obj);
+    }
+    error_propagate(errp, err);
 }

 static void output_type_enum(Visitor *v, const char *name, int *obj,
diff --git a/qapi/opts-visitor.c b/qapi/opts-visitor.c
index 66aeaed..4cb6436 100644
--- a/qapi/opts-visitor.c
+++ b/qapi/opts-visitor.c
@@ -133,7 +133,7 @@ opts_start_struct(Visitor *v, const char *name, void **obj,
     const QemuOpt *opt;

     if (obj) {
-        *obj = g_malloc0(size > 0 ? size : 1);
+        *obj = g_malloc0(size);
     }
     if (ov->depth++ > 0) {
         return;
@@ -314,6 +314,7 @@ opts_type_str(Visitor *v, const char *name, char **obj, Error **errp)

     opt = lookup_scalar(ov, name, errp);
     if (!opt) {
+        *obj = NULL;
         return;
     }
     *obj = g_strdup(opt->str ? opt->str : "");
diff --git a/qapi/qmp-input-visitor.c b/qapi/qmp-input-visitor.c
index 02d4233..77cce8b 100644
--- a/qapi/qmp-input-visitor.c
+++ b/qapi/qmp-input-visitor.c
@@ -120,6 +120,9 @@ static void qmp_input_start_struct(Visitor *v, const char *name, void **obj,
     QObject *qobj = qmp_input_get_object(qiv, name, true);
     Error *err = NULL;

+    if (obj) {
+        *obj = NULL;
+    }
     if (!qobj || qobject_type(qobj) != QTYPE_QDICT) {
         error_setg(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null",
                    "QDict");
@@ -267,6 +270,7 @@ static void qmp_input_type_str(Visitor *v, const char *name, char **obj,
     QString *qstr = qobject_to_qstring(qmp_input_get_object(qiv, name, true));

     if (!qstr) {
+        *obj = NULL;
         error_setg(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null",
                    "string");
         return;
diff --git a/qapi/string-input-visitor.c b/qapi/string-input-visitor.c
index d604575..797973a 100644
--- a/qapi/string-input-visitor.c
+++ b/qapi/string-input-visitor.c
@@ -293,6 +293,7 @@ static void parse_type_str(Visitor *v, const char *name, char **obj,
     if (siv->string) {
         *obj = g_strdup(siv->string);
     } else {
+        *obj = NULL;
         error_setg(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null",
                    "string");
     }
diff --git a/tests/test-qmp-input-strict.c b/tests/test-qmp-input-strict.c
index d71727e..d5f80ec 100644
--- a/tests/test-qmp-input-strict.c
+++ b/tests/test-qmp-input-strict.c
@@ -263,7 +263,7 @@ static void test_validate_fail_union_flat_no_discrim(TestInputVisitorData *data,
 static void test_validate_fail_alternate(TestInputVisitorData *data,
                                          const void *unused)
 {
-    UserDefAlternate *tmp = NULL;
+    UserDefAlternate *tmp;
     Visitor *v;
     Error *err = NULL;

-- 
2.5.5

^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [Qemu-devel] [PATCH v15 03/23] qmp: Drop dead command->type
  2016-04-28  0:01 [Qemu-devel] [PATCH v15 00/23] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 01/23] qapi-visit: Add visitor.type classification Eric Blake
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 02/23] qapi: Guarantee NULL obj on input visitor callback error Eric Blake
@ 2016-04-28  0:01 ` Eric Blake
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 04/23] qmp-input: Clean up stack handling Eric Blake
                   ` (20 subsequent siblings)
  23 siblings, 0 replies; 49+ messages in thread
From: Eric Blake @ 2016-04-28  0:01 UTC (permalink / raw)
  To: qemu-devel; +Cc: armbru, Michael Roth

Ever since QMP was first added back in commit 43c20a43, we have
never had any QmpCommandType other than QCT_NORMAL.  It's
pointless to carry around the cruft.

Signed-off-by: Eric Blake <eblake@redhat.com>

---
v15: no change
v14: no change
v13: no change
v12: new patch
---
 include/qapi/qmp/dispatch.h |  6 ------
 qapi/qmp-dispatch.c         | 18 +++++++-----------
 qapi/qmp-registry.c         |  1 -
 3 files changed, 7 insertions(+), 18 deletions(-)

diff --git a/include/qapi/qmp/dispatch.h b/include/qapi/qmp/dispatch.h
index 4955209..5609946 100644
--- a/include/qapi/qmp/dispatch.h
+++ b/include/qapi/qmp/dispatch.h
@@ -19,11 +19,6 @@

 typedef void (QmpCommandFunc)(QDict *, QObject **, Error **);

-typedef enum QmpCommandType
-{
-    QCT_NORMAL,
-} QmpCommandType;
-
 typedef enum QmpCommandOptions
 {
     QCO_NO_OPTIONS = 0x0,
@@ -33,7 +28,6 @@ typedef enum QmpCommandOptions
 typedef struct QmpCommand
 {
     const char *name;
-    QmpCommandType type;
     QmpCommandFunc *fn;
     QmpCommandOptions options;
     QTAILQ_ENTRY(QmpCommand) node;
diff --git a/qapi/qmp-dispatch.c b/qapi/qmp-dispatch.c
index 510a1ae..08faf85 100644
--- a/qapi/qmp-dispatch.c
+++ b/qapi/qmp-dispatch.c
@@ -94,17 +94,13 @@ static QObject *do_qmp_dispatch(QObject *request, Error **errp)
         QINCREF(args);
     }

-    switch (cmd->type) {
-    case QCT_NORMAL:
-        cmd->fn(args, &ret, &local_err);
-        if (local_err) {
-            error_propagate(errp, local_err);
-        } else if (cmd->options & QCO_NO_SUCCESS_RESP) {
-            g_assert(!ret);
-        } else if (!ret) {
-            ret = QOBJECT(qdict_new());
-        }
-        break;
+    cmd->fn(args, &ret, &local_err);
+    if (local_err) {
+        error_propagate(errp, local_err);
+    } else if (cmd->options & QCO_NO_SUCCESS_RESP) {
+        g_assert(!ret);
+    } else if (!ret) {
+        ret = QOBJECT(qdict_new());
     }

     QDECREF(args);
diff --git a/qapi/qmp-registry.c b/qapi/qmp-registry.c
index 4ebfbcc..4332a68 100644
--- a/qapi/qmp-registry.c
+++ b/qapi/qmp-registry.c
@@ -25,7 +25,6 @@ void qmp_register_command(const char *name, QmpCommandFunc *fn,
     QmpCommand *cmd = g_malloc0(sizeof(*cmd));

     cmd->name = name;
-    cmd->type = QCT_NORMAL;
     cmd->fn = fn;
     cmd->enabled = true;
     cmd->options = options;
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [Qemu-devel] [PATCH v15 04/23] qmp-input: Clean up stack handling
  2016-04-28  0:01 [Qemu-devel] [PATCH v15 00/23] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
                   ` (2 preceding siblings ...)
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 03/23] qmp: Drop dead command->type Eric Blake
@ 2016-04-28  0:01 ` Eric Blake
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 05/23] qapi: Use strict QMP input visitor in more places Eric Blake
                   ` (19 subsequent siblings)
  23 siblings, 0 replies; 49+ messages in thread
From: Eric Blake @ 2016-04-28  0:01 UTC (permalink / raw)
  To: qemu-devel; +Cc: armbru, Michael Roth

Management of the top of stack was a bit verbose; creating a
temporary variable and adding some comments makes the existing
code more legible before the next few patches improve things.
No semantic changes other than asserting that we are always
visiting a QObject, and not a NULL value.  In particular, the
check for 'name && qobject_type(qobj) == QTYPE_QDICT)' is a
bit overkill (a dict visit should always have a name); a later
patch revisits that, while this patch is only changing one
layer of indentation due to dropping 'if (qobj)'.

Signed-off-by: Eric Blake <eblake@redhat.com>

---
v15: comment improvements
v14: no change
v13: no change
v12: new patch
---
 qapi/qmp-input-visitor.c | 51 +++++++++++++++++++++++++++++++++---------------
 1 file changed, 35 insertions(+), 16 deletions(-)

diff --git a/qapi/qmp-input-visitor.c b/qapi/qmp-input-visitor.c
index 77cce8b..8550bc7 100644
--- a/qapi/qmp-input-visitor.c
+++ b/qapi/qmp-input-visitor.c
@@ -25,16 +25,23 @@

 typedef struct StackObject
 {
-    QObject *obj;
-    const QListEntry *entry;
-    GHashTable *h;
+    QObject *obj; /* Object being visited */
+
+    GHashTable *h;           /* If obj is dict: unvisited keys */
+    const QListEntry *entry; /* If obj is list: unvisited tail */
 } StackObject;

 struct QmpInputVisitor
 {
     Visitor visitor;
+
+    /* Stack of objects being visited.  stack[0] is root of visit,
+     * stack[1..] records the nesting of start_struct()/end_struct()
+     * and start_list()/end_list() pairs. */
     StackObject stack[QIV_STACK_SIZE];
     int nb_stack;
+
+    /* True to reject parse in visit_end_struct() if unvisited keys remain. */
     bool strict;
 };

@@ -47,19 +54,29 @@ static QObject *qmp_input_get_object(QmpInputVisitor *qiv,
                                      const char *name,
                                      bool consume)
 {
-    QObject *qobj = qiv->stack[qiv->nb_stack - 1].obj;
+    StackObject *tos = &qiv->stack[qiv->nb_stack - 1];
+    QObject *qobj = tos->obj;

-    if (qobj) {
-        if (name && qobject_type(qobj) == QTYPE_QDICT) {
-            if (qiv->stack[qiv->nb_stack - 1].h && consume) {
-                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);
+    assert(qobj);
+
+    /* If we have a name, and we're in a dictionary, then return that
+     * value. */
+    if (name && qobject_type(qobj) == QTYPE_QDICT) {
+        if (tos->h && consume) {
+            g_hash_table_remove(tos->h, name);
         }
+        return qdict_get(qobject_to_qdict(qobj), name);
     }

+    /* If we are in the middle of a list, then return the next element
+     * of the list. */
+    if (tos->entry) {
+        assert(qobject_type(qobj) == QTYPE_QLIST);
+        return qlist_entry_obj(tos->entry);
+    }
+
+    /* Otherwise, we are at the root of the visit or the start of a
+     * list, and return the object as-is. */
     return qobj;
 }

@@ -72,20 +89,22 @@ static void qdict_add_key(const char *key, QObject *obj, void *opaque)
 static void qmp_input_push(QmpInputVisitor *qiv, QObject *obj, Error **errp)
 {
     GHashTable *h;
+    StackObject *tos = &qiv->stack[qiv->nb_stack];

+    assert(obj);
     if (qiv->nb_stack >= QIV_STACK_SIZE) {
         error_setg(errp, "An internal buffer overran");
         return;
     }

-    qiv->stack[qiv->nb_stack].obj = obj;
-    qiv->stack[qiv->nb_stack].entry = NULL;
-    qiv->stack[qiv->nb_stack].h = NULL;
+    tos->obj = obj;
+    tos->entry = NULL;
+    tos->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;
+        tos->h = h;
     }

     qiv->nb_stack++;
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [Qemu-devel] [PATCH v15 05/23] qapi: Use strict QMP input visitor in more places
  2016-04-28  0:01 [Qemu-devel] [PATCH v15 00/23] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
                   ` (3 preceding siblings ...)
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 04/23] qmp-input: Clean up stack handling Eric Blake
@ 2016-04-28  0:01 ` Eric Blake
  2016-04-28 13:06   ` Markus Armbruster
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 06/23] qmp-input: Don't consume input when checking has_member Eric Blake
                   ` (18 subsequent siblings)
  23 siblings, 1 reply; 49+ messages in thread
From: Eric Blake @ 2016-04-28  0:01 UTC (permalink / raw)
  To: qemu-devel
  Cc: armbru, Michael Roth, Andreas Färber, Daniel P. Berrange,
	Gerd Hoffmann, Paolo Bonzini

Rather than having two separate ways to create a QMP input
visitor, where the safer approach has the more verbose name,
it is better to consolidate things into a single function
where the caller must explicitly choose whether to be strict
or to ignore excess input.  Use strict mode in more places of
internal code (such as when cloning a QAPI struct in
util/socket.c, where the QObject better not have excess
input since it was just generated by qmp-output), while
documenting in user-facing code a question of whether we
should change our policy about ignoring excess input.

In the case of qmp_object_add(), we intentionally switch to a
strict visitor; this matches the fact that the code for
user_creatable_add_type() is shared by both qmp_object_add()
(QMP input visitor) and by user_creatable_add_opts() (QemuOpts
visitor); the latter is always strict, so our usage in the
former should also be strict, so that both visits will
equally diagnose any excess input in a nested dict.  But in
practice, we don't really have any -object where the
properties are a nested dict, and excess input at the top
level is already caught earlier by object_property_set() on
an unknown key, whether from QemuOpts:

$ ./x86_64-softmmu/qemu-system-x86_64 -nographic -nodefaults -qmp stdio -object secret,id=sec0,data=letmein,format=raw,foo=bar
qemu-system-x86_64: Property '.foo' not found

or from QMP:

$ ./x86_64-softmmu/qemu-system-x86_64 -nographic -nodefaults -qmp stdio
{"QMP": {"version": {"qemu": {"micro": 93, "minor": 5, "major": 2}, "package": ""}, "capabilities": []}}
{"execute":"qmp_capabilities"}
{"return": {}}
{"execute":"object-add","arguments":{"qom-type":"secret","id":"sec0","props":{"format":"raw","data":"letmein","a":1}}}
{"error": {"class": "GenericError", "desc": "Property '.a' not found"}}

Signed-off-by: Eric Blake <eblake@redhat.com>

---
v15: new patch
---
 scripts/qapi-commands.py           |  2 +-
 include/qapi/qmp-input-visitor.h   |  9 +++++++--
 qapi/qmp-input-visitor.c           | 13 ++-----------
 qmp.c                              |  2 +-
 qom/qom-qobject.c                  |  3 ++-
 replay/replay-input.c              |  2 +-
 tests/test-qmp-commands.c          |  2 +-
 tests/test-qmp-input-strict.c      |  2 +-
 tests/test-qmp-input-visitor.c     |  2 +-
 tests/test-visitor-serialization.c |  2 +-
 util/qemu-sockets.c                |  2 +-
 docs/qapi-code-gen.txt             |  2 +-
 12 files changed, 20 insertions(+), 23 deletions(-)

diff --git a/scripts/qapi-commands.py b/scripts/qapi-commands.py
index b570069..6261e44 100644
--- a/scripts/qapi-commands.py
+++ b/scripts/qapi-commands.py
@@ -115,7 +115,7 @@ def gen_marshal(name, arg_type, ret_type):

     if arg_type and arg_type.members:
         ret += mcgen('''
-    QmpInputVisitor *qiv = qmp_input_visitor_new_strict(QOBJECT(args));
+    QmpInputVisitor *qiv = qmp_input_visitor_new(QOBJECT(args), true);
     QapiDeallocVisitor *qdv;
     Visitor *v;
     %(c_name)s arg = {0};
diff --git a/include/qapi/qmp-input-visitor.h b/include/qapi/qmp-input-visitor.h
index 3ed499c..b0624d8 100644
--- a/include/qapi/qmp-input-visitor.h
+++ b/include/qapi/qmp-input-visitor.h
@@ -19,8 +19,13 @@

 typedef struct QmpInputVisitor QmpInputVisitor;

-QmpInputVisitor *qmp_input_visitor_new(QObject *obj);
-QmpInputVisitor *qmp_input_visitor_new_strict(QObject *obj);
+/*
+ * Return a new input visitor that converts QMP to QAPI.
+ *
+ * Set @strict to reject a parse that doesn't consume all keys of a
+ * dictionary; otherwise excess input is ignored.
+ */
+QmpInputVisitor *qmp_input_visitor_new(QObject *obj, bool strict);

 void qmp_input_visitor_cleanup(QmpInputVisitor *v);

diff --git a/qapi/qmp-input-visitor.c b/qapi/qmp-input-visitor.c
index 8550bc7..c3c3271 100644
--- a/qapi/qmp-input-visitor.c
+++ b/qapi/qmp-input-visitor.c
@@ -356,7 +356,7 @@ void qmp_input_visitor_cleanup(QmpInputVisitor *v)
     g_free(v);
 }

-QmpInputVisitor *qmp_input_visitor_new(QObject *obj)
+QmpInputVisitor *qmp_input_visitor_new(QObject *obj, bool strict)
 {
     QmpInputVisitor *v;

@@ -376,19 +376,10 @@ QmpInputVisitor *qmp_input_visitor_new(QObject *obj)
     v->visitor.type_number = qmp_input_type_number;
     v->visitor.type_any = qmp_input_type_any;
     v->visitor.optional = qmp_input_optional;
+    v->strict = strict;

     qmp_input_push(v, obj, NULL);
     qobject_incref(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/qmp.c b/qmp.c
index 9d0953b..e784a67 100644
--- a/qmp.c
+++ b/qmp.c
@@ -663,7 +663,7 @@ void qmp_object_add(const char *type, const char *id,
         }
     }

-    qiv = qmp_input_visitor_new(props);
+    qiv = qmp_input_visitor_new(props, true);
     obj = user_creatable_add_type(type, id, pdict,
                                   qmp_input_get_visitor(qiv), errp);
     qmp_input_visitor_cleanup(qiv);
diff --git a/qom/qom-qobject.c b/qom/qom-qobject.c
index e6b17c1..b66088d 100644
--- a/qom/qom-qobject.c
+++ b/qom/qom-qobject.c
@@ -22,7 +22,8 @@ void object_property_set_qobject(Object *obj, QObject *value,
                                  const char *name, Error **errp)
 {
     QmpInputVisitor *qiv;
-    qiv = qmp_input_visitor_new(value);
+    /* TODO: Should we reject, rather than ignore, excess input? */
+    qiv = qmp_input_visitor_new(value, false);
     object_property_set(obj, qmp_input_get_visitor(qiv), name, errp);

     qmp_input_visitor_cleanup(qiv);
diff --git a/replay/replay-input.c b/replay/replay-input.c
index 06babe0..03e99d5 100644
--- a/replay/replay-input.c
+++ b/replay/replay-input.c
@@ -37,7 +37,7 @@ static InputEvent *qapi_clone_InputEvent(InputEvent *src)
         return NULL;
     }

-    qiv = qmp_input_visitor_new(obj);
+    qiv = qmp_input_visitor_new(obj, true);
     iv = qmp_input_get_visitor(qiv);
     visit_type_InputEvent(iv, NULL, &dst, &error_abort);
     qmp_input_visitor_cleanup(qiv);
diff --git a/tests/test-qmp-commands.c b/tests/test-qmp-commands.c
index 14a9ebb..597fb44 100644
--- a/tests/test-qmp-commands.c
+++ b/tests/test-qmp-commands.c
@@ -222,7 +222,7 @@ static void test_dealloc_partial(void)
         ud2_dict = qdict_new();
         qdict_put_obj(ud2_dict, "string0", QOBJECT(qstring_from_str(text)));

-        qiv = qmp_input_visitor_new(QOBJECT(ud2_dict));
+        qiv = qmp_input_visitor_new(QOBJECT(ud2_dict), true);
         visit_type_UserDefTwo(qmp_input_get_visitor(qiv), NULL, &ud2, &err);
         qmp_input_visitor_cleanup(qiv);
         QDECREF(ud2_dict);
diff --git a/tests/test-qmp-input-strict.c b/tests/test-qmp-input-strict.c
index d5f80ec..2b053a2 100644
--- a/tests/test-qmp-input-strict.c
+++ b/tests/test-qmp-input-strict.c
@@ -55,7 +55,7 @@ static Visitor *validate_test_init_internal(TestInputVisitorData *data,
     data->obj = qobject_from_jsonv(json_string, ap);
     g_assert(data->obj);

-    data->qiv = qmp_input_visitor_new_strict(data->obj);
+    data->qiv = qmp_input_visitor_new(data->obj, true);
     g_assert(data->qiv);

     v = qmp_input_get_visitor(data->qiv);
diff --git a/tests/test-qmp-input-visitor.c b/tests/test-qmp-input-visitor.c
index 80527eb..c039806 100644
--- a/tests/test-qmp-input-visitor.c
+++ b/tests/test-qmp-input-visitor.c
@@ -51,7 +51,7 @@ static Visitor *visitor_input_test_init_internal(TestInputVisitorData *data,
     data->obj = qobject_from_jsonv(json_string, ap);
     g_assert(data->obj);

-    data->qiv = qmp_input_visitor_new(data->obj);
+    data->qiv = qmp_input_visitor_new(data->obj, false);
     g_assert(data->qiv);

     v = qmp_input_get_visitor(data->qiv);
diff --git a/tests/test-visitor-serialization.c b/tests/test-visitor-serialization.c
index 9adbc30..7b14b5a 100644
--- a/tests/test-visitor-serialization.c
+++ b/tests/test-visitor-serialization.c
@@ -1038,7 +1038,7 @@ static void qmp_deserialize(void **native_out, void *datap,
     obj = qobject_from_json(qstring_get_str(output_json));

     QDECREF(output_json);
-    d->qiv = qmp_input_visitor_new(obj);
+    d->qiv = qmp_input_visitor_new(obj, true);
     qobject_decref(obj_orig);
     qobject_decref(obj);
     visit(qmp_input_get_visitor(d->qiv), native_out, errp);
diff --git a/util/qemu-sockets.c b/util/qemu-sockets.c
index 0d53691..2a2c524 100644
--- a/util/qemu-sockets.c
+++ b/util/qemu-sockets.c
@@ -1145,7 +1145,7 @@ void qapi_copy_SocketAddress(SocketAddress **p_dest,
         return;
     }

-    qiv = qmp_input_visitor_new(obj);
+    qiv = qmp_input_visitor_new(obj, true);
     iv = qmp_input_get_visitor(qiv);
     visit_type_SocketAddress(iv, NULL, p_dest, &error_abort);
     qmp_input_visitor_cleanup(qiv);
diff --git a/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt
index 0e4baff..4a917f9 100644
--- a/docs/qapi-code-gen.txt
+++ b/docs/qapi-code-gen.txt
@@ -996,7 +996,7 @@ Example:
     {
         Error *err = NULL;
         UserDefOne *retval;
-        QmpInputVisitor *qiv = qmp_input_visitor_new_strict(QOBJECT(args));
+        QmpInputVisitor *qiv = qmp_input_visitor_new(QOBJECT(args), true);
         QapiDeallocVisitor *qdv;
         Visitor *v;
         UserDefOneList *arg1 = NULL;
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [Qemu-devel] [PATCH v15 06/23] qmp-input: Don't consume input when checking has_member
  2016-04-28  0:01 [Qemu-devel] [PATCH v15 00/23] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
                   ` (4 preceding siblings ...)
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 05/23] qapi: Use strict QMP input visitor in more places Eric Blake
@ 2016-04-28  0:01 ` Eric Blake
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 07/23] qapi-commands: Wrap argument visit in visit_start_struct Eric Blake
                   ` (17 subsequent siblings)
  23 siblings, 0 replies; 49+ messages in thread
From: Eric Blake @ 2016-04-28  0:01 UTC (permalink / raw)
  To: qemu-devel; +Cc: armbru, Michael Roth

Commit e8316d7 mistakenly passed consume=true within
qmp_input_optional() when checking if an optional member was
present, but the mistake was silently ignored since the code
happily let us extract a member more than once.  Fix
qmp_input_optional() to not consume anything, then tighten up
the input visitor to ensure that a member is consumed exactly
once (all generated code follows this pattern; and the new
assert will catch any hand-written code that tries to visit
the same key more than once).

Signed-off-by: Eric Blake <eblake@redhat.com>

---
v15: change variable name, improve commit message
v14: no change
v13: no change
v12: new patch
---
 qapi/qmp-input-visitor.c | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/qapi/qmp-input-visitor.c b/qapi/qmp-input-visitor.c
index c3c3271..cae6387 100644
--- a/qapi/qmp-input-visitor.c
+++ b/qapi/qmp-input-visitor.c
@@ -56,16 +56,19 @@ static QObject *qmp_input_get_object(QmpInputVisitor *qiv,
 {
     StackObject *tos = &qiv->stack[qiv->nb_stack - 1];
     QObject *qobj = tos->obj;
+    QObject *ret;

     assert(qobj);

     /* If we have a name, and we're in a dictionary, then return that
      * value. */
     if (name && qobject_type(qobj) == QTYPE_QDICT) {
-        if (tos->h && consume) {
-            g_hash_table_remove(tos->h, name);
+        ret = qdict_get(qobject_to_qdict(qobj), name);
+        if (tos->h && consume && ret) {
+            bool removed = g_hash_table_remove(tos->h, name);
+            assert(removed);
         }
-        return qdict_get(qobject_to_qdict(qobj), name);
+        return ret;
     }

     /* If we are in the middle of a list, then return the next element
@@ -335,7 +338,7 @@ static void qmp_input_type_any(Visitor *v, const char *name, QObject **obj,
 static void qmp_input_optional(Visitor *v, const char *name, bool *present)
 {
     QmpInputVisitor *qiv = to_qiv(v);
-    QObject *qobj = qmp_input_get_object(qiv, name, true);
+    QObject *qobj = qmp_input_get_object(qiv, name, false);

     if (!qobj) {
         *present = false;
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [Qemu-devel] [PATCH v15 07/23] qapi-commands: Wrap argument visit in visit_start_struct
  2016-04-28  0:01 [Qemu-devel] [PATCH v15 00/23] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
                   ` (5 preceding siblings ...)
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 06/23] qmp-input: Don't consume input when checking has_member Eric Blake
@ 2016-04-28  0:01 ` Eric Blake
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 08/23] monitor: Let generated code validate arguments Eric Blake
                   ` (16 subsequent siblings)
  23 siblings, 0 replies; 49+ messages in thread
From: Eric Blake @ 2016-04-28  0:01 UTC (permalink / raw)
  To: qemu-devel; +Cc: armbru, Michael Roth

The qmp-input visitor was allowing callers to play rather fast
and loose: when visiting a QDict, you could grab members of the
root dictionary without first pushing into the dict; among the
culprit callers was the generated marshal code on the 'arguments'
dictionary of a QMP command.  But we are about to tighten the
input visitor, at which point the generated marshal code MUST
follow the same paradigms as everyone else, of pushing into the
struct before grabbing its keys.

Generated code grows as follows:

|@@ -515,7 +641,12 @@ void qmp_marshal_blockdev_backup(QDict *
|     BlockdevBackup arg = {0};
|
|     v = qmp_input_get_visitor(qiv);
|+    visit_start_struct(v, NULL, NULL, 0, &err);
|+    if (err) {
|+        goto out;
|+    }
|     visit_type_BlockdevBackup_members(v, &arg, &err);
|+    visit_end_struct(v, err ? NULL : &err);
|     if (err) {
|         goto out;
|     }
|@@ -527,7 +715,9 @@ out:
|     qmp_input_visitor_cleanup(qiv);
|     qdv = qapi_dealloc_visitor_new();
|     v = qapi_dealloc_get_visitor(qdv);
|+    visit_start_struct(v, NULL, NULL, 0, NULL);
|     visit_type_BlockdevBackup_members(v, &arg, NULL);
|+    visit_end_struct(v, NULL);
|     qapi_dealloc_visitor_cleanup(qdv);
| }

The use of 'err ? NULL : &err' is temporary; a later patch will
clean that up when it splits visit_end_struct().

Prior to this patch, the fact that there was no final
visit_end_struct() meant that even though we are using a strict
input visit, the marshalling code was not detecting excess input
at the top level (only in nested levels).  Fortunately, we have
code in monitor.c:qmp_check_client_args() that also checks for
no excess arguments at the top level.  But as the generated code
is more compact than the manual check, a later patch will clean
up monitor.c to drop the redundancy added here.

Signed-off-by: Eric Blake <eblake@redhat.com>

---
v15: hoist earlier in series, improve commit message, update docs
v14: rebase to master context
v13: rebase to earlier patches
v12: new patch
---
 scripts/qapi-commands.py | 7 +++++++
 docs/qapi-code-gen.txt   | 7 +++++++
 2 files changed, 14 insertions(+)

diff --git a/scripts/qapi-commands.py b/scripts/qapi-commands.py
index 6261e44..04549fa 100644
--- a/scripts/qapi-commands.py
+++ b/scripts/qapi-commands.py
@@ -121,7 +121,12 @@ def gen_marshal(name, arg_type, ret_type):
     %(c_name)s arg = {0};

     v = qmp_input_get_visitor(qiv);
+    visit_start_struct(v, NULL, NULL, 0, &err);
+    if (err) {
+        goto out;
+    }
     visit_type_%(c_name)s_members(v, &arg, &err);
+    visit_end_struct(v, err ? NULL : &err);
     if (err) {
         goto out;
     }
@@ -150,7 +155,9 @@ out:
     qmp_input_visitor_cleanup(qiv);
     qdv = qapi_dealloc_visitor_new();
     v = qapi_dealloc_get_visitor(qdv);
+    visit_start_struct(v, NULL, NULL, 0, NULL);
     visit_type_%(c_name)s_members(v, &arg, NULL);
+    visit_end_struct(v, NULL);
     qapi_dealloc_visitor_cleanup(qdv);
 ''',
                      c_name=arg_type.c_name())
diff --git a/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt
index 4a917f9..b4ae1be 100644
--- a/docs/qapi-code-gen.txt
+++ b/docs/qapi-code-gen.txt
@@ -1002,7 +1002,12 @@ Example:
         UserDefOneList *arg1 = NULL;

         v = qmp_input_get_visitor(qiv);
+        visit_start_struct(v, NULL, NULL, 0, &err);
+        if (err) {
+            goto out;
+        }
         visit_type_UserDefOneList(v, "arg1", &arg1, &err);
+        visit_end_struct(v, err ? NULL : &err);
         if (err) {
             goto out;
         }
@@ -1019,7 +1024,9 @@ Example:
         qmp_input_visitor_cleanup(qiv);
         qdv = qapi_dealloc_visitor_new();
         v = qapi_dealloc_get_visitor(qdv);
+        visit_start_struct(v, NULL, NULL, 0, NULL);
         visit_type_UserDefOneList(v, "arg1", &arg1, NULL);
+        visit_end_struct(v, NULL);
         qapi_dealloc_visitor_cleanup(qdv);
     }

-- 
2.5.5

^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [Qemu-devel] [PATCH v15 08/23] monitor: Let generated code validate arguments
  2016-04-28  0:01 [Qemu-devel] [PATCH v15 00/23] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
                   ` (6 preceding siblings ...)
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 07/23] qapi-commands: Wrap argument visit in visit_start_struct Eric Blake
@ 2016-04-28  0:01 ` Eric Blake
  2016-04-28 14:09   ` Markus Armbruster
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 09/23] qom: Wrap prop visit in visit_start_struct Eric Blake
                   ` (15 subsequent siblings)
  23 siblings, 1 reply; 49+ messages in thread
From: Eric Blake @ 2016-04-28  0:01 UTC (permalink / raw)
  To: qemu-devel; +Cc: armbru, Luiz Capitulino, Michael Roth

Having to manually call out the set of expected arguments in
qmp-commands.hx, in addition to what is already in *.json,
is tedious and prone to error.  The only reason we use
.args_type is for checking if there is any excess arguments
or incorrectly typed arguments during qmp_check_client_args(),
but a strict QMP Input visitor also does that checking.
Simplify things so that .args_type is only needed for the
few remaining commands that don't have a QAPI description
(namely, device_add, netdev_add, qmp_capabilities) or which
use 'gen':false (query-qmp-schema).

There was one case where the generated marshal code has to be
beefed up: for a command that has no (or empty) 'data' in the
.json file, we were just ignoring the passed-in QDict; now we
need to validate that there are no extra arguments.

Generated code changes like:

|@@ -1206,7 +1206,10 @@ void qmp_marshal_cont(QDict *args, QObje
| {
|     Error *err = NULL;
|
|-    (void)args;
|+    if (args && qdict_size(args)) {
|+        error_setg(errp, "Command %s does not take arguments", "cont");
|+        return;
|+    }
|
|     qmp_cont(&err);
|     error_propagate(errp, err);

QMP behavior for no-arg commands changes from:

{"execute":"query-version","arguments":{"a":1}}
{"error": {"class": "GenericError", "desc": "Invalid parameter 'a'"}}

to:

{"execute":"query-version","arguments":{"a":1}}
{"error": {"class": "GenericError", "desc": "Command 'query-version' does not take arguments"}}

and for commands with at least one (possibly-optional) argument,
the output changes from:

{"execute":"query-command-line-options","arguments":{"a":1}}
{"error": {"class": "GenericError", "desc": "Invalid parameter 'a'"}}

to:

{"execute":"query-command-line-options","arguments":{"a":1}}
{"error": {"class": "GenericError", "desc": "QMP input object member 'a' is unexpected"}}

Signed-off-by: Eric Blake <eblake@redhat.com>

---
v15: new patch
---
 scripts/qapi-commands.py |   8 ++-
 monitor.c                |   4 ++
 qmp-commands.hx          | 148 +----------------------------------------------
 3 files changed, 12 insertions(+), 148 deletions(-)

diff --git a/scripts/qapi-commands.py b/scripts/qapi-commands.py
index 04549fa..beb34c5 100644
--- a/scripts/qapi-commands.py
+++ b/scripts/qapi-commands.py
@@ -136,8 +136,12 @@ def gen_marshal(name, arg_type, ret_type):
     else:
         ret += mcgen('''

-    (void)args;
-''')
+    if (args && qdict_size(args)) {
+        error_setg(errp, "Command '%%s' does not take arguments", "%(name)s");
+        return;
+    }
+''',
+                     name=name)

     ret += gen_call(name, arg_type, ret_type)

diff --git a/monitor.c b/monitor.c
index d1c1930..bc8ff5e 100644
--- a/monitor.c
+++ b/monitor.c
@@ -3783,6 +3783,7 @@ out:
 /*
  * Client argument checking rules:
  *
+ * 0. If args_type is NULL, rely on the generated QAPI to do the check
  * 1. Client must provide all mandatory arguments
  * 2. Each argument provided by the client must be expected
  * 3. Each argument provided by the client must have the type expected
@@ -3795,6 +3796,9 @@ static void qmp_check_client_args(const mon_cmd_t *cmd, QDict *client_args,
     int flags;
     QDict *cmd_args;

+    if (!cmd->args_type) {
+        return;
+    }
     cmd_args = qdict_from_args_type(cmd->args_type);

     flags = 0;
diff --git a/qmp-commands.hx b/qmp-commands.hx
index de896a5..49189ce 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -62,7 +62,6 @@ EQMP

     {
         .name       = "quit",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_quit,
     },

@@ -83,7 +82,6 @@ EQMP

     {
         .name       = "eject",
-        .args_type  = "force:-f,device:B",
         .mhandler.cmd_new = qmp_marshal_eject,
     },

@@ -109,7 +107,6 @@ EQMP

     {
         .name       = "change",
-        .args_type  = "device:B,target:F,arg:s?",
         .mhandler.cmd_new = qmp_marshal_change,
     },

@@ -145,7 +142,6 @@ EQMP

     {
         .name       = "screendump",
-        .args_type  = "filename:F",
         .mhandler.cmd_new = qmp_marshal_screendump,
     },

@@ -168,7 +164,6 @@ EQMP

     {
         .name       = "stop",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_stop,
     },

@@ -189,7 +184,6 @@ EQMP

     {
         .name       = "cont",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_cont,
     },

@@ -210,7 +204,6 @@ EQMP

     {
         .name       = "system_wakeup",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_system_wakeup,
     },

@@ -231,7 +224,6 @@ EQMP

     {
         .name       = "system_reset",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_system_reset,
     },

@@ -252,7 +244,6 @@ EQMP

     {
         .name       = "system_powerdown",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_system_powerdown,
     },

@@ -309,7 +300,6 @@ EQMP

     {
         .name       = "device_del",
-        .args_type  = "id:s",
         .mhandler.cmd_new = qmp_marshal_device_del,
     },

@@ -337,7 +327,6 @@ EQMP

     {
         .name       = "send-key",
-        .args_type  = "keys:q,hold-time:i?",
         .mhandler.cmd_new = qmp_marshal_send_key,
     },

@@ -368,7 +357,6 @@ EQMP

     {
         .name       = "cpu",
-        .args_type  = "index:i",
         .mhandler.cmd_new = qmp_marshal_cpu,
     },

@@ -393,7 +381,6 @@ EQMP

     {
         .name       = "cpu-add",
-        .args_type  = "id:i",
         .mhandler.cmd_new = qmp_marshal_cpu_add,
     },

@@ -416,7 +403,6 @@ EQMP

     {
         .name       = "memsave",
-        .args_type  = "val:l,size:i,filename:s,cpu:i?",
         .mhandler.cmd_new = qmp_marshal_memsave,
     },

@@ -445,7 +431,6 @@ EQMP

     {
         .name       = "pmemsave",
-        .args_type  = "val:l,size:i,filename:s",
         .mhandler.cmd_new = qmp_marshal_pmemsave,
     },

@@ -473,7 +458,6 @@ EQMP

     {
         .name       = "inject-nmi",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_inject_nmi,
     },

@@ -496,7 +480,6 @@ EQMP

     {
         .name       = "ringbuf-write",
-        .args_type  = "device:s,data:s,format:s?",
         .mhandler.cmd_new = qmp_marshal_ringbuf_write,
     },

@@ -525,7 +508,6 @@ EQMP

     {
         .name       = "ringbuf-read",
-        .args_type  = "device:s,size:i,format:s?",
         .mhandler.cmd_new = qmp_marshal_ringbuf_read,
     },

@@ -561,7 +543,6 @@ EQMP

     {
         .name       = "xen-save-devices-state",
-        .args_type  = "filename:F",
     .mhandler.cmd_new = qmp_marshal_xen_save_devices_state,
     },

@@ -588,7 +569,6 @@ EQMP

     {
         .name       = "xen-set-global-dirty-log",
-        .args_type  = "enable:b",
         .mhandler.cmd_new = qmp_marshal_xen_set_global_dirty_log,
     },

@@ -612,7 +592,6 @@ EQMP

     {
         .name       = "migrate",
-        .args_type  = "detach:-d,blk:-b,inc:-i,uri:s",
         .mhandler.cmd_new = qmp_marshal_migrate,
     },

@@ -645,7 +624,6 @@ EQMP

     {
         .name       = "migrate_cancel",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_migrate_cancel,
     },

@@ -666,7 +644,6 @@ EQMP

     {
         .name       = "migrate-incoming",
-        .args_type  = "uri:s",
         .mhandler.cmd_new = qmp_marshal_migrate_incoming,
     },

@@ -694,7 +671,6 @@ Notes:
 EQMP
     {
         .name       = "migrate-set-cache-size",
-        .args_type  = "value:o",
         .mhandler.cmd_new = qmp_marshal_migrate_set_cache_size,
     },

@@ -717,7 +693,6 @@ Example:
 EQMP
     {
         .name       = "migrate-start-postcopy",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_migrate_start_postcopy,
     },

@@ -736,7 +711,6 @@ EQMP

     {
         .name       = "query-migrate-cache-size",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_query_migrate_cache_size,
     },

@@ -758,7 +732,6 @@ EQMP

     {
         .name       = "migrate_set_speed",
-        .args_type  = "value:o",
         .mhandler.cmd_new = qmp_marshal_migrate_set_speed,
     },

@@ -781,7 +754,6 @@ EQMP

     {
         .name       = "migrate_set_downtime",
-        .args_type  = "value:T",
         .mhandler.cmd_new = qmp_marshal_migrate_set_downtime,
     },

@@ -804,7 +776,6 @@ EQMP

     {
         .name       = "client_migrate_info",
-        .args_type  = "protocol:s,hostname:s,port:i?,tls-port:i?,cert-subject:s?",
         .params     = "protocol hostname port tls-port cert-subject",
         .help       = "set migration information for remote display",
         .mhandler.cmd_new = qmp_marshal_client_migrate_info,
@@ -838,7 +809,6 @@ EQMP

     {
         .name       = "dump-guest-memory",
-        .args_type  = "paging:b,protocol:s,detach:b?,begin:i?,end:i?,format:s?",
         .params     = "-p protocol [-d] [begin] [length] [format]",
         .help       = "dump guest memory to file",
         .mhandler.cmd_new = qmp_marshal_dump_guest_memory,
@@ -879,8 +849,7 @@ EQMP

     {
         .name       = "query-dump-guest-memory-capability",
-        .args_type  = "",
-    .mhandler.cmd_new = qmp_marshal_query_dump_guest_memory_capability,
+        .mhandler.cmd_new = qmp_marshal_query_dump_guest_memory_capability,
     },

 SQMP
@@ -899,7 +868,6 @@ EQMP

     {
         .name       = "query-dump",
-        .args_type  = "",
         .params     = "",
         .help       = "query background dump status",
         .mhandler.cmd_new = qmp_marshal_query_dump,
@@ -924,7 +892,6 @@ EQMP
 #if defined TARGET_S390X
     {
         .name       = "dump-skeys",
-        .args_type  = "filename:F",
         .mhandler.cmd_new = qmp_marshal_dump_skeys,
     },
 #endif
@@ -979,7 +946,6 @@ EQMP

     {
         .name       = "netdev_del",
-        .args_type  = "id:s",
         .mhandler.cmd_new = qmp_marshal_netdev_del,
     },

@@ -1003,7 +969,6 @@ EQMP

     {
         .name       = "object-add",
-        .args_type  = "qom-type:s,id:s,props:q?",
         .mhandler.cmd_new = qmp_marshal_object_add,
     },

@@ -1029,7 +994,6 @@ EQMP

     {
         .name       = "object-del",
-        .args_type  = "id:s",
         .mhandler.cmd_new = qmp_marshal_object_del,
     },

@@ -1054,7 +1018,6 @@ EQMP

     {
         .name       = "block_resize",
-        .args_type  = "device:s?,node-name:s?,size:o",
         .mhandler.cmd_new = qmp_marshal_block_resize,
     },

@@ -1079,7 +1042,6 @@ EQMP

     {
         .name       = "block-stream",
-        .args_type  = "device:B,base:s?,speed:o?,backing-file:s?,on-error:s?",
         .mhandler.cmd_new = qmp_marshal_block_stream,
     },

@@ -1122,7 +1084,6 @@ EQMP

     {
         .name       = "block-commit",
-        .args_type  = "device:B,base:s?,top:s?,backing-file:s?,speed:o?",
         .mhandler.cmd_new = qmp_marshal_block_commit,
     },

@@ -1185,8 +1146,6 @@ EQMP

     {
         .name       = "drive-backup",
-        .args_type  = "sync:s,device:B,target:s,speed:i?,mode:s?,format:s?,"
-                      "bitmap:s?,on-source-error:s?,on-target-error:s?",
         .mhandler.cmd_new = qmp_marshal_drive_backup,
     },

@@ -1239,8 +1198,6 @@ EQMP

     {
         .name       = "blockdev-backup",
-        .args_type  = "sync:s,device:B,target:B,speed:i?,"
-                      "on-source-error:s?,on-target-error:s?",
         .mhandler.cmd_new = qmp_marshal_blockdev_backup,
     },

@@ -1280,33 +1237,27 @@ EQMP

     {
         .name       = "block-job-set-speed",
-        .args_type  = "device:B,speed:o",
         .mhandler.cmd_new = qmp_marshal_block_job_set_speed,
     },

     {
         .name       = "block-job-cancel",
-        .args_type  = "device:B,force:b?",
         .mhandler.cmd_new = qmp_marshal_block_job_cancel,
     },
     {
         .name       = "block-job-pause",
-        .args_type  = "device:B",
         .mhandler.cmd_new = qmp_marshal_block_job_pause,
     },
     {
         .name       = "block-job-resume",
-        .args_type  = "device:B",
         .mhandler.cmd_new = qmp_marshal_block_job_resume,
     },
     {
         .name       = "block-job-complete",
-        .args_type  = "device:B",
         .mhandler.cmd_new = qmp_marshal_block_job_complete,
     },
     {
         .name       = "transaction",
-        .args_type  = "actions:q,properties:q?",
         .mhandler.cmd_new = qmp_marshal_transaction,
     },

@@ -1400,7 +1351,6 @@ EQMP

     {
         .name       = "block-dirty-bitmap-add",
-        .args_type  = "node:B,name:s,granularity:i?",
         .mhandler.cmd_new = qmp_marshal_block_dirty_bitmap_add,
     },

@@ -1428,7 +1378,6 @@ EQMP

     {
         .name       = "block-dirty-bitmap-remove",
-        .args_type  = "node:B,name:s",
         .mhandler.cmd_new = qmp_marshal_block_dirty_bitmap_remove,
     },

@@ -1456,7 +1405,6 @@ EQMP

     {
         .name       = "block-dirty-bitmap-clear",
-        .args_type  = "node:B,name:s",
         .mhandler.cmd_new = qmp_marshal_block_dirty_bitmap_clear,
     },

@@ -1485,7 +1433,6 @@ EQMP

     {
         .name       = "blockdev-snapshot-sync",
-        .args_type  = "device:s?,node-name:s?,snapshot-file:s,snapshot-node-name:s?,format:s?,mode:s?",
         .mhandler.cmd_new = qmp_marshal_blockdev_snapshot_sync,
     },

@@ -1521,7 +1468,6 @@ EQMP

     {
         .name       = "blockdev-snapshot",
-        .args_type  = "node:s,overlay:s",
         .mhandler.cmd_new = qmp_marshal_blockdev_snapshot,
     },

@@ -1559,7 +1505,6 @@ EQMP

     {
         .name       = "blockdev-snapshot-internal-sync",
-        .args_type  = "device:B,name:s",
         .mhandler.cmd_new = qmp_marshal_blockdev_snapshot_internal_sync,
     },

@@ -1588,7 +1533,6 @@ EQMP

     {
         .name       = "blockdev-snapshot-delete-internal-sync",
-        .args_type  = "device:B,id:s?,name:s?",
         .mhandler.cmd_new =
                       qmp_marshal_blockdev_snapshot_delete_internal_sync,
     },
@@ -1629,11 +1573,6 @@ EQMP

     {
         .name       = "drive-mirror",
-        .args_type  = "sync:s,device:B,target:s,speed:i?,mode:s?,format:s?,"
-                      "node-name:s?,replaces:s?,"
-                      "on-source-error:s?,on-target-error:s?,"
-                      "unmap:b?,"
-                      "granularity:i?,buf-size:i?",
         .mhandler.cmd_new = qmp_marshal_drive_mirror,
     },

@@ -1693,9 +1632,6 @@ EQMP

     {
         .name       = "blockdev-mirror",
-        .args_type  = "sync:s,device:B,target:B,replaces:s?,speed:i?,"
-                      "on-source-error:s?,on-target-error:s?,"
-                      "granularity:i?,buf-size:i?",
         .mhandler.cmd_new = qmp_marshal_blockdev_mirror,
     },

@@ -1741,7 +1677,6 @@ Example:
 EQMP
     {
         .name       = "change-backing-file",
-        .args_type  = "device:s,image-node-name:s,backing-file:s",
         .mhandler.cmd_new = qmp_marshal_change_backing_file,
     },

@@ -1780,7 +1715,6 @@ EQMP

     {
         .name       = "balloon",
-        .args_type  = "value:M",
         .mhandler.cmd_new = qmp_marshal_balloon,
     },

@@ -1803,7 +1737,6 @@ EQMP

     {
         .name       = "set_link",
-        .args_type  = "name:s,up:b",
         .mhandler.cmd_new = qmp_marshal_set_link,
     },

@@ -1827,7 +1760,6 @@ EQMP

     {
         .name       = "getfd",
-        .args_type  = "fdname:s",
         .params     = "getfd name",
         .help       = "receive a file descriptor via SCM rights and assign it a name",
         .mhandler.cmd_new = qmp_marshal_getfd,
@@ -1860,7 +1792,6 @@ EQMP

     {
         .name       = "closefd",
-        .args_type  = "fdname:s",
         .params     = "closefd name",
         .help       = "close a file descriptor previously passed via SCM rights",
         .mhandler.cmd_new = qmp_marshal_closefd,
@@ -1885,7 +1816,6 @@ EQMP

      {
         .name       = "add-fd",
-        .args_type  = "fdset-id:i?,opaque:s?",
         .params     = "add-fd fdset-id opaque",
         .help       = "Add a file descriptor, that was passed via SCM rights, to an fd set",
         .mhandler.cmd_new = qmp_marshal_add_fd,
@@ -1924,7 +1854,6 @@ EQMP

      {
         .name       = "remove-fd",
-        .args_type  = "fdset-id:i,fd:i?",
         .params     = "remove-fd fdset-id fd",
         .help       = "Remove a file descriptor from an fd set",
         .mhandler.cmd_new = qmp_marshal_remove_fd,
@@ -1957,7 +1886,6 @@ EQMP

     {
         .name       = "query-fdsets",
-        .args_type  = "",
         .help       = "Return information describing all fd sets",
         .mhandler.cmd_new = qmp_marshal_query_fdsets,
     },
@@ -2007,7 +1935,6 @@ EQMP

     {
         .name       = "block_passwd",
-        .args_type  = "device:s?,node-name:s?,password:s",
         .mhandler.cmd_new = qmp_marshal_block_passwd,
     },

@@ -2033,7 +1960,6 @@ EQMP

     {
         .name       = "block_set_io_throttle",
-        .args_type  = "device:B,bps:l,bps_rd:l,bps_wr:l,iops:l,iops_rd:l,iops_wr:l,bps_max:l?,bps_rd_max:l?,bps_wr_max:l?,iops_max:l?,iops_rd_max:l?,iops_wr_max:l?,bps_max_length:l?,bps_rd_max_length:l?,bps_wr_max_length:l?,iops_max_length:l?,iops_rd_max_length:l?,iops_wr_max_length:l?,iops_size:l?,group:s?",
         .mhandler.cmd_new = qmp_marshal_block_set_io_throttle,
     },

@@ -2090,7 +2016,6 @@ EQMP

     {
         .name       = "set_password",
-        .args_type  = "protocol:s,password:s,connected:s?",
         .mhandler.cmd_new = qmp_marshal_set_password,
     },

@@ -2116,7 +2041,6 @@ EQMP

     {
         .name       = "expire_password",
-        .args_type  = "protocol:s,time:s",
         .mhandler.cmd_new = qmp_marshal_expire_password,
     },

@@ -2141,7 +2065,6 @@ EQMP

     {
         .name       = "add_client",
-        .args_type  = "protocol:s,fdname:s,skipauth:b?,tls:b?",
         .mhandler.cmd_new = qmp_marshal_add_client,
     },

@@ -2192,7 +2115,6 @@ EQMP

     {
         .name       = "human-monitor-command",
-        .args_type  = "command-line:s,cpu-index:i?",
         .mhandler.cmd_new = qmp_marshal_human_monitor_command,
     },

@@ -2271,7 +2193,6 @@ EQMP

     {
         .name       = "query-version",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_query_version,
     },

@@ -2308,7 +2229,6 @@ EQMP

     {
         .name       = "query-commands",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_query_commands,
     },

@@ -2345,7 +2265,6 @@ EQMP

     {
         .name       = "query-events",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_query_events,
     },

@@ -2362,7 +2281,7 @@ EQMP

     {
         .name       = "query-qmp-schema",
-        .args_type  = "",
+	.args_type  = "",
         .mhandler.cmd_new = qmp_query_qmp_schema,
     },

@@ -2407,7 +2326,6 @@ EQMP

     {
         .name       = "query-chardev",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_query_chardev,
     },

@@ -2448,7 +2366,6 @@ EQMP

     {
         .name       = "query-chardev-backends",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_query_chardev_backends,
     },

@@ -2632,7 +2549,6 @@ EQMP

     {
         .name       = "query-block",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_query_block,
     },

@@ -2829,7 +2745,6 @@ EQMP

     {
         .name       = "query-blockstats",
-        .args_type  = "query-nodes:b?",
         .mhandler.cmd_new = qmp_marshal_query_blockstats,
     },

@@ -2884,7 +2799,6 @@ EQMP

     {
         .name       = "query-cpus",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_query_cpus,
     },

@@ -2923,7 +2837,6 @@ EQMP

     {
         .name       = "query-iothreads",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_query_iothreads,
     },

@@ -3140,7 +3053,6 @@ EQMP

     {
         .name       = "query-pci",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_query_pci,
     },

@@ -3164,7 +3076,6 @@ EQMP

     {
         .name       = "query-kvm",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_query_kvm,
     },

@@ -3204,7 +3115,6 @@ EQMP
     
     {
         .name       = "query-status",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_query_status,
     },

@@ -3248,7 +3158,6 @@ EQMP

     {
         .name       = "query-mice",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_query_mice,
     },

@@ -3311,12 +3220,10 @@ EQMP

     {
         .name       = "query-vnc",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_query_vnc,
     },
     {
         .name       = "query-vnc-servers",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_query_vnc_servers,
     },

@@ -3393,7 +3300,6 @@ EQMP
 #if defined(CONFIG_SPICE)
     {
         .name       = "query-spice",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_query_spice,
     },
 #endif
@@ -3417,7 +3323,6 @@ EQMP

     {
         .name       = "query-name",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_query_name,
     },

@@ -3440,7 +3345,6 @@ EQMP

     {
         .name       = "query-uuid",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_query_uuid,
     },

@@ -3489,7 +3393,6 @@ EQMP

     {
         .name       = "query-command-line-options",
-        .args_type  = "option:s?",
         .mhandler.cmd_new = qmp_marshal_query_command_line_options,
     },

@@ -3667,7 +3570,6 @@ EQMP

     {
         .name       = "query-migrate",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_query_migrate,
     },

@@ -3696,7 +3598,6 @@ EQMP

     {
         .name       = "migrate-set-capabilities",
-        .args_type  = "capabilities:q",
         .params     = "capability:s,state:b",
         .mhandler.cmd_new = qmp_marshal_migrate_set_capabilities,
     },
@@ -3734,7 +3635,6 @@ EQMP

     {
         .name       = "query-migrate-capabilities",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_query_migrate_capabilities,
     },

@@ -3763,8 +3663,6 @@ EQMP

     {
         .name       = "migrate-set-parameters",
-        .args_type  =
-            "compress-level:i?,compress-threads:i?,decompress-threads:i?,x-cpu-throttle-initial:i?,x-cpu-throttle-increment:i?",
         .mhandler.cmd_new = qmp_marshal_migrate_set_parameters,
     },
 SQMP
@@ -3801,7 +3699,6 @@ EQMP

     {
         .name       = "query-migrate-parameters",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_query_migrate_parameters,
     },

@@ -3829,88 +3726,73 @@ EQMP

     {
         .name       = "query-balloon",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_query_balloon,
     },

     {
         .name       = "query-block-jobs",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_query_block_jobs,
     },

     {
         .name       = "qom-list",
-        .args_type  = "path:s",
         .mhandler.cmd_new = qmp_marshal_qom_list,
     },

     {
         .name       = "qom-set",
-	.args_type  = "path:s,property:s,value:q",
         .mhandler.cmd_new = qmp_marshal_qom_set,
     },

     {
         .name       = "qom-get",
-	.args_type  = "path:s,property:s",
         .mhandler.cmd_new = qmp_marshal_qom_get,
     },

     {
         .name       = "nbd-server-start",
-        .args_type  = "addr:q,tls-creds:s?",
         .mhandler.cmd_new = qmp_marshal_nbd_server_start,
     },
     {
         .name       = "nbd-server-add",
-        .args_type  = "device:B,writable:b?",
         .mhandler.cmd_new = qmp_marshal_nbd_server_add,
     },
     {
         .name       = "nbd-server-stop",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_nbd_server_stop,
     },

     {
         .name       = "change-vnc-password",
-        .args_type  = "password:s",
         .mhandler.cmd_new = qmp_marshal_change_vnc_password,
     },
     {
         .name       = "qom-list-types",
-        .args_type  = "implements:s?,abstract:b?",
         .mhandler.cmd_new = qmp_marshal_qom_list_types,
     },

     {
         .name       = "device-list-properties",
-        .args_type  = "typename:s",
         .mhandler.cmd_new = qmp_marshal_device_list_properties,
     },

     {
         .name       = "query-machines",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_query_machines,
     },

     {
         .name       = "query-cpu-definitions",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_query_cpu_definitions,
     },

     {
         .name       = "query-target",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_query_target,
     },

     {
         .name       = "query-tpm",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_query_tpm,
     },

@@ -3944,7 +3826,6 @@ EQMP

     {
         .name       = "query-tpm-models",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_query_tpm_models,
     },

@@ -3965,7 +3846,6 @@ EQMP

     {
         .name       = "query-tpm-types",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_query_tpm_types,
     },

@@ -3986,7 +3866,6 @@ EQMP

     {
         .name       = "chardev-add",
-        .args_type  = "id:s,backend:q",
         .mhandler.cmd_new = qmp_marshal_chardev_add,
     },

@@ -4023,7 +3902,6 @@ EQMP

     {
         .name       = "chardev-remove",
-        .args_type  = "id:s",
         .mhandler.cmd_new = qmp_marshal_chardev_remove,
     },

@@ -4046,7 +3924,6 @@ Example:
 EQMP
     {
         .name       = "query-rx-filter",
-        .args_type  = "name:s?",
         .mhandler.cmd_new = qmp_marshal_query_rx_filter,
     },

@@ -4112,7 +3989,6 @@ EQMP

     {
         .name       = "blockdev-add",
-        .args_type  = "options:q",
         .mhandler.cmd_new = qmp_marshal_blockdev_add,
     },

@@ -4171,7 +4047,6 @@ EQMP

     {
         .name       = "x-blockdev-del",
-        .args_type  = "id:s?,node-name:s?",
         .mhandler.cmd_new = qmp_marshal_x_blockdev_del,
     },

@@ -4228,7 +4103,6 @@ EQMP

     {
         .name       = "blockdev-open-tray",
-        .args_type  = "device:s,force:b?",
         .mhandler.cmd_new = qmp_marshal_blockdev_open_tray,
     },

@@ -4276,7 +4150,6 @@ EQMP

     {
         .name       = "blockdev-close-tray",
-        .args_type  = "device:s",
         .mhandler.cmd_new = qmp_marshal_blockdev_close_tray,
     },

@@ -4311,7 +4184,6 @@ EQMP

     {
         .name       = "x-blockdev-remove-medium",
-        .args_type  = "device:s",
         .mhandler.cmd_new = qmp_marshal_x_blockdev_remove_medium,
     },

@@ -4359,7 +4231,6 @@ EQMP

     {
         .name       = "x-blockdev-insert-medium",
-        .args_type  = "device:s,node-name:s",
         .mhandler.cmd_new = qmp_marshal_x_blockdev_insert_medium,
     },

@@ -4399,7 +4270,6 @@ EQMP

     {
         .name       = "query-named-block-nodes",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_query_named_block_nodes,
     },

@@ -4461,7 +4331,6 @@ EQMP

     {
         .name       = "blockdev-change-medium",
-        .args_type  = "device:B,filename:F,format:s?,read-only-mode:s?",
         .mhandler.cmd_new = qmp_marshal_blockdev_change_medium,
     },

@@ -4514,7 +4383,6 @@ EQMP

     {
         .name       = "query-memdev",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_query_memdev,
     },

@@ -4552,7 +4420,6 @@ EQMP

     {
         .name       = "query-memory-devices",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_query_memory_devices,
     },

@@ -4579,7 +4446,6 @@ EQMP

     {
         .name       = "query-acpi-ospm-status",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_query_acpi_ospm_status,
     },

@@ -4602,7 +4468,6 @@ EQMP
 #if defined TARGET_I386
     {
         .name       = "rtc-reset-reinjection",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_rtc_reset_reinjection,
     },
 #endif
@@ -4623,7 +4488,6 @@ EQMP

     {
         .name       = "trace-event-get-state",
-        .args_type  = "name:s",
         .mhandler.cmd_new = qmp_marshal_trace_event_get_state,
     },

@@ -4641,7 +4505,6 @@ EQMP

     {
         .name       = "trace-event-set-state",
-        .args_type  = "name:s,enable:b,ignore-unavailable:b?",
         .mhandler.cmd_new = qmp_marshal_trace_event_set_state,
     },

@@ -4659,7 +4522,6 @@ EQMP

     {
         .name       = "input-send-event",
-        .args_type  = "console:i?,events:q",
         .mhandler.cmd_new = qmp_marshal_input_send_event,
     },

@@ -4725,7 +4587,6 @@ EQMP

     {
         .name       = "block-set-write-threshold",
-        .args_type  = "node-name:s,write-threshold:l",
         .mhandler.cmd_new = qmp_marshal_block_set_write_threshold,
     },

@@ -4753,7 +4614,6 @@ EQMP

     {
         .name       = "query-rocker",
-        .args_type  = "name:s",
         .mhandler.cmd_new = qmp_marshal_query_rocker,
     },

@@ -4774,7 +4634,6 @@ EQMP

     {
         .name       = "query-rocker-ports",
-        .args_type  = "name:s",
         .mhandler.cmd_new = qmp_marshal_query_rocker_ports,
     },

@@ -4799,7 +4658,6 @@ EQMP

     {
         .name       = "query-rocker-of-dpa-flows",
-        .args_type  = "name:s,tbl-id:i?",
         .mhandler.cmd_new = qmp_marshal_query_rocker_of_dpa_flows,
     },

@@ -4828,7 +4686,6 @@ EQMP

     {
         .name       = "query-rocker-of-dpa-groups",
-        .args_type  = "name:s,type:i?",
         .mhandler.cmd_new = qmp_marshal_query_rocker_of_dpa_groups,
     },

@@ -4859,7 +4716,6 @@ EQMP
 #if defined TARGET_ARM
     {
         .name       = "query-gic-capabilities",
-        .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_query_gic_capabilities,
     },
 #endif
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [Qemu-devel] [PATCH v15 09/23] qom: Wrap prop visit in visit_start_struct
  2016-04-28  0:01 [Qemu-devel] [PATCH v15 00/23] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
                   ` (7 preceding siblings ...)
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 08/23] monitor: Let generated code validate arguments Eric Blake
@ 2016-04-28  0:01 ` Eric Blake
  2016-04-28 14:46   ` Markus Armbruster
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 10/23] qmp-input: Require struct push to visit members of top dict Eric Blake
                   ` (14 subsequent siblings)
  23 siblings, 1 reply; 49+ messages in thread
From: Eric Blake @ 2016-04-28  0:01 UTC (permalink / raw)
  To: qemu-devel; +Cc: armbru, Andreas Färber

The qmp-input visitor was allowing callers to play rather fast
and loose: when visiting a QDict, you could grab members of the
root dictionary without first pushing into the dict; the final
such culprit was the QOM code for converting to and from object
properties.  But we are about to tighten the input visitor, at
which point user_creatable_add_type() as called with a QMP input
visitor via qmp_object_add() MUST follow the same paradigms as
everyone else, of pushing into the struct before grabbing its
keys.

The use of 'err ? NULL : &err' is temporary; a later patch will
clean that up when it splits visit_end_struct().

The change has no impact to the testsuite now, but is required to
avoid a failure in tests/test-netfilter once qmp-input is made
stricter to detect inconsistent 'name' arguments on the root visit.

Since user_creatable_add_type() is also called with OptsVisitor
through user_creatable_add_opts(), we must also check that there
is no negative impact there; both pre- and post-patch, we see:

$ ./x86_64-softmmu/qemu-system-x86_64 -nographic -nodefaults -qmp stdio -object secret,id=sec0,data=letmein,format=raw,foo=bar
qemu-system-x86_64: Property '.foo' not found

That is, the only new checking that the new visit_end_struct() can
perform is for excess input, but we already catch excess input
earlier in object_property_set().

Signed-off-by: Eric Blake <eblake@redhat.com>

---
v15: hoist earlier in series, improve commit message
v14: no change
v13: no change
v12: new patch
---
 qom/object_interfaces.c | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/qom/object_interfaces.c b/qom/object_interfaces.c
index ab5da35..4a60d6d 100644
--- a/qom/object_interfaces.c
+++ b/qom/object_interfaces.c
@@ -120,12 +120,20 @@ Object *user_creatable_add_type(const char *type, const char *id,

     obj = object_new(type);
     if (qdict) {
+        visit_start_struct(v, NULL, NULL, 0, &local_err);
+        if (local_err) {
+            goto out;
+        }
         for (e = qdict_first(qdict); e; e = qdict_next(qdict, e)) {
             object_property_set(obj, v, e->key, &local_err);
             if (local_err) {
-                goto out;
+                break;
             }
         }
+        visit_end_struct(v, local_err ? NULL : &local_err);
+        if (local_err) {
+            goto out;
+        }
     }

     object_property_add_child(object_get_objects_root(),
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [Qemu-devel] [PATCH v15 10/23] qmp-input: Require struct push to visit members of top dict
  2016-04-28  0:01 [Qemu-devel] [PATCH v15 00/23] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
                   ` (8 preceding siblings ...)
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 09/23] qom: Wrap prop visit in visit_start_struct Eric Blake
@ 2016-04-28  0:01 ` Eric Blake
  2016-04-28 15:00   ` Markus Armbruster
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 11/23] qmp-input: Refactor when list is advanced Eric Blake
                   ` (13 subsequent siblings)
  23 siblings, 1 reply; 49+ messages in thread
From: Eric Blake @ 2016-04-28  0:01 UTC (permalink / raw)
  To: qemu-devel; +Cc: armbru, Michael Roth

Don't embed the root of the visit into the stack of current
containers being visited.  That way, we no longer get confused
on whether the first visit of a dictionary is to the dictionary
itself or to one of the members of the dictionary, based on
whether the caller passed name=NULL; and makes the QMP Input
visitor like other visitors where the value of 'name' is now
ignored on the root visit.  (We may someday want to revisit
the rules on what 'name' should be on a top-level visit,
rather than just ignoring it; but that would be the topic of
another patch).

An audit of all qmp_input_visitor_new() call sites shows that
there were only two places where callers had previously been
visiting to a QDict with a non-NULL name to bypass a call to
visit_start_struct(), and those were fixed in prior patches.

Signed-off-by: Eric Blake <eblake@redhat.com>

---
v15: hoist earlier in series, improve variable naming
v14: no change
v13: no change
v12: new patch
---
 qapi/qmp-input-visitor.c | 45 ++++++++++++++++++++++++---------------------
 1 file changed, 24 insertions(+), 21 deletions(-)

diff --git a/qapi/qmp-input-visitor.c b/qapi/qmp-input-visitor.c
index cae6387..6409a83 100644
--- a/qapi/qmp-input-visitor.c
+++ b/qapi/qmp-input-visitor.c
@@ -35,9 +35,11 @@ struct QmpInputVisitor
 {
     Visitor visitor;

-    /* Stack of objects being visited.  stack[0] is root of visit,
-     * stack[1..] records the nesting of start_struct()/end_struct()
-     * and start_list()/end_list() pairs. */
+    /* Root of visit at visitor creation. */
+    QObject *root;
+
+    /* Stack of objects being visited (all entries will be either
+     * QDict or QList). */
     StackObject stack[QIV_STACK_SIZE];
     int nb_stack;

@@ -54,33 +56,34 @@ static QObject *qmp_input_get_object(QmpInputVisitor *qiv,
                                      const char *name,
                                      bool consume)
 {
-    StackObject *tos = &qiv->stack[qiv->nb_stack - 1];
-    QObject *qobj = tos->obj;
-    QObject *ret;
+    StackObject *tos;
+    QObject *qobj;
+    QObject *ret = NULL;

+    if (!qiv->nb_stack) {
+        /* Starting at root, name is ignored. */
+        return qiv->root;
+    }
+
+    /* We are in a container; find the next element. */
+    tos = &qiv->stack[qiv->nb_stack - 1];
+    qobj = tos->obj;
     assert(qobj);

-    /* If we have a name, and we're in a dictionary, then return that
-     * value. */
-    if (name && qobject_type(qobj) == QTYPE_QDICT) {
+    if (qobject_type(qobj) == QTYPE_QDICT) {
+        assert(name);
         ret = qdict_get(qobject_to_qdict(qobj), name);
         if (tos->h && consume && ret) {
             bool removed = g_hash_table_remove(tos->h, name);
             assert(removed);
         }
-        return ret;
-    }
-
-    /* If we are in the middle of a list, then return the next element
-     * of the list. */
-    if (tos->entry) {
+    } else {
         assert(qobject_type(qobj) == QTYPE_QLIST);
-        return qlist_entry_obj(tos->entry);
+        assert(!name);
+        ret = qlist_entry_obj(tos->entry);
     }

-    /* Otherwise, we are at the root of the visit or the start of a
-     * list, and return the object as-is. */
-    return qobj;
+    return ret;
 }

 static void qdict_add_key(const char *key, QObject *obj, void *opaque)
@@ -355,7 +358,7 @@ Visitor *qmp_input_get_visitor(QmpInputVisitor *v)

 void qmp_input_visitor_cleanup(QmpInputVisitor *v)
 {
-    qobject_decref(v->stack[0].obj);
+    qobject_decref(v->root);
     g_free(v);
 }

@@ -381,7 +384,7 @@ QmpInputVisitor *qmp_input_visitor_new(QObject *obj, bool strict)
     v->visitor.optional = qmp_input_optional;
     v->strict = strict;

-    qmp_input_push(v, obj, NULL);
+    v->root = obj;
     qobject_incref(obj);

     return v;
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [Qemu-devel] [PATCH v15 11/23] qmp-input: Refactor when list is advanced
  2016-04-28  0:01 [Qemu-devel] [PATCH v15 00/23] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
                   ` (9 preceding siblings ...)
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 10/23] qmp-input: Require struct push to visit members of top dict Eric Blake
@ 2016-04-28  0:01 ` Eric Blake
  2016-04-28 15:19   ` Markus Armbruster
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 12/23] qapi: Document visitor interfaces, add assertions Eric Blake
                   ` (12 subsequent siblings)
  23 siblings, 1 reply; 49+ messages in thread
From: Eric Blake @ 2016-04-28  0:01 UTC (permalink / raw)
  To: qemu-devel; +Cc: armbru, Michael Roth

In the QMP input visitor, visiting a list traverses two objects:
the QAPI GenericList of the caller (which gets advanced in
visit_next_list() regardless of this patch), and the QList input
that we are converting to QAPI.  For consistency with QDict
visits, we want to consume elements from the input QList during
the visit_type_FOO() for the list element; that is, we want ALL
the code for consuming an input to live in qmp_input_get_object(),
rather than having it split according to whether we are visiting
a dict or a list.  Making qmp_input_get_object() the common point
of consumption will make it easier for a later patch to refactor
visit_start_list() to cover the GenericList * head of a QAPI list,
and in turn will get rid of the 'first' flag (which lived in
qmp_input_next_list() pre-patch, and is hoisted to StackObject
by this patch).

This patch is therefore shifting the post-condition use of
'entry' from:

        start_list next_list type_ELT ... next_list type_ELT end_list
 entry  NULL       1st elt   1st elt      last elt  last elt gone

where type_ELT() returns (entry ? entry : 1st elt) and next_list() steps
entry

to this usage:

        start_list next_list type_ELT ... next_list type_ELT end_list
 entry  1st elt    1nd elt   2nd elt      last elt  NULL     gone

where type_ELT() steps entry and returns the old entry, and next_list()
leaves entry alone.

Signed-off-by: Eric Blake <eblake@redhat.com>

---
v15: rebase, improve commit message, hoist root handling earlier
so that this patch is more pinpointed
v14: no change
v13: no change
v12: new patch
---
 qapi/qmp-input-visitor.c | 30 ++++++++++++++++--------------
 1 file changed, 16 insertions(+), 14 deletions(-)

diff --git a/qapi/qmp-input-visitor.c b/qapi/qmp-input-visitor.c
index 6409a83..eb77adc 100644
--- a/qapi/qmp-input-visitor.c
+++ b/qapi/qmp-input-visitor.c
@@ -29,6 +29,7 @@ typedef struct StackObject

     GHashTable *h;           /* If obj is dict: unvisited keys */
     const QListEntry *entry; /* If obj is list: unvisited tail */
+    bool first;              /* If obj is list: will next_list() visit head */
 } StackObject;

 struct QmpInputVisitor
@@ -80,7 +81,11 @@ static QObject *qmp_input_get_object(QmpInputVisitor *qiv,
     } else {
         assert(qobject_type(qobj) == QTYPE_QLIST);
         assert(!name);
+        assert(!tos->first);
         ret = qlist_entry_obj(tos->entry);
+        if (consume) {
+            tos->entry = qlist_next(tos->entry);
+        }
     }

     return ret;
@@ -104,13 +109,16 @@ static void qmp_input_push(QmpInputVisitor *qiv, QObject *obj, Error **errp)
     }

     tos->obj = obj;
-    tos->entry = NULL;
-    tos->h = NULL;
+    assert(!tos->h);
+    assert(!tos->entry);

     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);
         tos->h = h;
+    } else if (qobject_type(obj) == QTYPE_QLIST) {
+        tos->entry = qlist_first(qobject_to_qlist(obj));
+        tos->first = true;
     }

     qiv->nb_stack++;
@@ -119,10 +127,11 @@ static void qmp_input_push(QmpInputVisitor *qiv, QObject *obj, Error **errp)

 static void qmp_input_pop(QmpInputVisitor *qiv, Error **errp)
 {
+    StackObject *tos = &qiv->stack[qiv->nb_stack - 1];
     assert(qiv->nb_stack > 0);

     if (qiv->strict) {
-        GHashTable * const top_ht = qiv->stack[qiv->nb_stack - 1].h;
+        GHashTable * const top_ht = tos->h;
         if (top_ht) {
             GHashTableIter iter;
             const char *key;
@@ -133,6 +142,7 @@ static void qmp_input_pop(QmpInputVisitor *qiv, Error **errp)
             }
             g_hash_table_unref(top_ht);
         }
+        tos->h = NULL;
     }

     qiv->nb_stack--;
@@ -192,23 +202,15 @@ static GenericList *qmp_input_next_list(Visitor *v, GenericList **list,
     QmpInputVisitor *qiv = to_qiv(v);
     GenericList *entry;
     StackObject *so = &qiv->stack[qiv->nb_stack - 1];
-    bool first;

-    if (so->entry == NULL) {
-        so->entry = qlist_first(qobject_to_qlist(so->obj));
-        first = true;
-    } else {
-        so->entry = qlist_next(so->entry);
-        first = false;
-    }
-
-    if (so->entry == NULL) {
+    if (!so->entry) {
         return NULL;
     }

     entry = g_malloc0(size);
-    if (first) {
+    if (so->first) {
         *list = entry;
+        so->first = false;
     } else {
         (*list)->next = entry;
     }
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [Qemu-devel] [PATCH v15 12/23] qapi: Document visitor interfaces, add assertions
  2016-04-28  0:01 [Qemu-devel] [PATCH v15 00/23] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
                   ` (10 preceding siblings ...)
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 11/23] qmp-input: Refactor when list is advanced Eric Blake
@ 2016-04-28  0:01 ` Eric Blake
  2016-04-28 16:34   ` Markus Armbruster
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 13/23] tests: Add check-qnull Eric Blake
                   ` (11 subsequent siblings)
  23 siblings, 1 reply; 49+ messages in thread
From: Eric Blake @ 2016-04-28  0:01 UTC (permalink / raw)
  To: qemu-devel; +Cc: armbru, Michael Roth

The visitor interface for mapping between QObject/QemuOpts/string
and QAPI is scandalously under-documented, making changes to visitor
core, individual visitors, and users of visitors difficult to
coordinate.  Among other questions: when is it safe to pass NULL,
vs. when a string must be provided; which visitors implement which
callbacks; the difference between concrete and virtual visits.

Correct this by retrofitting proper contracts, and document where some
of the interface warts remain (for example, we may want to modify
visit_end_* to require the same 'obj' as the visit_start counterpart,
so the dealloc visitor can be simplified).  Later patches in this
series will tackle some, but not all, of these warts.

Add assertions to (partially) enforce the contract.  Some of these
were only made possible by recent cleanup commits.

Signed-off-by: Eric Blake <eblake@redhat.com>

---
v15: wording tweaks based on review, rebase to some assertions going
in earlier
v14: rebase to master context
v13: minor wording tweaks for consistency
v12: major rework based on Markus' comments, drop R-b
[no v10, v11]
v9: no change
v8: rebase to 'name' motion
v7: retitle; more wording changes, add asserts to enforce the
wording, place later in series to rebase on fixes that would
otherwise trip the new assertions
v6: mention that input visitors blindly assign over *obj; wording
improvements
---
 include/qapi/visitor.h               | 445 +++++++++++++++++++++++++++++++++--
 include/qapi/visitor-impl.h          |  44 +++-
 include/qapi/dealloc-visitor.h       |   5 +
 include/qapi/opts-visitor.h          |   3 +
 include/qapi/string-input-visitor.h  |   4 +
 include/qapi/string-output-visitor.h |   4 +
 qapi/qapi-visit-core.c               |  18 +-
 7 files changed, 494 insertions(+), 29 deletions(-)

diff --git a/include/qapi/visitor.h b/include/qapi/visitor.h
index 690589f..0e028ba 100644
--- a/include/qapi/visitor.h
+++ b/include/qapi/visitor.h
@@ -16,8 +16,194 @@

 #include "qapi/qmp/qobject.h"

+/*
+ * The QAPI schema defines both a set of C data types, and a QMP wire
+ * format.  QAPI objects can contain references to other QAPI objects,
+ * resulting in a directed acyclic graph.  QAPI also generates visitor
+ * functions to walk these graphs.  This file represents the interface
+ * for doing work at each node of a QAPI graph; it can also be used
+ * for a virtual walk, where there is no actual QAPI C struct.
+ *
+ * There are three kinds of visitor classes: input visitors (QMP,
+ * string, and QemuOpts) parse an external representation and build
+ * the corresponding QAPI graph, output visitors (QMP and string) take
+ * a completed QAPI graph and generate an external representation, and
+ * the dealloc visitor can take a QAPI graph (possibly partially
+ * constructed) and recursively free its resources.  While the dealloc
+ * and QMP input/output visitors are general, the string and QemuOpts
+ * visitors have some implementation limitations; see the
+ * documentation for each visitor for more details on what it
+ * supports.  Also, see visitor-impl.h for the callback contracts
+ * implemented by each visitor, and docs/qapi-code-gen.txt for more
+ * about the QAPI code generator.
+ *
+ * All QAPI types have a corresponding function with a signature
+ * roughly compatible with this:
+ *
+ * void visit_type_FOO(Visitor *v, const char *name, T obj, Error **errp);
+ *
+ * where T is FOO for scalar types, and FOO * otherwise.  The scalar
+ * visitors are declared here; the remaining visitors are generated in
+ * qapi-visit.h.
+ *
+ * The @name parameter of visit_type_FOO() describes the relation
+ * between this QAPI value and its parent container.  When visiting
+ * the root of a tree, @name is ignored; when visiting a member of an
+ * object, @name is the key associated with the value; and when
+ * visiting a member of a list, @name is NULL.
+ *
+ * FIXME: Clients must pass NULL for @name when visiting a member of a
+ * list, but this leads to poor error messages; it might be nicer to
+ * require a non-NULL name such as "key.0" for '{ "key": [ "value" ]
+ * }' if an error is encountered on "value" (or to have the visitor
+ * core auto-generate the nicer name).
+ *
+ * The visit_type_FOO() functions expect a non-null @obj argument;
+ * they allocate *@obj during input visits, leave it unchanged on
+ * output visits, and recursively free any resources during a dealloc
+ * visit.  Each function also takes the customary @errp argument (see
+ * qapi/error.h for details), for reporting any errors (such as if a
+ * member @name is not present, or is present but not the specified
+ * type).
+ *
+ * FIXME: At present, input visitors may allocate an incomplete *@obj
+ * even when visit_type_FOO() reports an error.  Using an output
+ * visitor with an incomplete object has undefined behavior; callers
+ * must call qapi_free_FOO() (which uses the dealloc visitor, and
+ * safely handles an incomplete object) to avoid a memory leak.
+ *
+ * For the QAPI object types (structs, unions, and alternates), there
+ * is an additional generated function in qapi-visit.h compatible
+ * with:
+ *
+ * void visit_type_FOO_members(Visitor *v, FOO *obj, Error **errp);
+ *
+ * for visiting the members of a type without also allocating the QAPI
+ * struct.
+ *
+ * Additionally, in qapi-types.h, all QAPI pointer types (structs,
+ * unions, alternates, and lists) have a generated function compatible
+ * with:
+ *
+ * void qapi_free_FOO(FOO *obj);
+ *
+ * which behaves like free() (@obj can be NULL).  Because of these
+ * functions, the dealloc visitor is seldom used directly outside of
+ * generated code.  QAPI types can also inherit from a base class;
+ * when this happens, a function is generated for easily going from
+ * the derived type to the base type:
+ *
+ * BASE *qapi_CHILD_base(CHILD *obj);
+ *
+ * For a real QAPI struct, typical input usage involves:
+ *
+ * <example>
+ *  Foo *f;
+ *  Error *err = NULL;
+ *  Visitor *v;
+ *
+ *  v = ...obtain input visitor...
+ *  visit_type_Foo(v, NULL, &f, &err);
+ *  if (err) {
+ *      qapi_free_Foo(f);
+ *      ...handle error...
+ *  } else {
+ *      ...use f...
+ *  }
+ *  ...clean up v...
+ *  qapi_free_Foo(f);
+ * </example>
+ *
+ * For a list, it is:
+ * <example>
+ *  FooList *l;
+ *  Error *err = NULL;
+ *  Visitor *v;
+ *
+ *  v = ...obtain input visitor...
+ *  visit_type_FooList(v, NULL, &l, &err);
+ *  if (err) {
+ *      qapi_free_FooList(l);
+ *      ...handle error...
+ *  } else {
+ *      for ( ; l; l = l->next) {
+ *          ...use l->value...
+ *      }
+ *  }
+ *  ...clean up v...
+ *  qapi_free_FooList(l);
+ * </example>
+ *
+ * Similarly, typical output usage is:
+ *
+ * <example>
+ *  Foo *f = ...obtain populated object...
+ *  Error *err = NULL;
+ *  Visitor *v;
+ *
+ *  v = ...obtain output visitor...
+ *  visit_type_Foo(v, NULL, &f, &err);
+ *  if (err) {
+ *      ...handle error...
+ *  }
+ *  ...clean up v...
+ * </example>
+ *
+ * When visiting a real QAPI struct, this file provides several
+ * helpers that rely on in-tree information to control the walk:
+ * visit_optional() for the 'has_member' field associated with
+ * optional 'member' in the C struct; and visit_next_list() for
+ * advancing through a FooList linked list.  Only the generated
+ * visit_type functions need to use these helpers.
+ *
+ * It is also possible to use the visitors to do a virtual walk, where
+ * no actual QAPI struct is present.  In this situation, decisions
+ * about what needs to be walked are made by the calling code, and
+ * structured visits are split between pairs of start and end methods
+ * (where the end method must be called if the start function
+ * succeeded, even if an intermediate visit encounters an error).
+ * Thus, a virtual walk corresponding to '{ "list": [1, 2] }' looks
+ * like:
+ *
+ * <example>
+ *  Visitor *v;
+ *  Error *err = NULL;
+ *  int value;
+ *
+ *  v = ...obtain visitor...
+ *  visit_start_struct(v, NULL, NULL, 0, &err);
+ *  if (err) {
+ *      goto outobj;
+ *  }
+ *  visit_start_list(v, "list", &err);
+ *  if (err) {
+ *      goto outobj;
+ *  }
+ *  value = 1;
+ *  visit_type_int(v, NULL, &value, &err);
+ *  if (err) {
+ *      goto outlist;
+ *  }
+ *  value = 2;
+ *  visit_type_int(v, NULL, &value, &err);
+ *  if (err) {
+ *      goto outlist;
+ *  }
+ * outlist:
+ *  visit_end_list(v);
+ * outobj:
+ *  error_propagate(errp, err);
+ *  err = NULL;
+ *  visit_end_struct(v, &err);
+ *  error_propagate(errp, err);
+ *  ...clean up v...
+ * </example>
+ */
+
+/*** Useful types ***/
+
 /* This struct is layout-compatible with all other *List structs
- * created by the qapi generator.  It is used as a typical
+ * created by the QAPI generator.  It is used as a typical
  * singly-linked list. */
 typedef struct GenericList {
     struct GenericList *next;
@@ -25,35 +211,126 @@ typedef struct GenericList {
 } GenericList;

 /* This struct is layout-compatible with all Alternate types
- * created by the qapi generator. */
+ * created by the QAPI generator. */
 typedef struct GenericAlternate {
     QType type;
     char padding[];
 } GenericAlternate;

+/*** Visiting structures ***/
+
+/*
+ * Start visiting an object @obj (struct or union).
+ *
+ * @name expresses the relationship of this object to its parent
+ * container; see the general description of @name above.
+ *
+ * @obj must be non-NULL for a real walk, in which case @size
+ * determines how much memory an input visitor will allocate into
+ * *@obj.  @obj may also be NULL for a virtual walk, in which case
+ * @size is ignored.
+ *
+ * @errp obeys typical error usage, and reports failures such as a
+ * member @name is not present, or present but not an object.  On
+ * error, input visitors set *@obj to NULL.
+ *
+ * After visit_start_struct() succeeds, the caller may visit its
+ * members one after the other, passing the member's name and address
+ * within the struct.  Finally, visit_end_struct() needs to be called
+ * to clean up, even if intermediate visits fail.  See the examples
+ * above.
+ *
+ * FIXME Should this be named visit_start_object, since it is also
+ * used for QAPI unions, and maps to JSON objects?
+ */
 void visit_start_struct(Visitor *v, const char *name, void **obj,
                         size_t size, Error **errp);
+
+/*
+ * Complete an object visit started earlier.
+ *
+ * @errp obeys typical error usage, and reports failures such as
+ * unparsed keys remaining in the input stream.
+ *
+ * Must be called after any successful use of visit_start_struct(),
+ * even if intermediate processing was skipped due to errors, to allow
+ * the backend to release any resources.  Destroying the visitor early
+ * behaves as if this was implicitly called.
+ */
 void visit_end_struct(Visitor *v, Error **errp);

+
+/*** Visiting lists ***/
+
+/*
+ * Start visiting a list.
+ *
+ * @name expresses the relationship of this list to its parent
+ * container; see the general description of @name above.
+ *
+ * @errp obeys typical error usage, and reports failures such as a
+ * member @name is not present, or present but not a list.
+ *
+ * After visit_start_list() succeeds, the caller may visit its members
+ * one after the other.  A real visit uses visit_next_list() for
+ * traversing the linked list, while a virtual visit uses other means.
+ * For each list element, call the appropriate visit_type_FOO() with
+ * name set to NULL and obj set to the address of the value member of
+ * the list element.  Finally, visit_end_list() needs to be called to
+ * clean up, even if intermediate visits fail.  See the examples
+ * above.
+ */
 void visit_start_list(Visitor *v, const char *name, Error **errp);
+
+/*
+ * Iterate over a GenericList during a non-virtual list visit.
+ *
+ * @size represents the size of a linked list node (at least
+ * sizeof(GenericList)).
+ *
+ * @list must not be NULL; on the first call, @list contains the
+ * address of the list head, and on subsequent calls *@list must be
+ * the previously returned value.  Should be called in a loop until a
+ * NULL return or error occurs; for each non-NULL return, the caller
+ * then calls the appropriate visit_type_*() for the element type
+ * of the list, with that function's name parameter set to NULL and
+ * obj set to the address of (*@list)->value.
+ *
+ * FIXME: This interface is awkward; it requires all callbacks to
+ * track whether it is the first or a subsequent call.  A better
+ * interface would pass the head of the list through
+ * visit_start_list().
+ */
 GenericList *visit_next_list(Visitor *v, GenericList **list, size_t size);
+
+/*
+ * Complete a list visit started earlier.
+ *
+ * Must be called after any successful use of visit_start_list(), even
+ * if intermediate processing was skipped due to errors, to allow the
+ * backend to release any resources.  Destroying the visitor early
+ * behaves as if this was implicitly called.
+ */
 void visit_end_list(Visitor *v);

+
+/*** Visiting alternates ***/
+
 /*
- * Start the visit of an alternate @obj with the given @size.
+ * Start the visit of an alternate @obj.
  *
- * @name specifies the relationship to the containing struct (ignored
- * for a top level visit, the name of the key if this alternate is
- * part of an object, or NULL if this alternate is part of a list).
+ * @name expresses the relationship of this alternate to its parent
+ * container; see the general description of @name above.
  *
- * @obj must not be NULL. Input visitors will allocate @obj and
- * determine the qtype of the next thing to be visited, stored in
- * (*@obj)->type.  Other visitors will leave @obj unchanged.
+ * @obj must not be NULL. Input visitors use @size to determine how
+ * much memory to allocate into *@obj, then determine the qtype of the
+ * next thing to be visited, stored in (*@obj)->type.  Other visitors
+ * will leave @obj unchanged.
  *
  * If @promote_int, treat integers as QTYPE_FLOAT.
  *
- * If successful, this must be paired with visit_end_alternate(), even
- * if visiting the contents of the alternate fails.
+ * If successful, this must be paired with visit_end_alternate() to
+ * clean up, even if visiting the contents of the alternate fails.
  */
 void visit_start_alternate(Visitor *v, const char *name,
                            GenericAlternate **obj, size_t size,
@@ -62,27 +339,48 @@ void visit_start_alternate(Visitor *v, const char *name,
 /*
  * Finish visiting an alternate type.
  *
- * Must be called after a successful visit_start_alternate(), even if
- * an error occurred in the meantime.
+ * Must be called after any successful use of visit_start_alternate(),
+ * even if intermediate processing was skipped due to errors, to allow
+ * the backend to release any resources.  Destroying the visitor early
+ * behaves as if this was implicitly called.
  *
  * TODO: Should all the visit_end_* interfaces take obj parameter, so
  * that dealloc visitor need not track what was passed in visit_start?
  */
 void visit_end_alternate(Visitor *v);

-/**
- * Check if an optional member @name of an object needs visiting.
- * For input visitors, set *@present according to whether the
- * corresponding visit_type_*() needs calling; for other visitors,
- * leave *@present unchanged.  Return *@present for convenience.
+
+/*** Other helpers ***/
+
+/*
+ * Does optional struct member @name need visiting?
+ *
+ * @name must not be NULL.  This function is only useful between
+ * visit_start_struct() and visit_end_struct(), since only objects
+ * have optional keys.
+ *
+ * @present points to the address of the optional member's has_ flag.
+ *
+ * Input visitors set *@present according to input; other visitors
+ * leave it unchanged.  In either case, return *@present for
+ * convenience.
  */
 bool visit_optional(Visitor *v, const char *name, bool *present);

 /*
  * Visit an enum value.
  *
- * @strings expresses the mapping between C enum values and QAPI enum
- * names; it should be the ENUM_lookup array from visit-types.h.
+ * @name expresses the relationship of this enum to its parent
+ * container; see the general description of @name above.
+ *
+ * @obj must be non-NULL.  Input visitors parse input and set *@obj to
+ * the enumeration value, leaving @obj unchanged on error; other
+ * visitors use *@obj but leave it unchanged.
+ *
+ * Currently, all input visitors parse text input, and all output
+ * visitors produce text output.  The mapping between enumeration
+ * values and strings is done by the visitor core, using @strings; it
+ * should be the ENUM_lookup array from visit-types.h.
  *
  * May call visit_type_str() under the hood, and the enum visit may
  * fail even if the corresponding string visit succeeded; this implies
@@ -91,28 +389,135 @@ bool visit_optional(Visitor *v, const char *name, bool *present);
 void visit_type_enum(Visitor *v, const char *name, int *obj,
                      const char *const strings[], Error **errp);

+/*** Visiting built-in types ***/
+
+/*
+ * Visit an integer value.
+ *
+ * @name expresses the relationship of this integer to its parent
+ * container; see the general description of @name above.
+ *
+ * @obj must be non-NULL.  Input visitors set *@obj to the value;
+ * other visitors will leave *@obj unchanged.
+ */
 void visit_type_int(Visitor *v, const char *name, int64_t *obj, Error **errp);
+
+/*
+ * Visit a uint8_t value.
+ * Like visit_type_int(), except clamps the value to uint8_t range.
+ */
 void visit_type_uint8(Visitor *v, const char *name, uint8_t *obj,
                       Error **errp);
+
+/*
+ * Visit a uint16_t value.
+ * Like visit_type_int(), except clamps the value to uint16_t range.
+ */
 void visit_type_uint16(Visitor *v, const char *name, uint16_t *obj,
                        Error **errp);
+
+/*
+ * Visit a uint32_t value.
+ * Like visit_type_int(), except clamps the value to uint32_t range.
+ */
 void visit_type_uint32(Visitor *v, const char *name, uint32_t *obj,
                        Error **errp);
+
+/*
+ * Visit a uint64_t value.
+ * Like visit_type_int(), except clamps the value to uint64_t range,
+ * that is, ensures it is unsigned.
+ */
 void visit_type_uint64(Visitor *v, const char *name, uint64_t *obj,
                        Error **errp);
+
+/*
+ * Visit an int8_t value.
+ * Like visit_type_int(), except clamps the value to int8_t range.
+ */
 void visit_type_int8(Visitor *v, const char *name, int8_t *obj, Error **errp);
+
+/*
+ * Visit an int16_t value.
+ * Like visit_type_int(), except clamps the value to int16_t range.
+ */
 void visit_type_int16(Visitor *v, const char *name, int16_t *obj,
                       Error **errp);
+
+/*
+ * Visit an int32_t value.
+ * Like visit_type_int(), except clamps the value to int32_t range.
+ */
 void visit_type_int32(Visitor *v, const char *name, int32_t *obj,
                       Error **errp);
+
+/*
+ * Visit an int64_t value.
+ * Identical to visit_type_int().
+ */
 void visit_type_int64(Visitor *v, const char *name, int64_t *obj,
                       Error **errp);
+
+/*
+ * Visit a uint64_t value.
+ * Like visit_type_uint64(), except that some visitors may choose to
+ * recognize additional syntax, such as suffixes for easily scaling
+ * values.
+ */
 void visit_type_size(Visitor *v, const char *name, uint64_t *obj,
                      Error **errp);
+
+/*
+ * Visit a boolean value.
+ *
+ * @name expresses the relationship of this boolean to its parent
+ * container; see the general description of @name above.
+ *
+ * @obj must be non-NULL.  Input visitors set *@obj to the value;
+ * other visitors will leave *@obj unchanged.
+ */
 void visit_type_bool(Visitor *v, const char *name, bool *obj, Error **errp);
+
+/*
+ * Visit a string value.
+ *
+ * @name expresses the relationship of this string to its parent
+ * container; see the general description of @name above.
+ *
+ * @obj must be non-NULL.  Input visitors set *@obj to the value
+ * (never NULL).  Other visitors leave *@obj unchanged, and commonly
+ * treat NULL like "".
+ *
+ * It is safe to cast away const when preparing a (const char *) value
+ * into @obj for use by an output visitor.
+ *
+ * FIXME: Callers that try to output NULL *obj should not be allowed.
+ */
 void visit_type_str(Visitor *v, const char *name, char **obj, Error **errp);
+
+/*
+ * Visit a number (i.e. double) value.
+ *
+ * @name expresses the relationship of this number to its parent
+ * container; see the general description of @name above.
+ *
+ * @obj must be non-NULL.  Input visitors set *@obj to the value;
+ * other visitors will leave *@obj unchanged.  Visitors should
+ * document if infinity or NaN are not permitted.
+ */
 void visit_type_number(Visitor *v, const char *name, double *obj,
                        Error **errp);
+
+/*
+ * Visit an arbitrary value.
+ *
+ * @name expresses the relationship of this value to its parent
+ * container; see the general description of @name above.
+ *
+ * @obj must be non-NULL.  Input visitors set *@obj to the value;
+ * other visitors will leave *@obj unchanged.  *@obj must be non-NULL
+ * for output visitors.
+ */
 void visit_type_any(Visitor *v, const char *name, QObject **obj, Error **errp);

 #endif
diff --git a/include/qapi/visitor-impl.h b/include/qapi/visitor-impl.h
index 51c338a..796d180 100644
--- a/include/qapi/visitor-impl.h
+++ b/include/qapi/visitor-impl.h
@@ -15,6 +15,18 @@
 #include "qapi/visitor.h"

 /*
+ * This file describes the callback interface for implementing a QAPI
+ * visitor.  For the client interface, see visitor.h.  When
+ * implementing the callbacks, it is easiest to declare a struct with
+ * 'Visitor visitor;' as the first member.  A callback's contract
+ * matches the corresponding public functions' contract unless stated
+ * otherwise.  In the comments below, some callbacks are marked "must
+ * be set for $TYPE visits to work"; if a visitor implementation omits
+ * that callback, it should also document that it is only useful for a
+ * subset of QAPI.
+ */
+
+/*
  * There are three classes of visitors; setting the class determines
  * how QAPI enums are visited, as well as what additional restrictions
  * can be asserted.
@@ -27,43 +39,59 @@ typedef enum VisitorType {

 struct Visitor
 {
-    /* Must be set */
+    /* Must be set to visit structs */
     void (*start_struct)(Visitor *v, const char *name, void **obj,
                          size_t size, Error **errp);
+
+    /* Must be set to visit structs */
     void (*end_struct)(Visitor *v, Error **errp);

+    /* Must be set */
     void (*start_list)(Visitor *v, const char *name, Error **errp);
+
     /* Must be set */
     GenericList *(*next_list)(Visitor *v, GenericList **list, size_t size);
+
     /* Must be set */
     void (*end_list)(Visitor *v);

-    /* Optional, needed for input and dealloc visitors.  */
+    /* Must be set by input and dealloc visitors to visit alternates;
+     * optional for output visitors. */
     void (*start_alternate)(Visitor *v, const char *name,
                             GenericAlternate **obj, size_t size,
                             bool promote_int, Error **errp);

-    /* Optional, needed for dealloc visitor.  */
+    /* Optional, needed for dealloc visitor */
     void (*end_alternate)(Visitor *v);

-    /* Must be set. */
+    /* Must be set */
     void (*type_int64)(Visitor *v, const char *name, int64_t *obj,
                        Error **errp);
-    /* Must be set. */
+
+    /* Must be set */
     void (*type_uint64)(Visitor *v, const char *name, uint64_t *obj,
                         Error **errp);
-    /* Optional; fallback is type_uint64().  */
+
+    /* Optional; fallback is type_uint64() */
     void (*type_size)(Visitor *v, const char *name, uint64_t *obj,
                       Error **errp);
-    /* Must be set. */
+
+    /* Must be set */
     void (*type_bool)(Visitor *v, const char *name, bool *obj, Error **errp);
+
+    /* Must be set */
     void (*type_str)(Visitor *v, const char *name, char **obj, Error **errp);
+
+    /* Must be set to visit numbers */
     void (*type_number)(Visitor *v, const char *name, double *obj,
                         Error **errp);
+
+    /* Must be set to visit arbitrary QTypes */
     void (*type_any)(Visitor *v, const char *name, QObject **obj,
                      Error **errp);

-    /* May be NULL; most useful for input visitors. */
+    /* Must be set for input visitors, optional otherwise.  The core
+     * takes care of the return type in the public interface. */
     void (*optional)(Visitor *v, const char *name, bool *present);

     /* Must be set */
diff --git a/include/qapi/dealloc-visitor.h b/include/qapi/dealloc-visitor.h
index cf4c36d..45b06b2 100644
--- a/include/qapi/dealloc-visitor.h
+++ b/include/qapi/dealloc-visitor.h
@@ -18,6 +18,11 @@

 typedef struct QapiDeallocVisitor QapiDeallocVisitor;

+/*
+ * The dealloc visitor is primarly used only by generated
+ * qapi_free_FOO() functions, and is the only visitor designed to work
+ * correctly in the face of a partially-constructed QAPI tree.
+ */
 QapiDeallocVisitor *qapi_dealloc_visitor_new(void);
 void qapi_dealloc_visitor_cleanup(QapiDeallocVisitor *d);

diff --git a/include/qapi/opts-visitor.h b/include/qapi/opts-visitor.h
index fd48c14..633aa71 100644
--- a/include/qapi/opts-visitor.h
+++ b/include/qapi/opts-visitor.h
@@ -29,6 +29,9 @@ typedef struct OptsVisitor OptsVisitor;
  * - string representations of negative numbers yield negative values,
  * - values below INT64_MIN or LLONG_MIN are rejected,
  * - values above INT64_MAX or LLONG_MAX are rejected.
+ *
+ * The Opts input visitor does not implement support for visiting QAPI
+ * alternates, numbers (other than integers), or arbitrary QTypes.
  */
 OptsVisitor *opts_visitor_new(const QemuOpts *opts);
 void opts_visitor_cleanup(OptsVisitor *nv);
diff --git a/include/qapi/string-input-visitor.h b/include/qapi/string-input-visitor.h
index 089243c..fdf33ae 100644
--- a/include/qapi/string-input-visitor.h
+++ b/include/qapi/string-input-visitor.h
@@ -17,6 +17,10 @@

 typedef struct StringInputVisitor StringInputVisitor;

+/*
+ * The string input visitor does not implement support for visiting
+ * QAPI structs, alternates, or arbitrary QTypes.
+ */
 StringInputVisitor *string_input_visitor_new(const char *str);
 void string_input_visitor_cleanup(StringInputVisitor *v);

diff --git a/include/qapi/string-output-visitor.h b/include/qapi/string-output-visitor.h
index d99717f..3bb09af 100644
--- a/include/qapi/string-output-visitor.h
+++ b/include/qapi/string-output-visitor.h
@@ -17,6 +17,10 @@

 typedef struct StringOutputVisitor StringOutputVisitor;

+/*
+ * The string output visitor does not implement support for visiting
+ * QAPI structs, alternates, or arbitrary QTypes.
+ */
 StringOutputVisitor *string_output_visitor_new(bool human);
 void string_output_visitor_cleanup(StringOutputVisitor *v);

diff --git a/qapi/qapi-visit-core.c b/qapi/qapi-visit-core.c
index 3a131ce..a8492cf 100644
--- a/qapi/qapi-visit-core.c
+++ b/qapi/qapi-visit-core.c
@@ -25,6 +25,10 @@ void visit_start_struct(Visitor *v, const char *name, void **obj,
 {
     Error *err = NULL;

+    if (obj) {
+        assert(size);
+        assert(v->type != VISITOR_OUTPUT || *obj);
+    }
     v->start_struct(v, name, obj, size, &err);
     if (obj && v->type == VISITOR_INPUT) {
         assert(err || *obj);
@@ -60,6 +64,7 @@ void visit_start_alternate(Visitor *v, const char *name,
     Error *err = NULL;

     assert(obj && size >= sizeof(GenericAlternate));
+    assert(v->type != VISITOR_OUTPUT || *obj);
     if (v->start_alternate) {
         v->start_alternate(v, name, obj, size, promote_int, &err);
         if (v->type == VISITOR_INPUT) {
@@ -86,6 +91,7 @@ bool visit_optional(Visitor *v, const char *name, bool *present)

 void visit_type_int(Visitor *v, const char *name, int64_t *obj, Error **errp)
 {
+    assert(obj);
     v->type_int64(v, name, obj, errp);
 }

@@ -133,6 +139,7 @@ void visit_type_uint32(Visitor *v, const char *name, uint32_t *obj,
 void visit_type_uint64(Visitor *v, const char *name, uint64_t *obj,
                        Error **errp)
 {
+    assert(obj);
     v->type_uint64(v, name, obj, errp);
 }

@@ -180,12 +187,14 @@ void visit_type_int32(Visitor *v, const char *name, int32_t *obj,
 void visit_type_int64(Visitor *v, const char *name, int64_t *obj,
                       Error **errp)
 {
+    assert(obj);
     v->type_int64(v, name, obj, errp);
 }

 void visit_type_size(Visitor *v, const char *name, uint64_t *obj,
                      Error **errp)
 {
+    assert(obj);
     if (v->type_size) {
         v->type_size(v, name, obj, errp);
     } else {
@@ -195,6 +204,7 @@ void visit_type_size(Visitor *v, const char *name, uint64_t *obj,

 void visit_type_bool(Visitor *v, const char *name, bool *obj, Error **errp)
 {
+    assert(obj);
     v->type_bool(v, name, obj, errp);
 }

@@ -203,6 +213,10 @@ void visit_type_str(Visitor *v, const char *name, char **obj, Error **errp)
     Error *err = NULL;

     assert(obj);
+    /* TODO: Fix callers to not pass NULL when they mean "", so that we
+     * can enable:
+    assert(v->type != VISITOR_OUTPUT || *obj);
+     */
     v->type_str(v, name, obj, &err);
     if (v->type == VISITOR_INPUT) {
         assert(err || *obj);
@@ -213,6 +227,7 @@ void visit_type_str(Visitor *v, const char *name, char **obj, Error **errp)
 void visit_type_number(Visitor *v, const char *name, double *obj,
                        Error **errp)
 {
+    assert(obj);
     v->type_number(v, name, obj, errp);
 }

@@ -221,6 +236,7 @@ void visit_type_any(Visitor *v, const char *name, QObject **obj, Error **errp)
     Error *err = NULL;

     assert(obj);
+    assert(v->type != VISITOR_OUTPUT || *obj);
     v->type_any(v, name, obj, &err);
     if (v->type == VISITOR_INPUT) {
         assert(err || *obj);
@@ -278,7 +294,7 @@ static void input_type_enum(Visitor *v, const char *name, int *obj,
 void visit_type_enum(Visitor *v, const char *name, int *obj,
                      const char *const strings[], Error **errp)
 {
-    assert(strings);
+    assert(obj && strings);
     if (v->type == VISITOR_INPUT) {
         input_type_enum(v, name, obj, strings, errp);
     } else if (v->type == VISITOR_OUTPUT) {
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [Qemu-devel] [PATCH v15 13/23] tests: Add check-qnull
  2016-04-28  0:01 [Qemu-devel] [PATCH v15 00/23] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
                   ` (11 preceding siblings ...)
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 12/23] qapi: Document visitor interfaces, add assertions Eric Blake
@ 2016-04-28  0:01 ` Eric Blake
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 14/23] qapi: Add visit_type_null() visitor Eric Blake
                   ` (10 subsequent siblings)
  23 siblings, 0 replies; 49+ messages in thread
From: Eric Blake @ 2016-04-28  0:01 UTC (permalink / raw)
  To: qemu-devel; +Cc: armbru, Michael Roth

Add a new test, for checking reference counting of qnull(). As
part of the new file, move a previous reference counting change
added in commit a861564 to a more logical place.

Note that while most of the check-q*.c leave visitor stuff to
the test-qmp-*-visitor.c, in this case we actually want the
visitor tests in our new file because we are validating the
reference count of qnull_, which is an internal detail that
test-qmp-*-visitor should not be peeking into (or put another
way, qnull() is the only special case where we don't have
independent allocation of a QObject, so none of the other
visitor tests require the layering violation present in this
test).

Signed-off-by: Eric Blake <eblake@redhat.com>

---
v15: add comment, commit message tweak
v14: no change
v13: no change
v12: new patch
---
 tests/check-qnull.c             | 66 +++++++++++++++++++++++++++++++++++++++++
 tests/test-qmp-output-visitor.c |  2 --
 tests/.gitignore                |  1 +
 tests/Makefile                  |  6 +++-
 4 files changed, 72 insertions(+), 3 deletions(-)
 create mode 100644 tests/check-qnull.c

diff --git a/tests/check-qnull.c b/tests/check-qnull.c
new file mode 100644
index 0000000..4a1c3d8
--- /dev/null
+++ b/tests/check-qnull.c
@@ -0,0 +1,66 @@
+/*
+ * QNull unit-tests.
+ *
+ * Copyright (C) 2016 Red Hat Inc.
+ *
+ * This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
+ * See the COPYING.LIB file in the top-level directory.
+ */
+#include "qemu/osdep.h"
+#include <glib.h>
+
+#include "qapi/qmp/qobject.h"
+#include "qemu-common.h"
+#include "qapi/qmp-output-visitor.h"
+
+/*
+ * Public Interface test-cases
+ *
+ * (with some violations to access 'private' data)
+ */
+
+static void qnull_ref_test(void)
+{
+    QObject *obj;
+
+    g_assert(qnull_.refcnt == 1);
+    obj = qnull();
+    g_assert(obj);
+    g_assert(obj == &qnull_);
+    g_assert(qnull_.refcnt == 2);
+    g_assert(qobject_type(obj) == QTYPE_QNULL);
+    qobject_decref(obj);
+    g_assert(qnull_.refcnt == 1);
+}
+
+static void qnull_visit_test(void)
+{
+    QObject *obj;
+    QmpOutputVisitor *qov;
+
+    /*
+     * Most tests of interactions between QObject and visitors are in
+     * test-qmp-*-visitor; but these tests live here because they
+     * depend on layering violations to check qnull_ refcnt.
+     */
+
+    g_assert(qnull_.refcnt == 1);
+    qov = qmp_output_visitor_new();
+    /* FIXME: Empty visits are ugly, we should have a visit_type_null(). */
+    obj = qmp_output_get_qobject(qov);
+    g_assert(obj == &qnull_);
+    qobject_decref(obj);
+
+    qmp_output_visitor_cleanup(qov);
+    g_assert(qnull_.refcnt == 1);
+}
+
+int main(int argc, char **argv)
+{
+    g_test_init(&argc, &argv, NULL);
+
+    g_test_add_func("/public/qnull_ref", qnull_ref_test);
+    g_test_add_func("/public/qnull_visit", qnull_visit_test);
+
+    return g_test_run();
+}
diff --git a/tests/test-qmp-output-visitor.c b/tests/test-qmp-output-visitor.c
index c709267..fddb5a6 100644
--- a/tests/test-qmp-output-visitor.c
+++ b/tests/test-qmp-output-visitor.c
@@ -484,8 +484,6 @@ static void test_visitor_out_empty(TestOutputVisitorData *data,

     arg = qmp_output_get_qobject(data->qov);
     g_assert(qobject_type(arg) == QTYPE_QNULL);
-    /* Check that qnull reference counting is sane */
-    g_assert(arg->refcnt == 2);
     qobject_decref(arg);
 }

diff --git a/tests/.gitignore b/tests/.gitignore
index 9eed229..a06a8ba 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -3,6 +3,7 @@ check-qfloat
 check-qint
 check-qjson
 check-qlist
+check-qnull
 check-qstring
 check-qom-interface
 check-qom-proplist
diff --git a/tests/Makefile b/tests/Makefile
index 9194f18..9dddde6 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -16,6 +16,8 @@ check-unit-y += tests/check-qstring$(EXESUF)
 gcov-files-check-qstring-y = qobject/qstring.c
 check-unit-y += tests/check-qlist$(EXESUF)
 gcov-files-check-qlist-y = qobject/qlist.c
+check-unit-y += tests/check-qnull$(EXESUF)
+gcov-files-check-qnull-y = qobject/qnull.c
 check-unit-y += tests/check-qjson$(EXESUF)
 gcov-files-check-qjson-y = qobject/qjson.c
 check-unit-y += tests/test-qmp-output-visitor$(EXESUF)
@@ -382,7 +384,8 @@ GENERATED_HEADERS += tests/test-qapi-types.h tests/test-qapi-visit.h \
 	tests/test-qmp-introspect.h

 test-obj-y = tests/check-qint.o tests/check-qstring.o tests/check-qdict.o \
-	tests/check-qlist.o tests/check-qfloat.o tests/check-qjson.o \
+	tests/check-qlist.o tests/check-qfloat.o tests/check-qnull.o \
+	tests/check-qjson.o \
 	tests/test-coroutine.o tests/test-string-output-visitor.o \
 	tests/test-string-input-visitor.o tests/test-qmp-output-visitor.o \
 	tests/test-qmp-input-visitor.o tests/test-qmp-input-strict.o \
@@ -410,6 +413,7 @@ tests/check-qstring$(EXESUF): tests/check-qstring.o $(test-util-obj-y)
 tests/check-qdict$(EXESUF): tests/check-qdict.o $(test-util-obj-y)
 tests/check-qlist$(EXESUF): tests/check-qlist.o $(test-util-obj-y)
 tests/check-qfloat$(EXESUF): tests/check-qfloat.o $(test-util-obj-y)
+tests/check-qnull$(EXESUF): tests/check-qnull.o $(test-util-obj-y)
 tests/check-qjson$(EXESUF): tests/check-qjson.o $(test-util-obj-y)
 tests/check-qom-interface$(EXESUF): tests/check-qom-interface.o $(test-qom-obj-y)
 tests/check-qom-proplist$(EXESUF): tests/check-qom-proplist.o $(test-qom-obj-y)
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [Qemu-devel] [PATCH v15 14/23] qapi: Add visit_type_null() visitor
  2016-04-28  0:01 [Qemu-devel] [PATCH v15 00/23] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
                   ` (12 preceding siblings ...)
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 13/23] tests: Add check-qnull Eric Blake
@ 2016-04-28  0:01 ` Eric Blake
  2016-04-28 16:40   ` Markus Armbruster
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 15/23] qmp: Support explicit null during visits Eric Blake
                   ` (9 subsequent siblings)
  23 siblings, 1 reply; 49+ messages in thread
From: Eric Blake @ 2016-04-28  0:01 UTC (permalink / raw)
  To: qemu-devel; +Cc: armbru, Michael Roth

Right now, qmp-output-visitor happens to produce a QNull result
if nothing is actually visited between the creation of the visitor
and the request for the resulting QObject.  A stronger protocol
would require that a QMP output visit MUST visit something.  But
to still be able to produce a JSON 'null' output, we need a new
visitor function that states our intentions.  Yes, we could say
that such a visit must go through visit_type_any(), but that
feels clunky.

So this patch introduces the new visit_type_null() interface and
its no-op interface in the dealloc visitor, and stubs in the
qmp visitors (the next patch will finish the implementation).
For the visitors that will not implement the callback, document
the situation. The code in qapi-visit-core unconditionally
dereferences the callback pointer, so that a segfault will inform
a developer if they need to implement the callback for their
choice of visitor.

Note that JSON has a primitive null type, with the single value
null; likewise with the QNull type for QObject; but for QAPI,
we just have the 'null' value without a null type.  We may
eventually want to add more support in QAPI for null (most likely,
we'd use it via an alternate type that permits 'null' or an
object); but we'll create that usage when we need it.

Signed-off-by: Eric Blake <eblake@redhat.com>

---
v15: improve commit message, add stubs here
v14: no change
v13: no change
v12: rebase to earlier changes, drop R-b due to better documentation
[no v10, v11]
v9: no change
v8: rebase to 'name' motion
v7: new patch, based on discussion about spapr_drc.c
---
 include/qapi/visitor.h               | 12 ++++++++++++
 include/qapi/visitor-impl.h          |  3 +++
 include/qapi/opts-visitor.h          |  3 ++-
 include/qapi/string-input-visitor.h  |  2 +-
 include/qapi/string-output-visitor.h |  2 +-
 qapi/qapi-visit-core.c               |  5 +++++
 qapi/qapi-dealloc-visitor.c          |  5 +++++
 qapi/qmp-input-visitor.c             |  6 ++++++
 qapi/qmp-output-visitor.c            |  7 +++++++
 9 files changed, 42 insertions(+), 3 deletions(-)

diff --git a/include/qapi/visitor.h b/include/qapi/visitor.h
index 0e028ba..e79c09e 100644
--- a/include/qapi/visitor.h
+++ b/include/qapi/visitor.h
@@ -520,4 +520,16 @@ void visit_type_number(Visitor *v, const char *name, double *obj,
  */
 void visit_type_any(Visitor *v, const char *name, QObject **obj, Error **errp);

+/*
+ * Visit a JSON null value.
+ *
+ * @name expresses the relationship of the null value to its parent
+ * container; see the general description of @name above.
+ *
+ * Unlike all other visit_type_* functions, no obj parameter is
+ * needed; rather, this is a witness that an explicit null value is
+ * expected rather than any other type.
+ */
+void visit_type_null(Visitor *v, const char *name, Error **errp);
+
 #endif
diff --git a/include/qapi/visitor-impl.h b/include/qapi/visitor-impl.h
index 796d180..88d27d5 100644
--- a/include/qapi/visitor-impl.h
+++ b/include/qapi/visitor-impl.h
@@ -90,6 +90,9 @@ struct Visitor
     void (*type_any)(Visitor *v, const char *name, QObject **obj,
                      Error **errp);

+    /* Must be set to visit explicit null values.  */
+    void (*type_null)(Visitor *v, const char *name, Error **errp);
+
     /* Must be set for input visitors, optional otherwise.  The core
      * takes care of the return type in the public interface. */
     void (*optional)(Visitor *v, const char *name, bool *present);
diff --git a/include/qapi/opts-visitor.h b/include/qapi/opts-visitor.h
index 633aa71..fe37ed9 100644
--- a/include/qapi/opts-visitor.h
+++ b/include/qapi/opts-visitor.h
@@ -31,7 +31,8 @@ typedef struct OptsVisitor OptsVisitor;
  * - values above INT64_MAX or LLONG_MAX are rejected.
  *
  * The Opts input visitor does not implement support for visiting QAPI
- * alternates, numbers (other than integers), or arbitrary QTypes.
+ * alternates, numbers (other than integers), null, or arbitrary
+ * QTypes.
  */
 OptsVisitor *opts_visitor_new(const QemuOpts *opts);
 void opts_visitor_cleanup(OptsVisitor *nv);
diff --git a/include/qapi/string-input-visitor.h b/include/qapi/string-input-visitor.h
index fdf33ae..a8d8f67 100644
--- a/include/qapi/string-input-visitor.h
+++ b/include/qapi/string-input-visitor.h
@@ -19,7 +19,7 @@ typedef struct StringInputVisitor StringInputVisitor;

 /*
  * The string input visitor does not implement support for visiting
- * QAPI structs, alternates, or arbitrary QTypes.
+ * QAPI structs, alternates, null, or arbitrary QTypes.
  */
 StringInputVisitor *string_input_visitor_new(const char *str);
 void string_input_visitor_cleanup(StringInputVisitor *v);
diff --git a/include/qapi/string-output-visitor.h b/include/qapi/string-output-visitor.h
index 3bb09af..89b7e4b 100644
--- a/include/qapi/string-output-visitor.h
+++ b/include/qapi/string-output-visitor.h
@@ -19,7 +19,7 @@ typedef struct StringOutputVisitor StringOutputVisitor;

 /*
  * The string output visitor does not implement support for visiting
- * QAPI structs, alternates, or arbitrary QTypes.
+ * QAPI structs, alternates, null, or arbitrary QTypes.
  */
 StringOutputVisitor *string_output_visitor_new(bool human);
 void string_output_visitor_cleanup(StringOutputVisitor *v);
diff --git a/qapi/qapi-visit-core.c b/qapi/qapi-visit-core.c
index a8492cf..7eaa75c 100644
--- a/qapi/qapi-visit-core.c
+++ b/qapi/qapi-visit-core.c
@@ -244,6 +244,11 @@ void visit_type_any(Visitor *v, const char *name, QObject **obj, Error **errp)
     error_propagate(errp, err);
 }

+void visit_type_null(Visitor *v, const char *name, Error **errp)
+{
+    v->type_null(v, name, errp);
+}
+
 static void output_type_enum(Visitor *v, const char *name, int *obj,
                              const char *const strings[], Error **errp)
 {
diff --git a/qapi/qapi-dealloc-visitor.c b/qapi/qapi-dealloc-visitor.c
index c19a459..413d525 100644
--- a/qapi/qapi-dealloc-visitor.c
+++ b/qapi/qapi-dealloc-visitor.c
@@ -163,6 +163,10 @@ static void qapi_dealloc_type_anything(Visitor *v, const char *name,
     }
 }

+static void qapi_dealloc_type_null(Visitor *v, const char *name, Error **errp)
+{
+}
+
 Visitor *qapi_dealloc_get_visitor(QapiDeallocVisitor *v)
 {
     return &v->visitor;
@@ -193,6 +197,7 @@ QapiDeallocVisitor *qapi_dealloc_visitor_new(void)
     v->visitor.type_str = qapi_dealloc_type_str;
     v->visitor.type_number = qapi_dealloc_type_number;
     v->visitor.type_any = qapi_dealloc_type_anything;
+    v->visitor.type_null = qapi_dealloc_type_null;

     QTAILQ_INIT(&v->stack);

diff --git a/qapi/qmp-input-visitor.c b/qapi/qmp-input-visitor.c
index eb77adc..739bbd5 100644
--- a/qapi/qmp-input-visitor.c
+++ b/qapi/qmp-input-visitor.c
@@ -340,6 +340,11 @@ static void qmp_input_type_any(Visitor *v, const char *name, QObject **obj,
     *obj = qobj;
 }

+static void qmp_input_type_null(Visitor *v, const char *name, Error **errp)
+{
+    error_setg(errp, "FIXME: Implement");
+}
+
 static void qmp_input_optional(Visitor *v, const char *name, bool *present)
 {
     QmpInputVisitor *qiv = to_qiv(v);
@@ -383,6 +388,7 @@ QmpInputVisitor *qmp_input_visitor_new(QObject *obj, bool strict)
     v->visitor.type_str = qmp_input_type_str;
     v->visitor.type_number = qmp_input_type_number;
     v->visitor.type_any = qmp_input_type_any;
+    v->visitor.type_null = qmp_input_type_null;
     v->visitor.optional = qmp_input_optional;
     v->strict = strict;

diff --git a/qapi/qmp-output-visitor.c b/qapi/qmp-output-visitor.c
index 1f2a7ba..a67e3e8 100644
--- a/qapi/qmp-output-visitor.c
+++ b/qapi/qmp-output-visitor.c
@@ -18,6 +18,7 @@
 #include "qemu/queue.h"
 #include "qemu-common.h"
 #include "qapi/qmp/types.h"
+#include "qapi/error.h"

 typedef struct QStackEntry
 {
@@ -196,6 +197,11 @@ static void qmp_output_type_any(Visitor *v, const char *name, QObject **obj,
     qmp_output_add_obj(qov, name, *obj);
 }

+static void qmp_output_type_null(Visitor *v, const char *name, Error **errp)
+{
+    error_setg(errp, "FIXME: Implement");
+}
+
 /* Finish building, and return the root object. Will not be NULL. */
 QObject *qmp_output_get_qobject(QmpOutputVisitor *qov)
 {
@@ -246,6 +252,7 @@ QmpOutputVisitor *qmp_output_visitor_new(void)
     v->visitor.type_str = qmp_output_type_str;
     v->visitor.type_number = qmp_output_type_number;
     v->visitor.type_any = qmp_output_type_any;
+    v->visitor.type_null = qmp_output_type_null;

     QTAILQ_INIT(&v->stack);

-- 
2.5.5

^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [Qemu-devel] [PATCH v15 15/23] qmp: Support explicit null during visits
  2016-04-28  0:01 [Qemu-devel] [PATCH v15 00/23] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
                   ` (13 preceding siblings ...)
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 14/23] qapi: Add visit_type_null() visitor Eric Blake
@ 2016-04-28  0:01 ` Eric Blake
  2016-04-28 16:50   ` Markus Armbruster
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 16/23] spapr_drc: Expose 'null' in qom-get when there is no fdt Eric Blake
                   ` (8 subsequent siblings)
  23 siblings, 1 reply; 49+ messages in thread
From: Eric Blake @ 2016-04-28  0:01 UTC (permalink / raw)
  To: qemu-devel; +Cc: armbru, Michael Roth

Implement the new type_null() callback for the qmp input and
output visitors. While we don't yet have a use for this in QAPI
input (the generator will need some tweaks first), some
potential usages have already been discussed on the list.
Meanwhile, the output visitor could already output explicit null
via type_any, but this gives us finer control.

At any rate, it's easy to test that we can round-trip an explicit
null through manual use of visit_type_null() wrapped by a virtual
visit_start_struct() walk, even if we can't do the visit in a
QAPI type.  Repurpose the test_visitor_out_empty test,
particularly since a future patch will tighten semantics to
forbid use of qmp_output_get_qobject() without at least one
intervening visit_type_*.

Signed-off-by: Eric Blake <eblake@redhat.com>

---
v15: hoist stubs to earlier patch, testsuite improvements
v14: rebase to header inclusion cleanups
v13: no change
v12: retitle and merge in output visitor support, move refcount
tests to separate file
[no v10, v11]
v9: new patch
---
 qapi/qmp-input-visitor.c        |  8 +++++++-
 qapi/qmp-output-visitor.c       |  4 ++--
 tests/check-qnull.c             | 13 +++++++++++--
 tests/test-qmp-input-visitor.c  | 26 ++++++++++++++++++++++++++
 tests/test-qmp-output-visitor.c | 20 +++++++++++++++-----
 5 files changed, 61 insertions(+), 10 deletions(-)

diff --git a/qapi/qmp-input-visitor.c b/qapi/qmp-input-visitor.c
index 739bbd5..7e94be6 100644
--- a/qapi/qmp-input-visitor.c
+++ b/qapi/qmp-input-visitor.c
@@ -342,7 +342,13 @@ static void qmp_input_type_any(Visitor *v, const char *name, QObject **obj,

 static void qmp_input_type_null(Visitor *v, const char *name, Error **errp)
 {
-    error_setg(errp, "FIXME: Implement");
+    QmpInputVisitor *qiv = to_qiv(v);
+    QObject *qobj = qmp_input_get_object(qiv, name, true);
+
+    if (qobject_type(qobj) != QTYPE_QNULL) {
+        error_setg(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null",
+                   "null");
+    }
 }

 static void qmp_input_optional(Visitor *v, const char *name, bool *present)
diff --git a/qapi/qmp-output-visitor.c b/qapi/qmp-output-visitor.c
index a67e3e8..5681ad3 100644
--- a/qapi/qmp-output-visitor.c
+++ b/qapi/qmp-output-visitor.c
@@ -18,7 +18,6 @@
 #include "qemu/queue.h"
 #include "qemu-common.h"
 #include "qapi/qmp/types.h"
-#include "qapi/error.h"

 typedef struct QStackEntry
 {
@@ -199,7 +198,8 @@ static void qmp_output_type_any(Visitor *v, const char *name, QObject **obj,

 static void qmp_output_type_null(Visitor *v, const char *name, Error **errp)
 {
-    error_setg(errp, "FIXME: Implement");
+    QmpOutputVisitor *qov = to_qov(v);
+    qmp_output_add_obj(qov, name, qnull());
 }

 /* Finish building, and return the root object. Will not be NULL. */
diff --git a/tests/check-qnull.c b/tests/check-qnull.c
index 4a1c3d8..fd9c68f 100644
--- a/tests/check-qnull.c
+++ b/tests/check-qnull.c
@@ -11,7 +11,9 @@

 #include "qapi/qmp/qobject.h"
 #include "qemu-common.h"
+#include "qapi/qmp-input-visitor.h"
 #include "qapi/qmp-output-visitor.h"
+#include "qapi/error.h"

 /*
  * Public Interface test-cases
@@ -37,6 +39,7 @@ static void qnull_visit_test(void)
 {
     QObject *obj;
     QmpOutputVisitor *qov;
+    QmpInputVisitor *qiv;

     /*
      * Most tests of interactions between QObject and visitors are in
@@ -45,13 +48,19 @@ static void qnull_visit_test(void)
      */

     g_assert(qnull_.refcnt == 1);
+    obj = qnull();
+    qiv = qmp_input_visitor_new(obj, true);
+    qobject_decref(obj);
+    visit_type_null(qmp_input_get_visitor(qiv), NULL, &error_abort);
+    qmp_input_visitor_cleanup(qiv);
+
     qov = qmp_output_visitor_new();
-    /* FIXME: Empty visits are ugly, we should have a visit_type_null(). */
+    visit_type_null(qmp_output_get_visitor(qov), NULL, &error_abort);
     obj = qmp_output_get_qobject(qov);
     g_assert(obj == &qnull_);
     qobject_decref(obj);
-
     qmp_output_visitor_cleanup(qov);
+
     g_assert(qnull_.refcnt == 1);
 }

diff --git a/tests/test-qmp-input-visitor.c b/tests/test-qmp-input-visitor.c
index c039806..3b6ae92 100644
--- a/tests/test-qmp-input-visitor.c
+++ b/tests/test-qmp-input-visitor.c
@@ -279,6 +279,30 @@ static void test_visitor_in_any(TestInputVisitorData *data,
     qobject_decref(res);
 }

+static void test_visitor_in_null(TestInputVisitorData *data,
+                                 const void *unused)
+{
+    Visitor *v;
+    Error *err = NULL;
+    char *tmp;
+
+    /*
+     * FIXME: Since QAPI doesn't know the 'null' type yet, we can't
+     * test visit_type_null() by reading into a QAPI struct then
+     * checking that it was populated correctly.  The best we can do
+     * for now is ensure that we consumed null from the input, proven
+     * by the fact that we can't re-read the key.
+     */
+
+    v = visitor_input_test_init(data, "{ 'a': null }");
+    visit_start_struct(v, NULL, NULL, 0, &error_abort);
+    visit_type_null(v, "a", &error_abort);
+    visit_type_str(v, "a", &tmp, &err);
+    g_assert(err);
+    g_assert(!tmp);
+    visit_end_struct(v, &error_abort);
+}
+
 static void test_visitor_in_union_flat(TestInputVisitorData *data,
                                        const void *unused)
 {
@@ -829,6 +853,8 @@ int main(int argc, char **argv)
                            &in_visitor_data, test_visitor_in_list);
     input_visitor_test_add("/visitor/input/any",
                            &in_visitor_data, test_visitor_in_any);
+    input_visitor_test_add("/visitor/input/null",
+                           &in_visitor_data, test_visitor_in_null);
     input_visitor_test_add("/visitor/input/union-flat",
                            &in_visitor_data, test_visitor_in_union_flat);
     input_visitor_test_add("/visitor/input/alternate",
diff --git a/tests/test-qmp-output-visitor.c b/tests/test-qmp-output-visitor.c
index fddb5a6..8acc229 100644
--- a/tests/test-qmp-output-visitor.c
+++ b/tests/test-qmp-output-visitor.c
@@ -477,13 +477,23 @@ static void test_visitor_out_alternate(TestOutputVisitorData *data,
     qobject_decref(arg);
 }

-static void test_visitor_out_empty(TestOutputVisitorData *data,
-                                   const void *unused)
+static void test_visitor_out_null(TestOutputVisitorData *data,
+                                  const void *unused)
 {
     QObject *arg;
+    QDict *qdict;
+    QObject *nil;

+    visit_start_struct(data->ov, NULL, NULL, 0, &error_abort);
+    visit_type_null(data->ov, "a", &error_abort);
+    visit_end_struct(data->ov, &error_abort);
     arg = qmp_output_get_qobject(data->qov);
-    g_assert(qobject_type(arg) == QTYPE_QNULL);
+    g_assert(qobject_type(arg) == QTYPE_QDICT);
+    qdict = qobject_to_qdict(arg);
+    g_assert_cmpint(qdict_size(qdict), ==, 1);
+    nil = qdict_get(qdict, "a");
+    g_assert(nil);
+    g_assert(qobject_type(nil) == QTYPE_QNULL);
     qobject_decref(arg);
 }

@@ -837,8 +847,8 @@ int main(int argc, char **argv)
                             &out_visitor_data, test_visitor_out_union_flat);
     output_visitor_test_add("/visitor/output/alternate",
                             &out_visitor_data, test_visitor_out_alternate);
-    output_visitor_test_add("/visitor/output/empty",
-                            &out_visitor_data, test_visitor_out_empty);
+    output_visitor_test_add("/visitor/output/null",
+                            &out_visitor_data, test_visitor_out_null);
     output_visitor_test_add("/visitor/output/native_list/int",
                             &out_visitor_data,
                             test_visitor_out_native_list_int);
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [Qemu-devel] [PATCH v15 16/23] spapr_drc: Expose 'null' in qom-get when there is no fdt
  2016-04-28  0:01 [Qemu-devel] [PATCH v15 00/23] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
                   ` (14 preceding siblings ...)
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 15/23] qmp: Support explicit null during visits Eric Blake
@ 2016-04-28  0:01 ` Eric Blake
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 17/23] qmp: Add qmp_output_visitor_reset() Eric Blake
                   ` (7 subsequent siblings)
  23 siblings, 0 replies; 49+ messages in thread
From: Eric Blake @ 2016-04-28  0:01 UTC (permalink / raw)
  To: qemu-devel; +Cc: armbru, David Gibson, Alexander Graf, open list:sPAPR

Now that the QMP output visitor supports an explicit null
output, we should utilize it to make it easier to diagnose
the difference between a missing fdt ('null') vs. a
present-but-empty one ('{}').

(Note that this reverts the behavior of commit ab8bf1d, taking
us back to the behavior of commit 6c2f9a1 [which in turn
stemmed from a crash fix in 1d10b44]; but that this time,
the change is intentional and not an accidental side-effect.)

Signed-off-by: Eric Blake <eblake@redhat.com>
Acked-by: David Gibson <david@gibson.dropbear.id.au>

---
v15: no change
v14: no change
v13: no change
v12: tweak commit message
[no v10, v11]
v9: improved commit message
v8: rebase to 'name' motion
v7: new patch, based on discussion about spapr_drc.c
---
 hw/ppc/spapr_drc.c | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/hw/ppc/spapr_drc.c b/hw/ppc/spapr_drc.c
index 3173940..72d725c 100644
--- a/hw/ppc/spapr_drc.c
+++ b/hw/ppc/spapr_drc.c
@@ -269,11 +269,7 @@ static void prop_get_fdt(Object *obj, Visitor *v, const char *name,
     void *fdt;

     if (!drc->fdt) {
-        visit_start_struct(v, name, NULL, 0, &err);
-        if (!err) {
-            visit_end_struct(v, &err);
-        }
-        error_propagate(errp, err);
+        visit_type_null(v, NULL, errp);
         return;
     }

-- 
2.5.5

^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [Qemu-devel] [PATCH v15 17/23] qmp: Add qmp_output_visitor_reset()
  2016-04-28  0:01 [Qemu-devel] [PATCH v15 00/23] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
                   ` (15 preceding siblings ...)
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 16/23] spapr_drc: Expose 'null' in qom-get when there is no fdt Eric Blake
@ 2016-04-28  0:01 ` Eric Blake
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 18/23] qmp: Tighten output visitor rules Eric Blake
                   ` (6 subsequent siblings)
  23 siblings, 0 replies; 49+ messages in thread
From: Eric Blake @ 2016-04-28  0:01 UTC (permalink / raw)
  To: qemu-devel; +Cc: armbru, Michael Roth

Add a new qmp_output_visitor_reset(), to make it easier for a
caller to reset all state while still reusing an existing
visitor, regardless of whether the previous visit was
successfully completed.  Then use it in the testsuite.

The tests needing patching were found by tightening asserts
in the QMP output visitor; see the next patch.  An audit of
all other users of qmp_output_visitor_new() did not find any
other attempts to reuse a visitor.

Signed-off-by: Eric Blake <eblake@redhat.com>

---
v15: new patch, split off of v14 13/19
---
 include/qapi/qmp-output-visitor.h | 1 +
 qapi/qmp-output-visitor.c         | 8 +++++++-
 tests/test-qmp-output-visitor.c   | 6 ++++++
 3 files changed, 14 insertions(+), 1 deletion(-)

diff --git a/include/qapi/qmp-output-visitor.h b/include/qapi/qmp-output-visitor.h
index 2266770..5093f0d 100644
--- a/include/qapi/qmp-output-visitor.h
+++ b/include/qapi/qmp-output-visitor.h
@@ -21,6 +21,7 @@ typedef struct QmpOutputVisitor QmpOutputVisitor;

 QmpOutputVisitor *qmp_output_visitor_new(void);
 void qmp_output_visitor_cleanup(QmpOutputVisitor *v);
+void qmp_output_visitor_reset(QmpOutputVisitor *v);

 QObject *qmp_output_get_qobject(QmpOutputVisitor *v);
 Visitor *qmp_output_get_visitor(QmpOutputVisitor *v);
diff --git a/qapi/qmp-output-visitor.c b/qapi/qmp-output-visitor.c
index 5681ad3..6c44210 100644
--- a/qapi/qmp-output-visitor.c
+++ b/qapi/qmp-output-visitor.c
@@ -221,7 +221,7 @@ Visitor *qmp_output_get_visitor(QmpOutputVisitor *v)
     return &v->visitor;
 }

-void qmp_output_visitor_cleanup(QmpOutputVisitor *v)
+void qmp_output_visitor_reset(QmpOutputVisitor *v)
 {
     QStackEntry *e, *tmp;

@@ -231,6 +231,12 @@ void qmp_output_visitor_cleanup(QmpOutputVisitor *v)
     }

     qobject_decref(v->root);
+    v->root = NULL;
+}
+
+void qmp_output_visitor_cleanup(QmpOutputVisitor *v)
+{
+    qmp_output_visitor_reset(v);
     g_free(v);
 }

diff --git a/tests/test-qmp-output-visitor.c b/tests/test-qmp-output-visitor.c
index 8acc229..0e83099 100644
--- a/tests/test-qmp-output-visitor.c
+++ b/tests/test-qmp-output-visitor.c
@@ -139,6 +139,7 @@ static void test_visitor_out_enum(TestOutputVisitorData *data,
         g_assert_cmpstr(qstring_get_str(qobject_to_qstring(obj)), ==,
                         EnumOne_lookup[i]);
         qobject_decref(obj);
+        qmp_output_visitor_reset(data->qov);
     }
 }

@@ -153,6 +154,7 @@ static void test_visitor_out_enum_errors(TestOutputVisitorData *data,
         visit_type_EnumOne(data->ov, "unused", &bad_values[i], &err);
         g_assert(err);
         error_free(err);
+        qmp_output_visitor_reset(data->qov);
     }
 }

@@ -262,6 +264,7 @@ static void test_visitor_out_struct_errors(TestOutputVisitorData *data,
         visit_type_UserDefOne(data->ov, "unused", &pu, &err);
         g_assert(err);
         error_free(err);
+        qmp_output_visitor_reset(data->qov);
     }
 }

@@ -366,6 +369,7 @@ static void test_visitor_out_any(TestOutputVisitorData *data,
     qobject_decref(obj);
     qobject_decref(qobj);

+    qmp_output_visitor_reset(data->qov);
     qdict = qdict_new();
     qdict_put(qdict, "integer", qint_from_int(-42));
     qdict_put(qdict, "boolean", qbool_from_bool(true));
@@ -442,6 +446,7 @@ static void test_visitor_out_alternate(TestOutputVisitorData *data,
     qapi_free_UserDefAlternate(tmp);
     qobject_decref(arg);

+    qmp_output_visitor_reset(data->qov);
     tmp = g_new0(UserDefAlternate, 1);
     tmp->type = QTYPE_QSTRING;
     tmp->u.s = g_strdup("hello");
@@ -455,6 +460,7 @@ static void test_visitor_out_alternate(TestOutputVisitorData *data,
     qapi_free_UserDefAlternate(tmp);
     qobject_decref(arg);

+    qmp_output_visitor_reset(data->qov);
     tmp = g_new0(UserDefAlternate, 1);
     tmp->type = QTYPE_QDICT;
     tmp->u.udfu.integer = 1;
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [Qemu-devel] [PATCH v15 18/23] qmp: Tighten output visitor rules
  2016-04-28  0:01 [Qemu-devel] [PATCH v15 00/23] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
                   ` (16 preceding siblings ...)
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 17/23] qmp: Add qmp_output_visitor_reset() Eric Blake
@ 2016-04-28  0:01 ` Eric Blake
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 19/23] qapi: Split visit_end_struct() into pieces Eric Blake
                   ` (5 subsequent siblings)
  23 siblings, 0 replies; 49+ messages in thread
From: Eric Blake @ 2016-04-28  0:01 UTC (permalink / raw)
  To: qemu-devel; +Cc: armbru, Michael Roth

Tighten assertions in the QMP output visitor, so that:

- qmp_output_get_qobject() can only be called after pairing a
visit_end_* for every visit_start_* (rather than allowing it on
a partially built object)

- qmp_output_get_qobject() cannot be called unless at least one
visit_type_* or visit_start/visit_end pair has occurred since
creation/reset (the accidental return of NULL fixed by commit
ab8bf1d7 would have been much easier to diagnose)

- ensure that we are encountering the expected object or list
type, to provide protection against mismatched push(struct)/
pop(list) or push(list)/pop(struct), similar to the qmp-input
protection added in commit bdd8e6b5.

- ensure that except for the root, 'name' is non-null inside a
dict, and NULL inside a list (this may need changing later if
we add "name.0" support for better error messages for a list,
but for now it makes sure all users are at least consistent)

Signed-off-by: Eric Blake <eblake@redhat.com>

---
v15: split off qmp_output_visitor_reset(), improve comments,
use QTAILQ_EMPTY
v14: no change
v13: no change
v12: rebase to latest, move type_null() into earlier patches,
don't change signature of pop, don't auto-reset after a single
get_qobject
[no v10, v11]
v9: rebase to added patch, squash in more sanity checks, drop
Marc-Andre's R-b
v8: rename qmp_output_reset to qmp_output_visitor_reset
v7: new patch, based on discussion about spapr_drc.c

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 qapi/qmp-output-visitor.c | 30 +++++++++++++++---------------
 1 file changed, 15 insertions(+), 15 deletions(-)

diff --git a/qapi/qmp-output-visitor.c b/qapi/qmp-output-visitor.c
index 6c44210..7155bde 100644
--- a/qapi/qmp-output-visitor.c
+++ b/qapi/qmp-output-visitor.c
@@ -82,9 +82,8 @@ static void qmp_output_add_obj(QmpOutputVisitor *qov, const char *name,
     QObject *cur = e ? e->value : NULL;

     if (!cur) {
-        /* FIXME we should require the user to reset the visitor, rather
-         * than throwing away the previous root */
-        qobject_decref(qov->root);
+        /* Don't allow reuse of visitor on more than one root */
+        assert(!qov->root);
         qov->root = value;
     } else {
         switch (qobject_type(cur)) {
@@ -93,6 +92,7 @@ static void qmp_output_add_obj(QmpOutputVisitor *qov, const char *name,
             qdict_put_obj(qobject_to_qdict(cur), name, value);
             break;
         case QTYPE_QLIST:
+            assert(!name);
             qlist_append_obj(qobject_to_qlist(cur), value);
             break;
         default:
@@ -114,7 +114,8 @@ static void qmp_output_start_struct(Visitor *v, const char *name, void **obj,
 static void qmp_output_end_struct(Visitor *v, Error **errp)
 {
     QmpOutputVisitor *qov = to_qov(v);
-    qmp_output_pop(qov);
+    QObject *value = qmp_output_pop(qov);
+    assert(qobject_type(value) == QTYPE_QDICT);
 }

 static void qmp_output_start_list(Visitor *v, const char *name, Error **errp)
@@ -145,7 +146,8 @@ static GenericList *qmp_output_next_list(Visitor *v, GenericList **listp,
 static void qmp_output_end_list(Visitor *v)
 {
     QmpOutputVisitor *qov = to_qov(v);
-    qmp_output_pop(qov);
+    QObject *value = qmp_output_pop(qov);
+    assert(qobject_type(value) == QTYPE_QLIST);
 }

 static void qmp_output_type_int64(Visitor *v, const char *name, int64_t *obj,
@@ -202,18 +204,16 @@ static void qmp_output_type_null(Visitor *v, const char *name, Error **errp)
     qmp_output_add_obj(qov, name, qnull());
 }

-/* Finish building, and return the root object. Will not be NULL. */
+/* Finish building, and return the root object.
+ * The root object is never null. The caller becomes the object's
+ * owner, and should use qobject_decref() when done with it.  */
 QObject *qmp_output_get_qobject(QmpOutputVisitor *qov)
 {
-    /* FIXME: we should require that a visit occurred, and that it is
-     * complete (no starts without a matching end) */
-    QObject *obj = qov->root;
-    if (obj) {
-        qobject_incref(obj);
-    } else {
-        obj = qnull();
-    }
-    return obj;
+    /* A visit must have occurred, with each start paired with end.  */
+    assert(qov->root && QTAILQ_EMPTY(&qov->stack));
+
+    qobject_incref(qov->root);
+    return qov->root;
 }

 Visitor *qmp_output_get_visitor(QmpOutputVisitor *v)
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [Qemu-devel] [PATCH v15 19/23] qapi: Split visit_end_struct() into pieces
  2016-04-28  0:01 [Qemu-devel] [PATCH v15 00/23] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
                   ` (17 preceding siblings ...)
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 18/23] qmp: Tighten output visitor rules Eric Blake
@ 2016-04-28  0:01 ` Eric Blake
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 20/23] tests/string-input-visitor: Add negative integer tests Eric Blake
                   ` (4 subsequent siblings)
  23 siblings, 0 replies; 49+ messages in thread
From: Eric Blake @ 2016-04-28  0:01 UTC (permalink / raw)
  To: qemu-devel
  Cc: armbru, Kevin Wolf, Max Reitz, Michael Roth, David Gibson,
	Alexander Graf, Michael S. Tsirkin, Andreas Färber,
	open list:Block layer core, open list:sPAPR

As mentioned in previous patches, we want to call visit_end_struct()
functions unconditionally, so that visitors can release resources
tied up since the matching visit_start_struct() without also having
to worry about error priority if more than one error occurs.

Even though error_propagate() can be safely used to ignore a second
error during cleanup caused by a first error, it is simpler if the
cleanup cannot set an error.  So, split out the error checking
portion (basically, input visitors checking for unvisited keys) into
a new function visit_check_struct(), which can be safely skipped if
any earlier errors are encountered, and leave the cleanup portion
(which never fails, but must be called unconditionally if
visit_start_struct() succeeded) in visit_end_struct().

Generated code in qapi-visit.c has diffs resembling:

|@@ -59,10 +59,12 @@ void visit_type_ACPIOSTInfo(Visitor *v,
|         goto out_obj;
|     }
|     visit_type_ACPIOSTInfo_members(v, obj, &err);
|-    error_propagate(errp, err);
|-    err = NULL;
|+    if (err) {
|+        goto out_obj;
|+    }
|+    visit_check_struct(v, &err);
| out_obj:
|-    visit_end_struct(v, &err);
|+    visit_end_struct(v);
| out:

and in qapi-event.c:

@@ -47,7 +47,10 @@ void qapi_event_send_acpi_device_ost(ACP
|         goto out;
|     }
|     visit_type_q_obj_ACPI_DEVICE_OST_arg_members(v, &param, &err);
|-    visit_end_struct(v, err ? NULL : &err);
|+    if (!err) {
|+        visit_check_struct(v, &err);
|+    }
|+    visit_end_struct(v);
|     if (err) {
|         goto out;

Signed-off-by: Eric Blake <eblake@redhat.com>

---
v15: rebase, simplify user_creatable_add
v14: rebase to master
v13: rebase to earlier changes
v12: rebase to earlier changes, fix bug in spapr_drc not calling
visit_end_struct, fold in docs, fix stray DO_UPCAST from sneaking
back in
[no v10, v11]
v9: rebase to earlier changes, drop Marc-Andre's R-b
v8: rebase to 'name' motion
v7: rebase to earlier changes
v6: new patch, revised version of RFC based on discussion of v5 7/46
---
 include/qapi/visitor.h          | 21 ++++++++++++++++-----
 include/qapi/visitor-impl.h     |  5 ++++-
 scripts/qapi-commands.py        |  7 +++++--
 scripts/qapi-event.py           |  5 ++++-
 scripts/qapi-visit.py           | 15 +++++++++------
 qapi/qapi-visit-core.c          | 11 +++++++++--
 block/crypto.c                  | 14 ++++++++------
 hw/ppc/spapr_drc.c              |  3 ++-
 hw/virtio/virtio-balloon.c      | 15 ++++++++-------
 qapi/opts-visitor.c             | 17 +++++++++++++++--
 qapi/qapi-dealloc-visitor.c     |  2 +-
 qapi/qmp-input-visitor.c        | 35 ++++++++++++++++++++---------------
 qapi/qmp-output-visitor.c       |  2 +-
 qom/object.c                    |  5 ++---
 qom/object_interfaces.c         | 25 +++++++++++--------------
 tests/test-qmp-input-visitor.c  |  3 ++-
 tests/test-qmp-output-visitor.c |  3 ++-
 docs/qapi-code-gen.txt          | 15 ++++++++++-----
 18 files changed, 129 insertions(+), 74 deletions(-)

diff --git a/include/qapi/visitor.h b/include/qapi/visitor.h
index e79c09e..c882da6 100644
--- a/include/qapi/visitor.h
+++ b/include/qapi/visitor.h
@@ -191,10 +191,11 @@
  *  }
  * outlist:
  *  visit_end_list(v);
+ *  if (!err) {
+ *      visit_check_struct(v, &err);
+ *  }
  * outobj:
- *  error_propagate(errp, err);
- *  err = NULL;
- *  visit_end_struct(v, &err);
+ *  visit_end_struct(v);
  *  error_propagate(errp, err);
  *  ...clean up v...
  * </example>
@@ -247,17 +248,27 @@ void visit_start_struct(Visitor *v, const char *name, void **obj,
                         size_t size, Error **errp);

 /*
- * Complete an object visit started earlier.
+ * Prepare for completing an object visit.
  *
  * @errp obeys typical error usage, and reports failures such as
  * unparsed keys remaining in the input stream.
  *
+ * Should be called prior to visit_end_struct() if all other
+ * intermediate visit steps were successful, to allow the visitor one
+ * last chance to report errors.  May be skipped on a cleanup path,
+ * where there is no need to check for further errors.
+ */
+void visit_check_struct(Visitor *v, Error **errp);
+
+/*
+ * Complete an object visit started earlier.
+ *
  * Must be called after any successful use of visit_start_struct(),
  * even if intermediate processing was skipped due to errors, to allow
  * the backend to release any resources.  Destroying the visitor early
  * behaves as if this was implicitly called.
  */
-void visit_end_struct(Visitor *v, Error **errp);
+void visit_end_struct(Visitor *v);


 /*** Visiting lists ***/
diff --git a/include/qapi/visitor-impl.h b/include/qapi/visitor-impl.h
index 88d27d5..b20a922 100644
--- a/include/qapi/visitor-impl.h
+++ b/include/qapi/visitor-impl.h
@@ -43,8 +43,11 @@ struct Visitor
     void (*start_struct)(Visitor *v, const char *name, void **obj,
                          size_t size, Error **errp);

+    /* Optional; intended for input visitors */
+    void (*check_struct)(Visitor *v, Error **errp);
+
     /* Must be set to visit structs */
-    void (*end_struct)(Visitor *v, Error **errp);
+    void (*end_struct)(Visitor *v);

     /* Must be set */
     void (*start_list)(Visitor *v, const char *name, Error **errp);
diff --git a/scripts/qapi-commands.py b/scripts/qapi-commands.py
index beb34c5..ef03dc4 100644
--- a/scripts/qapi-commands.py
+++ b/scripts/qapi-commands.py
@@ -126,7 +126,10 @@ def gen_marshal(name, arg_type, ret_type):
         goto out;
     }
     visit_type_%(c_name)s_members(v, &arg, &err);
-    visit_end_struct(v, err ? NULL : &err);
+    if (!err) {
+        visit_check_struct(v, &err);
+    }
+    visit_end_struct(v);
     if (err) {
         goto out;
     }
@@ -161,7 +164,7 @@ out:
     v = qapi_dealloc_get_visitor(qdv);
     visit_start_struct(v, NULL, NULL, 0, NULL);
     visit_type_%(c_name)s_members(v, &arg, NULL);
-    visit_end_struct(v, NULL);
+    visit_end_struct(v);
     qapi_dealloc_visitor_cleanup(qdv);
 ''',
                      c_name=arg_type.c_name())
diff --git a/scripts/qapi-event.py b/scripts/qapi-event.py
index 9b5c5b5..21fb167 100644
--- a/scripts/qapi-event.py
+++ b/scripts/qapi-event.py
@@ -98,7 +98,10 @@ def gen_event_send(name, arg_type):
         goto out;
     }
     visit_type_%(c_name)s_members(v, &param, &err);
-    visit_end_struct(v, err ? NULL : &err);
+    if (!err) {
+        visit_check_struct(v, &err);
+    }
+    visit_end_struct(v);
     if (err) {
         goto out;
     }
diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
index 31d2330..bdf8971 100644
--- a/scripts/qapi-visit.py
+++ b/scripts/qapi-visit.py
@@ -186,9 +186,10 @@ void visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s **obj, Error
             break;
         }
         visit_type_%(c_type)s_members(v, &(*obj)->u.%(c_name)s, &err);
-        error_propagate(errp, err);
-        err = NULL;
-        visit_end_struct(v, &err);
+        if (!err) {
+            visit_check_struct(v, &err);
+        }
+        visit_end_struct(v);
 ''',
                          c_type=var.type.c_name(),
                          c_name=c_name(var.name))
@@ -236,10 +237,12 @@ void visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s **obj, Error
         goto out_obj;
     }
     visit_type_%(c_name)s_members(v, *obj, &err);
-    error_propagate(errp, err);
-    err = NULL;
+    if (err) {
+        goto out_obj;
+    }
+    visit_check_struct(v, &err);
 out_obj:
-    visit_end_struct(v, &err);
+    visit_end_struct(v);
 out:
     error_propagate(errp, err);
 }
diff --git a/qapi/qapi-visit-core.c b/qapi/qapi-visit-core.c
index 7eaa75c..138990d 100644
--- a/qapi/qapi-visit-core.c
+++ b/qapi/qapi-visit-core.c
@@ -36,9 +36,16 @@ void visit_start_struct(Visitor *v, const char *name, void **obj,
     error_propagate(errp, err);
 }

-void visit_end_struct(Visitor *v, Error **errp)
+void visit_check_struct(Visitor *v, Error **errp)
 {
-    v->end_struct(v, errp);
+    if (v->check_struct) {
+        v->check_struct(v, errp);
+    }
+}
+
+void visit_end_struct(Visitor *v)
+{
+    v->end_struct(v);
 }

 void visit_start_list(Visitor *v, const char *name, Error **errp)
diff --git a/block/crypto.c b/block/crypto.c
index 1903e84..2424a4c 100644
--- a/block/crypto.c
+++ b/block/crypto.c
@@ -196,7 +196,6 @@ block_crypto_open_opts_init(QCryptoBlockFormat format,
     OptsVisitor *ov;
     QCryptoBlockOpenOptions *ret = NULL;
     Error *local_err = NULL;
-    Error *end_err = NULL;

     ret = g_new0(QCryptoBlockOpenOptions, 1);
     ret->format = format;
@@ -219,9 +218,11 @@ block_crypto_open_opts_init(QCryptoBlockFormat format,
         error_setg(&local_err, "Unsupported block format %d", format);
         break;
     }
+    if (!local_err) {
+        visit_check_struct(opts_get_visitor(ov), &local_err);
+    }

-    visit_end_struct(opts_get_visitor(ov), &end_err);
-    error_propagate(&local_err, end_err);
+    visit_end_struct(opts_get_visitor(ov));

  out:
     if (local_err) {
@@ -242,7 +243,6 @@ block_crypto_create_opts_init(QCryptoBlockFormat format,
     OptsVisitor *ov;
     QCryptoBlockCreateOptions *ret = NULL;
     Error *local_err = NULL;
-    Error *end_err = NULL;

     ret = g_new0(QCryptoBlockCreateOptions, 1);
     ret->format = format;
@@ -265,9 +265,11 @@ block_crypto_create_opts_init(QCryptoBlockFormat format,
         error_setg(&local_err, "Unsupported block format %d", format);
         break;
     }
+    if (!local_err) {
+        visit_check_struct(opts_get_visitor(ov), &local_err);
+    }

-    visit_end_struct(opts_get_visitor(ov), &end_err);
-    error_propagate(&local_err, end_err);
+    visit_end_struct(opts_get_visitor(ov));

  out:
     if (local_err) {
diff --git a/hw/ppc/spapr_drc.c b/hw/ppc/spapr_drc.c
index 72d725c..5395c02 100644
--- a/hw/ppc/spapr_drc.c
+++ b/hw/ppc/spapr_drc.c
@@ -297,7 +297,8 @@ static void prop_get_fdt(Object *obj, Visitor *v, const char *name,
         case FDT_END_NODE:
             /* shouldn't ever see an FDT_END_NODE before FDT_BEGIN_NODE */
             g_assert(fdt_depth > 0);
-            visit_end_struct(v, &err);
+            visit_check_struct(v, &err);
+            visit_end_struct(v);
             if (err) {
                 error_propagate(errp, err);
                 return;
diff --git a/hw/virtio/virtio-balloon.c b/hw/virtio/virtio-balloon.c
index 9dbe681..8c15e09 100644
--- a/hw/virtio/virtio-balloon.c
+++ b/hw/virtio/virtio-balloon.c
@@ -138,17 +138,18 @@ static void balloon_stats_get_all(Object *obj, Visitor *v, const char *name,
     for (i = 0; i < VIRTIO_BALLOON_S_NR; i++) {
         visit_type_uint64(v, balloon_stat_names[i], &s->stats[i], &err);
         if (err) {
-            break;
+            goto out_nested;
         }
     }
-    error_propagate(errp, err);
-    err = NULL;
-    visit_end_struct(v, &err);
+    visit_check_struct(v, &err);
+out_nested:
+    visit_end_struct(v);

+    if (!err) {
+        visit_check_struct(v, &err);
+    }
 out_end:
-    error_propagate(errp, err);
-    err = NULL;
-    visit_end_struct(v, &err);
+    visit_end_struct(v);
 out:
     error_propagate(errp, err);
 }
diff --git a/qapi/opts-visitor.c b/qapi/opts-visitor.c
index 4cb6436..ece0598 100644
--- a/qapi/opts-visitor.c
+++ b/qapi/opts-visitor.c
@@ -159,13 +159,13 @@ opts_start_struct(Visitor *v, const char *name, void **obj,


 static void
-opts_end_struct(Visitor *v, Error **errp)
+opts_check_struct(Visitor *v, Error **errp)
 {
     OptsVisitor *ov = to_ov(v);
     GHashTableIter iter;
     GQueue *any;

-    if (--ov->depth > 0) {
+    if (ov->depth > 0) {
         return;
     }

@@ -177,6 +177,18 @@ opts_end_struct(Visitor *v, Error **errp)
         first = g_queue_peek_head(any);
         error_setg(errp, QERR_INVALID_PARAMETER, first->name);
     }
+}
+
+
+static void
+opts_end_struct(Visitor *v)
+{
+    OptsVisitor *ov = to_ov(v);
+
+    if (--ov->depth > 0) {
+        return;
+    }
+
     g_hash_table_destroy(ov->unprocessed_opts);
     ov->unprocessed_opts = NULL;
     if (ov->fake_id_opt) {
@@ -516,6 +528,7 @@ opts_visitor_new(const QemuOpts *opts)
     ov->visitor.type = VISITOR_INPUT;

     ov->visitor.start_struct = &opts_start_struct;
+    ov->visitor.check_struct = &opts_check_struct;
     ov->visitor.end_struct   = &opts_end_struct;

     ov->visitor.start_list = &opts_start_list;
diff --git a/qapi/qapi-dealloc-visitor.c b/qapi/qapi-dealloc-visitor.c
index 413d525..9005bad 100644
--- a/qapi/qapi-dealloc-visitor.c
+++ b/qapi/qapi-dealloc-visitor.c
@@ -67,7 +67,7 @@ static void qapi_dealloc_start_struct(Visitor *v, const char *name, void **obj,
     qapi_dealloc_push(qov, obj);
 }

-static void qapi_dealloc_end_struct(Visitor *v, Error **errp)
+static void qapi_dealloc_end_struct(Visitor *v)
 {
     QapiDeallocVisitor *qov = to_qov(v);
     void **obj = qapi_dealloc_pop(qov);
diff --git a/qapi/qmp-input-visitor.c b/qapi/qmp-input-visitor.c
index 7e94be6..ebabc36 100644
--- a/qapi/qmp-input-visitor.c
+++ b/qapi/qmp-input-visitor.c
@@ -125,9 +125,11 @@ static void qmp_input_push(QmpInputVisitor *qiv, QObject *obj, Error **errp)
 }


-static void qmp_input_pop(QmpInputVisitor *qiv, Error **errp)
+static void qmp_input_check_struct(Visitor *v, Error **errp)
 {
+    QmpInputVisitor *qiv = to_qiv(v);
     StackObject *tos = &qiv->stack[qiv->nb_stack - 1];
+
     assert(qiv->nb_stack > 0);

     if (qiv->strict) {
@@ -140,6 +142,20 @@ static void qmp_input_pop(QmpInputVisitor *qiv, Error **errp)
             if (g_hash_table_iter_next(&iter, (void **)&key, NULL)) {
                 error_setg(errp, QERR_QMP_EXTRA_MEMBER, key);
             }
+        }
+    }
+}
+
+static void qmp_input_pop(Visitor *v)
+{
+    QmpInputVisitor *qiv = to_qiv(v);
+    StackObject *tos = &qiv->stack[qiv->nb_stack - 1];
+
+    assert(qiv->nb_stack > 0);
+
+    if (qiv->strict) {
+        GHashTable * const top_ht = qiv->stack[qiv->nb_stack - 1].h;
+        if (top_ht) {
             g_hash_table_unref(top_ht);
         }
         tos->h = NULL;
@@ -175,12 +191,6 @@ static void qmp_input_start_struct(Visitor *v, const char *name, void **obj,
     }
 }

-static void qmp_input_end_struct(Visitor *v, Error **errp)
-{
-    QmpInputVisitor *qiv = to_qiv(v);
-
-    qmp_input_pop(qiv, errp);
-}

 static void qmp_input_start_list(Visitor *v, const char *name, Error **errp)
 {
@@ -218,12 +228,6 @@ static GenericList *qmp_input_next_list(Visitor *v, GenericList **list,
     return entry;
 }

-static void qmp_input_end_list(Visitor *v)
-{
-    QmpInputVisitor *qiv = to_qiv(v);
-
-    qmp_input_pop(qiv, &error_abort);
-}

 static void qmp_input_start_alternate(Visitor *v, const char *name,
                                       GenericAlternate **obj, size_t size,
@@ -383,10 +387,11 @@ QmpInputVisitor *qmp_input_visitor_new(QObject *obj, bool strict)

     v->visitor.type = VISITOR_INPUT;
     v->visitor.start_struct = qmp_input_start_struct;
-    v->visitor.end_struct = qmp_input_end_struct;
+    v->visitor.check_struct = qmp_input_check_struct;
+    v->visitor.end_struct = qmp_input_pop;
     v->visitor.start_list = qmp_input_start_list;
     v->visitor.next_list = qmp_input_next_list;
-    v->visitor.end_list = qmp_input_end_list;
+    v->visitor.end_list = qmp_input_pop;
     v->visitor.start_alternate = qmp_input_start_alternate;
     v->visitor.type_int64 = qmp_input_type_int64;
     v->visitor.type_uint64 = qmp_input_type_uint64;
diff --git a/qapi/qmp-output-visitor.c b/qapi/qmp-output-visitor.c
index 7155bde..a66d754 100644
--- a/qapi/qmp-output-visitor.c
+++ b/qapi/qmp-output-visitor.c
@@ -111,7 +111,7 @@ static void qmp_output_start_struct(Visitor *v, const char *name, void **obj,
     qmp_output_push(qov, dict);
 }

-static void qmp_output_end_struct(Visitor *v, Error **errp)
+static void qmp_output_end_struct(Visitor *v)
 {
     QmpOutputVisitor *qov = to_qov(v);
     QObject *value = qmp_output_pop(qov);
diff --git a/qom/object.c b/qom/object.c
index 8e6e68d..3bc8a00 100644
--- a/qom/object.c
+++ b/qom/object.c
@@ -2036,10 +2036,9 @@ static void property_get_tm(Object *obj, Visitor *v, const char *name,
     if (err) {
         goto out_end;
     }
+    visit_check_struct(v, &err);
 out_end:
-    error_propagate(errp, err);
-    err = NULL;
-    visit_end_struct(v, errp);
+    visit_end_struct(v);
 out:
     error_propagate(errp, err);

diff --git a/qom/object_interfaces.c b/qom/object_interfaces.c
index 4a60d6d..bae7ca5 100644
--- a/qom/object_interfaces.c
+++ b/qom/object_interfaces.c
@@ -42,7 +42,7 @@ Object *user_creatable_add(const QDict *qdict,
     char *type = NULL;
     char *id = NULL;
     Object *obj = NULL;
-    Error *local_err = NULL, *end_err = NULL;
+    Error *local_err = NULL;
     QDict *pdict;

     pdict = qdict_clone_shallow(qdict);
@@ -63,21 +63,15 @@ Object *user_creatable_add(const QDict *qdict,
     if (local_err) {
         goto out_visit;
     }
+    visit_check_struct(v, &local_err);
+    if (local_err) {
+        goto out_visit;
+    }

     obj = user_creatable_add_type(type, id, pdict, v, &local_err);
-    if (local_err) {
-        goto out_visit;
-    }

- out_visit:
-    visit_end_struct(v, &end_err);
-    if (end_err) {
-        error_propagate(&local_err, end_err);
-        if (obj) {
-            user_creatable_del(id, NULL);
-        }
-        goto out;
-    }
+out_visit:
+    visit_end_struct(v);

 out:
     QDECREF(pdict);
@@ -130,7 +124,10 @@ Object *user_creatable_add_type(const char *type, const char *id,
                 break;
             }
         }
-        visit_end_struct(v, local_err ? NULL : &local_err);
+        if (!local_err) {
+            visit_check_struct(v, &local_err);
+        }
+        visit_end_struct(v);
         if (local_err) {
             goto out;
         }
diff --git a/tests/test-qmp-input-visitor.c b/tests/test-qmp-input-visitor.c
index 3b6ae92..70eec9f 100644
--- a/tests/test-qmp-input-visitor.c
+++ b/tests/test-qmp-input-visitor.c
@@ -300,7 +300,8 @@ static void test_visitor_in_null(TestInputVisitorData *data,
     visit_type_str(v, "a", &tmp, &err);
     g_assert(err);
     g_assert(!tmp);
-    visit_end_struct(v, &error_abort);
+    visit_check_struct(v, &error_abort);
+    visit_end_struct(v);
 }

 static void test_visitor_in_union_flat(TestInputVisitorData *data,
diff --git a/tests/test-qmp-output-visitor.c b/tests/test-qmp-output-visitor.c
index 0e83099..0706fe9 100644
--- a/tests/test-qmp-output-visitor.c
+++ b/tests/test-qmp-output-visitor.c
@@ -492,7 +492,8 @@ static void test_visitor_out_null(TestOutputVisitorData *data,

     visit_start_struct(data->ov, NULL, NULL, 0, &error_abort);
     visit_type_null(data->ov, "a", &error_abort);
-    visit_end_struct(data->ov, &error_abort);
+    visit_check_struct(data->ov, &error_abort);
+    visit_end_struct(data->ov);
     arg = qmp_output_get_qobject(data->qov);
     g_assert(qobject_type(arg) == QTYPE_QDICT);
     qdict = qobject_to_qdict(arg);
diff --git a/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt
index b4ae1be..d135847 100644
--- a/docs/qapi-code-gen.txt
+++ b/docs/qapi-code-gen.txt
@@ -899,10 +899,12 @@ Example:
             goto out_obj;
         }
         visit_type_UserDefOne_members(v, *obj, &err);
-        error_propagate(errp, err);
-        err = NULL;
+        if (err) {
+            goto out_obj;
+        }
+        visit_check_struct(v, &err);
     out_obj:
-        visit_end_struct(v, &err);
+        visit_end_struct(v);
     out:
         error_propagate(errp, err);
     }
@@ -1007,7 +1009,10 @@ Example:
             goto out;
         }
         visit_type_UserDefOneList(v, "arg1", &arg1, &err);
-        visit_end_struct(v, err ? NULL : &err);
+        if (!err) {
+            visit_check_struct(v, &err);
+        }
+        visit_end_struct(v);
         if (err) {
             goto out;
         }
@@ -1026,7 +1031,7 @@ Example:
         v = qapi_dealloc_get_visitor(qdv);
         visit_start_struct(v, NULL, NULL, 0, NULL);
         visit_type_UserDefOneList(v, "arg1", &arg1, NULL);
-        visit_end_struct(v, NULL);
+        visit_end_struct(v);
         qapi_dealloc_visitor_cleanup(qdv);
     }

-- 
2.5.5

^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [Qemu-devel] [PATCH v15 20/23] tests/string-input-visitor: Add negative integer tests
  2016-04-28  0:01 [Qemu-devel] [PATCH v15 00/23] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
                   ` (18 preceding siblings ...)
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 19/23] qapi: Split visit_end_struct() into pieces Eric Blake
@ 2016-04-28  0:01 ` Eric Blake
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 21/23] qapi: Fix string input visitor handling of invalid list Eric Blake
                   ` (3 subsequent siblings)
  23 siblings, 0 replies; 49+ messages in thread
From: Eric Blake @ 2016-04-28  0:01 UTC (permalink / raw)
  To: qemu-devel; +Cc: armbru, Michael Roth

From: Markus Armbruster <armbru@redhat.com>

Add two negative tests, one for int and one for int16List.  The latter
exposes a bug: nonsensical input results in an empty list instead of
an error.

Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <1461325048-14122-1-git-send-email-armbru@redhat.com>
Signed-off-by: Eric Blake <eblake@redhat.com>

---
v15: new patch
---
 tests/test-string-input-visitor.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/tests/test-string-input-visitor.c b/tests/test-string-input-visitor.c
index 9e6906a..8114908 100644
--- a/tests/test-string-input-visitor.c
+++ b/tests/test-string-input-visitor.c
@@ -63,6 +63,13 @@ static void test_visitor_in_int(TestInputVisitorData *data,
     visit_type_int(v, NULL, &res, &err);
     g_assert(!err);
     g_assert_cmpint(res, ==, value);
+
+    visitor_input_teardown(data, unused);
+
+    v = visitor_input_test_init(data, "not an int");
+
+    visit_type_int(v, NULL, &res, &err);
+    error_free_or_abort(&err);
 }

 static void test_visitor_in_intList(TestInputVisitorData *data,
@@ -70,6 +77,7 @@ static void test_visitor_in_intList(TestInputVisitorData *data,
 {
     int64_t value[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 20};
     int16List *res = NULL, *tmp;
+    Error *err = NULL;
     Visitor *v;
     int i = 0;

@@ -90,6 +98,13 @@ static void test_visitor_in_intList(TestInputVisitorData *data,
         g_free(tmp);
         tmp = res;
     }
+
+    visitor_input_teardown(data, unused);
+
+    v = visitor_input_test_init(data, "not an int list");
+
+    visit_type_int16List(v, NULL, &res, &err);
+    /* FIXME fix the visitor, then error_free_or_abort(&err) here */
 }

 static void test_visitor_in_bool(TestInputVisitorData *data,
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [Qemu-devel] [PATCH v15 21/23] qapi: Fix string input visitor handling of invalid list
  2016-04-28  0:01 [Qemu-devel] [PATCH v15 00/23] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
                   ` (19 preceding siblings ...)
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 20/23] tests/string-input-visitor: Add negative integer tests Eric Blake
@ 2016-04-28  0:01 ` Eric Blake
  2016-04-28 17:18   ` Markus Armbruster
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 22/23] qapi: Simplify semantics of visit_next_list() Eric Blake
                   ` (2 subsequent siblings)
  23 siblings, 1 reply; 49+ messages in thread
From: Eric Blake @ 2016-04-28  0:01 UTC (permalink / raw)
  To: qemu-devel; +Cc: armbru, Michael Roth

As shown in the previous commit, the string input visitor was
treating bogus input as an empty list rather than an error.
Fix parse_str() to set errp, then the callers to exit early if
an error was reported.  Also, make sure the error message
for visit_type_uint64() gracefully handles a NULL 'name' when
called from the top level or a list context.

Meanwhile, fix the testsuite to use the generated
qapi_free_int16List() instead of rolling our own, and to
validate the fixed behavior, while at the same time documenting
one more change that we'd like to make in a later patch (a
failed visit_start_list should guarantee a NULL pointer,
regardless of what things were on input).

Signed-off-by: Eric Blake <eblake@redhat.com>

---
v15: new patch
---
 qapi/string-input-visitor.c       | 19 +++++++++++++------
 tests/test-string-input-visitor.c | 12 +++++-------
 2 files changed, 18 insertions(+), 13 deletions(-)

diff --git a/qapi/string-input-visitor.c b/qapi/string-input-visitor.c
index 797973a..ad150dc 100644
--- a/qapi/string-input-visitor.c
+++ b/qapi/string-input-visitor.c
@@ -44,7 +44,7 @@ static void free_range(void *range, void *dummy)
     g_free(range);
 }

-static void parse_str(StringInputVisitor *siv, Error **errp)
+static int parse_str(StringInputVisitor *siv, const char *name, Error **errp)
 {
     char *str = (char *) siv->string;
     long long start, end;
@@ -52,7 +52,7 @@ static void parse_str(StringInputVisitor *siv, Error **errp)
     char *endptr;

     if (siv->ranges) {
-        return;
+        return 0;
     }

     do {
@@ -117,11 +117,14 @@ static void parse_str(StringInputVisitor *siv, Error **errp)
         }
     } while (str);

-    return;
+    return 0;
 error:
     g_list_foreach(siv->ranges, free_range, NULL);
     g_list_free(siv->ranges);
     siv->ranges = NULL;
+    error_setg(errp, QERR_INVALID_PARAMETER_VALUE, name ? name : "null",
+               "an int64 value or range");
+    return -1;
 }

 static void
@@ -129,7 +132,9 @@ start_list(Visitor *v, const char *name, Error **errp)
 {
     StringInputVisitor *siv = to_siv(v);

-    parse_str(siv, errp);
+    if (parse_str(siv, name, errp) < 0) {
+        return;
+    }

     siv->cur_range = g_list_first(siv->ranges);
     if (siv->cur_range) {
@@ -195,7 +200,9 @@ static void parse_type_int64(Visitor *v, const char *name, int64_t *obj,
         return;
     }

-    parse_str(siv, errp);
+    if (parse_str(siv, name, errp) < 0) {
+        return;
+    }

     if (!siv->ranges) {
         goto error;
@@ -222,7 +229,7 @@ static void parse_type_int64(Visitor *v, const char *name, int64_t *obj,
     return;

 error:
-    error_setg(errp, QERR_INVALID_PARAMETER_VALUE, name,
+    error_setg(errp, QERR_INVALID_PARAMETER_VALUE, name ? name : "null",
                "an int64 value or range");
 }

diff --git a/tests/test-string-input-visitor.c b/tests/test-string-input-visitor.c
index 8114908..f99824d 100644
--- a/tests/test-string-input-visitor.c
+++ b/tests/test-string-input-visitor.c
@@ -92,19 +92,17 @@ static void test_visitor_in_intList(TestInputVisitorData *data,
     }
     g_assert(!tmp);

-    tmp = res;
-    while (tmp) {
-        res = res->next;
-        g_free(tmp);
-        tmp = res;
-    }
+    qapi_free_int16List(res);

     visitor_input_teardown(data, unused);

     v = visitor_input_test_init(data, "not an int list");

+    /* FIXME: res should be NULL on failure, regardless of starting value */
+    res = NULL;
     visit_type_int16List(v, NULL, &res, &err);
-    /* FIXME fix the visitor, then error_free_or_abort(&err) here */
+    error_free_or_abort(&err);
+    g_assert(!res);
 }

 static void test_visitor_in_bool(TestInputVisitorData *data,
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [Qemu-devel] [PATCH v15 22/23] qapi: Simplify semantics of visit_next_list()
  2016-04-28  0:01 [Qemu-devel] [PATCH v15 00/23] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
                   ` (20 preceding siblings ...)
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 21/23] qapi: Fix string input visitor handling of invalid list Eric Blake
@ 2016-04-28  0:01 ` Eric Blake
  2016-04-28 15:44   ` Eric Blake
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 23/23] qapi: Change visit_type_FOO() to no longer return partial objects Eric Blake
  2016-04-28 18:03 ` [Qemu-devel] [PATCH v15 00/23] qapi visitor cleanups (post-introspection cleanups subset E) Markus Armbruster
  23 siblings, 1 reply; 49+ messages in thread
From: Eric Blake @ 2016-04-28  0:01 UTC (permalink / raw)
  To: qemu-devel
  Cc: armbru, Michael Roth, David Gibson, Alexander Graf, open list:sPAPR

The semantics of the list visit are somewhat baroque, with the
following pseudocode when FooList is used:

start()
for (prev = head; cur = next(prev); prev = &cur) {
    visit(&cur->value)
}

Note that these semantics (advance before visit) requires that
the first call to next() return the list head, while all other
calls return the next element of the list; that is, every visitor
implementation is required to track extra state to decide whether
to return the input as-is, or to advance.  It also requires an
argument of 'GenericList **' to next(), solely because the first
iteration might need to modify the caller's GenericList head, so
that all other calls have to do a layer of dereferencing.

Thankfully, we only have two uses of list visits in the entire
code base: one in spapr_drc (which completely avoids
visit_next_list(), feeding in integers from a different source
than uint8List), and one in qapi-visit.py.  That is, all other
list visitors are generated in qapi-visit.c, and share the same
paradigm based on a qapi FooList type, so we can refactor how
lists are laid out with minimal churn among clients.

We can greatly simplify things by hoisting the special case
into the start() routine, and flipping the order in the loop
to visit before advance:

start(head)
for (tail = *head; tail; tail = next(tail)) {
    visit(&tail->value)
}

With the simpler semantics, visitors have less state to track,
the argument to next() is reduced to 'GenericList *', and it
also becomes obvious whether an input visitor is allocating a
FooList during visit_start_list() (rather than the old way of
not knowing if an allocation happened until the first
visit_next_list()).  As a minor drawback, we now allocate in
two functions instead of one, and have to pass the size to
both functions (unless we were to tweak the input visitors to
cache the size to start_list for reuse during next_list, but
that defeats the goal of less visitor state).

The signature of visit_start_list() is chosen to match
visit_start_struct(), with the new parameters after 'name'.

The spapr_drc case is a virtual visit, done by passing NULL for
list, similarly to how NULL is passed to visit_start_struct()
when a qapi type is not used in those visits.  It was easy to
provide these semantics for qmp-output and dealloc visitors,
and a bit harder for qmp-input (several prerequisite patches
refactored things to make this patch straightforward).  But it
turned out that the string and opts visitors munge enough other
state during visit_next_list() to make it easier to just
document and require a GenericList visit for now; an assertion
will remind us to adjust things if we need the semantics in the
future.

Several pre-requisite cleanup patches made the reshuffling of
the various visitors easier; particularly the qmp input visitor.

Signed-off-by: Eric Blake <eblake@redhat.com>

---
v15: address review comments, tweak commit message, rebase atop
fix for string-input list bug, have qmp input push() return entry
rather than taking another parameter
v14: no change
v13: documentation wording tweaks
v12: rebase to earlier changes, drop R-b, improve documentation,
split out qmp-input cleanups into prereq patches
[no v10, v11]
v9: no change
v8: consistent parameter order, fix qmp_input_get_next_type() to not
skip list entries
v7: new patch
---
 include/qapi/visitor.h               | 49 +++++++++++++++++++-----------------
 include/qapi/visitor-impl.h          |  8 +++---
 scripts/qapi-visit.py                | 16 ++++++------
 include/qapi/opts-visitor.h          |  3 ++-
 include/qapi/string-input-visitor.h  |  3 ++-
 include/qapi/string-output-visitor.h |  3 ++-
 qapi/qapi-visit-core.c               | 12 +++++----
 hw/ppc/spapr_drc.c                   |  2 +-
 qapi/opts-visitor.c                  | 33 +++++++++++-------------
 qapi/qapi-dealloc-visitor.c          | 35 ++++++--------------------
 qapi/qmp-input-visitor.c             | 40 +++++++++++++++--------------
 qapi/qmp-output-visitor.c            | 22 ++++------------
 qapi/string-input-visitor.c          | 29 +++++++++------------
 qapi/string-output-visitor.c         | 41 +++++++++++-------------------
 tests/test-string-input-visitor.c    |  2 --
 docs/qapi-code-gen.txt               | 16 ++++++------
 16 files changed, 136 insertions(+), 178 deletions(-)

diff --git a/include/qapi/visitor.h b/include/qapi/visitor.h
index c882da6..4f2a460 100644
--- a/include/qapi/visitor.h
+++ b/include/qapi/visitor.h
@@ -175,7 +175,7 @@
  *  if (err) {
  *      goto outobj;
  *  }
- *  visit_start_list(v, "list", &err);
+ *  visit_start_list(v, "list", NULL, 0, &err);
  *  if (err) {
  *      goto outobj;
  *  }
@@ -279,19 +279,27 @@ void visit_end_struct(Visitor *v);
  * @name expresses the relationship of this list to its parent
  * container; see the general description of @name above.
  *
+ * @list must be non-NULL for a real walk, in which case @size
+ * determines how much memory an input visitor will allocate into
+ * *@list (at least sizeof(GenericList)).  Some visitors also allow
+ * @list to be NULL for a virtual walk, in which case @size is
+ * ignored.
+ *
  * @errp obeys typical error usage, and reports failures such as a
- * member @name is not present, or present but not a list.
+ * member @name is not present, or present but not a list.  On error,
+ * input visitors set *@list to NULL.
  *
  * After visit_start_list() succeeds, the caller may visit its members
- * one after the other.  A real visit uses visit_next_list() for
- * traversing the linked list, while a virtual visit uses other means.
- * For each list element, call the appropriate visit_type_FOO() with
- * name set to NULL and obj set to the address of the value member of
- * the list element.  Finally, visit_end_list() needs to be called to
- * clean up, even if intermediate visits fail.  See the examples
- * above.
+ * one after the other.  A real visit (where @obj is non-NULL) uses
+ * visit_next_list() for traversing the linked list, while a virtual
+ * visit (where @obj is NULL) uses other means.  For each list
+ * element, call the appropriate visit_type_FOO() with name set to
+ * NULL and obj set to the address of the value member of the list
+ * element.  Finally, visit_end_list() needs to be called to clean up,
+ * even if intermediate visits fail.  See the examples above.
  */
-void visit_start_list(Visitor *v, const char *name, Error **errp);
+void visit_start_list(Visitor *v, const char *name, GenericList **list,
+                      size_t size, Error **errp);

 /*
  * Iterate over a GenericList during a non-virtual list visit.
@@ -299,20 +307,15 @@ void visit_start_list(Visitor *v, const char *name, Error **errp);
  * @size represents the size of a linked list node (at least
  * sizeof(GenericList)).
  *
- * @list must not be NULL; on the first call, @list contains the
- * address of the list head, and on subsequent calls *@list must be
- * the previously returned value.  Should be called in a loop until a
- * NULL return or error occurs; for each non-NULL return, the caller
- * then calls the appropriate visit_type_*() for the element type
- * of the list, with that function's name parameter set to NULL and
- * obj set to the address of (*@list)->value.
- *
- * FIXME: This interface is awkward; it requires all callbacks to
- * track whether it is the first or a subsequent call.  A better
- * interface would pass the head of the list through
- * visit_start_list().
+ * @tail must not be NULL; on the first call, @tail is the value of
+ * *list after visit_start_list(), and on subsequent calls @tail must
+ * be the previously returned value.  Should be called in a loop until
+ * a NULL return or error occurs; for each non-NULL return, the caller
+ * then calls the appropriate visit_type_*() for the element type of
+ * the list, with that function's name parameter set to NULL and obj
+ * set to the address of @tail->value.
  */
-GenericList *visit_next_list(Visitor *v, GenericList **list, size_t size);
+GenericList *visit_next_list(Visitor *v, GenericList *tail, size_t size);

 /*
  * Complete a list visit started earlier.
diff --git a/include/qapi/visitor-impl.h b/include/qapi/visitor-impl.h
index b20a922..145afd0 100644
--- a/include/qapi/visitor-impl.h
+++ b/include/qapi/visitor-impl.h
@@ -49,11 +49,13 @@ struct Visitor
     /* Must be set to visit structs */
     void (*end_struct)(Visitor *v);

-    /* Must be set */
-    void (*start_list)(Visitor *v, const char *name, Error **errp);
+    /* Must be set; implementations may require @list to be non-null,
+     * but must document it. */
+    void (*start_list)(Visitor *v, const char *name, GenericList **list,
+                       size_t size, Error **errp);

     /* Must be set */
-    GenericList *(*next_list)(Visitor *v, GenericList **list, size_t size);
+    GenericList *(*next_list)(Visitor *v, GenericList *tail, size_t size);

     /* Must be set */
     void (*end_list)(Visitor *v);
diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
index bdf8971..8b7efcc 100644
--- a/scripts/qapi-visit.py
+++ b/scripts/qapi-visit.py
@@ -117,18 +117,20 @@ def gen_visit_list(name, element_type):
 void visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s **obj, Error **errp)
 {
     Error *err = NULL;
-    GenericList *i, **prev;
+    %(c_name)s *tail;
+    size_t size = sizeof(**obj);

-    visit_start_list(v, name, &err);
+    visit_start_list(v, name, (GenericList **)obj, size, &err);
     if (err) {
         goto out;
     }

-    for (prev = (GenericList **)obj;
-         !err && (i = visit_next_list(v, prev, sizeof(**obj))) != NULL;
-         prev = &i) {
-        %(c_name)s *native_i = (%(c_name)s *)i;
-        visit_type_%(c_elt_type)s(v, NULL, &native_i->value, &err);
+    for (tail = *obj; tail;
+         tail = (%(c_name)s *)visit_next_list(v, (GenericList *)tail, size)) {
+        visit_type_%(c_elt_type)s(v, NULL, &tail->value, &err);
+        if (err) {
+            break;
+        }
     }

     visit_end_list(v);
diff --git a/include/qapi/opts-visitor.h b/include/qapi/opts-visitor.h
index fe37ed9..ae1bf7c 100644
--- a/include/qapi/opts-visitor.h
+++ b/include/qapi/opts-visitor.h
@@ -32,7 +32,8 @@ typedef struct OptsVisitor OptsVisitor;
  *
  * The Opts input visitor does not implement support for visiting QAPI
  * alternates, numbers (other than integers), null, or arbitrary
- * QTypes.
+ * QTypes.  It also requires a non-null list argument to
+ * visit_start_list().
  */
 OptsVisitor *opts_visitor_new(const QemuOpts *opts);
 void opts_visitor_cleanup(OptsVisitor *nv);
diff --git a/include/qapi/string-input-visitor.h b/include/qapi/string-input-visitor.h
index a8d8f67..7b76c2b 100644
--- a/include/qapi/string-input-visitor.h
+++ b/include/qapi/string-input-visitor.h
@@ -19,7 +19,8 @@ typedef struct StringInputVisitor StringInputVisitor;

 /*
  * The string input visitor does not implement support for visiting
- * QAPI structs, alternates, null, or arbitrary QTypes.
+ * QAPI structs, alternates, null, or arbitrary QTypes.  It also
+ * requires a non-null list argument to visit_start_list().
  */
 StringInputVisitor *string_input_visitor_new(const char *str);
 void string_input_visitor_cleanup(StringInputVisitor *v);
diff --git a/include/qapi/string-output-visitor.h b/include/qapi/string-output-visitor.h
index 89b7e4b..e10522a 100644
--- a/include/qapi/string-output-visitor.h
+++ b/include/qapi/string-output-visitor.h
@@ -19,7 +19,8 @@ typedef struct StringOutputVisitor StringOutputVisitor;

 /*
  * The string output visitor does not implement support for visiting
- * QAPI structs, alternates, null, or arbitrary QTypes.
+ * QAPI structs, alternates, null, or arbitrary QTypes.  It also
+ * requires a non-null list argument to visit_start_list().
  */
 StringOutputVisitor *string_output_visitor_new(bool human);
 void string_output_visitor_cleanup(StringOutputVisitor *v);
diff --git a/qapi/qapi-visit-core.c b/qapi/qapi-visit-core.c
index 138990d..e6d57f3 100644
--- a/qapi/qapi-visit-core.c
+++ b/qapi/qapi-visit-core.c
@@ -48,15 +48,17 @@ void visit_end_struct(Visitor *v)
     v->end_struct(v);
 }

-void visit_start_list(Visitor *v, const char *name, Error **errp)
+void visit_start_list(Visitor *v, const char *name, GenericList **list,
+                      size_t size, Error **errp)
 {
-    v->start_list(v, name, errp);
+    assert(!list || size >= sizeof(GenericList));
+    v->start_list(v, name, list, size, errp);
 }

-GenericList *visit_next_list(Visitor *v, GenericList **list, size_t size)
+GenericList *visit_next_list(Visitor *v, GenericList *tail, size_t size)
 {
-    assert(list && size >= sizeof(GenericList));
-    return v->next_list(v, list, size);
+    assert(tail && size >= sizeof(GenericList));
+    return v->next_list(v, tail, size);
 }

 void visit_end_list(Visitor *v)
diff --git a/hw/ppc/spapr_drc.c b/hw/ppc/spapr_drc.c
index 5395c02..dd3c502 100644
--- a/hw/ppc/spapr_drc.c
+++ b/hw/ppc/spapr_drc.c
@@ -309,7 +309,7 @@ static void prop_get_fdt(Object *obj, Visitor *v, const char *name,
             int i;
             prop = fdt_get_property_by_offset(fdt, fdt_offset, &prop_len);
             name = fdt_string(fdt, fdt32_to_cpu(prop->nameoff));
-            visit_start_list(v, name, &err);
+            visit_start_list(v, name, NULL, 0, &err);
             if (err) {
                 error_propagate(errp, err);
                 return;
diff --git a/qapi/opts-visitor.c b/qapi/opts-visitor.c
index ece0598..4cf1cf8 100644
--- a/qapi/opts-visitor.c
+++ b/qapi/opts-visitor.c
@@ -23,9 +23,8 @@
 enum ListMode
 {
     LM_NONE,             /* not traversing a list of repeated options */
-    LM_STARTED,          /* opts_start_list() succeeded */

-    LM_IN_PROGRESS,      /* opts_next_list() has been called.
+    LM_IN_PROGRESS,      /* opts_next_list() ready to be called.
                           *
                           * Generating the next list link will consume the most
                           * recently parsed QemuOpt instance of the repeated
@@ -214,35 +213,33 @@ lookup_distinct(const OptsVisitor *ov, const char *name, Error **errp)


 static void
-opts_start_list(Visitor *v, const char *name, Error **errp)
+opts_start_list(Visitor *v, const char *name, GenericList **list, size_t size,
+                Error **errp)
 {
     OptsVisitor *ov = to_ov(v);

     /* we can't traverse a list in a list */
     assert(ov->list_mode == LM_NONE);
+    /* we don't support visits without a list */
+    assert(list);
     ov->repeated_opts = lookup_distinct(ov, name, errp);
-    if (ov->repeated_opts != NULL) {
-        ov->list_mode = LM_STARTED;
+    if (ov->repeated_opts) {
+        ov->list_mode = LM_IN_PROGRESS;
+        *list = g_malloc0(size);
+    } else {
+        *list = NULL;
     }
 }


 static GenericList *
-opts_next_list(Visitor *v, GenericList **list, size_t size)
+opts_next_list(Visitor *v, GenericList *tail, size_t size)
 {
     OptsVisitor *ov = to_ov(v);
-    GenericList **link;

     switch (ov->list_mode) {
-    case LM_STARTED:
-        ov->list_mode = LM_IN_PROGRESS;
-        link = list;
-        break;
-
     case LM_SIGNED_INTERVAL:
     case LM_UNSIGNED_INTERVAL:
-        link = &(*list)->next;
-
         if (ov->list_mode == LM_SIGNED_INTERVAL) {
             if (ov->range_next.s < ov->range_limit.s) {
                 ++ov->range_next.s;
@@ -263,7 +260,6 @@ opts_next_list(Visitor *v, GenericList **list, size_t size)
             g_hash_table_remove(ov->unprocessed_opts, opt->name);
             return NULL;
         }
-        link = &(*list)->next;
         break;
     }

@@ -271,8 +267,8 @@ opts_next_list(Visitor *v, GenericList **list, size_t size)
         abort();
     }

-    *link = g_malloc0(size);
-    return *link;
+    tail->next = g_malloc0(size);
+    return tail->next;
 }


@@ -281,8 +277,7 @@ opts_end_list(Visitor *v)
 {
     OptsVisitor *ov = to_ov(v);

-    assert(ov->list_mode == LM_STARTED ||
-           ov->list_mode == LM_IN_PROGRESS ||
+    assert(ov->list_mode == LM_IN_PROGRESS ||
            ov->list_mode == LM_SIGNED_INTERVAL ||
            ov->list_mode == LM_UNSIGNED_INTERVAL);
     ov->repeated_opts = NULL;
diff --git a/qapi/qapi-dealloc-visitor.c b/qapi/qapi-dealloc-visitor.c
index 9005bad..cd68b55 100644
--- a/qapi/qapi-dealloc-visitor.c
+++ b/qapi/qapi-dealloc-visitor.c
@@ -22,7 +22,6 @@
 typedef struct StackEntry
 {
     void *value;
-    bool is_list_head;
     QTAILQ_ENTRY(StackEntry) node;
 } StackEntry;

@@ -43,10 +42,6 @@ static void qapi_dealloc_push(QapiDeallocVisitor *qov, void *value)

     e->value = value;

-    /* see if we're just pushing a list head tracker */
-    if (value == NULL) {
-        e->is_list_head = true;
-    }
     QTAILQ_INSERT_HEAD(&qov->stack, e, node);
 }

@@ -93,38 +88,22 @@ static void qapi_dealloc_end_alternate(Visitor *v)
     }
 }

-static void qapi_dealloc_start_list(Visitor *v, const char *name, Error **errp)
+static void qapi_dealloc_start_list(Visitor *v, const char *name,
+                                    GenericList **list, size_t size,
+                                    Error **errp)
 {
-    QapiDeallocVisitor *qov = to_qov(v);
-    qapi_dealloc_push(qov, NULL);
 }

-static GenericList *qapi_dealloc_next_list(Visitor *v, GenericList **listp,
+static GenericList *qapi_dealloc_next_list(Visitor *v, GenericList *tail,
                                            size_t size)
 {
-    GenericList *list = *listp;
-    QapiDeallocVisitor *qov = to_qov(v);
-    StackEntry *e = QTAILQ_FIRST(&qov->stack);
-
-    if (e && e->is_list_head) {
-        e->is_list_head = false;
-        return list;
-    }
-
-    if (list) {
-        list = list->next;
-        g_free(*listp);
-        return list;
-    }
-
-    return NULL;
+    GenericList *next = tail->next;
+    g_free(tail);
+    return next;
 }

 static void qapi_dealloc_end_list(Visitor *v)
 {
-    QapiDeallocVisitor *qov = to_qov(v);
-    void *obj = qapi_dealloc_pop(qov);
-    assert(obj == NULL); /* should've been list head tracker with no payload */
 }

 static void qapi_dealloc_type_str(Visitor *v, const char *name, char **obj,
diff --git a/qapi/qmp-input-visitor.c b/qapi/qmp-input-visitor.c
index ebabc36..f3993f4 100644
--- a/qapi/qmp-input-visitor.c
+++ b/qapi/qmp-input-visitor.c
@@ -29,7 +29,6 @@ typedef struct StackObject

     GHashTable *h;           /* If obj is dict: unvisited keys */
     const QListEntry *entry; /* If obj is list: unvisited tail */
-    bool first;              /* If obj is list: will next_list() visit head */
 } StackObject;

 struct QmpInputVisitor
@@ -81,7 +80,6 @@ static QObject *qmp_input_get_object(QmpInputVisitor *qiv,
     } else {
         assert(qobject_type(qobj) == QTYPE_QLIST);
         assert(!name);
-        assert(!tos->first);
         ret = qlist_entry_obj(tos->entry);
         if (consume) {
             tos->entry = qlist_next(tos->entry);
@@ -97,7 +95,8 @@ static void qdict_add_key(const char *key, QObject *obj, void *opaque)
     g_hash_table_insert(h, (gpointer) key, NULL);
 }

-static void qmp_input_push(QmpInputVisitor *qiv, QObject *obj, Error **errp)
+static const QListEntry *qmp_input_push(QmpInputVisitor *qiv, QObject *obj,
+                                        Error **errp)
 {
     GHashTable *h;
     StackObject *tos = &qiv->stack[qiv->nb_stack];
@@ -105,7 +104,7 @@ static void qmp_input_push(QmpInputVisitor *qiv, QObject *obj, Error **errp)
     assert(obj);
     if (qiv->nb_stack >= QIV_STACK_SIZE) {
         error_setg(errp, "An internal buffer overran");
-        return;
+        return NULL;
     }

     tos->obj = obj;
@@ -118,10 +117,10 @@ static void qmp_input_push(QmpInputVisitor *qiv, QObject *obj, Error **errp)
         tos->h = h;
     } else if (qobject_type(obj) == QTYPE_QLIST) {
         tos->entry = qlist_first(qobject_to_qlist(obj));
-        tos->first = true;
     }

     qiv->nb_stack++;
+    return tos->entry;
 }


@@ -192,40 +191,43 @@ static void qmp_input_start_struct(Visitor *v, const char *name, void **obj,
 }


-static void qmp_input_start_list(Visitor *v, const char *name, Error **errp)
+static void qmp_input_start_list(Visitor *v, const char *name,
+                                 GenericList **list, size_t size, Error **errp)
 {
     QmpInputVisitor *qiv = to_qiv(v);
     QObject *qobj = qmp_input_get_object(qiv, name, true);
+    const QListEntry *entry;

     if (!qobj || qobject_type(qobj) != QTYPE_QLIST) {
+        if (list) {
+            *list = NULL;
+        }
         error_setg(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null",
                    "list");
         return;
     }

-    qmp_input_push(qiv, qobj, errp);
+    entry = qmp_input_push(qiv, qobj, errp);
+    if (list) {
+        if (entry) {
+            *list = g_malloc0(size);
+        } else {
+            *list = NULL;
+        }
+    }
 }

-static GenericList *qmp_input_next_list(Visitor *v, GenericList **list,
+static GenericList *qmp_input_next_list(Visitor *v, GenericList *tail,
                                         size_t size)
 {
     QmpInputVisitor *qiv = to_qiv(v);
-    GenericList *entry;
     StackObject *so = &qiv->stack[qiv->nb_stack - 1];

     if (!so->entry) {
         return NULL;
     }
-
-    entry = g_malloc0(size);
-    if (so->first) {
-        *list = entry;
-        so->first = false;
-    } else {
-        (*list)->next = entry;
-    }
-
-    return entry;
+    tail->next = g_malloc0(size);
+    return tail->next;
 }


diff --git a/qapi/qmp-output-visitor.c b/qapi/qmp-output-visitor.c
index a66d754..ef9f62c 100644
--- a/qapi/qmp-output-visitor.c
+++ b/qapi/qmp-output-visitor.c
@@ -22,7 +22,6 @@
 typedef struct QStackEntry
 {
     QObject *value;
-    bool is_list_head;
     QTAILQ_ENTRY(QStackEntry) node;
 } QStackEntry;

@@ -52,9 +51,6 @@ static void qmp_output_push_obj(QmpOutputVisitor *qov, QObject *value)
     assert(qov->root);
     assert(value);
     e->value = value;
-    if (qobject_type(e->value) == QTYPE_QLIST) {
-        e->is_list_head = true;
-    }
     QTAILQ_INSERT_HEAD(&qov->stack, e, node);
 }

@@ -118,7 +114,9 @@ static void qmp_output_end_struct(Visitor *v)
     assert(qobject_type(value) == QTYPE_QDICT);
 }

-static void qmp_output_start_list(Visitor *v, const char *name, Error **errp)
+static void qmp_output_start_list(Visitor *v, const char *name,
+                                  GenericList **listp, size_t size,
+                                  Error **errp)
 {
     QmpOutputVisitor *qov = to_qov(v);
     QList *list = qlist_new();
@@ -127,20 +125,10 @@ static void qmp_output_start_list(Visitor *v, const char *name, Error **errp)
     qmp_output_push(qov, list);
 }

-static GenericList *qmp_output_next_list(Visitor *v, GenericList **listp,
+static GenericList *qmp_output_next_list(Visitor *v, GenericList *tail,
                                          size_t size)
 {
-    GenericList *list = *listp;
-    QmpOutputVisitor *qov = to_qov(v);
-    QStackEntry *e = QTAILQ_FIRST(&qov->stack);
-
-    assert(e);
-    if (e->is_list_head) {
-        e->is_list_head = false;
-        return list;
-    }
-
-    return list ? list->next : NULL;
+    return tail->next;
 }

 static void qmp_output_end_list(Visitor *v)
diff --git a/qapi/string-input-visitor.c b/qapi/string-input-visitor.c
index ad150dc..30b5879 100644
--- a/qapi/string-input-visitor.c
+++ b/qapi/string-input-visitor.c
@@ -25,8 +25,6 @@ struct StringInputVisitor
 {
     Visitor visitor;

-    bool head;
-
     GList *ranges;
     GList *cur_range;
     int64_t cur;
@@ -128,11 +126,16 @@ error:
 }

 static void
-start_list(Visitor *v, const char *name, Error **errp)
+start_list(Visitor *v, const char *name, GenericList **list, size_t size,
+           Error **errp)
 {
     StringInputVisitor *siv = to_siv(v);

+    /* We don't support visits without a list */
+    assert(list);
+
     if (parse_str(siv, name, errp) < 0) {
+        *list = NULL;
         return;
     }

@@ -142,13 +145,15 @@ start_list(Visitor *v, const char *name, Error **errp)
         if (r) {
             siv->cur = r->begin;
         }
+        *list = g_malloc0(size);
+    } else {
+        *list = NULL;
     }
 }

-static GenericList *next_list(Visitor *v, GenericList **list, size_t size)
+static GenericList *next_list(Visitor *v, GenericList *tail, size_t size)
 {
     StringInputVisitor *siv = to_siv(v);
-    GenericList **link;
     Range *r;

     if (!siv->ranges || !siv->cur_range) {
@@ -172,21 +177,12 @@ static GenericList *next_list(Visitor *v, GenericList **list, size_t size)
         siv->cur = r->begin;
     }

-    if (siv->head) {
-        link = list;
-        siv->head = false;
-    } else {
-        link = &(*list)->next;
-    }
-
-    *link = g_malloc0(size);
-    return *link;
+    tail->next = g_malloc0(size);
+    return tail->next;
 }

 static void end_list(Visitor *v)
 {
-    StringInputVisitor *siv = to_siv(v);
-    siv->head = true;
 }

 static void parse_type_int64(Visitor *v, const char *name, int64_t *obj,
@@ -369,6 +365,5 @@ StringInputVisitor *string_input_visitor_new(const char *str)
     v->visitor.optional = parse_optional;

     v->string = str;
-    v->head = true;
     return v;
 }
diff --git a/qapi/string-output-visitor.c b/qapi/string-output-visitor.c
index 0d44d7e..d013196 100644
--- a/qapi/string-output-visitor.c
+++ b/qapi/string-output-visitor.c
@@ -20,7 +20,7 @@

 enum ListMode {
     LM_NONE,             /* not traversing a list of repeated options */
-    LM_STARTED,          /* start_list() succeeded */
+    LM_STARTED,          /* next_list() ready to be called */

     LM_IN_PROGRESS,      /* next_list() has been called.
                           *
@@ -48,7 +48,7 @@ enum ListMode {

     LM_UNSIGNED_INTERVAL,/* Same as above, only for an unsigned interval. */

-    LM_END
+    LM_END,              /* next_list() called, about to see last element. */
 };

 typedef enum ListMode ListMode;
@@ -58,7 +58,6 @@ struct StringOutputVisitor
     Visitor visitor;
     bool human;
     GString *string;
-    bool head;
     ListMode list_mode;
     union {
         int64_t s;
@@ -266,39 +265,29 @@ static void print_type_number(Visitor *v, const char *name, double *obj,
 }

 static void
-start_list(Visitor *v, const char *name, Error **errp)
+start_list(Visitor *v, const char *name, GenericList **list, size_t size,
+           Error **errp)
 {
     StringOutputVisitor *sov = to_sov(v);

     /* we can't traverse a list in a list */
     assert(sov->list_mode == LM_NONE);
-    sov->list_mode = LM_STARTED;
-    sov->head = true;
+    /* We don't support visits without a list */
+    assert(list);
+    /* List handling is only needed if there are at least two elements */
+    if (*list && (*list)->next) {
+        sov->list_mode = LM_STARTED;
+    }
 }

-static GenericList *next_list(Visitor *v, GenericList **list, size_t size)
+static GenericList *next_list(Visitor *v, GenericList *tail, size_t size)
 {
     StringOutputVisitor *sov = to_sov(v);
-    GenericList *ret = NULL;
-    if (*list) {
-        if (sov->head) {
-            ret = *list;
-        } else {
-            ret = (*list)->next;
-        }
+    GenericList *ret = tail->next;

-        if (sov->head) {
-            if (ret && ret->next == NULL) {
-                sov->list_mode = LM_NONE;
-            }
-            sov->head = false;
-        } else {
-            if (ret && ret->next == NULL) {
-                sov->list_mode = LM_END;
-            }
-        }
+    if (ret && !ret->next) {
+        sov->list_mode = LM_END;
     }
-
     return ret;
 }

@@ -311,8 +300,6 @@ static void end_list(Visitor *v)
            sov->list_mode == LM_NONE ||
            sov->list_mode == LM_IN_PROGRESS);
     sov->list_mode = LM_NONE;
-    sov->head = true;
-
 }

 char *string_output_get_string(StringOutputVisitor *sov)
diff --git a/tests/test-string-input-visitor.c b/tests/test-string-input-visitor.c
index f99824d..5a56920 100644
--- a/tests/test-string-input-visitor.c
+++ b/tests/test-string-input-visitor.c
@@ -98,8 +98,6 @@ static void test_visitor_in_intList(TestInputVisitorData *data,

     v = visitor_input_test_init(data, "not an int list");

-    /* FIXME: res should be NULL on failure, regardless of starting value */
-    res = NULL;
     visit_type_int16List(v, NULL, &res, &err);
     error_free_or_abort(&err);
     g_assert(!res);
diff --git a/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt
index d135847..fe61c5c 100644
--- a/docs/qapi-code-gen.txt
+++ b/docs/qapi-code-gen.txt
@@ -912,18 +912,20 @@ Example:
     void visit_type_UserDefOneList(Visitor *v, const char *name, UserDefOneList **obj, Error **errp)
     {
         Error *err = NULL;
-        GenericList *i, **prev;
+        UserDefOneList *tail;
+        size_t size = sizeof(**obj);

-        visit_start_list(v, name, &err);
+        visit_start_list(v, name, (GenericList **)obj, size, &err);
         if (err) {
             goto out;
         }

-        for (prev = (GenericList **)obj;
-             !err && (i = visit_next_list(v, prev, sizeof(**obj))) != NULL;
-             prev = &i) {
-            UserDefOneList *native_i = (UserDefOneList *)i;
-            visit_type_UserDefOne(v, NULL, &native_i->value, &err);
+        for (tail = *obj; tail;
+             tail = (UserDefOneList *)visit_next_list(v, (GenericList *)tail, size)) {
+            visit_type_UserDefOne(v, NULL, &tail->value, &err);
+            if (err) {
+                break;
+            }
         }

         visit_end_list(v);
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [Qemu-devel] [PATCH v15 23/23] qapi: Change visit_type_FOO() to no longer return partial objects
  2016-04-28  0:01 [Qemu-devel] [PATCH v15 00/23] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
                   ` (21 preceding siblings ...)
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 22/23] qapi: Simplify semantics of visit_next_list() Eric Blake
@ 2016-04-28  0:01 ` Eric Blake
  2016-04-28 17:42   ` Markus Armbruster
  2016-04-28 18:03 ` [Qemu-devel] [PATCH v15 00/23] qapi visitor cleanups (post-introspection cleanups subset E) Markus Armbruster
  23 siblings, 1 reply; 49+ messages in thread
From: Eric Blake @ 2016-04-28  0:01 UTC (permalink / raw)
  To: qemu-devel; +Cc: armbru, Michael Roth

Returning a partial object on error is an invitation for a careless
caller to leak memory.  We already fixed things in an earlier
patch to guarantee NULL if visit_start fails ("qapi: Guarantee
NULL obj on input visitor callback error"), but that does not
help the case where visit_start succeeds but some other failure
happens before visit_end, such that we leak a partially constructed
object outside visit_type_FOO(). As no one outside the testsuite
was actually relying on these semantics, it is cleaner to just
document and guarantee that ALL pointer-based visit_type_FOO()
functions always leave a safe value in *obj during an input visitor
(either the new object on success, or NULL if an error is
encountered), so callers can now unconditionally use
qapi_free_FOO() to clean up regardless of whether an error occurred.

The decision is done by adding visit_is_input(), then updating the
generated code to check if additional cleanup is needed based on
the type of visitor in use.

Note that we still leave *obj unchanged after a scalar-based
visit_type_FOO(); I did not feel like auditing all uses of
visit_type_Enum() to see if the callers would tolerate a specific
sentinel value (not to mention having to decide whether it would
be better to use 0 or ENUM__MAX as that sentinel).

Signed-off-by: Eric Blake <eblake@redhat.com>

---
v15: rebase, redo semantics on how generated code learns
about input visitors, hoist unrelated assertions earlier
v14: no change
v13: rebase to latest, documentation tweaks
v12: rebase to latest, don't modify callback signatures, use newer
approach for detecting input visitors, avoid 'allocated' boolean
[no v10, v11]
v9: fix bug in use of errp
v8: rebase to earlier changes
v7: rebase to earlier changes, enhance commit message, also fix
visit_type_str() and visit_type_any()
v6: rebase on top of earlier doc and formatting improvements, mention
that *obj can be uninitialized on entry to an input visitor, rework
semantics to keep valgrind happy on uninitialized input, break some
long lines
---
 include/qapi/visitor.h         | 24 ++++++++++++++++--------
 scripts/qapi-visit.py          | 22 +++++++++++++---------
 qapi/qapi-visit-core.c         |  6 ++++++
 tests/test-qmp-commands.c      | 13 ++++++-------
 tests/test-qmp-input-strict.c  | 17 +++++++----------
 tests/test-qmp-input-visitor.c | 10 ++--------
 docs/qapi-code-gen.txt         |  8 ++++++++
 7 files changed, 58 insertions(+), 42 deletions(-)

diff --git a/include/qapi/visitor.h b/include/qapi/visitor.h
index 4f2a460..7b97711 100644
--- a/include/qapi/visitor.h
+++ b/include/qapi/visitor.h
@@ -66,11 +66,14 @@
  * member @name is not present, or is present but not the specified
  * type).
  *
- * FIXME: At present, input visitors may allocate an incomplete *@obj
- * even when visit_type_FOO() reports an error.  Using an output
- * visitor with an incomplete object has undefined behavior; callers
- * must call qapi_free_FOO() (which uses the dealloc visitor, and
- * safely handles an incomplete object) to avoid a memory leak.
+ * If an error is detected during visit_type_FOO() with an input
+ * visitor, then *@obj will be NULL for pointer types, and left
+ * unchanged for scalar types.  Using an output visitor with an
+ * incomplete object has undefined behavior (other than a special case
+ * for visit_type_str() treating NULL like ""), while the dealloc
+ * visitor safely handles incomplete objects.  Since input visitors
+ * never produce an incomplete object, such an object is possible only
+ * by manual construction.
  *
  * For the QAPI object types (structs, unions, and alternates), there
  * is an additional generated function in qapi-visit.h compatible
@@ -105,7 +108,6 @@
  *  v = ...obtain input visitor...
  *  visit_type_Foo(v, NULL, &f, &err);
  *  if (err) {
- *      qapi_free_Foo(f);
  *      ...handle error...
  *  } else {
  *      ...use f...
@@ -123,7 +125,6 @@
  *  v = ...obtain input visitor...
  *  visit_type_FooList(v, NULL, &l, &err);
  *  if (err) {
- *      qapi_free_FooList(l);
  *      ...handle error...
  *  } else {
  *      for ( ; l; l = l->next) {
@@ -153,7 +154,9 @@
  * helpers that rely on in-tree information to control the walk:
  * visit_optional() for the 'has_member' field associated with
  * optional 'member' in the C struct; and visit_next_list() for
- * advancing through a FooList linked list.  Only the generated
+ * advancing through a FooList linked list.  Similarly, the
+ * visit_is_input() helper makes it possible to write code that is
+ * visitor-agnostic everywhere except for cleanup.  Only the generated
  * visit_type functions need to use these helpers.
  *
  * It is also possible to use the visitors to do a virtual walk, where
@@ -403,6 +406,11 @@ bool visit_optional(Visitor *v, const char *name, bool *present);
 void visit_type_enum(Visitor *v, const char *name, int *obj,
                      const char *const strings[], Error **errp);

+/*
+ * Check if visitor is an input visitor.
+ */
+bool visit_is_input(Visitor *v);
+
 /*** Visiting built-in types ***/

 /*
diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
index 8b7efcc..70ea8ca 100644
--- a/scripts/qapi-visit.py
+++ b/scripts/qapi-visit.py
@@ -108,10 +108,6 @@ out:


 def gen_visit_list(name, element_type):
-    # FIXME: if *obj is NULL on entry, and the first visit_next_list()
-    # assigns to *obj, while a later one fails, we should clean up *obj
-    # rather than leaving it non-NULL. As currently written, the caller must
-    # call qapi_free_FOOList() to avoid a memory leak of the partial FOOList.
     return mcgen('''

 void visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s **obj, Error **errp)
@@ -134,6 +130,10 @@ void visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s **obj, Error
     }

     visit_end_list(v);
+    if (err && visit_is_input(v)) {
+        qapi_free_%(c_name)s(*obj);
+        *obj = NULL;
+    }
 out:
     error_propagate(errp, err);
 }
@@ -211,20 +211,20 @@ void visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s **obj, Error
                    "%(name)s");
     }
     visit_end_alternate(v);
+    if (err && visit_is_input(v)) {
+        qapi_free_%(c_name)s(*obj);
+        *obj = NULL;
+    }
 out:
     error_propagate(errp, err);
 }
 ''',
-                 name=name)
+                 name=name, c_name=c_name(name))

     return ret


 def gen_visit_object(name, base, members, variants):
-    # FIXME: if *obj is NULL on entry, and visit_start_struct() assigns to
-    # *obj, but then visit_type_FOO_members() fails, we should clean up *obj
-    # rather than leaving it non-NULL. As currently written, the caller must
-    # call qapi_free_FOO() to avoid a memory leak of the partial FOO.
     return mcgen('''

 void visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s **obj, Error **errp)
@@ -245,6 +245,10 @@ void visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s **obj, Error
     visit_check_struct(v, &err);
 out_obj:
     visit_end_struct(v);
+    if (err && visit_is_input(v)) {
+        qapi_free_%(c_name)s(*obj);
+        *obj = NULL;
+    }
 out:
     error_propagate(errp, err);
 }
diff --git a/qapi/qapi-visit-core.c b/qapi/qapi-visit-core.c
index e6d57f3..b30a22e 100644
--- a/qapi/qapi-visit-core.c
+++ b/qapi/qapi-visit-core.c
@@ -48,6 +48,7 @@ void visit_end_struct(Visitor *v)
     v->end_struct(v);
 }

+
 void visit_start_list(Visitor *v, const char *name, GenericList **list,
                       size_t size, Error **errp)
 {
@@ -98,6 +99,11 @@ bool visit_optional(Visitor *v, const char *name, bool *present)
     return *present;
 }

+bool visit_is_input(Visitor *v)
+{
+    return v->type == VISITOR_INPUT;
+}
+
 void visit_type_int(Visitor *v, const char *name, int64_t *obj, Error **errp)
 {
     assert(obj);
diff --git a/tests/test-qmp-commands.c b/tests/test-qmp-commands.c
index 597fb44..5c3edd7 100644
--- a/tests/test-qmp-commands.c
+++ b/tests/test-qmp-commands.c
@@ -228,14 +228,13 @@ static void test_dealloc_partial(void)
         QDECREF(ud2_dict);
     }

-    /* verify partial success */
-    assert(ud2 != NULL);
-    assert(ud2->string0 != NULL);
-    assert(strcmp(ud2->string0, text) == 0);
-    assert(ud2->dict1 == NULL);
-
-    /* confirm & release construction error */
+    /* verify that visit_type_XXX() cleans up properly on error */
     error_free_or_abort(&err);
+    assert(!ud2);
+
+    /* Manually create a partial object, leaving ud2->dict1 at NULL */
+    ud2 = g_new0(UserDefTwo, 1);
+    ud2->string0 = g_strdup(text);

     /* tear down partial object */
     qapi_free_UserDefTwo(ud2);
diff --git a/tests/test-qmp-input-strict.c b/tests/test-qmp-input-strict.c
index 2b053a2..4602529 100644
--- a/tests/test-qmp-input-strict.c
+++ b/tests/test-qmp-input-strict.c
@@ -182,10 +182,7 @@ static void test_validate_fail_struct(TestInputVisitorData *data,

     visit_type_TestStruct(v, NULL, &p, &err);
     error_free_or_abort(&err);
-    if (p) {
-        g_free(p->string);
-    }
-    g_free(p);
+    g_assert(!p);
 }

 static void test_validate_fail_struct_nested(TestInputVisitorData *data,
@@ -199,7 +196,7 @@ static void test_validate_fail_struct_nested(TestInputVisitorData *data,

     visit_type_UserDefTwo(v, NULL, &udp, &err);
     error_free_or_abort(&err);
-    qapi_free_UserDefTwo(udp);
+    g_assert(!udp);
 }

 static void test_validate_fail_list(TestInputVisitorData *data,
@@ -213,7 +210,7 @@ static void test_validate_fail_list(TestInputVisitorData *data,

     visit_type_UserDefOneList(v, NULL, &head, &err);
     error_free_or_abort(&err);
-    qapi_free_UserDefOneList(head);
+    g_assert(!head);
 }

 static void test_validate_fail_union_native_list(TestInputVisitorData *data,
@@ -228,7 +225,7 @@ static void test_validate_fail_union_native_list(TestInputVisitorData *data,

     visit_type_UserDefNativeListUnion(v, NULL, &tmp, &err);
     error_free_or_abort(&err);
-    qapi_free_UserDefNativeListUnion(tmp);
+    g_assert(!tmp);
 }

 static void test_validate_fail_union_flat(TestInputVisitorData *data,
@@ -242,7 +239,7 @@ static void test_validate_fail_union_flat(TestInputVisitorData *data,

     visit_type_UserDefFlatUnion(v, NULL, &tmp, &err);
     error_free_or_abort(&err);
-    qapi_free_UserDefFlatUnion(tmp);
+    g_assert(!tmp);
 }

 static void test_validate_fail_union_flat_no_discrim(TestInputVisitorData *data,
@@ -257,7 +254,7 @@ static void test_validate_fail_union_flat_no_discrim(TestInputVisitorData *data,

     visit_type_UserDefFlatUnion2(v, NULL, &tmp, &err);
     error_free_or_abort(&err);
-    qapi_free_UserDefFlatUnion2(tmp);
+    g_assert(!tmp);
 }

 static void test_validate_fail_alternate(TestInputVisitorData *data,
@@ -271,7 +268,7 @@ static void test_validate_fail_alternate(TestInputVisitorData *data,

     visit_type_UserDefAlternate(v, NULL, &tmp, &err);
     error_free_or_abort(&err);
-    qapi_free_UserDefAlternate(tmp);
+    g_assert(!tmp);
 }

 static void do_test_validate_qmp_introspect(TestInputVisitorData *data,
diff --git a/tests/test-qmp-input-visitor.c b/tests/test-qmp-input-visitor.c
index 70eec9f..828aaa3 100644
--- a/tests/test-qmp-input-visitor.c
+++ b/tests/test-qmp-input-visitor.c
@@ -770,18 +770,12 @@ static void test_visitor_in_errors(TestInputVisitorData *data,

     visit_type_TestStruct(v, NULL, &p, &err);
     error_free_or_abort(&err);
-    /* FIXME - a failed parse should not leave a partially-allocated p
-     * for us to clean up; this could cause callers to leak memory. */
-    g_assert(p->string == NULL);
-
-    g_free(p->string);
-    g_free(p);
+    g_assert(!p);

     v = visitor_input_test_init(data, "[ '1', '2', false, '3' ]");
     visit_type_strList(v, NULL, &q, &err);
     error_free_or_abort(&err);
-    assert(q);
-    qapi_free_strList(q);
+    assert(!q);
 }

 static void test_visitor_in_wrong_type(TestInputVisitorData *data,
diff --git a/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt
index fe61c5c..d7d6987 100644
--- a/docs/qapi-code-gen.txt
+++ b/docs/qapi-code-gen.txt
@@ -905,6 +905,10 @@ Example:
         visit_check_struct(v, &err);
     out_obj:
         visit_end_struct(v);
+        if (err && visit_is_input(v)) {
+            qapi_free_UserDefOne(*obj);
+            *obj = NULL;
+        }
     out:
         error_propagate(errp, err);
     }
@@ -929,6 +933,10 @@ Example:
         }

         visit_end_list(v);
+        if (err && visit_is_input(v)) {
+            qapi_free_UserDefOneList(*obj);
+            *obj = NULL;
+        }
     out:
         error_propagate(errp, err);
     }
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 49+ messages in thread

* Re: [Qemu-devel] [PATCH v15 02/23] qapi: Guarantee NULL obj on input visitor callback error
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 02/23] qapi: Guarantee NULL obj on input visitor callback error Eric Blake
@ 2016-04-28 12:24   ` Markus Armbruster
  2016-04-28 13:00     ` Eric Blake
  2016-04-28 16:02   ` [Qemu-devel] [PATCH v15 02A/23] fixup! " Eric Blake
  1 sibling, 1 reply; 49+ messages in thread
From: Markus Armbruster @ 2016-04-28 12:24 UTC (permalink / raw)
  To: Eric Blake; +Cc: qemu-devel, Michael Roth

Eric Blake <eblake@redhat.com> writes:

> Our existing input visitors were not very consistent on errors
> in a function taking 'TYPE **obj' (that is, start_struct(),
> start_alternate(), next_list(), type_str(), and type_any()).
> While all of them set '*obj' to allocated storage on success,
> it was not obvious whether '*obj' was guaranteed safe on failure,
> or whether it was left uninitialized.  But a future patch wants
> to guarantee that visit_type_FOO() does not leak a partially-
> constructed obj back to the caller; it is easier to implement
> this if we can reliably state that '*obj' is assigned on exit,
> even on failures.  Add assertions to enforce it.

I had to read this several times, because by now I've forgotten that
we're talking about input visitors only.  Easy enough to avoid: ... that
input visitors assign to *obj regardless of success or failure.

Begs the question what is assigned to it on failure, though.

>
> The opts-visitor start_struct() doesn't set an error, but it
> also was doing a weird check for 0 size; all callers pass in
> non-zero size if obj is non-NULL.
>
> The testsuite has at least one spot where we no longer need
> to pre-initialize a variable prior to a visit; valgrind confirms
> that the test is still fine with the cleanup.
>
> A later patch will document the design constraint implemented
> here.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
>
> ---
> v15: enhance commit message, hoist assertions from later in series
> v14: no change
> v13: no change
> v12: new patch
> ---
>  qapi/qapi-visit-core.c        | 34 ++++++++++++++++++++++++++++++----
>  qapi/opts-visitor.c           |  3 ++-
>  qapi/qmp-input-visitor.c      |  4 ++++
>  qapi/string-input-visitor.c   |  1 +
>  tests/test-qmp-input-strict.c |  2 +-
>  5 files changed, 38 insertions(+), 6 deletions(-)
>
> diff --git a/qapi/qapi-visit-core.c b/qapi/qapi-visit-core.c
> index 3cd7edc..3a131ce 100644
> --- a/qapi/qapi-visit-core.c
> +++ b/qapi/qapi-visit-core.c
> @@ -23,7 +23,13 @@
>  void visit_start_struct(Visitor *v, const char *name, void **obj,
>                          size_t size, Error **errp)
>  {
> -    v->start_struct(v, name, obj, size, errp);
> +    Error *err = NULL;
> +
> +    v->start_struct(v, name, obj, size, &err);
> +    if (obj && v->type == VISITOR_INPUT) {
> +        assert(err || *obj);
> +    }
> +    error_propagate(errp, err);
>  }

The commit message claims you're adding assertions to enforce input
visitors assign *obj even on failure.  This assertion doesn't do that.
It enforces "on success, *obj is non-null".  Is that what you want?  Or
do you actually want something like "either err or *obj are non-null"?
I.e.

           assert(!err != !*obj);

>
>  void visit_end_struct(Visitor *v, Error **errp)
> @@ -51,9 +57,15 @@ void visit_start_alternate(Visitor *v, const char *name,
>                             GenericAlternate **obj, size_t size,
>                             bool promote_int, Error **errp)
>  {
> +    Error *err = NULL;
> +
>      assert(obj && size >= sizeof(GenericAlternate));
>      if (v->start_alternate) {
> -        v->start_alternate(v, name, obj, size, promote_int, errp);
> +        v->start_alternate(v, name, obj, size, promote_int, &err);
> +        if (v->type == VISITOR_INPUT) {
> +            assert(err || *obj);
> +        }
> +        error_propagate(errp, err);
>      }
>  }
>

Hmm, you check the postcondition only when v implements
start_alternate().  Shouldn't it hold regardless of v?  If yes, then
let's check it regardless of v:

       if (v->start_alternate) {
           v->start_alternate(v, name, obj, size, promote_int, &err);
       }
       if (v->type == VISITOR_INPUT) {
           assert(err || *obj);
       }
       error_propagate(errp, err);

But that makes it pretty obvious that the postcondition won't hold when
!v->start_alternate.  May v->start_alternate() be null for an input
visitor?  According to visitor-impl.h, it may not.  Okay.

> @@ -188,7 +200,14 @@ void visit_type_bool(Visitor *v, const char *name, bool *obj, Error **errp)
>
>  void visit_type_str(Visitor *v, const char *name, char **obj, Error **errp)
>  {
> -    v->type_str(v, name, obj, errp);
> +    Error *err = NULL;
> +
> +    assert(obj);
> +    v->type_str(v, name, obj, &err);
> +    if (v->type == VISITOR_INPUT) {
> +        assert(err || *obj);
> +    }
> +    error_propagate(errp, err);
>  }
>
>  void visit_type_number(Visitor *v, const char *name, double *obj,
> @@ -199,7 +218,14 @@ void visit_type_number(Visitor *v, const char *name, double *obj,
>
>  void visit_type_any(Visitor *v, const char *name, QObject **obj, Error **errp)
>  {
> -    v->type_any(v, name, obj, errp);
> +    Error *err = NULL;
> +
> +    assert(obj);
> +    v->type_any(v, name, obj, &err);
> +    if (v->type == VISITOR_INPUT) {
> +        assert(err || *obj);
> +    }
> +    error_propagate(errp, err);
>  }
>

The commit message lists start_struct(), start_alternate(), next_list(),
type_str(), and type_any().  You cover them except for next_list().  Why
is that missing?

>  static void output_type_enum(Visitor *v, const char *name, int *obj,
> diff --git a/qapi/opts-visitor.c b/qapi/opts-visitor.c
> index 66aeaed..4cb6436 100644
> --- a/qapi/opts-visitor.c
> +++ b/qapi/opts-visitor.c
> @@ -133,7 +133,7 @@ opts_start_struct(Visitor *v, const char *name, void **obj,
>      const QemuOpt *opt;
>
>      if (obj) {
> -        *obj = g_malloc0(size > 0 ? size : 1);
> +        *obj = g_malloc0(size);
>      }
>      if (ov->depth++ > 0) {
>          return;
> @@ -314,6 +314,7 @@ opts_type_str(Visitor *v, const char *name, char **obj, Error **errp)
>
>      opt = lookup_scalar(ov, name, errp);
>      if (!opt) {
> +        *obj = NULL;
>          return;
>      }
>      *obj = g_strdup(opt->str ? opt->str : "");
> diff --git a/qapi/qmp-input-visitor.c b/qapi/qmp-input-visitor.c
> index 02d4233..77cce8b 100644
> --- a/qapi/qmp-input-visitor.c
> +++ b/qapi/qmp-input-visitor.c
> @@ -120,6 +120,9 @@ static void qmp_input_start_struct(Visitor *v, const char *name, void **obj,
>      QObject *qobj = qmp_input_get_object(qiv, name, true);
>      Error *err = NULL;
>
> +    if (obj) {
> +        *obj = NULL;
> +    }
>      if (!qobj || qobject_type(qobj) != QTYPE_QDICT) {
>          error_setg(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null",
>                     "QDict");
> @@ -267,6 +270,7 @@ static void qmp_input_type_str(Visitor *v, const char *name, char **obj,
>      QString *qstr = qobject_to_qstring(qmp_input_get_object(qiv, name, true));
>
>      if (!qstr) {
> +        *obj = NULL;
>          error_setg(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null",
>                     "string");
>          return;
> diff --git a/qapi/string-input-visitor.c b/qapi/string-input-visitor.c
> index d604575..797973a 100644
> --- a/qapi/string-input-visitor.c
> +++ b/qapi/string-input-visitor.c
> @@ -293,6 +293,7 @@ static void parse_type_str(Visitor *v, const char *name, char **obj,
>      if (siv->string) {
>          *obj = g_strdup(siv->string);
>      } else {
> +        *obj = NULL;
>          error_setg(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null",
>                     "string");
>      }
> diff --git a/tests/test-qmp-input-strict.c b/tests/test-qmp-input-strict.c
> index d71727e..d5f80ec 100644
> --- a/tests/test-qmp-input-strict.c
> +++ b/tests/test-qmp-input-strict.c
> @@ -263,7 +263,7 @@ static void test_validate_fail_union_flat_no_discrim(TestInputVisitorData *data,
>  static void test_validate_fail_alternate(TestInputVisitorData *data,
>                                           const void *unused)
>  {
> -    UserDefAlternate *tmp = NULL;
> +    UserDefAlternate *tmp;
>      Visitor *v;
>      Error *err = NULL;

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [Qemu-devel] [PATCH v15 02/23] qapi: Guarantee NULL obj on input visitor callback error
  2016-04-28 12:24   ` Markus Armbruster
@ 2016-04-28 13:00     ` Eric Blake
  2016-04-28 15:41       ` Eric Blake
  0 siblings, 1 reply; 49+ messages in thread
From: Eric Blake @ 2016-04-28 13:00 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: qemu-devel, Michael Roth

[-- Attachment #1: Type: text/plain, Size: 3452 bytes --]

On 04/28/2016 06:24 AM, Markus Armbruster wrote:
> Eric Blake <eblake@redhat.com> writes:
> 
>> Our existing input visitors were not very consistent on errors
>> in a function taking 'TYPE **obj' (that is, start_struct(),
>> start_alternate(), next_list(), type_str(), and type_any()).
>> While all of them set '*obj' to allocated storage on success,
>> it was not obvious whether '*obj' was guaranteed safe on failure,
>> or whether it was left uninitialized.  But a future patch wants
>> to guarantee that visit_type_FOO() does not leak a partially-
>> constructed obj back to the caller; it is easier to implement
>> this if we can reliably state that '*obj' is assigned on exit,
>> even on failures.  Add assertions to enforce it.
> 
> I had to read this several times, because by now I've forgotten that
> we're talking about input visitors only.  Easy enough to avoid: ... that
> input visitors assign to *obj regardless of success or failure.
> 
> Begs the question what is assigned to it on failure, though.

Easy enough to improve the commit message - the intent is that these all
set *obj to NULL on failure.


>> +    v->start_struct(v, name, obj, size, &err);
>> +    if (obj && v->type == VISITOR_INPUT) {
>> +        assert(err || *obj);
>> +    }
>> +    error_propagate(errp, err);
>>  }
> 
> The commit message claims you're adding assertions to enforce input
> visitors assign *obj even on failure.  This assertion doesn't do that.
> It enforces "on success, *obj is non-null".  Is that what you want?  Or
> do you actually want something like "either err or *obj are non-null"?
> I.e.
> 
>            assert(!err != !*obj);

Hmm - that's an even tighter assertion.  I'll run it through the
testsuite, and if it passes, I'll use it.


> 
> Hmm, you check the postcondition only when v implements
> start_alternate().  Shouldn't it hold regardless of v?  If yes, then
> let's check it regardless of v:
> 
>        if (v->start_alternate) {
>            v->start_alternate(v, name, obj, size, promote_int, &err);
>        }
>        if (v->type == VISITOR_INPUT) {
>            assert(err || *obj);
>        }
>        error_propagate(errp, err);
> 
> But that makes it pretty obvious that the postcondition won't hold when
> !v->start_alternate.  May v->start_alternate() be null for an input
> visitor?  According to visitor-impl.h, it may not.  Okay.

Doesn't hurt to make the check unconditional (to make sure no new input
visitors forget to implement start_alternate).  I'm also debating about
adding an assertion (more likely in the doc patch, since it is less
related to the theme of this one) that obj->type is sensible.


> 
> The commit message lists start_struct(), start_alternate(), next_list(),
> type_str(), and type_any().  You cover them except for next_list().  Why
> is that missing?

Because *obj can be NULL after next_list() if the list is empty.  But
there may still be a weaker assertion worth having: if err, then *obj
must be NULL; and if *obj, then err must not be set (weaker in that for
all the other functions touched, exactly one of the two conditions can
result, but here, !err and !*obj is valid as a third condition).

Depending on what else you find later in the series, I may just post a
fixup for this patch.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [Qemu-devel] [PATCH v15 05/23] qapi: Use strict QMP input visitor in more places
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 05/23] qapi: Use strict QMP input visitor in more places Eric Blake
@ 2016-04-28 13:06   ` Markus Armbruster
  2016-04-28 14:28     ` Eric Blake
  0 siblings, 1 reply; 49+ messages in thread
From: Markus Armbruster @ 2016-04-28 13:06 UTC (permalink / raw)
  To: Eric Blake
  Cc: qemu-devel, Michael Roth, Gerd Hoffmann, Paolo Bonzini,
	Andreas Färber

Eric Blake <eblake@redhat.com> writes:

> Rather than having two separate ways to create a QMP input
> visitor, where the safer approach has the more verbose name,
> it is better to consolidate things into a single function
> where the caller must explicitly choose whether to be strict
> or to ignore excess input.  Use strict mode in more places of
> internal code (such as when cloning a QAPI struct in
> util/socket.c, where the QObject better not have excess
> input since it was just generated by qmp-output), while
> documenting in user-facing code a question of whether we
> should change our policy about ignoring excess input.

Which external interface is this?

>
> In the case of qmp_object_add(), we intentionally switch to a
> strict visitor; this matches the fact that the code for
> user_creatable_add_type() is shared by both qmp_object_add()
> (QMP input visitor) and by user_creatable_add_opts() (QemuOpts
> visitor); the latter is always strict, so our usage in the
> former should also be strict, so that both visits will
> equally diagnose any excess input in a nested dict.  But in
> practice, we don't really have any -object where the
> properties are a nested dict, and excess input at the top
> level is already caught earlier by object_property_set() on
> an unknown key, whether from QemuOpts:
>
> $ ./x86_64-softmmu/qemu-system-x86_64 -nographic -nodefaults -qmp stdio -object secret,id=sec0,data=letmein,format=raw,foo=bar
> qemu-system-x86_64: Property '.foo' not found

Let's update the error message now that the error message regression is
fixed.  Can do on commit.

>
> or from QMP:
>
> $ ./x86_64-softmmu/qemu-system-x86_64 -nographic -nodefaults -qmp stdio
> {"QMP": {"version": {"qemu": {"micro": 93, "minor": 5, "major": 2}, "package": ""}, "capabilities": []}}
> {"execute":"qmp_capabilities"}
> {"return": {}}
> {"execute":"object-add","arguments":{"qom-type":"secret","id":"sec0","props":{"format":"raw","data":"letmein","a":1}}}
> {"error": {"class": "GenericError", "desc": "Property '.a' not found"}}
>
> Signed-off-by: Eric Blake <eblake@redhat.com>

Should we split this into a patch to change the interface, and one or
more separate patches to switch to the strict visitor?  Could result in
clearer and more complete commit messages.

>
> ---
> v15: new patch

"v15: new patch" *groan*

> ---
>  scripts/qapi-commands.py           |  2 +-
>  include/qapi/qmp-input-visitor.h   |  9 +++++++--
>  qapi/qmp-input-visitor.c           | 13 ++-----------
>  qmp.c                              |  2 +-
>  qom/qom-qobject.c                  |  3 ++-
>  replay/replay-input.c              |  2 +-
>  tests/test-qmp-commands.c          |  2 +-
>  tests/test-qmp-input-strict.c      |  2 +-
>  tests/test-qmp-input-visitor.c     |  2 +-
>  tests/test-visitor-serialization.c |  2 +-
>  util/qemu-sockets.c                |  2 +-
>  docs/qapi-code-gen.txt             |  2 +-
>  12 files changed, 20 insertions(+), 23 deletions(-)
>
> diff --git a/scripts/qapi-commands.py b/scripts/qapi-commands.py
> index b570069..6261e44 100644
> --- a/scripts/qapi-commands.py
> +++ b/scripts/qapi-commands.py
> @@ -115,7 +115,7 @@ def gen_marshal(name, arg_type, ret_type):
>
>      if arg_type and arg_type.members:
>          ret += mcgen('''
> -    QmpInputVisitor *qiv = qmp_input_visitor_new_strict(QOBJECT(args));
> +    QmpInputVisitor *qiv = qmp_input_visitor_new(QOBJECT(args), true);
>      QapiDeallocVisitor *qdv;
>      Visitor *v;
>      %(c_name)s arg = {0};

Stay strict.

> diff --git a/include/qapi/qmp-input-visitor.h b/include/qapi/qmp-input-visitor.h
> index 3ed499c..b0624d8 100644
> --- a/include/qapi/qmp-input-visitor.h
> +++ b/include/qapi/qmp-input-visitor.h
> @@ -19,8 +19,13 @@
>
>  typedef struct QmpInputVisitor QmpInputVisitor;
>
> -QmpInputVisitor *qmp_input_visitor_new(QObject *obj);
> -QmpInputVisitor *qmp_input_visitor_new_strict(QObject *obj);
> +/*
> + * Return a new input visitor that converts QMP to QAPI.
> + *
> + * Set @strict to reject a parse that doesn't consume all keys of a
> + * dictionary; otherwise excess input is ignored.
> + */
> +QmpInputVisitor *qmp_input_visitor_new(QObject *obj, bool strict);
>
>  void qmp_input_visitor_cleanup(QmpInputVisitor *v);
>
> diff --git a/qapi/qmp-input-visitor.c b/qapi/qmp-input-visitor.c
> index 8550bc7..c3c3271 100644
> --- a/qapi/qmp-input-visitor.c
> +++ b/qapi/qmp-input-visitor.c
> @@ -356,7 +356,7 @@ void qmp_input_visitor_cleanup(QmpInputVisitor *v)
>      g_free(v);
>  }
>
> -QmpInputVisitor *qmp_input_visitor_new(QObject *obj)
> +QmpInputVisitor *qmp_input_visitor_new(QObject *obj, bool strict)
>  {
>      QmpInputVisitor *v;
>
> @@ -376,19 +376,10 @@ QmpInputVisitor *qmp_input_visitor_new(QObject *obj)
>      v->visitor.type_number = qmp_input_type_number;
>      v->visitor.type_any = qmp_input_type_any;
>      v->visitor.optional = qmp_input_optional;
> +    v->strict = strict;
>
>      qmp_input_push(v, obj, NULL);
>      qobject_incref(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/qmp.c b/qmp.c
> index 9d0953b..e784a67 100644
> --- a/qmp.c
> +++ b/qmp.c
> @@ -663,7 +663,7 @@ void qmp_object_add(const char *type, const char *id,
>          }
>      }
>
> -    qiv = qmp_input_visitor_new(props);
> +    qiv = qmp_input_visitor_new(props, true);
>      obj = user_creatable_add_type(type, id, pdict,
>                                    qmp_input_get_visitor(qiv), errp);
>      qmp_input_visitor_cleanup(qiv);

Switch to strict.  The commit message explains why this is a good idea.

> diff --git a/qom/qom-qobject.c b/qom/qom-qobject.c
> index e6b17c1..b66088d 100644
> --- a/qom/qom-qobject.c
> +++ b/qom/qom-qobject.c
> @@ -22,7 +22,8 @@ void object_property_set_qobject(Object *obj, QObject *value,
>                                   const char *name, Error **errp)
>  {
>      QmpInputVisitor *qiv;
> -    qiv = qmp_input_visitor_new(value);
> +    /* TODO: Should we reject, rather than ignore, excess input? */
> +    qiv = qmp_input_visitor_new(value, false);
>      object_property_set(obj, qmp_input_get_visitor(qiv), name, errp);
>
>      qmp_input_visitor_cleanup(qiv);

Stay lenient, but document this should perhaps switch to strict.  The
commit message hints at this one.

> diff --git a/replay/replay-input.c b/replay/replay-input.c
> index 06babe0..03e99d5 100644
> --- a/replay/replay-input.c
> +++ b/replay/replay-input.c
> @@ -37,7 +37,7 @@ static InputEvent *qapi_clone_InputEvent(InputEvent *src)
>          return NULL;
>      }
>
> -    qiv = qmp_input_visitor_new(obj);
> +    qiv = qmp_input_visitor_new(obj, true);
>      iv = qmp_input_get_visitor(qiv);
>      visit_type_InputEvent(iv, NULL, &dst, &error_abort);
>      qmp_input_visitor_cleanup(qiv);

Switch to strict.  Not mentioned in commit message.  Why is it a good
idea?

> diff --git a/tests/test-qmp-commands.c b/tests/test-qmp-commands.c
> index 14a9ebb..597fb44 100644
> --- a/tests/test-qmp-commands.c
> +++ b/tests/test-qmp-commands.c
> @@ -222,7 +222,7 @@ static void test_dealloc_partial(void)
>          ud2_dict = qdict_new();
>          qdict_put_obj(ud2_dict, "string0", QOBJECT(qstring_from_str(text)));
>
> -        qiv = qmp_input_visitor_new(QOBJECT(ud2_dict));
> +        qiv = qmp_input_visitor_new(QOBJECT(ud2_dict), true);
>          visit_type_UserDefTwo(qmp_input_get_visitor(qiv), NULL, &ud2, &err);
>          qmp_input_visitor_cleanup(qiv);
>          QDECREF(ud2_dict);

Switch to strict.  Not mentioned in commit message.  Why is it a good
idea?

> diff --git a/tests/test-qmp-input-strict.c b/tests/test-qmp-input-strict.c
> index d5f80ec..2b053a2 100644
> --- a/tests/test-qmp-input-strict.c
> +++ b/tests/test-qmp-input-strict.c
> @@ -55,7 +55,7 @@ static Visitor *validate_test_init_internal(TestInputVisitorData *data,
>      data->obj = qobject_from_jsonv(json_string, ap);
>      g_assert(data->obj);
>
> -    data->qiv = qmp_input_visitor_new_strict(data->obj);
> +    data->qiv = qmp_input_visitor_new(data->obj, true);
>      g_assert(data->qiv);
>
>      v = qmp_input_get_visitor(data->qiv);

Stay strict (because we're testing it).

> diff --git a/tests/test-qmp-input-visitor.c b/tests/test-qmp-input-visitor.c
> index 80527eb..c039806 100644
> --- a/tests/test-qmp-input-visitor.c
> +++ b/tests/test-qmp-input-visitor.c
> @@ -51,7 +51,7 @@ static Visitor *visitor_input_test_init_internal(TestInputVisitorData *data,
>      data->obj = qobject_from_jsonv(json_string, ap);
>      g_assert(data->obj);
>
> -    data->qiv = qmp_input_visitor_new(data->obj);
> +    data->qiv = qmp_input_visitor_new(data->obj, false);
>      g_assert(data->qiv);
>
>      v = qmp_input_get_visitor(data->qiv);
> diff --git a/tests/test-visitor-serialization.c b/tests/test-visitor-serialization.c

Stay lenient (because we're testing it).

> index 9adbc30..7b14b5a 100644
> --- a/tests/test-visitor-serialization.c
> +++ b/tests/test-visitor-serialization.c
> @@ -1038,7 +1038,7 @@ static void qmp_deserialize(void **native_out, void *datap,
>      obj = qobject_from_json(qstring_get_str(output_json));
>
>      QDECREF(output_json);
> -    d->qiv = qmp_input_visitor_new(obj);
> +    d->qiv = qmp_input_visitor_new(obj, true);
>      qobject_decref(obj_orig);
>      qobject_decref(obj);
>      visit(qmp_input_get_visitor(d->qiv), native_out, errp);

Switch to strict.  Not mentioned in commit message.  Why is it a good
idea?

> diff --git a/util/qemu-sockets.c b/util/qemu-sockets.c
> index 0d53691..2a2c524 100644
> --- a/util/qemu-sockets.c
> +++ b/util/qemu-sockets.c
> @@ -1145,7 +1145,7 @@ void qapi_copy_SocketAddress(SocketAddress **p_dest,
>          return;
>      }
>
> -    qiv = qmp_input_visitor_new(obj);
> +    qiv = qmp_input_visitor_new(obj, true);
>      iv = qmp_input_get_visitor(qiv);
>      visit_type_SocketAddress(iv, NULL, p_dest, &error_abort);
>      qmp_input_visitor_cleanup(qiv);

Switch to strict.  The commit message explains why this is a good idea.

> diff --git a/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt
> index 0e4baff..4a917f9 100644
> --- a/docs/qapi-code-gen.txt
> +++ b/docs/qapi-code-gen.txt
> @@ -996,7 +996,7 @@ Example:
>      {
>          Error *err = NULL;
>          UserDefOne *retval;
> -        QmpInputVisitor *qiv = qmp_input_visitor_new_strict(QOBJECT(args));
> +        QmpInputVisitor *qiv = qmp_input_visitor_new(QOBJECT(args), true);
>          QapiDeallocVisitor *qdv;
>          Visitor *v;
>          UserDefOneList *arg1 = NULL;

Stay strict.

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [Qemu-devel] [PATCH v15 08/23] monitor: Let generated code validate arguments
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 08/23] monitor: Let generated code validate arguments Eric Blake
@ 2016-04-28 14:09   ` Markus Armbruster
  2016-04-28 14:39     ` Marc-André Lureau
  2016-04-28 14:47     ` Eric Blake
  0 siblings, 2 replies; 49+ messages in thread
From: Markus Armbruster @ 2016-04-28 14:09 UTC (permalink / raw)
  To: Eric Blake
  Cc: qemu-devel, Michael Roth, Luiz Capitulino, Marc-André Lureau

Eric Blake <eblake@redhat.com> writes:

> Having to manually call out the set of expected arguments in
> qmp-commands.hx, in addition to what is already in *.json,
> is tedious and prone to error.  The only reason we use
> .args_type is for checking if there is any excess arguments
> or incorrectly typed arguments during qmp_check_client_args(),
> but a strict QMP Input visitor also does that checking.

We also use it for completion.

> Simplify things so that .args_type is only needed for the
> few remaining commands that don't have a QAPI description
> (namely, device_add, netdev_add, qmp_capabilities) or which
> use 'gen':false (query-qmp-schema).
>
> There was one case where the generated marshal code has to be
> beefed up: for a command that has no (or empty) 'data' in the
> .json file, we were just ignoring the passed-in QDict; now we
> need to validate that there are no extra arguments.
>
> Generated code changes like:
>
> |@@ -1206,7 +1206,10 @@ void qmp_marshal_cont(QDict *args, QObje
> | {
> |     Error *err = NULL;
> |
> |-    (void)args;
> |+    if (args && qdict_size(args)) {
> |+        error_setg(errp, "Command %s does not take arguments", "cont");
> |+        return;
> |+    }
> |
> |     qmp_cont(&err);
> |     error_propagate(errp, err);
>
> QMP behavior for no-arg commands changes from:
>
> {"execute":"query-version","arguments":{"a":1}}
> {"error": {"class": "GenericError", "desc": "Invalid parameter 'a'"}}
>
> to:
>
> {"execute":"query-version","arguments":{"a":1}}
> {"error": {"class": "GenericError", "desc": "Command 'query-version' does not take arguments"}}
>
> and for commands with at least one (possibly-optional) argument,
> the output changes from:
>
> {"execute":"query-command-line-options","arguments":{"a":1}}
> {"error": {"class": "GenericError", "desc": "Invalid parameter 'a'"}}
>
> to:
>
> {"execute":"query-command-line-options","arguments":{"a":1}}
> {"error": {"class": "GenericError", "desc": "QMP input object member 'a' is unexpected"}}

This error message becomes rather generic.  Tolerable.  Can we restore
the old message without trouble?

> Signed-off-by: Eric Blake <eblake@redhat.com>
>
> ---
> v15: new patch
> ---
>  scripts/qapi-commands.py |   8 ++-
>  monitor.c                |   4 ++
>  qmp-commands.hx          | 148 +----------------------------------------------
>  3 files changed, 12 insertions(+), 148 deletions(-)
>
> diff --git a/scripts/qapi-commands.py b/scripts/qapi-commands.py
> index 04549fa..beb34c5 100644
> --- a/scripts/qapi-commands.py
> +++ b/scripts/qapi-commands.py
> @@ -136,8 +136,12 @@ def gen_marshal(name, arg_type, ret_type):
       if arg_type and arg_type.members:
           visit the parameter struct
>      else:
>          ret += mcgen('''
>
> -    (void)args;
> -''')
> +    if (args && qdict_size(args)) {
> +        error_setg(errp, "Command '%%s' does not take arguments", "%(name)s");
> +        return;
> +    }
> +''',
> +                     name=name)
>
>      ret += gen_call(name, arg_type, ret_type)
>

Works for me.

Naive question: is the special case "no arguments" really necessary
here?  Could we visit the empty struct instead?

> diff --git a/monitor.c b/monitor.c
> index d1c1930..bc8ff5e 100644
> --- a/monitor.c
> +++ b/monitor.c
> @@ -3783,6 +3783,7 @@ out:
>  /*
>   * Client argument checking rules:
>   *
> + * 0. If args_type is NULL, rely on the generated QAPI to do the check
>   * 1. Client must provide all mandatory arguments
>   * 2. Each argument provided by the client must be expected
>   * 3. Each argument provided by the client must have the type expected
> @@ -3795,6 +3796,9 @@ static void qmp_check_client_args(const mon_cmd_t *cmd, QDict *client_args,
>      int flags;
>      QDict *cmd_args;
>
> +    if (!cmd->args_type) {
> +        return;
> +    }



>      cmd_args = qdict_from_args_type(cmd->args_type);
>
>      flags = 0;

Let's review the other uses of args_type, to understand the impact of
deleting args_type values in qmp-commands.hx:

* monitor_parse_arguments()

  HMP only, not affected.

* monitor_find_completion_by_table()

  MONITOR_USE_READLINE only, see monitor_init().  But then it parses
  input in HMP syntax with parse_cmdline().  Crap code.

We're good.

> diff --git a/qmp-commands.hx b/qmp-commands.hx
> index de896a5..49189ce 100644
> --- a/qmp-commands.hx
> +++ b/qmp-commands.hx
> @@ -62,7 +62,6 @@ EQMP
>
>      {
>          .name       = "quit",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_quit,
>      },
>
> @@ -83,7 +82,6 @@ EQMP
>
>      {
>          .name       = "eject",
> -        .args_type  = "force:-f,device:B",
>          .mhandler.cmd_new = qmp_marshal_eject,
>      },
>
> @@ -109,7 +107,6 @@ EQMP
>
>      {
>          .name       = "change",
> -        .args_type  = "device:B,target:F,arg:s?",
>          .mhandler.cmd_new = qmp_marshal_change,
>      },
>
> @@ -145,7 +142,6 @@ EQMP
>
>      {
>          .name       = "screendump",
> -        .args_type  = "filename:F",
>          .mhandler.cmd_new = qmp_marshal_screendump,
>      },
>
> @@ -168,7 +164,6 @@ EQMP
>
>      {
>          .name       = "stop",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_stop,
>      },
>
> @@ -189,7 +184,6 @@ EQMP
>
>      {
>          .name       = "cont",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_cont,
>      },
>
> @@ -210,7 +204,6 @@ EQMP
>
>      {
>          .name       = "system_wakeup",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_system_wakeup,
>      },
>
> @@ -231,7 +224,6 @@ EQMP
>
>      {
>          .name       = "system_reset",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_system_reset,
>      },
>
> @@ -252,7 +244,6 @@ EQMP
>
>      {
>          .name       = "system_powerdown",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_system_powerdown,
>      },
>
> @@ -309,7 +300,6 @@ EQMP
>
>      {
>          .name       = "device_del",
> -        .args_type  = "id:s",
>          .mhandler.cmd_new = qmp_marshal_device_del,
>      },
>
> @@ -337,7 +327,6 @@ EQMP
>
>      {
>          .name       = "send-key",
> -        .args_type  = "keys:q,hold-time:i?",
>          .mhandler.cmd_new = qmp_marshal_send_key,
>      },
>
> @@ -368,7 +357,6 @@ EQMP
>
>      {
>          .name       = "cpu",
> -        .args_type  = "index:i",
>          .mhandler.cmd_new = qmp_marshal_cpu,
>      },
>
> @@ -393,7 +381,6 @@ EQMP
>
>      {
>          .name       = "cpu-add",
> -        .args_type  = "id:i",
>          .mhandler.cmd_new = qmp_marshal_cpu_add,
>      },
>
> @@ -416,7 +403,6 @@ EQMP
>
>      {
>          .name       = "memsave",
> -        .args_type  = "val:l,size:i,filename:s,cpu:i?",
>          .mhandler.cmd_new = qmp_marshal_memsave,
>      },
>
> @@ -445,7 +431,6 @@ EQMP
>
>      {
>          .name       = "pmemsave",
> -        .args_type  = "val:l,size:i,filename:s",
>          .mhandler.cmd_new = qmp_marshal_pmemsave,
>      },
>
> @@ -473,7 +458,6 @@ EQMP
>
>      {
>          .name       = "inject-nmi",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_inject_nmi,
>      },
>
> @@ -496,7 +480,6 @@ EQMP
>
>      {
>          .name       = "ringbuf-write",
> -        .args_type  = "device:s,data:s,format:s?",
>          .mhandler.cmd_new = qmp_marshal_ringbuf_write,
>      },
>
> @@ -525,7 +508,6 @@ EQMP
>
>      {
>          .name       = "ringbuf-read",
> -        .args_type  = "device:s,size:i,format:s?",
>          .mhandler.cmd_new = qmp_marshal_ringbuf_read,
>      },
>
> @@ -561,7 +543,6 @@ EQMP
>
>      {
>          .name       = "xen-save-devices-state",
> -        .args_type  = "filename:F",
>      .mhandler.cmd_new = qmp_marshal_xen_save_devices_state,
>      },
>
> @@ -588,7 +569,6 @@ EQMP
>
>      {
>          .name       = "xen-set-global-dirty-log",
> -        .args_type  = "enable:b",
>          .mhandler.cmd_new = qmp_marshal_xen_set_global_dirty_log,
>      },
>
> @@ -612,7 +592,6 @@ EQMP
>
>      {
>          .name       = "migrate",
> -        .args_type  = "detach:-d,blk:-b,inc:-i,uri:s",
>          .mhandler.cmd_new = qmp_marshal_migrate,
>      },
>
> @@ -645,7 +624,6 @@ EQMP
>
>      {
>          .name       = "migrate_cancel",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_migrate_cancel,
>      },
>
> @@ -666,7 +644,6 @@ EQMP
>
>      {
>          .name       = "migrate-incoming",
> -        .args_type  = "uri:s",
>          .mhandler.cmd_new = qmp_marshal_migrate_incoming,
>      },
>
> @@ -694,7 +671,6 @@ Notes:
>  EQMP
>      {
>          .name       = "migrate-set-cache-size",
> -        .args_type  = "value:o",
>          .mhandler.cmd_new = qmp_marshal_migrate_set_cache_size,
>      },
>
> @@ -717,7 +693,6 @@ Example:
>  EQMP
>      {
>          .name       = "migrate-start-postcopy",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_migrate_start_postcopy,
>      },
>
> @@ -736,7 +711,6 @@ EQMP
>
>      {
>          .name       = "query-migrate-cache-size",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_query_migrate_cache_size,
>      },
>
> @@ -758,7 +732,6 @@ EQMP
>
>      {
>          .name       = "migrate_set_speed",
> -        .args_type  = "value:o",
>          .mhandler.cmd_new = qmp_marshal_migrate_set_speed,
>      },
>
> @@ -781,7 +754,6 @@ EQMP
>
>      {
>          .name       = "migrate_set_downtime",
> -        .args_type  = "value:T",
>          .mhandler.cmd_new = qmp_marshal_migrate_set_downtime,
>      },
>
> @@ -804,7 +776,6 @@ EQMP
>
>      {
>          .name       = "client_migrate_info",
> -        .args_type  = "protocol:s,hostname:s,port:i?,tls-port:i?,cert-subject:s?",
>          .params     = "protocol hostname port tls-port cert-subject",
>          .help       = "set migration information for remote display",
>          .mhandler.cmd_new = qmp_marshal_client_migrate_info,
> @@ -838,7 +809,6 @@ EQMP
>
>      {
>          .name       = "dump-guest-memory",
> -        .args_type  = "paging:b,protocol:s,detach:b?,begin:i?,end:i?,format:s?",
>          .params     = "-p protocol [-d] [begin] [length] [format]",
>          .help       = "dump guest memory to file",
>          .mhandler.cmd_new = qmp_marshal_dump_guest_memory,
> @@ -879,8 +849,7 @@ EQMP
>
>      {
>          .name       = "query-dump-guest-memory-capability",
> -        .args_type  = "",
> -    .mhandler.cmd_new = qmp_marshal_query_dump_guest_memory_capability,
> +        .mhandler.cmd_new = qmp_marshal_query_dump_guest_memory_capability,
>      },
>
>  SQMP
> @@ -899,7 +868,6 @@ EQMP
>
>      {
>          .name       = "query-dump",
> -        .args_type  = "",
>          .params     = "",
>          .help       = "query background dump status",
>          .mhandler.cmd_new = qmp_marshal_query_dump,
> @@ -924,7 +892,6 @@ EQMP
>  #if defined TARGET_S390X
>      {
>          .name       = "dump-skeys",
> -        .args_type  = "filename:F",
>          .mhandler.cmd_new = qmp_marshal_dump_skeys,
>      },
>  #endif
> @@ -979,7 +946,6 @@ EQMP
>
>      {
>          .name       = "netdev_del",
> -        .args_type  = "id:s",
>          .mhandler.cmd_new = qmp_marshal_netdev_del,
>      },
>
> @@ -1003,7 +969,6 @@ EQMP
>
>      {
>          .name       = "object-add",
> -        .args_type  = "qom-type:s,id:s,props:q?",
>          .mhandler.cmd_new = qmp_marshal_object_add,
>      },
>
> @@ -1029,7 +994,6 @@ EQMP
>
>      {
>          .name       = "object-del",
> -        .args_type  = "id:s",
>          .mhandler.cmd_new = qmp_marshal_object_del,
>      },
>
> @@ -1054,7 +1018,6 @@ EQMP
>
>      {
>          .name       = "block_resize",
> -        .args_type  = "device:s?,node-name:s?,size:o",
>          .mhandler.cmd_new = qmp_marshal_block_resize,
>      },
>
> @@ -1079,7 +1042,6 @@ EQMP
>
>      {
>          .name       = "block-stream",
> -        .args_type  = "device:B,base:s?,speed:o?,backing-file:s?,on-error:s?",
>          .mhandler.cmd_new = qmp_marshal_block_stream,
>      },
>
> @@ -1122,7 +1084,6 @@ EQMP
>
>      {
>          .name       = "block-commit",
> -        .args_type  = "device:B,base:s?,top:s?,backing-file:s?,speed:o?",
>          .mhandler.cmd_new = qmp_marshal_block_commit,
>      },
>
> @@ -1185,8 +1146,6 @@ EQMP
>
>      {
>          .name       = "drive-backup",
> -        .args_type  = "sync:s,device:B,target:s,speed:i?,mode:s?,format:s?,"
> -                      "bitmap:s?,on-source-error:s?,on-target-error:s?",
>          .mhandler.cmd_new = qmp_marshal_drive_backup,
>      },
>
> @@ -1239,8 +1198,6 @@ EQMP
>
>      {
>          .name       = "blockdev-backup",
> -        .args_type  = "sync:s,device:B,target:B,speed:i?,"
> -                      "on-source-error:s?,on-target-error:s?",
>          .mhandler.cmd_new = qmp_marshal_blockdev_backup,
>      },
>
> @@ -1280,33 +1237,27 @@ EQMP
>
>      {
>          .name       = "block-job-set-speed",
> -        .args_type  = "device:B,speed:o",
>          .mhandler.cmd_new = qmp_marshal_block_job_set_speed,
>      },
>
>      {
>          .name       = "block-job-cancel",
> -        .args_type  = "device:B,force:b?",
>          .mhandler.cmd_new = qmp_marshal_block_job_cancel,
>      },
>      {
>          .name       = "block-job-pause",
> -        .args_type  = "device:B",
>          .mhandler.cmd_new = qmp_marshal_block_job_pause,
>      },
>      {
>          .name       = "block-job-resume",
> -        .args_type  = "device:B",
>          .mhandler.cmd_new = qmp_marshal_block_job_resume,
>      },
>      {
>          .name       = "block-job-complete",
> -        .args_type  = "device:B",
>          .mhandler.cmd_new = qmp_marshal_block_job_complete,
>      },
>      {
>          .name       = "transaction",
> -        .args_type  = "actions:q,properties:q?",
>          .mhandler.cmd_new = qmp_marshal_transaction,
>      },
>
> @@ -1400,7 +1351,6 @@ EQMP
>
>      {
>          .name       = "block-dirty-bitmap-add",
> -        .args_type  = "node:B,name:s,granularity:i?",
>          .mhandler.cmd_new = qmp_marshal_block_dirty_bitmap_add,
>      },
>
> @@ -1428,7 +1378,6 @@ EQMP
>
>      {
>          .name       = "block-dirty-bitmap-remove",
> -        .args_type  = "node:B,name:s",
>          .mhandler.cmd_new = qmp_marshal_block_dirty_bitmap_remove,
>      },
>
> @@ -1456,7 +1405,6 @@ EQMP
>
>      {
>          .name       = "block-dirty-bitmap-clear",
> -        .args_type  = "node:B,name:s",
>          .mhandler.cmd_new = qmp_marshal_block_dirty_bitmap_clear,
>      },
>
> @@ -1485,7 +1433,6 @@ EQMP
>
>      {
>          .name       = "blockdev-snapshot-sync",
> -        .args_type  = "device:s?,node-name:s?,snapshot-file:s,snapshot-node-name:s?,format:s?,mode:s?",
>          .mhandler.cmd_new = qmp_marshal_blockdev_snapshot_sync,
>      },
>
> @@ -1521,7 +1468,6 @@ EQMP
>
>      {
>          .name       = "blockdev-snapshot",
> -        .args_type  = "node:s,overlay:s",
>          .mhandler.cmd_new = qmp_marshal_blockdev_snapshot,
>      },
>
> @@ -1559,7 +1505,6 @@ EQMP
>
>      {
>          .name       = "blockdev-snapshot-internal-sync",
> -        .args_type  = "device:B,name:s",
>          .mhandler.cmd_new = qmp_marshal_blockdev_snapshot_internal_sync,
>      },
>
> @@ -1588,7 +1533,6 @@ EQMP
>
>      {
>          .name       = "blockdev-snapshot-delete-internal-sync",
> -        .args_type  = "device:B,id:s?,name:s?",
>          .mhandler.cmd_new =
>                        qmp_marshal_blockdev_snapshot_delete_internal_sync,
>      },
> @@ -1629,11 +1573,6 @@ EQMP
>
>      {
>          .name       = "drive-mirror",
> -        .args_type  = "sync:s,device:B,target:s,speed:i?,mode:s?,format:s?,"
> -                      "node-name:s?,replaces:s?,"
> -                      "on-source-error:s?,on-target-error:s?,"
> -                      "unmap:b?,"
> -                      "granularity:i?,buf-size:i?",
>          .mhandler.cmd_new = qmp_marshal_drive_mirror,
>      },
>
> @@ -1693,9 +1632,6 @@ EQMP
>
>      {
>          .name       = "blockdev-mirror",
> -        .args_type  = "sync:s,device:B,target:B,replaces:s?,speed:i?,"
> -                      "on-source-error:s?,on-target-error:s?,"
> -                      "granularity:i?,buf-size:i?",
>          .mhandler.cmd_new = qmp_marshal_blockdev_mirror,
>      },
>
> @@ -1741,7 +1677,6 @@ Example:
>  EQMP
>      {
>          .name       = "change-backing-file",
> -        .args_type  = "device:s,image-node-name:s,backing-file:s",
>          .mhandler.cmd_new = qmp_marshal_change_backing_file,
>      },
>
> @@ -1780,7 +1715,6 @@ EQMP
>
>      {
>          .name       = "balloon",
> -        .args_type  = "value:M",
>          .mhandler.cmd_new = qmp_marshal_balloon,
>      },
>
> @@ -1803,7 +1737,6 @@ EQMP
>
>      {
>          .name       = "set_link",
> -        .args_type  = "name:s,up:b",
>          .mhandler.cmd_new = qmp_marshal_set_link,
>      },
>
> @@ -1827,7 +1760,6 @@ EQMP
>
>      {
>          .name       = "getfd",
> -        .args_type  = "fdname:s",
>          .params     = "getfd name",
>          .help       = "receive a file descriptor via SCM rights and assign it a name",
>          .mhandler.cmd_new = qmp_marshal_getfd,
> @@ -1860,7 +1792,6 @@ EQMP
>
>      {
>          .name       = "closefd",
> -        .args_type  = "fdname:s",
>          .params     = "closefd name",
>          .help       = "close a file descriptor previously passed via SCM rights",
>          .mhandler.cmd_new = qmp_marshal_closefd,
> @@ -1885,7 +1816,6 @@ EQMP
>
>       {
>          .name       = "add-fd",
> -        .args_type  = "fdset-id:i?,opaque:s?",
>          .params     = "add-fd fdset-id opaque",
>          .help       = "Add a file descriptor, that was passed via SCM rights, to an fd set",
>          .mhandler.cmd_new = qmp_marshal_add_fd,
> @@ -1924,7 +1854,6 @@ EQMP
>
>       {
>          .name       = "remove-fd",
> -        .args_type  = "fdset-id:i,fd:i?",
>          .params     = "remove-fd fdset-id fd",
>          .help       = "Remove a file descriptor from an fd set",
>          .mhandler.cmd_new = qmp_marshal_remove_fd,
> @@ -1957,7 +1886,6 @@ EQMP
>
>      {
>          .name       = "query-fdsets",
> -        .args_type  = "",
>          .help       = "Return information describing all fd sets",
>          .mhandler.cmd_new = qmp_marshal_query_fdsets,
>      },
> @@ -2007,7 +1935,6 @@ EQMP
>
>      {
>          .name       = "block_passwd",
> -        .args_type  = "device:s?,node-name:s?,password:s",
>          .mhandler.cmd_new = qmp_marshal_block_passwd,
>      },
>
> @@ -2033,7 +1960,6 @@ EQMP
>
>      {
>          .name       = "block_set_io_throttle",
> -        .args_type  = "device:B,bps:l,bps_rd:l,bps_wr:l,iops:l,iops_rd:l,iops_wr:l,bps_max:l?,bps_rd_max:l?,bps_wr_max:l?,iops_max:l?,iops_rd_max:l?,iops_wr_max:l?,bps_max_length:l?,bps_rd_max_length:l?,bps_wr_max_length:l?,iops_max_length:l?,iops_rd_max_length:l?,iops_wr_max_length:l?,iops_size:l?,group:s?",
>          .mhandler.cmd_new = qmp_marshal_block_set_io_throttle,
>      },
>
> @@ -2090,7 +2016,6 @@ EQMP
>
>      {
>          .name       = "set_password",
> -        .args_type  = "protocol:s,password:s,connected:s?",
>          .mhandler.cmd_new = qmp_marshal_set_password,
>      },
>
> @@ -2116,7 +2041,6 @@ EQMP
>
>      {
>          .name       = "expire_password",
> -        .args_type  = "protocol:s,time:s",
>          .mhandler.cmd_new = qmp_marshal_expire_password,
>      },
>
> @@ -2141,7 +2065,6 @@ EQMP
>
>      {
>          .name       = "add_client",
> -        .args_type  = "protocol:s,fdname:s,skipauth:b?,tls:b?",
>          .mhandler.cmd_new = qmp_marshal_add_client,
>      },
>
> @@ -2192,7 +2115,6 @@ EQMP
>
>      {
>          .name       = "human-monitor-command",
> -        .args_type  = "command-line:s,cpu-index:i?",
>          .mhandler.cmd_new = qmp_marshal_human_monitor_command,
>      },
>
> @@ -2271,7 +2193,6 @@ EQMP
>
>      {
>          .name       = "query-version",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_query_version,
>      },
>
> @@ -2308,7 +2229,6 @@ EQMP
>
>      {
>          .name       = "query-commands",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_query_commands,
>      },
>
> @@ -2345,7 +2265,6 @@ EQMP
>
>      {
>          .name       = "query-events",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_query_events,
>      },
>
> @@ -2362,7 +2281,7 @@ EQMP
>
>      {
>          .name       = "query-qmp-schema",
> -        .args_type  = "",
> +	.args_type  = "",
>          .mhandler.cmd_new = qmp_query_qmp_schema,
>      },

Spurious hunk.

>
> @@ -2407,7 +2326,6 @@ EQMP
>
>      {
>          .name       = "query-chardev",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_query_chardev,
>      },
>
> @@ -2448,7 +2366,6 @@ EQMP
>
>      {
>          .name       = "query-chardev-backends",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_query_chardev_backends,
>      },
>
> @@ -2632,7 +2549,6 @@ EQMP
>
>      {
>          .name       = "query-block",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_query_block,
>      },
>
> @@ -2829,7 +2745,6 @@ EQMP
>
>      {
>          .name       = "query-blockstats",
> -        .args_type  = "query-nodes:b?",
>          .mhandler.cmd_new = qmp_marshal_query_blockstats,
>      },
>
> @@ -2884,7 +2799,6 @@ EQMP
>
>      {
>          .name       = "query-cpus",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_query_cpus,
>      },
>
> @@ -2923,7 +2837,6 @@ EQMP
>
>      {
>          .name       = "query-iothreads",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_query_iothreads,
>      },
>
> @@ -3140,7 +3053,6 @@ EQMP
>
>      {
>          .name       = "query-pci",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_query_pci,
>      },
>
> @@ -3164,7 +3076,6 @@ EQMP
>
>      {
>          .name       = "query-kvm",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_query_kvm,
>      },
>
> @@ -3204,7 +3115,6 @@ EQMP
>      
>      {
>          .name       = "query-status",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_query_status,
>      },
>
> @@ -3248,7 +3158,6 @@ EQMP
>
>      {
>          .name       = "query-mice",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_query_mice,
>      },
>
> @@ -3311,12 +3220,10 @@ EQMP
>
>      {
>          .name       = "query-vnc",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_query_vnc,
>      },
>      {
>          .name       = "query-vnc-servers",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_query_vnc_servers,
>      },
>
> @@ -3393,7 +3300,6 @@ EQMP
>  #if defined(CONFIG_SPICE)
>      {
>          .name       = "query-spice",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_query_spice,
>      },
>  #endif
> @@ -3417,7 +3323,6 @@ EQMP
>
>      {
>          .name       = "query-name",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_query_name,
>      },
>
> @@ -3440,7 +3345,6 @@ EQMP
>
>      {
>          .name       = "query-uuid",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_query_uuid,
>      },
>
> @@ -3489,7 +3393,6 @@ EQMP
>
>      {
>          .name       = "query-command-line-options",
> -        .args_type  = "option:s?",
>          .mhandler.cmd_new = qmp_marshal_query_command_line_options,
>      },
>
> @@ -3667,7 +3570,6 @@ EQMP
>
>      {
>          .name       = "query-migrate",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_query_migrate,
>      },
>
> @@ -3696,7 +3598,6 @@ EQMP
>
>      {
>          .name       = "migrate-set-capabilities",
> -        .args_type  = "capabilities:q",
>          .params     = "capability:s,state:b",
>          .mhandler.cmd_new = qmp_marshal_migrate_set_capabilities,
>      },
> @@ -3734,7 +3635,6 @@ EQMP
>
>      {
>          .name       = "query-migrate-capabilities",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_query_migrate_capabilities,
>      },
>
> @@ -3763,8 +3663,6 @@ EQMP
>
>      {
>          .name       = "migrate-set-parameters",
> -        .args_type  =
> -            "compress-level:i?,compress-threads:i?,decompress-threads:i?,x-cpu-throttle-initial:i?,x-cpu-throttle-increment:i?",
>          .mhandler.cmd_new = qmp_marshal_migrate_set_parameters,
>      },
>  SQMP
> @@ -3801,7 +3699,6 @@ EQMP
>
>      {
>          .name       = "query-migrate-parameters",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_query_migrate_parameters,
>      },
>
> @@ -3829,88 +3726,73 @@ EQMP
>
>      {
>          .name       = "query-balloon",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_query_balloon,
>      },
>
>      {
>          .name       = "query-block-jobs",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_query_block_jobs,
>      },
>
>      {
>          .name       = "qom-list",
> -        .args_type  = "path:s",
>          .mhandler.cmd_new = qmp_marshal_qom_list,
>      },
>
>      {
>          .name       = "qom-set",
> -	.args_type  = "path:s,property:s,value:q",
>          .mhandler.cmd_new = qmp_marshal_qom_set,
>      },
>
>      {
>          .name       = "qom-get",
> -	.args_type  = "path:s,property:s",
>          .mhandler.cmd_new = qmp_marshal_qom_get,
>      },
>
>      {
>          .name       = "nbd-server-start",
> -        .args_type  = "addr:q,tls-creds:s?",
>          .mhandler.cmd_new = qmp_marshal_nbd_server_start,
>      },
>      {
>          .name       = "nbd-server-add",
> -        .args_type  = "device:B,writable:b?",
>          .mhandler.cmd_new = qmp_marshal_nbd_server_add,
>      },
>      {
>          .name       = "nbd-server-stop",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_nbd_server_stop,
>      },
>
>      {
>          .name       = "change-vnc-password",
> -        .args_type  = "password:s",
>          .mhandler.cmd_new = qmp_marshal_change_vnc_password,
>      },
>      {
>          .name       = "qom-list-types",
> -        .args_type  = "implements:s?,abstract:b?",
>          .mhandler.cmd_new = qmp_marshal_qom_list_types,
>      },
>
>      {
>          .name       = "device-list-properties",
> -        .args_type  = "typename:s",
>          .mhandler.cmd_new = qmp_marshal_device_list_properties,
>      },
>
>      {
>          .name       = "query-machines",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_query_machines,
>      },
>
>      {
>          .name       = "query-cpu-definitions",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_query_cpu_definitions,
>      },
>
>      {
>          .name       = "query-target",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_query_target,
>      },
>
>      {
>          .name       = "query-tpm",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_query_tpm,
>      },
>
> @@ -3944,7 +3826,6 @@ EQMP
>
>      {
>          .name       = "query-tpm-models",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_query_tpm_models,
>      },
>
> @@ -3965,7 +3846,6 @@ EQMP
>
>      {
>          .name       = "query-tpm-types",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_query_tpm_types,
>      },
>
> @@ -3986,7 +3866,6 @@ EQMP
>
>      {
>          .name       = "chardev-add",
> -        .args_type  = "id:s,backend:q",
>          .mhandler.cmd_new = qmp_marshal_chardev_add,
>      },
>
> @@ -4023,7 +3902,6 @@ EQMP
>
>      {
>          .name       = "chardev-remove",
> -        .args_type  = "id:s",
>          .mhandler.cmd_new = qmp_marshal_chardev_remove,
>      },
>
> @@ -4046,7 +3924,6 @@ Example:
>  EQMP
>      {
>          .name       = "query-rx-filter",
> -        .args_type  = "name:s?",
>          .mhandler.cmd_new = qmp_marshal_query_rx_filter,
>      },
>
> @@ -4112,7 +3989,6 @@ EQMP
>
>      {
>          .name       = "blockdev-add",
> -        .args_type  = "options:q",
>          .mhandler.cmd_new = qmp_marshal_blockdev_add,
>      },
>
> @@ -4171,7 +4047,6 @@ EQMP
>
>      {
>          .name       = "x-blockdev-del",
> -        .args_type  = "id:s?,node-name:s?",
>          .mhandler.cmd_new = qmp_marshal_x_blockdev_del,
>      },
>
> @@ -4228,7 +4103,6 @@ EQMP
>
>      {
>          .name       = "blockdev-open-tray",
> -        .args_type  = "device:s,force:b?",
>          .mhandler.cmd_new = qmp_marshal_blockdev_open_tray,
>      },
>
> @@ -4276,7 +4150,6 @@ EQMP
>
>      {
>          .name       = "blockdev-close-tray",
> -        .args_type  = "device:s",
>          .mhandler.cmd_new = qmp_marshal_blockdev_close_tray,
>      },
>
> @@ -4311,7 +4184,6 @@ EQMP
>
>      {
>          .name       = "x-blockdev-remove-medium",
> -        .args_type  = "device:s",
>          .mhandler.cmd_new = qmp_marshal_x_blockdev_remove_medium,
>      },
>
> @@ -4359,7 +4231,6 @@ EQMP
>
>      {
>          .name       = "x-blockdev-insert-medium",
> -        .args_type  = "device:s,node-name:s",
>          .mhandler.cmd_new = qmp_marshal_x_blockdev_insert_medium,
>      },
>
> @@ -4399,7 +4270,6 @@ EQMP
>
>      {
>          .name       = "query-named-block-nodes",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_query_named_block_nodes,
>      },
>
> @@ -4461,7 +4331,6 @@ EQMP
>
>      {
>          .name       = "blockdev-change-medium",
> -        .args_type  = "device:B,filename:F,format:s?,read-only-mode:s?",
>          .mhandler.cmd_new = qmp_marshal_blockdev_change_medium,
>      },
>
> @@ -4514,7 +4383,6 @@ EQMP
>
>      {
>          .name       = "query-memdev",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_query_memdev,
>      },
>
> @@ -4552,7 +4420,6 @@ EQMP
>
>      {
>          .name       = "query-memory-devices",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_query_memory_devices,
>      },
>
> @@ -4579,7 +4446,6 @@ EQMP
>
>      {
>          .name       = "query-acpi-ospm-status",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_query_acpi_ospm_status,
>      },
>
> @@ -4602,7 +4468,6 @@ EQMP
>  #if defined TARGET_I386
>      {
>          .name       = "rtc-reset-reinjection",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_rtc_reset_reinjection,
>      },
>  #endif
> @@ -4623,7 +4488,6 @@ EQMP
>
>      {
>          .name       = "trace-event-get-state",
> -        .args_type  = "name:s",
>          .mhandler.cmd_new = qmp_marshal_trace_event_get_state,
>      },
>
> @@ -4641,7 +4505,6 @@ EQMP
>
>      {
>          .name       = "trace-event-set-state",
> -        .args_type  = "name:s,enable:b,ignore-unavailable:b?",
>          .mhandler.cmd_new = qmp_marshal_trace_event_set_state,
>      },
>
> @@ -4659,7 +4522,6 @@ EQMP
>
>      {
>          .name       = "input-send-event",
> -        .args_type  = "console:i?,events:q",
>          .mhandler.cmd_new = qmp_marshal_input_send_event,
>      },
>
> @@ -4725,7 +4587,6 @@ EQMP
>
>      {
>          .name       = "block-set-write-threshold",
> -        .args_type  = "node-name:s,write-threshold:l",
>          .mhandler.cmd_new = qmp_marshal_block_set_write_threshold,
>      },
>
> @@ -4753,7 +4614,6 @@ EQMP
>
>      {
>          .name       = "query-rocker",
> -        .args_type  = "name:s",
>          .mhandler.cmd_new = qmp_marshal_query_rocker,
>      },
>
> @@ -4774,7 +4634,6 @@ EQMP
>
>      {
>          .name       = "query-rocker-ports",
> -        .args_type  = "name:s",
>          .mhandler.cmd_new = qmp_marshal_query_rocker_ports,
>      },
>
> @@ -4799,7 +4658,6 @@ EQMP
>
>      {
>          .name       = "query-rocker-of-dpa-flows",
> -        .args_type  = "name:s,tbl-id:i?",
>          .mhandler.cmd_new = qmp_marshal_query_rocker_of_dpa_flows,
>      },
>
> @@ -4828,7 +4686,6 @@ EQMP
>
>      {
>          .name       = "query-rocker-of-dpa-groups",
> -        .args_type  = "name:s,type:i?",
>          .mhandler.cmd_new = qmp_marshal_query_rocker_of_dpa_groups,
>      },
>
> @@ -4859,7 +4716,6 @@ EQMP
>  #if defined TARGET_ARM
>      {
>          .name       = "query-gic-capabilities",
> -        .args_type  = "",
>          .mhandler.cmd_new = qmp_marshal_query_gic_capabilities,
>      },
>  #endif

Entries defining anything beyond .name and .mhandler.cmd_new:

* device_add, qmp_capabilities

  Not QAPIfied, need everything.

* netdev_add, query-qmp-schema

  Need .args_type because of 'gen': false.

* client_migrate_info, dump-guest-memory, query-dump, getfd, closefd,
  add-fd, remove-fd, query-fdsets, migrate-set-capabilities

  Define .params and/or .help.  Does this make any sense?

A comment explaining which members need to be set would be nice.

Have you checked Marc-André's work for overlap?  Cc'ing him.

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [Qemu-devel] [PATCH v15 05/23] qapi: Use strict QMP input visitor in more places
  2016-04-28 13:06   ` Markus Armbruster
@ 2016-04-28 14:28     ` Eric Blake
  0 siblings, 0 replies; 49+ messages in thread
From: Eric Blake @ 2016-04-28 14:28 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: qemu-devel, Michael Roth, Gerd Hoffmann, Paolo Bonzini,
	Andreas Färber

[-- Attachment #1: Type: text/plain, Size: 6669 bytes --]

On 04/28/2016 07:06 AM, Markus Armbruster wrote:
> Eric Blake <eblake@redhat.com> writes:
> 
>> Rather than having two separate ways to create a QMP input
>> visitor, where the safer approach has the more verbose name,
>> it is better to consolidate things into a single function
>> where the caller must explicitly choose whether to be strict
>> or to ignore excess input.  Use strict mode in more places of
>> internal code (such as when cloning a QAPI struct in
>> util/socket.c, where the QObject better not have excess
>> input since it was just generated by qmp-output), while
>> documenting in user-facing code a question of whether we
>> should change our policy about ignoring excess input.
> 
> Which external interface is this?

QMP qom-set, via object_property_set_qobject()

> 
>>
>> In the case of qmp_object_add(), we intentionally switch to a
>> strict visitor; this matches the fact that the code for
>> user_creatable_add_type() is shared by both qmp_object_add()
>> (QMP input visitor) and by user_creatable_add_opts() (QemuOpts
>> visitor); the latter is always strict, so our usage in the
>> former should also be strict, so that both visits will
>> equally diagnose any excess input in a nested dict.  But in
>> practice, we don't really have any -object where the
>> properties are a nested dict, and excess input at the top
>> level is already caught earlier by object_property_set() on
>> an unknown key, whether from QemuOpts:
>>
>> $ ./x86_64-softmmu/qemu-system-x86_64 -nographic -nodefaults -qmp stdio -object secret,id=sec0,data=letmein,format=raw,foo=bar
>> qemu-system-x86_64: Property '.foo' not found
> 
> Let's update the error message now that the error message regression is
> fixed.  Can do on commit.
> 

Thanks. And now you know why I reported the regression.

>>
>> or from QMP:
>>
>> $ ./x86_64-softmmu/qemu-system-x86_64 -nographic -nodefaults -qmp stdio
>> {"QMP": {"version": {"qemu": {"micro": 93, "minor": 5, "major": 2}, "package": ""}, "capabilities": []}}
>> {"execute":"qmp_capabilities"}
>> {"return": {}}
>> {"execute":"object-add","arguments":{"qom-type":"secret","id":"sec0","props":{"format":"raw","data":"letmein","a":1}}}
>> {"error": {"class": "GenericError", "desc": "Property '.a' not found"}}
>>
>> Signed-off-by: Eric Blake <eblake@redhat.com>
> 
> Should we split this into a patch to change the interface, and one or
> more separate patches to switch to the strict visitor?  Could result in
> clearer and more complete commit messages.

Could do.


>> +++ b/qom/qom-qobject.c
>> @@ -22,7 +22,8 @@ void object_property_set_qobject(Object *obj, QObject *value,
>>                                   const char *name, Error **errp)
>>  {
>>      QmpInputVisitor *qiv;
>> -    qiv = qmp_input_visitor_new(value);
>> +    /* TODO: Should we reject, rather than ignore, excess input? */
>> +    qiv = qmp_input_visitor_new(value, false);
>>      object_property_set(obj, qmp_input_get_visitor(qiv), name, errp);
>>
>>      qmp_input_visitor_cleanup(qiv);
> 
> Stay lenient, but document this should perhaps switch to strict.  The
> commit message hints at this one.

"hints at" and "explicitly mentions by name" are two different things, I
get your point that my commit message could have been better.


>> +++ b/replay/replay-input.c
>> @@ -37,7 +37,7 @@ static InputEvent *qapi_clone_InputEvent(InputEvent *src)
>>          return NULL;
>>      }
>>
>> -    qiv = qmp_input_visitor_new(obj);
>> +    qiv = qmp_input_visitor_new(obj, true);
>>      iv = qmp_input_get_visitor(qiv);
>>      visit_type_InputEvent(iv, NULL, &dst, &error_abort);
>>      qmp_input_visitor_cleanup(qiv);
> 
> Switch to strict.  Not mentioned in commit message.  Why is it a good
> idea?

Same category as util/qemu-sockets.c: when cloning, we shouldn't be
introducing any junk from the QObject just produced from the qmp-output
visitor.  (Hmm, that also means that when cloning, we are now doing MORE
work to check for a "can't happen" condition - but it all goes away in a
later patch when I introduce a real cloning visitor that is more
efficient than a round-trip qmp-output => qmp-input double pass - so
maybe on that grounds, I don't need to convert this or the
qemu-sockets.c case)

>> +++ b/tests/test-qmp-commands.c
>> @@ -222,7 +222,7 @@ static void test_dealloc_partial(void)
>>          ud2_dict = qdict_new();
>>          qdict_put_obj(ud2_dict, "string0", QOBJECT(qstring_from_str(text)));
>>
>> -        qiv = qmp_input_visitor_new(QOBJECT(ud2_dict));
>> +        qiv = qmp_input_visitor_new(QOBJECT(ud2_dict), true);
>>          visit_type_UserDefTwo(qmp_input_get_visitor(qiv), NULL, &ud2, &err);
>>          qmp_input_visitor_cleanup(qiv);
>>          QDECREF(ud2_dict);
> 
> Switch to strict.  Not mentioned in commit message.  Why is it a good
> idea?

Because it's a testsuite and still passes with stricter testing, thus
any future additions to the testsuite will exercise strict mode which is
what we want to use in more places.

>> +++ b/tests/test-qmp-input-visitor.c
>> @@ -51,7 +51,7 @@ static Visitor *visitor_input_test_init_internal(TestInputVisitorData *data,
>>      data->obj = qobject_from_jsonv(json_string, ap);
>>      g_assert(data->obj);
>>
>> -    data->qiv = qmp_input_visitor_new(data->obj);
>> +    data->qiv = qmp_input_visitor_new(data->obj, false);
>>      g_assert(data->qiv);
>>
>>      v = qmp_input_get_visitor(data->qiv);
>> diff --git a/tests/test-visitor-serialization.c b/tests/test-visitor-serialization.c
> 
> Stay lenient (because we're testing it).

If we get rid of all lenient clients other than test-qmp-input-visitor,
then maybe lenient mode is worth nuking, and simplify our testsuite by
consolidating test-qmp-input-{visitor,strict}?  But food for thought for
a later day.

>> +++ b/tests/test-visitor-serialization.c
>> @@ -1038,7 +1038,7 @@ static void qmp_deserialize(void **native_out, void *datap,
>>      obj = qobject_from_json(qstring_get_str(output_json));
>>
>>      QDECREF(output_json);
>> -    d->qiv = qmp_input_visitor_new(obj);
>> +    d->qiv = qmp_input_visitor_new(obj, true);
>>      qobject_decref(obj_orig);
>>      qobject_decref(obj);
>>      visit(qmp_input_get_visitor(d->qiv), native_out, errp);
> 
> Switch to strict.  Not mentioned in commit message.  Why is it a good
> idea?

Same argument as test-qmp-commands, and same flaw in the commit message
for not calling it out.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [Qemu-devel] [PATCH v15 08/23] monitor: Let generated code validate arguments
  2016-04-28 14:09   ` Markus Armbruster
@ 2016-04-28 14:39     ` Marc-André Lureau
  2016-04-28 18:00       ` Markus Armbruster
  2016-04-28 14:47     ` Eric Blake
  1 sibling, 1 reply; 49+ messages in thread
From: Marc-André Lureau @ 2016-04-28 14:39 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: Eric Blake, Luiz Capitulino, QEMU, Michael Roth

On Thu, Apr 28, 2016 at 4:09 PM, Markus Armbruster <armbru@redhat.com> wrote:
>
> * device_add, qmp_capabilities
>
>   Not QAPIfied, need everything.

Some related commits from https://github.com/elmarco/qemu/commits/qapi


https://github.com/elmarco/qemu/commit/e6828a0a926e20d3ee91ea956152b610ca30d2b8

https://github.com/elmarco/qemu/commit/ba871db1c8d0edd0cbe7be716952f72c9df519a2

>
> * netdev_add, query-qmp-schema
>
>   Need .args_type because of 'gen': false.
>
> * client_migrate_info, dump-guest-memory, query-dump, getfd, closefd,
>   add-fd, remove-fd, query-fdsets, migrate-set-capabilities
>
>   Define .params and/or .help.  Does this make any sense?
>
> A comment explaining which members need to be set would be nice.
>
> Have you checked Marc-André's work for overlap?  Cc'ing him.

yeah, it's conflicting with my work. Unfortunately, since I have been
asked to wait since a long time, I am no longer on top of things, nor
what I need to wait for. I have the feeling we really want
qmp_dispatch() in monitor.c:
https://github.com/elmarco/qemu/commit/af4158993088ee2bbf9087c440990880d7943eb1.

Eric, I have been waiting for your series to settle for getting back
to qapi, but it keeps growing ;) I hope you are familiar with the work
I did in the branch so we don't duplicate efforts.

-- 
Marc-André Lureau

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [Qemu-devel] [PATCH v15 09/23] qom: Wrap prop visit in visit_start_struct
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 09/23] qom: Wrap prop visit in visit_start_struct Eric Blake
@ 2016-04-28 14:46   ` Markus Armbruster
  2016-04-28 15:14     ` Eric Blake
  0 siblings, 1 reply; 49+ messages in thread
From: Markus Armbruster @ 2016-04-28 14:46 UTC (permalink / raw)
  To: Eric Blake; +Cc: qemu-devel, Andreas Färber

Eric Blake <eblake@redhat.com> writes:

> The qmp-input visitor was allowing callers to play rather fast
> and loose: when visiting a QDict, you could grab members of the
> root dictionary without first pushing into the dict; the final
> such culprit was the QOM code for converting to and from object
> properties.  But we are about to tighten the input visitor, at
> which point user_creatable_add_type() as called with a QMP input
> visitor via qmp_object_add() MUST follow the same paradigms as
> everyone else, of pushing into the struct before grabbing its
> keys.
>
> The use of 'err ? NULL : &err' is temporary; a later patch will
> clean that up when it splits visit_end_struct().
>
> The change has no impact to the testsuite now, but is required to
> avoid a failure in tests/test-netfilter once qmp-input is made
> stricter to detect inconsistent 'name' arguments on the root visit.
>
> Since user_creatable_add_type() is also called with OptsVisitor
> through user_creatable_add_opts(), we must also check that there
> is no negative impact there; both pre- and post-patch, we see:
>
> $ ./x86_64-softmmu/qemu-system-x86_64 -nographic -nodefaults -qmp stdio -object secret,id=sec0,data=letmein,format=raw,foo=bar
> qemu-system-x86_64: Property '.foo' not found

Let's update the error message now that the error message regression is
fixed.  Can do on commit.

> That is, the only new checking that the new visit_end_struct() can
> perform is for excess input, but we already catch excess input
> earlier in object_property_set().

Answers the question I had for v14.  Didn't double check myself; I trust
your analysis.

> Signed-off-by: Eric Blake <eblake@redhat.com>
>
> ---
> v15: hoist earlier in series, improve commit message
> v14: no change
> v13: no change
> v12: new patch
> ---
>  qom/object_interfaces.c | 10 +++++++++-
>  1 file changed, 9 insertions(+), 1 deletion(-)
>
> diff --git a/qom/object_interfaces.c b/qom/object_interfaces.c
> index ab5da35..4a60d6d 100644
> --- a/qom/object_interfaces.c
> +++ b/qom/object_interfaces.c
> @@ -120,12 +120,20 @@ Object *user_creatable_add_type(const char *type, const char *id,
>
>      obj = object_new(type);
>      if (qdict) {
> +        visit_start_struct(v, NULL, NULL, 0, &local_err);
> +        if (local_err) {
> +            goto out;
> +        }
>          for (e = qdict_first(qdict); e; e = qdict_next(qdict, e)) {
>              object_property_set(obj, v, e->key, &local_err);
>              if (local_err) {
> -                goto out;
> +                break;
>              }
>          }
> +        visit_end_struct(v, local_err ? NULL : &local_err);
> +        if (local_err) {
> +            goto out;
> +        }
>      }
>
>      object_property_add_child(object_get_objects_root(),

Hmm.  How should this behave if !qdict?

Compare:

       visit_start_struct(v, NULL, NULL, 0, &local_err);
       if (local_err) {
           goto out;
       }
       if (qdict) {
           for (e = qdict_first(qdict); e; e = qdict_next(qdict, e)) {
               object_property_set(obj, v, e->key, &local_err);
               if (local_err) {
                   break;
               }
           }
       }
       visit_end_struct(v, local_err ? NULL : &local_err);
       if (local_err) {
           goto out;
       }

The difference is that we get errors from visit_start_struct() and
visit_end_struct() even when !qdict.

However, both callers seem to pass non-null qdict.  Should we sidestep
the question by making that a precondition?

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [Qemu-devel] [PATCH v15 08/23] monitor: Let generated code validate arguments
  2016-04-28 14:09   ` Markus Armbruster
  2016-04-28 14:39     ` Marc-André Lureau
@ 2016-04-28 14:47     ` Eric Blake
  1 sibling, 0 replies; 49+ messages in thread
From: Eric Blake @ 2016-04-28 14:47 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: qemu-devel, Michael Roth, Luiz Capitulino, Marc-André Lureau

[-- Attachment #1: Type: text/plain, Size: 3729 bytes --]

On 04/28/2016 08:09 AM, Markus Armbruster wrote:
> Eric Blake <eblake@redhat.com> writes:
> 
>> Having to manually call out the set of expected arguments in
>> qmp-commands.hx, in addition to what is already in *.json,
>> is tedious and prone to error.  The only reason we use
>> .args_type is for checking if there is any excess arguments
>> or incorrectly typed arguments during qmp_check_client_args(),
>> but a strict QMP Input visitor also does that checking.
> 
> We also use it for completion.

Does scripts/qmp/qmp-shell do completion?  It's a script, is it parsing
the .hx file?

I know we do completion for HMP, but this is QMP that I'm tweaking, and
other than qmp-shell, I don't know of any qemu code that wants to do QMP
completion.  We have query-qmp-schema that could be used to provide
completion, if needed.

>> and for commands with at least one (possibly-optional) argument,
>> the output changes from:
>>
>> {"execute":"query-command-line-options","arguments":{"a":1}}
>> {"error": {"class": "GenericError", "desc": "Invalid parameter 'a'"}}
>>

QERR_INVALID_PARAMETER

>> to:
>>
>> {"execute":"query-command-line-options","arguments":{"a":1}}
>> {"error": {"class": "GenericError", "desc": "QMP input object member 'a' is unexpected"}}
> 
> This error message becomes rather generic.  Tolerable.  Can we restore
> the old message without trouble?

QERR_QMP_EXTRA_MEMBER

Yes, it's quite easy to switch between the two error message strings by
tweaking the choice of message in qmp_input_check_struct() (at the end
of the series; it's in qmp_input_pop() at this point of the series).

>>
>> -    (void)args;
>> -''')
>> +    if (args && qdict_size(args)) {
>> +        error_setg(errp, "Command '%%s' does not take arguments", "%(name)s");
>> +        return;
>> +    }
>> +''',
>> +                     name=name)
>>
>>      ret += gen_call(name, arg_type, ret_type)
>>
> 
> Works for me.
> 
> Naive question: is the special case "no arguments" really necessary
> here?  Could we visit the empty struct instead?

If we had an empty struct visitor around. But right now the generator
special-cases ':empty' so that it has no generated functions.

>> @@ -2362,7 +2281,7 @@ EQMP
>>
>>      {
>>          .name       = "query-qmp-schema",
>> -        .args_type  = "",
>> +	.args_type  = "",
>>          .mhandler.cmd_new = qmp_query_qmp_schema,
>>      },
> 
> Spurious hunk.

Whoops. I first deleted it, then realized this was one of the few places
that doesn't use qmp_marshal_ so I had to restore it, but restored it
with the wrong whitespace.

> 
> Entries defining anything beyond .name and .mhandler.cmd_new:
> 
> * device_add, qmp_capabilities
> 
>   Not QAPIfied, need everything.
> 
> * netdev_add, query-qmp-schema
> 
>   Need .args_type because of 'gen': false.
> 
> * client_migrate_info, dump-guest-memory, query-dump, getfd, closefd,
>   add-fd, remove-fd, query-fdsets, migrate-set-capabilities
> 
>   Define .params and/or .help.  Does this make any sense?
> 
> A comment explaining which members need to be set would be nice.
> 
> Have you checked Marc-André's work for overlap?  Cc'ing him.

Marc-André QAPIfies device_add and qmp_capabilities, then completely
eliminates qmp-commands.hx.  This probably duplicates some of the work
in his queue, but should be fine in the short term.  As for whether any
of the other fields are needed or useful, I didn't check (but suspect
that no, they are not needed, again because this is QMP not HMP and that
only HMP 'info cmd' cares).

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [Qemu-devel] [PATCH v15 10/23] qmp-input: Require struct push to visit members of top dict
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 10/23] qmp-input: Require struct push to visit members of top dict Eric Blake
@ 2016-04-28 15:00   ` Markus Armbruster
  2016-04-28 15:04     ` Eric Blake
  0 siblings, 1 reply; 49+ messages in thread
From: Markus Armbruster @ 2016-04-28 15:00 UTC (permalink / raw)
  To: Eric Blake; +Cc: qemu-devel, Michael Roth

Eric Blake <eblake@redhat.com> writes:

> Don't embed the root of the visit into the stack of current
> containers being visited.  That way, we no longer get confused
> on whether the first visit of a dictionary is to the dictionary
> itself or to one of the members of the dictionary, based on
> whether the caller passed name=NULL; and makes the QMP Input
> visitor like other visitors where the value of 'name' is now
> ignored on the root visit.  (We may someday want to revisit
> the rules on what 'name' should be on a top-level visit,
> rather than just ignoring it; but that would be the topic of
> another patch).
>
> An audit of all qmp_input_visitor_new() call sites shows that
> there were only two places where callers had previously been
> visiting to a QDict with a non-NULL name to bypass a call to
> visit_start_struct(), and those were fixed in prior patches.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
>
> ---
> v15: hoist earlier in series, improve variable naming
> v14: no change
> v13: no change
> v12: new patch
> ---
>  qapi/qmp-input-visitor.c | 45 ++++++++++++++++++++++++---------------------
>  1 file changed, 24 insertions(+), 21 deletions(-)
>
> diff --git a/qapi/qmp-input-visitor.c b/qapi/qmp-input-visitor.c
> index cae6387..6409a83 100644
> --- a/qapi/qmp-input-visitor.c
> +++ b/qapi/qmp-input-visitor.c
> @@ -35,9 +35,11 @@ struct QmpInputVisitor
>  {
>      Visitor visitor;
>
> -    /* Stack of objects being visited.  stack[0] is root of visit,
> -     * stack[1..] records the nesting of start_struct()/end_struct()
> -     * and start_list()/end_list() pairs. */
> +    /* Root of visit at visitor creation. */
> +    QObject *root;
> +
> +    /* Stack of objects being visited (all entries will be either
> +     * QDict or QList). */
>      StackObject stack[QIV_STACK_SIZE];
>      int nb_stack;
>
> @@ -54,33 +56,34 @@ static QObject *qmp_input_get_object(QmpInputVisitor *qiv,
>                                       const char *name,
>                                       bool consume)
>  {
> -    StackObject *tos = &qiv->stack[qiv->nb_stack - 1];
> -    QObject *qobj = tos->obj;
> -    QObject *ret;
> +    StackObject *tos;
> +    QObject *qobj;
> +    QObject *ret = NULL;

Dead initializer?

>
> +    if (!qiv->nb_stack) {
> +        /* Starting at root, name is ignored. */
> +        return qiv->root;
> +    }
> +
> +    /* We are in a container; find the next element. */
> +    tos = &qiv->stack[qiv->nb_stack - 1];
> +    qobj = tos->obj;
>      assert(qobj);
>
> -    /* If we have a name, and we're in a dictionary, then return that
> -     * value. */
> -    if (name && qobject_type(qobj) == QTYPE_QDICT) {
> +    if (qobject_type(qobj) == QTYPE_QDICT) {
> +        assert(name);
>          ret = qdict_get(qobject_to_qdict(qobj), name);
>          if (tos->h && consume && ret) {
>              bool removed = g_hash_table_remove(tos->h, name);
>              assert(removed);
>          }
> -        return ret;
> -    }
> -
> -    /* If we are in the middle of a list, then return the next element
> -     * of the list. */
> -    if (tos->entry) {
> +    } else {
>          assert(qobject_type(qobj) == QTYPE_QLIST);
> -        return qlist_entry_obj(tos->entry);
> +        assert(!name);
> +        ret = qlist_entry_obj(tos->entry);
>      }
>
> -    /* Otherwise, we are at the root of the visit or the start of a
> -     * list, and return the object as-is. */
> -    return qobj;
> +    return ret;
>  }
>
>  static void qdict_add_key(const char *key, QObject *obj, void *opaque)
> @@ -355,7 +358,7 @@ Visitor *qmp_input_get_visitor(QmpInputVisitor *v)
>
>  void qmp_input_visitor_cleanup(QmpInputVisitor *v)
>  {
> -    qobject_decref(v->stack[0].obj);
> +    qobject_decref(v->root);
>      g_free(v);
>  }
>
> @@ -381,7 +384,7 @@ QmpInputVisitor *qmp_input_visitor_new(QObject *obj, bool strict)
>      v->visitor.optional = qmp_input_optional;
>      v->strict = strict;
>
> -    qmp_input_push(v, obj, NULL);
> +    v->root = obj;
>      qobject_incref(obj);
>
>      return v;

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [Qemu-devel] [PATCH v15 10/23] qmp-input: Require struct push to visit members of top dict
  2016-04-28 15:00   ` Markus Armbruster
@ 2016-04-28 15:04     ` Eric Blake
  0 siblings, 0 replies; 49+ messages in thread
From: Eric Blake @ 2016-04-28 15:04 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: qemu-devel, Michael Roth

[-- Attachment #1: Type: text/plain, Size: 2033 bytes --]

On 04/28/2016 09:00 AM, Markus Armbruster wrote:
> Eric Blake <eblake@redhat.com> writes:
> 
>> Don't embed the root of the visit into the stack of current
>> containers being visited.  That way, we no longer get confused
>> on whether the first visit of a dictionary is to the dictionary
>> itself or to one of the members of the dictionary, based on
>> whether the caller passed name=NULL; and makes the QMP Input
>> visitor like other visitors where the value of 'name' is now
>> ignored on the root visit.  (We may someday want to revisit
>> the rules on what 'name' should be on a top-level visit,
>> rather than just ignoring it; but that would be the topic of
>> another patch).
>>
>> An audit of all qmp_input_visitor_new() call sites shows that
>> there were only two places where callers had previously been
>> visiting to a QDict with a non-NULL name to bypass a call to
>> visit_start_struct(), and those were fixed in prior patches.
>>
>> Signed-off-by: Eric Blake <eblake@redhat.com>
>>
>> ---

>> @@ -54,33 +56,34 @@ static QObject *qmp_input_get_object(QmpInputVisitor *qiv,
>>                                       const char *name,
>>                                       bool consume)
>>  {
>> -    StackObject *tos = &qiv->stack[qiv->nb_stack - 1];
>> -    QObject *qobj = tos->obj;
>> -    QObject *ret;
>> +    StackObject *tos;
>> +    QObject *qobj;
>> +    QObject *ret = NULL;
> 
> Dead initializer?
> 

Probably a rebase artifact.

>> +    if (qobject_type(qobj) == QTYPE_QDICT) {
>> +        assert(name);
>>          ret = qdict_get(qobject_to_qdict(qobj), name);

...

>> +    } else {
>>          assert(qobject_type(qobj) == QTYPE_QLIST);
>> -        return qlist_entry_obj(tos->entry);
>> +        assert(!name);
>> +        ret = qlist_entry_obj(tos->entry);

It does indeed get set on all branches, so the initializer can be dropped.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [Qemu-devel] [PATCH v15 09/23] qom: Wrap prop visit in visit_start_struct
  2016-04-28 14:46   ` Markus Armbruster
@ 2016-04-28 15:14     ` Eric Blake
  0 siblings, 0 replies; 49+ messages in thread
From: Eric Blake @ 2016-04-28 15:14 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: qemu-devel, Andreas Färber

[-- Attachment #1: Type: text/plain, Size: 2794 bytes --]

On 04/28/2016 08:46 AM, Markus Armbruster wrote:
> Eric Blake <eblake@redhat.com> writes:
> 
>> The qmp-input visitor was allowing callers to play rather fast
>> and loose: when visiting a QDict, you could grab members of the
>> root dictionary without first pushing into the dict; the final
>> such culprit was the QOM code for converting to and from object
>> properties.  But we are about to tighten the input visitor, at
>> which point user_creatable_add_type() as called with a QMP input
>> visitor via qmp_object_add() MUST follow the same paradigms as
>> everyone else, of pushing into the struct before grabbing its
>> keys.
>>

>>
>> $ ./x86_64-softmmu/qemu-system-x86_64 -nographic -nodefaults -qmp stdio -object secret,id=sec0,data=letmein,format=raw,foo=bar
>> qemu-system-x86_64: Property '.foo' not found
> 
> Let's update the error message now that the error message regression is
> fixed.  Can do on commit.

I see when you fixed the error message regression your commits omitted
data=letmein,format=raw; I included them here because they are marked
mandatory in qapi, to make sure that we didn't have to think about
whether excess arguments trump missing mandatory arguments in terms of
error reporting.  Up to you if you also feel comfortable shortening the
command line to match the one in your commit message that fixed the
regression.


>> +++ b/qom/object_interfaces.c
>> @@ -120,12 +120,20 @@ Object *user_creatable_add_type(const char *type, const char *id,
>>
>>      obj = object_new(type);
>>      if (qdict) {
>> +        visit_start_struct(v, NULL, NULL, 0, &local_err);
>> +        if (local_err) {
>> +            goto out;
>> +        }
>>          for (e = qdict_first(qdict); e; e = qdict_next(qdict, e)) {
>>              object_property_set(obj, v, e->key, &local_err);
>>              if (local_err) {
>> -                goto out;
>> +                break;
>>              }
>>          }
>> +        visit_end_struct(v, local_err ? NULL : &local_err);
>> +        if (local_err) {
>> +            goto out;
>> +        }
>>      }
>>
>>      object_property_add_child(object_get_objects_root(),
> 
> Hmm.  How should this behave if !qdict?

Pre-existing.

!qdict is not possible from qmp_object-add() (which throws
QERR_INVALID_PARAMETER_TYPE on a missing or wrong QObject type for 'props').

Also not possible from user_creatable_add().  We could turn it into
assert(qdict) and drop a level of indentation.

> 
> However, both callers seem to pass non-null qdict.  Should we sidestep
> the question by making that a precondition?

We came to the same conclusion, so yes :)

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [Qemu-devel] [PATCH v15 11/23] qmp-input: Refactor when list is advanced
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 11/23] qmp-input: Refactor when list is advanced Eric Blake
@ 2016-04-28 15:19   ` Markus Armbruster
  0 siblings, 0 replies; 49+ messages in thread
From: Markus Armbruster @ 2016-04-28 15:19 UTC (permalink / raw)
  To: Eric Blake; +Cc: qemu-devel, Michael Roth

Eric Blake <eblake@redhat.com> writes:

> In the QMP input visitor, visiting a list traverses two objects:
> the QAPI GenericList of the caller (which gets advanced in
> visit_next_list() regardless of this patch), and the QList input
> that we are converting to QAPI.  For consistency with QDict
> visits, we want to consume elements from the input QList during
> the visit_type_FOO() for the list element; that is, we want ALL
> the code for consuming an input to live in qmp_input_get_object(),
> rather than having it split according to whether we are visiting
> a dict or a list.  Making qmp_input_get_object() the common point
> of consumption will make it easier for a later patch to refactor
> visit_start_list() to cover the GenericList * head of a QAPI list,
> and in turn will get rid of the 'first' flag (which lived in
> qmp_input_next_list() pre-patch, and is hoisted to StackObject
> by this patch).
>
> This patch is therefore shifting the post-condition use of
> 'entry' from:
>
>         start_list next_list type_ELT ... next_list type_ELT end_list
>  entry  NULL       1st elt   1st elt      last elt  last elt gone
>
> where type_ELT() returns (entry ? entry : 1st elt) and next_list() steps
> entry
>
> to this usage:
>
>         start_list next_list type_ELT ... next_list type_ELT end_list
>  entry  1st elt    1nd elt   2nd elt      last elt  NULL     gone
>
> where type_ELT() steps entry and returns the old entry, and next_list()
> leaves entry alone.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
>
> ---
> v15: rebase, improve commit message, hoist root handling earlier
> so that this patch is more pinpointed
> v14: no change
> v13: no change
> v12: new patch
> ---
>  qapi/qmp-input-visitor.c | 30 ++++++++++++++++--------------
>  1 file changed, 16 insertions(+), 14 deletions(-)
>
> diff --git a/qapi/qmp-input-visitor.c b/qapi/qmp-input-visitor.c
> index 6409a83..eb77adc 100644
> --- a/qapi/qmp-input-visitor.c
> +++ b/qapi/qmp-input-visitor.c
> @@ -29,6 +29,7 @@ typedef struct StackObject
>
>      GHashTable *h;           /* If obj is dict: unvisited keys */
>      const QListEntry *entry; /* If obj is list: unvisited tail */
> +    bool first;              /* If obj is list: will next_list() visit head */

I like documenting bools with the yes/no question they answer.
Questions end with a question mark, though :)

Perhaps: /* If obj is list: next_list() not yet called? */

Since this will go away soon, feel free not to change anything here.

>  } StackObject;
>
>  struct QmpInputVisitor
> @@ -80,7 +81,11 @@ static QObject *qmp_input_get_object(QmpInputVisitor *qiv,
>      } else {
>          assert(qobject_type(qobj) == QTYPE_QLIST);
>          assert(!name);
> +        assert(!tos->first);
>          ret = qlist_entry_obj(tos->entry);
> +        if (consume) {
> +            tos->entry = qlist_next(tos->entry);
> +        }
>      }
>
>      return ret;
> @@ -104,13 +109,16 @@ static void qmp_input_push(QmpInputVisitor *qiv, QObject *obj, Error **errp)
>      }
>
>      tos->obj = obj;
> -    tos->entry = NULL;
> -    tos->h = NULL;
> +    assert(!tos->h);
> +    assert(!tos->entry);
>
>      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);
>          tos->h = h;
> +    } else if (qobject_type(obj) == QTYPE_QLIST) {
> +        tos->entry = qlist_first(qobject_to_qlist(obj));
> +        tos->first = true;
>      }
>
>      qiv->nb_stack++;
> @@ -119,10 +127,11 @@ static void qmp_input_push(QmpInputVisitor *qiv, QObject *obj, Error **errp)
>
>  static void qmp_input_pop(QmpInputVisitor *qiv, Error **errp)
>  {
> +    StackObject *tos = &qiv->stack[qiv->nb_stack - 1];
>      assert(qiv->nb_stack > 0);
>
>      if (qiv->strict) {
> -        GHashTable * const top_ht = qiv->stack[qiv->nb_stack - 1].h;
> +        GHashTable * const top_ht = tos->h;

Let's clean this up to *const while we're touching it.  Can do on
commit.

>          if (top_ht) {
>              GHashTableIter iter;
>              const char *key;
> @@ -133,6 +142,7 @@ static void qmp_input_pop(QmpInputVisitor *qiv, Error **errp)
>              }
>              g_hash_table_unref(top_ht);
>          }
> +        tos->h = NULL;
>      }
>
>      qiv->nb_stack--;
> @@ -192,23 +202,15 @@ static GenericList *qmp_input_next_list(Visitor *v, GenericList **list,
>      QmpInputVisitor *qiv = to_qiv(v);
>      GenericList *entry;
>      StackObject *so = &qiv->stack[qiv->nb_stack - 1];
> -    bool first;
>
> -    if (so->entry == NULL) {
> -        so->entry = qlist_first(qobject_to_qlist(so->obj));
> -        first = true;
> -    } else {
> -        so->entry = qlist_next(so->entry);
> -        first = false;
> -    }
> -
> -    if (so->entry == NULL) {
> +    if (!so->entry) {
>          return NULL;
>      }
>
>      entry = g_malloc0(size);
> -    if (first) {
> +    if (so->first) {
>          *list = entry;
> +        so->first = false;
>      } else {
>          (*list)->next = entry;
>      }

This stuff never fails to confuse me.  But now there's hope it's mostly
because the *old* code is confusing.

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [Qemu-devel] [PATCH v15 02/23] qapi: Guarantee NULL obj on input visitor callback error
  2016-04-28 13:00     ` Eric Blake
@ 2016-04-28 15:41       ` Eric Blake
  0 siblings, 0 replies; 49+ messages in thread
From: Eric Blake @ 2016-04-28 15:41 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: qemu-devel, Michael Roth

[-- Attachment #1: Type: text/plain, Size: 1056 bytes --]

On 04/28/2016 07:00 AM, Eric Blake wrote:
>> The commit message lists start_struct(), start_alternate(), next_list(),
>> type_str(), and type_any().  You cover them except for next_list().  Why
>> is that missing?
> 
> Because *obj can be NULL after next_list() if the list is empty.  But
> there may still be a weaker assertion worth having: if err, then *obj
> must be NULL; and if *obj, then err must not be set (weaker in that for
> all the other functions touched, exactly one of the two conditions can
> result, but here, !err and !*obj is valid as a third condition).

Actually, because visit_next_list() can't fail (we removed the errp
argument earlier).  When we finally move the list head allocation into
visit_start_list later in the series (22/23), then we should add the
(weaker) assert there.

> 
> Depending on what else you find later in the series, I may just post a
> fixup for this patch.

Still my plan.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [Qemu-devel] [PATCH v15 22/23] qapi: Simplify semantics of visit_next_list()
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 22/23] qapi: Simplify semantics of visit_next_list() Eric Blake
@ 2016-04-28 15:44   ` Eric Blake
  0 siblings, 0 replies; 49+ messages in thread
From: Eric Blake @ 2016-04-28 15:44 UTC (permalink / raw)
  To: qemu-devel
  Cc: Alexander Graf, David Gibson, open list:sPAPR, armbru, Michael Roth

[-- Attachment #1: Type: text/plain, Size: 1202 bytes --]

On 04/27/2016 06:01 PM, Eric Blake wrote:
> The semantics of the list visit are somewhat baroque, with the
> following pseudocode when FooList is used:
> 

> 
> We can greatly simplify things by hoisting the special case
> into the start() routine, and flipping the order in the loop
> to visit before advance:
> 
> start(head)
> for (tail = *head; tail; tail = next(tail)) {
>     visit(&tail->value)
> }
> 

> +++ b/qapi/qapi-visit-core.c
> @@ -48,15 +48,17 @@ void visit_end_struct(Visitor *v)
>      v->end_struct(v);
>  }
> 
> -void visit_start_list(Visitor *v, const char *name, Error **errp)
> +void visit_start_list(Visitor *v, const char *name, GenericList **list,
> +                      size_t size, Error **errp)
>  {
> -    v->start_list(v, name, errp);
> +    assert(!list || size >= sizeof(GenericList));
> +    v->start_list(v, name, list, size, errp);
>  }

As mentioned in 02/23, this would be a good place to assert that:

if (v->type == VISITOR_INPUT) {
    assert(!(err && *list));
}

of course, that means declaring a local 'err'.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 49+ messages in thread

* [Qemu-devel] [PATCH v15 02A/23] fixup! qapi: Guarantee NULL obj on input visitor callback error
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 02/23] qapi: Guarantee NULL obj on input visitor callback error Eric Blake
  2016-04-28 12:24   ` Markus Armbruster
@ 2016-04-28 16:02   ` Eric Blake
  1 sibling, 0 replies; 49+ messages in thread
From: Eric Blake @ 2016-04-28 16:02 UTC (permalink / raw)
  To: qemu-devel; +Cc: armbru, Michael Roth

[Updated commit message:]

Our existing input visitors were not very consistent on errors
in a function taking 'TYPE **obj' (that is, start_struct(),
start_alternate(), type_str(), and type_any(). next_list() is
similar, except that since commit 08f9541, it can't fail).
While all of them set '*obj' to allocated storage on success,
it was not obvious whether '*obj' was guaranteed safe on failure,
or whether it was left uninitialized.  But a future patch wants
to guarantee that visit_type_FOO() does not leak a partially-
constructed obj back to the caller; it is easier to implement
this if we can reliably state that input visitors assign '*obj'
regardless of success or failure, and that on failure *obj is
NULL.  Add assertions to enforce consistency in the final
setting of err vs. *obj.

The opts-visitor start_struct() doesn't set an error, but it
also was doing a weird check for 0 size; all callers pass in
non-zero size if obj is non-NULL.

The testsuite has at least one spot where we no longer need
to pre-initialize a variable prior to a visit; valgrind confirms
that the test is still fine with the cleanup.

A later patch will document the design constraint implemented
here.

Signed-off-by: Eric Blake <eblake@redhat.com>

---
v15: enhance commit message, hoist assertions from later in series
v14: no change
v13: no change
v12: new patch
---
 qapi/qapi-visit-core.c | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/qapi/qapi-visit-core.c b/qapi/qapi-visit-core.c
index 3a131ce..7ad5ff4 100644
--- a/qapi/qapi-visit-core.c
+++ b/qapi/qapi-visit-core.c
@@ -27,7 +27,7 @@ void visit_start_struct(Visitor *v, const char *name, void **obj,

     v->start_struct(v, name, obj, size, &err);
     if (obj && v->type == VISITOR_INPUT) {
-        assert(err || *obj);
+        assert(!err != !*obj);
     }
     error_propagate(errp, err);
 }
@@ -62,11 +62,11 @@ void visit_start_alternate(Visitor *v, const char *name,
     assert(obj && size >= sizeof(GenericAlternate));
     if (v->start_alternate) {
         v->start_alternate(v, name, obj, size, promote_int, &err);
-        if (v->type == VISITOR_INPUT) {
-            assert(err || *obj);
-        }
-        error_propagate(errp, err);
     }
+    if (v->type == VISITOR_INPUT) {
+        assert(!err != !*obj);
+    }
+    error_propagate(errp, err);
 }

 void visit_end_alternate(Visitor *v)
@@ -205,7 +205,7 @@ void visit_type_str(Visitor *v, const char *name, char **obj, Error **errp)
     assert(obj);
     v->type_str(v, name, obj, &err);
     if (v->type == VISITOR_INPUT) {
-        assert(err || *obj);
+        assert(!err != !*obj);
     }
     error_propagate(errp, err);
 }
@@ -223,7 +223,7 @@ void visit_type_any(Visitor *v, const char *name, QObject **obj, Error **errp)
     assert(obj);
     v->type_any(v, name, obj, &err);
     if (v->type == VISITOR_INPUT) {
-        assert(err || *obj);
+        assert(!err != !*obj);
     }
     error_propagate(errp, err);
 }
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 49+ messages in thread

* Re: [Qemu-devel] [PATCH v15 12/23] qapi: Document visitor interfaces, add assertions
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 12/23] qapi: Document visitor interfaces, add assertions Eric Blake
@ 2016-04-28 16:34   ` Markus Armbruster
  2016-04-28 19:02     ` Eric Blake
  0 siblings, 1 reply; 49+ messages in thread
From: Markus Armbruster @ 2016-04-28 16:34 UTC (permalink / raw)
  To: Eric Blake; +Cc: qemu-devel, Michael Roth

Eric Blake <eblake@redhat.com> writes:

> The visitor interface for mapping between QObject/QemuOpts/string
> and QAPI is scandalously under-documented, making changes to visitor
> core, individual visitors, and users of visitors difficult to
> coordinate.  Among other questions: when is it safe to pass NULL,
> vs. when a string must be provided; which visitors implement which
> callbacks; the difference between concrete and virtual visits.
>
> Correct this by retrofitting proper contracts, and document where some
> of the interface warts remain (for example, we may want to modify
> visit_end_* to require the same 'obj' as the visit_start counterpart,
> so the dealloc visitor can be simplified).  Later patches in this
> series will tackle some, but not all, of these warts.
>
> Add assertions to (partially) enforce the contract.  Some of these
> were only made possible by recent cleanup commits.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
>
> ---
> v15: wording tweaks based on review, rebase to some assertions going
> in earlier
> v14: rebase to master context
> v13: minor wording tweaks for consistency
> v12: major rework based on Markus' comments, drop R-b
> [no v10, v11]
> v9: no change
> v8: rebase to 'name' motion
> v7: retitle; more wording changes, add asserts to enforce the
> wording, place later in series to rebase on fixes that would
> otherwise trip the new assertions
> v6: mention that input visitors blindly assign over *obj; wording
> improvements
> ---
>  include/qapi/visitor.h               | 445 +++++++++++++++++++++++++++++++++--
>  include/qapi/visitor-impl.h          |  44 +++-
>  include/qapi/dealloc-visitor.h       |   5 +
>  include/qapi/opts-visitor.h          |   3 +
>  include/qapi/string-input-visitor.h  |   4 +
>  include/qapi/string-output-visitor.h |   4 +
>  qapi/qapi-visit-core.c               |  18 +-
>  7 files changed, 494 insertions(+), 29 deletions(-)
>
> diff --git a/include/qapi/visitor.h b/include/qapi/visitor.h
> index 690589f..0e028ba 100644
> --- a/include/qapi/visitor.h
> +++ b/include/qapi/visitor.h
> @@ -16,8 +16,194 @@
>
>  #include "qapi/qmp/qobject.h"
>
> +/*
> + * The QAPI schema defines both a set of C data types, and a QMP wire
> + * format.  QAPI objects can contain references to other QAPI objects,
> + * resulting in a directed acyclic graph.  QAPI also generates visitor
> + * functions to walk these graphs.  This file represents the interface
> + * for doing work at each node of a QAPI graph; it can also be used
> + * for a virtual walk, where there is no actual QAPI C struct.
> + *
> + * There are three kinds of visitor classes: input visitors (QMP,
> + * string, and QemuOpts) parse an external representation and build
> + * the corresponding QAPI graph, output visitors (QMP and string) take
> + * a completed QAPI graph and generate an external representation, and
> + * the dealloc visitor can take a QAPI graph (possibly partially
> + * constructed) and recursively free its resources.  While the dealloc
> + * and QMP input/output visitors are general, the string and QemuOpts
> + * visitors have some implementation limitations; see the
> + * documentation for each visitor for more details on what it
> + * supports.  Also, see visitor-impl.h for the callback contracts
> + * implemented by each visitor, and docs/qapi-code-gen.txt for more
> + * about the QAPI code generator.
> + *
> + * All QAPI types have a corresponding function with a signature
> + * roughly compatible with this:
> + *
> + * void visit_type_FOO(Visitor *v, const char *name, T obj, Error **errp);
> + *
> + * where T is FOO for scalar types, and FOO * otherwise.  The scalar
> + * visitors are declared here; the remaining visitors are generated in
> + * qapi-visit.h.
> + *
> + * The @name parameter of visit_type_FOO() describes the relation
> + * between this QAPI value and its parent container.  When visiting
> + * the root of a tree, @name is ignored; when visiting a member of an
> + * object, @name is the key associated with the value; and when
> + * visiting a member of a list, @name is NULL.
> + *
> + * FIXME: Clients must pass NULL for @name when visiting a member of a
> + * list, but this leads to poor error messages; it might be nicer to
> + * require a non-NULL name such as "key.0" for '{ "key": [ "value" ]
> + * }' if an error is encountered on "value" (or to have the visitor
> + * core auto-generate the nicer name).
> + *
> + * The visit_type_FOO() functions expect a non-null @obj argument;
> + * they allocate *@obj during input visits, leave it unchanged on
> + * output visits, and recursively free any resources during a dealloc
> + * visit.  Each function also takes the customary @errp argument (see
> + * qapi/error.h for details), for reporting any errors (such as if a
> + * member @name is not present, or is present but not the specified
> + * type).
> + *
> + * FIXME: At present, input visitors may allocate an incomplete *@obj
> + * even when visit_type_FOO() reports an error.  Using an output
> + * visitor with an incomplete object has undefined behavior; callers
> + * must call qapi_free_FOO() (which uses the dealloc visitor, and
> + * safely handles an incomplete object) to avoid a memory leak.

To make clear what exactly is broken, suggest to add "This is an awkward
interface."  Can do on commit.

> + *
> + * For the QAPI object types (structs, unions, and alternates), there
> + * is an additional generated function in qapi-visit.h compatible
> + * with:
> + *
> + * void visit_type_FOO_members(Visitor *v, FOO *obj, Error **errp);
> + *
> + * for visiting the members of a type without also allocating the QAPI
> + * struct.
> + *
> + * Additionally, in qapi-types.h, all QAPI pointer types (structs,
> + * unions, alternates, and lists) have a generated function compatible
> + * with:
> + *
> + * void qapi_free_FOO(FOO *obj);
> + *
> + * which behaves like free() (@obj can be NULL).  Because of these

Perhaps "which behaves like free() in that @obj may be null."  Can do on
commit.

> + * functions, the dealloc visitor is seldom used directly outside of
> + * generated code.  QAPI types can also inherit from a base class;
> + * when this happens, a function is generated for easily going from
> + * the derived type to the base type:
> + *
> + * BASE *qapi_CHILD_base(CHILD *obj);
> + *
> + * For a real QAPI struct, typical input usage involves:
> + *
> + * <example>
> + *  Foo *f;
> + *  Error *err = NULL;
> + *  Visitor *v;
> + *
> + *  v = ...obtain input visitor...
> + *  visit_type_Foo(v, NULL, &f, &err);
> + *  if (err) {
> + *      qapi_free_Foo(f);
> + *      ...handle error...
> + *  } else {
> + *      ...use f...
> + *  }
> + *  ...clean up v...
> + *  qapi_free_Foo(f);
> + * </example>
> + *
> + * For a list, it is:
> + * <example>
> + *  FooList *l;
> + *  Error *err = NULL;
> + *  Visitor *v;
> + *
> + *  v = ...obtain input visitor...
> + *  visit_type_FooList(v, NULL, &l, &err);
> + *  if (err) {
> + *      qapi_free_FooList(l);
> + *      ...handle error...
> + *  } else {
> + *      for ( ; l; l = l->next) {
> + *          ...use l->value...
> + *      }
> + *  }
> + *  ...clean up v...
> + *  qapi_free_FooList(l);
> + * </example>
> + *
> + * Similarly, typical output usage is:
> + *
> + * <example>
> + *  Foo *f = ...obtain populated object...
> + *  Error *err = NULL;
> + *  Visitor *v;
> + *
> + *  v = ...obtain output visitor...
> + *  visit_type_Foo(v, NULL, &f, &err);
> + *  if (err) {
> + *      ...handle error...
> + *  }
> + *  ...clean up v...
> + * </example>
> + *
> + * When visiting a real QAPI struct, this file provides several
> + * helpers that rely on in-tree information to control the walk:
> + * visit_optional() for the 'has_member' field associated with
> + * optional 'member' in the C struct; and visit_next_list() for
> + * advancing through a FooList linked list.  Only the generated
> + * visit_type functions need to use these helpers.
> + *
> + * It is also possible to use the visitors to do a virtual walk, where
> + * no actual QAPI struct is present.  In this situation, decisions
> + * about what needs to be walked are made by the calling code, and
> + * structured visits are split between pairs of start and end methods
> + * (where the end method must be called if the start function
> + * succeeded, even if an intermediate visit encounters an error).
> + * Thus, a virtual walk corresponding to '{ "list": [1, 2] }' looks
> + * like:
> + *
> + * <example>
> + *  Visitor *v;
> + *  Error *err = NULL;
> + *  int value;
> + *
> + *  v = ...obtain visitor...
> + *  visit_start_struct(v, NULL, NULL, 0, &err);
> + *  if (err) {
> + *      goto outobj;
> + *  }
> + *  visit_start_list(v, "list", &err);
> + *  if (err) {
> + *      goto outobj;
> + *  }
> + *  value = 1;
> + *  visit_type_int(v, NULL, &value, &err);
> + *  if (err) {
> + *      goto outlist;
> + *  }
> + *  value = 2;
> + *  visit_type_int(v, NULL, &value, &err);
> + *  if (err) {
> + *      goto outlist;
> + *  }
> + * outlist:
> + *  visit_end_list(v);
> + * outobj:
> + *  error_propagate(errp, err);
> + *  err = NULL;
> + *  visit_end_struct(v, &err);
> + *  error_propagate(errp, err);
> + *  ...clean up v...
> + * </example>
> + */
> +
> +/*** Useful types ***/
> +
>  /* This struct is layout-compatible with all other *List structs
> - * created by the qapi generator.  It is used as a typical
> + * created by the QAPI generator.  It is used as a typical
>   * singly-linked list. */
>  typedef struct GenericList {
>      struct GenericList *next;
> @@ -25,35 +211,126 @@ typedef struct GenericList {
>  } GenericList;
>
>  /* This struct is layout-compatible with all Alternate types
> - * created by the qapi generator. */
> + * created by the QAPI generator. */
>  typedef struct GenericAlternate {
>      QType type;
>      char padding[];
>  } GenericAlternate;
>
> +/*** Visiting structures ***/
> +
> +/*
> + * Start visiting an object @obj (struct or union).
> + *
> + * @name expresses the relationship of this object to its parent
> + * container; see the general description of @name above.
> + *
> + * @obj must be non-NULL for a real walk, in which case @size
> + * determines how much memory an input visitor will allocate into
> + * *@obj.  @obj may also be NULL for a virtual walk, in which case
> + * @size is ignored.
> + *
> + * @errp obeys typical error usage, and reports failures such as a
> + * member @name is not present, or present but not an object.  On
> + * error, input visitors set *@obj to NULL.
> + *
> + * After visit_start_struct() succeeds, the caller may visit its
> + * members one after the other, passing the member's name and address
> + * within the struct.  Finally, visit_end_struct() needs to be called
> + * to clean up, even if intermediate visits fail.  See the examples
> + * above.
> + *
> + * FIXME Should this be named visit_start_object, since it is also
> + * used for QAPI unions, and maps to JSON objects?
> + */
>  void visit_start_struct(Visitor *v, const char *name, void **obj,
>                          size_t size, Error **errp);
> +
> +/*
> + * Complete an object visit started earlier.
> + *
> + * @errp obeys typical error usage, and reports failures such as
> + * unparsed keys remaining in the input stream.
> + *
> + * Must be called after any successful use of visit_start_struct(),
> + * even if intermediate processing was skipped due to errors, to allow
> + * the backend to release any resources.  Destroying the visitor early
> + * behaves as if this was implicitly called.
> + */
>  void visit_end_struct(Visitor *v, Error **errp);
>
> +
> +/*** Visiting lists ***/
> +
> +/*
> + * Start visiting a list.
> + *
> + * @name expresses the relationship of this list to its parent
> + * container; see the general description of @name above.
> + *
> + * @errp obeys typical error usage, and reports failures such as a
> + * member @name is not present, or present but not a list.
> + *
> + * After visit_start_list() succeeds, the caller may visit its members
> + * one after the other.  A real visit uses visit_next_list() for
> + * traversing the linked list, while a virtual visit uses other means.
> + * For each list element, call the appropriate visit_type_FOO() with
> + * name set to NULL and obj set to the address of the value member of
> + * the list element.  Finally, visit_end_list() needs to be called to
> + * clean up, even if intermediate visits fail.  See the examples
> + * above.
> + */
>  void visit_start_list(Visitor *v, const char *name, Error **errp);
> +
> +/*
> + * Iterate over a GenericList during a non-virtual list visit.
> + *
> + * @size represents the size of a linked list node (at least
> + * sizeof(GenericList)).
> + *
> + * @list must not be NULL; on the first call, @list contains the
> + * address of the list head, and on subsequent calls *@list must be
> + * the previously returned value.  Should be called in a loop until a
> + * NULL return or error occurs; for each non-NULL return, the caller
> + * then calls the appropriate visit_type_*() for the element type
> + * of the list, with that function's name parameter set to NULL and
> + * obj set to the address of (*@list)->value.
> + *
> + * FIXME: This interface is awkward; it requires all callbacks to
> + * track whether it is the first or a subsequent call.  A better
> + * interface would pass the head of the list through
> + * visit_start_list().
> + */
>  GenericList *visit_next_list(Visitor *v, GenericList **list, size_t size);
> +
> +/*
> + * Complete a list visit started earlier.
> + *
> + * Must be called after any successful use of visit_start_list(), even
> + * if intermediate processing was skipped due to errors, to allow the
> + * backend to release any resources.  Destroying the visitor early
> + * behaves as if this was implicitly called.
> + */
>  void visit_end_list(Visitor *v);
>
> +
> +/*** Visiting alternates ***/
> +
>  /*
> - * Start the visit of an alternate @obj with the given @size.
> + * Start the visit of an alternate @obj.
>   *
> - * @name specifies the relationship to the containing struct (ignored
> - * for a top level visit, the name of the key if this alternate is
> - * part of an object, or NULL if this alternate is part of a list).
> + * @name expresses the relationship of this alternate to its parent
> + * container; see the general description of @name above.
>   *
> - * @obj must not be NULL. Input visitors will allocate @obj and
> - * determine the qtype of the next thing to be visited, stored in
> - * (*@obj)->type.  Other visitors will leave @obj unchanged.
> + * @obj must not be NULL. Input visitors use @size to determine how
> + * much memory to allocate into *@obj, then determine the qtype of the
> + * next thing to be visited, stored in (*@obj)->type.  Other visitors
> + * will leave @obj unchanged.
>   *
>   * If @promote_int, treat integers as QTYPE_FLOAT.
>   *
> - * If successful, this must be paired with visit_end_alternate(), even
> - * if visiting the contents of the alternate fails.
> + * If successful, this must be paired with visit_end_alternate() to
> + * clean up, even if visiting the contents of the alternate fails.
>   */
>  void visit_start_alternate(Visitor *v, const char *name,
>                             GenericAlternate **obj, size_t size,
> @@ -62,27 +339,48 @@ void visit_start_alternate(Visitor *v, const char *name,
>  /*
>   * Finish visiting an alternate type.
>   *
> - * Must be called after a successful visit_start_alternate(), even if
> - * an error occurred in the meantime.
> + * Must be called after any successful use of visit_start_alternate(),
> + * even if intermediate processing was skipped due to errors, to allow
> + * the backend to release any resources.  Destroying the visitor early
> + * behaves as if this was implicitly called.
>   *
>   * TODO: Should all the visit_end_* interfaces take obj parameter, so
>   * that dealloc visitor need not track what was passed in visit_start?
>   */
>  void visit_end_alternate(Visitor *v);
>
> -/**
> - * Check if an optional member @name of an object needs visiting.
> - * For input visitors, set *@present according to whether the
> - * corresponding visit_type_*() needs calling; for other visitors,
> - * leave *@present unchanged.  Return *@present for convenience.
> +
> +/*** Other helpers ***/
> +
> +/*
> + * Does optional struct member @name need visiting?
> + *
> + * @name must not be NULL.  This function is only useful between
> + * visit_start_struct() and visit_end_struct(), since only objects
> + * have optional keys.
> + *
> + * @present points to the address of the optional member's has_ flag.
> + *
> + * Input visitors set *@present according to input; other visitors
> + * leave it unchanged.  In either case, return *@present for
> + * convenience.
>   */
>  bool visit_optional(Visitor *v, const char *name, bool *present);
>
>  /*
>   * Visit an enum value.
>   *
> - * @strings expresses the mapping between C enum values and QAPI enum
> - * names; it should be the ENUM_lookup array from visit-types.h.
> + * @name expresses the relationship of this enum to its parent
> + * container; see the general description of @name above.
> + *
> + * @obj must be non-NULL.  Input visitors parse input and set *@obj to
> + * the enumeration value, leaving @obj unchanged on error; other
> + * visitors use *@obj but leave it unchanged.
> + *
> + * Currently, all input visitors parse text input, and all output
> + * visitors produce text output.  The mapping between enumeration
> + * values and strings is done by the visitor core, using @strings; it
> + * should be the ENUM_lookup array from visit-types.h.
>   *
>   * May call visit_type_str() under the hood, and the enum visit may
>   * fail even if the corresponding string visit succeeded; this implies
> @@ -91,28 +389,135 @@ bool visit_optional(Visitor *v, const char *name, bool *present);
>  void visit_type_enum(Visitor *v, const char *name, int *obj,
>                       const char *const strings[], Error **errp);
>
> +/*** Visiting built-in types ***/
> +
> +/*
> + * Visit an integer value.
> + *
> + * @name expresses the relationship of this integer to its parent
> + * container; see the general description of @name above.
> + *
> + * @obj must be non-NULL.  Input visitors set *@obj to the value;
> + * other visitors will leave *@obj unchanged.
> + */
>  void visit_type_int(Visitor *v, const char *name, int64_t *obj, Error **errp);
> +
> +/*
> + * Visit a uint8_t value.
> + * Like visit_type_int(), except clamps the value to uint8_t range.
> + */
>  void visit_type_uint8(Visitor *v, const char *name, uint8_t *obj,
>                        Error **errp);
> +
> +/*
> + * Visit a uint16_t value.
> + * Like visit_type_int(), except clamps the value to uint16_t range.
> + */
>  void visit_type_uint16(Visitor *v, const char *name, uint16_t *obj,
>                         Error **errp);
> +
> +/*
> + * Visit a uint32_t value.
> + * Like visit_type_int(), except clamps the value to uint32_t range.
> + */
>  void visit_type_uint32(Visitor *v, const char *name, uint32_t *obj,
>                         Error **errp);
> +
> +/*
> + * Visit a uint64_t value.
> + * Like visit_type_int(), except clamps the value to uint64_t range,
> + * that is, ensures it is unsigned.
> + */
>  void visit_type_uint64(Visitor *v, const char *name, uint64_t *obj,
>                         Error **errp);
> +
> +/*
> + * Visit an int8_t value.
> + * Like visit_type_int(), except clamps the value to int8_t range.
> + */
>  void visit_type_int8(Visitor *v, const char *name, int8_t *obj, Error **errp);
> +
> +/*
> + * Visit an int16_t value.
> + * Like visit_type_int(), except clamps the value to int16_t range.
> + */
>  void visit_type_int16(Visitor *v, const char *name, int16_t *obj,
>                        Error **errp);
> +
> +/*
> + * Visit an int32_t value.
> + * Like visit_type_int(), except clamps the value to int32_t range.
> + */
>  void visit_type_int32(Visitor *v, const char *name, int32_t *obj,
>                        Error **errp);
> +
> +/*
> + * Visit an int64_t value.
> + * Identical to visit_type_int().
> + */
>  void visit_type_int64(Visitor *v, const char *name, int64_t *obj,
>                        Error **errp);
> +
> +/*
> + * Visit a uint64_t value.
> + * Like visit_type_uint64(), except that some visitors may choose to
> + * recognize additional syntax, such as suffixes for easily scaling
> + * values.
> + */
>  void visit_type_size(Visitor *v, const char *name, uint64_t *obj,
>                       Error **errp);
> +
> +/*
> + * Visit a boolean value.
> + *
> + * @name expresses the relationship of this boolean to its parent
> + * container; see the general description of @name above.
> + *
> + * @obj must be non-NULL.  Input visitors set *@obj to the value;
> + * other visitors will leave *@obj unchanged.
> + */
>  void visit_type_bool(Visitor *v, const char *name, bool *obj, Error **errp);
> +
> +/*
> + * Visit a string value.
> + *
> + * @name expresses the relationship of this string to its parent
> + * container; see the general description of @name above.
> + *
> + * @obj must be non-NULL.  Input visitors set *@obj to the value
> + * (never NULL).  Other visitors leave *@obj unchanged, and commonly
> + * treat NULL like "".
> + *
> + * It is safe to cast away const when preparing a (const char *) value
> + * into @obj for use by an output visitor.
> + *
> + * FIXME: Callers that try to output NULL *obj should not be allowed.
> + */
>  void visit_type_str(Visitor *v, const char *name, char **obj, Error **errp);
> +
> +/*
> + * Visit a number (i.e. double) value.
> + *
> + * @name expresses the relationship of this number to its parent
> + * container; see the general description of @name above.
> + *
> + * @obj must be non-NULL.  Input visitors set *@obj to the value;
> + * other visitors will leave *@obj unchanged.  Visitors should
> + * document if infinity or NaN are not permitted.

Do we have such visitors?

> + */
>  void visit_type_number(Visitor *v, const char *name, double *obj,
>                         Error **errp);
> +
> +/*
> + * Visit an arbitrary value.
> + *
> + * @name expresses the relationship of this value to its parent
> + * container; see the general description of @name above.
> + *
> + * @obj must be non-NULL.  Input visitors set *@obj to the value;
> + * other visitors will leave *@obj unchanged.  *@obj must be non-NULL
> + * for output visitors.
> + */
>  void visit_type_any(Visitor *v, const char *name, QObject **obj, Error **errp);
>
>  #endif
[...]

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [Qemu-devel] [PATCH v15 14/23] qapi: Add visit_type_null() visitor
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 14/23] qapi: Add visit_type_null() visitor Eric Blake
@ 2016-04-28 16:40   ` Markus Armbruster
  0 siblings, 0 replies; 49+ messages in thread
From: Markus Armbruster @ 2016-04-28 16:40 UTC (permalink / raw)
  To: Eric Blake; +Cc: qemu-devel, Michael Roth

Eric Blake <eblake@redhat.com> writes:

> Right now, qmp-output-visitor happens to produce a QNull result
> if nothing is actually visited between the creation of the visitor
> and the request for the resulting QObject.  A stronger protocol
> would require that a QMP output visit MUST visit something.  But
> to still be able to produce a JSON 'null' output, we need a new
> visitor function that states our intentions.  Yes, we could say
> that such a visit must go through visit_type_any(), but that
> feels clunky.
>
> So this patch introduces the new visit_type_null() interface and
> its no-op interface in the dealloc visitor, and stubs in the
> qmp visitors (the next patch will finish the implementation).
> For the visitors that will not implement the callback, document
> the situation. The code in qapi-visit-core unconditionally
> dereferences the callback pointer, so that a segfault will inform
> a developer if they need to implement the callback for their
> choice of visitor.
>
> Note that JSON has a primitive null type, with the single value
> null; likewise with the QNull type for QObject; but for QAPI,
> we just have the 'null' value without a null type.  We may
> eventually want to add more support in QAPI for null (most likely,
> we'd use it via an alternate type that permits 'null' or an
> object); but we'll create that usage when we need it.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
>
> ---
> v15: improve commit message, add stubs here
> v14: no change
> v13: no change
> v12: rebase to earlier changes, drop R-b due to better documentation
> [no v10, v11]
> v9: no change
> v8: rebase to 'name' motion
> v7: new patch, based on discussion about spapr_drc.c
> ---
>  include/qapi/visitor.h               | 12 ++++++++++++
>  include/qapi/visitor-impl.h          |  3 +++
>  include/qapi/opts-visitor.h          |  3 ++-
>  include/qapi/string-input-visitor.h  |  2 +-
>  include/qapi/string-output-visitor.h |  2 +-
>  qapi/qapi-visit-core.c               |  5 +++++
>  qapi/qapi-dealloc-visitor.c          |  5 +++++
>  qapi/qmp-input-visitor.c             |  6 ++++++
>  qapi/qmp-output-visitor.c            |  7 +++++++
>  9 files changed, 42 insertions(+), 3 deletions(-)
>
> diff --git a/include/qapi/visitor.h b/include/qapi/visitor.h
> index 0e028ba..e79c09e 100644
> --- a/include/qapi/visitor.h
> +++ b/include/qapi/visitor.h
> @@ -520,4 +520,16 @@ void visit_type_number(Visitor *v, const char *name, double *obj,
>   */
>  void visit_type_any(Visitor *v, const char *name, QObject **obj, Error **errp);
>
> +/*
> + * Visit a JSON null value.
> + *
> + * @name expresses the relationship of the null value to its parent
> + * container; see the general description of @name above.
> + *
> + * Unlike all other visit_type_* functions, no obj parameter is
> + * needed; rather, this is a witness that an explicit null value is
> + * expected rather than any other type.
> + */
> +void visit_type_null(Visitor *v, const char *name, Error **errp);
> +
>  #endif
> diff --git a/include/qapi/visitor-impl.h b/include/qapi/visitor-impl.h
> index 796d180..88d27d5 100644
> --- a/include/qapi/visitor-impl.h
> +++ b/include/qapi/visitor-impl.h
> @@ -90,6 +90,9 @@ struct Visitor
>      void (*type_any)(Visitor *v, const char *name, QObject **obj,
>                       Error **errp);
>
> +    /* Must be set to visit explicit null values.  */
> +    void (*type_null)(Visitor *v, const char *name, Error **errp);
> +
>      /* Must be set for input visitors, optional otherwise.  The core
>       * takes care of the return type in the public interface. */
>      void (*optional)(Visitor *v, const char *name, bool *present);
> diff --git a/include/qapi/opts-visitor.h b/include/qapi/opts-visitor.h
> index 633aa71..fe37ed9 100644
> --- a/include/qapi/opts-visitor.h
> +++ b/include/qapi/opts-visitor.h
> @@ -31,7 +31,8 @@ typedef struct OptsVisitor OptsVisitor;
>   * - values above INT64_MAX or LLONG_MAX are rejected.
>   *
>   * The Opts input visitor does not implement support for visiting QAPI
> - * alternates, numbers (other than integers), or arbitrary QTypes.
> + * alternates, numbers (other than integers), null, or arbitrary
> + * QTypes.
>   */
>  OptsVisitor *opts_visitor_new(const QemuOpts *opts);
>  void opts_visitor_cleanup(OptsVisitor *nv);
> diff --git a/include/qapi/string-input-visitor.h b/include/qapi/string-input-visitor.h
> index fdf33ae..a8d8f67 100644
> --- a/include/qapi/string-input-visitor.h
> +++ b/include/qapi/string-input-visitor.h
> @@ -19,7 +19,7 @@ typedef struct StringInputVisitor StringInputVisitor;
>
>  /*
>   * The string input visitor does not implement support for visiting
> - * QAPI structs, alternates, or arbitrary QTypes.
> + * QAPI structs, alternates, null, or arbitrary QTypes.
>   */
>  StringInputVisitor *string_input_visitor_new(const char *str);
>  void string_input_visitor_cleanup(StringInputVisitor *v);
> diff --git a/include/qapi/string-output-visitor.h b/include/qapi/string-output-visitor.h
> index 3bb09af..89b7e4b 100644
> --- a/include/qapi/string-output-visitor.h
> +++ b/include/qapi/string-output-visitor.h
> @@ -19,7 +19,7 @@ typedef struct StringOutputVisitor StringOutputVisitor;
>
>  /*
>   * The string output visitor does not implement support for visiting
> - * QAPI structs, alternates, or arbitrary QTypes.
> + * QAPI structs, alternates, null, or arbitrary QTypes.
>   */
>  StringOutputVisitor *string_output_visitor_new(bool human);
>  void string_output_visitor_cleanup(StringOutputVisitor *v);
> diff --git a/qapi/qapi-visit-core.c b/qapi/qapi-visit-core.c
> index a8492cf..7eaa75c 100644
> --- a/qapi/qapi-visit-core.c
> +++ b/qapi/qapi-visit-core.c
> @@ -244,6 +244,11 @@ void visit_type_any(Visitor *v, const char *name, QObject **obj, Error **errp)
>      error_propagate(errp, err);
>  }
>
> +void visit_type_null(Visitor *v, const char *name, Error **errp)
> +{
> +    v->type_null(v, name, errp);
> +}
> +
>  static void output_type_enum(Visitor *v, const char *name, int *obj,
>                               const char *const strings[], Error **errp)
>  {
> diff --git a/qapi/qapi-dealloc-visitor.c b/qapi/qapi-dealloc-visitor.c
> index c19a459..413d525 100644
> --- a/qapi/qapi-dealloc-visitor.c
> +++ b/qapi/qapi-dealloc-visitor.c
> @@ -163,6 +163,10 @@ static void qapi_dealloc_type_anything(Visitor *v, const char *name,
>      }
>  }
>
> +static void qapi_dealloc_type_null(Visitor *v, const char *name, Error **errp)
> +{
> +}
> +
>  Visitor *qapi_dealloc_get_visitor(QapiDeallocVisitor *v)
>  {
>      return &v->visitor;
> @@ -193,6 +197,7 @@ QapiDeallocVisitor *qapi_dealloc_visitor_new(void)
>      v->visitor.type_str = qapi_dealloc_type_str;
>      v->visitor.type_number = qapi_dealloc_type_number;
>      v->visitor.type_any = qapi_dealloc_type_anything;
> +    v->visitor.type_null = qapi_dealloc_type_null;
>
>      QTAILQ_INIT(&v->stack);
>
> diff --git a/qapi/qmp-input-visitor.c b/qapi/qmp-input-visitor.c
> index eb77adc..739bbd5 100644
> --- a/qapi/qmp-input-visitor.c
> +++ b/qapi/qmp-input-visitor.c
> @@ -340,6 +340,11 @@ static void qmp_input_type_any(Visitor *v, const char *name, QObject **obj,
>      *obj = qobj;
>  }
>
> +static void qmp_input_type_null(Visitor *v, const char *name, Error **errp)
> +{
> +    error_setg(errp, "FIXME: Implement");
> +}
> +

I'd simply abort().  Can do on commit.

>  static void qmp_input_optional(Visitor *v, const char *name, bool *present)
>  {
>      QmpInputVisitor *qiv = to_qiv(v);
> @@ -383,6 +388,7 @@ QmpInputVisitor *qmp_input_visitor_new(QObject *obj, bool strict)
>      v->visitor.type_str = qmp_input_type_str;
>      v->visitor.type_number = qmp_input_type_number;
>      v->visitor.type_any = qmp_input_type_any;
> +    v->visitor.type_null = qmp_input_type_null;
>      v->visitor.optional = qmp_input_optional;
>      v->strict = strict;
>
> diff --git a/qapi/qmp-output-visitor.c b/qapi/qmp-output-visitor.c
> index 1f2a7ba..a67e3e8 100644
> --- a/qapi/qmp-output-visitor.c
> +++ b/qapi/qmp-output-visitor.c
> @@ -18,6 +18,7 @@
>  #include "qemu/queue.h"
>  #include "qemu-common.h"
>  #include "qapi/qmp/types.h"
> +#include "qapi/error.h"
>
>  typedef struct QStackEntry
>  {
> @@ -196,6 +197,11 @@ static void qmp_output_type_any(Visitor *v, const char *name, QObject **obj,
>      qmp_output_add_obj(qov, name, *obj);
>  }
>
> +static void qmp_output_type_null(Visitor *v, const char *name, Error **errp)
> +{
> +    error_setg(errp, "FIXME: Implement");
> +}
> +

Likewise.

>  /* Finish building, and return the root object. Will not be NULL. */
>  QObject *qmp_output_get_qobject(QmpOutputVisitor *qov)
>  {
> @@ -246,6 +252,7 @@ QmpOutputVisitor *qmp_output_visitor_new(void)
>      v->visitor.type_str = qmp_output_type_str;
>      v->visitor.type_number = qmp_output_type_number;
>      v->visitor.type_any = qmp_output_type_any;
> +    v->visitor.type_null = qmp_output_type_null;
>
>      QTAILQ_INIT(&v->stack);

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [Qemu-devel] [PATCH v15 15/23] qmp: Support explicit null during visits
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 15/23] qmp: Support explicit null during visits Eric Blake
@ 2016-04-28 16:50   ` Markus Armbruster
  2016-04-28 19:07     ` Eric Blake
  0 siblings, 1 reply; 49+ messages in thread
From: Markus Armbruster @ 2016-04-28 16:50 UTC (permalink / raw)
  To: Eric Blake; +Cc: qemu-devel, Michael Roth

Eric Blake <eblake@redhat.com> writes:

> Implement the new type_null() callback for the qmp input and
> output visitors. While we don't yet have a use for this in QAPI
> input (the generator will need some tweaks first), some
> potential usages have already been discussed on the list.
> Meanwhile, the output visitor could already output explicit null
> via type_any, but this gives us finer control.
>
> At any rate, it's easy to test that we can round-trip an explicit
> null through manual use of visit_type_null() wrapped by a virtual
> visit_start_struct() walk, even if we can't do the visit in a
> QAPI type.  Repurpose the test_visitor_out_empty test,
> particularly since a future patch will tighten semantics to
> forbid use of qmp_output_get_qobject() without at least one
> intervening visit_type_*.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
[...]
> diff --git a/tests/test-qmp-input-visitor.c b/tests/test-qmp-input-visitor.c
> index c039806..3b6ae92 100644
> --- a/tests/test-qmp-input-visitor.c
> +++ b/tests/test-qmp-input-visitor.c
> @@ -279,6 +279,30 @@ static void test_visitor_in_any(TestInputVisitorData *data,
>      qobject_decref(res);
>  }
>
> +static void test_visitor_in_null(TestInputVisitorData *data,
> +                                 const void *unused)
> +{
> +    Visitor *v;
> +    Error *err = NULL;
> +    char *tmp;
> +
> +    /*
> +     * FIXME: Since QAPI doesn't know the 'null' type yet, we can't
> +     * test visit_type_null() by reading into a QAPI struct then
> +     * checking that it was populated correctly.  The best we can do
> +     * for now is ensure that we consumed null from the input, proven
> +     * by the fact that we can't re-read the key.
> +     */
> +
> +    v = visitor_input_test_init(data, "{ 'a': null }");
> +    visit_start_struct(v, NULL, NULL, 0, &error_abort);
> +    visit_type_null(v, "a", &error_abort);
> +    visit_type_str(v, "a", &tmp, &err);
> +    g_assert(err);

I'd prefer error_free_or_abort().  Can do on commit.

> +    g_assert(!tmp);
> +    visit_end_struct(v, &error_abort);
> +}
> +
>  static void test_visitor_in_union_flat(TestInputVisitorData *data,
>                                         const void *unused)
>  {
[...]

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [Qemu-devel] [PATCH v15 21/23] qapi: Fix string input visitor handling of invalid list
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 21/23] qapi: Fix string input visitor handling of invalid list Eric Blake
@ 2016-04-28 17:18   ` Markus Armbruster
  0 siblings, 0 replies; 49+ messages in thread
From: Markus Armbruster @ 2016-04-28 17:18 UTC (permalink / raw)
  To: Eric Blake; +Cc: qemu-devel, Michael Roth

Eric Blake <eblake@redhat.com> writes:

> As shown in the previous commit, the string input visitor was
> treating bogus input as an empty list rather than an error.
> Fix parse_str() to set errp, then the callers to exit early if
> an error was reported.  Also, make sure the error message
> for visit_type_uint64() gracefully handles a NULL 'name' when
> called from the top level or a list context.
>
> Meanwhile, fix the testsuite to use the generated
> qapi_free_int16List() instead of rolling our own, and to
> validate the fixed behavior, while at the same time documenting
> one more change that we'd like to make in a later patch (a
> failed visit_start_list should guarantee a NULL pointer,
> regardless of what things were on input).
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
>
> ---
> v15: new patch
> ---
>  qapi/string-input-visitor.c       | 19 +++++++++++++------
>  tests/test-string-input-visitor.c | 12 +++++-------
>  2 files changed, 18 insertions(+), 13 deletions(-)
>
> diff --git a/qapi/string-input-visitor.c b/qapi/string-input-visitor.c
> index 797973a..ad150dc 100644
> --- a/qapi/string-input-visitor.c
> +++ b/qapi/string-input-visitor.c
> @@ -44,7 +44,7 @@ static void free_range(void *range, void *dummy)
>      g_free(range);
>  }
>
> -static void parse_str(StringInputVisitor *siv, Error **errp)
> +static int parse_str(StringInputVisitor *siv, const char *name, Error **errp)
>  {
>      char *str = (char *) siv->string;
>      long long start, end;
> @@ -52,7 +52,7 @@ static void parse_str(StringInputVisitor *siv, Error **errp)
>      char *endptr;
>
>      if (siv->ranges) {
> -        return;
> +        return 0;
>      }
>
>      do {
> @@ -117,11 +117,14 @@ static void parse_str(StringInputVisitor *siv, Error **errp)
>          }
>      } while (str);
>
> -    return;
> +    return 0;
>  error:
>      g_list_foreach(siv->ranges, free_range, NULL);
>      g_list_free(siv->ranges);
>      siv->ranges = NULL;
> +    error_setg(errp, QERR_INVALID_PARAMETER_VALUE, name ? name : "null",
> +               "an int64 value or range");
> +    return -1;
>  }

You make the function return success/failure so that you can do

    if (parse_str(..., errp) < 0) {
        ...
    }

instead of the cumbersome and less readable

    Error *err = NULL;

    parse_str(..., &err);
    if (err) {
        error_propagate(errp, err);
        ...
    }

For what it's worth, GLib recommends doing this practice with GError,
except with true / false instead of 0 / -1 (thanks to Marc-André for
pointing this out to me).  I got a private branch where I'm
investigating adopting this convention across QEMU.

>
>  static void
> @@ -129,7 +132,9 @@ start_list(Visitor *v, const char *name, Error **errp)
>  {
>      StringInputVisitor *siv = to_siv(v);
>
> -    parse_str(siv, errp);
> +    if (parse_str(siv, name, errp) < 0) {
> +        return;
> +    }
>
>      siv->cur_range = g_list_first(siv->ranges);
>      if (siv->cur_range) {

Is parse_str()'s error message "Parameter '%s' expects an int64 value or
range" appropriate here?

> @@ -195,7 +200,9 @@ static void parse_type_int64(Visitor *v, const char *name, int64_t *obj,
>          return;
>      }
>
> -    parse_str(siv, errp);
> +    if (parse_str(siv, name, errp) < 0) {
> +        return;
> +    }
>
>      if (!siv->ranges) {
>          goto error;

parse_str()'s error message is appropriate here.  It duplicates the one
visible below, though.  Makes me wonder why parse_str() returns an Error
object.  I guess it'll makes sense once we improve it to return more
specific errors.  Anyway, let's not worry about it now.

> @@ -222,7 +229,7 @@ static void parse_type_int64(Visitor *v, const char *name, int64_t *obj,
>      return;
>
>  error:
> -    error_setg(errp, QERR_INVALID_PARAMETER_VALUE, name,
> +    error_setg(errp, QERR_INVALID_PARAMETER_VALUE, name ? name : "null",
>                 "an int64 value or range");

Separate bug, separate patch?

>  }
>
> diff --git a/tests/test-string-input-visitor.c b/tests/test-string-input-visitor.c
> index 8114908..f99824d 100644
> --- a/tests/test-string-input-visitor.c
> +++ b/tests/test-string-input-visitor.c
> @@ -92,19 +92,17 @@ static void test_visitor_in_intList(TestInputVisitorData *data,
>      }
>      g_assert(!tmp);
>
> -    tmp = res;
> -    while (tmp) {
> -        res = res->next;
> -        g_free(tmp);
> -        tmp = res;
> -    }
> +    qapi_free_int16List(res);
>
>      visitor_input_teardown(data, unused);
>
>      v = visitor_input_test_init(data, "not an int list");
>
> +    /* FIXME: res should be NULL on failure, regardless of starting value */
> +    res = NULL;
>      visit_type_int16List(v, NULL, &res, &err);
> -    /* FIXME fix the visitor, then error_free_or_abort(&err) here */
> +    error_free_or_abort(&err);
> +    g_assert(!res);
>  }
>
>  static void test_visitor_in_bool(TestInputVisitorData *data,

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [Qemu-devel] [PATCH v15 23/23] qapi: Change visit_type_FOO() to no longer return partial objects
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 23/23] qapi: Change visit_type_FOO() to no longer return partial objects Eric Blake
@ 2016-04-28 17:42   ` Markus Armbruster
  0 siblings, 0 replies; 49+ messages in thread
From: Markus Armbruster @ 2016-04-28 17:42 UTC (permalink / raw)
  To: Eric Blake; +Cc: qemu-devel, Michael Roth

Eric Blake <eblake@redhat.com> writes:

> Returning a partial object on error is an invitation for a careless
> caller to leak memory.  We already fixed things in an earlier
> patch to guarantee NULL if visit_start fails ("qapi: Guarantee
> NULL obj on input visitor callback error"), but that does not
> help the case where visit_start succeeds but some other failure
> happens before visit_end, such that we leak a partially constructed
> object outside visit_type_FOO(). As no one outside the testsuite
> was actually relying on these semantics, it is cleaner to just
> document and guarantee that ALL pointer-based visit_type_FOO()
> functions always leave a safe value in *obj during an input visitor
> (either the new object on success, or NULL if an error is
> encountered), so callers can now unconditionally use
> qapi_free_FOO() to clean up regardless of whether an error occurred.
>
> The decision is done by adding visit_is_input(), then updating the
> generated code to check if additional cleanup is needed based on
> the type of visitor in use.
>
> Note that we still leave *obj unchanged after a scalar-based
> visit_type_FOO(); I did not feel like auditing all uses of
> visit_type_Enum() to see if the callers would tolerate a specific
> sentinel value (not to mention having to decide whether it would
> be better to use 0 or ENUM__MAX as that sentinel).
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
[...]
> diff --git a/qapi/qapi-visit-core.c b/qapi/qapi-visit-core.c
> index e6d57f3..b30a22e 100644
> --- a/qapi/qapi-visit-core.c
> +++ b/qapi/qapi-visit-core.c
> @@ -48,6 +48,7 @@ void visit_end_struct(Visitor *v)
>      v->end_struct(v);
>  }
>
> +
>  void visit_start_list(Visitor *v, const char *name, GenericList **list,
>                        size_t size, Error **errp)
>  {

Spurious hunk.  Can drop on commit.

[...]

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [Qemu-devel] [PATCH v15 08/23] monitor: Let generated code validate arguments
  2016-04-28 14:39     ` Marc-André Lureau
@ 2016-04-28 18:00       ` Markus Armbruster
  2016-04-28 18:58         ` Eric Blake
  0 siblings, 1 reply; 49+ messages in thread
From: Markus Armbruster @ 2016-04-28 18:00 UTC (permalink / raw)
  To: Marc-André Lureau; +Cc: Michael Roth, QEMU, Luiz Capitulino

Marc-André Lureau <marcandre.lureau@gmail.com> writes:

> On Thu, Apr 28, 2016 at 4:09 PM, Markus Armbruster <armbru@redhat.com> wrote:
>>
>> * device_add, qmp_capabilities
>>
>>   Not QAPIfied, need everything.
>
> Some related commits from https://github.com/elmarco/qemu/commits/qapi
>
>
> https://github.com/elmarco/qemu/commit/e6828a0a926e20d3ee91ea956152b610ca30d2b8
>
> https://github.com/elmarco/qemu/commit/ba871db1c8d0edd0cbe7be716952f72c9df519a2
>
>>
>> * netdev_add, query-qmp-schema
>>
>>   Need .args_type because of 'gen': false.
>>
>> * client_migrate_info, dump-guest-memory, query-dump, getfd, closefd,
>>   add-fd, remove-fd, query-fdsets, migrate-set-capabilities
>>
>>   Define .params and/or .help.  Does this make any sense?
>>
>> A comment explaining which members need to be set would be nice.
>>
>> Have you checked Marc-André's work for overlap?  Cc'ing him.
>
> yeah, it's conflicting with my work. Unfortunately, since I have been
> asked to wait since a long time, I am no longer on top of things, nor
> what I need to wait for. I have the feeling we really want
> qmp_dispatch() in monitor.c:
> https://github.com/elmarco/qemu/commit/af4158993088ee2bbf9087c440990880d7943eb1.
>
> Eric, I have been waiting for your series to settle for getting back
> to qapi, but it keeps growing ;) I hope you are familiar with the work
> I did in the branch so we don't duplicate efforts.

I'd very much prefer to give precedence to Marc-André's solution.

I think this patch is in this series just because the previous patch
notes a redundancy, and promises its cleanup.  What about this:

* We shelve this patch for now, and s/a later patch/a future patch/ in
  the previous patch's commit message.

* We continue to flush Eric's QAPI queue.  If we come to a point where
  we really want this patch, we insert it, and apologize to Marc-André.
  But hopefully we come to a point where Marc-André's work fits first!
  We'll refer to this patch when reviewing his solution, to make sure
  we're not losing anything.

Okay?

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [Qemu-devel] [PATCH v15 00/23] qapi visitor cleanups (post-introspection cleanups subset E)
  2016-04-28  0:01 [Qemu-devel] [PATCH v15 00/23] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
                   ` (22 preceding siblings ...)
  2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 23/23] qapi: Change visit_type_FOO() to no longer return partial objects Eric Blake
@ 2016-04-28 18:03 ` Markus Armbruster
  23 siblings, 0 replies; 49+ messages in thread
From: Markus Armbruster @ 2016-04-28 18:03 UTC (permalink / raw)
  To: Eric Blake; +Cc: qemu-devel

Eric Blake <eblake@redhat.com> writes:

> 2.7 material; hopefully this iteration is close enough for
> Markus to stick it in his qapi-next staging branch, so we
> can move on to my other pending series.
>
> Based on master, with no prerequisite patches.
>
> Also available as a tag at this location:
> git fetch git://repo.or.cz/qemu/ericb.git qapi-cleanupv15e
>
> and will soon be part of my branch with the rest of the v5 series, at:
> http://repo.or.cz/qemu/ericb.git/shortlog/refs/heads/qapi
>
> v14 was:
> https://lists.gnu.org/archive/html/qemu-devel/2016-04/msg01486.html
>
> Since then, I've rearranged several patches (including moving hunks
> between patches), added a couple of new ones, and in general
> addressed a lot of Markus' findings.  The comparison to the previous
> posting looks rather big, but a lot of it is due to comment changes
> or rebase artifacts from shuffling things around, where a lot of
> the end results are still the same.

More churn than I hoped, but I think we're converging anyway.  Eric,
would you like me to investigate whether I can touch it up in my tree?
I guess a full respin would be easier and cleaner.  Should be the last
one.

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [Qemu-devel] [PATCH v15 08/23] monitor: Let generated code validate arguments
  2016-04-28 18:00       ` Markus Armbruster
@ 2016-04-28 18:58         ` Eric Blake
  0 siblings, 0 replies; 49+ messages in thread
From: Eric Blake @ 2016-04-28 18:58 UTC (permalink / raw)
  To: Markus Armbruster, Marc-André Lureau
  Cc: Luiz Capitulino, Michael Roth, QEMU

[-- Attachment #1: Type: text/plain, Size: 1476 bytes --]

On 04/28/2016 12:00 PM, Markus Armbruster wrote:
>>> Have you checked Marc-André's work for overlap?  Cc'ing him.
>>
>> yeah, it's conflicting with my work. Unfortunately, since I have been
>> asked to wait since a long time, I am no longer on top of things, nor
>> what I need to wait for. I have the feeling we really want
>> qmp_dispatch() in monitor.c:
>> https://github.com/elmarco/qemu/commit/af4158993088ee2bbf9087c440990880d7943eb1.
>>
>> Eric, I have been waiting for your series to settle for getting back
>> to qapi, but it keeps growing ;) I hope you are familiar with the work
>> I did in the branch so we don't duplicate efforts.
> 
> I'd very much prefer to give precedence to Marc-André's solution.
> 
> I think this patch is in this series just because the previous patch
> notes a redundancy, and promises its cleanup.  What about this:
> 
> * We shelve this patch for now, and s/a later patch/a future patch/ in
>   the previous patch's commit message.
> 
> * We continue to flush Eric's QAPI queue.  If we come to a point where
>   we really want this patch, we insert it, and apologize to Marc-André.
>   But hopefully we come to a point where Marc-André's work fits first!
>   We'll refer to this patch when reviewing his solution, to make sure
>   we're not losing anything.
> 
> Okay?

Sounds good to me :)

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [Qemu-devel] [PATCH v15 12/23] qapi: Document visitor interfaces, add assertions
  2016-04-28 16:34   ` Markus Armbruster
@ 2016-04-28 19:02     ` Eric Blake
  0 siblings, 0 replies; 49+ messages in thread
From: Eric Blake @ 2016-04-28 19:02 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: qemu-devel, Michael Roth

[-- Attachment #1: Type: text/plain, Size: 869 bytes --]

On 04/28/2016 10:34 AM, Markus Armbruster wrote:
>> +/*
>> + * Visit a number (i.e. double) value.
>> + *
>> + * @name expresses the relationship of this number to its parent
>> + * container; see the general description of @name above.
>> + *
>> + * @obj must be non-NULL.  Input visitors set *@obj to the value;
>> + * other visitors will leave *@obj unchanged.  Visitors should
>> + * document if infinity or NaN are not permitted.
> 
> Do we have such visitors?

Not at the moment, although qmp-output-visitor produces invalid JSON as
a result.  I've got a pending series that adds a json-output-visitor,
and that one explicitly produces an error on infinity or NaN.

https://lists.gnu.org/archive/html/qemu-devel/2015-12/msg03937.html

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [Qemu-devel] [PATCH v15 15/23] qmp: Support explicit null during visits
  2016-04-28 16:50   ` Markus Armbruster
@ 2016-04-28 19:07     ` Eric Blake
  0 siblings, 0 replies; 49+ messages in thread
From: Eric Blake @ 2016-04-28 19:07 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: qemu-devel, Michael Roth

[-- Attachment #1: Type: text/plain, Size: 2966 bytes --]

On 04/28/2016 10:50 AM, Markus Armbruster wrote:
> Eric Blake <eblake@redhat.com> writes:
> 
>> Implement the new type_null() callback for the qmp input and
>> output visitors. While we don't yet have a use for this in QAPI
>> input (the generator will need some tweaks first), some
>> potential usages have already been discussed on the list.
>> Meanwhile, the output visitor could already output explicit null
>> via type_any, but this gives us finer control.
>>
>> At any rate, it's easy to test that we can round-trip an explicit
>> null through manual use of visit_type_null() wrapped by a virtual
>> visit_start_struct() walk, even if we can't do the visit in a
>> QAPI type.  Repurpose the test_visitor_out_empty test,
>> particularly since a future patch will tighten semantics to
>> forbid use of qmp_output_get_qobject() without at least one
>> intervening visit_type_*.
>>
>> Signed-off-by: Eric Blake <eblake@redhat.com>
> [...]
>> diff --git a/tests/test-qmp-input-visitor.c b/tests/test-qmp-input-visitor.c
>> index c039806..3b6ae92 100644
>> --- a/tests/test-qmp-input-visitor.c
>> +++ b/tests/test-qmp-input-visitor.c
>> @@ -279,6 +279,30 @@ static void test_visitor_in_any(TestInputVisitorData *data,
>>      qobject_decref(res);
>>  }
>>
>> +static void test_visitor_in_null(TestInputVisitorData *data,
>> +                                 const void *unused)
>> +{
>> +    Visitor *v;
>> +    Error *err = NULL;
>> +    char *tmp;
>> +
>> +    /*
>> +     * FIXME: Since QAPI doesn't know the 'null' type yet, we can't
>> +     * test visit_type_null() by reading into a QAPI struct then
>> +     * checking that it was populated correctly.  The best we can do
>> +     * for now is ensure that we consumed null from the input, proven
>> +     * by the fact that we can't re-read the key.
>> +     */
>> +
>> +    v = visitor_input_test_init(data, "{ 'a': null }");
>> +    visit_start_struct(v, NULL, NULL, 0, &error_abort);
>> +    visit_type_null(v, "a", &error_abort);
>> +    visit_type_str(v, "a", &tmp, &err);
>> +    g_assert(err);
> 
> I'd prefer error_free_or_abort().  Can do on commit.

Not just prefer, but needed to avoid a memleak.  Thanks for the good catch.

Also, I realized I don't do any negative test.  Maybe this test could be
expanded as:

v = visitor_input_test_init(data, "{ 'a': null, 'b': '' }");
visit_start_struct(v, NULL, NULL, 0, &error_abort);
visit_type_null(v, "a", &error_abort);
visit_type_null(v, "a", &tmp, &err);
error_free_or_abort();
visit_type_null(v, "b", &err);
error_free_or_abort();

> 
>> +    g_assert(!tmp);
>> +    visit_end_struct(v, &error_abort);
>> +}
>> +
>>  static void test_visitor_in_union_flat(TestInputVisitorData *data,
>>                                         const void *unused)
>>  {
> [...]
> 

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 49+ messages in thread

end of thread, other threads:[~2016-04-28 19:07 UTC | newest]

Thread overview: 49+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-04-28  0:01 [Qemu-devel] [PATCH v15 00/23] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 01/23] qapi-visit: Add visitor.type classification Eric Blake
2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 02/23] qapi: Guarantee NULL obj on input visitor callback error Eric Blake
2016-04-28 12:24   ` Markus Armbruster
2016-04-28 13:00     ` Eric Blake
2016-04-28 15:41       ` Eric Blake
2016-04-28 16:02   ` [Qemu-devel] [PATCH v15 02A/23] fixup! " Eric Blake
2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 03/23] qmp: Drop dead command->type Eric Blake
2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 04/23] qmp-input: Clean up stack handling Eric Blake
2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 05/23] qapi: Use strict QMP input visitor in more places Eric Blake
2016-04-28 13:06   ` Markus Armbruster
2016-04-28 14:28     ` Eric Blake
2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 06/23] qmp-input: Don't consume input when checking has_member Eric Blake
2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 07/23] qapi-commands: Wrap argument visit in visit_start_struct Eric Blake
2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 08/23] monitor: Let generated code validate arguments Eric Blake
2016-04-28 14:09   ` Markus Armbruster
2016-04-28 14:39     ` Marc-André Lureau
2016-04-28 18:00       ` Markus Armbruster
2016-04-28 18:58         ` Eric Blake
2016-04-28 14:47     ` Eric Blake
2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 09/23] qom: Wrap prop visit in visit_start_struct Eric Blake
2016-04-28 14:46   ` Markus Armbruster
2016-04-28 15:14     ` Eric Blake
2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 10/23] qmp-input: Require struct push to visit members of top dict Eric Blake
2016-04-28 15:00   ` Markus Armbruster
2016-04-28 15:04     ` Eric Blake
2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 11/23] qmp-input: Refactor when list is advanced Eric Blake
2016-04-28 15:19   ` Markus Armbruster
2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 12/23] qapi: Document visitor interfaces, add assertions Eric Blake
2016-04-28 16:34   ` Markus Armbruster
2016-04-28 19:02     ` Eric Blake
2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 13/23] tests: Add check-qnull Eric Blake
2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 14/23] qapi: Add visit_type_null() visitor Eric Blake
2016-04-28 16:40   ` Markus Armbruster
2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 15/23] qmp: Support explicit null during visits Eric Blake
2016-04-28 16:50   ` Markus Armbruster
2016-04-28 19:07     ` Eric Blake
2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 16/23] spapr_drc: Expose 'null' in qom-get when there is no fdt Eric Blake
2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 17/23] qmp: Add qmp_output_visitor_reset() Eric Blake
2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 18/23] qmp: Tighten output visitor rules Eric Blake
2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 19/23] qapi: Split visit_end_struct() into pieces Eric Blake
2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 20/23] tests/string-input-visitor: Add negative integer tests Eric Blake
2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 21/23] qapi: Fix string input visitor handling of invalid list Eric Blake
2016-04-28 17:18   ` Markus Armbruster
2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 22/23] qapi: Simplify semantics of visit_next_list() Eric Blake
2016-04-28 15:44   ` Eric Blake
2016-04-28  0:01 ` [Qemu-devel] [PATCH v15 23/23] qapi: Change visit_type_FOO() to no longer return partial objects Eric Blake
2016-04-28 17:42   ` Markus Armbruster
2016-04-28 18:03 ` [Qemu-devel] [PATCH v15 00/23] qapi visitor cleanups (post-introspection cleanups subset E) Markus Armbruster

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.