summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--docs/qapi-code-gen.txt16
-rw-r--r--hw/ppc/spapr_drc.c2
-rw-r--r--include/qapi/opts-visitor.h3
-rw-r--r--include/qapi/string-input-visitor.h3
-rw-r--r--include/qapi/string-output-visitor.h3
-rw-r--r--include/qapi/visitor-impl.h8
-rw-r--r--include/qapi/visitor.h49
-rw-r--r--qapi/opts-visitor.c33
-rw-r--r--qapi/qapi-dealloc-visitor.c35
-rw-r--r--qapi/qapi-visit-core.c18
-rw-r--r--qapi/qmp-input-visitor.c40
-rw-r--r--qapi/qmp-output-visitor.c22
-rw-r--r--qapi/string-input-visitor.c29
-rw-r--r--qapi/string-output-visitor.c41
-rw-r--r--scripts/qapi-visit.py16
-rw-r--r--tests/test-string-input-visitor.c2
16 files changed, 142 insertions, 178 deletions
diff --git a/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt
index d135847c87..fe61c5c423 100644
--- a/docs/qapi-code-gen.txt
+++ b/docs/qapi-code-gen.txt
@@ -912,18 +912,20 @@ Example:
     void visit_type_UserDefOneList(Visitor *v, const char *name, UserDefOneList **obj, Error **errp)
     {
         Error *err = NULL;
-        GenericList *i, **prev;
+        UserDefOneList *tail;
+        size_t size = sizeof(**obj);
 
-        visit_start_list(v, name, &err);
+        visit_start_list(v, name, (GenericList **)obj, size, &err);
         if (err) {
             goto out;
         }
 
-        for (prev = (GenericList **)obj;
-             !err && (i = visit_next_list(v, prev, sizeof(**obj))) != NULL;
-             prev = &i) {
-            UserDefOneList *native_i = (UserDefOneList *)i;
-            visit_type_UserDefOne(v, NULL, &native_i->value, &err);
+        for (tail = *obj; tail;
+             tail = (UserDefOneList *)visit_next_list(v, (GenericList *)tail, size)) {
+            visit_type_UserDefOne(v, NULL, &tail->value, &err);
+            if (err) {
+                break;
+            }
         }
 
         visit_end_list(v);
diff --git a/hw/ppc/spapr_drc.c b/hw/ppc/spapr_drc.c
index e3a6191ad4..94c875d752 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/include/qapi/opts-visitor.h b/include/qapi/opts-visitor.h
index fe37ed9a36..ae1bf7cf51 100644
--- a/include/qapi/opts-visitor.h
+++ b/include/qapi/opts-visitor.h
@@ -32,7 +32,8 @@ typedef struct OptsVisitor OptsVisitor;
  *
  * The Opts input visitor does not implement support for visiting QAPI
  * alternates, numbers (other than integers), null, or arbitrary
- * QTypes.
+ * QTypes.  It also requires a non-null list argument to
+ * visit_start_list().
  */
 OptsVisitor *opts_visitor_new(const QemuOpts *opts);
 void opts_visitor_cleanup(OptsVisitor *nv);
diff --git a/include/qapi/string-input-visitor.h b/include/qapi/string-input-visitor.h
index a8d8f6723b..7b76c2b9e3 100644
--- a/include/qapi/string-input-visitor.h
+++ b/include/qapi/string-input-visitor.h
@@ -19,7 +19,8 @@ typedef struct StringInputVisitor StringInputVisitor;
 
 /*
  * The string input visitor does not implement support for visiting
- * QAPI structs, alternates, null, or arbitrary QTypes.
+ * QAPI structs, alternates, null, or arbitrary QTypes.  It also
+ * requires a non-null list argument to visit_start_list().
  */
 StringInputVisitor *string_input_visitor_new(const char *str);
 void string_input_visitor_cleanup(StringInputVisitor *v);
diff --git a/include/qapi/string-output-visitor.h b/include/qapi/string-output-visitor.h
index 89b7e4bc9b..e10522a35b 100644
--- a/include/qapi/string-output-visitor.h
+++ b/include/qapi/string-output-visitor.h
@@ -19,7 +19,8 @@ typedef struct StringOutputVisitor StringOutputVisitor;
 
 /*
  * The string output visitor does not implement support for visiting
- * QAPI structs, alternates, null, or arbitrary QTypes.
+ * QAPI structs, alternates, null, or arbitrary QTypes.  It also
+ * requires a non-null list argument to visit_start_list().
  */
 StringOutputVisitor *string_output_visitor_new(bool human);
 void string_output_visitor_cleanup(StringOutputVisitor *v);
diff --git a/include/qapi/visitor-impl.h b/include/qapi/visitor-impl.h
index b20a9226e0..145afd03e7 100644
--- a/include/qapi/visitor-impl.h
+++ b/include/qapi/visitor-impl.h
@@ -49,11 +49,13 @@ struct Visitor
     /* Must be set to visit structs */
     void (*end_struct)(Visitor *v);
 
-    /* Must be set */
-    void (*start_list)(Visitor *v, const char *name, Error **errp);
+    /* Must be set; implementations may require @list to be non-null,
+     * but must document it. */
+    void (*start_list)(Visitor *v, const char *name, GenericList **list,
+                       size_t size, Error **errp);
 
     /* Must be set */
-    GenericList *(*next_list)(Visitor *v, GenericList **list, size_t size);
+    GenericList *(*next_list)(Visitor *v, GenericList *tail, size_t size);
 
     /* Must be set */
     void (*end_list)(Visitor *v);
diff --git a/include/qapi/visitor.h b/include/qapi/visitor.h
index fe268fe78d..8de6b436fb 100644
--- a/include/qapi/visitor.h
+++ b/include/qapi/visitor.h
@@ -176,7 +176,7 @@
  *  if (err) {
  *      goto out;
  *  }
- *  visit_start_list(v, "list", &err);
+ *  visit_start_list(v, "list", NULL, 0, &err);
  *  if (err) {
  *      goto outobj;
  *  }
@@ -281,19 +281,27 @@ void visit_end_struct(Visitor *v);
  * @name expresses the relationship of this list to its parent
  * container; see the general description of @name above.
  *
+ * @list must be non-NULL for a real walk, in which case @size
+ * determines how much memory an input visitor will allocate into
+ * *@list (at least sizeof(GenericList)).  Some visitors also allow
+ * @list to be NULL for a virtual walk, in which case @size is
+ * ignored.
+ *
  * @errp obeys typical error usage, and reports failures such as a
- * member @name is not present, or present but not a list.
+ * member @name is not present, or present but not a list.  On error,
+ * input visitors set *@list to NULL.
  *
  * After visit_start_list() succeeds, the caller may visit its members
- * one after the other.  A real visit uses visit_next_list() for
- * traversing the linked list, while a virtual visit uses other means.
- * For each list element, call the appropriate visit_type_FOO() with
- * name set to NULL and obj set to the address of the value member of
- * the list element.  Finally, visit_end_list() needs to be called to
- * clean up, even if intermediate visits fail.  See the examples
- * above.
+ * one after the other.  A real visit (where @obj is non-NULL) uses
+ * visit_next_list() for traversing the linked list, while a virtual
+ * visit (where @obj is NULL) uses other means.  For each list
+ * element, call the appropriate visit_type_FOO() with name set to
+ * NULL and obj set to the address of the value member of the list
+ * element.  Finally, visit_end_list() needs to be called to clean up,
+ * even if intermediate visits fail.  See the examples above.
  */
-void visit_start_list(Visitor *v, const char *name, Error **errp);
+void visit_start_list(Visitor *v, const char *name, GenericList **list,
+                      size_t size, Error **errp);
 
 /*
  * Iterate over a GenericList during a non-virtual list visit.
@@ -301,20 +309,15 @@ void visit_start_list(Visitor *v, const char *name, Error **errp);
  * @size represents the size of a linked list node (at least
  * sizeof(GenericList)).
  *
- * @list must not be NULL; on the first call, @list contains the
- * address of the list head, and on subsequent calls *@list must be
- * the previously returned value.  Should be called in a loop until a
- * NULL return or error occurs; for each non-NULL return, the caller
- * then calls the appropriate visit_type_*() for the element type
- * of the list, with that function's name parameter set to NULL and
- * obj set to the address of (*@list)->value.
- *
- * FIXME: This interface is awkward; it requires all callbacks to
- * track whether it is the first or a subsequent call.  A better
- * interface would pass the head of the list through
- * visit_start_list().
+ * @tail must not be NULL; on the first call, @tail is the value of
+ * *list after visit_start_list(), and on subsequent calls @tail must
+ * be the previously returned value.  Should be called in a loop until
+ * a NULL return or error occurs; for each non-NULL return, the caller
+ * then calls the appropriate visit_type_*() for the element type of
+ * the list, with that function's name parameter set to NULL and obj
+ * set to the address of @tail->value.
  */
-GenericList *visit_next_list(Visitor *v, GenericList **list, size_t size);
+GenericList *visit_next_list(Visitor *v, GenericList *tail, size_t size);
 
 /*
  * Complete a list visit started earlier.
diff --git a/qapi/opts-visitor.c b/qapi/opts-visitor.c
index ece0598e34..4cf1cf885b 100644
--- a/qapi/opts-visitor.c
+++ b/qapi/opts-visitor.c
@@ -23,9 +23,8 @@
 enum ListMode
 {
     LM_NONE,             /* not traversing a list of repeated options */
-    LM_STARTED,          /* opts_start_list() succeeded */
 
-    LM_IN_PROGRESS,      /* opts_next_list() has been called.
+    LM_IN_PROGRESS,      /* opts_next_list() ready to be called.
                           *
                           * Generating the next list link will consume the most
                           * recently parsed QemuOpt instance of the repeated
@@ -214,35 +213,33 @@ lookup_distinct(const OptsVisitor *ov, const char *name, Error **errp)
 
 
 static void
-opts_start_list(Visitor *v, const char *name, Error **errp)
+opts_start_list(Visitor *v, const char *name, GenericList **list, size_t size,
+                Error **errp)
 {
     OptsVisitor *ov = to_ov(v);
 
     /* we can't traverse a list in a list */
     assert(ov->list_mode == LM_NONE);
+    /* we don't support visits without a list */
+    assert(list);
     ov->repeated_opts = lookup_distinct(ov, name, errp);
-    if (ov->repeated_opts != NULL) {
-        ov->list_mode = LM_STARTED;
+    if (ov->repeated_opts) {
+        ov->list_mode = LM_IN_PROGRESS;
+        *list = g_malloc0(size);
+    } else {
+        *list = NULL;
     }
 }
 
 
 static GenericList *
-opts_next_list(Visitor *v, GenericList **list, size_t size)
+opts_next_list(Visitor *v, GenericList *tail, size_t size)
 {
     OptsVisitor *ov = to_ov(v);
-    GenericList **link;
 
     switch (ov->list_mode) {
-    case LM_STARTED:
-        ov->list_mode = LM_IN_PROGRESS;
-        link = list;
-        break;
-
     case LM_SIGNED_INTERVAL:
     case LM_UNSIGNED_INTERVAL:
-        link = &(*list)->next;
-
         if (ov->list_mode == LM_SIGNED_INTERVAL) {
             if (ov->range_next.s < ov->range_limit.s) {
                 ++ov->range_next.s;
@@ -263,7 +260,6 @@ opts_next_list(Visitor *v, GenericList **list, size_t size)
             g_hash_table_remove(ov->unprocessed_opts, opt->name);
             return NULL;
         }
-        link = &(*list)->next;
         break;
     }
 
@@ -271,8 +267,8 @@ opts_next_list(Visitor *v, GenericList **list, size_t size)
         abort();
     }
 
-    *link = g_malloc0(size);
-    return *link;
+    tail->next = g_malloc0(size);
+    return tail->next;
 }
 
 
@@ -281,8 +277,7 @@ opts_end_list(Visitor *v)
 {
     OptsVisitor *ov = to_ov(v);
 
-    assert(ov->list_mode == LM_STARTED ||
-           ov->list_mode == LM_IN_PROGRESS ||
+    assert(ov->list_mode == LM_IN_PROGRESS ||
            ov->list_mode == LM_SIGNED_INTERVAL ||
            ov->list_mode == LM_UNSIGNED_INTERVAL);
     ov->repeated_opts = NULL;
diff --git a/qapi/qapi-dealloc-visitor.c b/qapi/qapi-dealloc-visitor.c
index 9005badb25..cd68b55a1a 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/qapi-visit-core.c b/qapi/qapi-visit-core.c
index 58144f67b7..d6bf4bd253 100644
--- a/qapi/qapi-visit-core.c
+++ b/qapi/qapi-visit-core.c
@@ -48,15 +48,23 @@ 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);
+    Error *err = NULL;
+
+    assert(!list || size >= sizeof(GenericList));
+    v->start_list(v, name, list, size, &err);
+    if (list && v->type == VISITOR_INPUT) {
+        assert(!(err && *list));
+    }
+    error_propagate(errp, err);
 }
 
-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/qapi/qmp-input-visitor.c b/qapi/qmp-input-visitor.c
index 90b9df11c4..aea90a1378 100644
--- a/qapi/qmp-input-visitor.c
+++ b/qapi/qmp-input-visitor.c
@@ -29,7 +29,6 @@ typedef struct StackObject
 
     GHashTable *h;           /* If obj is dict: unvisited keys */
     const QListEntry *entry; /* If obj is list: unvisited tail */
-    bool first;              /* If obj is list: next_list() not yet called? */
 } StackObject;
 
 struct QmpInputVisitor
@@ -81,7 +80,6 @@ static QObject *qmp_input_get_object(QmpInputVisitor *qiv,
     } else {
         assert(qobject_type(qobj) == QTYPE_QLIST);
         assert(!name);
-        assert(!tos->first);
         ret = qlist_entry_obj(tos->entry);
         if (consume) {
             tos->entry = qlist_next(tos->entry);
@@ -97,7 +95,8 @@ static void qdict_add_key(const char *key, QObject *obj, void *opaque)
     g_hash_table_insert(h, (gpointer) key, NULL);
 }
 
-static void qmp_input_push(QmpInputVisitor *qiv, QObject *obj, Error **errp)
+static const QListEntry *qmp_input_push(QmpInputVisitor *qiv, QObject *obj,
+                                        Error **errp)
 {
     GHashTable *h;
     StackObject *tos = &qiv->stack[qiv->nb_stack];
@@ -105,7 +104,7 @@ static void qmp_input_push(QmpInputVisitor *qiv, QObject *obj, Error **errp)
     assert(obj);
     if (qiv->nb_stack >= QIV_STACK_SIZE) {
         error_setg(errp, "An internal buffer overran");
-        return;
+        return NULL;
     }
 
     tos->obj = obj;
@@ -118,10 +117,10 @@ static void qmp_input_push(QmpInputVisitor *qiv, QObject *obj, Error **errp)
         tos->h = h;
     } else if (qobject_type(obj) == QTYPE_QLIST) {
         tos->entry = qlist_first(qobject_to_qlist(obj));
-        tos->first = true;
     }
 
     qiv->nb_stack++;
+    return tos->entry;
 }
 
 
@@ -192,40 +191,43 @@ static void qmp_input_start_struct(Visitor *v, const char *name, void **obj,
 }
 
 
-static void qmp_input_start_list(Visitor *v, const char *name, Error **errp)
+static void qmp_input_start_list(Visitor *v, const char *name,
+                                 GenericList **list, size_t size, Error **errp)
 {
     QmpInputVisitor *qiv = to_qiv(v);
     QObject *qobj = qmp_input_get_object(qiv, name, true);
+    const QListEntry *entry;
 
     if (!qobj || qobject_type(qobj) != QTYPE_QLIST) {
+        if (list) {
+            *list = NULL;
+        }
         error_setg(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null",
                    "list");
         return;
     }
 
-    qmp_input_push(qiv, qobj, errp);
+    entry = qmp_input_push(qiv, qobj, errp);
+    if (list) {
+        if (entry) {
+            *list = g_malloc0(size);
+        } else {
+            *list = NULL;
+        }
+    }
 }
 
-static GenericList *qmp_input_next_list(Visitor *v, GenericList **list,
+static GenericList *qmp_input_next_list(Visitor *v, GenericList *tail,
                                         size_t size)
 {
     QmpInputVisitor *qiv = to_qiv(v);
-    GenericList *entry;
     StackObject *so = &qiv->stack[qiv->nb_stack - 1];
 
     if (!so->entry) {
         return NULL;
     }
-
-    entry = g_malloc0(size);
-    if (so->first) {
-        *list = entry;
-        so->first = false;
-    } else {
-        (*list)->next = entry;
-    }
-
-    return entry;
+    tail->next = g_malloc0(size);
+    return tail->next;
 }
 
 
diff --git a/qapi/qmp-output-visitor.c b/qapi/qmp-output-visitor.c
index 232ad58221..4d3cf78333 100644
--- a/qapi/qmp-output-visitor.c
+++ b/qapi/qmp-output-visitor.c
@@ -22,7 +22,6 @@
 typedef struct QStackEntry
 {
     QObject *value;
-    bool is_list_head;
     QTAILQ_ENTRY(QStackEntry) node;
 } QStackEntry;
 
@@ -52,9 +51,6 @@ static void qmp_output_push_obj(QmpOutputVisitor *qov, QObject *value)
     assert(qov->root);
     assert(value);
     e->value = value;
-    if (qobject_type(e->value) == QTYPE_QLIST) {
-        e->is_list_head = true;
-    }
     QTAILQ_INSERT_HEAD(&qov->stack, e, node);
 }
 
@@ -118,7 +114,9 @@ static void qmp_output_end_struct(Visitor *v)
     assert(qobject_type(value) == QTYPE_QDICT);
 }
 
-static void qmp_output_start_list(Visitor *v, const char *name, Error **errp)
+static void qmp_output_start_list(Visitor *v, const char *name,
+                                  GenericList **listp, size_t size,
+                                  Error **errp)
 {
     QmpOutputVisitor *qov = to_qov(v);
     QList *list = qlist_new();
@@ -127,20 +125,10 @@ static void qmp_output_start_list(Visitor *v, const char *name, Error **errp)
     qmp_output_push(qov, list);
 }
 
-static GenericList *qmp_output_next_list(Visitor *v, GenericList **listp,
+static GenericList *qmp_output_next_list(Visitor *v, GenericList *tail,
                                          size_t size)
 {
-    GenericList *list = *listp;
-    QmpOutputVisitor *qov = to_qov(v);
-    QStackEntry *e = QTAILQ_FIRST(&qov->stack);
-
-    assert(e);
-    if (e->is_list_head) {
-        e->is_list_head = false;
-        return list;
-    }
-
-    return list ? list->next : NULL;
+    return tail->next;
 }
 
 static void qmp_output_end_list(Visitor *v)
diff --git a/qapi/string-input-visitor.c b/qapi/string-input-visitor.c
index ad150dccf6..30b58791c9 100644
--- a/qapi/string-input-visitor.c
+++ b/qapi/string-input-visitor.c
@@ -25,8 +25,6 @@ struct StringInputVisitor
 {
     Visitor visitor;
 
-    bool head;
-
     GList *ranges;
     GList *cur_range;
     int64_t cur;
@@ -128,11 +126,16 @@ error:
 }
 
 static void
-start_list(Visitor *v, const char *name, Error **errp)
+start_list(Visitor *v, const char *name, GenericList **list, size_t size,
+           Error **errp)
 {
     StringInputVisitor *siv = to_siv(v);
 
+    /* We don't support visits without a list */
+    assert(list);
+
     if (parse_str(siv, name, errp) < 0) {
+        *list = NULL;
         return;
     }
 
@@ -142,13 +145,15 @@ start_list(Visitor *v, const char *name, Error **errp)
         if (r) {
             siv->cur = r->begin;
         }
+        *list = g_malloc0(size);
+    } else {
+        *list = NULL;
     }
 }
 
-static GenericList *next_list(Visitor *v, GenericList **list, size_t size)
+static GenericList *next_list(Visitor *v, GenericList *tail, size_t size)
 {
     StringInputVisitor *siv = to_siv(v);
-    GenericList **link;
     Range *r;
 
     if (!siv->ranges || !siv->cur_range) {
@@ -172,21 +177,12 @@ static GenericList *next_list(Visitor *v, GenericList **list, size_t size)
         siv->cur = r->begin;
     }
 
-    if (siv->head) {
-        link = list;
-        siv->head = false;
-    } else {
-        link = &(*list)->next;
-    }
-
-    *link = g_malloc0(size);
-    return *link;
+    tail->next = g_malloc0(size);
+    return tail->next;
 }
 
 static void end_list(Visitor *v)
 {
-    StringInputVisitor *siv = to_siv(v);
-    siv->head = true;
 }
 
 static void parse_type_int64(Visitor *v, const char *name, int64_t *obj,
@@ -369,6 +365,5 @@ StringInputVisitor *string_input_visitor_new(const char *str)
     v->visitor.optional = parse_optional;
 
     v->string = str;
-    v->head = true;
     return v;
 }
diff --git a/qapi/string-output-visitor.c b/qapi/string-output-visitor.c
index 0d44d7e6a5..d01319628b 100644
--- a/qapi/string-output-visitor.c
+++ b/qapi/string-output-visitor.c
@@ -20,7 +20,7 @@
 
 enum ListMode {
     LM_NONE,             /* not traversing a list of repeated options */
-    LM_STARTED,          /* start_list() succeeded */
+    LM_STARTED,          /* next_list() ready to be called */
 
     LM_IN_PROGRESS,      /* next_list() has been called.
                           *
@@ -48,7 +48,7 @@ enum ListMode {
 
     LM_UNSIGNED_INTERVAL,/* Same as above, only for an unsigned interval. */
 
-    LM_END
+    LM_END,              /* next_list() called, about to see last element. */
 };
 
 typedef enum ListMode ListMode;
@@ -58,7 +58,6 @@ struct StringOutputVisitor
     Visitor visitor;
     bool human;
     GString *string;
-    bool head;
     ListMode list_mode;
     union {
         int64_t s;
@@ -266,39 +265,29 @@ static void print_type_number(Visitor *v, const char *name, double *obj,
 }
 
 static void
-start_list(Visitor *v, const char *name, Error **errp)
+start_list(Visitor *v, const char *name, GenericList **list, size_t size,
+           Error **errp)
 {
     StringOutputVisitor *sov = to_sov(v);
 
     /* we can't traverse a list in a list */
     assert(sov->list_mode == LM_NONE);
-    sov->list_mode = LM_STARTED;
-    sov->head = true;
+    /* We don't support visits without a list */
+    assert(list);
+    /* List handling is only needed if there are at least two elements */
+    if (*list && (*list)->next) {
+        sov->list_mode = LM_STARTED;
+    }
 }
 
-static GenericList *next_list(Visitor *v, GenericList **list, size_t size)
+static GenericList *next_list(Visitor *v, GenericList *tail, size_t size)
 {
     StringOutputVisitor *sov = to_sov(v);
-    GenericList *ret = NULL;
-    if (*list) {
-        if (sov->head) {
-            ret = *list;
-        } else {
-            ret = (*list)->next;
-        }
+    GenericList *ret = tail->next;
 
-        if (sov->head) {
-            if (ret && ret->next == NULL) {
-                sov->list_mode = LM_NONE;
-            }
-            sov->head = false;
-        } else {
-            if (ret && ret->next == NULL) {
-                sov->list_mode = LM_END;
-            }
-        }
+    if (ret && !ret->next) {
+        sov->list_mode = LM_END;
     }
-
     return ret;
 }
 
@@ -311,8 +300,6 @@ static void end_list(Visitor *v)
            sov->list_mode == LM_NONE ||
            sov->list_mode == LM_IN_PROGRESS);
     sov->list_mode = LM_NONE;
-    sov->head = true;
-
 }
 
 char *string_output_get_string(StringOutputVisitor *sov)
diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
index bdf8971440..8b7efcc74a 100644
--- a/scripts/qapi-visit.py
+++ b/scripts/qapi-visit.py
@@ -117,18 +117,20 @@ def gen_visit_list(name, element_type):
 void visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s **obj, Error **errp)
 {
     Error *err = NULL;
-    GenericList *i, **prev;
+    %(c_name)s *tail;
+    size_t size = sizeof(**obj);
 
-    visit_start_list(v, name, &err);
+    visit_start_list(v, name, (GenericList **)obj, size, &err);
     if (err) {
         goto out;
     }
 
-    for (prev = (GenericList **)obj;
-         !err && (i = visit_next_list(v, prev, sizeof(**obj))) != NULL;
-         prev = &i) {
-        %(c_name)s *native_i = (%(c_name)s *)i;
-        visit_type_%(c_elt_type)s(v, NULL, &native_i->value, &err);
+    for (tail = *obj; tail;
+         tail = (%(c_name)s *)visit_next_list(v, (GenericList *)tail, size)) {
+        visit_type_%(c_elt_type)s(v, NULL, &tail->value, &err);
+        if (err) {
+            break;
+        }
     }
 
     visit_end_list(v);
diff --git a/tests/test-string-input-visitor.c b/tests/test-string-input-visitor.c
index f99824d240..5a56920222 100644
--- a/tests/test-string-input-visitor.c
+++ b/tests/test-string-input-visitor.c
@@ -98,8 +98,6 @@ static void test_visitor_in_intList(TestInputVisitorData *data,
 
     v = visitor_input_test_init(data, "not an int list");
 
-    /* FIXME: res should be NULL on failure, regardless of starting value */
-    res = NULL;
     visit_type_int16List(v, NULL, &res, &err);
     error_free_or_abort(&err);
     g_assert(!res);