All of lore.kernel.org
 help / color / mirror / Atom feed
* [Qemu-devel] [PATCH v14 00/19] qapi visitor cleanups (post-introspection cleanups subset E)
@ 2016-04-08 16:12 Eric Blake
  2016-04-08 16:12 ` [Qemu-devel] [PATCH v14 01/19] qapi: Consolidate object visitors Eric Blake
                   ` (19 more replies)
  0 siblings, 20 replies; 61+ messages in thread
From: Eric Blake @ 2016-04-08 16:12 UTC (permalink / raw)
  To: qemu-devel; +Cc: armbru

This is now 2.7 material (it is too invasive for hard freeze).

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-cleanupv14e

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

v13 was:
https://lists.gnu.org/archive/html/qemu-devel/2016-03/msg01224.html

Only minor rebasing since then, plus one patch deferred from my
previous series:

001/19:[down] 'qapi: Consolidate object visitors'
002/19:[----] [--] 'qapi-visit: Add visitor.type classification'
003/19:[----] [--] 'qapi: Guarantee NULL obj on input visitor callback error'
004/19:[----] [--] 'qmp: Drop dead command->type'
005/19:[----] [--] 'qmp-input: Clean up stack handling'
006/19:[----] [--] 'qmp-input: Don't consume input when checking has_member'
007/19:[----] [--] 'qmp-input: Refactor when list is advanced'
008/19:[----] [-C] 'qapi: Document visitor interfaces, add assertions'
009/19:[----] [--] 'tests: Add check-qnull'
010/19:[----] [--] 'qapi: Add visit_type_null() visitor'
011/19:[0001] [FC] 'qmp: Support explicit null during visits'
012/19:[----] [--] 'spapr_drc: Expose 'null' in qom-get when there is no fdt'
013/19:[----] [--] 'qmp: Tighten output visitor rules'
014/19:[0014] [FC] 'qapi: Split visit_end_struct() into pieces'
015/19:[----] [-C] 'qapi-commands: Wrap argument visit in visit_start_struct'
016/19:[----] [--] 'qom: Wrap prop visit in visit_start_struct'
017/19:[----] [--] 'qmp-input: Require struct push to visit members of top dict'
018/19:[----] [--] 'qapi: Simplify semantics of visit_next_list()'
019/19:[----] [--] 'qapi: Change visit_type_FOO() to no longer return partial objects'

Eric Blake (19):
  qapi: Consolidate object visitors
  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
  qmp-input: Don't consume input when checking has_member
  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: Tighten output visitor rules
  qapi: Split visit_end_struct() into pieces
  qapi-commands: Wrap argument visit in visit_start_struct
  qom: Wrap prop visit in visit_start_struct
  qmp-input: Require struct push to visit members of top dict
  qapi: Simplify semantics of visit_next_list()
  qapi: Change visit_type_FOO() to no longer return partial objects

 include/qapi/visitor.h               | 485 +++++++++++++++++++++++++++++++++--
 include/qapi/visitor-impl.h          |  80 ++++--
 scripts/qapi.py                      |  10 +-
 scripts/qapi-commands.py             |  10 +
 scripts/qapi-event.py                |   5 +-
 scripts/qapi-introspect.py           |  10 +-
 scripts/qapi-types.py                |  13 +-
 scripts/qapi-visit.py                |  68 ++---
 include/qapi/dealloc-visitor.h       |   4 +
 include/qapi/opts-visitor.h          |   4 +
 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               | 112 ++++++--
 block/crypto.c                       |  14 +-
 hw/ppc/spapr_drc.c                   |  11 +-
 hw/virtio/virtio-balloon.c           |  15 +-
 qapi/opts-visitor.c                  |  65 ++---
 qapi/qapi-dealloc-visitor.c          |  43 +---
 qapi/qmp-dispatch.c                  |  18 +-
 qapi/qmp-input-visitor.c             | 168 +++++++-----
 qapi/qmp-output-visitor.c            |  72 +++---
 qapi/qmp-registry.c                  |   1 -
 qapi/string-input-visitor.c          |  39 +--
 qapi/string-output-visitor.c         |  43 ++--
 qom/object.c                         |   5 +-
 qom/object_interfaces.c              |  29 ++-
 tests/check-qnull.c                  |  69 +++++
 tests/test-qmp-commands.c            |  13 +-
 tests/test-qmp-input-strict.c        |  19 +-
 tests/test-qmp-input-visitor.c       |  27 +-
 tests/test-qmp-output-visitor.c      |  17 +-
 docs/qapi-code-gen.txt               |  33 ++-
 tests/.gitignore                     |   1 +
 tests/Makefile                       |   6 +-
 tests/qapi-schema/test-qapi.py       |  10 +-
 37 files changed, 1110 insertions(+), 426 deletions(-)
 create mode 100644 tests/check-qnull.c

-- 
2.5.5

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

* [Qemu-devel] [PATCH v14 01/19] qapi: Consolidate object visitors
  2016-04-08 16:12 [Qemu-devel] [PATCH v14 00/19] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
@ 2016-04-08 16:12 ` Eric Blake
  2016-04-13 12:48   ` Markus Armbruster
  2016-04-08 16:12 ` [Qemu-devel] [PATCH v14 02/19] qapi-visit: Add visitor.type classification Eric Blake
                   ` (18 subsequent siblings)
  19 siblings, 1 reply; 61+ messages in thread
From: Eric Blake @ 2016-04-08 16:12 UTC (permalink / raw)
  To: qemu-devel; +Cc: armbru, Michael Roth

Rather than having two separate visitor callbacks with items
already broken out, pass the actual QAPISchemaObjectType object
to the visitor.  This lets the visitor access things like
type.is_implicit() without needing another parameter, resolving
a TODO from previous patches.

For convenience and consistency, the 'name' and 'info' parameters
are still provided, even though they are now redundant with
'typ.name' and 'typ.info'.

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

---
v14: fix testsuite failures
[posted earlier as part of "easier unboxed visits/qapi implicit types"]
v6: new patch
---
 scripts/qapi.py                | 10 ++--------
 scripts/qapi-introspect.py     | 10 +++++-----
 scripts/qapi-types.py          | 13 ++++++-------
 scripts/qapi-visit.py          | 12 ++++++------
 tests/qapi-schema/test-qapi.py | 10 +++++-----
 5 files changed, 24 insertions(+), 31 deletions(-)

diff --git a/scripts/qapi.py b/scripts/qapi.py
index b13ae47..4dde43a 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -808,10 +808,7 @@ class QAPISchemaVisitor(object):
     def visit_array_type(self, name, info, element_type):
         pass

-    def visit_object_type(self, name, info, base, members, variants):
-        pass
-
-    def visit_object_type_flat(self, name, info, members, variants):
+    def visit_object_type(self, name, info, typ):
         pass

     def visit_alternate_type(self, name, info, variants):
@@ -1005,10 +1002,7 @@ class QAPISchemaObjectType(QAPISchemaType):
         return 'object'

     def visit(self, visitor):
-        visitor.visit_object_type(self.name, self.info,
-                                  self.base, self.local_members, self.variants)
-        visitor.visit_object_type_flat(self.name, self.info,
-                                       self.members, self.variants)
+        visitor.visit_object_type(self.name, self.info, self)


 class QAPISchemaMember(object):
diff --git a/scripts/qapi-introspect.py b/scripts/qapi-introspect.py
index e0f926b..474eafd 100644
--- a/scripts/qapi-introspect.py
+++ b/scripts/qapi-introspect.py
@@ -141,11 +141,11 @@ const char %(c_name)s[] = %(c_string)s;
         element = self._use_type(element_type)
         self._gen_json('[' + element + ']', 'array', {'element-type': element})

-    def visit_object_type_flat(self, name, info, members, variants):
-        obj = {'members': [self._gen_member(m) for m in members]}
-        if variants:
-            obj.update(self._gen_variants(variants.tag_member.name,
-                                          variants.variants))
+    def visit_object_type(self, name, info, typ):
+        obj = {'members': [self._gen_member(m) for m in typ.members]}
+        if typ.variants:
+            obj.update(self._gen_variants(typ.variants.tag_member.name,
+                                          typ.variants.variants))
         self._gen_json(name, 'object', obj)

     def visit_alternate_type(self, name, info, variants):
diff --git a/scripts/qapi-types.py b/scripts/qapi-types.py
index 437cf6c..60de4b6 100644
--- a/scripts/qapi-types.py
+++ b/scripts/qapi-types.py
@@ -220,17 +220,16 @@ class QAPISchemaGenTypeVisitor(QAPISchemaVisitor):
             self.decl += gen_array(name, element_type)
             self._gen_type_cleanup(name)

-    def visit_object_type(self, name, info, base, members, variants):
+    def visit_object_type(self, name, info, typ):
         # Nothing to do for the special empty builtin
         if name == 'q_empty':
             return
         self._fwdecl += gen_fwd_object_or_array(name)
-        self.decl += gen_object(name, base, members, variants)
-        if base and not base.is_implicit():
-            self.decl += gen_upcast(name, base)
-        # TODO Worth changing the visitor signature, so we could
-        # directly use rather than repeat type.is_implicit()?
-        if not name.startswith('q_'):
+        self.decl += gen_object(name, typ.base, typ.local_members,
+                                typ.variants)
+        if typ.base and not typ.base.is_implicit():
+            self.decl += gen_upcast(name, typ.base)
+        if not typ.is_implicit():
             # implicit types won't be directly allocated/freed
             self._gen_type_cleanup(name)

diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
index 31d2330..dc8b39c 100644
--- a/scripts/qapi-visit.py
+++ b/scripts/qapi-visit.py
@@ -289,18 +289,18 @@ class QAPISchemaGenVisitVisitor(QAPISchemaVisitor):
             self.decl += decl
             self.defn += defn

-    def visit_object_type(self, name, info, base, members, variants):
+    def visit_object_type(self, name, info, typ):
         # Nothing to do for the special empty builtin
         if name == 'q_empty':
             return
         self.decl += gen_visit_members_decl(name)
-        self.defn += gen_visit_object_members(name, base, members, variants)
-        # TODO Worth changing the visitor signature, so we could
-        # directly use rather than repeat type.is_implicit()?
-        if not name.startswith('q_'):
+        self.defn += gen_visit_object_members(name, typ.base,
+                                              typ.local_members, typ.variants)
+        if not typ.is_implicit():
             # only explicit types need an allocating visit
             self.decl += gen_visit_decl(name)
-            self.defn += gen_visit_object(name, base, members, variants)
+            self.defn += gen_visit_object(name, typ.base, typ.local_members,
+                                          typ.variants)

     def visit_alternate_type(self, name, info, variants):
         self.decl += gen_visit_decl(name)
diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py
index 649677e..ccd1704 100644
--- a/tests/qapi-schema/test-qapi.py
+++ b/tests/qapi-schema/test-qapi.py
@@ -22,14 +22,14 @@ class QAPISchemaTestVisitor(QAPISchemaVisitor):
         if prefix:
             print '    prefix %s' % prefix

-    def visit_object_type(self, name, info, base, members, variants):
+    def visit_object_type(self, name, info, typ):
         print 'object %s' % name
-        if base:
-            print '    base %s' % base.name
-        for m in members:
+        if typ.base:
+            print '    base %s' % typ.base.name
+        for m in typ.local_members:
             print '    member %s: %s optional=%s' % \
                 (m.name, m.type.name, m.optional)
-        self._print_variants(variants)
+        self._print_variants(typ.variants)

     def visit_alternate_type(self, name, info, variants):
         print 'alternate %s' % name
-- 
2.5.5

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

* [Qemu-devel] [PATCH v14 02/19] qapi-visit: Add visitor.type classification
  2016-04-08 16:12 [Qemu-devel] [PATCH v14 00/19] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
  2016-04-08 16:12 ` [Qemu-devel] [PATCH v14 01/19] qapi: Consolidate object visitors Eric Blake
@ 2016-04-08 16:12 ` Eric Blake
  2016-04-13 13:49   ` Markus Armbruster
  2016-04-08 16:12 ` [Qemu-devel] [PATCH v14 03/19] qapi: Guarantee NULL obj on input visitor callback error Eric Blake
                   ` (17 subsequent siblings)
  19 siblings, 1 reply; 61+ messages in thread
From: Eric Blake @ 2016-04-08 16:12 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.

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.

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

---
v14: no change
v13: no change
v12: new patch
---
 include/qapi/visitor-impl.h  | 21 ++++++++++++---------
 qapi/qapi-visit-core.c       | 28 +++++++++++++++-------------
 qapi/opts-visitor.c          | 12 ++----------
 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 +-
 8 files changed, 34 insertions(+), 42 deletions(-)

diff --git a/include/qapi/visitor-impl.h b/include/qapi/visitor-impl.h
index 2bd8f29..228a2a6 100644
--- a/include/qapi/visitor-impl.h
+++ b/include/qapi/visitor-impl.h
@@ -14,6 +14,15 @@

 #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 +45,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 +63,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..f98cf2e 100644
--- a/qapi/opts-visitor.c
+++ b/qapi/opts-visitor.c
@@ -507,6 +507,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 +516,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] 61+ messages in thread

* [Qemu-devel] [PATCH v14 03/19] qapi: Guarantee NULL obj on input visitor callback error
  2016-04-08 16:12 [Qemu-devel] [PATCH v14 00/19] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
  2016-04-08 16:12 ` [Qemu-devel] [PATCH v14 01/19] qapi: Consolidate object visitors Eric Blake
  2016-04-08 16:12 ` [Qemu-devel] [PATCH v14 02/19] qapi-visit: Add visitor.type classification Eric Blake
@ 2016-04-08 16:12 ` Eric Blake
  2016-04-13 14:04   ` Markus Armbruster
  2016-04-08 16:12 ` [Qemu-devel] [PATCH v14 04/19] qmp: Drop dead command->type Eric Blake
                   ` (16 subsequent siblings)
  19 siblings, 1 reply; 61+ messages in thread
From: Eric Blake @ 2016-04-08 16:12 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'. 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.

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.

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

---
v14: no change
v13: no change
v12: new patch
---
 qapi/opts-visitor.c         | 3 ++-
 qapi/qmp-input-visitor.c    | 4 ++++
 qapi/string-input-visitor.c | 1 +
 3 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/qapi/opts-visitor.c b/qapi/opts-visitor.c
index f98cf2e..cdb6e42 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");
     }
-- 
2.5.5

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

* [Qemu-devel] [PATCH v14 04/19] qmp: Drop dead command->type
  2016-04-08 16:12 [Qemu-devel] [PATCH v14 00/19] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
                   ` (2 preceding siblings ...)
  2016-04-08 16:12 ` [Qemu-devel] [PATCH v14 03/19] qapi: Guarantee NULL obj on input visitor callback error Eric Blake
@ 2016-04-08 16:12 ` Eric Blake
  2016-04-08 16:12 ` [Qemu-devel] [PATCH v14 05/19] qmp-input: Clean up stack handling Eric Blake
                   ` (15 subsequent siblings)
  19 siblings, 0 replies; 61+ messages in thread
From: Eric Blake @ 2016-04-08 16:12 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>

---
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] 61+ messages in thread

* [Qemu-devel] [PATCH v14 05/19] qmp-input: Clean up stack handling
  2016-04-08 16:12 [Qemu-devel] [PATCH v14 00/19] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
                   ` (3 preceding siblings ...)
  2016-04-08 16:12 ` [Qemu-devel] [PATCH v14 04/19] qmp: Drop dead command->type Eric Blake
@ 2016-04-08 16:12 ` Eric Blake
  2016-04-13 15:53   ` Markus Armbruster
  2016-04-08 16:12 ` [Qemu-devel] [PATCH v14 06/19] qmp-input: Don't consume input when checking has_member Eric Blake
                   ` (14 subsequent siblings)
  19 siblings, 1 reply; 61+ messages in thread
From: Eric Blake @ 2016-04-08 16:12 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.

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

---
v14: no change
v13: no change
v12: new patch
---
 qapi/qmp-input-visitor.c | 52 ++++++++++++++++++++++++++++++++++--------------
 1 file changed, 37 insertions(+), 15 deletions(-)

diff --git a/qapi/qmp-input-visitor.c b/qapi/qmp-input-visitor.c
index 77cce8b..7ba6d3d 100644
--- a/qapi/qmp-input-visitor.c
+++ b/qapi/qmp-input-visitor.c
@@ -25,16 +25,26 @@

 typedef struct StackObject
 {
-    QObject *obj;
+    QObject *obj; /* Object being visited */
+
+    /* If obj is list: NULL if list is at head, otherwise tail of list
+     * still needing visits */
     const QListEntry *entry;
-    GHashTable *h;
+
+    GHashTable *h; /* If obj is dict: remaining keys needing visits */
 } StackObject;

 struct QmpInputVisitor
 {
     Visitor visitor;
+
+    /* Stack of objects being visited.  stack[0] is root of visit,
+     * stack[1] and below correspond to visit_start_struct (nested
+     * QDict) and visit_start_list (nested QList).  */
     StackObject stack[QIV_STACK_SIZE];
     int nb_stack;
+
+    /* True to track whether all keys in QDict have been parsed.  */
     bool strict;
 };

@@ -47,19 +57,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 +92,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] 61+ messages in thread

* [Qemu-devel] [PATCH v14 06/19] qmp-input: Don't consume input when checking has_member
  2016-04-08 16:12 [Qemu-devel] [PATCH v14 00/19] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
                   ` (4 preceding siblings ...)
  2016-04-08 16:12 ` [Qemu-devel] [PATCH v14 05/19] qmp-input: Clean up stack handling Eric Blake
@ 2016-04-08 16:12 ` Eric Blake
  2016-04-13 16:06   ` Markus Armbruster
  2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 07/19] qmp-input: Refactor when list is advanced Eric Blake
                   ` (13 subsequent siblings)
  19 siblings, 1 reply; 61+ messages in thread
From: Eric Blake @ 2016-04-08 16:12 UTC (permalink / raw)
  To: qemu-devel; +Cc: armbru, Michael Roth

Commit e8316d7 mistakenly passed consume=true 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.  Tighten up the input visitor to ensure that a member is
consumed exactly once.  To keep the testsuite happy in the case
of incomplete input, we have to check whether a member exists
in the dictionary before trying to remove it.

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

---
v14: no change
v13: no change
v12: new patch
---
 qapi/qmp-input-visitor.c | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/qapi/qmp-input-visitor.c b/qapi/qmp-input-visitor.c
index 7ba6d3d..cfaf378 100644
--- a/qapi/qmp-input-visitor.c
+++ b/qapi/qmp-input-visitor.c
@@ -65,10 +65,12 @@ static QObject *qmp_input_get_object(QmpInputVisitor *qiv,
     /* 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);
+        qobj = qdict_get(qobject_to_qdict(qobj), name);
+        if (tos->h && consume && qobj) {
+            bool removed = g_hash_table_remove(tos->h, name);
+            assert(removed);
         }
-        return qdict_get(qobject_to_qdict(qobj), name);
+        return qobj;
     }

     /* If we are in the middle of a list, then return the next element
@@ -338,7 +340,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] 61+ messages in thread

* [Qemu-devel] [PATCH v14 07/19] qmp-input: Refactor when list is advanced
  2016-04-08 16:12 [Qemu-devel] [PATCH v14 00/19] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
                   ` (5 preceding siblings ...)
  2016-04-08 16:12 ` [Qemu-devel] [PATCH v14 06/19] qmp-input: Don't consume input when checking has_member Eric Blake
@ 2016-04-08 16:13 ` Eric Blake
  2016-04-13 17:38   ` Markus Armbruster
  2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 08/19] qapi: Document visitor interfaces, add assertions Eric Blake
                   ` (12 subsequent siblings)
  19 siblings, 1 reply; 61+ messages in thread
From: Eric Blake @ 2016-04-08 16:13 UTC (permalink / raw)
  To: qemu-devel; +Cc: armbru, Michael Roth

Refactor the code to advance the QListEntry pointer at the point
where visit_type_FOO() is called, rather than visit_next_list().
This will allow a future patch to move the visit of the list head
into visit_start_list(), and get rid of the 'first' flag.

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

---
v14: no change
v13: no change
v12: new patch
---
 qapi/qmp-input-visitor.c | 40 +++++++++++++++++++++-------------------
 1 file changed, 21 insertions(+), 19 deletions(-)

diff --git a/qapi/qmp-input-visitor.c b/qapi/qmp-input-visitor.c
index cfaf378..1820360 100644
--- a/qapi/qmp-input-visitor.c
+++ b/qapi/qmp-input-visitor.c
@@ -27,9 +27,10 @@ typedef struct StackObject
 {
     QObject *obj; /* Object being visited */

-    /* If obj is list: NULL if list is at head, otherwise tail of list
-     * still needing visits */
+    /* If obj is list: tail of list still needing visits */
     const QListEntry *entry;
+    /* If obj is list: true if head is not visited yet */
+    bool first;

     GHashTable *h; /* If obj is dict: remaining keys needing visits */
 } StackObject;
@@ -77,7 +78,12 @@ static QObject *qmp_input_get_object(QmpInputVisitor *qiv,
      * of the list.  */
     if (tos->entry) {
         assert(qobject_type(qobj) == QTYPE_QLIST);
-        return qlist_entry_obj(tos->entry);
+        assert(!tos->first);
+        qobj = qlist_entry_obj(tos->entry);
+        if (consume) {
+            tos->entry = qlist_next(tos->entry);
+        }
+        return qobj;
     }

     /* Otherwise, we are at the root of the visit or the start of a
@@ -91,7 +97,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 void qmp_input_push(QmpInputVisitor *qiv, QObject *obj,
+                           const QListEntry *entry, Error **errp)
 {
     GHashTable *h;
     StackObject *tos = &qiv->stack[qiv->nb_stack];
@@ -103,7 +110,8 @@ static void qmp_input_push(QmpInputVisitor *qiv, QObject *obj, Error **errp)
     }

     tos->obj = obj;
-    tos->entry = NULL;
+    tos->entry = entry;
+    tos->first = true;
     tos->h = NULL;

     if (qiv->strict && qobject_type(obj) == QTYPE_QDICT) {
@@ -153,7 +161,7 @@ static void qmp_input_start_struct(Visitor *v, const char *name, void **obj,
         return;
     }

-    qmp_input_push(qiv, qobj, &err);
+    qmp_input_push(qiv, qobj, NULL, &err);
     if (err) {
         error_propagate(errp, err);
         return;
@@ -175,6 +183,7 @@ static void qmp_input_start_list(Visitor *v, const char *name, 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) {
         error_setg(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null",
@@ -182,7 +191,8 @@ static void qmp_input_start_list(Visitor *v, const char *name, Error **errp)
         return;
     }

-    qmp_input_push(qiv, qobj, errp);
+    entry = qlist_first(qobject_to_qlist(qobj));
+    qmp_input_push(qiv, qobj, entry, errp);
 }

 static GenericList *qmp_input_next_list(Visitor *v, GenericList **list,
@@ -191,23 +201,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;
     }
@@ -382,7 +384,7 @@ QmpInputVisitor *qmp_input_visitor_new(QObject *obj)
     v->visitor.type_any = qmp_input_type_any;
     v->visitor.optional = qmp_input_optional;

-    qmp_input_push(v, obj, NULL);
+    qmp_input_push(v, obj, NULL, NULL);
     qobject_incref(obj);

     return v;
-- 
2.5.5

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

* [Qemu-devel] [PATCH v14 08/19] qapi: Document visitor interfaces, add assertions
  2016-04-08 16:12 [Qemu-devel] [PATCH v14 00/19] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
                   ` (6 preceding siblings ...)
  2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 07/19] qmp-input: Refactor when list is advanced Eric Blake
@ 2016-04-08 16:13 ` Eric Blake
  2016-04-14 15:22   ` Markus Armbruster
  2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 09/19] tests: Add check-qnull Eric Blake
                   ` (11 subsequent siblings)
  19 siblings, 1 reply; 61+ messages in thread
From: Eric Blake @ 2016-04-08 16:13 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>

---
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               | 433 +++++++++++++++++++++++++++++++++--
 include/qapi/visitor-impl.h          |  42 +++-
 include/qapi/dealloc-visitor.h       |   4 +
 include/qapi/opts-visitor.h          |   4 +
 include/qapi/qmp-input-visitor.h     |   8 +
 include/qapi/string-input-visitor.h  |   4 +
 include/qapi/string-output-visitor.h |   4 +
 qapi/qapi-visit-core.c               |  19 +-
 8 files changed, 494 insertions(+), 24 deletions(-)

diff --git a/include/qapi/visitor.h b/include/qapi/visitor.h
index 9a8d010..faf67d2 100644
--- a/include/qapi/visitor.h
+++ b/include/qapi/visitor.h
@@ -16,8 +16,185 @@

 #include "qapi/qmp/qobject.h"

+/*
+ * The QAPI schema defines both a set of C data types, and a QMP wire
+ * format.  A QAPI object is formed as a directed acyclic graph of
+ * QAPI values.  QAPI also generates visitor functions to walk these
+ * graphs.  This file represents the interface for doing work at each
+ * point 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 parse an
+ * external representation and allocate the corresponding QAPI graph
+ * (QMP, string, and QemuOpts), output visitors take a completed QAPI
+ * graph and generate an external representation (QMP and string), and
+ * the dealloc visitor can take a (partial) QAPI graph 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, void *obj, Error **errp);
+ *
+ * except that *@obj is typed correctly as a pointer or scalar,
+ * depending on the type of FOO.  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 usually ignored (although some
+ * visitors require it to be NULL); 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.
+ *
+ * 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 has an @errp argument which may be NULL
+ * to ignore errors, or point to a NULL Error object on entry for
+ * reporting any errors (such as if a member @name is not present, or
+ * is present but not the specified type).
+ *
+ * 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).
+ *
+ * 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.
+ *
+ * Likewise, the QAPI object types (structs, unions, and alternates)
+ * have a generated function in qapi-visit.h compatible with:
+ *
+ * void visit_type_FOO_members(Visitor *v, FOO *obj, Error **errp);
+ *
+ * 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, a typical input life cycle is thus:
+ *
+ * <example>
+ *  Foo *f;
+ *  FooList *l;
+ *  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...
+ *  }
+ *  v = ...obtain input visitor...
+ *  visit_type_FooList(v, NULL, &l, &err);
+ *  if (err) {
+ *      qapi_free_FooList(l);
+ *      ...handle error...
+ *  } else {
+ *      while (l) {
+ *          ...use l->value...
+ *          l = l->next;
+ *      }
+ *  }
+ *  ...clean up v...
+ * </example>
+ *
+ * Similarly, a typical output life cycle is:
+ *
+ * <example>
+ *  Foo *f = g_new0(Foo, 1);
+ *  Error *err = NULL;
+ *  Visitor *v;
+ *
+ *  ...populate f...
+ *  v = ...obtain output visitor...
+ *  visit_type_Foo(v, NULL, &f, &err);
+ *  if (err) {
+ *      ...handle error...
+ *  }
+ *  qapi_free_Foo(f);
+ * </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,26 +202,117 @@ 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 value @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 must be NULL-initialized, and is set if an error is detected
+ * (such as if a member @name is not present, or is 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 must be NULL-initialized, and is set if an error is detected
+ * (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 may
+ * behave 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 must be NULL-initialized, and is set if an error is detected
+ * (such as if a member @name is not present, or is 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 list visit.
+ *
+ * @size represents the size of a linked list node.
+ *
+ * @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.  Must be called in a loop until a
+ * NULL return or error occurs; for each non-NULL return, the caller
+ * must then call 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 may
+ * behave 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 with the given @size (at least
+ * sizeof(GenericAlternate)).
  *
- * @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
@@ -52,8 +320,8 @@ void visit_end_list(Visitor *v);
  *
  * 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,46 +330,181 @@ 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 may
+ * behave 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.
+ *
+ * @name expresses the relationship of this enum to its parent
+ * container; see the general description of @name above.
+ *
+ * @strings expresses the mapping between C enum values and QAPI enum
+ * names; it should be the ENUM_lookup array from visit-types.h.
+ *
+ * @obj must be non-NULL.  For input visitors, parse a string and set
+ * *@obj to the numeric value of the enum type using @strings as the
+ * mapping, leaving @obj unchanged on error; other visitors leave
+ * *@obj unchanged.  Output visitors use *@obj to reverse the mapping
+ * and visit the appropriate output string.
+ */
 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 "".
+ *
+ * Note that using an output visitor along with a (const char *) value
+ * requires casting away const when computing @obj.
+ *
+ * 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 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.
+ */
 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 228a2a6..d967b18 100644
--- a/include/qapi/visitor-impl.h
+++ b/include/qapi/visitor-impl.h
@@ -14,6 +14,16 @@

 #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.  */
@@ -25,18 +35,24 @@ 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 */
+
+    /* Must be set.  */
     GenericList *(*next_list)(Visitor *v, GenericList **list, size_t size);
-    /* Must be set */
+
+    /* 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);
@@ -44,24 +60,34 @@ struct 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().  */
     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..7aa8293 100644
--- a/include/qapi/dealloc-visitor.h
+++ b/include/qapi/dealloc-visitor.h
@@ -18,6 +18,10 @@

 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..2002e37 100644
--- a/include/qapi/opts-visitor.h
+++ b/include/qapi/opts-visitor.h
@@ -29,6 +29,10 @@ 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 yet 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/qmp-input-visitor.h b/include/qapi/qmp-input-visitor.h
index 3ed499c..d75ff98 100644
--- a/include/qapi/qmp-input-visitor.h
+++ b/include/qapi/qmp-input-visitor.h
@@ -19,6 +19,14 @@

 typedef struct QmpInputVisitor QmpInputVisitor;

+/*
+ * FIXME: When visiting a QDict, passing a non-NULL @name for the
+ * first visit_type_FOO() when the root is a QDict will find that
+ * particular key within the QDict.  In the future, the contract may
+ * be tightened to require visit_start_struct() with ignored @name as
+ * the first visit; in the meantime, the first visit is safest when
+ * using NULL for @name.
+ */
 QmpInputVisitor *qmp_input_visitor_new(QObject *obj);
 QmpInputVisitor *qmp_input_visitor_new_strict(QObject *obj);

diff --git a/include/qapi/string-input-visitor.h b/include/qapi/string-input-visitor.h
index 089243c..4c8d1ea 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 yet 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..094a11e 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 yet 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 3cd7edc..71f3b5d 100644
--- a/qapi/qapi-visit-core.c
+++ b/qapi/qapi-visit-core.c
@@ -23,6 +23,10 @@
 void visit_start_struct(Visitor *v, const char *name, void **obj,
                         size_t size, Error **errp)
 {
+    if (obj) {
+        assert(size);
+        assert(v->type != VISITOR_OUTPUT || *obj);
+    }
     v->start_struct(v, name, obj, size, errp);
 }

@@ -74,6 +78,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);
 }

@@ -121,6 +126,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);
 }

@@ -168,12 +174,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 {
@@ -183,22 +191,31 @@ 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);
 }

 void visit_type_str(Visitor *v, const char *name, char **obj, Error **errp)
 {
+    assert(obj);
+    /* TODO: Fix callers to not pass NULL when they mean "", so that we
+     * can enable:
+    assert(v->type != VISITOR_OUTPUT || *obj);
+     */
     v->type_str(v, name, obj, errp);
 }

 void visit_type_number(Visitor *v, const char *name, double *obj,
                        Error **errp)
 {
+    assert(obj);
     v->type_number(v, name, obj, errp);
 }

 void visit_type_any(Visitor *v, const char *name, QObject **obj, Error **errp)
 {
+    assert(obj);
+    assert(v->type != VISITOR_OUTPUT || *obj);
     v->type_any(v, name, obj, errp);
 }

@@ -252,7 +269,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] 61+ messages in thread

* [Qemu-devel] [PATCH v14 09/19] tests: Add check-qnull
  2016-04-08 16:12 [Qemu-devel] [PATCH v14 00/19] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
                   ` (7 preceding siblings ...)
  2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 08/19] qapi: Document visitor interfaces, add assertions Eric Blake
@ 2016-04-08 16:13 ` Eric Blake
  2016-04-14 16:13   ` Markus Armbruster
  2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 10/19] qapi: Add visit_type_null() visitor Eric Blake
                   ` (10 subsequent siblings)
  19 siblings, 1 reply; 61+ messages in thread
From: Eric Blake @ 2016-04-08 16:13 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 into a more logical place.

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

---
v14: no change
v13: no change
v12: new patch
---
 tests/check-qnull.c             | 60 +++++++++++++++++++++++++++++++++++++++++
 tests/test-qmp-output-visitor.c |  2 --
 tests/.gitignore                |  1 +
 tests/Makefile                  |  6 ++++-
 4 files changed, 66 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..b0fb1e6
--- /dev/null
+++ b/tests/check-qnull.c
@@ -0,0 +1,60 @@
+/*
+ * 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;
+
+    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 9de9598..efdb9fb 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)
@@ -380,7 +382,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 \
@@ -408,6 +411,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] 61+ messages in thread

* [Qemu-devel] [PATCH v14 10/19] qapi: Add visit_type_null() visitor
  2016-04-08 16:12 [Qemu-devel] [PATCH v14 00/19] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
                   ` (8 preceding siblings ...)
  2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 09/19] tests: Add check-qnull Eric Blake
@ 2016-04-08 16:13 ` Eric Blake
  2016-04-14 17:09   ` Markus Armbruster
  2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 11/19] qmp: Support explicit null during visits Eric Blake
                   ` (9 subsequent siblings)
  19 siblings, 1 reply; 61+ messages in thread
From: Eric Blake @ 2016-04-08 16:13 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 the next patch
will then wire it up into the qmp visitors.  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.

If QAPI had a 'null' type, we'd also have to use visit_type_null()
in the generated visitor functions (most likely, we'd use it via
an alternate type that permits 'null' or an object); we'll create
that usage when we need it.

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

---
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          |  2 +-
 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 +++++
 7 files changed, 28 insertions(+), 3 deletions(-)

diff --git a/include/qapi/visitor.h b/include/qapi/visitor.h
index faf67d2..735b389 100644
--- a/include/qapi/visitor.h
+++ b/include/qapi/visitor.h
@@ -507,4 +507,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 d967b18..90bcaec 100644
--- a/include/qapi/visitor-impl.h
+++ b/include/qapi/visitor-impl.h
@@ -86,6 +86,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 2002e37..3fcd327 100644
--- a/include/qapi/opts-visitor.h
+++ b/include/qapi/opts-visitor.h
@@ -31,7 +31,7 @@ typedef struct OptsVisitor OptsVisitor;
  * - values above INT64_MAX or LLONG_MAX are rejected.
  *
  * The Opts input visitor does not yet implement support for visiting
- * QAPI alternates, numbers (other than integers), or arbitrary
+ * QAPI alternates, numbers (other than integers), null, or arbitrary
  * QTypes.
  */
 OptsVisitor *opts_visitor_new(const QemuOpts *opts);
diff --git a/include/qapi/string-input-visitor.h b/include/qapi/string-input-visitor.h
index 4c8d1ea..1a34c52 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 yet implement support for
- * visiting QAPI structs, alternates, or arbitrary QTypes.
+ * visiting 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 094a11e..2564833 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 yet implement support for
- * visiting QAPI structs, alternates, or arbitrary QTypes.
+ * visiting 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 71f3b5d..064b9f1 100644
--- a/qapi/qapi-visit-core.c
+++ b/qapi/qapi-visit-core.c
@@ -219,6 +219,11 @@ void visit_type_any(Visitor *v, const char *name, QObject **obj, Error **errp)
     v->type_any(v, name, obj, errp);
 }

+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);

-- 
2.5.5

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

* [Qemu-devel] [PATCH v14 11/19] qmp: Support explicit null during visits
  2016-04-08 16:12 [Qemu-devel] [PATCH v14 00/19] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
                   ` (9 preceding siblings ...)
  2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 10/19] qapi: Add visit_type_null() visitor Eric Blake
@ 2016-04-08 16:13 ` Eric Blake
  2016-04-15  8:29   ` Markus Armbruster
  2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 12/19] spapr_drc: Expose 'null' in qom-get when there is no fdt Eric Blake
                   ` (8 subsequent siblings)
  19 siblings, 1 reply; 61+ messages in thread
From: Eric Blake @ 2016-04-08 16:13 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), one usage
is already envisioned: when changing blockdev parameters, it
would be nice to have a difference between leaving a tuning
parameter unchanged (omit that parameter from the struct) and
to explicitly reset the parameter to its default without having
to know what the default value is (specify the parameter with
an explicit null value, which will require us to allow a QAPI
alternate that chooses between the normal value and an explicit
null).  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().  Repurpose the
test_visitor_out_empty test, particularly since a future patch
will tighten semantics to forbid immediate use of
qmp_output_get_qobject() without at least one visit_type_*.

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

---
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        | 12 ++++++++++++
 qapi/qmp-output-visitor.c       |  7 +++++++
 tests/check-qnull.c             | 11 ++++++++++-
 tests/test-qmp-input-visitor.c  | 16 ++++++++++++++++
 tests/test-qmp-output-visitor.c |  9 +++++----
 5 files changed, 50 insertions(+), 5 deletions(-)

diff --git a/qapi/qmp-input-visitor.c b/qapi/qmp-input-visitor.c
index 1820360..e5b412a 100644
--- a/qapi/qmp-input-visitor.c
+++ b/qapi/qmp-input-visitor.c
@@ -339,6 +339,17 @@ 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)
+{
+    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)
 {
     QmpInputVisitor *qiv = to_qiv(v);
@@ -382,6 +393,7 @@ QmpInputVisitor *qmp_input_visitor_new(QObject *obj)
     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;

     qmp_input_push(v, obj, NULL, NULL);
diff --git a/qapi/qmp-output-visitor.c b/qapi/qmp-output-visitor.c
index 1f2a7ba..5681ad3 100644
--- a/qapi/qmp-output-visitor.c
+++ b/qapi/qmp-output-visitor.c
@@ -196,6 +196,12 @@ 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)
+{
+    QmpOutputVisitor *qov = to_qov(v);
+    qmp_output_add_obj(qov, name, qnull());
+}
+
 /* 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);

diff --git a/tests/check-qnull.c b/tests/check-qnull.c
index b0fb1e6..43a222d 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,15 +39,22 @@ static void qnull_visit_test(void)
 {
     QObject *obj;
     QmpOutputVisitor *qov;
+    QmpInputVisitor *qiv;

     g_assert(qnull_.refcnt == 1);
+    obj = qnull();
+    qiv = qmp_input_visitor_new(obj);
+    qobject_decref(obj);
+    visit_type_null(qmp_input_get_visitor(qiv), NULL, &error_abort);
+
     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);
+    qmp_input_visitor_cleanup(qiv);
     g_assert(qnull_.refcnt == 1);
 }

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

+static void test_visitor_in_null(TestInputVisitorData *data,
+                                 const void *unused)
+{
+    Visitor *v;
+
+    v = visitor_input_test_init(data, "null");
+    visit_type_null(v, NULL, &error_abort);
+
+    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_end_struct(v, &error_abort);
+}
+
 static void test_visitor_in_union_flat(TestInputVisitorData *data,
                                        const void *unused)
 {
@@ -829,6 +843,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..e656d99 100644
--- a/tests/test-qmp-output-visitor.c
+++ b/tests/test-qmp-output-visitor.c
@@ -477,11 +477,12 @@ 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;

+    visit_type_null(data->ov, NULL, &error_abort);
     arg = qmp_output_get_qobject(data->qov);
     g_assert(qobject_type(arg) == QTYPE_QNULL);
     qobject_decref(arg);
@@ -837,8 +838,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] 61+ messages in thread

* [Qemu-devel] [PATCH v14 12/19] spapr_drc: Expose 'null' in qom-get when there is no fdt
  2016-04-08 16:12 [Qemu-devel] [PATCH v14 00/19] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
                   ` (10 preceding siblings ...)
  2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 11/19] qmp: Support explicit null during visits Eric Blake
@ 2016-04-08 16:13 ` Eric Blake
  2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 13/19] qmp: Tighten output visitor rules Eric Blake
                   ` (7 subsequent siblings)
  19 siblings, 0 replies; 61+ messages in thread
From: Eric Blake @ 2016-04-08 16:13 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>

---
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] 61+ messages in thread

* [Qemu-devel] [PATCH v14 13/19] qmp: Tighten output visitor rules
  2016-04-08 16:12 [Qemu-devel] [PATCH v14 00/19] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
                   ` (11 preceding siblings ...)
  2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 12/19] spapr_drc: Expose 'null' in qom-get when there is no fdt Eric Blake
@ 2016-04-08 16:13 ` Eric Blake
  2016-04-15  9:02   ` Markus Armbruster
  2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 14/19] qapi: Split visit_end_struct() into pieces Eric Blake
                   ` (6 subsequent siblings)
  19 siblings, 1 reply; 61+ messages in thread
From: Eric Blake @ 2016-04-08 16:13 UTC (permalink / raw)
  To: qemu-devel; +Cc: armbru, Michael Roth

Add a new qmp_output_visitor_reset(), which must be called before
reusing an exising QmpOutputVisitor on a new root object.  Tighten
assertions to require that qmp_output_get_qobject() can only be
called after pairing a visit_end_* for every visit_start_* (rather
than allowing it to return a partially built object), and that it
must not 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).

Also, check that we are encountering the expected object or list
type (both during visit_end*, and also by validating whether 'name'
was NULL - although the latter may need to change later if we
improve error messages by always passing in a sensible name).
This provides protection against mismatched push(struct)/pop(list)
or push(list)/pop(struct), similar to the qmp-input protection
added in commit bdd8e6b5.

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

---
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
---
 include/qapi/qmp-output-visitor.h |  1 +
 qapi/qmp-output-visitor.c         | 39 +++++++++++++++++++++++----------------
 tests/test-qmp-output-visitor.c   |  6 ++++++
 3 files changed, 30 insertions(+), 16 deletions(-)

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..7c48dfb 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,9 @@ static void qmp_output_add_obj(QmpOutputVisitor *qov, const char *name,
             qdict_put_obj(qobject_to_qdict(cur), name, value);
             break;
         case QTYPE_QLIST:
+            /* FIXME: assertion needs adjustment if we fix visit-core
+             * to pass "name.0" style name during lists.  */
+            assert(!name);
             qlist_append_obj(qobject_to_qlist(cur), value);
             break;
         default:
@@ -114,7 +116,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 +148,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 +206,15 @@ 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. Will not be NULL.
+ * Caller must use qobject_decref() on the result.  */
 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_FIRST(&qov->stack));
+
+    qobject_incref(qov->root);
+    return qov->root;
 }

 Visitor *qmp_output_get_visitor(QmpOutputVisitor *v)
@@ -221,7 +222,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 +232,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 e656d99..7be33ba 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] 61+ messages in thread

* [Qemu-devel] [PATCH v14 14/19] qapi: Split visit_end_struct() into pieces
  2016-04-08 16:12 [Qemu-devel] [PATCH v14 00/19] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
                   ` (12 preceding siblings ...)
  2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 13/19] qmp: Tighten output visitor rules Eric Blake
@ 2016-04-08 16:13 ` Eric Blake
  2016-04-15 11:03   ` Markus Armbruster
  2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 15/19] qapi-commands: Wrap argument visit in visit_start_struct Eric Blake
                   ` (5 subsequent siblings)
  19 siblings, 1 reply; 61+ messages in thread
From: Eric Blake @ 2016-04-08 16:13 UTC (permalink / raw)
  To: qemu-devel
  Cc: armbru, Kevin Wolf, 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 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:

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

---
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         | 20 +++++++++++++++-----
 include/qapi/visitor-impl.h    |  5 ++++-
 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       | 34 +++++++++++++++++++---------------
 qapi/qmp-output-visitor.c      |  2 +-
 qom/object.c                   |  5 ++---
 qom/object_interfaces.c        | 16 +++++++---------
 tests/test-qmp-input-visitor.c |  3 ++-
 docs/qapi-code-gen.txt         |  8 +++++---
 16 files changed, 111 insertions(+), 64 deletions(-)

diff --git a/include/qapi/visitor.h b/include/qapi/visitor.h
index 735b389..4c29722 100644
--- a/include/qapi/visitor.h
+++ b/include/qapi/visitor.h
@@ -182,10 +182,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>
@@ -238,17 +239,26 @@ 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 must be NULL-initialized, and is set if an error is detected
  * (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.
+ */
+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 may
  * behave 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 90bcaec..d44fcd1 100644
--- a/include/qapi/visitor-impl.h
+++ b/include/qapi/visitor-impl.h
@@ -39,8 +39,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-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 dc8b39c..bfe4a09 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 064b9f1..76ea690 100644
--- a/qapi/qapi-visit-core.c
+++ b/qapi/qapi-visit-core.c
@@ -30,9 +30,16 @@ void visit_start_struct(Visitor *v, const char *name, void **obj,
     v->start_struct(v, name, obj, size, errp);
 }

-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 c74101e..47f5f5e 100644
--- a/hw/virtio/virtio-balloon.c
+++ b/hw/virtio/virtio-balloon.c
@@ -137,17 +137,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 cdb6e42..a08d5a7 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) {
@@ -511,6 +523,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 e5b412a..a94cfa9 100644
--- a/qapi/qmp-input-visitor.c
+++ b/qapi/qmp-input-visitor.c
@@ -124,8 +124,10 @@ static void qmp_input_push(QmpInputVisitor *qiv, QObject *obj,
 }


-static void qmp_input_pop(QmpInputVisitor *qiv, Error **errp)
+static void qmp_input_check_struct(Visitor *v, Error **errp)
 {
+    QmpInputVisitor *qiv = to_qiv(v);
+
     assert(qiv->nb_stack > 0);

     if (qiv->strict) {
@@ -138,6 +140,19 @@ 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);
+
+    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);
         }
     }
@@ -172,12 +187,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)
 {
@@ -217,12 +226,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,
@@ -382,10 +385,11 @@ QmpInputVisitor *qmp_input_visitor_new(QObject *obj)

     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 7c48dfb..ecb2005 100644
--- a/qapi/qmp-output-visitor.c
+++ b/qapi/qmp-output-visitor.c
@@ -113,7 +113,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 ab5da35..a0bf6b7 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);
@@ -69,16 +69,14 @@ Object *user_creatable_add(const QDict *qdict,
         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;
+    visit_check_struct(v, &local_err);
+    if (local_err) {
+        user_creatable_del(id, NULL);
     }

+out_visit:
+    visit_end_struct(v);
+
 out:
     QDECREF(pdict);
     g_free(id);
diff --git a/tests/test-qmp-input-visitor.c b/tests/test-qmp-input-visitor.c
index d06383a..ac8ebea 100644
--- a/tests/test-qmp-input-visitor.c
+++ b/tests/test-qmp-input-visitor.c
@@ -290,7 +290,8 @@ static void test_visitor_in_null(TestInputVisitorData *data,
     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_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/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt
index 0e4baff..3b2422f 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);
     }
-- 
2.5.5

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

* [Qemu-devel] [PATCH v14 15/19] qapi-commands: Wrap argument visit in visit_start_struct
  2016-04-08 16:12 [Qemu-devel] [PATCH v14 00/19] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
                   ` (13 preceding siblings ...)
  2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 14/19] qapi: Split visit_end_struct() into pieces Eric Blake
@ 2016-04-08 16:13 ` Eric Blake
  2016-04-15 11:42   ` Markus Armbruster
  2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 16/19] qom: Wrap prop " Eric Blake
                   ` (4 subsequent siblings)
  19 siblings, 1 reply; 61+ messages in thread
From: Eric Blake @ 2016-04-08 16:13 UTC (permalink / raw)
  To: qemu-devel; +Cc: armbru, Michael Roth

The qmp-input visitor was playing rather fast and loose: when
visiting a QDict, you could grab members of the root dictionary
without first pushing into the dict.  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, because the value of 'name'
should be ignored on the top-level visit.

Generated code grows as follows:

|@@ -515,7 +695,15 @@ 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);
|+    if (!err) {
|+        visit_check_struct(v, &err);
|+    }
|+    visit_end_struct(v);
|     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);
|     qapi_dealloc_visitor_cleanup(qdv);
| }

Note that this change could also make it possible for the
marshalling code to automatically detect excess input at the top
level, and not just in nested dictionaries.  However, that checking
is not currently useful (and we rely on the manual checking in
monitor.c:qmp_check_client_args() instead) as long as qmp-commands.hx
uses .args_type, and as long as we have 'name:O' as an arg-type that
explicitly allows unknown top-level keys because we haven't yet
converted 'device_add' and 'netdev_add' to introspectible use of
'any'.

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

---
v14: rebase to master context
v13: rebase to earlier patches
v12: new patch
---
 scripts/qapi-commands.py | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/scripts/qapi-commands.py b/scripts/qapi-commands.py
index b570069..9c1bd25 100644
--- a/scripts/qapi-commands.py
+++ b/scripts/qapi-commands.py
@@ -121,7 +121,15 @@ 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);
+    if (!err) {
+        visit_check_struct(v, &err);
+    }
+    visit_end_struct(v);
     if (err) {
         goto out;
     }
@@ -150,7 +158,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);
     qapi_dealloc_visitor_cleanup(qdv);
 ''',
                      c_name=arg_type.c_name())
-- 
2.5.5

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

* [Qemu-devel] [PATCH v14 16/19] qom: Wrap prop visit in visit_start_struct
  2016-04-08 16:12 [Qemu-devel] [PATCH v14 00/19] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
                   ` (14 preceding siblings ...)
  2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 15/19] qapi-commands: Wrap argument visit in visit_start_struct Eric Blake
@ 2016-04-08 16:13 ` Eric Blake
  2016-04-15 11:52   ` Markus Armbruster
  2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 17/19] qmp-input: Require struct push to visit members of top dict Eric Blake
                   ` (3 subsequent siblings)
  19 siblings, 1 reply; 61+ messages in thread
From: Eric Blake @ 2016-04-08 16:13 UTC (permalink / raw)
  To: qemu-devel; +Cc: armbru, Andreas Färber

The qmp-input visitor was playing rather fast and loose: when
visiting a QDict, you could grab members of the root dictionary
without first pushing into the dict.  But we are about to tighten
the input visitor, at which point user_creatable_add_type() MUST
follow the same paradigms as everyone else, of pushing into the
struct before grabbing its keys, because the value of 'name'
should be ignored on the top-level visit.

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.

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

---
v14: no change
v13: no change
v12: new patch
---
 qom/object_interfaces.c | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)

diff --git a/qom/object_interfaces.c b/qom/object_interfaces.c
index a0bf6b7..62ec75a 100644
--- a/qom/object_interfaces.c
+++ b/qom/object_interfaces.c
@@ -118,12 +118,23 @@ 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;
             }
         }
+        if (!local_err) {
+            visit_check_struct(v, &local_err);
+        }
+        visit_end_struct(v);
+        if (local_err) {
+            goto out;
+        }
     }

     object_property_add_child(object_get_objects_root(),
-- 
2.5.5

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

* [Qemu-devel] [PATCH v14 17/19] qmp-input: Require struct push to visit members of top dict
  2016-04-08 16:12 [Qemu-devel] [PATCH v14 00/19] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
                   ` (15 preceding siblings ...)
  2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 16/19] qom: Wrap prop " Eric Blake
@ 2016-04-08 16:13 ` Eric Blake
  2016-04-15 12:53   ` Markus Armbruster
  2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 18/19] qapi: Simplify semantics of visit_next_list() Eric Blake
                   ` (2 subsequent siblings)
  19 siblings, 1 reply; 61+ messages in thread
From: Eric Blake @ 2016-04-08 16:13 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, and we no
longer have to require the root visit to pass name=NULL.

An audit of all qmp_input_visitor_new* call sites shows that
the only places where the visit starts directly on a QDict,
but where the first visit_type_* within the visit had passed
a non-NULL name, were fixed in the previous two places to
properly push into the object with visit_start_struct().

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

---
v14: no change
v13: no change
v12: new patch
---
 include/qapi/visitor.h           |  3 +--
 include/qapi/qmp-input-visitor.h |  8 ------
 qapi/qmp-input-visitor.c         | 54 ++++++++++++++++++++++------------------
 3 files changed, 31 insertions(+), 34 deletions(-)

diff --git a/include/qapi/visitor.h b/include/qapi/visitor.h
index 4c29722..e1213a3 100644
--- a/include/qapi/visitor.h
+++ b/include/qapi/visitor.h
@@ -47,8 +47,7 @@
  *
  * 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 usually ignored (although some
- * visitors require it to be NULL); when visiting a member of an
+ * 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.
  *
diff --git a/include/qapi/qmp-input-visitor.h b/include/qapi/qmp-input-visitor.h
index d75ff98..3ed499c 100644
--- a/include/qapi/qmp-input-visitor.h
+++ b/include/qapi/qmp-input-visitor.h
@@ -19,14 +19,6 @@

 typedef struct QmpInputVisitor QmpInputVisitor;

-/*
- * FIXME: When visiting a QDict, passing a non-NULL @name for the
- * first visit_type_FOO() when the root is a QDict will find that
- * particular key within the QDict.  In the future, the contract may
- * be tightened to require visit_start_struct() with ignored @name as
- * the first visit; in the meantime, the first visit is safest when
- * using NULL for @name.
- */
 QmpInputVisitor *qmp_input_visitor_new(QObject *obj);
 QmpInputVisitor *qmp_input_visitor_new_strict(QObject *obj);

diff --git a/qapi/qmp-input-visitor.c b/qapi/qmp-input-visitor.c
index a94cfa9..16f2f5a 100644
--- a/qapi/qmp-input-visitor.c
+++ b/qapi/qmp-input-visitor.c
@@ -39,9 +39,11 @@ struct QmpInputVisitor
 {
     Visitor visitor;

-    /* Stack of objects being visited.  stack[0] is root of visit,
-     * stack[1] and below correspond to visit_start_struct (nested
-     * QDict) and visit_start_list (nested QList).  */
+    /* 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;

@@ -58,36 +60,40 @@ 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;
+    StackObject *tos;
+    QObject *qobj;

+    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);
         qobj = qdict_get(qobject_to_qdict(qobj), name);
         if (tos->h && consume && qobj) {
             bool removed = g_hash_table_remove(tos->h, name);
             assert(removed);
         }
-        return qobj;
-    }
-
-    /* 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);
-        assert(!tos->first);
-        qobj = qlist_entry_obj(tos->entry);
-        if (consume) {
-            tos->entry = qlist_next(tos->entry);
+        /* FIXME: assertion needs adjustment if we fix visit-core
+         * to pass "name.0" style name during lists.  */
+        assert(!name);
+
+        if (tos->entry) {
+            assert(!tos->first);
+            qobj = qlist_entry_obj(tos->entry);
+            if (consume) {
+                tos->entry = qlist_next(tos->entry);
+            }
         }
-        return qobj;
     }
-
-    /* Otherwise, we are at the root of the visit or the start of a
-     * list, and return the object as-is.  */
     return qobj;
 }

@@ -373,7 +379,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);
 }

@@ -400,7 +406,7 @@ QmpInputVisitor *qmp_input_visitor_new(QObject *obj)
     v->visitor.type_null = qmp_input_type_null;
     v->visitor.optional = qmp_input_optional;

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

     return v;
-- 
2.5.5

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

* [Qemu-devel] [PATCH v14 18/19] qapi: Simplify semantics of visit_next_list()
  2016-04-08 16:12 [Qemu-devel] [PATCH v14 00/19] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
                   ` (16 preceding siblings ...)
  2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 17/19] qmp-input: Require struct push to visit members of top dict Eric Blake
@ 2016-04-08 16:13 ` Eric Blake
  2016-04-15 14:09   ` Markus Armbruster
  2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 19/19] qapi: Change visit_type_FOO() to no longer return partial objects Eric Blake
  2016-04-15 15:41 ` [Qemu-devel] [PATCH v14 00/19] qapi visitor cleanups (post-introspection cleanups subset E) Markus Armbruster
  19 siblings, 1 reply; 61+ messages in thread
From: Eric Blake @ 2016-04-08 16:13 UTC (permalink / raw)
  To: qemu-devel
  Cc: armbru, Michael Roth, David Gibson, Alexander Graf, open list:sPAPR

We 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).  What's more, 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.

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()).

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.

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

---
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               | 47 +++++++++++++++++++-----------------
 include/qapi/visitor-impl.h          |  7 +++---
 scripts/qapi-visit.py                | 18 +++++++-------
 include/qapi/opts-visitor.h          |  2 +-
 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             | 32 ++++++++++++------------
 qapi/qmp-output-visitor.c            | 22 ++++-------------
 qapi/string-input-visitor.c          | 36 +++++++++++++--------------
 qapi/string-output-visitor.c         | 41 +++++++++++--------------------
 docs/qapi-code-gen.txt               | 17 +++++++------
 15 files changed, 133 insertions(+), 177 deletions(-)

diff --git a/include/qapi/visitor.h b/include/qapi/visitor.h
index e1213a3..a22a7ce 100644
--- a/include/qapi/visitor.h
+++ b/include/qapi/visitor.h
@@ -165,7 +165,7 @@
  *  if (err) {
  *      goto outobj;
  *  }
- *  visit_start_list(v, "list", &err);
+ *  visit_start_list(v, "list", NULL, 0, &err);
  *  if (err) {
  *      goto outobj;
  *  }
@@ -268,40 +268,43 @@ 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 must be NULL-initialized, and is set if an error is detected
  * (such as if a member @name is not present, or is present but not a
- * list).
+ * list).  On error, input visitors set *@obj 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 list visit.
+ * Iterate over a GenericList during a non-virtual list visit.
  *
- * @size represents the size of a linked list node.
+ * @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.  Must be called in a loop until a
+ * @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.  Must be called in a loop until a
  * NULL return or error occurs; for each non-NULL return, the caller
  * must then call 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().
+ * 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 d44fcd1..0471465 100644
--- a/include/qapi/visitor-impl.h
+++ b/include/qapi/visitor-impl.h
@@ -45,11 +45,12 @@ 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; document if @list may not be NULL.  */
+    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 bfe4a09..69f0133 100644
--- a/scripts/qapi-visit.py
+++ b/scripts/qapi-visit.py
@@ -117,20 +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);
 out:
     error_propagate(errp, err);
diff --git a/include/qapi/opts-visitor.h b/include/qapi/opts-visitor.h
index 3fcd327..7c9a7e5 100644
--- a/include/qapi/opts-visitor.h
+++ b/include/qapi/opts-visitor.h
@@ -32,7 +32,7 @@ typedef struct OptsVisitor OptsVisitor;
  *
  * The Opts input visitor does not yet implement support for visiting
  * QAPI alternates, numbers (other than integers), null, or arbitrary
- * QTypes.
+ * QTypes.  It also requires non-NULL list 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 1a34c52..d26a845 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 yet implement support for
- * visiting QAPI structs, alternates, null, or arbitrary QTypes.
+ * visiting QAPI structs, alternates, null, or arbitrary QTypes.  It
+ * also requires non-NULL list 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 2564833..0a9354c 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 yet implement support for
- * visiting QAPI structs, alternates, null, or arbitrary QTypes.
+ * visiting QAPI structs, alternates, null, or arbitrary QTypes.  It
+ * also requires non-NULL list 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 76ea690..622b315 100644
--- a/qapi/qapi-visit-core.c
+++ b/qapi/qapi-visit-core.c
@@ -42,15 +42,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 a08d5a7..6d572cc 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 GenericList, yet */
+    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 16f2f5a..5360744 100644
--- a/qapi/qmp-input-visitor.c
+++ b/qapi/qmp-input-visitor.c
@@ -29,8 +29,6 @@ typedef struct StackObject

     /* If obj is list: tail of list still needing visits */
     const QListEntry *entry;
-    /* If obj is list: true if head is not visited yet */
-    bool first;

     GHashTable *h; /* If obj is dict: remaining keys needing visits */
 } StackObject;
@@ -87,7 +85,6 @@ static QObject *qmp_input_get_object(QmpInputVisitor *qiv,
         assert(!name);

         if (tos->entry) {
-            assert(!tos->first);
             qobj = qlist_entry_obj(tos->entry);
             if (consume) {
                 tos->entry = qlist_next(tos->entry);
@@ -117,7 +114,6 @@ static void qmp_input_push(QmpInputVisitor *qiv, QObject *obj,

     tos->obj = obj;
     tos->entry = entry;
-    tos->first = true;
     tos->h = NULL;

     if (qiv->strict && qobject_type(obj) == QTYPE_QDICT) {
@@ -194,13 +190,17 @@ 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;
@@ -208,28 +208,26 @@ static void qmp_input_start_list(Visitor *v, const char *name, Error **errp)

     entry = qlist_first(qobject_to_qlist(qobj));
     qmp_input_push(qiv, qobj, entry, 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 ecb2005..40657be 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);
 }

@@ -120,7 +116,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();
@@ -129,20 +127,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 797973a..77dd1a7 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;
@@ -125,11 +123,21 @@ 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);
+    Error *err = NULL;

-    parse_str(siv, errp);
+    /* We don't support visits without a GenericList, yet */
+    assert(list);
+
+    parse_str(siv, &err);
+    if (err) {
+        *list = NULL;
+        error_propagate(errp, err);
+        return;
+    }

     siv->cur_range = g_list_first(siv->ranges);
     if (siv->cur_range) {
@@ -137,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) {
@@ -167,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,
@@ -362,6 +363,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..e27e2df 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,          /* start_list() succeeded with multiple elements */

     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 GenericList, yet */
+    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/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt
index 3b2422f..99080db 100644
--- a/docs/qapi-code-gen.txt
+++ b/docs/qapi-code-gen.txt
@@ -912,18 +912,19 @@ 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] 61+ messages in thread

* [Qemu-devel] [PATCH v14 19/19] qapi: Change visit_type_FOO() to no longer return partial objects
  2016-04-08 16:12 [Qemu-devel] [PATCH v14 00/19] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
                   ` (17 preceding siblings ...)
  2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 18/19] qapi: Simplify semantics of visit_next_list() Eric Blake
@ 2016-04-08 16:13 ` Eric Blake
  2016-04-15 14:49   ` Markus Armbruster
  2016-04-15 15:41 ` [Qemu-devel] [PATCH v14 00/19] qapi visitor cleanups (post-introspection cleanups subset E) Markus Armbruster
  19 siblings, 1 reply; 61+ messages in thread
From: Eric Blake @ 2016-04-08 16:13 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.  As no one outside the testsuite was actually
relying on these semantics, it is cleaner to just document and
guarantee that ALL pointer-based visit_type_FOO() functions always
leave a safe value in *obj during an input visitor (either the new
object on success, or NULL if an error is encountered), so callers
can now unconditionally use qapi_free_FOO() to clean up regardless
of whether an error occurred.

The decision is done by enhancing qapi-visit-core to return true
for input visitors (the callbacks themselves do not need
modification); since we've documented that visit_end_* must be
called after any successful visit_start_*, that is a sufficient
point for knowing that something was allocated during start.

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

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

---
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         | 42 ++++++++++++++++++++++++++++++------------
 include/qapi/visitor-impl.h    |  8 +++++---
 scripts/qapi-visit.py          | 25 +++++++++++++------------
 qapi/qapi-visit-core.c         | 41 ++++++++++++++++++++++++++++++++++-------
 tests/test-qmp-commands.c      | 13 ++++++-------
 tests/test-qmp-input-strict.c  | 19 ++++++++-----------
 tests/test-qmp-input-visitor.c | 10 ++--------
 docs/qapi-code-gen.txt         | 10 ++++++++--
 8 files changed, 106 insertions(+), 62 deletions(-)

diff --git a/include/qapi/visitor.h b/include/qapi/visitor.h
index a22a7ce..4cc6d3a 100644
--- a/include/qapi/visitor.h
+++ b/include/qapi/visitor.h
@@ -65,14 +65,16 @@
  * }' if an error is encountered on "value" (or to have the visitor
  * core auto-generate the nicer name).
  *
- * FIXME: At present, input visitors may allocate an incomplete *@obj
- * even when visit_type_FOO() reports an error.  Using an output
- * visitor with an incomplete object has undefined behavior; callers
- * must call qapi_free_FOO() (which uses the dealloc visitor, and
- * safely handles an incomplete object) to avoid a memory leak.
+ * If an error is detected during visit_type_FOO() with an input
+ * visitor, then *@obj will be NULL for pointer types, and left
+ * unchanged for scalar types.  Using an output visitor with an
+ * incomplete object has undefined behavior (other than a special case
+ * for visit_type_str() treating NULL like ""), while the dealloc
+ * visitor safely handles incomplete objects.
  *
- * Likewise, the QAPI object types (structs, unions, and alternates)
- * have a generated function in qapi-visit.h compatible with:
+ * Additionally, the QAPI object types (structs, unions, and
+ * alternates) have a generated function in qapi-visit.h compatible
+ * with:
  *
  * void visit_type_FOO_members(Visitor *v, FOO *obj, Error **errp);
  *
@@ -104,7 +106,6 @@
  *  v = ...obtain input visitor...
  *  visit_type_Foo(v, NULL, &f, &err);
  *  if (err) {
- *      qapi_free_Foo(f);
  *      ...handle error...
  *  } else {
  *      ...use f...
@@ -112,7 +113,6 @@
  *  v = ...obtain input visitor...
  *  visit_type_FooList(v, NULL, &l, &err);
  *  if (err) {
- *      qapi_free_FooList(l);
  *      ...handle error...
  *  } else {
  *      while (l) {
@@ -256,8 +256,14 @@ void visit_check_struct(Visitor *v, Error **errp);
  * even if intermediate processing was skipped due to errors, to allow
  * the backend to release any resources.  Destroying the visitor may
  * behave as if this was implicitly called.
+ *
+ * Returns true if this is an input visitor (that is, an allocation
+ * occurred during visit_start_struct() if obj was non-NULL).  The
+ * caller can use this, along with tracking whether a local error
+ * occurred in the meantime, to decide when to undo allocation before
+ * returning control from a visit_type_FOO() function.
  */
-void visit_end_struct(Visitor *v);
+bool visit_end_struct(Visitor *v);


 /* === Visiting lists */
@@ -313,8 +319,14 @@ GenericList *visit_next_list(Visitor *v, GenericList *tail, size_t size);
  * even if intermediate processing was skipped due to errors, to allow
  * the backend to release any resources.  Destroying the visitor may
  * behave as if this was implicitly called.
+ *
+ * Returns true if this is an input visitor (that is, an allocation
+ * occurred during visit_start_list() if list was non-NULL).  The
+ * caller can use this, along with tracking whether a local error
+ * occurred in the meantime, to decide when to undo allocation before
+ * returning control from a visit_type_FOO() function.
  */
-void visit_end_list(Visitor *v);
+bool visit_end_list(Visitor *v);


 /* === Visiting alternates */
@@ -347,10 +359,16 @@ void visit_start_alternate(Visitor *v, const char *name,
  * the backend to release any resources.  Destroying the visitor may
  * behave as if this was implicitly called.
  *
+ * Returns true if this is an input visitor (that is, an allocation
+ * occurred during visit_start_alternate() if obj was non-NULL).  The
+ * caller can use this, along with tracking whether a local error
+ * occurred in the meantime, to decide when to undo allocation before
+ * returning control from a visit_type_FOO() function.
+ *
  * TODO: Should all the visit_end_* interfaces take obj parameter, so
  * that dealloc visitor need not track what was passed in visit_start?
  */
-void visit_end_alternate(Visitor *v);
+bool visit_end_alternate(Visitor *v);


 /* === Other helpers */
diff --git a/include/qapi/visitor-impl.h b/include/qapi/visitor-impl.h
index 0471465..f113869 100644
--- a/include/qapi/visitor-impl.h
+++ b/include/qapi/visitor-impl.h
@@ -42,7 +42,8 @@ struct Visitor
     /* Optional; intended for input visitors.  */
     void (*check_struct)(Visitor *v, Error **errp);

-    /* Must be set to visit structs.  */
+    /* Must be set to visit structs.  The core takes care of the
+     * return value.  */
     void (*end_struct)(Visitor *v);

     /* Must be set; document if @list may not be NULL.  */
@@ -52,7 +53,7 @@ struct Visitor
     /* Must be set.  */
     GenericList *(*next_list)(Visitor *v, GenericList *tail, size_t size);

-    /* Must be set.  */
+    /* Must be set.  The core takes care of the return value.  */
     void (*end_list)(Visitor *v);

     /* Must be set by input and dealloc visitors to visit alternates;
@@ -61,7 +62,8 @@ struct Visitor
                             GenericAlternate **obj, size_t size,
                             bool promote_int, Error **errp);

-    /* Optional, needed for dealloc visitor.  */
+    /* Optional, needed for dealloc visitor.  The core takes care of
+     * the return value.  */
     void (*end_alternate)(Visitor *v);

     /* Must be set.  */
diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
index 69f0133..8f51d8c 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)
@@ -131,7 +127,10 @@ void visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s **obj, Error
             break;
         }
     }
-    visit_end_list(v);
+    if (visit_end_list(v) && err) {
+        qapi_free_%(c_name)s(*obj);
+        *obj = NULL;
+    }
 out:
     error_propagate(errp, err);
 }
@@ -208,21 +207,20 @@ void visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s **obj, Error
         error_setg(&err, QERR_INVALID_PARAMETER_TYPE, name ? name : "null",
                    "%(name)s");
     }
-    visit_end_alternate(v);
+    if (visit_end_alternate(v) && err) {
+        qapi_free_%(c_name)s(*obj);
+        *obj = NULL;
+    }
 out:
     error_propagate(errp, err);
 }
 ''',
-                 name=name)
+                 name=name, c_name=c_name(name))

     return ret


 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)
@@ -242,7 +240,10 @@ void visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s **obj, Error
     }
     visit_check_struct(v, &err);
 out_obj:
-    visit_end_struct(v);
+    if (visit_end_struct(v) && err) {
+        qapi_free_%(c_name)s(*obj);
+        *obj = NULL;
+    }
 out:
     error_propagate(errp, err);
 }
diff --git a/qapi/qapi-visit-core.c b/qapi/qapi-visit-core.c
index 622b315..8477cc0 100644
--- a/qapi/qapi-visit-core.c
+++ b/qapi/qapi-visit-core.c
@@ -23,11 +23,17 @@
 void visit_start_struct(Visitor *v, const char *name, void **obj,
                         size_t size, Error **errp)
 {
+    Error *err = NULL;
+
     if (obj) {
         assert(size);
         assert(v->type != VISITOR_OUTPUT || *obj);
     }
-    v->start_struct(v, name, obj, size, errp);
+    v->start_struct(v, name, obj, size, &err);
+    if (obj && v->type == VISITOR_INPUT) {
+        assert(err || *obj);
+    }
+    error_propagate(errp, err);
 }

 void visit_check_struct(Visitor *v, Error **errp)
@@ -37,11 +43,13 @@ void visit_check_struct(Visitor *v, Error **errp)
     }
 }

-void visit_end_struct(Visitor *v)
+bool visit_end_struct(Visitor *v)
 {
     v->end_struct(v);
+    return v->type == VISITOR_INPUT;
 }

+
 void visit_start_list(Visitor *v, const char *name, GenericList **list,
                       size_t size, Error **errp)
 {
@@ -55,26 +63,34 @@ GenericList *visit_next_list(Visitor *v, GenericList *tail, size_t size)
     return v->next_list(v, tail, size);
 }

-void visit_end_list(Visitor *v)
+bool visit_end_list(Visitor *v)
 {
     v->end_list(v);
+    return v->type == VISITOR_INPUT;
 }

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

-void visit_end_alternate(Visitor *v)
+bool visit_end_alternate(Visitor *v)
 {
     if (v->end_alternate) {
         v->end_alternate(v);
     }
+    return v->type == VISITOR_INPUT;
 }

 bool visit_optional(Visitor *v, const char *name, bool *present)
@@ -206,12 +222,17 @@ void visit_type_bool(Visitor *v, const char *name, bool *obj, Error **errp)

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

 void visit_type_number(Visitor *v, const char *name, double *obj,
@@ -223,9 +244,15 @@ void visit_type_number(Visitor *v, const char *name, double *obj,

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

 void visit_type_null(Visitor *v, const char *name, Error **errp)
diff --git a/tests/test-qmp-commands.c b/tests/test-qmp-commands.c
index 14a9ebb..d6c494d 100644
--- a/tests/test-qmp-commands.c
+++ b/tests/test-qmp-commands.c
@@ -228,14 +228,13 @@ static void test_dealloc_partial(void)
         QDECREF(ud2_dict);
     }

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

     /* tear down partial object */
     qapi_free_UserDefTwo(ud2);
diff --git a/tests/test-qmp-input-strict.c b/tests/test-qmp-input-strict.c
index d71727e..e039455 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,13 +254,13 @@ static void test_validate_fail_union_flat_no_discrim(TestInputVisitorData *data,

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

 static void test_validate_fail_alternate(TestInputVisitorData *data,
                                          const void *unused)
 {
-    UserDefAlternate *tmp = NULL;
+    UserDefAlternate *tmp;
     Visitor *v;
     Error *err = NULL;

@@ -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 ac8ebea..0b4153a 100644
--- a/tests/test-qmp-input-visitor.c
+++ b/tests/test-qmp-input-visitor.c
@@ -760,18 +760,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 99080db..3b2a27f 100644
--- a/docs/qapi-code-gen.txt
+++ b/docs/qapi-code-gen.txt
@@ -904,7 +904,10 @@ Example:
         }
         visit_check_struct(v, &err);
     out_obj:
-        visit_end_struct(v);
+        if (visit_end_struct(v) && err) {
+            qapi_free_UserDefOne(*obj);
+            *obj = NULL;
+        }
     out:
         error_propagate(errp, err);
     }
@@ -927,7 +930,10 @@ Example:
             }
         }

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

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

* Re: [Qemu-devel] [PATCH v14 01/19] qapi: Consolidate object visitors
  2016-04-08 16:12 ` [Qemu-devel] [PATCH v14 01/19] qapi: Consolidate object visitors Eric Blake
@ 2016-04-13 12:48   ` Markus Armbruster
  2016-04-13 16:13     ` Eric Blake
  0 siblings, 1 reply; 61+ messages in thread
From: Markus Armbruster @ 2016-04-13 12:48 UTC (permalink / raw)
  To: Eric Blake; +Cc: qemu-devel, Michael Roth

Eric Blake <eblake@redhat.com> writes:

> Rather than having two separate visitor callbacks with items
> already broken out, pass the actual QAPISchemaObjectType object
> to the visitor.  This lets the visitor access things like
> type.is_implicit() without needing another parameter, resolving
> a TODO from previous patches.
>
> For convenience and consistency, the 'name' and 'info' parameters
> are still provided, even though they are now redundant with
> 'typ.name' and 'typ.info'.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>

I've made you push this one back in the queue a couple of times, because
there are pros and cons, and the work at hand didn't actually require
the patch.  At some point we need to decide.  Perhaps that point is now.

The patch replaces two somewhat unclean "is implicit" tests by clean
.is_implicit() calls.  Any other use of the change in this series?

Recap of pros and cons:

* The existing interface

      def visit_object_type(self, name, info, base, members, variants):
      def visit_object_type_flat(self, name, info, members, variants):

  is explicit and narrow, but when you need more information, you have
  to add parameters or functions.

* The new interface

     def visit_object_type(self, name, info, typ):

  avoids that, but now its users can access everything.

This patch touches only visiting of objects, because only for objects we
have a TODO.  Should we change the other visit_ methods as well, for
consistency?

>
> ---
> v14: fix testsuite failures
> [posted earlier as part of "easier unboxed visits/qapi implicit types"]
> v6: new patch
> ---
>  scripts/qapi.py                | 10 ++--------
>  scripts/qapi-introspect.py     | 10 +++++-----
>  scripts/qapi-types.py          | 13 ++++++-------
>  scripts/qapi-visit.py          | 12 ++++++------
>  tests/qapi-schema/test-qapi.py | 10 +++++-----
>  5 files changed, 24 insertions(+), 31 deletions(-)
>
> diff --git a/scripts/qapi.py b/scripts/qapi.py
> index b13ae47..4dde43a 100644
> --- a/scripts/qapi.py
> +++ b/scripts/qapi.py
> @@ -808,10 +808,7 @@ class QAPISchemaVisitor(object):
>      def visit_array_type(self, name, info, element_type):
>          pass
>
> -    def visit_object_type(self, name, info, base, members, variants):
> -        pass
> -
> -    def visit_object_type_flat(self, name, info, members, variants):
> +    def visit_object_type(self, name, info, typ):
x>          pass
>
>      def visit_alternate_type(self, name, info, variants):
> @@ -1005,10 +1002,7 @@ class QAPISchemaObjectType(QAPISchemaType):
>          return 'object'
>
>      def visit(self, visitor):
> -        visitor.visit_object_type(self.name, self.info,
> -                                  self.base, self.local_members, self.variants)
> -        visitor.visit_object_type_flat(self.name, self.info,
> -                                       self.members, self.variants)
> +        visitor.visit_object_type(self.name, self.info, self)
>
>
>  class QAPISchemaMember(object):
> diff --git a/scripts/qapi-introspect.py b/scripts/qapi-introspect.py
> index e0f926b..474eafd 100644
> --- a/scripts/qapi-introspect.py
> +++ b/scripts/qapi-introspect.py
> @@ -141,11 +141,11 @@ const char %(c_name)s[] = %(c_string)s;
>          element = self._use_type(element_type)
>          self._gen_json('[' + element + ']', 'array', {'element-type': element})
>
> -    def visit_object_type_flat(self, name, info, members, variants):
> -        obj = {'members': [self._gen_member(m) for m in members]}
> -        if variants:
> -            obj.update(self._gen_variants(variants.tag_member.name,
> -                                          variants.variants))
> +    def visit_object_type(self, name, info, typ):
> +        obj = {'members': [self._gen_member(m) for m in typ.members]}
> +        if typ.variants:
> +            obj.update(self._gen_variants(typ.variants.tag_member.name,
> +                                          typ.variants.variants))
>          self._gen_json(name, 'object', obj)
>
>      def visit_alternate_type(self, name, info, variants):
> diff --git a/scripts/qapi-types.py b/scripts/qapi-types.py
> index 437cf6c..60de4b6 100644
> --- a/scripts/qapi-types.py
> +++ b/scripts/qapi-types.py
> @@ -220,17 +220,16 @@ class QAPISchemaGenTypeVisitor(QAPISchemaVisitor):
>              self.decl += gen_array(name, element_type)
>              self._gen_type_cleanup(name)
>
> -    def visit_object_type(self, name, info, base, members, variants):
> +    def visit_object_type(self, name, info, typ):
>          # Nothing to do for the special empty builtin
>          if name == 'q_empty':
>              return
>          self._fwdecl += gen_fwd_object_or_array(name)
> -        self.decl += gen_object(name, base, members, variants)
> -        if base and not base.is_implicit():
> -            self.decl += gen_upcast(name, base)
> -        # TODO Worth changing the visitor signature, so we could
> -        # directly use rather than repeat type.is_implicit()?
> -        if not name.startswith('q_'):
> +        self.decl += gen_object(name, typ.base, typ.local_members,
> +                                typ.variants)
> +        if typ.base and not typ.base.is_implicit():
> +            self.decl += gen_upcast(name, typ.base)
> +        if not typ.is_implicit():
>              # implicit types won't be directly allocated/freed
>              self._gen_type_cleanup(name)
>
> diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
> index 31d2330..dc8b39c 100644
> --- a/scripts/qapi-visit.py
> +++ b/scripts/qapi-visit.py
> @@ -289,18 +289,18 @@ class QAPISchemaGenVisitVisitor(QAPISchemaVisitor):
>              self.decl += decl
>              self.defn += defn
>
> -    def visit_object_type(self, name, info, base, members, variants):
> +    def visit_object_type(self, name, info, typ):
>          # Nothing to do for the special empty builtin
>          if name == 'q_empty':
>              return
>          self.decl += gen_visit_members_decl(name)
> -        self.defn += gen_visit_object_members(name, base, members, variants)
> -        # TODO Worth changing the visitor signature, so we could
> -        # directly use rather than repeat type.is_implicit()?
> -        if not name.startswith('q_'):
> +        self.defn += gen_visit_object_members(name, typ.base,
> +                                              typ.local_members, typ.variants)
Line gets a bit long.  Hanging indent?  Or change
gen_visit_object_members() to take the type?

> +        if not typ.is_implicit():
>              # only explicit types need an allocating visit
>              self.decl += gen_visit_decl(name)
> -            self.defn += gen_visit_object(name, base, members, variants)
> +            self.defn += gen_visit_object(name, typ.base, typ.local_members,
> +                                          typ.variants)
>
>      def visit_alternate_type(self, name, info, variants):
>          self.decl += gen_visit_decl(name)
> diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py
> index 649677e..ccd1704 100644
> --- a/tests/qapi-schema/test-qapi.py
> +++ b/tests/qapi-schema/test-qapi.py
> @@ -22,14 +22,14 @@ class QAPISchemaTestVisitor(QAPISchemaVisitor):
>          if prefix:
>              print '    prefix %s' % prefix
>
> -    def visit_object_type(self, name, info, base, members, variants):
> +    def visit_object_type(self, name, info, typ):
>          print 'object %s' % name
> -        if base:
> -            print '    base %s' % base.name
> -        for m in members:
> +        if typ.base:
> +            print '    base %s' % typ.base.name
> +        for m in typ.local_members:
>              print '    member %s: %s optional=%s' % \
>                  (m.name, m.type.name, m.optional)
> -        self._print_variants(variants)
> +        self._print_variants(typ.variants)
>
>      def visit_alternate_type(self, name, info, variants):
>          print 'alternate %s' % name

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

* Re: [Qemu-devel] [PATCH v14 02/19] qapi-visit: Add visitor.type classification
  2016-04-08 16:12 ` [Qemu-devel] [PATCH v14 02/19] qapi-visit: Add visitor.type classification Eric Blake
@ 2016-04-13 13:49   ` Markus Armbruster
  2016-04-13 16:23     ` Eric Blake
  0 siblings, 1 reply; 61+ messages in thread
From: Markus Armbruster @ 2016-04-13 13:49 UTC (permalink / raw)
  To: Eric Blake; +Cc: qemu-devel, Michael Roth

Eric Blake <eblake@redhat.com> writes:

> 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.
>
> 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.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
>
> ---
> v14: no change
> v13: no change
> v12: new patch
> ---
>  include/qapi/visitor-impl.h  | 21 ++++++++++++---------
>  qapi/qapi-visit-core.c       | 28 +++++++++++++++-------------
>  qapi/opts-visitor.c          | 12 ++----------
>  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 +-
>  8 files changed, 34 insertions(+), 42 deletions(-)
>
> diff --git a/include/qapi/visitor-impl.h b/include/qapi/visitor-impl.h
> index 2bd8f29..228a2a6 100644
> --- a/include/qapi/visitor-impl.h
> +++ b/include/qapi/visitor-impl.h
> @@ -14,6 +14,15 @@
>
>  #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 */

I think we should explain what makes a visitor an input/output/dealloc
visitor.  Not necessarily in this patch, and not necessarily in this
place, just somewhere.  Right now, the information is scattered.

> @@ -36,10 +45,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 +63,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..f98cf2e 100644
> --- a/qapi/opts-visitor.c
> +++ b/qapi/opts-visitor.c
> @@ -507,6 +507,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 +516,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;
> -

Hmm, this comment doesn't look worthless.  With its statement gone, I
guess it should move somewhere else.  What do you think?

>      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;

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

* Re: [Qemu-devel] [PATCH v14 03/19] qapi: Guarantee NULL obj on input visitor callback error
  2016-04-08 16:12 ` [Qemu-devel] [PATCH v14 03/19] qapi: Guarantee NULL obj on input visitor callback error Eric Blake
@ 2016-04-13 14:04   ` Markus Armbruster
  0 siblings, 0 replies; 61+ messages in thread
From: Markus Armbruster @ 2016-04-13 14:04 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'. While all of them set '*obj'

Suggest to list the methods.  I guess it's start_struct(),
start_alternate(), type_str(), type_any().

> 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.

There are two sane behaviors on failure: (1) don't touch *obj, (2) set
it to null.  I generally like "do nothing on failure", i.e. (1), but I'm
fine with (2) when it's more convenient.  We'll see.

> 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.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
>
> ---
> v14: no change
> v13: no change
> v12: new patch
> ---
>  qapi/opts-visitor.c         | 3 ++-
>  qapi/qmp-input-visitor.c    | 4 ++++
>  qapi/string-input-visitor.c | 1 +
>  3 files changed, 7 insertions(+), 1 deletion(-)
>
> diff --git a/qapi/opts-visitor.c b/qapi/opts-visitor.c
> index f98cf2e..cdb6e42 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");
>      }

If you want to make setting *obj on failure part of the method contract,
you get to write it into the contract.  Looks like you do that later in
this series, when you retrofit the missing contracts.  Okay.

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

* Re: [Qemu-devel] [PATCH v14 05/19] qmp-input: Clean up stack handling
  2016-04-08 16:12 ` [Qemu-devel] [PATCH v14 05/19] qmp-input: Clean up stack handling Eric Blake
@ 2016-04-13 15:53   ` Markus Armbruster
  2016-04-13 16:36     ` Eric Blake
  0 siblings, 1 reply; 61+ messages in thread
From: Markus Armbruster @ 2016-04-13 15:53 UTC (permalink / raw)
  To: Eric Blake; +Cc: qemu-devel, Michael Roth

Eric Blake <eblake@redhat.com> writes:

> 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.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
>
> ---
> v14: no change
> v13: no change
> v12: new patch
> ---
>  qapi/qmp-input-visitor.c | 52 ++++++++++++++++++++++++++++++++++--------------
>  1 file changed, 37 insertions(+), 15 deletions(-)
>
> diff --git a/qapi/qmp-input-visitor.c b/qapi/qmp-input-visitor.c
> index 77cce8b..7ba6d3d 100644
> --- a/qapi/qmp-input-visitor.c
> +++ b/qapi/qmp-input-visitor.c
> @@ -25,16 +25,26 @@
>
>  typedef struct StackObject
>  {
> -    QObject *obj;
> +    QObject *obj; /* Object being visited */
> +
> +    /* If obj is list: NULL if list is at head, otherwise tail of list
> +     * still needing visits */

"still needing visits?"

>      const QListEntry *entry;
> -    GHashTable *h;
> +
> +    GHashTable *h; /* If obj is dict: remaining keys needing visits */

Ah, now I get it.  Swap the two?

>  } StackObject;

The mixture of block comments and comments to the right is a bit
awkward.  What about:

   typedef struct StackObject {
       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] and below correspond to visit_start_struct (nested
> +     * QDict) and visit_start_list (nested QList).  */

I guess what you want to say is stack[1..] record the nesting of
start_struct() ... end_struct() and start_list() ... end_list() pairs.

Comment gets rewritten in PATCH 17, no need to worry too much about it.

>      StackObject stack[QIV_STACK_SIZE];
>      int nb_stack;
> +
> +    /* True to track whether all keys in QDict have been parsed.  */
>      bool strict;

I think @strict switches on rejection of unexpected dictionary keys.
See qmp_input_pop() below.

I dislike the fact that we have two input visitors, and the one with the
obvious name ignores certain errors.  I don't doubt that it has its
uses, but reporting errors should be the default, and ignoring them
should be a conscious decision.  Anyway, not this patch's problem.

>  };
>
> @@ -47,19 +57,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. */

Can we be in a dictionary and not have a name?

Either one...

> +    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.  */

... or two spaces between end of sentence and */.  I like the
old-fashioned double space between sentences myself, but not before */.
Regardless of what I like, we should try to be consistent.

I got used to winged comments, where this issue is moot.

> +    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 +92,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++;
   }


   static void qmp_input_pop(QmpInputVisitor *qiv, Error **errp)
   {
       assert(qiv->nb_stack > 0);

       if (qiv->strict) {
           GHashTable * const top_ht = qiv->stack[qiv->nb_stack - 1].h;
           if (top_ht) {
               GHashTableIter iter;
               const char *key;

               g_hash_table_iter_init(&iter, top_ht);
               if (g_hash_table_iter_next(&iter, (void **)&key, NULL)) {
                   error_setg(errp, QERR_QMP_EXTRA_MEMBER, key);

This looks wrong.  If we have more than one extra members, the second
call error_setg() will fail an assertion in error_setv(), unless errp is
null.

               }
               g_hash_table_unref(top_ht);
           }
       }

       qiv->nb_stack--;
   }

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

* Re: [Qemu-devel] [PATCH v14 06/19] qmp-input: Don't consume input when checking has_member
  2016-04-08 16:12 ` [Qemu-devel] [PATCH v14 06/19] qmp-input: Don't consume input when checking has_member Eric Blake
@ 2016-04-13 16:06   ` Markus Armbruster
  2016-04-13 16:43     ` Eric Blake
  0 siblings, 1 reply; 61+ messages in thread
From: Markus Armbruster @ 2016-04-13 16:06 UTC (permalink / raw)
  To: Eric Blake; +Cc: qemu-devel, Michael Roth

Eric Blake <eblake@redhat.com> writes:

> Commit e8316d7 mistakenly passed consume=true

in qmp_input_optional(), right?

>                                               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.  Tighten up the input visitor to ensure that a member is
> consumed exactly once.  To keep the testsuite happy in the case
> of incomplete input, we have to check whether a member exists
> in the dictionary before trying to remove it.

Sure this is only for the testsuite's benefit?

You fix commit e8316d7's incorrect consume=true, don't you?  Recommend
to mention that explicitly.

>
> Signed-off-by: Eric Blake <eblake@redhat.com>
>
> ---
> v14: no change
> v13: no change
> v12: new patch
> ---
>  qapi/qmp-input-visitor.c | 10 ++++++----
>  1 file changed, 6 insertions(+), 4 deletions(-)
>
> diff --git a/qapi/qmp-input-visitor.c b/qapi/qmp-input-visitor.c
> index 7ba6d3d..cfaf378 100644
> --- a/qapi/qmp-input-visitor.c
> +++ b/qapi/qmp-input-visitor.c
> @@ -65,10 +65,12 @@ static QObject *qmp_input_get_object(QmpInputVisitor *qiv,
>      /* 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);
> +        qobj = qdict_get(qobject_to_qdict(qobj), name);
> +        if (tos->h && consume && qobj) {
> +            bool removed = g_hash_table_remove(tos->h, name);
> +            assert(removed);
>          }
> -        return qdict_get(qobject_to_qdict(qobj), name);
> +        return qobj;
>      }
>
>      /* If we are in the middle of a list, then return the next element
> @@ -338,7 +340,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;

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

* Re: [Qemu-devel] [PATCH v14 01/19] qapi: Consolidate object visitors
  2016-04-13 12:48   ` Markus Armbruster
@ 2016-04-13 16:13     ` Eric Blake
  2016-04-15 15:05       ` Markus Armbruster
  0 siblings, 1 reply; 61+ messages in thread
From: Eric Blake @ 2016-04-13 16:13 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: qemu-devel, Michael Roth

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

On 04/13/2016 06:48 AM, Markus Armbruster wrote:
> Eric Blake <eblake@redhat.com> writes:
> 
>> Rather than having two separate visitor callbacks with items
>> already broken out, pass the actual QAPISchemaObjectType object
>> to the visitor.  This lets the visitor access things like
>> type.is_implicit() without needing another parameter, resolving
>> a TODO from previous patches.
>>
>> For convenience and consistency, the 'name' and 'info' parameters
>> are still provided, even though they are now redundant with
>> 'typ.name' and 'typ.info'.
>>
>> Signed-off-by: Eric Blake <eblake@redhat.com>
> 
> I've made you push this one back in the queue a couple of times, because
> there are pros and cons, and the work at hand didn't actually require
> the patch.  At some point we need to decide.  Perhaps that point is now.
> 
> The patch replaces two somewhat unclean "is implicit" tests by clean
> .is_implicit() calls.  Any other use of the change in this series?

I'm not seeing any other direct simplification in this series.  As a
quick test, I just rebased it to the end of this series with no merge
conflicts, and everything else still compiled and passed without it.
I'm less sure whether any of my later pending series depend on it.

> 
> Recap of pros and cons:
> 
> * The existing interface
> 
>       def visit_object_type(self, name, info, base, members, variants):
>       def visit_object_type_flat(self, name, info, members, variants):
> 
>   is explicit and narrow, but when you need more information, you have
>   to add parameters or functions.
> 
> * The new interface
> 
>      def visit_object_type(self, name, info, typ):
> 
>   avoids that, but now its users can access everything.
> 
> This patch touches only visiting of objects, because only for objects we
> have a TODO.  Should we change the other visit_ methods as well, for
> consistency?

I have a pending patch in subset F (last posted at v6) that adds a 'box'
parameter to visit_event and visit_command:
https://lists.gnu.org/archive/html/qemu-devel/2015-12/msg04397.html
If we change all the other visit_ methods for consistency, then those
methods would directly access command.box and event.box instead of
needing to add a separate parameter.

> 
>>
>> ---
>> v14: fix testsuite failures
>> [posted earlier as part of "easier unboxed visits/qapi implicit types"]
>> v6: new patch

>> +    def visit_object_type(self, name, info, typ):
>>          # Nothing to do for the special empty builtin
>>          if name == 'q_empty':
>>              return
>>          self.decl += gen_visit_members_decl(name)
>> -        self.defn += gen_visit_object_members(name, base, members, variants)
>> -        # TODO Worth changing the visitor signature, so we could
>> -        # directly use rather than repeat type.is_implicit()?
>> -        if not name.startswith('q_'):
>> +        self.defn += gen_visit_object_members(name, typ.base,
>> +                                              typ.local_members, typ.variants)
> Line gets a bit long.  Hanging indent?  Or change
> gen_visit_object_members() to take the type?

gen_visit_object_members() taking the type seems reasonable.

> 
>> +        if not typ.is_implicit():
>>              # only explicit types need an allocating visit
>>              self.decl += gen_visit_decl(name)
>> -            self.defn += gen_visit_object(name, base, members, variants)
>> +            self.defn += gen_visit_object(name, typ.base, typ.local_members,
>> +                                          typ.variants)

but gen_visit_object() still has to take the explosion of data because
it is shared by alternates, where we have to special-case .local_members.

-- 
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] 61+ messages in thread

* Re: [Qemu-devel] [PATCH v14 02/19] qapi-visit: Add visitor.type classification
  2016-04-13 13:49   ` Markus Armbruster
@ 2016-04-13 16:23     ` Eric Blake
  2016-04-15 15:24       ` Markus Armbruster
  0 siblings, 1 reply; 61+ messages in thread
From: Eric Blake @ 2016-04-13 16:23 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: qemu-devel, Michael Roth

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

On 04/13/2016 07:49 AM, Markus Armbruster wrote:
> Eric Blake <eblake@redhat.com> writes:
> 
>> 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.
>>
>> 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.
>>
>> Signed-off-by: Eric Blake <eblake@redhat.com>
>>

>> +/* 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 */
> 
> I think we should explain what makes a visitor an input/output/dealloc
> visitor.  Not necessarily in this patch, and not necessarily in this
> place, just somewhere.  Right now, the information is scattered.

8/19 might be the patch that does just that.  We'll see what you think
when you get further through the review.

>> @@ -514,16 +516,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;
>> -
> 
> Hmm, this comment doesn't look worthless.  With its statement gone, I
> guess it should move somewhere else.  What do you think?

The first half of the comment is fluff.  The second half, about a
looked-up key being removed from unprocessed_opts even if lookup fails,
might be something I can move, but where? Maybe to the visit_type_enum()
in qapi-visit-core.c, stating that an input visitor will visit the
string even if conversion to enum fails? It really only affects what
happens for an input visitor that has a visit_check_struct() (commit
14/19 of the series), but even then, we really only report an input
visit failure regarding unvisited options if there was no earlier error
- but the mere fact that visiting an enum type fails whether the string
was present but not a valid enum value, or whether the string was not
even present, means that we won't be reaching the visit_check_struct()
to even care about errors about unvisited members.

Maybe that means I just move the documentation into the commit message,
and explain why the comment disappears (because a later patch will
guarantee the semantics that we only care about reporting unvisited
members in an input visitor only if all other visits are successful, so
it doesn't matter on earlier failure whether we consumed or did not
consume input).

-- 
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] 61+ messages in thread

* Re: [Qemu-devel] [PATCH v14 05/19] qmp-input: Clean up stack handling
  2016-04-13 15:53   ` Markus Armbruster
@ 2016-04-13 16:36     ` Eric Blake
  2016-04-13 16:40       ` Eric Blake
  2016-04-15 15:27       ` Markus Armbruster
  0 siblings, 2 replies; 61+ messages in thread
From: Eric Blake @ 2016-04-13 16:36 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: qemu-devel, Michael Roth

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

On 04/13/2016 09:53 AM, Markus Armbruster wrote:
> Eric Blake <eblake@redhat.com> writes:
> 
>> 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.
>>
>> Signed-off-by: Eric Blake <eblake@redhat.com>
>>
>> ---

> 
> The mixture of block comments and comments to the right is a bit
> awkward.  What about:
> 
>    typedef struct StackObject {
>        QObject *obj; /* Object being visited */
> 
>        GHashTable *h;              /* if obj is dict: unvisited keys */
>        const QListEntry *entry;    /* if obj is list: unvisited tail */
>    } StackObject;
> 

Works for me.

>>
>>  struct QmpInputVisitor
>>  {
>>      Visitor visitor;
>> +
>> +    /* Stack of objects being visited.  stack[0] is root of visit,
>> +     * stack[1] and below correspond to visit_start_struct (nested
>> +     * QDict) and visit_start_list (nested QList).  */
> 
> I guess what you want to say is stack[1..] record the nesting of
> start_struct() ... end_struct() and start_list() ... end_list() pairs.
> 
> Comment gets rewritten in PATCH 17, no need to worry too much about it.
> 
>>      StackObject stack[QIV_STACK_SIZE];
>>      int nb_stack;
>> +
>> +    /* True to track whether all keys in QDict have been parsed.  */
>>      bool strict;
> 
> I think @strict switches on rejection of unexpected dictionary keys.
> See qmp_input_pop() below.
> 
> I dislike the fact that we have two input visitors, and the one with the
> obvious name ignores certain errors.  I don't doubt that it has its
> uses, but reporting errors should be the default, and ignoring them
> should be a conscious decision.  Anyway, not this patch's problem.

Dan also has a pending patch that reworks it to add yet another
parameter (the ability to take input in string format and auto-convert
it to the correct type).  In that one, he exposes a third method for
choosing which visitor you get, and which then under the hood call a
helper with two boolean flags.  Maybe it's time to just convert all
clients to always passing the parameters they want, along with auditing
whether ignoring extra input is a sane option for that client - but as
you say, it's fine for a separate patch.

>> +
>> +    /* If we have a name, and we're in a dictionary, then return that
>> +     * value. */
> 
> Can we be in a dictionary and not have a name?

The converse happens: we can certainly have a name and not be in a
dictionary, for a top-level visit.  But it has weird semantics until I
clean it up later in 17/19.  For this patch, it was just code motion and
documentation (the 'if (name && qobject_type...)' condition here is the
same pre- and post-patch), where I was just getting rid of a dead 'if
(qobj)'.


> 
> 
>    static void qmp_input_pop(QmpInputVisitor *qiv, Error **errp)
>    {
>        assert(qiv->nb_stack > 0);
> 
>        if (qiv->strict) {
>            GHashTable * const top_ht = qiv->stack[qiv->nb_stack - 1].h;
>            if (top_ht) {
>                GHashTableIter iter;
>                const char *key;
> 
>                g_hash_table_iter_init(&iter, top_ht);
>                if (g_hash_table_iter_next(&iter, (void **)&key, NULL)) {
>                    error_setg(errp, QERR_QMP_EXTRA_MEMBER, key);
> 
> This looks wrong.  If we have more than one extra members, the second
> call error_setg() will fail an assertion in error_setv(), unless errp is
> null.

Whoops - looks like f96493b1 is broken for missing a 'break' statement.
 I'll send that as a separate for-2.6 cleanup that we should pull sooner
rather than later.

-- 
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] 61+ messages in thread

* Re: [Qemu-devel] [PATCH v14 05/19] qmp-input: Clean up stack handling
  2016-04-13 16:36     ` Eric Blake
@ 2016-04-13 16:40       ` Eric Blake
  2016-04-15 15:27       ` Markus Armbruster
  1 sibling, 0 replies; 61+ messages in thread
From: Eric Blake @ 2016-04-13 16:40 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: qemu-devel, Michael Roth

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

On 04/13/2016 10:36 AM, Eric Blake wrote:

>>    static void qmp_input_pop(QmpInputVisitor *qiv, Error **errp)
>>    {
>>        assert(qiv->nb_stack > 0);
>>
>>        if (qiv->strict) {
>>            GHashTable * const top_ht = qiv->stack[qiv->nb_stack - 1].h;
>>            if (top_ht) {
>>                GHashTableIter iter;
>>                const char *key;
>>
>>                g_hash_table_iter_init(&iter, top_ht);
>>                if (g_hash_table_iter_next(&iter, (void **)&key, NULL)) {
>>                    error_setg(errp, QERR_QMP_EXTRA_MEMBER, key);
>>
>> This looks wrong.  If we have more than one extra members, the second
>> call error_setg() will fail an assertion in error_setv(), unless errp is
>> null.
> 
> Whoops - looks like f96493b1 is broken for missing a 'break' statement.
>  I'll send that as a separate for-2.6 cleanup that we should pull sooner
> rather than later.

Scratch that, there's no error.  The outer statement is an 'if', not a
'for', so there's nothing to break out of.  The error gets set at most
once, regardless of whether there are additional unvisited members.

-- 
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] 61+ messages in thread

* Re: [Qemu-devel] [PATCH v14 06/19] qmp-input: Don't consume input when checking has_member
  2016-04-13 16:06   ` Markus Armbruster
@ 2016-04-13 16:43     ` Eric Blake
  2016-04-15 15:28       ` Markus Armbruster
  0 siblings, 1 reply; 61+ messages in thread
From: Eric Blake @ 2016-04-13 16:43 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: qemu-devel, Michael Roth

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

On 04/13/2016 10:06 AM, Markus Armbruster wrote:
> Eric Blake <eblake@redhat.com> writes:
> 
>> Commit e8316d7 mistakenly passed consume=true
> 
> in qmp_input_optional(), right?

yes

> 
>>                                               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.  Tighten up the input visitor to ensure that a member is
>> consumed exactly once.

[1] by fixing qmp_input_optional() to pass consume=false

>>  To keep the testsuite happy in the case
>> of incomplete input, we have to check whether a member exists
>> in the dictionary before trying to remove it.
> 
> Sure this is only for the testsuite's benefit?

The testsuite was the only client that failed under the tighter
semantics; but the better semantics allow later patches to further
improve the code while guaranteeing that clients remain sane.

> 
> You fix commit e8316d7's incorrect consume=true, don't you?  Recommend
> to mention that explicitly.

I thought I did, but I can add wording [1] along those lines.


-- 
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] 61+ messages in thread

* Re: [Qemu-devel] [PATCH v14 07/19] qmp-input: Refactor when list is advanced
  2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 07/19] qmp-input: Refactor when list is advanced Eric Blake
@ 2016-04-13 17:38   ` Markus Armbruster
  2016-04-13 19:58     ` Eric Blake
  0 siblings, 1 reply; 61+ messages in thread
From: Markus Armbruster @ 2016-04-13 17:38 UTC (permalink / raw)
  To: Eric Blake; +Cc: qemu-devel, Michael Roth

Eric Blake <eblake@redhat.com> writes:

> Refactor the code to advance the QListEntry pointer at the point
> where visit_type_FOO() is called, rather than visit_next_list().
> This will allow a future patch to move the visit of the list head
> into visit_start_list(), and get rid of the 'first' flag.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>

After having read the patch, I think the change is from this sequence of
states

           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 one

           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.  Correct?

Now have a look at the typical generated visit:

    void visit_type_FOOList(Visitor *v, const char *name, FOOList **obj, Error **errp)
    {
        Error *err = NULL;
        FOOList *tail;
        size_t size = sizeof(**obj);

        visit_start_list(v, name, (GenericList **)obj, size, &err);
        if (err) {
            goto out;
        }
        for (tail = *obj;
             tail;
             tail = (FOOList *)visit_next_list(v, (GenericList *)tail, size)) {
            visit_type_FOO(v, NULL, &tail->value, &err);
            if (err) {
                break;
            }
        }
        if (visit_end_list(v) && err) {
            qapi_free_FOOList(*obj);
            *obj = NULL;
        }
    out:
        error_propagate(errp, err);
    }

While the loop variable is still stepped in the for's third expression
as it should, the actual stepping below the hood now happens in the loop
body.  I find this mildly confusing.

Perhaps a more natural loop would be

    void visit_type_FOOList(Visitor *v, const char *name, FOOList **obj, Error **errp)
    {
        Error *err = NULL;
        GenericList *tail;
        size_t size = sizeof(**obj);

        visit_start_list(v, name, (GenericList **)obj, size, &err);
        if (err) {
            goto out;
        }
        while ((tail = visit_next_list(v, tail, size))) {
            visit_type_FOO(v, NULL, &((FOOList *)tail)->value, &err);
            if (err) {
                break;
            }
        }
        if (visit_end_list(v) && err) {
            qapi_free_FOOList(*obj);
            *obj = NULL;
        }
    out:
        error_propagate(errp, err);
    }

Might also permit de-duplicating the g_malloc0(size), since we now call
next_list() *before* each iteration instead of *between* iterations.

> ---
> v14: no change
> v13: no change
> v12: new patch
> ---
>  qapi/qmp-input-visitor.c | 40 +++++++++++++++++++++-------------------
>  1 file changed, 21 insertions(+), 19 deletions(-)
>
> diff --git a/qapi/qmp-input-visitor.c b/qapi/qmp-input-visitor.c
> index cfaf378..1820360 100644
> --- a/qapi/qmp-input-visitor.c
> +++ b/qapi/qmp-input-visitor.c
> @@ -27,9 +27,10 @@ typedef struct StackObject
>  {
>      QObject *obj; /* Object being visited */
>
> -    /* If obj is list: NULL if list is at head, otherwise tail of list
> -     * still needing visits */
> +    /* If obj is list: tail of list still needing visits */
>      const QListEntry *entry;
> +    /* If obj is list: true if head is not visited yet */
> +    bool first;

Temporary, PATCH 18 will get rid of it.  Good, because the less state,
the happier I am :)

>
>      GHashTable *h; /* If obj is dict: remaining keys needing visits */
>  } StackObject;
> @@ -77,7 +78,12 @@ static QObject *qmp_input_get_object(QmpInputVisitor *qiv,
       /* If we are in the middle of a list, then return the next element
>       * of the list.  */
>      if (tos->entry) {

Before the patch, the condition matches the comment: tos->entry implies
this is a list and we're not at its head.

After the patch, the condition only implies it's a list, but ...

>          assert(qobject_type(qobj) == QTYPE_QLIST);
> -        return qlist_entry_obj(tos->entry);
> +        assert(!tos->first);

... you assert that "not at its head" can't happen.  Why is that the
case?

> +        qobj = qlist_entry_obj(tos->entry);
> +        if (consume) {
> +            tos->entry = qlist_next(tos->entry);
> +        }
> +        return qobj;

The remainder of the hunk adds the qlist_next() call moved from
qmp_input_next_list(), as advertized by the commit message.  Good.

>      }
>
>      /* Otherwise, we are at the root of the visit or the start of a
> @@ -91,7 +97,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 void qmp_input_push(QmpInputVisitor *qiv, QObject *obj,
> +                           const QListEntry *entry, Error **errp)
>  {
>      GHashTable *h;
>      StackObject *tos = &qiv->stack[qiv->nb_stack];
> @@ -103,7 +110,8 @@ static void qmp_input_push(QmpInputVisitor *qiv, QObject *obj, Error **errp)
>      }
>
>      tos->obj = obj;
> -    tos->entry = NULL;
> +    tos->entry = entry;
> +    tos->first = true;
>      tos->h = NULL;
>
>      if (qiv->strict && qobject_type(obj) == QTYPE_QDICT) {

Here, you change the initial state.  Not explicitly advertized in the
commit message, unlike the change to qlist_next().  I guess that's okay,
but perhaps my ramblings give you ideas on improving the commit message.

I wonder whether we really need the new argument.  Could we do something
like

       if (qobject_type(obj) == QTYPE_QLIST) {
           tos->entry = qlist_first(qobject_to_qlist(obj));
           tos->first = true;
       }

?

> @@ -153,7 +161,7 @@ static void qmp_input_start_struct(Visitor *v, const char *name, void **obj,
>          return;
>      }
>
> -    qmp_input_push(qiv, qobj, &err);
> +    qmp_input_push(qiv, qobj, NULL, &err);

Since @qobj is a dictionary, the StackObject's entry will be unused, so
pass null.  Okay.

>      if (err) {
>          error_propagate(errp, err);
>          return;
> @@ -175,6 +183,7 @@ static void qmp_input_start_list(Visitor *v, const char *name, 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) {
>          error_setg(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null",
> @@ -182,7 +191,8 @@ static void qmp_input_start_list(Visitor *v, const char *name, Error **errp)
>          return;
>      }
>
> -    qmp_input_push(qiv, qobj, errp);
> +    entry = qlist_first(qobject_to_qlist(qobj));
> +    qmp_input_push(qiv, qobj, entry, errp);

@qobj is a list, the StackObject's entry must be initialized to its
first member, so pass that.

>  }
>
>  static GenericList *qmp_input_next_list(Visitor *v, GenericList **list,
> @@ -191,23 +201,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;
>      }

Here, you drop the qlist_next() call.  It moves to
qmp_input_get_object().  Good.

> @@ -382,7 +384,7 @@ QmpInputVisitor *qmp_input_visitor_new(QObject *obj)
>      v->visitor.type_any = qmp_input_type_any;
>      v->visitor.optional = qmp_input_optional;
>
> -    qmp_input_push(v, obj, NULL);
> +    qmp_input_push(v, obj, NULL, NULL);

@obj is the root of the visit.  Can it be a list?

If yes, then StackObject's entry is not its first element.  Why is that
correct?

>      qobject_incref(obj);
>
>      return v;

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

* Re: [Qemu-devel] [PATCH v14 07/19] qmp-input: Refactor when list is advanced
  2016-04-13 17:38   ` Markus Armbruster
@ 2016-04-13 19:58     ` Eric Blake
  0 siblings, 0 replies; 61+ messages in thread
From: Eric Blake @ 2016-04-13 19:58 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: qemu-devel, Michael Roth

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

On 04/13/2016 11:38 AM, Markus Armbruster wrote:
> Eric Blake <eblake@redhat.com> writes:
> 
>> Refactor the code to advance the QListEntry pointer at the point
>> where visit_type_FOO() is called, rather than visit_next_list().
>> This will allow a future patch to move the visit of the list head
>> into visit_start_list(), and get rid of the 'first' flag.
>>
>> Signed-off-by: Eric Blake <eblake@redhat.com>
> 
> After having read the patch, I think the change is from this sequence of
> states
> 
>            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 one
> 
>            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.  Correct?

Yes. I think I know what diagram I'll be adding to my commit message :)

> 
> Now have a look at the typical generated visit:

Which gets greatly simplified later in 18/19 (ie. this patch is doing
some of the refactoring to make 18/19 easier).

> 
>     void visit_type_FOOList(Visitor *v, const char *name, FOOList **obj, Error **errp)
>     {
>         Error *err = NULL;
>         FOOList *tail;
>         size_t size = sizeof(**obj);
> 
>         visit_start_list(v, name, (GenericList **)obj, size, &err);
>         if (err) {
>             goto out;
>         }
>         for (tail = *obj;
>              tail;
>              tail = (FOOList *)visit_next_list(v, (GenericList *)tail, size)) {
>             visit_type_FOO(v, NULL, &tail->value, &err);
>             if (err) {
>                 break;
>             }
>         }
>         if (visit_end_list(v) && err) {
>             qapi_free_FOOList(*obj);
>             *obj = NULL;
>         }
>     out:
>         error_propagate(errp, err);
>     }
> 
> While the loop variable is still stepped in the for's third expression
> as it should, the actual stepping below the hood now happens in the loop
> body.  I find this mildly confusing.

There are TWO lists being stepped.

The generated code is stepping through the GenericList *.  The
under-the-hood code is stepping through QList input.  My refactoring
here is to advance through the under-the-hood QList at the point where
we CONSUME data (similar to how we remove things from QDict at the point
where we CONSUME it) - during visit_type_FOO, and doesn't touch the
GenericList* visits.  Yes, that means the two are slightly out of sync,
until 18/19 fixes the GenericList* visit to be sane (none of this magic
visit the head of the list twice during visit_next_list).

> 
> Perhaps a more natural loop would be
> 
>     void visit_type_FOOList(Visitor *v, const char *name, FOOList **obj, Error **errp)
>     {
>         Error *err = NULL;
>         GenericList *tail;
>         size_t size = sizeof(**obj);
> 
>         visit_start_list(v, name, (GenericList **)obj, size, &err);
>         if (err) {
>             goto out;
>         }
>         while ((tail = visit_next_list(v, tail, size))) {

tail has to be set to something before this while loop will work.

>             visit_type_FOO(v, NULL, &((FOOList *)tail)->value, &err);
>             if (err) {
>                 break;
>             }
>         }

And that's kind of the whole point of 18/19.

>         if (visit_end_list(v) && err) {
>             qapi_free_FOOList(*obj);
>             *obj = NULL;
>         }
>     out:
>         error_propagate(errp, err);
>     }
> 
> Might also permit de-duplicating the g_malloc0(size), since we now call
> next_list() *before* each iteration instead of *between* iterations.

One other wrinkle to think about in both this patch and 18/19: We have
the annoying difference in representation that an empty GenericList* is
the NULL pointer, while an empty QList is an actual object.

>> @@ -77,7 +78,12 @@ static QObject *qmp_input_get_object(QmpInputVisitor *qiv,
>        /* If we are in the middle of a list, then return the next element
>>       * of the list.  */
>>      if (tos->entry) {
> 
> Before the patch, the condition matches the comment: tos->entry implies
> this is a list and we're not at its head.
> 
> After the patch, the condition only implies it's a list, but ...
> 
>>          assert(qobject_type(qobj) == QTYPE_QLIST);
>> -        return qlist_entry_obj(tos->entry);
>> +        assert(!tos->first);
> 
> ... you assert that "not at its head" can't happen.  Why is that the
> case?

Because the only way to consume an element is by calling
visit_type_FOO(), but until 18/19 reorders the generated loop, we don't
call visit_type_FOO() until AFTER our first visit_next_list().  The only
time "at its head" can happen is BEFORE the first visit_next_list().

> 
>> +        qobj = qlist_entry_obj(tos->entry);
>> +        if (consume) {
>> +            tos->entry = qlist_next(tos->entry);
>> +        }
>> +        return qobj;
> 
> The remainder of the hunk adds the qlist_next() call moved from
> qmp_input_next_list(), as advertized by the commit message.  Good.
> 
>>      }
>>
>>      /* Otherwise, we are at the root of the visit or the start of a
>> @@ -91,7 +97,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 void qmp_input_push(QmpInputVisitor *qiv, QObject *obj,
>> +                           const QListEntry *entry, Error **errp)
>>  {
>>      GHashTable *h;
>>      StackObject *tos = &qiv->stack[qiv->nb_stack];
>> @@ -103,7 +110,8 @@ static void qmp_input_push(QmpInputVisitor *qiv, QObject *obj, Error **errp)
>>      }
>>
>>      tos->obj = obj;
>> -    tos->entry = NULL;
>> +    tos->entry = entry;
>> +    tos->first = true;
>>      tos->h = NULL;
>>
>>      if (qiv->strict && qobject_type(obj) == QTYPE_QDICT) {
> 
> Here, you change the initial state.  Not explicitly advertized in the
> commit message, unlike the change to qlist_next().  I guess that's okay,
> but perhaps my ramblings give you ideas on improving the commit message.

Sure.

> 
> I wonder whether we really need the new argument.  Could we do something
> like
> 
>        if (qobject_type(obj) == QTYPE_QLIST) {
>            tos->entry = qlist_first(qobject_to_qlist(obj));
>            tos->first = true;
>        }
> 

I'll try it.  But I seem to recall the reason I did it this way was
because...

> ?
> 
>> @@ -153,7 +161,7 @@ static void qmp_input_start_struct(Visitor *v, const char *name, void **obj,
>>          return;
>>      }
>>
>> -    qmp_input_push(qiv, qobj, &err);
>> +    qmp_input_push(qiv, qobj, NULL, &err);
> 
> Since @qobj is a dictionary, the StackObject's entry will be unused, so
> pass null.  Okay.
> 
>>      if (err) {
>>          error_propagate(errp, err);
>>          return;
>> @@ -175,6 +183,7 @@ static void qmp_input_start_list(Visitor *v, const char *name, 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) {
>>          error_setg(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null",
>> @@ -182,7 +191,8 @@ static void qmp_input_start_list(Visitor *v, const char *name, Error **errp)
>>          return;
>>      }
>>
>> -    qmp_input_push(qiv, qobj, errp);
>> +    entry = qlist_first(qobject_to_qlist(qobj));
>> +    qmp_input_push(qiv, qobj, entry, errp);
> 
> @qobj is a list, the StackObject's entry must be initialized to its
> first member, so pass that.

...in 18/19, the semantics of visit_start_list() change such that we
have to know whether we are visiting an empty QList (we set **obj
differently for an empty vs. non-empty list) - so it was either grab
entry locally and pass it in as a parameter, or let qmp_input_push()
populate entry and then read it after the fact.

>> @@ -382,7 +384,7 @@ QmpInputVisitor *qmp_input_visitor_new(QObject *obj)
>>      v->visitor.type_any = qmp_input_type_any;
>>      v->visitor.optional = qmp_input_optional;
>>
>> -    qmp_input_push(v, obj, NULL);
>> +    qmp_input_push(v, obj, NULL, NULL);
> 
> @obj is the root of the visit.  Can it be a list?
> 
> If yes, then StackObject's entry is not its first element.  Why is that
> correct?

Because when the root of the visit is a QList, you STILL have to call
visit_start_list() before visiting list element members.  It is
visit_start_list() that sets up entry in preparation for visit_next_list().

The root of the QMP input visitor is currently a bit odd; we have weird
semantics where a dictionary as root behaves differently depending on
whether name is NULL or non-NULL.  See 17/19, where I clean that up
later in the series, at which point I get rid of the qmp_input_push()
during visitor initialization.

-- 
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] 61+ messages in thread

* Re: [Qemu-devel] [PATCH v14 08/19] qapi: Document visitor interfaces, add assertions
  2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 08/19] qapi: Document visitor interfaces, add assertions Eric Blake
@ 2016-04-14 15:22   ` Markus Armbruster
  2016-04-26 21:50     ` Eric Blake
  0 siblings, 1 reply; 61+ messages in thread
From: Markus Armbruster @ 2016-04-14 15:22 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>
>
> ---
> 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               | 433 +++++++++++++++++++++++++++++++++--
>  include/qapi/visitor-impl.h          |  42 +++-
>  include/qapi/dealloc-visitor.h       |   4 +
>  include/qapi/opts-visitor.h          |   4 +
>  include/qapi/qmp-input-visitor.h     |   8 +
>  include/qapi/string-input-visitor.h  |   4 +
>  include/qapi/string-output-visitor.h |   4 +
>  qapi/qapi-visit-core.c               |  19 +-
>  8 files changed, 494 insertions(+), 24 deletions(-)
>
> diff --git a/include/qapi/visitor.h b/include/qapi/visitor.h
> index 9a8d010..faf67d2 100644
> --- a/include/qapi/visitor.h
> +++ b/include/qapi/visitor.h
> @@ -16,8 +16,185 @@
>
>  #include "qapi/qmp/qobject.h"
>
> +/*
> + * The QAPI schema defines both a set of C data types, and a QMP wire
> + * format.  A QAPI object is formed as a directed acyclic graph of
> + * QAPI values.

I understand what you're trying to say, but I find the value / object
dichotomy odd.  For me, A QAPI object isn't a DAG, it's a node in a DAG.
Perhaps: "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
> + * point of a QAPI graph; it can also be used for a virtual walk,

Point?  Common graph terminology would be node or edge.

> + * where there is no actual QAPI C struct.
> + *
> + * There are three kinds of visitor classes: input visitors parse an
> + * external representation and allocate the corresponding QAPI graph

"Create" or "build"?  We're not merely allocating...

> + * (QMP, string, and QemuOpts),

The parenthesis applies "external representation, not to "QAPI graph".
Move it there?

> + *                              output visitors take a completed QAPI
> + * graph and generate an external representation (QMP and string), and
> + * the dealloc visitor can take a (partial) QAPI graph and recursively

Suggest (possibly partially constructed).

> + * 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.

Nice directory of resources.

> + *
> + * All QAPI types have a corresponding function with a signature
> + * roughly compatible with this:
> + *
> + * void visit_type_FOO(Visitor *v, const char *name, void *obj, Error **errp);
> + *
> + * except that *@obj is typed correctly as a pointer or scalar,
> + * depending on the type of FOO.

Perhaps we could say

    * void visit_type_FOO(Visitor *v, const char *name, T *obj, Error **errp);
    *
    * where T is FOO for scalar FOOs, 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 usually ignored (although some
> + * visitors require it to be NULL);

The inconsistent behavior at roots is a bit of a wart.  Is it addressed
later in the series?  Shoult we call out the wartiness here?

> +                                    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.
> + *
> + * The visit_type_FOO() functions expect a non-NULL @obj argument;

I'd spell it non-null.

> + * they allocate *@obj during input visits, leave it unchanged on
> + * output visits, and recursively free any resources during a dealloc
> + * visit.  Each function also has an @errp argument which may be NULL
> + * to ignore errors, or point to a NULL Error object on entry for
> + * reporting any errors (such as if a member @name is not present, or
> + * is present but not the specified type).

We don't normally explain the Error ** parameter in any detail, unless
it deviates from common usage (which it really shouldn't).  Perhaps:
"Each function also takes the customary @errp argument.  See
qapi/error.h for details."

> + *
> + * 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).
> + *
> + * 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.
> + *
> + * Likewise, the QAPI object types (structs, unions, and alternates)
> + * have a generated function in qapi-visit.h compatible with:
> + *
> + * void visit_type_FOO_members(Visitor *v, FOO *obj, Error **errp);

This confused me briefly, until I spotted the _members part.  Perhaps we
can better prime the reader:

    * For QAPI object types (structs, unions, and alternates), we
    * additionally have a generated function in qapi-visit.h compatible
    * with

> + *
> + * 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);

Golden opportunity to terminate the already long sentence with a period
;)

>                                                   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, a typical input life cycle is thus:

"Life cycle" suggests the example ranges from creation to destruction.

> + *
> + * <example>
> + *  Foo *f;
> + *  FooList *l;
> + *  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 ... missing?

The example stops before destruction.  You can either append
qapi_free_Foo() to it, or rephrase the introduction not to claim "life
cycle".  Same for list below.

Suggest to split the example right here:

    * </example>
    *
    * For a list, it's:
    * <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 {
> + *      while (l) {

I like to collect sufficiently simple loop control in one place, like this:

           for (; l; l->next) {

> + *          ...use l->value...
> + *          l = l->next;
> + *      }
> + *  }
> + *  ...clean up v...
> + * </example>
> + *
> + * Similarly, a typical output life cycle is:

Here, a full "life cycle" makes the example needlessly artificial,
because...

> + *
> + * <example>
> + *  Foo *f = g_new0(Foo, 1);

... you have to stub out creation here (a /* stub */ comment would make
sense), and ...

> + *  Error *err = NULL;
> + *  Visitor *v;
> + *
> + *  ...populate f...
> + *  v = ...obtain output visitor...
> + *  visit_type_Foo(v, NULL, &f, &err);
> + *  if (err) {
> + *      ...handle error...
> + *  }
> + *  qapi_free_Foo(f);

... you have to have a qapi_free_Foo(f) here.  In contrast, common
output visitor use neither creates nor frees, it simply receives an
object as argument.

... clean up v ... missing?

> + * </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 */
> +

Structuring the header with headlines can be useful, but I haven't seen
the /* === Headline text */ decoration style in QEMU.  We have a few
occurences of /*** Headline text ***/.

>  /* 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,26 +202,117 @@ 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 value @obj (struct or union).

"value" feels redundant.

> + *
> + * @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 must be NULL-initialized,

Not really.  It may be NULL (nothing to initialize), &error_abort
(happens to contain null, but that doesn't matter), &error_fatal
(likewise).  Suggest to drop "must be NULL-initialized," and rely on the
common @errp rules implicitly.

More of the same below.

>                                     and is set if an error is detected
> + * (such as if a member @name is not present, or is 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 must be NULL-initialized, and is set if an error is detected
> + * (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 may
> + * behave as if this was implicitly called.

"may" is of course pretty useless :)  Do we intend to nail it down one
way or the other?

> + */
>  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 must be NULL-initialized, and is set if an error is detected
> + * (such as if a member @name is not present, or is 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 list visit.
> + *
> + * @size represents the size of a linked list node.
> + *
> + * @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.  Must be called in a loop until a
> + * NULL return or error occurs; for each non-NULL return, the caller
> + * must then call the appropriate visit_type_*() for the element type

Does anything bad happen if you elect to stop the loop before NULL
return or error?

If no, then the two last "must" are misleading.

> + * 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 may
> + * behave as if this was implicitly called.

Remark on visit_end_struct()'s "may" applies.

> + */
>  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 with the given @size (at least
> + * sizeof(GenericAlternate)).

I like to limit the first paragraph of the contract to a single line.
Easy enough here: document @size further down, like you do for
visit_start_struct().

When is @size used?  In visit_start_struct(), it's used only by an input
visitor with non-null @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
> @@ -52,8 +320,8 @@ void visit_end_list(Visitor *v);
>   *
>   * 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,46 +330,181 @@ 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 may
> + * behave as if this was implicitly called.

Remark on visit_end_struct()'s "may" applies.

>   *
>   * 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.
> + *
> + * @name expresses the relationship of this enum to its parent
> + * container; see the general description of @name above.
> + *
> + * @strings expresses the mapping between C enum values and QAPI enum
> + * names; it should be the ENUM_lookup array from visit-types.h.
> + *
> + * @obj must be non-NULL.  For input visitors, parse a string and set
> + * *@obj to the numeric value of the enum type using @strings as the
> + * mapping, leaving @obj unchanged on error; other visitors leave
> + * *@obj unchanged.  Output visitors use *@obj to reverse the mapping
> + * and visit the appropriate output string.

Our current input visitors' input and our output visitor's output is
text, but that's detail.  We could theoretically do binary serialization
with a pair of visitors, where the output visitor writes the numeric
encoding, and the input visitor reads it back.  Except we can't anymore
since PATCH 02 baked the text assumption into the visitor core.  Hmm.

I guess baking it into the core is okay.  If we ever need more, we can
bring back the type_enum() callback.

Suggest to relax the contract a bit:

    * @obj must be non-NULL.  Input visitors parse input and set *@obj
    * to the enumeration value, leaving @obj unchanged on error; other
    * visitors leave *@obj unchanged.  Output visitors use *@obj.
    *
    * 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.
    * The core calls visit_type_str() to read / write the strings.

> + */
>  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 "".
> + *
> + * Note that using an output visitor along with a (const char *) value
> + * requires casting away const when computing @obj.

I'm pretty sure the user can figure that out himself with the compiler's
help :)

> + *
> + * 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 value.

Suggest "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.
> + */
>  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 228a2a6..d967b18 100644
> --- a/include/qapi/visitor-impl.h
> +++ b/include/qapi/visitor-impl.h
> @@ -14,6 +14,16 @@
>
>  #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.  */
> @@ -25,18 +35,24 @@ typedef enum VisitorType {
>
>  struct Visitor
>  {
> -    /* Must be set */
> +    /* Must be set to visit structs.  */

I actually prefer to do short one line comments as phrases, not
sentences, i.e. without a period at the end.

>      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.  */

I guess we say /* Must be set to visit FOOs */ when at least one visitor
doesn't implement it.  Else we say /* Must be set */.  Correct?

>      void (*start_list)(Visitor *v, const char *name, Error **errp);
> -    /* Must be set */
> +
> +    /* Must be set.  */
>      GenericList *(*next_list)(Visitor *v, GenericList **list, size_t size);
> -    /* Must be set */
> +
> +    /* 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);
> @@ -44,24 +60,34 @@ struct 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().  */
>      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..7aa8293 100644
> --- a/include/qapi/dealloc-visitor.h
> +++ b/include/qapi/dealloc-visitor.h
> @@ -18,6 +18,10 @@
>
>  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.
> + */

Wing your winged comments at both ends, please.

>  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..2002e37 100644
> --- a/include/qapi/opts-visitor.h
> +++ b/include/qapi/opts-visitor.h
> @@ -29,6 +29,10 @@ 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 yet implement support for visiting
> + * QAPI alternates, numbers (other than integers), or arbitrary
> + * QTypes.

"Yet" suggests this is likely to change.  I doubt it is.

>   */
>  OptsVisitor *opts_visitor_new(const QemuOpts *opts);
>  void opts_visitor_cleanup(OptsVisitor *nv);
> diff --git a/include/qapi/qmp-input-visitor.h b/include/qapi/qmp-input-visitor.h
> index 3ed499c..d75ff98 100644
> --- a/include/qapi/qmp-input-visitor.h
> +++ b/include/qapi/qmp-input-visitor.h
> @@ -19,6 +19,14 @@
>
>  typedef struct QmpInputVisitor QmpInputVisitor;
>
> +/*
> + * FIXME: When visiting a QDict, passing a non-NULL @name for the
> + * first visit_type_FOO() when the root is a QDict will find that
> + * particular key within the QDict.  In the future, the contract may
> + * be tightened to require visit_start_struct() with ignored @name as
> + * the first visit; in the meantime, the first visit is safest when
> + * using NULL for @name.
> + */

Can't grok this right now.  I guess I'm getting tired...

>  QmpInputVisitor *qmp_input_visitor_new(QObject *obj);
>  QmpInputVisitor *qmp_input_visitor_new_strict(QObject *obj);
>
> diff --git a/include/qapi/string-input-visitor.h b/include/qapi/string-input-visitor.h
> index 089243c..4c8d1ea 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 yet implement support for
> + * visiting QAPI structs, alternates, or arbitrary QTypes.
> + */

"Yet" suggests this is likely to change.  Is it?

>  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..094a11e 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 yet implement support for
> + * visiting QAPI structs, alternates, or arbitrary QTypes.
> + */

"Yet" suggests this is likely to change.  Is it?

>  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 3cd7edc..71f3b5d 100644
> --- a/qapi/qapi-visit-core.c
> +++ b/qapi/qapi-visit-core.c
> @@ -23,6 +23,10 @@
>  void visit_start_struct(Visitor *v, const char *name, void **obj,
>                          size_t size, Error **errp)
>  {
> +    if (obj) {
> +        assert(size);

Yes, because the generator puts a dummy member into empty structs.

> +        assert(v->type != VISITOR_OUTPUT || *obj);

Can you point me to the spot in the contract that requires this?

> +    }
>      v->start_struct(v, name, obj, size, errp);
>  }
>
> @@ -74,6 +78,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);
>  }
>
> @@ -121,6 +126,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);
>  }
>
> @@ -168,12 +174,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 {
> @@ -183,22 +191,31 @@ 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);
>  }
>
>  void visit_type_str(Visitor *v, const char *name, char **obj, Error **errp)
>  {
> +    assert(obj);
> +    /* TODO: Fix callers to not pass NULL when they mean "", so that we
> +     * can enable:
> +    assert(v->type != VISITOR_OUTPUT || *obj);
> +     */
>      v->type_str(v, name, obj, errp);
>  }
>
>  void visit_type_number(Visitor *v, const char *name, double *obj,
>                         Error **errp)
>  {
> +    assert(obj);
>      v->type_number(v, name, obj, errp);
>  }
>
>  void visit_type_any(Visitor *v, const char *name, QObject **obj, Error **errp)
>  {
> +    assert(obj);
> +    assert(v->type != VISITOR_OUTPUT || *obj);
>      v->type_any(v, name, obj, errp);
>  }
>
> @@ -252,7 +269,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) {

Unlike last time, my remarks are pretty much only about how to say
things, not about what to say.  Progress!

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

* Re: [Qemu-devel] [PATCH v14 09/19] tests: Add check-qnull
  2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 09/19] tests: Add check-qnull Eric Blake
@ 2016-04-14 16:13   ` Markus Armbruster
  2016-04-14 17:37     ` Markus Armbruster
  0 siblings, 1 reply; 61+ messages in thread
From: Markus Armbruster @ 2016-04-14 16:13 UTC (permalink / raw)
  To: Eric Blake; +Cc: qemu-devel, Michael Roth

Eric Blake <eblake@redhat.com> writes:

> 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 into a more logical place.

into or to?  Those darn prepositions...

> Signed-off-by: Eric Blake <eblake@redhat.com>
>
> ---
> v14: no change
> v13: no change
> v12: new patch
> ---
>  tests/check-qnull.c             | 60 +++++++++++++++++++++++++++++++++++++++++
>  tests/test-qmp-output-visitor.c |  2 --
>  tests/.gitignore                |  1 +
>  tests/Makefile                  |  6 ++++-
>  4 files changed, 66 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..b0fb1e6
> --- /dev/null
> +++ b/tests/check-qnull.c
> @@ -0,0 +1,60 @@
> +/*
> + * 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_);

This one's an implementation detail, but the reference count checks that
follow need to rely on it.  Okay.

> +    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;
> +
> +    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();
> +}
[...]

Nice & simple patch, welcome relief after the big & subtle PATCH 08.

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

* Re: [Qemu-devel] [PATCH v14 10/19] qapi: Add visit_type_null() visitor
  2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 10/19] qapi: Add visit_type_null() visitor Eric Blake
@ 2016-04-14 17:09   ` Markus Armbruster
  0 siblings, 0 replies; 61+ messages in thread
From: Markus Armbruster @ 2016-04-14 17:09 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 the next patch
> will then wire it up into the qmp visitors.  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.
>
> If QAPI had a 'null' type, we'd also have to use visit_type_null()

JSON has a primitive null type with the single value null.

QAPI has the null value.  It doesn't have a (builtin) null type (yet).

> in the generated visitor functions (most likely, we'd use it via
> an alternate type that permits 'null' or an object); we'll create
> that usage when we need it.

Agreed.

> Signed-off-by: Eric Blake <eblake@redhat.com>
>
> ---
> 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          |  2 +-
>  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 +++++
>  7 files changed, 28 insertions(+), 3 deletions(-)
>
> diff --git a/include/qapi/visitor.h b/include/qapi/visitor.h
> index faf67d2..735b389 100644
> --- a/include/qapi/visitor.h
> +++ b/include/qapi/visitor.h
> @@ -507,4 +507,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 d967b18..90bcaec 100644
> --- a/include/qapi/visitor-impl.h
> +++ b/include/qapi/visitor-impl.h
> @@ -86,6 +86,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 2002e37..3fcd327 100644
> --- a/include/qapi/opts-visitor.h
> +++ b/include/qapi/opts-visitor.h
> @@ -31,7 +31,7 @@ typedef struct OptsVisitor OptsVisitor;
>   * - values above INT64_MAX or LLONG_MAX are rejected.
>   *
>   * The Opts input visitor does not yet implement support for visiting
> - * QAPI alternates, numbers (other than integers), or arbitrary
> + * QAPI alternates, numbers (other than integers), null, or arbitrary
>   * QTypes.
>   */
>  OptsVisitor *opts_visitor_new(const QemuOpts *opts);
> diff --git a/include/qapi/string-input-visitor.h b/include/qapi/string-input-visitor.h
> index 4c8d1ea..1a34c52 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 yet implement support for
> - * visiting QAPI structs, alternates, or arbitrary QTypes.
> + * visiting 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 094a11e..2564833 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 yet implement support for
> - * visiting QAPI structs, alternates, or arbitrary QTypes.
> + * visiting 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 71f3b5d..064b9f1 100644
> --- a/qapi/qapi-visit-core.c
> +++ b/qapi/qapi-visit-core.c
> @@ -219,6 +219,11 @@ void visit_type_any(Visitor *v, const char *name, QObject **obj, Error **errp)
>      v->type_any(v, name, obj, errp);
>  }
>
> +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);

Let's stick stub implementations marked FIXME into the QMP visitors in
this patch, and fill them out in the next one.

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

* Re: [Qemu-devel] [PATCH v14 09/19] tests: Add check-qnull
  2016-04-14 16:13   ` Markus Armbruster
@ 2016-04-14 17:37     ` Markus Armbruster
  2016-04-14 18:54       ` Eric Blake
  0 siblings, 1 reply; 61+ messages in thread
From: Markus Armbruster @ 2016-04-14 17:37 UTC (permalink / raw)
  To: Eric Blake; +Cc: qemu-devel, Michael Roth

There's one unusual bit: the other check-q*.c leave the visitor stuff to
the test-*-visitor.c, but this one doesn't.  Hmm.

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

* Re: [Qemu-devel] [PATCH v14 09/19] tests: Add check-qnull
  2016-04-14 17:37     ` Markus Armbruster
@ 2016-04-14 18:54       ` Eric Blake
  0 siblings, 0 replies; 61+ messages in thread
From: Eric Blake @ 2016-04-14 18:54 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: qemu-devel, Michael Roth

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

On 04/14/2016 11:37 AM, Markus Armbruster wrote:
> There's one unusual bit: the other check-q*.c leave the visitor stuff to
> the test-*-visitor.c, but this one doesn't.  Hmm.

Yeah, but the other test-*-visitor don't muck with internal reference
counts of qnull_.  I guess I just call it out more prominently in the
commit message?

-- 
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] 61+ messages in thread

* Re: [Qemu-devel] [PATCH v14 11/19] qmp: Support explicit null during visits
  2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 11/19] qmp: Support explicit null during visits Eric Blake
@ 2016-04-15  8:29   ` Markus Armbruster
  0 siblings, 0 replies; 61+ messages in thread
From: Markus Armbruster @ 2016-04-15  8:29 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), one usage
> is already envisioned: when changing blockdev parameters, it
> would be nice to have a difference between leaving a tuning
> parameter unchanged (omit that parameter from the struct) and
> to explicitly reset the parameter to its default without having
> to know what the default value is (specify the parameter with
> an explicit null value, which will require us to allow a QAPI
> alternate that chooses between the normal value and an explicit
> null).

I'm not sure we'll do it that way.  More cautious would be:

    one possible usage has been discussed: when changing blockdev
    parameters, it would be nice to have a difference between leaving a
    tuning parameter unchanged and to explicitly reset the parameter to
    its default without having to know what the default value is.  One
    way to do that would be omitting the parameter to leave it
    unchanged, and setting it to null to reset to the default.

But I'd simply refrain from going into detail here: shorten the whole
thing to "possible usages have been discussed."

>         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().  Repurpose the
> test_visitor_out_empty test, particularly since a future patch
> will tighten semantics to forbid immediate use of
> qmp_output_get_qobject() without at least one visit_type_*.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
>
> ---
> 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        | 12 ++++++++++++
>  qapi/qmp-output-visitor.c       |  7 +++++++
>  tests/check-qnull.c             | 11 ++++++++++-
>  tests/test-qmp-input-visitor.c  | 16 ++++++++++++++++
>  tests/test-qmp-output-visitor.c |  9 +++++----
>  5 files changed, 50 insertions(+), 5 deletions(-)
>
> diff --git a/qapi/qmp-input-visitor.c b/qapi/qmp-input-visitor.c
> index 1820360..e5b412a 100644
> --- a/qapi/qmp-input-visitor.c
> +++ b/qapi/qmp-input-visitor.c
> @@ -339,6 +339,17 @@ 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)
> +{
> +    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)
>  {
>      QmpInputVisitor *qiv = to_qiv(v);
> @@ -382,6 +393,7 @@ QmpInputVisitor *qmp_input_visitor_new(QObject *obj)
>      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;
>
>      qmp_input_push(v, obj, NULL, NULL);
> diff --git a/qapi/qmp-output-visitor.c b/qapi/qmp-output-visitor.c
> index 1f2a7ba..5681ad3 100644
> --- a/qapi/qmp-output-visitor.c
> +++ b/qapi/qmp-output-visitor.c
> @@ -196,6 +196,12 @@ 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)
> +{
> +    QmpOutputVisitor *qov = to_qov(v);
> +    qmp_output_add_obj(qov, name, qnull());
> +}
> +
>  /* 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);
>
> diff --git a/tests/check-qnull.c b/tests/check-qnull.c
> index b0fb1e6..43a222d 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,15 +39,22 @@ static void qnull_visit_test(void)
>  {
>      QObject *obj;
>      QmpOutputVisitor *qov;
> +    QmpInputVisitor *qiv;
>
>      g_assert(qnull_.refcnt == 1);
> +    obj = qnull();
> +    qiv = qmp_input_visitor_new(obj);
> +    qobject_decref(obj);
> +    visit_type_null(qmp_input_get_visitor(qiv), NULL, &error_abort);

I'd destroy qiv here...

> +
>      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);
> +    qmp_input_visitor_cleanup(qiv);

... instead of here.

>      g_assert(qnull_.refcnt == 1);
>  }
>

See discussion of the split between check-qnull and test-qmp-*-visitor
elsewhere in this thread.

> diff --git a/tests/test-qmp-input-visitor.c b/tests/test-qmp-input-visitor.c
> index 80527eb..d06383a 100644
> --- a/tests/test-qmp-input-visitor.c
> +++ b/tests/test-qmp-input-visitor.c
> @@ -279,6 +279,20 @@ static void test_visitor_in_any(TestInputVisitorData *data,
>      qobject_decref(res);
>  }
>
> +static void test_visitor_in_null(TestInputVisitorData *data,
> +                                 const void *unused)
> +{
> +    Visitor *v;
> +
> +    v = visitor_input_test_init(data, "null");
> +    visit_type_null(v, NULL, &error_abort);

Effectively a duplicate of qnull_visit_test()'s first half.

Unlike the other test_visitor_in_FOO(), this one ignores the result
instead of checking it.

> +
> +    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_end_struct(v, &error_abort);
> +}
> +
>  static void test_visitor_in_union_flat(TestInputVisitorData *data,
>                                         const void *unused)
>  {
> @@ -829,6 +843,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..e656d99 100644
> --- a/tests/test-qmp-output-visitor.c
> +++ b/tests/test-qmp-output-visitor.c
> @@ -477,11 +477,12 @@ 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;
>
> +    visit_type_null(data->ov, NULL, &error_abort);
>      arg = qmp_output_get_qobject(data->qov);
>      g_assert(qobject_type(arg) == QTYPE_QNULL);
>      qobject_decref(arg);

Effectively a duplicate of qnull_visit_test()'s second half, less the
final reference count check.

> @@ -837,8 +838,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);

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

* Re: [Qemu-devel] [PATCH v14 13/19] qmp: Tighten output visitor rules
  2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 13/19] qmp: Tighten output visitor rules Eric Blake
@ 2016-04-15  9:02   ` Markus Armbruster
  2016-04-27  1:29     ` Eric Blake
  0 siblings, 1 reply; 61+ messages in thread
From: Markus Armbruster @ 2016-04-15  9:02 UTC (permalink / raw)
  To: Eric Blake; +Cc: qemu-devel, Michael Roth

Eric Blake <eblake@redhat.com> writes:

> Add a new qmp_output_visitor_reset(), which must be called before
> reusing an exising QmpOutputVisitor on a new root object.  Tighten
> assertions to require that qmp_output_get_qobject() can only be
> called after pairing a visit_end_* for every visit_start_* (rather
> than allowing it to return a partially built object), and that it
> must not 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).
>
> Also, check that we are encountering the expected object or list
> type (both during visit_end*, and also by validating whether 'name'
> was NULL - although the latter may need to change later if we
> improve error messages by always passing in a sensible name).
> This provides protection against mismatched push(struct)/pop(list)
> or push(list)/pop(struct), similar to the qmp-input protection
> added in commit bdd8e6b5.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>

As written, the commit message makes me wonder why we add
qmp_output_visitor_reset() in the same commit.  I think the reason is
the tightened rules make it necessary.  The commit message could make
that clearer by explaining the rule changes first, then point out we
need a reset to comply with the rules.

>
> ---
> 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
> ---
>  include/qapi/qmp-output-visitor.h |  1 +
>  qapi/qmp-output-visitor.c         | 39 +++++++++++++++++++++++----------------
>  tests/test-qmp-output-visitor.c   |  6 ++++++
>  3 files changed, 30 insertions(+), 16 deletions(-)
>
> 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..7c48dfb 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,9 @@ static void qmp_output_add_obj(QmpOutputVisitor *qov, const char *name,
>              qdict_put_obj(qobject_to_qdict(cur), name, value);
>              break;
>          case QTYPE_QLIST:
> +            /* FIXME: assertion needs adjustment if we fix visit-core
> +             * to pass "name.0" style name during lists.  */

visit-core merely passes through whatever name it gets from the client.
Thus, saying we 'fix visit-core to pass "name.0"' is a bit misleading.
What we'd do is change it to require "name.0", then update its users to
comply.

Moreover, this is a note, not a FIXME: nothing is broken here.  The
closest we get to "broken" are the bad error messages, but they're
elsewhere.

I'd simply drop the comment.

> +            assert(!name);

PATCH 08 made this part of the contract.  It also added a bunch of
contract-checking assertions.  Should this one be in PATCH 08, too?

>              qlist_append_obj(qobject_to_qlist(cur), value);
>              break;
>          default:
> @@ -114,7 +116,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 +148,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 +206,15 @@ 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. Will not be NULL.
> + * Caller must use qobject_decref() on the result.  */

Well, it must only if it wants the object freed, but I know what you
mean.

Perhaps:

   /*
    * Finish building, and return the root object.
    * The root object is never null.
    * The caller becomes the object's owner.  It should free it with
    * qobject_decref().
    */
  
>  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_FIRST(&qov->stack));

&& QTAILQ_EMPTY(&qov-stack) would be clearer, wouldn't it?

> +
> +    qobject_incref(qov->root);
> +    return qov->root;
>  }
>
>  Visitor *qmp_output_get_visitor(QmpOutputVisitor *v)
> @@ -221,7 +222,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 +232,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 e656d99..7be33ba 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;

How did you find the places that now need qmp_output_visitor_reset()?

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

* Re: [Qemu-devel] [PATCH v14 14/19] qapi: Split visit_end_struct() into pieces
  2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 14/19] qapi: Split visit_end_struct() into pieces Eric Blake
@ 2016-04-15 11:03   ` Markus Armbruster
  0 siblings, 0 replies; 61+ messages in thread
From: Markus Armbruster @ 2016-04-15 11:03 UTC (permalink / raw)
  To: Eric Blake
  Cc: qemu-devel, Kevin Wolf, Michael Roth, open list:Block layer core,
	Michael S. Tsirkin, Alexander Graf, open list:sPAPR,
	Andreas Färber, David Gibson

Eric Blake <eblake@redhat.com> writes:

> 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 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:
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
>
> ---
> 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         | 20 +++++++++++++++-----
>  include/qapi/visitor-impl.h    |  5 ++++-
>  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       | 34 +++++++++++++++++++---------------
>  qapi/qmp-output-visitor.c      |  2 +-
>  qom/object.c                   |  5 ++---
>  qom/object_interfaces.c        | 16 +++++++---------
>  tests/test-qmp-input-visitor.c |  3 ++-
>  docs/qapi-code-gen.txt         |  8 +++++---
>  16 files changed, 111 insertions(+), 64 deletions(-)
>
> diff --git a/include/qapi/visitor.h b/include/qapi/visitor.h
> index 735b389..4c29722 100644
> --- a/include/qapi/visitor.h
> +++ b/include/qapi/visitor.h
> @@ -182,10 +182,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>
> @@ -238,17 +239,26 @@ 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 must be NULL-initialized, and is set if an error is detected
>   * (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.
> + */
> +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 may
>   * behave 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 90bcaec..d44fcd1 100644
> --- a/include/qapi/visitor-impl.h
> +++ b/include/qapi/visitor-impl.h
> @@ -39,8 +39,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-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 dc8b39c..bfe4a09 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 064b9f1..76ea690 100644
> --- a/qapi/qapi-visit-core.c
> +++ b/qapi/qapi-visit-core.c
> @@ -30,9 +30,16 @@ void visit_start_struct(Visitor *v, const char *name, void **obj,
>      v->start_struct(v, name, obj, size, errp);
>  }
>
> -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 c74101e..47f5f5e 100644
> --- a/hw/virtio/virtio-balloon.c
> +++ b/hw/virtio/virtio-balloon.c
> @@ -137,17 +137,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 cdb6e42..a08d5a7 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) {
> @@ -511,6 +523,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 e5b412a..a94cfa9 100644
> --- a/qapi/qmp-input-visitor.c
> +++ b/qapi/qmp-input-visitor.c
> @@ -124,8 +124,10 @@ static void qmp_input_push(QmpInputVisitor *qiv, QObject *obj,
>  }
>
>
> -static void qmp_input_pop(QmpInputVisitor *qiv, Error **errp)
> +static void qmp_input_check_struct(Visitor *v, Error **errp)
>  {
> +    QmpInputVisitor *qiv = to_qiv(v);
> +
>      assert(qiv->nb_stack > 0);
>
>      if (qiv->strict) {
> @@ -138,6 +140,19 @@ 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);
> +
> +    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);
>          }
>      }
> @@ -172,12 +187,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)
>  {
> @@ -217,12 +226,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,
> @@ -382,10 +385,11 @@ QmpInputVisitor *qmp_input_visitor_new(QObject *obj)
>
>      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 7c48dfb..ecb2005 100644
> --- a/qapi/qmp-output-visitor.c
> +++ b/qapi/qmp-output-visitor.c
> @@ -113,7 +113,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 ab5da35..a0bf6b7 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);
> @@ -69,16 +69,14 @@ Object *user_creatable_add(const QDict *qdict,
>          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;
> +    visit_check_struct(v, &local_err);
> +    if (local_err) {
> +        user_creatable_del(id, NULL);
>      }
>
> +out_visit:
> +    visit_end_struct(v);
> +
>  out:
>      QDECREF(pdict);
>      g_free(id);

I think this could be simplified further:

       visit_check_struct(v, &local_err);
       if (local_err) {
           goto out_visit;
       }

       obj = user_creatable_add_type(type, id, pdict, v, &local_err);

   out_visit:
       visit_end_struct(v);

   out:


> diff --git a/tests/test-qmp-input-visitor.c b/tests/test-qmp-input-visitor.c
> index d06383a..ac8ebea 100644
> --- a/tests/test-qmp-input-visitor.c
> +++ b/tests/test-qmp-input-visitor.c
> @@ -290,7 +290,8 @@ static void test_visitor_in_null(TestInputVisitorData *data,
>      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_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/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt
> index 0e4baff..3b2422f 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);
>      }

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

* Re: [Qemu-devel] [PATCH v14 15/19] qapi-commands: Wrap argument visit in visit_start_struct
  2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 15/19] qapi-commands: Wrap argument visit in visit_start_struct Eric Blake
@ 2016-04-15 11:42   ` Markus Armbruster
  2016-04-26 12:56     ` Eric Blake
  0 siblings, 1 reply; 61+ messages in thread
From: Markus Armbruster @ 2016-04-15 11:42 UTC (permalink / raw)
  To: Eric Blake; +Cc: qemu-devel, Michael Roth

Eric Blake <eblake@redhat.com> writes:

> The qmp-input visitor was playing rather fast and loose: when

I guess (some of) its *users* are playing fast and loose, and the
visitor itself lets them.  The patch's title suggests "some of its
users" == qapi-commands.py.

> visiting a QDict, you could grab members of the root dictionary
> without first pushing into the dict.  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, because the value of 'name'
> should be ignored on the top-level visit.
>
> Generated code grows as follows:
>
> |@@ -515,7 +695,15 @@ 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);
> |+    if (!err) {
> |+        visit_check_struct(v, &err);
> |+    }

Does this find errors that weren't found before?

> |+    visit_end_struct(v);
> |     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);
> |     qapi_dealloc_visitor_cleanup(qdv);
> | }
>
> Note that this change could also make it possible for the
> marshalling code to automatically detect excess input at the top
> level, and not just in nested dictionaries.  However, that checking
> is not currently useful (and we rely on the manual checking in
> monitor.c:qmp_check_client_args() instead) as long as qmp-commands.hx
> uses .args_type, and as long as we have 'name:O' as an arg-type that
> explicitly allows unknown top-level keys because we haven't yet
> converted 'device_add' and 'netdev_add' to introspectible use of
> 'any'.

Hmm, that explains why finding these additional errors wouldn't be
useful.  Good to know, but doesn't quite answer my question.

> Signed-off-by: Eric Blake <eblake@redhat.com>
>
> ---
> v14: rebase to master context
> v13: rebase to earlier patches
> v12: new patch
> ---
>  scripts/qapi-commands.py | 10 ++++++++++
>  1 file changed, 10 insertions(+)
>
> diff --git a/scripts/qapi-commands.py b/scripts/qapi-commands.py
> index b570069..9c1bd25 100644
> --- a/scripts/qapi-commands.py
> +++ b/scripts/qapi-commands.py
> @@ -121,7 +121,15 @@ 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);
> +    if (!err) {
> +        visit_check_struct(v, &err);
> +    }
> +    visit_end_struct(v);
>      if (err) {
>          goto out;
>      }
> @@ -150,7 +158,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);
>      qapi_dealloc_visitor_cleanup(qdv);
>  ''',
>                       c_name=arg_type.c_name())

No visit_check_struct() here.  I think its contract should explicitly
state that you may omit it when you're not interested in errors.

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

* Re: [Qemu-devel] [PATCH v14 16/19] qom: Wrap prop visit in visit_start_struct
  2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 16/19] qom: Wrap prop " Eric Blake
@ 2016-04-15 11:52   ` Markus Armbruster
  0 siblings, 0 replies; 61+ messages in thread
From: Markus Armbruster @ 2016-04-15 11:52 UTC (permalink / raw)
  To: Eric Blake; +Cc: qemu-devel, Andreas Färber

Eric Blake <eblake@redhat.com> writes:

> The qmp-input visitor was playing rather fast and loose: when
> visiting a QDict, you could grab members of the root dictionary
> without first pushing into the dict.  But we are about to tighten
> the input visitor, at which point user_creatable_add_type() MUST
> follow the same paradigms as everyone else, of pushing into the
> struct before grabbing its keys, because the value of 'name'
> should be ignored on the top-level visit.

Aha, this is another second client playing fast and loose.  

>
> 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.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
>
> ---
> v14: no change
> v13: no change
> v12: new patch
> ---
>  qom/object_interfaces.c | 13 ++++++++++++-
>  1 file changed, 12 insertions(+), 1 deletion(-)
>
> diff --git a/qom/object_interfaces.c b/qom/object_interfaces.c
> index a0bf6b7..62ec75a 100644
> --- a/qom/object_interfaces.c
> +++ b/qom/object_interfaces.c
> @@ -118,12 +118,23 @@ 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;
>              }
>          }
> +        if (!local_err) {
> +            visit_check_struct(v, &local_err);

Does this find errors that weren't found before?

> +        }
> +        visit_end_struct(v);
> +        if (local_err) {
> +            goto out;
> +        }
>      }
>
>      object_property_add_child(object_get_objects_root(),

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

* Re: [Qemu-devel] [PATCH v14 17/19] qmp-input: Require struct push to visit members of top dict
  2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 17/19] qmp-input: Require struct push to visit members of top dict Eric Blake
@ 2016-04-15 12:53   ` Markus Armbruster
  0 siblings, 0 replies; 61+ messages in thread
From: Markus Armbruster @ 2016-04-15 12:53 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

I got confused pretty much every time I looked at this code...

> on whether the first visit of a dictionary is to the dictionary
> itself or to one of the members of the dictionary, and we no
> longer have to require the root visit to pass name=NULL.
>
> An audit of all qmp_input_visitor_new* call sites shows that
> the only places where the visit starts directly on a QDict,
> but where the first visit_type_* within the visit had passed
> a non-NULL name, were fixed in the previous two places to
> properly push into the object with visit_start_struct().
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
>
> ---
> v14: no change
> v13: no change
> v12: new patch
> ---
>  include/qapi/visitor.h           |  3 +--
>  include/qapi/qmp-input-visitor.h |  8 ------
>  qapi/qmp-input-visitor.c         | 54 ++++++++++++++++++++++------------------
>  3 files changed, 31 insertions(+), 34 deletions(-)
>
> diff --git a/include/qapi/visitor.h b/include/qapi/visitor.h
> index 4c29722..e1213a3 100644
> --- a/include/qapi/visitor.h
> +++ b/include/qapi/visitor.h
> @@ -47,8 +47,7 @@
>   *
>   * 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 usually ignored (although some
> - * visitors require it to be NULL); when visiting a member of an
> + * 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.
>   *

Should we require a root visit's name to be null?  Not in this patch.

> diff --git a/include/qapi/qmp-input-visitor.h b/include/qapi/qmp-input-visitor.h
> index d75ff98..3ed499c 100644
> --- a/include/qapi/qmp-input-visitor.h
> +++ b/include/qapi/qmp-input-visitor.h
> @@ -19,14 +19,6 @@
>
>  typedef struct QmpInputVisitor QmpInputVisitor;
>
> -/*
> - * FIXME: When visiting a QDict, passing a non-NULL @name for the
> - * first visit_type_FOO() when the root is a QDict will find that
> - * particular key within the QDict.  In the future, the contract may
> - * be tightened to require visit_start_struct() with ignored @name as
> - * the first visit; in the meantime, the first visit is safest when
> - * using NULL for @name.
> - */
>  QmpInputVisitor *qmp_input_visitor_new(QObject *obj);
>  QmpInputVisitor *qmp_input_visitor_new_strict(QObject *obj);
>
> diff --git a/qapi/qmp-input-visitor.c b/qapi/qmp-input-visitor.c
> index a94cfa9..16f2f5a 100644
> --- a/qapi/qmp-input-visitor.c
> +++ b/qapi/qmp-input-visitor.c
> @@ -39,9 +39,11 @@ struct QmpInputVisitor
>  {
>      Visitor visitor;
>
> -    /* Stack of objects being visited.  stack[0] is root of visit,
> -     * stack[1] and below correspond to visit_start_struct (nested
> -     * QDict) and visit_start_list (nested QList).  */
> +    /* 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;
>
> @@ -58,36 +60,40 @@ 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;
> +    StackObject *tos;
> +    QObject *qobj;
>
> +    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);
>          qobj = qdict_get(qobject_to_qdict(qobj), name);
>          if (tos->h && consume && qobj) {
>              bool removed = g_hash_table_remove(tos->h, name);
>              assert(removed);
>          }
> -        return qobj;
> -    }
> -
> -    /* 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);
> -        assert(!tos->first);
> -        qobj = qlist_entry_obj(tos->entry);
> -        if (consume) {
> -            tos->entry = qlist_next(tos->entry);
> +        /* FIXME: assertion needs adjustment if we fix visit-core
> +         * to pass "name.0" style name during lists.  */

My remarks to the same comment in PATCH 13 apply.

Additionally, if we want a comment on name here, is this the correct
patch to add it?

> +        assert(!name);
> +
> +        if (tos->entry) {
> +            assert(!tos->first);
> +            qobj = qlist_entry_obj(tos->entry);
> +            if (consume) {
> +                tos->entry = qlist_next(tos->entry);
> +            }
>          }
> -        return qobj;
>      }
> -
> -    /* Otherwise, we are at the root of the visit or the start of a
> -     * list, and return the object as-is.  */
>      return qobj;
>  }
>

qobj has two roles in this function.  First, it's tos->obj.  Then it
morphs into the function result, which is either the member of tos->obj
named by name, the proper tail of tos->obj pointed to by tos->entry, or
tos->obj itself.  I'd prefer to keep them separate, perhaps like this:

       /* We are in a container; find the next element */
       tos = &qiv->stack[qiv->nb_stack - 1];
       assert(tos->qobj);

       if (qobject_type(tos->qobj) == QTYPE_QDICT) {
           assert(name);
           qobj = qdict_get(qobject_to_qdict(tos->qobj), name);
           if (tos->h && consume && qobj) {
               bool removed = g_hash_table_remove(tos->h, name);
               assert(removed);
           }
       } else {
           assert(qobject_type(tos->qobj) == QTYPE_QLIST);
           assert(!name);

           if (tos->entry) {
               assert(!tos->first);
               qobj = qlist_entry_obj(tos->entry);
               if (consume) {
                   tos->entry = qlist_next(tos->entry);
               }
           } else {
               qobj = tos->qobj;
           }
       }
       return qobj;

Having a second variable to hold tos->qobj would fine, too.

Something else that's bugging me: we step tos->entry here only for the
"not first" case.  For the "first" case, we do it in qmp_input_push().
Could we clean that up?  Not necessarily in this patch or even series.

> @@ -373,7 +379,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);
>  }
>
> @@ -400,7 +406,7 @@ QmpInputVisitor *qmp_input_visitor_new(QObject *obj)
>      v->visitor.type_null = qmp_input_type_null;
>      v->visitor.optional = qmp_input_optional;
>
> -    qmp_input_push(v, obj, NULL, NULL);
> +    v->root = obj;
>      qobject_incref(obj);
>
>      return v;

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

* Re: [Qemu-devel] [PATCH v14 18/19] qapi: Simplify semantics of visit_next_list()
  2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 18/19] qapi: Simplify semantics of visit_next_list() Eric Blake
@ 2016-04-15 14:09   ` Markus Armbruster
  2016-04-22  8:46     ` Markus Armbruster
  0 siblings, 1 reply; 61+ messages in thread
From: Markus Armbruster @ 2016-04-15 14:09 UTC (permalink / raw)
  To: Eric Blake
  Cc: qemu-devel, Alexander Graf, David Gibson, open list:sPAPR, Michael Roth

Eric Blake <eblake@redhat.com> writes:

> We 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

Period here, convert the parenthesis to a sentence.

>               (that is, all other list visitors are generated
> in qapi-visit.c, and share the same paradigm based on a qapi
> FooList type).

The above reviews existing uses.  Next, you show that visit_next_list()
semantics are awkward.  Suggest to start with the awkward semantics, and
only then review existing uses.

>                 What's more, 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.
>
> 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()).

Minor drawback: we now allocate both in start_list() and in next_list().
Minor, because the allocation is trivial.

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

Good idea.

> 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.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
>
> ---
> 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               | 47 +++++++++++++++++++-----------------
>  include/qapi/visitor-impl.h          |  7 +++---
>  scripts/qapi-visit.py                | 18 +++++++-------
>  include/qapi/opts-visitor.h          |  2 +-
>  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             | 32 ++++++++++++------------
>  qapi/qmp-output-visitor.c            | 22 ++++-------------
>  qapi/string-input-visitor.c          | 36 +++++++++++++--------------
>  qapi/string-output-visitor.c         | 41 +++++++++++--------------------
>  docs/qapi-code-gen.txt               | 17 +++++++------
>  15 files changed, 133 insertions(+), 177 deletions(-)
>
> diff --git a/include/qapi/visitor.h b/include/qapi/visitor.h
> index e1213a3..a22a7ce 100644
> --- a/include/qapi/visitor.h
> +++ b/include/qapi/visitor.h
> @@ -165,7 +165,7 @@
>   *  if (err) {
>   *      goto outobj;
>   *  }
> - *  visit_start_list(v, "list", &err);
> + *  visit_start_list(v, "list", NULL, 0, &err);
>   *  if (err) {
>   *      goto outobj;
>   *  }
> @@ -268,40 +268,43 @@ 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 must be NULL-initialized, and is set if an error is detected
>   * (such as if a member @name is not present, or is present but not a
> - * list).
> + * list).  On error, input visitors set *@obj to NULL.

I guess you copied this from visit_start_struct(), which only makes
sense.  Except the thing is called @list here.  More of the same below.

>   *
>   * 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 list visit.
> + * Iterate over a GenericList during a non-virtual list visit.

Is "non-virtual" new in this patch?  If not, is this the right patch to
insert it?

>   *
> - * @size represents the size of a linked list node.
> + * @size represents the size of a linked list node (at least
> + * sizeof(GenericList)).

Likewise.

>   *
> - * @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.  Must be called in a loop until a
> + * @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.  Must be called in a loop until a
>   * NULL return or error occurs; for each non-NULL return, the caller
>   * must then call 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().
> + * 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 d44fcd1..0471465 100644
> --- a/include/qapi/visitor-impl.h
> +++ b/include/qapi/visitor-impl.h
> @@ -45,11 +45,12 @@ 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; document if @list may not be NULL.  */

Too terse, I'm afraid.  Suggest something like "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 bfe4a09..69f0133 100644
> --- a/scripts/qapi-visit.py
> +++ b/scripts/qapi-visit.py
> @@ -117,20 +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;
> +        }
>      }
> -

Keep the blank lines around the loop?

>      visit_end_list(v);
>  out:
>      error_propagate(errp, err);
> diff --git a/include/qapi/opts-visitor.h b/include/qapi/opts-visitor.h
> index 3fcd327..7c9a7e5 100644
> --- a/include/qapi/opts-visitor.h
> +++ b/include/qapi/opts-visitor.h
> @@ -32,7 +32,7 @@ typedef struct OptsVisitor OptsVisitor;
>   *
>   * The Opts input visitor does not yet implement support for visiting
>   * QAPI alternates, numbers (other than integers), null, or arbitrary
> - * QTypes.
> + * QTypes.  It also requires non-NULL list to visit_start_list().

non-null list argument to

>   */
>  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 1a34c52..d26a845 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 yet implement support for
> - * visiting QAPI structs, alternates, null, or arbitrary QTypes.
> + * visiting QAPI structs, alternates, null, or arbitrary QTypes.  It
> + * also requires non-NULL list to visit_start_list().

Likewise.

>   */
>  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 2564833..0a9354c 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 yet implement support for
> - * visiting QAPI structs, alternates, null, or arbitrary QTypes.
> + * visiting QAPI structs, alternates, null, or arbitrary QTypes.  It
> + * also requires non-NULL list to visit_start_list().

Likewise.

>   */
>  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 76ea690..622b315 100644
> --- a/qapi/qapi-visit-core.c
> +++ b/qapi/qapi-visit-core.c
> @@ -42,15 +42,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 a08d5a7..6d572cc 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 GenericList, yet */

without a list

Do we plan to support them?  If not, scratch "yet".

> +    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 */
>  }
>

One step closer to killing the stack.

>  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 16f2f5a..5360744 100644
> --- a/qapi/qmp-input-visitor.c
> +++ b/qapi/qmp-input-visitor.c
> @@ -29,8 +29,6 @@ typedef struct StackObject
>
>      /* If obj is list: tail of list still needing visits */
>      const QListEntry *entry;
> -    /* If obj is list: true if head is not visited yet */
> -    bool first;
>
>      GHashTable *h; /* If obj is dict: remaining keys needing visits */
>  } StackObject;
> @@ -87,7 +85,6 @@ static QObject *qmp_input_get_object(QmpInputVisitor *qiv,
>          assert(!name);
>
>          if (tos->entry) {
> -            assert(!tos->first);
>              qobj = qlist_entry_obj(tos->entry);
>              if (consume) {
>                  tos->entry = qlist_next(tos->entry);
> @@ -117,7 +114,6 @@ static void qmp_input_push(QmpInputVisitor *qiv, QObject *obj,
>
>      tos->obj = obj;
>      tos->entry = entry;
> -    tos->first = true;
>      tos->h = NULL;
>
>      if (qiv->strict && qobject_type(obj) == QTYPE_QDICT) {
> @@ -194,13 +190,17 @@ 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;
> @@ -208,28 +208,26 @@ static void qmp_input_start_list(Visitor *v, const char *name, Error **errp)
>
>      entry = qlist_first(qobject_to_qlist(qobj));
>      qmp_input_push(qiv, qobj, entry, 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 ecb2005..40657be 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);
>  }
>
> @@ -120,7 +116,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();
> @@ -129,20 +127,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 797973a..77dd1a7 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;
> @@ -125,11 +123,21 @@ 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);
> +    Error *err = NULL;
>
> -    parse_str(siv, errp);
> +    /* We don't support visits without a GenericList, yet */

without a list

Do we plan to support them?  If not, scratch "yet".

> +    assert(list);
> +
> +    parse_str(siv, &err);
> +    if (err) {
> +        *list = NULL;
> +        error_propagate(errp, err);
> +        return;
> +    }
>
>      siv->cur_range = g_list_first(siv->ranges);
>      if (siv->cur_range) {
> @@ -137,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) {
> @@ -167,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,
> @@ -362,6 +363,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..e27e2df 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,          /* start_list() succeeded with multiple elements */

In opts-visitor.c, the comment says "opts_next_list() ready to be
called".  Any reason for the difference?

>
>      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 GenericList, yet */

without a list

Do we plan to support them?  If not, scratch "yet".

> +    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/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt
> index 3b2422f..99080db 100644
> --- a/docs/qapi-code-gen.txt
> +++ b/docs/qapi-code-gen.txt
> @@ -912,18 +912,19 @@ 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);

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

* Re: [Qemu-devel] [PATCH v14 19/19] qapi: Change visit_type_FOO() to no longer return partial objects
  2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 19/19] qapi: Change visit_type_FOO() to no longer return partial objects Eric Blake
@ 2016-04-15 14:49   ` Markus Armbruster
  2016-04-27 21:51     ` Eric Blake
  0 siblings, 1 reply; 61+ messages in thread
From: Markus Armbruster @ 2016-04-15 14:49 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.  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.

Hmm, wasn't the "assign null on error" part done in a prior patch?
Checking...  no, only half of it, in PATCH 03: there, we went from "may
store an incomplete object on error" to "store either an incomplete
object or null on error".  Now we go on to just "store null on error".
Correct?

> The decision is done by enhancing qapi-visit-core to return true
> for input visitors (the callbacks themselves do not need
> modification); since we've documented that visit_end_* must be
> called after any successful visit_start_*, that is a sufficient
> point for knowing that something was allocated during start.

I find this sentence a bit confusing.  Let me try:

    To help visitor-agnostic code, such as the generated qapi-visit.c,
    make the visit_end_FOO() return true when something was allocated.
    Easily done in the visitor core, no need to change the callbacks.

But see my comments on the visit_end_FOO() inline.

> 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).

Should this note be in PATCH 03?

The inconsistency isn't pretty, but tolerable if it simplifies things.

> Signed-off-by: Eric Blake <eblake@redhat.com>
>
> ---
> 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         | 42 ++++++++++++++++++++++++++++++------------
>  include/qapi/visitor-impl.h    |  8 +++++---
>  scripts/qapi-visit.py          | 25 +++++++++++++------------
>  qapi/qapi-visit-core.c         | 41 ++++++++++++++++++++++++++++++++++-------
>  tests/test-qmp-commands.c      | 13 ++++++-------
>  tests/test-qmp-input-strict.c  | 19 ++++++++-----------
>  tests/test-qmp-input-visitor.c | 10 ++--------
>  docs/qapi-code-gen.txt         | 10 ++++++++--
>  8 files changed, 106 insertions(+), 62 deletions(-)
>
> diff --git a/include/qapi/visitor.h b/include/qapi/visitor.h
> index a22a7ce..4cc6d3a 100644
> --- a/include/qapi/visitor.h
> +++ b/include/qapi/visitor.h
> @@ -65,14 +65,16 @@
>   * }' if an error is encountered on "value" (or to have the visitor
>   * core auto-generate the nicer name).
>   *
> - * FIXME: At present, input visitors may allocate an incomplete *@obj
> - * even when visit_type_FOO() reports an error.  Using an output
> - * visitor with an incomplete object has undefined behavior; callers
> - * must call qapi_free_FOO() (which uses the dealloc visitor, and
> - * safely handles an incomplete object) to avoid a memory leak.
> + * If an error is detected during visit_type_FOO() with an input
> + * visitor, then *@obj will be NULL for pointer types, and left
> + * unchanged for scalar types.

Okay.

> + *                              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.

Where do the incomplete objects come from now?  I thought this patch
gets rid of them.

>   *
> - * Likewise, the QAPI object types (structs, unions, and alternates)
> - * have a generated function in qapi-visit.h compatible with:
> + * Additionally, the QAPI object types (structs, unions, and
> + * alternates) have a generated function in qapi-visit.h compatible
> + * with:
>   *
>   * void visit_type_FOO_members(Visitor *v, FOO *obj, Error **errp);
>   *

Replaces "Likewise" by "Additionally".  Why not write it this way from
the start?

> @@ -104,7 +106,6 @@
>   *  v = ...obtain input visitor...
>   *  visit_type_Foo(v, NULL, &f, &err);
>   *  if (err) {
> - *      qapi_free_Foo(f);
>   *      ...handle error...
>   *  } else {
>   *      ...use f...
> @@ -112,7 +113,6 @@
>   *  v = ...obtain input visitor...
>   *  visit_type_FooList(v, NULL, &l, &err);
>   *  if (err) {
> - *      qapi_free_FooList(l);
>   *      ...handle error...
>   *  } else {
>   *      while (l) {
> @@ -256,8 +256,14 @@ void visit_check_struct(Visitor *v, Error **errp);
>   * even if intermediate processing was skipped due to errors, to allow
>   * the backend to release any resources.  Destroying the visitor may
>   * behave as if this was implicitly called.
> + *
> + * Returns true if this is an input visitor (that is, an allocation
> + * occurred during visit_start_struct() if obj was non-NULL).  The
> + * caller can use this, along with tracking whether a local error
> + * occurred in the meantime, to decide when to undo allocation before
> + * returning control from a visit_type_FOO() function.
>   */
> -void visit_end_struct(Visitor *v);
> +bool visit_end_struct(Visitor *v);

I generally like functions to return something useful, but not in this
case, because the function name gives you no clue about its value.
Consider:

    if (visit_end_struct(v) && err) {
        qapi_free_FOO(*obj);
        *obj = NULL;
    }

To find out what this means, a reader not familiar with visitors almost
certainly needs to refer to visit_end_struct()'s contract or code.

Compare:

    visit_end_struct(v);
    if (err && v->type == VISITOR_INPUT) {
        qapi_free_FOO(*obj);
        *obj = NULL;
    }

Or:

    visit_end_struct(v);
    if (err && visit_is_input(v)) {
        qapi_free_FOO(*obj);
        *obj = NULL;
    }

>
>
>  /* === Visiting lists */
> @@ -313,8 +319,14 @@ GenericList *visit_next_list(Visitor *v, GenericList *tail, size_t size);
>   * even if intermediate processing was skipped due to errors, to allow
>   * the backend to release any resources.  Destroying the visitor may
>   * behave as if this was implicitly called.
> + *
> + * Returns true if this is an input visitor (that is, an allocation
> + * occurred during visit_start_list() if list was non-NULL).  The
> + * caller can use this, along with tracking whether a local error
> + * occurred in the meantime, to decide when to undo allocation before
> + * returning control from a visit_type_FOO() function.
>   */
> -void visit_end_list(Visitor *v);
> +bool visit_end_list(Visitor *v);
>
>
>  /* === Visiting alternates */
> @@ -347,10 +359,16 @@ void visit_start_alternate(Visitor *v, const char *name,
>   * the backend to release any resources.  Destroying the visitor may
>   * behave as if this was implicitly called.
>   *
> + * Returns true if this is an input visitor (that is, an allocation
> + * occurred during visit_start_alternate() if obj was non-NULL).  The
> + * caller can use this, along with tracking whether a local error
> + * occurred in the meantime, to decide when to undo allocation before
> + * returning control from a visit_type_FOO() function.
> + *
>   * TODO: Should all the visit_end_* interfaces take obj parameter, so
>   * that dealloc visitor need not track what was passed in visit_start?
>   */
> -void visit_end_alternate(Visitor *v);
> +bool visit_end_alternate(Visitor *v);
>
>
>  /* === Other helpers */
> diff --git a/include/qapi/visitor-impl.h b/include/qapi/visitor-impl.h
> index 0471465..f113869 100644
> --- a/include/qapi/visitor-impl.h
> +++ b/include/qapi/visitor-impl.h
> @@ -42,7 +42,8 @@ struct Visitor
>      /* Optional; intended for input visitors.  */
>      void (*check_struct)(Visitor *v, Error **errp);
>
> -    /* Must be set to visit structs.  */
> +    /* Must be set to visit structs.  The core takes care of the
> +     * return value.  */
>      void (*end_struct)(Visitor *v);
>
>      /* Must be set; document if @list may not be NULL.  */
> @@ -52,7 +53,7 @@ struct Visitor
>      /* Must be set.  */
>      GenericList *(*next_list)(Visitor *v, GenericList *tail, size_t size);
>
> -    /* Must be set.  */
> +    /* Must be set.  The core takes care of the return value.  */
>      void (*end_list)(Visitor *v);
>
>      /* Must be set by input and dealloc visitors to visit alternates;
> @@ -61,7 +62,8 @@ struct Visitor
>                              GenericAlternate **obj, size_t size,
>                              bool promote_int, Error **errp);
>
> -    /* Optional, needed for dealloc visitor.  */
> +    /* Optional, needed for dealloc visitor.  The core takes care of
> +     * the return value.  */
>      void (*end_alternate)(Visitor *v);
>
>      /* Must be set.  */
> diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
> index 69f0133..8f51d8c 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)
> @@ -131,7 +127,10 @@ void visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s **obj, Error
>              break;
>          }
>      }
> -    visit_end_list(v);
> +    if (visit_end_list(v) && err) {
> +        qapi_free_%(c_name)s(*obj);
> +        *obj = NULL;
> +    }
>  out:
>      error_propagate(errp, err);
>  }
> @@ -208,21 +207,20 @@ void visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s **obj, Error
>          error_setg(&err, QERR_INVALID_PARAMETER_TYPE, name ? name : "null",
>                     "%(name)s");
>      }
> -    visit_end_alternate(v);
> +    if (visit_end_alternate(v) && err) {
> +        qapi_free_%(c_name)s(*obj);
> +        *obj = NULL;
> +    }
>  out:
>      error_propagate(errp, err);
>  }
>  ''',
> -                 name=name)
> +                 name=name, c_name=c_name(name))
>
>      return ret
>
>
>  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)
> @@ -242,7 +240,10 @@ void visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s **obj, Error
>      }
>      visit_check_struct(v, &err);
>  out_obj:
> -    visit_end_struct(v);
> +    if (visit_end_struct(v) && err) {
> +        qapi_free_%(c_name)s(*obj);
> +        *obj = NULL;
> +    }
>  out:
>      error_propagate(errp, err);
>  }
> diff --git a/qapi/qapi-visit-core.c b/qapi/qapi-visit-core.c
> index 622b315..8477cc0 100644
> --- a/qapi/qapi-visit-core.c
> +++ b/qapi/qapi-visit-core.c
> @@ -23,11 +23,17 @@
>  void visit_start_struct(Visitor *v, const char *name, void **obj,
>                          size_t size, Error **errp)
>  {
> +    Error *err = NULL;
> +
>      if (obj) {
>          assert(size);
>          assert(v->type != VISITOR_OUTPUT || *obj);
>      }
> -    v->start_struct(v, name, obj, size, errp);
> +    v->start_struct(v, name, obj, size, &err);
> +    if (obj && v->type == VISITOR_INPUT) {
> +        assert(err || *obj);
> +    }
> +    error_propagate(errp, err);

Sure this belongs to this patch?  More of the same below.

>  }
>
>  void visit_check_struct(Visitor *v, Error **errp)
> @@ -37,11 +43,13 @@ void visit_check_struct(Visitor *v, Error **errp)
>      }
>  }
>
> -void visit_end_struct(Visitor *v)
> +bool visit_end_struct(Visitor *v)
>  {
>      v->end_struct(v);
> +    return v->type == VISITOR_INPUT;
>  }
>
> +
>  void visit_start_list(Visitor *v, const char *name, GenericList **list,
>                        size_t size, Error **errp)
>  {
> @@ -55,26 +63,34 @@ GenericList *visit_next_list(Visitor *v, GenericList *tail, size_t size)
>      return v->next_list(v, tail, size);
>  }
>
> -void visit_end_list(Visitor *v)
> +bool visit_end_list(Visitor *v)
>  {
>      v->end_list(v);
> +    return v->type == VISITOR_INPUT;
>  }
>
>  void visit_start_alternate(Visitor *v, const char *name,
>                             GenericAlternate **obj, size_t size,
>                             bool promote_int, Error **errp)
>  {
> +    Error *err = NULL;
> +
>      assert(obj && size >= sizeof(GenericAlternate));
>      if (v->start_alternate) {
> -        v->start_alternate(v, name, obj, size, promote_int, errp);
> +        v->start_alternate(v, name, obj, size, promote_int, &err);
> +        if (v->type == VISITOR_INPUT) {
> +            assert(err || *obj);
> +        }
> +        error_propagate(errp, err);
>      }
>  }
>
> -void visit_end_alternate(Visitor *v)
> +bool visit_end_alternate(Visitor *v)
>  {
>      if (v->end_alternate) {
>          v->end_alternate(v);
>      }
> +    return v->type == VISITOR_INPUT;
>  }
>
>  bool visit_optional(Visitor *v, const char *name, bool *present)
> @@ -206,12 +222,17 @@ void visit_type_bool(Visitor *v, const char *name, bool *obj, Error **errp)
>
>  void visit_type_str(Visitor *v, const char *name, char **obj, Error **errp)
>  {
> +    Error *err = NULL;
>      assert(obj);
>      /* TODO: Fix callers to not pass NULL when they mean "", so that we
>       * can enable:
>      assert(v->type != VISITOR_OUTPUT || *obj);
>       */
> -    v->type_str(v, name, obj, errp);
> +    v->type_str(v, name, obj, &err);
> +    if (v->type == VISITOR_INPUT) {
> +        assert(err || *obj);
> +    }
> +    error_propagate(errp, err);
>  }
>
>  void visit_type_number(Visitor *v, const char *name, double *obj,
> @@ -223,9 +244,15 @@ void visit_type_number(Visitor *v, const char *name, double *obj,
>
>  void visit_type_any(Visitor *v, const char *name, QObject **obj, Error **errp)
>  {
> +    Error *err = NULL;
> +
>      assert(obj);
>      assert(v->type != VISITOR_OUTPUT || *obj);
> -    v->type_any(v, name, obj, errp);
> +    v->type_any(v, name, obj, &err);
> +    if (v->type == VISITOR_INPUT) {
> +        assert(err || *obj);
> +    }
> +    error_propagate(errp, err);
>  }
>
>  void visit_type_null(Visitor *v, const char *name, Error **errp)
> diff --git a/tests/test-qmp-commands.c b/tests/test-qmp-commands.c
> index 14a9ebb..d6c494d 100644
> --- a/tests/test-qmp-commands.c
> +++ b/tests/test-qmp-commands.c
> @@ -228,14 +228,13 @@ static void test_dealloc_partial(void)
>          QDECREF(ud2_dict);
>      }
>
> -    /* verify partial success */
> -    assert(ud2 != NULL);
> -    assert(ud2->string0 != NULL);
> -    assert(strcmp(ud2->string0, text) == 0);
> -    assert(ud2->dict1 == NULL);
> -
> -    /* confirm & release construction error */
> +    /* verify that visit_type_XXX() cleans up properly on error */
>      error_free_or_abort(&err);
> +    assert(!ud2);
> +
> +    /* Manually create a partial object, leaving ud2->dict1 at NULL */
> +    ud2 = g_new0(UserDefTwo, 1);
> +    ud2->string0 = g_strdup(text);
>
>      /* tear down partial object */
>      qapi_free_UserDefTwo(ud2);

Somewhat redundant now, because the free hidden in the failed visit
already covers partial object teardown.  I don't mind.

> diff --git a/tests/test-qmp-input-strict.c b/tests/test-qmp-input-strict.c
> index d71727e..e039455 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,13 +254,13 @@ static void test_validate_fail_union_flat_no_discrim(TestInputVisitorData *data,
>
>      visit_type_UserDefFlatUnion2(v, NULL, &tmp, &err);
>      error_free_or_abort(&err);
> -    qapi_free_UserDefFlatUnion2(tmp);
> +    g_assert(!tmp);
>  }
>
>  static void test_validate_fail_alternate(TestInputVisitorData *data,
>                                           const void *unused)
>  {
> -    UserDefAlternate *tmp = NULL;
> +    UserDefAlternate *tmp;

Did this initialization become dead in PATCH 03 already?

>      Visitor *v;
>      Error *err = NULL;
>
> @@ -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 ac8ebea..0b4153a 100644
> --- a/tests/test-qmp-input-visitor.c
> +++ b/tests/test-qmp-input-visitor.c
> @@ -760,18 +760,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 99080db..3b2a27f 100644
> --- a/docs/qapi-code-gen.txt
> +++ b/docs/qapi-code-gen.txt
> @@ -904,7 +904,10 @@ Example:
>          }
>          visit_check_struct(v, &err);
>      out_obj:
> -        visit_end_struct(v);
> +        if (visit_end_struct(v) && err) {
> +            qapi_free_UserDefOne(*obj);
> +            *obj = NULL;
> +        }
>      out:
>          error_propagate(errp, err);
>      }
> @@ -927,7 +930,10 @@ Example:
>              }
>          }
>
> -        visit_end_list(v);
> +        if (visit_end_list(v) && err) {
> +            qapi_free_UserDefOneList(*obj);
> +            *obj = NULL;
> +        }
>      out:
>          error_propagate(errp, err);
>      }

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

* Re: [Qemu-devel] [PATCH v14 01/19] qapi: Consolidate object visitors
  2016-04-13 16:13     ` Eric Blake
@ 2016-04-15 15:05       ` Markus Armbruster
  0 siblings, 0 replies; 61+ messages in thread
From: Markus Armbruster @ 2016-04-15 15:05 UTC (permalink / raw)
  To: Eric Blake; +Cc: qemu-devel, Michael Roth

Eric Blake <eblake@redhat.com> writes:

> On 04/13/2016 06:48 AM, Markus Armbruster wrote:
>> Eric Blake <eblake@redhat.com> writes:
>> 
>>> Rather than having two separate visitor callbacks with items
>>> already broken out, pass the actual QAPISchemaObjectType object
>>> to the visitor.  This lets the visitor access things like
>>> type.is_implicit() without needing another parameter, resolving
>>> a TODO from previous patches.
>>>
>>> For convenience and consistency, the 'name' and 'info' parameters
>>> are still provided, even though they are now redundant with
>>> 'typ.name' and 'typ.info'.
>>>
>>> Signed-off-by: Eric Blake <eblake@redhat.com>
>> 
>> I've made you push this one back in the queue a couple of times, because
>> there are pros and cons, and the work at hand didn't actually require
>> the patch.  At some point we need to decide.  Perhaps that point is now.
>> 
>> The patch replaces two somewhat unclean "is implicit" tests by clean
>> .is_implicit() calls.  Any other use of the change in this series?
>
> I'm not seeing any other direct simplification in this series.  As a
> quick test, I just rebased it to the end of this series with no merge
> conflicts, and everything else still compiled and passed without it.
> I'm less sure whether any of my later pending series depend on it.
>
>> 
>> Recap of pros and cons:
>> 
>> * The existing interface
>> 
>>       def visit_object_type(self, name, info, base, members, variants):
>>       def visit_object_type_flat(self, name, info, members, variants):
>> 
>>   is explicit and narrow, but when you need more information, you have
>>   to add parameters or functions.
>> 
>> * The new interface
>> 
>>      def visit_object_type(self, name, info, typ):
>> 
>>   avoids that, but now its users can access everything.
>> 
>> This patch touches only visiting of objects, because only for objects we
>> have a TODO.  Should we change the other visit_ methods as well, for
>> consistency?
>
> I have a pending patch in subset F (last posted at v6) that adds a 'box'
> parameter to visit_event and visit_command:
> https://lists.gnu.org/archive/html/qemu-devel/2015-12/msg04397.html
> If we change all the other visit_ methods for consistency, then those
> methods would directly access command.box and event.box instead of
> needing to add a separate parameter.

Let's move this patch into subset F (should be the next series, as this
one is E), and figure it out there.

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

* Re: [Qemu-devel] [PATCH v14 02/19] qapi-visit: Add visitor.type classification
  2016-04-13 16:23     ` Eric Blake
@ 2016-04-15 15:24       ` Markus Armbruster
  0 siblings, 0 replies; 61+ messages in thread
From: Markus Armbruster @ 2016-04-15 15:24 UTC (permalink / raw)
  To: Eric Blake; +Cc: qemu-devel, Michael Roth

Eric Blake <eblake@redhat.com> writes:

> On 04/13/2016 07:49 AM, Markus Armbruster wrote:
>> Eric Blake <eblake@redhat.com> writes:
>> 
>>> 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.
>>>
>>> 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.
>>>
>>> Signed-off-by: Eric Blake <eblake@redhat.com>
>>>
>
>>> +/* 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 */
>> 
>> I think we should explain what makes a visitor an input/output/dealloc
>> visitor.  Not necessarily in this patch, and not necessarily in this
>> place, just somewhere.  Right now, the information is scattered.
>
> 8/19 might be the patch that does just that.  We'll see what you think
> when you get further through the review.
>
>>> @@ -514,16 +516,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;
>>> -
>> 
>> Hmm, this comment doesn't look worthless.  With its statement gone, I
>> guess it should move somewhere else.  What do you think?
>
> The first half of the comment is fluff.  The second half, about a
> looked-up key being removed from unprocessed_opts even if lookup fails,
> might be something I can move, but where? Maybe to the visit_type_enum()
> in qapi-visit-core.c, stating that an input visitor will visit the
> string even if conversion to enum fails? It really only affects what
> happens for an input visitor that has a visit_check_struct() (commit
> 14/19 of the series), but even then, we really only report an input
> visit failure regarding unvisited options if there was no earlier error
> - but the mere fact that visiting an enum type fails whether the string
> was present but not a valid enum value, or whether the string was not
> even present, means that we won't be reaching the visit_check_struct()
> to even care about errors about unvisited members.
>
> Maybe that means I just move the documentation into the commit message,
> and explain why the comment disappears (because a later patch will
> guarantee the semantics that we only care about reporting unvisited
> members in an input visitor only if all other visits are successful, so
> it doesn't matter on earlier failure whether we consumed or did not
> consume input).

The second half of the comment points out that visiting an enum can have
its side effect on unprocessed_opts even when the visit fails, namely
when input_type_enum() fails after visit_type_str() succeded.  Works as
long as visit_type_str() has no "unwelcome" side effects.

Your patch moves the enum handling into the visitor core.  I think to
replace the comment, we need two:

1. In the visitor implementation contract: state that visit_type_str()
may be called for enums, and that the enum visit may fail even when
visit_type_str() succeeds.  No need to go into input vs. output detail,
I think.

2. In opts_type_str(): point out that processed() gets called even
though the visit may still fail if its an enum, but it should be
harmless.

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

* Re: [Qemu-devel] [PATCH v14 05/19] qmp-input: Clean up stack handling
  2016-04-13 16:36     ` Eric Blake
  2016-04-13 16:40       ` Eric Blake
@ 2016-04-15 15:27       ` Markus Armbruster
  1 sibling, 0 replies; 61+ messages in thread
From: Markus Armbruster @ 2016-04-15 15:27 UTC (permalink / raw)
  To: Eric Blake; +Cc: qemu-devel, Michael Roth

Eric Blake <eblake@redhat.com> writes:

> On 04/13/2016 09:53 AM, Markus Armbruster wrote:
>> Eric Blake <eblake@redhat.com> writes:
>> 
>>> 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.
>>>
>>> Signed-off-by: Eric Blake <eblake@redhat.com>
>>>
>>> ---
>
>> 
>> The mixture of block comments and comments to the right is a bit
>> awkward.  What about:
>> 
>>    typedef struct StackObject {
>>        QObject *obj; /* Object being visited */
>> 
>>        GHashTable *h;              /* if obj is dict: unvisited keys */
>>        const QListEntry *entry;    /* if obj is list: unvisited tail */
>>    } StackObject;
>> 
>
> Works for me.
>
>>>
>>>  struct QmpInputVisitor
>>>  {
>>>      Visitor visitor;
>>> +
>>> +    /* Stack of objects being visited.  stack[0] is root of visit,
>>> +     * stack[1] and below correspond to visit_start_struct (nested
>>> +     * QDict) and visit_start_list (nested QList).  */
>> 
>> I guess what you want to say is stack[1..] record the nesting of
>> start_struct() ... end_struct() and start_list() ... end_list() pairs.
>> 
>> Comment gets rewritten in PATCH 17, no need to worry too much about it.
>> 
>>>      StackObject stack[QIV_STACK_SIZE];
>>>      int nb_stack;
>>> +
>>> +    /* True to track whether all keys in QDict have been parsed.  */
>>>      bool strict;
>> 
>> I think @strict switches on rejection of unexpected dictionary keys.
>> See qmp_input_pop() below.
>> 
>> I dislike the fact that we have two input visitors, and the one with the
>> obvious name ignores certain errors.  I don't doubt that it has its
>> uses, but reporting errors should be the default, and ignoring them
>> should be a conscious decision.  Anyway, not this patch's problem.
>
> Dan also has a pending patch that reworks it to add yet another
> parameter (the ability to take input in string format and auto-convert
> it to the correct type).  In that one, he exposes a third method for
> choosing which visitor you get, and which then under the hood call a
> helper with two boolean flags.  Maybe it's time to just convert all
> clients to always passing the parameters they want, along with auditing
> whether ignoring extra input is a sane option for that client - but as
> you say, it's fine for a separate patch.
>
>>> +
>>> +    /* If we have a name, and we're in a dictionary, then return that
>>> +     * value. */
>> 
>> Can we be in a dictionary and not have a name?
>
> The converse happens: we can certainly have a name and not be in a
> dictionary, for a top-level visit.  But it has weird semantics until I
> clean it up later in 17/19.  For this patch, it was just code motion and
> documentation (the 'if (name && qobject_type...)' condition here is the
> same pre- and post-patch), where I was just getting rid of a dead 'if
> (qobj)'.

So "in dictionary" implies "have a name".  Why do we bother to test
@name then?

>> 
>> 
>>    static void qmp_input_pop(QmpInputVisitor *qiv, Error **errp)
>>    {
>>        assert(qiv->nb_stack > 0);
>> 
>>        if (qiv->strict) {
>>            GHashTable * const top_ht = qiv->stack[qiv->nb_stack - 1].h;
>>            if (top_ht) {
>>                GHashTableIter iter;
>>                const char *key;
>> 
>>                g_hash_table_iter_init(&iter, top_ht);
>>                if (g_hash_table_iter_next(&iter, (void **)&key, NULL)) {
>>                    error_setg(errp, QERR_QMP_EXTRA_MEMBER, key);
>> 
>> This looks wrong.  If we have more than one extra members, the second
>> call error_setg() will fail an assertion in error_setv(), unless errp is
>> null.
>
> Whoops - looks like f96493b1 is broken for missing a 'break' statement.
>  I'll send that as a separate for-2.6 cleanup that we should pull sooner
> rather than later.

False alarm, as you noticed.

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

* Re: [Qemu-devel] [PATCH v14 06/19] qmp-input: Don't consume input when checking has_member
  2016-04-13 16:43     ` Eric Blake
@ 2016-04-15 15:28       ` Markus Armbruster
  0 siblings, 0 replies; 61+ messages in thread
From: Markus Armbruster @ 2016-04-15 15:28 UTC (permalink / raw)
  To: Eric Blake; +Cc: qemu-devel, Michael Roth

Eric Blake <eblake@redhat.com> writes:

> On 04/13/2016 10:06 AM, Markus Armbruster wrote:
>> Eric Blake <eblake@redhat.com> writes:
>> 
>>> Commit e8316d7 mistakenly passed consume=true
>> 
>> in qmp_input_optional(), right?
>
> yes
>
>> 
>>>                                               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.  Tighten up the input visitor to ensure that a member is
>>> consumed exactly once.
>
> [1] by fixing qmp_input_optional() to pass consume=false
>
>>>  To keep the testsuite happy in the case
>>> of incomplete input, we have to check whether a member exists
>>> in the dictionary before trying to remove it.
>> 
>> Sure this is only for the testsuite's benefit?
>
> The testsuite was the only client that failed under the tighter
> semantics; but the better semantics allow later patches to further
> improve the code while guaranteeing that clients remain sane.

Do we know that non-testsuite code can't fail for some input?

>> 
>> You fix commit e8316d7's incorrect consume=true, don't you?  Recommend
>> to mention that explicitly.
>
> I thought I did, but I can add wording [1] along those lines.

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

* Re: [Qemu-devel] [PATCH v14 00/19] qapi visitor cleanups (post-introspection cleanups subset E)
  2016-04-08 16:12 [Qemu-devel] [PATCH v14 00/19] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
                   ` (18 preceding siblings ...)
  2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 19/19] qapi: Change visit_type_FOO() to no longer return partial objects Eric Blake
@ 2016-04-15 15:41 ` Markus Armbruster
  19 siblings, 0 replies; 61+ messages in thread
From: Markus Armbruster @ 2016-04-15 15:41 UTC (permalink / raw)
  To: Eric Blake; +Cc: qemu-devel

Eric Blake <eblake@redhat.com> writes:

> This is now 2.7 material (it is too invasive for hard freeze).

Yes.

> Based on master, with no prerequisite patches.

I had quite a few comments, but I guess that's more because of me than
because of your patches :)  Seriously, the patches feel close to ready.
Most of my comments were of the "we could perhaps say this a bit more
clearly" kind.

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

* Re: [Qemu-devel] [PATCH v14 18/19] qapi: Simplify semantics of visit_next_list()
  2016-04-15 14:09   ` Markus Armbruster
@ 2016-04-22  8:46     ` Markus Armbruster
  2016-04-22 11:35       ` Markus Armbruster
  0 siblings, 1 reply; 61+ messages in thread
From: Markus Armbruster @ 2016-04-22  8:46 UTC (permalink / raw)
  To: Eric Blake
  Cc: qemu-devel, Alexander Graf, David Gibson, open list:sPAPR, Michael Roth

Markus Armbruster <armbru@redhat.com> writes:

[...]
>> diff --git a/qapi/string-input-visitor.c b/qapi/string-input-visitor.c
>> index 797973a..77dd1a7 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;
>> @@ -125,11 +123,21 @@ 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);
>> +    Error *err = NULL;
>>
>> -    parse_str(siv, errp);
>> +    /* We don't support visits without a GenericList, yet */
>
> without a list
>
> Do we plan to support them?  If not, scratch "yet".
>
>> +    assert(list);
>> +
>> +    parse_str(siv, &err);
>> +    if (err) {
>> +        *list = NULL;
>> +        error_propagate(errp, err);
>> +        return;
>> +    }

parse_str() never sets an error, and therefore your new error check is
dead.  Just as well, because it would be wrong.

parse_str() parses a complete string into a non-empty list of uint64_t
ranges.  On success, it sets siv->ranges to this list.  On error, it
sets it to null.  It could also set an error then, but it doesn't.

If it did, then what would start_list() do with it?  Reporting it would
be wrong, because the list members need not be integers.

If they aren't, the speculative parse_str()'s failure will be ignored.

If they are, parse_type_int64() will call parse_str() again, then use
siv->ranges.

If the first parse_str() succeeds, the second will do nothing, and we'll
use the first one's siv->ranges.  Works.

If the first parse_str() fails, the second will fail as well, because
its input is the same.  We'll use the second one's failure.  Works.

When used outside list context, parse_type_int64() will call parse_str()
for the first time, and use its result.  Works.

Note that opts-visitor does it differently: opts_start_list() doesn't
parse numbers, opts_type_int64() and opts_type_uint64() do.

Further note the latent bug in parse_type_int64(): we first call
parse_str(siv, errp), and goto error if it fails, where we promptly
error_setg(errp, ...).  If parse_str() set an error, the error_setg()
would fail error_setv()'s assertion.

Please drop parse_str()'s unused errp parameter, and add a comment to
start_list() explaining the speculative call to parse_str() there.

Alternatively, change the string visitor to work like the opts visitor.

>>
>>      siv->cur_range = g_list_first(siv->ranges);
>>      if (siv->cur_range) {
[...]

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

* Re: [Qemu-devel] [PATCH v14 18/19] qapi: Simplify semantics of visit_next_list()
  2016-04-22  8:46     ` Markus Armbruster
@ 2016-04-22 11:35       ` Markus Armbruster
  2016-04-22 11:37         ` [Qemu-devel] [PATCH] tests/string-input-visitor: Add negative integer tests Markus Armbruster
  2016-04-27 20:22         ` [Qemu-devel] [PATCH v14 18/19] qapi: Simplify semantics of visit_next_list() Eric Blake
  0 siblings, 2 replies; 61+ messages in thread
From: Markus Armbruster @ 2016-04-22 11:35 UTC (permalink / raw)
  To: Eric Blake
  Cc: qemu-devel, Alexander Graf, David Gibson, open list:sPAPR, Michael Roth

Markus Armbruster <armbru@redhat.com> writes:

> Markus Armbruster <armbru@redhat.com> writes:
>
> [...]
>>> diff --git a/qapi/string-input-visitor.c b/qapi/string-input-visitor.c
>>> index 797973a..77dd1a7 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;
>>> @@ -125,11 +123,21 @@ 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);
>>> +    Error *err = NULL;
>>>
>>> -    parse_str(siv, errp);
>>> +    /* We don't support visits without a GenericList, yet */
>>
>> without a list
>>
>> Do we plan to support them?  If not, scratch "yet".
>>
>>> +    assert(list);
>>> +
>>> +    parse_str(siv, &err);
>>> +    if (err) {
>>> +        *list = NULL;
>>> +        error_propagate(errp, err);
>>> +        return;
>>> +    }
>
> parse_str() never sets an error, and therefore your new error check is
> dead.  Just as well, because it would be wrong.
>
> parse_str() parses a complete string into a non-empty list of uint64_t
> ranges.  On success, it sets siv->ranges to this list.  On error, it
> sets it to null.  It could also set an error then, but it doesn't.
>
> If it did, then what would start_list() do with it?  Reporting it would
> be wrong, because the list members need not be integers.
>
> If they aren't, the speculative parse_str()'s failure will be ignored.
>
> If they are, parse_type_int64() will call parse_str() again, then use
> siv->ranges.
>
> If the first parse_str() succeeds, the second will do nothing, and we'll
> use the first one's siv->ranges.  Works.
>
> If the first parse_str() fails, the second will fail as well, because
> its input is the same.  We'll use the second one's failure.  Works.

No, it doesn't: failure gets interpreted as empty list.  I'll post my
test case separately.

> When used outside list context, parse_type_int64() will call parse_str()
> for the first time, and use its result.  Works.
>
> Note that opts-visitor does it differently: opts_start_list() doesn't
> parse numbers, opts_type_int64() and opts_type_uint64() do.
>
> Further note the latent bug in parse_type_int64(): we first call
> parse_str(siv, errp), and goto error if it fails, where we promptly
> error_setg(errp, ...).  If parse_str() set an error, the error_setg()
> would fail error_setv()'s assertion.
>
> Please drop parse_str()'s unused errp parameter, and add a comment to
> start_list() explaining the speculative call to parse_str() there.

Insufficient, doesn't fix the bug.  After parse_str(), we need to be
able to distinguish empty list from error.  Moving the error_set() into
parse_str() could work.  Returning succes/failure and dropping the errp
parameter could also work.

> Alternatively, change the string visitor to work like the opts visitor.
>
>>>
>>>      siv->cur_range = g_list_first(siv->ranges);
>>>      if (siv->cur_range) {
> [...]

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

* [Qemu-devel] [PATCH] tests/string-input-visitor: Add negative integer tests
  2016-04-22 11:35       ` Markus Armbruster
@ 2016-04-22 11:37         ` Markus Armbruster
  2016-04-27 20:22         ` [Qemu-devel] [PATCH v14 18/19] qapi: Simplify semantics of visit_next_list() Eric Blake
  1 sibling, 0 replies; 61+ messages in thread
From: Markus Armbruster @ 2016-04-22 11:37 UTC (permalink / raw)
  To: qemu-devel

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>
---
 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] 61+ messages in thread

* Re: [Qemu-devel] [PATCH v14 15/19] qapi-commands: Wrap argument visit in visit_start_struct
  2016-04-15 11:42   ` Markus Armbruster
@ 2016-04-26 12:56     ` Eric Blake
  0 siblings, 0 replies; 61+ messages in thread
From: Eric Blake @ 2016-04-26 12:56 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: qemu-devel, Michael Roth

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

On 04/15/2016 05:42 AM, Markus Armbruster wrote:
> Eric Blake <eblake@redhat.com> writes:
> 
>> The qmp-input visitor was playing rather fast and loose: when
> 
> I guess (some of) its *users* are playing fast and loose, and the
> visitor itself lets them.  The patch's title suggests "some of its
> users" == qapi-commands.py.
> 
>> visiting a QDict, you could grab members of the root dictionary
>> without first pushing into the dict.  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, because the value of 'name'
>> should be ignored on the top-level visit.
>>
>> Generated code grows as follows:
>>
>> |@@ -515,7 +695,15 @@ 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);
>> |+    if (!err) {
>> |+        visit_check_struct(v, &err);
>> |+    }
> 
> Does this find errors that weren't found before?

All that this could find is excess input, but we are already checking
for excess input prior to calling into the generated marshaling code, so
it doesn't find anything new.

>>
>> Note that this change could also make it possible for the
>> marshalling code to automatically detect excess input at the top
>> level, and not just in nested dictionaries.  However, that checking
>> is not currently useful (and we rely on the manual checking in
>> monitor.c:qmp_check_client_args() instead) as long as qmp-commands.hx
>> uses .args_type, and as long as we have 'name:O' as an arg-type that
>> explicitly allows unknown top-level keys because we haven't yet
>> converted 'device_add' and 'netdev_add' to introspectible use of
>> 'any'.
> 
> Hmm, that explains why finding these additional errors wouldn't be
> useful.  Good to know, but doesn't quite answer my question.

I guess what it really translates to is we are now doing redundant
checking, and I should do a followup patch to simplify monitor.c.

>> @@ -150,7 +158,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);
>>      qapi_dealloc_visitor_cleanup(qdv);
>>  ''',
>>                       c_name=arg_type.c_name())
> 
> No visit_check_struct() here.  I think its contract should explicitly
> state that you may omit it when you're not interested in errors.

Indeed, calling visit_check_struct(, NULL) can't report any errors, so
skipping it should be documented as safe.

-- 
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] 61+ messages in thread

* Re: [Qemu-devel] [PATCH v14 08/19] qapi: Document visitor interfaces, add assertions
  2016-04-14 15:22   ` Markus Armbruster
@ 2016-04-26 21:50     ` Eric Blake
  2016-04-28 16:33       ` Markus Armbruster
  0 siblings, 1 reply; 61+ messages in thread
From: Eric Blake @ 2016-04-26 21:50 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: qemu-devel, Michael Roth

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

On 04/14/2016 09:22 AM, Markus Armbruster wrote:
> 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.
>>
>> +/*
>> + * The QAPI schema defines both a set of C data types, and a QMP wire
>> + * format.  A QAPI object is formed as a directed acyclic graph of
>> + * QAPI values.
> 
> I understand what you're trying to say, but I find the value / object
> dichotomy odd.  For me, A QAPI object isn't a DAG, it's a node in a DAG.
> Perhaps: "QAPI objects can contain references to other QAPI objects,
> resulting in a directed acyclic graph."

Thanks for a lot of good comments. I'm replying only to the questions
you left amidst all the good review.


>> +++ b/qapi/qapi-visit-core.c
>> @@ -23,6 +23,10 @@
>>  void visit_start_struct(Visitor *v, const char *name, void **obj,
>>                          size_t size, Error **errp)
>>  {
>> +    if (obj) {
>> +        assert(size);
> 
> Yes, because the generator puts a dummy member into empty structs.
> 
>> +        assert(v->type != VISITOR_OUTPUT || *obj);
> 
> Can you point me to the spot in the contract that requires this?

Translation of the assert: If you are using an output visitor, and not
doing a virtual walk (obj is non-NULL), then the object must be
completely built (*obj is non-NULL).  For an input visitor, *obj is NULL
on entry (we're allocating it, after all); for the dealloc visitor, *obj
may or may not be NULL (since we handle cleanup of partial allocation).

In the text, "output visitors (QMP and string) take a completed QAPI
graph", but maybe I can further clarify that a completed object means
that *obj is non-NULL and all 'has_member' and 'member' members are
complete.

> 
> Unlike last time, my remarks are pretty much only about how to say
> things, not about what to say.  Progress!

Yay!  Hopefully I'll post v15 soon and we can get this in at the start
of the 2.7 cycle.

-- 
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] 61+ messages in thread

* Re: [Qemu-devel] [PATCH v14 13/19] qmp: Tighten output visitor rules
  2016-04-15  9:02   ` Markus Armbruster
@ 2016-04-27  1:29     ` Eric Blake
  2016-04-27  6:29       ` Markus Armbruster
  0 siblings, 1 reply; 61+ messages in thread
From: Eric Blake @ 2016-04-27  1:29 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: qemu-devel, Michael Roth

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

On 04/15/2016 03:02 AM, Markus Armbruster wrote:
> Eric Blake <eblake@redhat.com> writes:
> 
>> Add a new qmp_output_visitor_reset(), which must be called before
>> reusing an exising QmpOutputVisitor on a new root object.  Tighten
>> assertions to require that qmp_output_get_qobject() can only be
>> called after pairing a visit_end_* for every visit_start_* (rather
>> than allowing it to return a partially built object), and that it
>> must not 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).
>>
>> Also, check that we are encountering the expected object or list
>> type (both during visit_end*, and also by validating whether 'name'
>> was NULL - although the latter may need to change later if we
>> improve error messages by always passing in a sensible name).
>> This provides protection against mismatched push(struct)/pop(list)
>> or push(list)/pop(struct), similar to the qmp-input protection
>> added in commit bdd8e6b5.
>>
>> Signed-off-by: Eric Blake <eblake@redhat.com>
> 
> As written, the commit message makes me wonder why we add
> qmp_output_visitor_reset() in the same commit.  I think the reason is
> the tightened rules make it necessary.  The commit message could make
> that clearer by explaining the rule changes first, then point out we
> need a reset to comply with the rules.

I think I'll try splitting the addition of qmp_output_visitor_reset()
into a separate patch.

>> @@ -93,6 +92,9 @@ static void qmp_output_add_obj(QmpOutputVisitor *qov, const char *name,
>>              qdict_put_obj(qobject_to_qdict(cur), name, value);
>>              break;
>>          case QTYPE_QLIST:
>> +            /* FIXME: assertion needs adjustment if we fix visit-core
>> +             * to pass "name.0" style name during lists.  */
> 
> visit-core merely passes through whatever name it gets from the client.
> Thus, saying we 'fix visit-core to pass "name.0"' is a bit misleading.
> What we'd do is change it to require "name.0", then update its users to
> comply.

Maybe it's not too inaccurate - the only callers are the generated
visit_type_FOOList() functions, but having a common "name.%d" generator
in the core may be easier than bloating each generated visit_type_FOOList.

> 
> Moreover, this is a note, not a FIXME: nothing is broken here.  The
> closest we get to "broken" are the bad error messages, but they're
> elsewhere.
> 
> I'd simply drop the comment.

But this solution nicely sidesteps the "how will we fix error messages",
so I've dropped the comment.

> 
>> +            assert(!name);
> 
> PATCH 08 made this part of the contract.  It also added a bunch of
> contract-checking assertions.  Should this one be in PATCH 08, too?

Well, it's only weakly part of the contract unless (until?) we fix
callers/core to pass in "name.0", and then the assert would trigger.
However, checking the assertion in patch 8 is harder, without making the
core track whether it is currently in a list or struct visit (that is,
the only place where we know whether 'name' should be NULL or not is
where we've tracked a stack of our current visit_start_* calls; but the
core is not tracking a stack because that would be redundant with the
stacks in the qmp visitors).  So for now I think I'll just keep it here.


>> +++ b/tests/test-qmp-output-visitor.c
>> @@ -139,6 +139,7 @@ static void test_visitor_out_enum(TestOutputVisitorData *data,

>> @@ -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;
> 
> How did you find the places that now need qmp_output_visitor_reset()?

Ran the test, found what asserted, and added a reset() to make the test
pass again.

-- 
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] 61+ messages in thread

* Re: [Qemu-devel] [PATCH v14 13/19] qmp: Tighten output visitor rules
  2016-04-27  1:29     ` Eric Blake
@ 2016-04-27  6:29       ` Markus Armbruster
  2016-04-27 12:22         ` Eric Blake
  0 siblings, 1 reply; 61+ messages in thread
From: Markus Armbruster @ 2016-04-27  6:29 UTC (permalink / raw)
  To: Eric Blake; +Cc: qemu-devel, Michael Roth

Eric Blake <eblake@redhat.com> writes:

> On 04/15/2016 03:02 AM, Markus Armbruster wrote:
>> Eric Blake <eblake@redhat.com> writes:
>> 
>>> Add a new qmp_output_visitor_reset(), which must be called before
>>> reusing an exising QmpOutputVisitor on a new root object.  Tighten
>>> assertions to require that qmp_output_get_qobject() can only be
>>> called after pairing a visit_end_* for every visit_start_* (rather
>>> than allowing it to return a partially built object), and that it
>>> must not 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).
>>>
>>> Also, check that we are encountering the expected object or list
>>> type (both during visit_end*, and also by validating whether 'name'
>>> was NULL - although the latter may need to change later if we
>>> improve error messages by always passing in a sensible name).
>>> This provides protection against mismatched push(struct)/pop(list)
>>> or push(list)/pop(struct), similar to the qmp-input protection
>>> added in commit bdd8e6b5.
>>>
>>> Signed-off-by: Eric Blake <eblake@redhat.com>
>> 
>> As written, the commit message makes me wonder why we add
>> qmp_output_visitor_reset() in the same commit.  I think the reason is
>> the tightened rules make it necessary.  The commit message could make
>> that clearer by explaining the rule changes first, then point out we
>> need a reset to comply with the rules.
>
> I think I'll try splitting the addition of qmp_output_visitor_reset()
> into a separate patch.
>
>>> @@ -93,6 +92,9 @@ static void qmp_output_add_obj(QmpOutputVisitor *qov, const char *name,
>>>              qdict_put_obj(qobject_to_qdict(cur), name, value);
>>>              break;
>>>          case QTYPE_QLIST:
>>> +            /* FIXME: assertion needs adjustment if we fix visit-core
>>> +             * to pass "name.0" style name during lists.  */
>> 
>> visit-core merely passes through whatever name it gets from the client.
>> Thus, saying we 'fix visit-core to pass "name.0"' is a bit misleading.
>> What we'd do is change it to require "name.0", then update its users to
>> comply.
>
> Maybe it's not too inaccurate - the only callers are the generated
> visit_type_FOOList() functions, but having a common "name.%d" generator
> in the core may be easier than bloating each generated visit_type_FOOList.
>
>> 
>> Moreover, this is a note, not a FIXME: nothing is broken here.  The
>> closest we get to "broken" are the bad error messages, but they're
>> elsewhere.
>> 
>> I'd simply drop the comment.
>
> But this solution nicely sidesteps the "how will we fix error messages",
> so I've dropped the comment.
>
>> 
>>> +            assert(!name);
>> 
>> PATCH 08 made this part of the contract.  It also added a bunch of
>> contract-checking assertions.  Should this one be in PATCH 08, too?
>
> Well, it's only weakly part of the contract unless (until?) we fix
> callers/core to pass in "name.0", and then the assert would trigger.
> However, checking the assertion in patch 8 is harder, without making the
> core track whether it is currently in a list or struct visit (that is,
> the only place where we know whether 'name' should be NULL or not is
> where we've tracked a stack of our current visit_start_* calls; but the
> core is not tracking a stack because that would be redundant with the
> stacks in the qmp visitors).  So for now I think I'll just keep it here.

Worth mentioning in the commit message?  (I'm not sure)

>
>>> +++ b/tests/test-qmp-output-visitor.c
>>> @@ -139,6 +139,7 @@ static void test_visitor_out_enum(TestOutputVisitorData *data,
>
>>> @@ -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;
>> 
>> How did you find the places that now need qmp_output_visitor_reset()?
>
> Ran the test, found what asserted, and added a reset() to make the test
> pass again.

Should've gotten them all in tests, because tests have trivial control
flow.  There's a risk of missing resets in QEMU proper.  I figure it's
small.  Generated visits shouldn't reuse visitor objects, thus should be
fine.  Manual visits need review: find the spots that create visitor
objects, trace the flow to their death, and convince yourself there's no
reuse.

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

* Re: [Qemu-devel] [PATCH v14 13/19] qmp: Tighten output visitor rules
  2016-04-27  6:29       ` Markus Armbruster
@ 2016-04-27 12:22         ` Eric Blake
  0 siblings, 0 replies; 61+ messages in thread
From: Eric Blake @ 2016-04-27 12:22 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: qemu-devel, Michael Roth

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

On 04/27/2016 12:29 AM, Markus Armbruster wrote:

>>>> @@ -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;
>>>
>>> How did you find the places that now need qmp_output_visitor_reset()?
>>
>> Ran the test, found what asserted, and added a reset() to make the test
>> pass again.
> 
> Should've gotten them all in tests, because tests have trivial control
> flow.  There's a risk of missing resets in QEMU proper.  I figure it's
> small.  Generated visits shouldn't reuse visitor objects, thus should be
> fine.  Manual visits need review: find the spots that create visitor
> objects, trace the flow to their death, and convince yourself there's no
> reuse.

Okay, I've done an audit, and will update the commit message accordingly
- outside of generated code and testsuites, there were only 5 uses of
qmp_output_visitor_new(), all of which were used on a single visit
before destruction.  So to date only the testsuite was reusing things.
[2 of those uses will be dropped in a later series of mine, when I add a
QAPI clone visitor]

-- 
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] 61+ messages in thread

* Re: [Qemu-devel] [PATCH v14 18/19] qapi: Simplify semantics of visit_next_list()
  2016-04-22 11:35       ` Markus Armbruster
  2016-04-22 11:37         ` [Qemu-devel] [PATCH] tests/string-input-visitor: Add negative integer tests Markus Armbruster
@ 2016-04-27 20:22         ` Eric Blake
  1 sibling, 0 replies; 61+ messages in thread
From: Eric Blake @ 2016-04-27 20:22 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: qemu-devel, Alexander Graf, David Gibson, open list:sPAPR, Michael Roth

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

On 04/22/2016 05:35 AM, Markus Armbruster wrote:

>>>>
>>>>  static void
>>>> -start_list(Visitor *v, const char *name, Error **errp)
>>>> +start_list(Visitor *v, const char *name, GenericList **list, size_t size,

>>>> +
>>>> +    parse_str(siv, &err);
>>>> +    if (err) {
>>>> +        *list = NULL;
>>>> +        error_propagate(errp, err);
>>>> +        return;
>>>> +    }
>>
>> parse_str() never sets an error, and therefore your new error check is
>> dead.  Just as well, because it would be wrong.
>>
>> parse_str() parses a complete string into a non-empty list of uint64_t
>> ranges.  On success, it sets siv->ranges to this list.  On error, it
>> sets it to null.  It could also set an error then, but it doesn't.
>>
>> If it did, then what would start_list() do with it?  Reporting it would
>> be wrong, because the list members need not be integers.

parse_str() is only ever called for start_list and parse_type_int64 - so
the real question is do we ever support the string input visitor on
anything other than an integral list.  Per the comments I'm adding
earlier in the series:

 * The string input visitor does not implement support for visiting
 * QAPI structs, alternates, null, or arbitrary QTypes.

so it sounds like the only lists it supports ARE integer lists.

>>
>> If they aren't, the speculative parse_str()'s failure will be ignored.
>>
>> If they are, parse_type_int64() will call parse_str() again, then use
>> siv->ranges.
>>
>> If the first parse_str() succeeds, the second will do nothing, and we'll
>> use the first one's siv->ranges.  Works.
>>
>> If the first parse_str() fails, the second will fail as well, because
>> its input is the same.  We'll use the second one's failure.  Works.
> 
> No, it doesn't: failure gets interpreted as empty list.  I'll post my
> test case separately.
> 
>> When used outside list context, parse_type_int64() will call parse_str()
>> for the first time, and use its result.  Works.
>>
>> Note that opts-visitor does it differently: opts_start_list() doesn't
>> parse numbers, opts_type_int64() and opts_type_uint64() do.

I like the approach used in opts-visitor (start_list should only check
if a list is present, but save the parsing for when the items are
actually consumed off the list).  But opts-visitor also handles structs,
unlike the string visitor, which forces the separation (at start_list,
we don't know what the list element will be, unlike the string visitor
where we know the list element is integral).

>>
>> Further note the latent bug in parse_type_int64(): we first call
>> parse_str(siv, errp), and goto error if it fails, where we promptly
>> error_setg(errp, ...).  If parse_str() set an error, the error_setg()
>> would fail error_setv()'s assertion.
>>
>> Please drop parse_str()'s unused errp parameter, and add a comment to
>> start_list() explaining the speculative call to parse_str() there.
> 
> Insufficient, doesn't fix the bug.  After parse_str(), we need to be
> able to distinguish empty list from error.  Moving the error_set() into
> parse_str() could work.  Returning succes/failure and dropping the errp
> parameter could also work.

I'm playing with these ideas, but will get the bug fixed (along with
your testsuite addition) as a prerequisite to the list refactoring (to
make sure that the refactored list still passes the fixed test).

> 
>> Alternatively, change the string visitor to work like the opts visitor.

Trickier, but may be my only option if the other approaches don't work.
 Thanks again for spotting yet another ugly corner case worth fixing
(this series seems to have been a never-ending source of them...)

-- 
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] 61+ messages in thread

* Re: [Qemu-devel] [PATCH v14 19/19] qapi: Change visit_type_FOO() to no longer return partial objects
  2016-04-15 14:49   ` Markus Armbruster
@ 2016-04-27 21:51     ` Eric Blake
  0 siblings, 0 replies; 61+ messages in thread
From: Eric Blake @ 2016-04-27 21:51 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: qemu-devel, Michael Roth

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

On 04/15/2016 08:49 AM, Markus Armbruster wrote:
> Eric Blake <eblake@redhat.com> writes:
> 
>> Returning a partial object on error is an invitation for a careless
>> caller to leak memory.  As no one outside the testsuite was actually
>> relying on these semantics, it is cleaner to just document and
>> guarantee that ALL pointer-based visit_type_FOO() functions always
>> leave a safe value in *obj during an input visitor (either the new
>> object on success, or NULL if an error is encountered), so callers
>> can now unconditionally use qapi_free_FOO() to clean up regardless
>> of whether an error occurred.
> 
> Hmm, wasn't the "assign null on error" part done in a prior patch?
> Checking...  no, only half of it, in PATCH 03: there, we went from "may
> store an incomplete object on error" to "store either an incomplete
> object or null on error".  Now we go on to just "store null on error".
> Correct?

Yes. I'll tweak the wording to make it clearer.

> 
>> The decision is done by enhancing qapi-visit-core to return true
>> for input visitors (the callbacks themselves do not need
>> modification); since we've documented that visit_end_* must be
>> called after any successful visit_start_*, that is a sufficient
>> point for knowing that something was allocated during start.
> 
> I find this sentence a bit confusing.  Let me try:
> 
>     To help visitor-agnostic code, such as the generated qapi-visit.c,
>     make the visit_end_FOO() return true when something was allocated.
>     Easily done in the visitor core, no need to change the callbacks.
> 
> But see my comments on the visit_end_FOO() inline.

Reply below, where your comments are indeed worth thinking about.

> 
>> 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).
> 
> Should this note be in PATCH 03?
> 
> The inconsistency isn't pretty, but tolerable if it simplifies things.

No. Patch 03 fixed visit_start_struct, not visit_type_FOO.  Since it is
this patch that is tweaking visit_type_FOO, I have to explain why
visit_type_ENUM preserves values, while visit_type_OBJ sets to NULL.


>>   *
>> - * 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.
> 
> Okay.

And this matches the commit message explaining the difference between
scalar and object (and also applies to visit_type_int being a scalar
that leaves the value unchanged on error).

> 
>> + *                              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.
> 
> Where do the incomplete objects come from now?  I thought this patch
> gets rid of them.

Still possible to create one by manual means, just no longer possible
from a QAPI input visitor.  I'll tweak the wording.


>> -void visit_end_struct(Visitor *v);
>> +bool visit_end_struct(Visitor *v);
> 
> I generally like functions to return something useful, but not in this
> case, because the function name gives you no clue about its value.
> Consider:
> 
>     if (visit_end_struct(v) && err) {
>         qapi_free_FOO(*obj);
>         *obj = NULL;
>     }
> 
> To find out what this means, a reader not familiar with visitors almost
> certainly needs to refer to visit_end_struct()'s contract or code.
> 
> Compare:
> 
>     visit_end_struct(v);
>     if (err && v->type == VISITOR_INPUT) {

v->type is a layering violation...

>         qapi_free_FOO(*obj);
>         *obj = NULL;
>     }
> 
> Or:
> 
>     visit_end_struct(v);
>     if (err && visit_is_input(v)) {

...but this is doable by exporting visit_is_input().

>         qapi_free_FOO(*obj);
>         *obj = NULL;
>     }

Makes the generated code have more lines, but who really cares.  So
consider it done in v15.

>> +++ b/qapi/qapi-visit-core.c
>> @@ -23,11 +23,17 @@
>>  void visit_start_struct(Visitor *v, const char *name, void **obj,
>>                          size_t size, Error **errp)
>>  {
>> +    Error *err = NULL;
>> +
>>      if (obj) {
>>          assert(size);
>>          assert(v->type != VISITOR_OUTPUT || *obj);
>>      }
>> -    v->start_struct(v, name, obj, size, errp);
>> +    v->start_struct(v, name, obj, size, &err);
>> +    if (obj && v->type == VISITOR_INPUT) {
>> +        assert(err || *obj);
>> +    }
>> +    error_propagate(errp, err);
> 
> Sure this belongs to this patch?  More of the same below.

Hmm. Patch 3 was the one that tightened semantics on visit_start, so I
can certainly try to hoist the added assertions there.


>>  static void test_validate_fail_alternate(TestInputVisitorData *data,
>>                                           const void *unused)
>>  {
>> -    UserDefAlternate *tmp = NULL;
>> +    UserDefAlternate *tmp;
> 
> Did this initialization become dead in PATCH 03 already?

Probably :)

-- 
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] 61+ messages in thread

* Re: [Qemu-devel] [PATCH v14 08/19] qapi: Document visitor interfaces, add assertions
  2016-04-26 21:50     ` Eric Blake
@ 2016-04-28 16:33       ` Markus Armbruster
  0 siblings, 0 replies; 61+ messages in thread
From: Markus Armbruster @ 2016-04-28 16:33 UTC (permalink / raw)
  To: Eric Blake; +Cc: qemu-devel, Michael Roth

Eric Blake <eblake@redhat.com> writes:

> On 04/14/2016 09:22 AM, Markus Armbruster wrote:
>> Eric Blake <eblake@redhat.com> writes:
[...]
>>> +++ b/qapi/qapi-visit-core.c
>>> @@ -23,6 +23,10 @@
>>>  void visit_start_struct(Visitor *v, const char *name, void **obj,
>>>                          size_t size, Error **errp)
>>>  {
>>> +    if (obj) {
>>> +        assert(size);
>> 
>> Yes, because the generator puts a dummy member into empty structs.
>> 
>>> +        assert(v->type != VISITOR_OUTPUT || *obj);
>> 
>> Can you point me to the spot in the contract that requires this?
>
> Translation of the assert: If you are using an output visitor, and not
> doing a virtual walk (obj is non-NULL), then the object must be
> completely built (*obj is non-NULL).  For an input visitor, *obj is NULL
> on entry (we're allocating it, after all);

Actually, it may be anything, including uninitialized, and it'll be
overwritten.

>                                            for the dealloc visitor, *obj
> may or may not be NULL (since we handle cleanup of partial allocation).

Got the assertion now, thanks!

> In the text, "output visitors (QMP and string) take a completed QAPI
> graph", but maybe I can further clarify that a completed object means
> that *obj is non-NULL and all 'has_member' and 'member' members are
> complete.
[...]

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

end of thread, other threads:[~2016-04-28 16:34 UTC | newest]

Thread overview: 61+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-04-08 16:12 [Qemu-devel] [PATCH v14 00/19] qapi visitor cleanups (post-introspection cleanups subset E) Eric Blake
2016-04-08 16:12 ` [Qemu-devel] [PATCH v14 01/19] qapi: Consolidate object visitors Eric Blake
2016-04-13 12:48   ` Markus Armbruster
2016-04-13 16:13     ` Eric Blake
2016-04-15 15:05       ` Markus Armbruster
2016-04-08 16:12 ` [Qemu-devel] [PATCH v14 02/19] qapi-visit: Add visitor.type classification Eric Blake
2016-04-13 13:49   ` Markus Armbruster
2016-04-13 16:23     ` Eric Blake
2016-04-15 15:24       ` Markus Armbruster
2016-04-08 16:12 ` [Qemu-devel] [PATCH v14 03/19] qapi: Guarantee NULL obj on input visitor callback error Eric Blake
2016-04-13 14:04   ` Markus Armbruster
2016-04-08 16:12 ` [Qemu-devel] [PATCH v14 04/19] qmp: Drop dead command->type Eric Blake
2016-04-08 16:12 ` [Qemu-devel] [PATCH v14 05/19] qmp-input: Clean up stack handling Eric Blake
2016-04-13 15:53   ` Markus Armbruster
2016-04-13 16:36     ` Eric Blake
2016-04-13 16:40       ` Eric Blake
2016-04-15 15:27       ` Markus Armbruster
2016-04-08 16:12 ` [Qemu-devel] [PATCH v14 06/19] qmp-input: Don't consume input when checking has_member Eric Blake
2016-04-13 16:06   ` Markus Armbruster
2016-04-13 16:43     ` Eric Blake
2016-04-15 15:28       ` Markus Armbruster
2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 07/19] qmp-input: Refactor when list is advanced Eric Blake
2016-04-13 17:38   ` Markus Armbruster
2016-04-13 19:58     ` Eric Blake
2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 08/19] qapi: Document visitor interfaces, add assertions Eric Blake
2016-04-14 15:22   ` Markus Armbruster
2016-04-26 21:50     ` Eric Blake
2016-04-28 16:33       ` Markus Armbruster
2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 09/19] tests: Add check-qnull Eric Blake
2016-04-14 16:13   ` Markus Armbruster
2016-04-14 17:37     ` Markus Armbruster
2016-04-14 18:54       ` Eric Blake
2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 10/19] qapi: Add visit_type_null() visitor Eric Blake
2016-04-14 17:09   ` Markus Armbruster
2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 11/19] qmp: Support explicit null during visits Eric Blake
2016-04-15  8:29   ` Markus Armbruster
2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 12/19] spapr_drc: Expose 'null' in qom-get when there is no fdt Eric Blake
2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 13/19] qmp: Tighten output visitor rules Eric Blake
2016-04-15  9:02   ` Markus Armbruster
2016-04-27  1:29     ` Eric Blake
2016-04-27  6:29       ` Markus Armbruster
2016-04-27 12:22         ` Eric Blake
2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 14/19] qapi: Split visit_end_struct() into pieces Eric Blake
2016-04-15 11:03   ` Markus Armbruster
2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 15/19] qapi-commands: Wrap argument visit in visit_start_struct Eric Blake
2016-04-15 11:42   ` Markus Armbruster
2016-04-26 12:56     ` Eric Blake
2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 16/19] qom: Wrap prop " Eric Blake
2016-04-15 11:52   ` Markus Armbruster
2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 17/19] qmp-input: Require struct push to visit members of top dict Eric Blake
2016-04-15 12:53   ` Markus Armbruster
2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 18/19] qapi: Simplify semantics of visit_next_list() Eric Blake
2016-04-15 14:09   ` Markus Armbruster
2016-04-22  8:46     ` Markus Armbruster
2016-04-22 11:35       ` Markus Armbruster
2016-04-22 11:37         ` [Qemu-devel] [PATCH] tests/string-input-visitor: Add negative integer tests Markus Armbruster
2016-04-27 20:22         ` [Qemu-devel] [PATCH v14 18/19] qapi: Simplify semantics of visit_next_list() Eric Blake
2016-04-08 16:13 ` [Qemu-devel] [PATCH v14 19/19] qapi: Change visit_type_FOO() to no longer return partial objects Eric Blake
2016-04-15 14:49   ` Markus Armbruster
2016-04-27 21:51     ` Eric Blake
2016-04-15 15:41 ` [Qemu-devel] [PATCH v14 00/19] 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.