Skip to contentSkip to conversation

qapi: rewrite string-input-visitor

PATCH Superseded David Hildenbrand 3 participants 34 messages Nov 15, 2018 – Nov 19, 2018

v1 of 3 Superseded Latest is v3 →

lore
Share & tools
lore
#0 lore raw

cover qapi: rewrite string-input-visitor

Rewrite string-input-visitor to be (hopefully) less ugly. Support int and uint lists (including ranges, but not implemented via type "Range").

Virtual walks are now supported and more errors are cought (and some bugs fixed). Fix and extend the tests. Parsing of uint64_t is now properly supported.

Importantly, when parsing a list we now return the list and not an ordered set (we are not an ordered set parser after all). Whoever needs that can add it on top. As far as I can see, current code can deal with it but I'll have to look at the details.

While at it, introduce and use qemu_strtod() and qemu_strtod_finite(). However don't convert monitor.c as that seems to be too broken for me (it ignores both conversion errors and range errors, and there is an actual unit test in which conversion is expected to fail).

RFC - v1:
- Too much to name it all, just some highlights
- Pull some tests before the rework
- Introduce qemu_strtod_finite() and use it in more places
- More tests
- "qapi: rewrite string-input-visitor"
-- programming error -> assert
-- g_assert() -> assert(), g_assert_not_reached() -> abort() -- Accept single-element ranges "1-1"
-- Split parsing and returning the next range element -- Minor style fixes

David Hildenbrand (9):
cutils: add qemu_strtod() and qemu_strtod_finite()
cutils: use qemu_strtod_finite() in do_strtosz()
qapi: use qemu_strtod_finite() in string-input-visitor
qapi: use qemu_strtod_finite() in qobject-input-visitor
test-string-input-visitor: add more tests
qapi: rewrite string-input-visitor
test-string-input-visitor: use virtual walk
test-string-input-visitor: split off uint64 list tests
test-string-input-visitor: add range overflow tests

 include/qapi/string-input-visitor.h |   4 +-
 include/qemu/cutils.h               |   8 +-
 monitor.c                           |   2 +-
 qapi/qobject-input-visitor.c        |   9 +-
 qapi/string-input-visitor.c         | 418 ++++++++++++++++------------
 tests/test-cutils.c                 |  14 +-
 tests/test-string-input-visitor.c   | 172 +++++++++---
 util/cutils.c                       |  52 +++-
 8 files changed, 432 insertions(+), 247 deletions(-)
Signature · 1 line
2.17.2
#1 lore raw

PATCH 1/9 cutils: add qemu_strtod() and qemu_strtod_finite()

Let's provide a wrapper for strtod().

---
 include/qemu/cutils.h |  2 ++
 util/cutils.c         | 38 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 40 insertions(+)
include/qemu/cutils.h +2 -0
index 7071bfe2d4..756b41c193 100644
@@ -146,6 +146,8 @@ int qemu_strtoi64(const char *nptr, const char **endptr, int base,
                   int64_t *result);
 int qemu_strtou64(const char *nptr, const char **endptr, int base,
                   uint64_t *result);
+int qemu_strtod(const char *nptr, const char **endptr, double *result);
+int qemu_strtod_finite(const char *nptr, const char **endptr, double *result);
 
 int parse_uint(const char *s, unsigned long long *value, char **endptr,
                int base);
util/cutils.c +38 -0
index 698bd315bd..7868a683e8 100644
@@ -544,6 +544,44 @@ int qemu_strtou64(const char *nptr, const char **endptr, int base,
     return check_strtox_error(nptr, ep, endptr, errno);
 }
 
+/**
+ * Convert string @nptr to a double.
+ *
+ * Works like qemu_strtoul(), except it stores +/-HUGE_VAL on
+ * overflow/underflow.
+ */
+int qemu_strtod(const char *nptr, const char **endptr, double *result)
+{
+    char *ep;
+
+    if (!nptr) {
+        if (endptr) {
+            *endptr = nptr;
+        }
+        return -EINVAL;
+    }
+
+    errno = 0;
+    *result = strtod(nptr, &ep);
+    return check_strtox_error(nptr, ep, endptr, errno);
+}
+
+/**
+ * Convert string @nptr to a finite double.
+ *
+ * Works like qemu_strtoul(), except it stores +/-HUGE_VAL on
+ * overflow/underflow. "NaN" or "inf" are rejcted with -EINVAL.
+ */
+int qemu_strtod_finite(const char *nptr, const char **endptr, double *result)
+{
+    int ret = qemu_strtod(nptr, endptr, result);
+
+    if (!ret && !isfinite(*result)) {
+        return -EINVAL;
+    }
+    return ret;
+}
+
 /**
  * Searches for the first occurrence of 'c' in 's', and returns a pointer
  * to the trailing null byte if none was found.
Signature · 1 line
2.17.2
#7 lore raw

PATCH 2/9 cutils: use qemu_strtod_finite() in do_strtosz()

In reply to David Hildenbrand

Let's use the new function. In order to do so, we have to convert all users of qemu_strtosz*() to pass a "const char **end" ptr.

We will now also reject "inf" properly.

---
 include/qemu/cutils.h |  6 +++---
 monitor.c             |  2 +-
 tests/test-cutils.c   | 14 +++++++-------
 util/cutils.c         | 14 ++++++--------
 4 files changed, 17 insertions(+), 19 deletions(-)
include/qemu/cutils.h +3 -3
index 756b41c193..d2dad3057c 100644
@@ -153,9 +153,9 @@ int parse_uint(const char *s, unsigned long long *value, char **endptr,
                int base);
 int parse_uint_full(const char *s, unsigned long long *value, int base);
 
-int qemu_strtosz(const char *nptr, char **end, uint64_t *result);
-int qemu_strtosz_MiB(const char *nptr, char **end, uint64_t *result);
-int qemu_strtosz_metric(const char *nptr, char **end, uint64_t *result);
+int qemu_strtosz(const char *nptr, const char **end, uint64_t *result);
+int qemu_strtosz_MiB(const char *nptr, const char **end, uint64_t *result);
+int qemu_strtosz_metric(const char *nptr, const char **end, uint64_t *result);
 
 /* used to print char* safely */
 #define STR_OR_NULL(str) ((str) ? (str) : "null")
monitor.c +1 -1
index d39390c2f2..ee9893c785 100644
@@ -3231,7 +3231,7 @@ static QDict *monitor_parse_arguments(Monitor *mon,
             {
                 int ret;
                 uint64_t val;
-                char *end;
+                const char *end;
 
                 while (qemu_isspace(*p)) {
                     p++;
tests/test-cutils.c +7 -7
index d85c3e0f6d..e21dcbf12b 100644
@@ -1950,7 +1950,7 @@ static void test_qemu_strtou64_full_max(void)
 static void test_qemu_strtosz_simple(void)
 {
     const char *str;
-    char *endptr = NULL;
+    const char *endptr = NULL;
     int err;
     uint64_t res = 0xbaadf00d;
 
@@ -2017,7 +2017,7 @@ static void test_qemu_strtosz_units(void)
     const char *p = "1P";
     const char *e = "1E";
     int err;
-    char *endptr = NULL;
+    const char *endptr = NULL;
     uint64_t res = 0xbaadf00d;
 
     /* default is M */
@@ -2066,7 +2066,7 @@ static void test_qemu_strtosz_float(void)
 {
     const char *str = "12.345M";
     int err;
-    char *endptr = NULL;
+    const char *endptr = NULL;
     uint64_t res = 0xbaadf00d;
 
     err = qemu_strtosz(str, &endptr, &res);
@@ -2078,7 +2078,7 @@ static void test_qemu_strtosz_float(void)
 static void test_qemu_strtosz_invalid(void)
 {
     const char *str;
-    char *endptr = NULL;
+    const char *endptr = NULL;
     int err;
     uint64_t res = 0xbaadf00d;
 
@@ -2101,7 +2101,7 @@ static void test_qemu_strtosz_invalid(void)
 static void test_qemu_strtosz_trailing(void)
 {
     const char *str;
-    char *endptr = NULL;
+    const char *endptr = NULL;
     int err;
     uint64_t res = 0xbaadf00d;
 
@@ -2126,7 +2126,7 @@ static void test_qemu_strtosz_trailing(void)
 static void test_qemu_strtosz_erange(void)
 {
     const char *str;
-    char *endptr = NULL;
+    const char *endptr = NULL;
     int err;
     uint64_t res = 0xbaadf00d;
 
@@ -2160,7 +2160,7 @@ static void test_qemu_strtosz_metric(void)
 {
     const char *str = "12345k";
     int err;
-    char *endptr = NULL;
+    const char *endptr = NULL;
     uint64_t res = 0xbaadf00d;
 
     err = qemu_strtosz_metric(str, &endptr, &res);
util/cutils.c +6 -8
index 7868a683e8..dd61fb4558 100644
@@ -206,19 +206,17 @@ static int64_t suffix_mul(char suffix, int64_t unit)
  * in *end, if not NULL. Return -ERANGE on overflow, Return -EINVAL on
  * other error.
  */
-static int do_strtosz(const char *nptr, char **end,
+static int do_strtosz(const char *nptr, const char **end,
                       const char default_suffix, int64_t unit,
                       uint64_t *result)
 {
     int retval;
-    char *endptr;
+    const char *endptr;
     unsigned char c;
     int mul_required = 0;
     double val, mul, integral, fraction;
 
-    errno = 0;
-    val = strtod(nptr, &endptr);
-    if (isnan(val) || endptr == nptr || errno != 0) {
+    if (qemu_strtod_finite(nptr, &endptr, &val)) {
         retval = -EINVAL;
         goto out;
     }
@@ -259,17 +257,17 @@ out:
     return retval;
 }
 
-int qemu_strtosz(const char *nptr, char **end, uint64_t *result)
+int qemu_strtosz(const char *nptr, const char **end, uint64_t *result)
 {
     return do_strtosz(nptr, end, 'B', 1024, result);
 }
 
-int qemu_strtosz_MiB(const char *nptr, char **end, uint64_t *result)
+int qemu_strtosz_MiB(const char *nptr, const char **end, uint64_t *result)
 {
     return do_strtosz(nptr, end, 'M', 1024, result);
 }
 
-int qemu_strtosz_metric(const char *nptr, char **end, uint64_t *result)
+int qemu_strtosz_metric(const char *nptr, const char **end, uint64_t *result)
 {
     return do_strtosz(nptr, end, 'B', 1000, result);
 }
Signature · 1 line
2.17.2
#11 lore raw

PATCH 3/9 qapi: use qemu_strtod_finite() in string-input-visitor

In reply to David Hildenbrand

Let's use the new function. "NaN" and "inf" are now properly rejected.

---
 qapi/string-input-visitor.c | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)
qapi/string-input-visitor.c +2 -4
index b3fdd0827d..b89c6c4e06 100644
@@ -20,6 +20,7 @@
 #include "qemu/option.h"
 #include "qemu/queue.h"
 #include "qemu/range.h"
+#include "qemu/cutils.h"
 
 
 struct StringInputVisitor
@@ -313,12 +314,9 @@ static void parse_type_number(Visitor *v, const char *name, double *obj,
                               Error **errp)
 {
     StringInputVisitor *siv = to_siv(v);
-    char *endp = (char *) siv->string;
     double val;
 
-    errno = 0;
-    val = strtod(siv->string, &endp);
-    if (errno || endp == siv->string || *endp) {
+    if (qemu_strtod_finite(siv->string, NULL, &val)) {
         error_setg(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null",
                    "number");
         return;
Signature · 1 line
2.17.2
#16 lore raw

PATCH 4/9 qapi: use qemu_strtod_finite() in qobject-input-visitor

In reply to David Hildenbrand

Let's use the new function. Just as current behavior, we have to consume the whole string (now it's just way clearer what's going on).

---
 qapi/qobject-input-visitor.c | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)
qapi/qobject-input-visitor.c +5 -4
index 3e88b27f9e..07465f9947 100644
@@ -562,19 +562,20 @@ static void qobject_input_type_number_keyval(Visitor *v, const char *name,
 {
     QObjectInputVisitor *qiv = to_qiv(v);
     const char *str = qobject_input_get_keyval(qiv, name, errp);
-    char *endp;
+    double val;
 
     if (!str) {
         return;
     }
 
-    errno = 0;
-    *obj = strtod(str, &endp);
-    if (errno || endp == str || *endp || !isfinite(*obj)) {
+    if (qemu_strtod_finite(str, NULL, &val)) {
         /* TODO report -ERANGE more nicely */
         error_setg(errp, QERR_INVALID_PARAMETER_TYPE,
                    full_name(qiv, name), "number");
+        return;
     }
+
+    *obj = val;
 }
 
 static void qobject_input_type_any(Visitor *v, const char *name, QObject **obj,
Signature · 1 line
2.17.2
#19 lore raw

PATCH 5/9 test-string-input-visitor: add more tests

In reply to David Hildenbrand

Test that very big/small values are not accepted and that ranges with only one element work.

Rename expect4 to expect5, as we will be moving that to a separate ulist test after the rework.

---
 tests/test-string-input-visitor.c | 22 ++++++++++++++++++++--
 1 file changed, 20 insertions(+), 2 deletions(-)
tests/test-string-input-visitor.c +20 -2
index 88e0e1aa9a..f55e0696c0 100644
@@ -121,7 +121,8 @@ static void test_visitor_in_intList(TestInputVisitorData *data,
     int64_t expect1[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 20 };
     int64_t expect2[] = { 32767, -32768, -32767 };
     int64_t expect3[] = { INT64_MAX, INT64_MIN };
-    uint64_t expect4[] = { UINT64_MAX };
+    int64_t expect4[] = { 1 };
+    uint64_t expect5[] = { UINT64_MAX };
     Error *err = NULL;
     int64List *res = NULL;
     int64List *tail;
@@ -140,8 +141,25 @@ static void test_visitor_in_intList(TestInputVisitorData *data,
                                 "-9223372036854775808,9223372036854775807");
     check_ilist(v, expect3, ARRAY_SIZE(expect3));
 
+    v = visitor_input_test_init(data, "1-1");
+    check_ilist(v, expect4, ARRAY_SIZE(expect4));
+
     v = visitor_input_test_init(data, "18446744073709551615");
-    check_ulist(v, expect4, ARRAY_SIZE(expect4));
+    check_ulist(v, expect5, ARRAY_SIZE(expect5));
+
+    /* Value too large */
+
+    v = visitor_input_test_init(data, "9223372036854775808");
+    visit_type_int64List(v, NULL, &res, &err);
+    error_free_or_abort(&err);
+    g_assert(!res);
+
+    /* Value too small */
+
+    v = visitor_input_test_init(data, "-9223372036854775809");
+    visit_type_int64List(v, NULL, &res, &err);
+    error_free_or_abort(&err);
+    g_assert(!res);
 
     /* Empty list */
 
Signature · 1 line
2.17.2
#23 lore raw

PATCH 6/9 qapi: rewrite string-input-visitor

In reply to David Hildenbrand

The input visitor has some problems right now, especially
- unsigned type "Range" is used to process signed ranges, resulting in inconsistent behavior and ugly/magical code
- uint64_t are parsed like int64_t, so big uint64_t values are not supported and error messages are misleading
- lists/ranges of int64_t are accepted although no list is parsed and we should rather report an error
- lists/ranges are preparsed using int64_t, making it hard to implement uint64_t values or uint64_t lists
- types that don't support lists don't bail out
- visiting beyond the end of a list is not handled properly

So let's rewrite it by getting rid of usage of the type "Range" and properly supporting lists of int64_t and uint64_t (including ranges of both types), fixing the above mentioned issues.

Lists of other types are not supported and will properly report an error. Virtual walks are now supported.

Tests have to be fixed up:
- Two BUGs were hardcoded that are fixed now
- The string-input-visitor now actually returns a parsed list and not an ordered set.

---
 include/qapi/string-input-visitor.h |   4 +-
 qapi/string-input-visitor.c         | 410 ++++++++++++++++------------
 tests/test-string-input-visitor.c   |  18 +-
 3 files changed, 239 insertions(+), 193 deletions(-)
include/qapi/string-input-visitor.h +2 -2
index 33551340e3..921f3875b9 100644
@@ -19,8 +19,8 @@ typedef struct StringInputVisitor StringInputVisitor;
 
 /*
  * The string input visitor does not implement support for visiting
- * QAPI structs, alternates, null, or arbitrary QTypes.  It also
- * requires a non-null list argument to visit_start_list().
+ * QAPI structs, alternates, null, or arbitrary QTypes. Only flat lists
+ * of integers (except type "size") are supported.
  */
 Visitor *string_input_visitor_new(const char *str);
 
qapi/string-input-visitor.c +233 -177

534 lines not on this page · raw message

tests/test-string-input-visitor.c +4 -14
index f55e0696c0..a198aedfce 100644
@@ -92,16 +92,6 @@ static void check_ulist(Visitor *v, uint64_t *expected, size_t n)
     uint64List *tail;
     int i;
 
-    /* BUG: unsigned numbers above INT64_MAX don't work */
-    for (i = 0; i < n; i++) {
-        if (expected[i] > INT64_MAX) {
-            Error *err = NULL;
-            visit_type_uint64List(v, NULL, &res, &err);
-            error_free_or_abort(&err);
-            return;
-        }
-    }
-
     visit_type_uint64List(v, NULL, &res, &error_abort);
     tail = res;
     for (i = 0; i < n; i++) {
@@ -117,10 +107,10 @@ static void check_ulist(Visitor *v, uint64_t *expected, size_t n)
 static void test_visitor_in_intList(TestInputVisitorData *data,
                                     const void *unused)
 {
-    /* Note: the visitor *sorts* ranges *unsigned* */
-    int64_t expect1[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 20 };
+    int64_t expect1[] = { 1, 2, 0, 2, 3, 4, 20, 5, 6, 7,
+                          8, 9, 1, 2, 3, 4, 5, 6, 7, 8 };
     int64_t expect2[] = { 32767, -32768, -32767 };
-    int64_t expect3[] = { INT64_MAX, INT64_MIN };
+    int64_t expect3[] = { INT64_MIN, INT64_MAX };
     int64_t expect4[] = { 1 };
     uint64_t expect5[] = { UINT64_MAX };
     Error *err = NULL;
@@ -207,7 +197,7 @@ static void test_visitor_in_intList(TestInputVisitorData *data,
     visit_type_int64(v, NULL, &tail->value, &err);
     g_assert_cmpint(tail->value, ==, 0);
     visit_type_int64(v, NULL, &val, &err);
-    g_assert_cmpint(val, ==, 1); /* BUG */
+    error_free_or_abort(&err);
     visit_check_list(v, &error_abort);
     visit_end_list(v, (void **)&res);
 
Signature · 1 line
2.17.2
#28 lore raw

PATCH 7/9 test-string-input-visitor: use virtual walk

In reply to David Hildenbrand

We now support virtual walks, so use that instead.

---
 tests/test-string-input-visitor.c | 36 +++++++++++--------------------
 1 file changed, 12 insertions(+), 24 deletions(-)
tests/test-string-input-visitor.c +12 -24
index a198aedfce..4ad4cc0b56 100644
@@ -115,7 +115,6 @@ static void test_visitor_in_intList(TestInputVisitorData *data,
     uint64_t expect5[] = { UINT64_MAX };
     Error *err = NULL;
     int64List *res = NULL;
-    int64List *tail;
     Visitor *v;
     int64_t val;
 
@@ -169,39 +168,28 @@ static void test_visitor_in_intList(TestInputVisitorData *data,
 
     v = visitor_input_test_init(data, "0,2-3");
 
-    /* Would be simpler if the visitor genuinely supported virtual walks */
-    visit_start_list(v, NULL, (GenericList **)&res, sizeof(*res),
-                     &error_abort);
-    tail = res;
-    visit_type_int64(v, NULL, &tail->value, &error_abort);
-    g_assert_cmpint(tail->value, ==, 0);
-    tail = (int64List *)visit_next_list(v, (GenericList *)tail, sizeof(*res));
-    g_assert(tail);
-    visit_type_int64(v, NULL, &tail->value, &error_abort);
-    g_assert_cmpint(tail->value, ==, 2);
-    tail = (int64List *)visit_next_list(v, (GenericList *)tail, sizeof(*res));
-    g_assert(tail);
+    visit_start_list(v, NULL, NULL, 0, &error_abort);
+    visit_type_int64(v, NULL, &val, &error_abort);
+    g_assert_cmpint(val, ==, 0);
+    visit_type_int64(v, NULL, &val, &error_abort);
+    g_assert_cmpint(val, ==, 2);
 
     visit_check_list(v, &err);
     error_free_or_abort(&err);
-    visit_end_list(v, (void **)&res);
-
-    qapi_free_int64List(res);
+    visit_end_list(v, NULL);
 
     /* Visit beyond end of list */
+
     v = visitor_input_test_init(data, "0");
 
-    visit_start_list(v, NULL, (GenericList **)&res, sizeof(*res),
-                     &error_abort);
-    tail = res;
-    visit_type_int64(v, NULL, &tail->value, &err);
-    g_assert_cmpint(tail->value, ==, 0);
+    visit_start_list(v, NULL, NULL, 0, &error_abort);
+    visit_type_int64(v, NULL, &val, &err);
+    g_assert_cmpint(val, ==, 0);
     visit_type_int64(v, NULL, &val, &err);
     error_free_or_abort(&err);
-    visit_check_list(v, &error_abort);
-    visit_end_list(v, (void **)&res);
 
-    qapi_free_int64List(res);
+    visit_check_list(v, &error_abort);
+    visit_end_list(v, NULL);
 }
 
 static void test_visitor_in_bool(TestInputVisitorData *data,
Signature · 1 line
2.17.2
#30 lore raw

PATCH 8/9 test-string-input-visitor: split off uint64 list tests

In reply to David Hildenbrand

Basically copy all int64 list tests but adapt them to work on uint64 instead. The values for very big/very small values have to be adapted.

---
 tests/test-string-input-visitor.c | 94 +++++++++++++++++++++++++++++--
 1 file changed, 90 insertions(+), 4 deletions(-)
tests/test-string-input-visitor.c +90 -4
index 4ad4cc0b56..d2ec453252 100644
@@ -112,7 +112,6 @@ static void test_visitor_in_intList(TestInputVisitorData *data,
     int64_t expect2[] = { 32767, -32768, -32767 };
     int64_t expect3[] = { INT64_MIN, INT64_MAX };
     int64_t expect4[] = { 1 };
-    uint64_t expect5[] = { UINT64_MAX };
     Error *err = NULL;
     int64List *res = NULL;
     Visitor *v;
@@ -133,9 +132,6 @@ static void test_visitor_in_intList(TestInputVisitorData *data,
     v = visitor_input_test_init(data, "1-1");
     check_ilist(v, expect4, ARRAY_SIZE(expect4));
 
-    v = visitor_input_test_init(data, "18446744073709551615");
-    check_ulist(v, expect5, ARRAY_SIZE(expect5));
-
     /* Value too large */
 
     v = visitor_input_test_init(data, "9223372036854775808");
@@ -192,6 +188,94 @@ static void test_visitor_in_intList(TestInputVisitorData *data,
     visit_end_list(v, NULL);
 }
 
+static void test_visitor_in_uintList(TestInputVisitorData *data,
+                                     const void *unused)
+{
+    uint64_t expect1[] = { 1, 2, 0, 2, 3, 4, 20, 5, 6, 7,
+                           8, 9, 1, 2, 3, 4, 5, 6, 7, 8 };
+    uint64_t expect2[] = { 32767, -32768, -32767 };
+    uint64_t expect3[] = { INT64_MIN, INT64_MAX };
+    uint64_t expect4[] = { 1 };
+    uint64_t expect5[] = { UINT64_MAX };
+    Error *err = NULL;
+    uint64List *res = NULL;
+    Visitor *v;
+    uint64_t val;
+
+    /* Valid lists */
+
+    v = visitor_input_test_init(data, "1,2,0,2-4,20,5-9,1-8");
+    check_ulist(v, expect1, ARRAY_SIZE(expect1));
+
+    v = visitor_input_test_init(data, "32767,-32768--32767");
+    check_ulist(v, expect2, ARRAY_SIZE(expect2));
+
+    v = visitor_input_test_init(data,
+                                "-9223372036854775808,9223372036854775807");
+    check_ulist(v, expect3, ARRAY_SIZE(expect3));
+
+    v = visitor_input_test_init(data, "1-1");
+    check_ulist(v, expect4, ARRAY_SIZE(expect4));
+
+    v = visitor_input_test_init(data, "18446744073709551615");
+    check_ulist(v, expect5, ARRAY_SIZE(expect5));
+
+    /* Value too large */
+
+    v = visitor_input_test_init(data, "18446744073709551616");
+    visit_type_uint64List(v, NULL, &res, &err);
+    error_free_or_abort(&err);
+    g_assert(!res);
+
+    /* Value too small */
+
+    v = visitor_input_test_init(data, "-18446744073709551616");
+    visit_type_uint64List(v, NULL, &res, &err);
+    error_free_or_abort(&err);
+    g_assert(!res);
+
+    /* Empty list */
+
+    v = visitor_input_test_init(data, "");
+    visit_type_uint64List(v, NULL, &res, &error_abort);
+    g_assert(!res);
+
+    /* Not a list */
+
+    v = visitor_input_test_init(data, "not an uint list");
+
+    visit_type_uint64List(v, NULL, &res, &err);
+    error_free_or_abort(&err);
+    g_assert(!res);
+
+    /* Unvisited list tail */
+
+    v = visitor_input_test_init(data, "0,2-3");
+
+    visit_start_list(v, NULL, NULL, 0, &error_abort);
+    visit_type_uint64(v, NULL, &val, &error_abort);
+    g_assert_cmpuint(val, ==, 0);
+    visit_type_uint64(v, NULL, &val, &error_abort);
+    g_assert_cmpuint(val, ==, 2);
+
+    visit_check_list(v, &err);
+    error_free_or_abort(&err);
+    visit_end_list(v, NULL);
+
+    /* Visit beyond end of list */
+
+    v = visitor_input_test_init(data, "0");
+
+    visit_start_list(v, NULL, NULL, 0, &error_abort);
+    visit_type_uint64(v, NULL, &val, &err);
+    g_assert_cmpuint(val, ==, 0);
+    visit_type_uint64(v, NULL, &val, &err);
+    error_free_or_abort(&err);
+
+    visit_check_list(v, &error_abort);
+    visit_end_list(v, NULL);
+}
+
 static void test_visitor_in_bool(TestInputVisitorData *data,
                                  const void *unused)
 {
@@ -352,6 +436,8 @@ int main(int argc, char **argv)
                            &in_visitor_data, test_visitor_in_int);
     input_visitor_test_add("/string-visitor/input/intList",
                            &in_visitor_data, test_visitor_in_intList);
+    input_visitor_test_add("/string-visitor/input/uintList",
+                           &in_visitor_data, test_visitor_in_uintList);
     input_visitor_test_add("/string-visitor/input/bool",
                            &in_visitor_data, test_visitor_in_bool);
     input_visitor_test_add("/string-visitor/input/number",
Signature · 1 line
2.17.2
#32 lore raw

PATCH 9/9 test-string-input-visitor: add range overflow tests

In reply to David Hildenbrand

Let's make sure that the range handling code can properly deal with ranges that end at the biggest possible number.

---
 tests/test-string-input-visitor.c | 10 ++++++++++
 1 file changed, 10 insertions(+)
tests/test-string-input-visitor.c +10 -0
index d2ec453252..86b1f9d593 100644
@@ -112,6 +112,7 @@ static void test_visitor_in_intList(TestInputVisitorData *data,
     int64_t expect2[] = { 32767, -32768, -32767 };
     int64_t expect3[] = { INT64_MIN, INT64_MAX };
     int64_t expect4[] = { 1 };
+    int64_t expect5[] = { INT64_MAX - 2,  INT64_MAX - 1, INT64_MAX };
     Error *err = NULL;
     int64List *res = NULL;
     Visitor *v;
@@ -132,6 +133,10 @@ static void test_visitor_in_intList(TestInputVisitorData *data,
     v = visitor_input_test_init(data, "1-1");
     check_ilist(v, expect4, ARRAY_SIZE(expect4));
 
+    v = visitor_input_test_init(data,
+                                "9223372036854775805-9223372036854775807");
+    check_ilist(v, expect5, ARRAY_SIZE(expect5));
+
     /* Value too large */
 
     v = visitor_input_test_init(data, "9223372036854775808");
@@ -197,6 +202,7 @@ static void test_visitor_in_uintList(TestInputVisitorData *data,
     uint64_t expect3[] = { INT64_MIN, INT64_MAX };
     uint64_t expect4[] = { 1 };
     uint64_t expect5[] = { UINT64_MAX };
+    uint64_t expect6[] = { UINT64_MAX - 2,  UINT64_MAX - 1, UINT64_MAX };
     Error *err = NULL;
     uint64List *res = NULL;
     Visitor *v;
@@ -220,6 +226,10 @@ static void test_visitor_in_uintList(TestInputVisitorData *data,
     v = visitor_input_test_init(data, "18446744073709551615");
     check_ulist(v, expect5, ARRAY_SIZE(expect5));
 
+    v = visitor_input_test_init(data,
+                                "18446744073709551613-18446744073709551615");
+    check_ulist(v, expect6, ARRAY_SIZE(expect6));
+
     /* Value too large */
 
     v = visitor_input_test_init(data, "18446744073709551616");
Signature · 1 line
2.17.2
Reviewed-by on v1 1/9
#2 lore raw

Re: [Qemu-devel] [PATCH v1 1/9] cutils: add qemu_strtod() and qemu_strtod_finite()

In reply to David Hildenbrand
Quoted 10 lines · David Hildenbrand

Let's provide a wrapper for strtod().

---
  include/qemu/cutils.h |  2 ++
  util/cutils.c         | 38 ++++++++++++++++++++++++++++++++++++++
  2 files changed, 40 insertions(+)

util/cutils.c int qemu_strtou64(const char *nptr, const char **endptr, int base, · line 573

Quoted 6 lines
util/cutils.c +6 -0
+
+/**
+ * Convert string @nptr to a finite double.
+ *
+ * Works like qemu_strtoul(), except it stores +/-HUGE_VAL on
+ * overflow/underflow. "NaN" or "inf" are rejcted with -EINVAL.

s/rejcted/rejected/

util/cutils.c int qemu_strtou64(const char *nptr, const char **endptr, int base, · line 577

Quoted 4 lines

+ */

util/cutils.c +3 -0
+int qemu_strtod_finite(const char *nptr, const char **endptr, double *result)
+{
+    int ret = qemu_strtod(nptr, endptr, result);

On overflow, result is set to HUGE_VAL (aka "inf") with ret set to -ERANGE. (The C standard uses HUGE_VAL rather than directly requiring infinity on overflow, in order to cater to museum platforms where the largest representable double is still finite; but no one develops qemu on a non-IEEE machine these days so we know that HUGE_VAL == INF).

util/cutils.c int qemu_strtou64(const char *nptr, const char **endptr, int base, · line 581

Quoted 4 lines
util/cutils.c +4 -0
+
+    if (!ret && !isfinite(*result)) {
+        return -EINVAL;
+    }

This check means that overflow ("1e9999") fails with -ERANGE, while actual infinity ("inf") fails with -EINVAL, letting the user distinguish between the two. Still, I wonder if assigning a non-finite value into result on -ERANGE is the wisest course of action. We'll just have to see in the next patches that use this.

With the typo fix,

Signature · 3 lines
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org
#8 lore raw

Re: [Qemu-devel] [PATCH v1 2/9] cutils: use qemu_strtod_finite() in do_strtosz()

In reply to David Hildenbrand
Quoted 21 lines · David Hildenbrand

Let's use the new function. In order to do so, we have to convert all users of qemu_strtosz*() to pass a "const char **end" ptr.

We will now also reject "inf" properly.

---
  include/qemu/cutils.h |  6 +++---
  monitor.c             |  2 +-
  tests/test-cutils.c   | 14 +++++++-------
  util/cutils.c         | 14 ++++++--------
  4 files changed, 17 insertions(+), 19 deletions(-)

+++ b/tests/test-cutils.c

tests/test-cutils.c +1 -1
@@ -1950,7 +1950,7 @@ static void test_qemu_strtou64_full_max(void)
  static void test_qemu_strtosz_simple(void)
  {
      const char *str;
-    char *endptr = NULL;
+    const char *endptr = NULL;

...

util/cutils.c

Quoted 1 line
util/cutils.c +0 -0

Conversion of call sites is good (in fact, you could drop the ' = NULL' initialization, since do_strtosz() guarantees it is set to something sane even on error). But where's the added test coverage of rejecting "inf" as a size?

Quoted 14 lines

index 7868a683e8..dd61fb4558 100644

util/cutils.c +1 -2
@@ -206,19 +206,17 @@ static int64_t suffix_mul(char suffix, int64_t unit)
   * in *end, if not NULL. Return -ERANGE on overflow, Return -EINVAL on
   * other error.
   */
-static int do_strtosz(const char *nptr, char **end,
+static int do_strtosz(const char *nptr, const char **end,
                        const char default_suffix, int64_t unit,
                        uint64_t *result)
  {
      int retval;
-    char *endptr;

util/cutils.c static int64_t suffix_mul(char suffix, int64_t unit) · line 220

Quoted 10 lines
util/cutils.c +2 -3
+    const char *endptr;
      unsigned char c;
      int mul_required = 0;
      double val, mul, integral, fraction;
 
-    errno = 0;
-    val = strtod(nptr, &endptr);
-    if (isnan(val) || endptr == nptr || errno != 0) {
+    if (qemu_strtod_finite(nptr, &endptr, &val)) {
          retval = -EINVAL;

This slams -ERANGE failure into -EINVAL. Do we care? Or would it have been better to just do:

retval = qemu_strtod_finite(...);

if (retval) {
    goto out;

util/cutils.c out:

Quoted 3 lines
util/cutils.c +0 -0
          goto out;
      }
@@ -259,17 +257,17 @@ out:

More context:

out:

    if (end) {
        *end = endptr;
    } else if (*endptr) {
        retval = -EINVAL;
    }

util/cutils.c out: · line 258

Quoted 2 lines
util/cutils.c +0 -0
      return retval;
  }

Everything else looks okay.

Hmm - while touching this, is it worth making mul_required be a bool, to match its use?

Signature · 3 lines
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org
Reviewed-by on v1 3/9
#12 lore raw

Re: [Qemu-devel] [PATCH v1 3/9] qapi: use qemu_strtod_finite() in string-input-visitor

In reply to David Hildenbrand
Quoted 6 lines · David Hildenbrand

Let's use the new function. "NaN" and "inf" are now properly rejected.

---
  qapi/string-input-visitor.c | 6 ++----
  1 file changed, 2 insertions(+), 4 deletions(-)

Should there be any added test coverage in this patch, or is it later in the series?

Signature · 3 lines
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org
#13 lore raw

Re: [Qemu-devel] [PATCH v1 3/9] qapi: use qemu_strtod_finite() in string-input-visitor

Quoted 8 lines · Eric Blake
Quoted 6 lines · David Hildenbrand

Let's use the new function. "NaN" and "inf" are now properly rejected.

---
  qapi/string-input-visitor.c | 6 ++----
  1 file changed, 2 insertions(+), 4 deletions(-)
Quoted 4 lines

Should there be any added test coverage in this patch, or is it later in the series?

Nope not yet, but can add.

Signature · 3 lines
Thanks,

David / dhildenb
Reviewed-by on v1 4/9
#17 lore raw

Re: [Qemu-devel] [PATCH v1 4/9] qapi: use qemu_strtod_finite() in qobject-input-visitor

In reply to David Hildenbrand
Quoted 8 lines · David Hildenbrand

Let's use the new function. Just as current behavior, we have to consume the whole string (now it's just way clearer what's going on).

---
  qapi/qobject-input-visitor.c | 9 +++++----
  1 file changed, 5 insertions(+), 4 deletions(-)
Signature · 3 lines
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org
#3 lore raw

Re: [Qemu-devel] [PATCH v1 1/9] cutils: add qemu_strtod() and qemu_strtod_finite()

In reply to Eric Blake
Quoted 11 lines · Eric Blake
Quoted 8 lines · David Hildenbrand

Let's provide a wrapper for strtod().

---
  include/qemu/cutils.h |  2 ++
  util/cutils.c         | 38 ++++++++++++++++++++++++++++++++++++++
  2 files changed, 40 insertions(+)
Quoted 8 lines

util/cutils.c int qemu_strtou64(const char *nptr, const char **endptr, int base, · line 573

Quoted 6 lines
util/cutils.c +6 -0
+
+/**
+ * Convert string @nptr to a finite double.
+ *
+ * Works like qemu_strtoul(), except it stores +/-HUGE_VAL on
+ * overflow/underflow. "NaN" or "inf" are rejcted with -EINVAL.

s/rejcted/rejected/

Also, just overflow. Floating-point underflow is when a computation's mathematical result is too close to zero to be represented without extraordinary rounding error.

Skip this paragraph unless you're ready to nerd out. IEEE 754 section 7.5 defines underflow to happen

[...] either

a) after rounding — when a non-zero result computed as though the exponent range were unbounded would lie strictly between ±b^emin, or

b) before rounding — when a non-zero result computed as though both the exponent range and the precision were unbounded would lie strictly between ±b^emin.

where b^emin is the smallest normal number.

The "Works like qemu_strtoul()" is a bit lazy. I guess it works like qemu_strtoul() in the sense that it adds to strtod() what qemu_strtoul() adds to strtoul(). I consciously didn't take a similar shortcut in commit 4295f879bec: I documented both qemu_strtol() and qemu_strtoul() in longhand, and used "Works like" shorthand only where that's actually the case: qemu_strtoll() works like qemu_strtol(), and qemu_strtoull() works like qemu_strtoul(). I'd prefer longhand for qemu_strtod(). It costs us a few lines, but it results in a clearer contract.

Quoted 10 lines

util/cutils.c int qemu_strtou64(const char *nptr, const char **endptr, int base, · line 577

Quoted 4 lines

+ */

util/cutils.c +3 -0
+int qemu_strtod_finite(const char *nptr, const char **endptr, double *result)
+{
+    int ret = qemu_strtod(nptr, endptr, result);

On overflow, result is set to HUGE_VAL (aka "inf") with ret set to -ERANGE. (The C standard uses HUGE_VAL rather than directly requiring infinity on overflow, in order to cater to museum platforms where the largest representable double is still finite; but no one develops qemu on a non-IEEE machine these days so we know that HUGE_VAL == INF).

Aside: museum clauses like this one make the standard much harder to read than necessary. I wish they'll purge them from C2X.

Quoted 4 lines

util/cutils.c int qemu_strtou64(const char *nptr, const char **endptr, int base, · line 581

Quoted 4 lines
util/cutils.c +4 -0
+
+    if (!ret && !isfinite(*result)) {
+        return -EINVAL;
+    }

qemu_strtol() & friends leave *result alone when they return -EINVAL. This one doesn't. Unlikely to hurt anyone, but I'd prefer to keep them consistent.

Quoted 5 lines

This check means that overflow ("1e9999") fails with -ERANGE, while actual infinity ("inf") fails with -EINVAL, letting the user distinguish between the two. Still, I wonder if assigning a non-finite value into result on -ERANGE is the wisest course of action. We'll just have to see in the next patches that use this.

I guess it's about as "wise" as qemu_strtol() storing LONG_MAX on integer overflow.

I'm fine with the semantics David picked, as long as they're spelled out in the function contract.

Quoted 3 lines

With the typo fix,

#9 lore raw

Re: [Qemu-devel] [PATCH v1 2/9] cutils: use qemu_strtod_finite() in do_strtosz()

In reply to Eric Blake
Quoted 3 lines · Eric Blake
Quoted 2 lines · David Hildenbrand

Let's use the new function. In order to do so, we have to convert all users of qemu_strtosz*() to pass a "const char **end" ptr.

We shouldn't have qemu_strtol() "improve" on strtol() by splicing in another const. Since we did, we havr to do the same for qemu_strtod().

Quoted 3 lines
Quoted 3 lines

We will now also reject "inf" properly.

Ah, so this is a bug fix! The commit message's title should tell. Suggest something like:

cutils: Fix qemu_strtosz() & friends to reject non-finite sizes

qemu_strtosz() & friends reject NaNs, but happily accept inifities. They shouldn't. Fix that.

The fix makes use of qemu_strtod_finite(). To avoid ugly casts, change the @end parameter of qemu_strtosz() & friends from char ** to const char **.

I'd split the patch into just the fix (with the ugly cast) and the parameter type change (getting rid of the ugly cast), but I'm a compulsive patch splitter. Up to you.

Quoted 17 lines
Quoted 16 lines
---
  include/qemu/cutils.h |  6 +++---
  monitor.c             |  2 +-
  tests/test-cutils.c   | 14 +++++++-------
  util/cutils.c         | 14 ++++++--------
  4 files changed, 17 insertions(+), 19 deletions(-)

+++ b/tests/test-cutils.c

tests/test-cutils.c +1 -1
@@ -1950,7 +1950,7 @@ static void test_qemu_strtou64_full_max(void)
  static void test_qemu_strtosz_simple(void)
  {
      const char *str;
-    char *endptr = NULL;
+    const char *endptr = NULL;

...

Quoted 5 lines

util/cutils.c

Quoted 1 line
util/cutils.c +0 -0

Conversion of call sites is good (in fact, you could drop the ' = NULL' initialization, since do_strtosz() guarantees it is set to something sane even on error).

Yes.

Quoted 2 lines

But where's the added test coverage of
rejecting "inf" as a size?

Let's stick it into test_qemu_strtosz_invalid().

Quoted 28 lines
Quoted 14 lines

index 7868a683e8..dd61fb4558 100644

util/cutils.c +1 -1
@@ -206,19 +206,17 @@ static int64_t suffix_mul(char suffix, int64_t unit)
   * in *end, if not NULL. Return -ERANGE on overflow, Return -EINVAL on
   * other error.
   */
-static int do_strtosz(const char *nptr, char **end,
+static int do_strtosz(const char *nptr, const char **end,
                        const char default_suffix, int64_t unit,
                        uint64_t *result)
  {
      int retval;

util/cutils.c static int64_t suffix_mul(char suffix, int64_t unit) · line 220

Quoted 10 lines
util/cutils.c +2 -3
-    char *endptr;
+    const char *endptr;
      unsigned char c;
      int mul_required = 0;
      double val, mul, integral, fraction;
  -    errno = 0;
-    val = strtod(nptr, &endptr);
-    if (isnan(val) || endptr == nptr || errno != 0) {
+    if (qemu_strtod_finite(nptr, &endptr, &val)) {
          retval = -EINVAL;

This slams -ERANGE failure into -EINVAL. Do we care? Or would it have been better to just do:

Quoted 3 lines

retval = qemu_strtod_finite(...);

if (retval) {
    goto out;

If distinguishing between ERANGE and EINVAL makes sense for qemu_strtod_finite(), it probably makes sense for qemu_strtod(), too.

Quoted 13 lines

util/cutils.c out:

Quoted 3 lines
util/cutils.c +0 -0
          goto out;
      }
@@ -259,17 +257,17 @@ out:

More context:

out:

    if (end) {
        *end = endptr;
    } else if (*endptr) {
        retval = -EINVAL;
    }
Quoted 7 lines

util/cutils.c out: · line 258

Quoted 2 lines
util/cutils.c +0 -0
      return retval;
  }

Everything else looks okay.

Hmm - while touching this, is it worth making mul_required be a bool, to match its use?

Touches no line containing mul_required, so I wouldn't.

#14 lore raw

Re: [Qemu-devel] [PATCH v1 3/9] qapi: use qemu_strtod_finite() in string-input-visitor

In reply to David Hildenbrand
Quoted 3 lines · David Hildenbrand

Let's use the new function. "NaN" and "inf" are now properly rejected.

Whether this is a bug fix or just a change is debatable. But the commit message's title should highlight the change. Perhaps you want to steal from the one I suggested for the previous patch.

We need to assess backward compatibility impact. I can do that.

Quoted 30 lines
---
 qapi/string-input-visitor.c | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)
qapi/string-input-visitor.c +2 -4
index b3fdd0827d..b89c6c4e06 100644
@@ -20,6 +20,7 @@
 #include "qemu/option.h"
 #include "qemu/queue.h"
 #include "qemu/range.h"
+#include "qemu/cutils.h"
 
 
 struct StringInputVisitor
@@ -313,12 +314,9 @@ static void parse_type_number(Visitor *v, const char *name, double *obj,
                               Error **errp)
 {
     StringInputVisitor *siv = to_siv(v);
-    char *endp = (char *) siv->string;
     double val;
 
-    errno = 0;
-    val = strtod(siv->string, &endp);
-    if (errno || endp == siv->string || *endp) {
+    if (qemu_strtod_finite(siv->string, NULL, &val)) {
         error_setg(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null",
                    "number");
         return;
#20 lore raw

Re: [Qemu-devel] [PATCH v1 5/9] test-string-input-visitor: add more tests

In reply to David Hildenbrand
Quoted 11 lines · David Hildenbrand

Test that very big/small values are not accepted and that ranges with only one element work.

Rename expect4 to expect5, as we will be moving that to a separate ulist test after the rework.

---
  tests/test-string-input-visitor.c | 22 ++++++++++++++++++++--
  1 file changed, 20 insertions(+), 2 deletions(-)

I don't see a test for a range that wraps around (such as UINT_MAX-0); that's worth testing (whether it happens to work or is rejected as invalid). Do we require ranges to be ascending, or does 6-5 result in the sequence 5, 6? I also recall that our range code imposes a limit on the maximum elements present in a single range, in order to prevent denial-of-service attacks where a caller could request 0-INT_MAX to exhaust resources enumerating everything in the range; does our testsuite cover those limits?

Signature · 3 lines
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org
#4 lore raw

Re: [Qemu-devel] [PATCH v1 1/9] cutils: add qemu_strtod() and qemu_strtod_finite()

In reply to Markus Armbruster
Quoted 22 lines · Markus Armbruster
Quoted 11 lines · Eric Blake
Quoted 8 lines · David Hildenbrand

Let's provide a wrapper for strtod().

---
  include/qemu/cutils.h |  2 ++
  util/cutils.c         | 38 ++++++++++++++++++++++++++++++++++++++
  2 files changed, 40 insertions(+)
Quoted 8 lines

util/cutils.c int qemu_strtou64(const char *nptr, const char **endptr, int base, · line 573

Quoted 6 lines
util/cutils.c +6 -0
+
+/**
+ * Convert string @nptr to a finite double.
+ *
+ * Works like qemu_strtoul(), except it stores +/-HUGE_VAL on
+ * overflow/underflow. "NaN" or "inf" are rejcted with -EINVAL.

s/rejcted/rejected/

Quoted 3 lines

Also, just overflow. Floating-point underflow is when a computation's mathematical result is too close to zero to be represented without extraordinary rounding error.

Indeed, as the "man strod" states
"... would cause overflow, plus or minus HUGE_VAL (HUGE_VALF, HUGE_VALL) is returned (according to the sign of the value)"

Quoted 16 lines

Skip this paragraph unless you're ready to nerd out. IEEE 754 section 7.5 defines underflow to happen

[...] either

a) after rounding — when a non-zero result computed as though the exponent range were unbounded would lie strictly between ±b^emin, or

b) before rounding — when a non-zero result computed as though both the exponent range and the precision were unbounded would lie strictly between ±b^emin.

where b^emin is the smallest normal number.

Quoted 8 lines

The "Works like qemu_strtoul()" is a bit lazy. I guess it works like qemu_strtoul() in the sense that it adds to strtod() what qemu_strtoul() adds to strtoul(). I consciously didn't take a similar shortcut in commit 4295f879bec: I documented both qemu_strtol() and qemu_strtoul() in longhand, and used "Works like" shorthand only where that's actually the case: qemu_strtoll() works like qemu_strtol(), and qemu_strtoull() works like qemu_strtoul(). I'd prefer longhand for qemu_strtod(). It costs us a few lines, but it results in a clearer contract.

/**
* Convert string @nptr to a double.
*
* This is a wrapper around strtod() that is harder to misuse.
* Semantics of @nptr and @endptr match strtod() with differences
* noted below.
*
* @nptr may be null, and no conversion is performed then.
*
* If no conversion is performed, store @nptr in *@endptr and return
* -EINVAL.
*
* If @endptr is null, and the string isn't fully converted, return
* -EINVAL. This is the case when the pointer that would be stored in
* a non-null @endptr points to a character other than '\0'. *
* If the conversion overflows @result, store +/-HUGE_VAL, depending on
* the sign, in @result and return -ERANGE.
*
* Else store the converted value in @result, and return zero. */

Quoted 15 lines
Quoted 11 lines

util/cutils.c int qemu_strtou64(const char *nptr, const char **endptr, int base, · line 577

Quoted 5 lines

+ */

util/cutils.c +3 -0
+int qemu_strtod_finite(const char *nptr, const char **endptr, double *result)
+{
+    int ret = qemu_strtod(nptr, endptr, result);

On overflow, result is set to HUGE_VAL (aka "inf") with ret set to -ERANGE. (The C standard uses HUGE_VAL rather than directly requiring infinity on overflow, in order to cater to museum platforms where the largest representable double is still finite; but no one develops qemu on a non-IEEE machine these days so we know that HUGE_VAL == INF).

Aside: museum clauses like this one make the standard much harder to read than necessary. I wish they'll purge them from C2X.

Quoted 8 lines
Quoted 4 lines

util/cutils.c int qemu_strtou64(const char *nptr, const char **endptr, int base, · line 581

Quoted 4 lines
util/cutils.c +4 -0
+
+    if (!ret && !isfinite(*result)) {
+        return -EINVAL;
+    }

qemu_strtol() & friends leave *result alone when they return -EINVAL. This one doesn't. Unlikely to hurt anyone, but I'd prefer to keep them consistent.

Will use a temporary and also properly set endptr in case we return -EINVAL; And add a fully-blown description as noted above :)

Quoted 7 lines
Quoted 6 lines

This check means that overflow ("1e9999") fails with -ERANGE, while actual infinity ("inf") fails with -EINVAL, letting the user distinguish between the two. Still, I wonder if assigning a non-finite value into result on -ERANGE is the wisest course of action. We'll just have to see in the next patches that use this.

Quoted 5 lines

I guess it's about as "wise" as qemu_strtol() storing LONG_MAX on integer overflow.

I'm fine with the semantics David picked, as long as they're spelled out in the function contract.

I think for now we're fine treating explicit "infinity" user input as -EINVAL. We could return something like "HUGE_VAL - 1" along with -ERANGE, but I guess for now this is overkill. Most callers will bail out on -ERANGE either way. And if not, they have to make sure they can deal with HUGE_VAL.

Quoted 4 lines
Quoted 4 lines

With the typo fix,

Thanks!

Signature · 3 lines
Thanks,

David / dhildenb
#21 lore raw

Re: [Qemu-devel] [PATCH v1 5/9] test-string-input-visitor: add more tests

In reply to Eric Blake
Quoted 13 lines · Eric Blake
Quoted 11 lines · David Hildenbrand

Test that very big/small values are not accepted and that ranges with only one element work.

Rename expect4 to expect5, as we will be moving that to a separate ulist test after the rework.

---
  tests/test-string-input-visitor.c | 22 ++++++++++++++++++++--
  1 file changed, 20 insertions(+), 2 deletions(-)
Quoted 9 lines

I don't see a test for a range that wraps around (such as UINT_MAX-0); that's worth testing (whether it happens to work or is rejected as invalid). Do we require ranges to be ascending, or does 6-5 result in the sequence 5, 6? I also recall that our range code imposes a limit on the maximum elements present in a single range, in order to prevent denial-of-service attacks where a caller could request 0-INT_MAX to exhaust resources enumerating everything in the range; does our testsuite cover those limits?

Ranges have to be ascending and old code enforced that. New code still enforces it. Wrapping ranges are AFAIC also not supported - not ascending. I can add a test.

The limit is a good point. It is neither in the tests nor in the new code. But now we finally have an explanation on the 65000-somewhat thingy. I assume that we need such a limit?

Signature · 3 lines
Thanks,

David / dhildenb
#10 lore raw

Re: [Qemu-devel] [PATCH v1 2/9] cutils: use qemu_strtod_finite() in do_strtosz()

In reply to Markus Armbruster
Quoted 21 lines · Markus Armbruster
Quoted 3 lines · Eric Blake
Quoted 2 lines · David Hildenbrand

Let's use the new function. In order to do so, we have to convert all users of qemu_strtosz*() to pass a "const char **end" ptr.

We shouldn't have qemu_strtol() "improve" on strtol() by splicing in another const. Since we did, we havr to do the same for qemu_strtod().

Quoted 3 lines
Quoted 3 lines

We will now also reject "inf" properly.

Ah, so this is a bug fix! The commit message's title should tell. Suggest something like:

cutils: Fix qemu_strtosz() & friends to reject non-finite sizes

qemu_strtosz() & friends reject NaNs, but happily accept inifities. They shouldn't. Fix that.

Quoted 7 lines

The fix makes use of qemu_strtod_finite(). To avoid ugly casts, change the @end parameter of qemu_strtosz() & friends from char ** to const char **.

I'd split the patch into just the fix (with the ugly cast) and the parameter type change (getting rid of the ugly cast), but I'm a compulsive patch splitter. Up to you.

I'll leave it in one patch for now. (and will also add two test cases for "inf" and "NaN"). If this patch grows even bigger, it makes sense to split.

Commit message updated.

Quoted 18 lines
Quoted 18 lines
Quoted 17 lines
---
  include/qemu/cutils.h |  6 +++---
  monitor.c             |  2 +-
  tests/test-cutils.c   | 14 +++++++-------
  util/cutils.c         | 14 ++++++--------
  4 files changed, 17 insertions(+), 19 deletions(-)

+++ b/tests/test-cutils.c

tests/test-cutils.c +1 -1
@@ -1950,7 +1950,7 @@ static void test_qemu_strtou64_full_max(void)
  static void test_qemu_strtosz_simple(void)
  {
      const char *str;
-    char *endptr = NULL;
+    const char *endptr = NULL;

...

Quoted 8 lines
Quoted 5 lines

util/cutils.c

Quoted 1 line
util/cutils.c +0 -0

Conversion of call sites is good (in fact, you could drop the ' = NULL' initialization, since do_strtosz() guarantees it is set to something sane even on error).

Yes.

Dropped.

Quoted 4 lines
Quoted 2 lines

But where's the added test coverage of
rejecting "inf" as a size?

Let's stick it into test_qemu_strtosz_invalid().

Done.

@@ -2096,12 +2096,22 @@ static void test_qemu_strtosz_invalid(void)

     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, -EINVAL);
     g_assert(endptr == str);
+
+    str = "inf";
+    err = qemu_strtosz(str, &endptr, &res);
+    g_assert_cmpint(err, ==, -EINVAL);
+    g_assert(endptr == str);
+
+    str = "NaN";
+    err = qemu_strtosz(str, &endptr, &res);
+    g_assert_cmpint(err, ==, -EINVAL);
+    g_assert(endptr == str);

}

Quoted 29 lines
Quoted 29 lines
Quoted 15 lines

index 7868a683e8..dd61fb4558 100644

util/cutils.c +1 -1
@@ -206,19 +206,17 @@ static int64_t suffix_mul(char suffix, int64_t unit)
   * in *end, if not NULL. Return -ERANGE on overflow, Return -EINVAL on
   * other error.
   */
-static int do_strtosz(const char *nptr, char **end,
+static int do_strtosz(const char *nptr, const char **end,
                        const char default_suffix, int64_t unit,
                        uint64_t *result)
  {
      int retval;

util/cutils.c static int64_t suffix_mul(char suffix, int64_t unit) · line 220

Quoted 10 lines
util/cutils.c +2 -3
-    char *endptr;
+    const char *endptr;
      unsigned char c;
      int mul_required = 0;
      double val, mul, integral, fraction;
  -    errno = 0;
-    val = strtod(nptr, &endptr);
-    if (isnan(val) || endptr == nptr || errno != 0) {
+    if (qemu_strtod_finite(nptr, &endptr, &val)) {
          retval = -EINVAL;

This slams -ERANGE failure into -EINVAL. Do we care? Or would it have been better to just do:

Quoted 6 lines
Quoted 3 lines

retval = qemu_strtod_finite(...);

if (retval) {
    goto out;

If distinguishing between ERANGE and EINVAL makes sense for qemu_strtod_finite(), it probably makes sense for qemu_strtod(), too.

Yes, will do it like that.

Quoted 17 lines
Quoted 17 lines

util/cutils.c out:

Quoted 4 lines
util/cutils.c +0 -0
          goto out;
      }
@@ -259,17 +257,17 @@ out:

More context:

out:

    if (end) {
        *end = endptr;
    } else if (*endptr) {
        retval = -EINVAL;
    }
Quoted 2 lines
util/cutils.c +0 -0
      return retval;
  }
Quoted 7 lines
Quoted 4 lines

Everything else looks okay.

Hmm - while touching this, is it worth making mul_required be a bool, to match its use?

Touches no line containing mul_required, so I wouldn't.

Alright, thanks to the both of you.

Signature · 3 lines
Thanks,

David / dhildenb
#5 lore raw

Re: [Qemu-devel] [PATCH v1 1/9] cutils: add qemu_strtod() and qemu_strtod_finite()

In reply to David Hildenbrand
Quoted 18 lines · David Hildenbrand
Quoted 7 lines

Also, just overflow. Floating-point underflow is when a computation's mathematical result is too close to zero to be represented without extraordinary rounding error.

works like qemu_strtoul(). I'd prefer longhand for qemu_strtod(). It costs us a few lines, but it results in a clearer contract.

/**
* Convert string @nptr to a double.
*
* This is a wrapper around strtod() that is harder to misuse.
* Semantics of @nptr and @endptr match strtod() with differences
* noted below.
*
* @nptr may be null, and no conversion is performed then. *
* If no conversion is performed, store @nptr in *@endptr and return

Quoted 8 lines

* -EINVAL.
*
* If @endptr is null, and the string isn't fully converted, return
* -EINVAL. This is the case when the pointer that would be stored in
* a non-null @endptr points to a character other than '\0'. *
* If the conversion overflows @result, store +/-HUGE_VAL, depending on
* the sign, in @result and return -ERANGE.

Fails to mention underflow. Maybe add:

If the conversion underflows, store +/-0.0 in @result, depending on the sign, and return -ERANGE.

Signature · 3 lines
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org
#22 lore raw

Re: [Qemu-devel] [PATCH v1 5/9] test-string-input-visitor: add more tests

In reply to David Hildenbrand
Quoted 23 lines · David Hildenbrand
Quoted 13 lines · Eric Blake
Quoted 11 lines · David Hildenbrand

Test that very big/small values are not accepted and that ranges with only one element work.

Rename expect4 to expect5, as we will be moving that to a separate ulist test after the rework.

---
  tests/test-string-input-visitor.c | 22 ++++++++++++++++++++--
  1 file changed, 20 insertions(+), 2 deletions(-)
Quoted 9 lines

I don't see a test for a range that wraps around (such as UINT_MAX-0); that's worth testing (whether it happens to work or is rejected as invalid). Do we require ranges to be ascending, or does 6-5 result in the sequence 5, 6? I also recall that our range code imposes a limit on the maximum elements present in a single range, in order to prevent denial-of-service attacks where a caller could request 0-INT_MAX to exhaust resources enumerating everything in the range; does our testsuite cover those limits?

Quoted 3 lines

Ranges have to be ascending and old code enforced that. New code still enforces it. Wrapping ranges are AFAIC also not supported - not ascending. I can add a test.

Good.

Quoted 3 lines

The limit is a good point. It is neither in the tests nor in the new code. But now we finally have an explanation on the 65000-somewhat thingy. I assume that we need such a limit?

Yes, we do. We don't expect untrusted input here, but typo in the monitor (say 0--1 parsed as uint64_t 0, UINT_MAX) killing the VM by eating all memory is not a happy user experience.

#15 lore raw

Re: [Qemu-devel] [PATCH v1 3/9] qapi: use qemu_strtod_finite() in string-input-visitor

In reply to Markus Armbruster
Quoted 9 lines · Markus Armbruster
Quoted 3 lines · David Hildenbrand

Let's use the new function. "NaN" and "inf" are now properly rejected.

Whether this is a bug fix or just a change is debatable. But the commit message's title should highlight the change. Perhaps you want to steal from the one I suggested for the previous patch.

Yes, will copy+modify that one.

Quoted 2 lines

We need to assess backward compatibility impact. I can do that.

Thanks! I assume this shouldn't be an issue.

Signature · 3 lines
Thanks,

David / dhildenb
#6 lore raw

Re: [Qemu-devel] [PATCH v1 1/9] cutils: add qemu_strtod() and qemu_strtod_finite()

In reply to Eric Blake
Quoted 2 lines · Eric Blake

If the conversion underflows, store ±0.0 in @result, depending on the sign, and return -ERANGE.

Will do! Thanks!

Signature · 3 lines
Thanks,

David / dhildenb
#24 lore raw
In reply to David Hildenbrand
Quoted 11 lines · David Hildenbrand

The input visitor has some problems right now, especially
- unsigned type "Range" is used to process signed ranges, resulting in inconsistent behavior and ugly/magical code
- uint64_t are parsed like int64_t, so big uint64_t values are not supported and error messages are misleading
- lists/ranges of int64_t are accepted although no list is parsed and we should rather report an error
- lists/ranges are preparsed using int64_t, making it hard to implement uint64_t values or uint64_t lists
- types that don't support lists don't bail out
- visiting beyond the end of a list is not handled properly

- We don't actually parse lists, we parse *sets*: members are sorted, and duplicates eliminated

Reproducers for the problems would be nice. Suggestion, not demand.

Quoted 5 lines

So let's rewrite it by getting rid of usage of the type "Range" and properly supporting lists of int64_t and uint64_t (including ranges of both types), fixing the above mentioned issues.

Quoted 7 lines

Lists of other types are not supported and will properly report an error. Virtual walks are now supported.

Tests have to be fixed up:
- Two BUGs were hardcoded that are fixed now
- The string-input-visitor now actually returns a parsed list and not an ordered set.

I'd expect this to necessitate an update of callers that expect a set, but...

Quoted 6 lines
---
 include/qapi/string-input-visitor.h |   4 +-
 qapi/string-input-visitor.c         | 410 ++++++++++++++++------------
 tests/test-string-input-visitor.c   |  18 +-
 3 files changed, 239 insertions(+), 193 deletions(-)

... there's none.

Let me know if you need help finding them. I think we tracked them down during the discussion that led to this series.

Quoted 162 lines
include/qapi/string-input-visitor.h +2 -2
index 33551340e3..921f3875b9 100644
@@ -19,8 +19,8 @@ typedef struct StringInputVisitor StringInputVisitor;
 
 /*
  * The string input visitor does not implement support for visiting
- * QAPI structs, alternates, null, or arbitrary QTypes.  It also
- * requires a non-null list argument to visit_start_list().
+ * QAPI structs, alternates, null, or arbitrary QTypes. Only flat lists
+ * of integers (except type "size") are supported.
  */
 Visitor *string_input_visitor_new(const char *str);
 
qapi/string-input-visitor.c +25 -87
index b89c6c4e06..4113be01fb 100644
@@ -4,10 +4,10 @@
  * Copyright Red Hat, Inc. 2012-2016
  *
  * Author: Paolo Bonzini <[email protected]>
+ *         David Hildenbrand <[email protected]>
  *
  * 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"
@@ -18,21 +18,39 @@
 #include "qapi/qmp/qerror.h"
 #include "qapi/qmp/qnull.h"
 #include "qemu/option.h"
-#include "qemu/queue.h"
-#include "qemu/range.h"
 #include "qemu/cutils.h"
 
+typedef enum ListMode {
+    /* no list parsing active / no list expected */
+    LM_NONE,
+    /* we have an unparsed string remaining */
+    LM_UNPARSED,
+    /* we have an unfinished int64 range */
+    LM_INT64_RANGE,
+    /* we have an unfinished uint64 range */
+    LM_UINT64_RANGE,
+    /* we have parsed the string completely and no range is remaining */
+    LM_END,
+} ListMode;
+
+typedef union RangeElement {
+    int64_t i64;
+    uint64_t u64;
+} RangeElement;
 
 struct StringInputVisitor
 {
     Visitor visitor;
 
-    GList *ranges;
-    GList *cur_range;
-    int64_t cur;
+    /* List parsing state */
+    ListMode lm;
+    RangeElement rangeNext;
+    RangeElement rangeEnd;
+    const char *unparsed_string;
+    void *list;
 
+    /* The original string to parse */
     const char *string;
-    void *list; /* Only needed for sanity checking the caller */
 };
 
 static StringInputVisitor *to_siv(Visitor *v)
@@ -40,136 +58,43 @@ static StringInputVisitor *to_siv(Visitor *v)
     return container_of(v, StringInputVisitor, visitor);
 }
 
-static void free_range(void *range, void *dummy)
-{
-    g_free(range);
-}
-
-static int parse_str(StringInputVisitor *siv, const char *name, Error **errp)
-{
-    char *str = (char *) siv->string;
-    long long start, end;
-    Range *cur;
-    char *endptr;
-
-    if (siv->ranges) {
-        return 0;
-    }
-
-    if (!*str) {
-        return 0;
-    }
-
-    do {
-        errno = 0;
-        start = strtoll(str, &endptr, 0);
-        if (errno == 0 && endptr > str) {
-            if (*endptr == '\0') {
-                cur = g_malloc0(sizeof(*cur));
-                range_set_bounds(cur, start, start);
-                siv->ranges = range_list_insert(siv->ranges, cur);
-                cur = NULL;
-                str = NULL;
-            } else if (*endptr == '-') {
-                str = endptr + 1;
-                errno = 0;
-                end = strtoll(str, &endptr, 0);
-                if (errno == 0 && endptr > str && start <= end &&
-                    (start > INT64_MAX - 65536 ||
-                     end < start + 65536)) {
-                    if (*endptr == '\0') {
-                        cur = g_malloc0(sizeof(*cur));
-                        range_set_bounds(cur, start, end);
-                        siv->ranges = range_list_insert(siv->ranges, cur);
-                        cur = NULL;
-                        str = NULL;
-                    } else if (*endptr == ',') {
-                        str = endptr + 1;
-                        cur = g_malloc0(sizeof(*cur));
-                        range_set_bounds(cur, start, end);
-                        siv->ranges = range_list_insert(siv->ranges, cur);
-                        cur = NULL;
-                    } else {
-                        goto error;
-                    }
-                } else {
-                    goto error;
-                }
-            } else if (*endptr == ',') {
-                str = endptr + 1;
-                cur = g_malloc0(sizeof(*cur));
-                range_set_bounds(cur, start, start);
-                siv->ranges = range_list_insert(siv->ranges, cur);
-                cur = NULL;
-            } else {
-                goto error;
-            }
-        } else {
-            goto error;
-        }
-    } while (str);
-
-    return 0;
-error:
-    g_list_foreach(siv->ranges, free_range, NULL);
-    g_list_free(siv->ranges);
-    siv->ranges = NULL;
-    error_setg(errp, QERR_INVALID_PARAMETER_VALUE, name ? name : "null",
-               "an int64 value or range");
-    return -1;
-}
-
-static void

qapi/string-input-visitor.c static StringInputVisitor *to_siv(Visitor *v) · line 66

Quoted 10 lines
qapi/string-input-visitor.c +3 -4
-start_list(Visitor *v, const char *name, GenericList **list, size_t size,
-           Error **errp)
+static void 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);
+    /* properly set the list parsing state */

This comment feels redundant.

Quoted 177 lines
qapi/string-input-visitor.c +88 -53
+    assert(siv->lm == LM_NONE);
     siv->list = list;
+    siv->unparsed_string = siv->string;
 
-    if (parse_str(siv, name, errp) < 0) {
-        *list = NULL;
-        return;
-    }
-
-    siv->cur_range = g_list_first(siv->ranges);
-    if (siv->cur_range) {
-        Range *r = siv->cur_range->data;
-        if (r) {
-            siv->cur = range_lob(r);
+    if (!siv->string[0]) {
+        if (list) {
+            *list = NULL;
         }
-        *list = g_malloc0(size);
+        siv->lm = LM_END;
     } else {
-        *list = NULL;
+        if (list) {
+            *list = g_malloc0(size);
+        }
+        siv->lm = LM_UNPARSED;
     }
 }
 
 static GenericList *next_list(Visitor *v, GenericList *tail, size_t size)
 {
     StringInputVisitor *siv = to_siv(v);
-    Range *r;
 
-    if (!siv->ranges || !siv->cur_range) {
+    switch (siv->lm) {
+    case LM_END:
         return NULL;
-    }
-
-    r = siv->cur_range->data;
-    if (!r) {
-        return NULL;
-    }
-
-    if (!range_contains(r, siv->cur)) {
-        siv->cur_range = g_list_next(siv->cur_range);
-        if (!siv->cur_range) {
-            return NULL;
-        }
-        r = siv->cur_range->data;
-        if (!r) {
-            return NULL;
-        }
-        siv->cur = range_lob(r);
+    case LM_INT64_RANGE:
+    case LM_UINT64_RANGE:
+    case LM_UNPARSED:
+        /* we have an unparsed string or something left in a range */
+        break;
+    default:
+        abort();
     }
 
     tail->next = g_malloc0(size);
@@ -179,88 +104,215 @@ static GenericList *next_list(Visitor *v, GenericList *tail, size_t size)
 static void check_list(Visitor *v, Error **errp)
 {
     const StringInputVisitor *siv = to_siv(v);
-    Range *r;
-    GList *cur_range;
 
-    if (!siv->ranges || !siv->cur_range) {
+    switch (siv->lm) {
+    case LM_INT64_RANGE:
+    case LM_UINT64_RANGE:
+    case LM_UNPARSED:
+        error_setg(errp, "Fewer list elements expected");
         return;
-    }
-
-    r = siv->cur_range->data;
-    if (!r) {
+    case LM_END:
         return;
+    default:
+        abort();
     }
-
-    if (!range_contains(r, siv->cur)) {
-        cur_range = g_list_next(siv->cur_range);
-        if (!cur_range) {
-            return;
-        }
-        r = cur_range->data;
-        if (!r) {
-            return;
-        }
-    }
-
-    error_setg(errp, "Range contains too many values");
 }
 
 static void end_list(Visitor *v, void **obj)
 {
     StringInputVisitor *siv = to_siv(v);
 
+    assert(siv->lm != LM_NONE);
     assert(siv->list == obj);
+    siv->list = NULL;
+    siv->unparsed_string = NULL;
+    siv->lm = LM_NONE;
+}
+
+static int try_parse_int64_list_entry(StringInputVisitor *siv, int64_t *obj)
+{
+    const char *endptr;
+    int64_t start, end;
+
+    if (qemu_strtoi64(siv->unparsed_string, &endptr, 0, &start)) {
+        return -EINVAL;
+    }
+    end = start;
+
+    switch (endptr[0]) {
+    case '\0':
+        siv->unparsed_string = endptr;
+        break;
+    case ',':
+        siv->unparsed_string = endptr + 1;
+        break;
+    case '-':
+        /* parse the end of the range */
+        if (qemu_strtoi64(endptr + 1, &endptr, 0, &end)) {
+            return -EINVAL;
+        }
+        if (start > end) {
+            return -EINVAL;
+        }
+        switch (endptr[0]) {
+        case '\0':
+            siv->unparsed_string = endptr;
+            break;
+        case ',':
+            siv->unparsed_string = endptr + 1;
+            break;
+        default:
+            return -EINVAL;
+        }
+        break;
+    default:
+        return -EINVAL;
+    }
+
+    /* we have a proper range (with maybe only one element) */
+    siv->lm = LM_INT64_RANGE;
+    siv->rangeNext.i64 = start;
+    siv->rangeEnd.i64 = end;
+    return 0;
 }
 
 static void parse_type_int64(Visitor *v, const char *name, int64_t *obj,
                              Error **errp)
 {
     StringInputVisitor *siv = to_siv(v);
-
-    if (parse_str(siv, name, errp) < 0) {
+    int64_t val;
+
+    switch (siv->lm) {
+    case LM_NONE:
+        /* just parse a simple int64, bail out if not completely consumed */
+        if (qemu_strtoi64(siv->string, NULL, 0, &val)) {
+                error_setg(errp, QERR_INVALID_PARAMETER_VALUE,
+                           name ? name : "null", "int64");
+            return;
+        }

qapi/string-input-visitor.c static GenericList *next_list(Visitor *v, GenericList *tail, size_t size) · line 202

Quoted 10 lines
qapi/string-input-visitor.c +9 -0
+        *obj = val;
         return;
+    case LM_UNPARSED:
+        if (try_parse_int64_list_entry(siv, obj)) {
+            error_setg(errp, QERR_INVALID_PARAMETER_VALUE, name ? name : "null",
+                       "list of int64 values or ranges");
+            return;
+        }
+        assert(siv->lm == LM_INT64_RANGE);
+        /* FALLTHROUGH */

Please spell it /* fall through */, because:

$ git-grep -Ei '/\* *fall.?thr[a-z]* *\*/' | sed 's/.*\* *\(fall[^*]*\)\*.*/\1/i' | sort | uniq -c
      4 FALL THROUGH 
      8 FALLTHROUGH 
     61 FALLTHRU 
     36 Fall through 
     20 Fallthrough 
      4 Fallthru 
    237 fall through 
      1 fall-through 
     16 fallthrough 
     39 fallthru 

qapi/string-input-visitor.c static GenericList *next_list(Visitor *v, GenericList *tail, size_t size) · line 214

Quoted 12 lines
qapi/string-input-visitor.c +12 -0
+    case LM_INT64_RANGE:
+        /* return the next element in the range */
+        assert(siv->rangeNext.i64 <= siv->rangeEnd.i64);
+        *obj = siv->rangeNext.i64++;
+
+        if (siv->rangeNext.i64 > siv->rangeEnd.i64 || *obj == INT64_MAX) {
+            /* end of range, check if there is more to parse */
+            if (siv->unparsed_string[0]) {
+                siv->lm = LM_UNPARSED;
+            } else {
+                siv->lm = LM_END;
+            }

I'd do

               siv->lm = siv->unparsed_string[0] ? LM_UNPARSED : LM_END;

Matter of taste, entirely up to you.

Quoted 15 lines
qapi/string-input-visitor.c +8 -5
+        }
+        return;
+    case LM_END:
+        error_setg(errp, "Fewer list elements expected");
+        return;
+    default:
+        abort();
     }
+}
 
-    if (!siv->ranges) {
-        goto error;
-    }
-
-    if (!siv->cur_range) {

qapi/string-input-visitor.c static GenericList *next_list(Visitor *v, GenericList *tail, size_t size) · line 230

Quoted 10 lines
qapi/string-input-visitor.c +5 -4
-        Range *r;
+static int try_parse_uint64_list_entry(StringInputVisitor *siv, uint64_t *obj)
+{
+    const char *endptr;
+    uint64_t start, end;
 
-        siv->cur_range = g_list_first(siv->ranges);
-        if (!siv->cur_range) {
-            goto error;
+    /* parse a simple uint64 or range */

try_parse_int64_list_entry() doesn't have this comment. I think either both or none should have it. You decide.

Quoted 205 lines
qapi/string-input-visitor.c +83 -24
+    if (qemu_strtou64(siv->unparsed_string, &endptr, 0, &start)) {
+        return -EINVAL;
+    }
+    end = start;
+
+    switch (endptr[0]) {
+    case '\0':
+        siv->unparsed_string = endptr;
+        break;
+    case ',':
+        siv->unparsed_string = endptr + 1;
+        break;
+    case '-':
+        /* parse the end of the range */
+        if (qemu_strtou64(endptr + 1, &endptr, 0, &end)) {
+            return -EINVAL;
         }
-
-        r = siv->cur_range->data;
-        if (!r) {
-            goto error;
+        if (start > end) {
+            return -EINVAL;
         }
-
-        siv->cur = range_lob(r);
+        switch (endptr[0]) {
+        case '\0':
+            siv->unparsed_string = endptr;
+            break;
+        case ',':
+            siv->unparsed_string = endptr + 1;
+            break;
+        default:
+            return -EINVAL;
+        }
+        break;
+    default:
+        return -EINVAL;
     }
 
-    *obj = siv->cur;
-    siv->cur++;
-    return;
-
-error:
-    error_setg(errp, QERR_INVALID_PARAMETER_VALUE, name ? name : "null",
-               "an int64 value or range");
+    /* we have a proper range (with maybe only one element) */
+    siv->lm = LM_UINT64_RANGE;
+    siv->rangeNext.u64 = start;
+    siv->rangeEnd.u64 = end;
+    return 0;
 }
 
 static void parse_type_uint64(Visitor *v, const char *name, uint64_t *obj,
                               Error **errp)
 {
-    /* FIXME: parse_type_int64 mishandles values over INT64_MAX */
-    int64_t i;
-    Error *err = NULL;
-    parse_type_int64(v, name, &i, &err);
-    if (err) {
-        error_propagate(errp, err);
-    } else {
-        *obj = i;
+    StringInputVisitor *siv = to_siv(v);
+    uint64_t val;
+
+    switch (siv->lm) {
+    case LM_NONE:
+        /* just parse a simple uint64, bail out if not completely consumed */
+        if (qemu_strtou64(siv->string, NULL, 0, &val)) {
+            error_setg(errp, QERR_INVALID_PARAMETER_VALUE, name ? name : "null",
+                       "uint64");
+            return;
+        }
+        *obj = val;
+        return;
+    case LM_UNPARSED:
+        if (try_parse_uint64_list_entry(siv, obj)) {
+            error_setg(errp, QERR_INVALID_PARAMETER_VALUE, name ? name : "null",
+                       "list of uint64 values or ranges");
+            return;
+        }
+        assert(siv->lm == LM_UINT64_RANGE);
+        /* FALLTHROUGH */
+    case LM_UINT64_RANGE:
+        /* return the next element in the range */
+        assert(siv->rangeNext.u64 <= siv->rangeEnd.u64);
+        *obj = siv->rangeNext.u64++;
+
+        if (siv->rangeNext.u64 > siv->rangeEnd.u64 || *obj == UINT64_MAX) {
+            /* end of range, check if there is more to parse */
+            if (siv->unparsed_string[0]) {
+                siv->lm = LM_UNPARSED;
+            } else {
+                siv->lm = LM_END;
+            }
+        }
+        return;
+    case LM_END:
+        error_setg(errp, "Fewer list elements expected");
+        return;
+    default:
+        abort();
     }
 }
 
@@ -271,6 +323,7 @@ static void parse_type_size(Visitor *v, const char *name, uint64_t *obj,
     Error *err = NULL;
     uint64_t val;
 
+    assert(siv->lm == LM_NONE);
     parse_option_size(name, siv->string, &val, &err);
     if (err) {
         error_propagate(errp, err);
@@ -285,6 +338,7 @@ static void parse_type_bool(Visitor *v, const char *name, bool *obj,
 {
     StringInputVisitor *siv = to_siv(v);
 
+    assert(siv->lm == LM_NONE);
     if (!strcasecmp(siv->string, "on") ||
         !strcasecmp(siv->string, "yes") ||
         !strcasecmp(siv->string, "true")) {
@@ -307,6 +361,7 @@ static void parse_type_str(Visitor *v, const char *name, char **obj,
 {
     StringInputVisitor *siv = to_siv(v);
 
+    assert(siv->lm == LM_NONE);
     *obj = g_strdup(siv->string);
 }
 
@@ -316,6 +371,7 @@ static void parse_type_number(Visitor *v, const char *name, double *obj,
     StringInputVisitor *siv = to_siv(v);
     double val;
 
+    assert(siv->lm == LM_NONE);
     if (qemu_strtod_finite(siv->string, NULL, &val)) {
         error_setg(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null",
                    "number");
@@ -330,9 +386,10 @@ static void parse_type_null(Visitor *v, const char *name, QNull **obj,
 {
     StringInputVisitor *siv = to_siv(v);
 
+    assert(siv->lm == LM_NONE);
     *obj = NULL;
 
-    if (!siv->string || siv->string[0]) {
+    if (siv->string[0]) {
         error_setg(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null",
                    "null");
         return;
@@ -345,8 +402,6 @@ static void string_input_free(Visitor *v)
 {
     StringInputVisitor *siv = to_siv(v);
 
-    g_list_foreach(siv->ranges, free_range, NULL);
-    g_list_free(siv->ranges);
     g_free(siv);
 }
 
@@ -372,5 +427,6 @@ Visitor *string_input_visitor_new(const char *str)
     v->visitor.free = string_input_free;
 
     v->string = str;
+    v->lm = LM_NONE;
     return &v->visitor;
 }
tests/test-string-input-visitor.c +3 -13
index f55e0696c0..a198aedfce 100644
@@ -92,16 +92,6 @@ static void check_ulist(Visitor *v, uint64_t *expected, size_t n)
     uint64List *tail;
     int i;
 
-    /* BUG: unsigned numbers above INT64_MAX don't work */
-    for (i = 0; i < n; i++) {
-        if (expected[i] > INT64_MAX) {
-            Error *err = NULL;
-            visit_type_uint64List(v, NULL, &res, &err);
-            error_free_or_abort(&err);
-            return;
-        }
-    }
-
     visit_type_uint64List(v, NULL, &res, &error_abort);
     tail = res;
     for (i = 0; i < n; i++) {
@@ -117,10 +107,10 @@ static void check_ulist(Visitor *v, uint64_t *expected, size_t n)
 static void test_visitor_in_intList(TestInputVisitorData *data,
                                     const void *unused)
 {
-    /* Note: the visitor *sorts* ranges *unsigned* */
-    int64_t expect1[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 20 };
+    int64_t expect1[] = { 1, 2, 0, 2, 3, 4, 20, 5, 6, 7,
+                          8, 9, 1, 2, 3, 4, 5, 6, 7, 8 };
     int64_t expect2[] = { 32767, -32768, -32767 };
-    int64_t expect3[] = { INT64_MAX, INT64_MIN };
+    int64_t expect3[] = { INT64_MIN, INT64_MAX };
     int64_t expect4[] = { 1 };
     uint64_t expect5[] = { UINT64_MAX };
     Error *err = NULL;

tests/test-string-input-visitor.c static void test_visitor_in_intList(TestInputVisitorData *data, · line 202

Quoted 7 lines
tests/test-string-input-visitor.c +1 -1
@@ -207,7 +197,7 @@ static void test_visitor_in_intList(TestInputVisitorData *data,
     visit_type_int64(v, NULL, &tail->value, &err);
     g_assert_cmpint(tail->value, ==, 0);
     visit_type_int64(v, NULL, &val, &err);
-    g_assert_cmpint(val, ==, 1); /* BUG */
+    error_free_or_abort(&err);
     visit_check_list(v, &error_abort);
     visit_end_list(v, (void **)&res);

Lovely!

#25 lore raw
In reply to Markus Armbruster
Quoted 15 lines
Quoted 5 lines

Tests have to be fixed up:
- Two BUGs were hardcoded that are fixed now
- The string-input-visitor now actually returns a parsed list and not an ordered set.

I'd expect this to necessitate an update of callers that expect a set, but...

Quoted 6 lines
---
 include/qapi/string-input-visitor.h |   4 +-
 qapi/string-input-visitor.c         | 410 ++++++++++++++++------------
 tests/test-string-input-visitor.c   |  18 +-
 3 files changed, 239 insertions(+), 193 deletions(-)
Quoted 5 lines

... there's none.

Let me know if you need help finding them. I think we tracked them down during the discussion that led to this series.

Indeed, I missed to document that. So here is the outcome:

1. backends/hostmem.c:host_memory_backend_set_host_nodes()

-> calls visit_type_uint16List(via bitmap)
-> the code can deal with duplicates/unsorted lists (bitmap_set)

Side node: I am not sure if there should be some range checks, but maybe the bitmap is large enough .... hm ...

2. qapi-visit.c::visit_type_Memdev_members()

-> calls visit_type_uint16List()
-> I think this never used for input, only for output / freeing

3. qapi-visit.c::visit_type_NumaNodeOptions_members()

-> calls visit_type_uint16List()
-> I think this never used for input, only for output / freeing

4. qapi-visit.c::visit_type_RockerOfDpaGroup_members

-> calls visit_type_uint32List()
-> I think this never used for input, only for output / freeing

5. qapi-visit.c::visit_type_RxFilterInfo_members()

-> calls visit_type_intList()
-> I think this never used for input, only for output / freeing

6. numa.c::query_memdev()

-> calls object_property_get_uint16List()
--> String parsed via visit_type_uint16List() into list -> qmp_query_memdev() uses this list
--> Not relevant if unique or sorted
-> hmp_info_memdev() uses this list
--> List converted again to a string using string output visitor

-> I don't think unique/sorted is relevant here.

Am I missing anything / is any of my statements wrong?

Thanks!

Quoted 40 lines
Quoted 40 lines
include/qapi/string-input-visitor.h +2 -2
index 33551340e3..921f3875b9 100644
@@ -19,8 +19,8 @@ typedef struct StringInputVisitor StringInputVisitor;
 
 /*
  * The string input visitor does not implement support for visiting
- * QAPI structs, alternates, null, or arbitrary QTypes.  It also
- * requires a non-null list argument to visit_start_list().
+ * QAPI structs, alternates, null, or arbitrary QTypes. Only flat lists
+ * of integers (except type "size") are supported.
  */
 Visitor *string_input_visitor_new(const char *str);
 
qapi/string-input-visitor.c +1 -3
index b89c6c4e06..4113be01fb 100644
@@ -4,10 +4,10 @@
  * Copyright Red Hat, Inc. 2012-2016
  *
  * Author: Paolo Bonzini <[email protected]>
+ *         David Hildenbrand <[email protected]>
  *
  * 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"
@@ -18,21 +18,39 @@
 #include "qapi/qmp/qerror.h"
 #include "qapi/qmp/qnull.h"
 #include "qemu/option.h"
-#include "qemu/queue.h"
-#include "qemu/range.h"
 #include "qemu/cutils.h"
 

[...]

Quoted 29 lines
Quoted 16 lines

static void parse_type_int64(Visitor *v, const char *name, int64_t *obj,
Error **errp)

 {
     StringInputVisitor *siv = to_siv(v);
qapi/string-input-visitor.c +10 -2
-
-    if (parse_str(siv, name, errp) < 0) {
+    int64_t val;
+
+    switch (siv->lm) {
+    case LM_NONE:
+        /* just parse a simple int64, bail out if not completely consumed */
+        if (qemu_strtoi64(siv->string, NULL, 0, &val)) {
+                error_setg(errp, QERR_INVALID_PARAMETER_VALUE,
+                           name ? name : "null", "int64");
+            return;
+        }

qapi/string-input-visitor.c static GenericList *next_list(Visitor *v, GenericList *tail, size_t size) · line 202

Quoted 10 lines
qapi/string-input-visitor.c +9 -0
+        *obj = val;
         return;
+    case LM_UNPARSED:
+        if (try_parse_int64_list_entry(siv, obj)) {
+            error_setg(errp, QERR_INVALID_PARAMETER_VALUE, name ? name : "null",
+                       "list of int64 values or ranges");
+            return;
+        }
+        assert(siv->lm == LM_INT64_RANGE);
+        /* FALLTHROUGH */

Please spell it /* fall through */, because:

Quoted 11 lines
$ git-grep -Ei '/\* *fall.?thr[a-z]* *\*/' | sed 's/.*\* *\(fall[^*]*\)\*.*/\1/i' | sort | uniq -c
      4 FALL THROUGH 
      8 FALLTHROUGH 
     61 FALLTHRU 
     36 Fall through 
     20 Fallthrough 
      4 Fallthru 
    237 fall through 
      1 fall-through 
     16 fallthrough 
     39 fallthru 

Done!

[...]

Quoted 14 lines

qapi/string-input-visitor.c static GenericList *next_list(Visitor *v, GenericList *tail, size_t size) · line 214

Quoted 13 lines
qapi/string-input-visitor.c +12 -0
+    case LM_INT64_RANGE:
+        /* return the next element in the range */
+        assert(siv->rangeNext.i64 <= siv->rangeEnd.i64);
+        *obj = siv->rangeNext.i64++;
+
+        if (siv->rangeNext.i64 > siv->rangeEnd.i64 || *obj == INT64_MAX) {
+            /* end of range, check if there is more to parse */
+            if (siv->unparsed_string[0]) {
+                siv->lm = LM_UNPARSED;
+            } else {
+                siv->lm = LM_END;
+            }
Quoted 5 lines

I'd do

               siv->lm = siv->unparsed_string[0] ? LM_UNPARSED : LM_END;

Matter of taste, entirely up to you.

Yes, makes sense, thanks!

Quoted 22 lines
Quoted 22 lines
qapi/string-input-visitor.c +12 -6
+        }
+        return;
+    case LM_END:
+        error_setg(errp, "Fewer list elements expected");
+        return;
+    default:
+        abort();
     }
+}
 
-    if (!siv->ranges) {
-        goto error;
-    }
-
-    if (!siv->cur_range) {
-        Range *r;
+static int try_parse_uint64_list_entry(StringInputVisitor *siv, uint64_t *obj)
+{
+    const char *endptr;
+    uint64_t start, end;
Quoted 7 lines

qapi/string-input-visitor.c static GenericList *next_list(Visitor *v, GenericList *tail, size_t size) · line 230

Quoted 4 lines
qapi/string-input-visitor.c +1 -3
-        siv->cur_range = g_list_first(siv->ranges);
-        if (!siv->cur_range) {
-            goto error;
+    /* parse a simple uint64 or range */

try_parse_int64_list_entry() doesn't have this comment. I think either both or none should have it. You decide.

Indeed, the comment got lost on the way. Will readd it.

[...]

Signature · 3 lines
Thanks,

David / dhildenb
#26 lore raw

Copying Igor and Eduardo for a hostmem.c bug. Search for "core dumped".

Quoted 21 lines · David Hildenbrand
Quoted 15 lines
Quoted 5 lines

Tests have to be fixed up:
- Two BUGs were hardcoded that are fixed now
- The string-input-visitor now actually returns a parsed list and not an ordered set.

I'd expect this to necessitate an update of callers that expect a set, but...

Quoted 6 lines
---
 include/qapi/string-input-visitor.h |   4 +-
 qapi/string-input-visitor.c         | 410 ++++++++++++++++------------
 tests/test-string-input-visitor.c   |  18 +-
 3 files changed, 239 insertions(+), 193 deletions(-)
Quoted 5 lines

... there's none.

Let me know if you need help finding them. I think we tracked them down during the discussion that led to this series.

Quoted 6 lines

Indeed, I missed to document that. So here is the outcome:

1. backends/hostmem.c:host_memory_backend_set_host_nodes()

-> calls visit_type_uint16List(via bitmap)
-> the code can deal with duplicates/unsorted lists (bitmap_set)

Yes.

Quoted 2 lines

Side node: I am not sure if there should be some range checks, but maybe the bitmap is large enough .... hm ...

Fishy. MAX_NODES is 128. Tinker, tinker, ...

    $ upstream-qemu -nodefaults -object memory-backend-file,id=mem0,mem-path=x,size=4096,host-nodes=12345
    Segmentation fault (core dumped)

Igor, Eduardo, this is yours.

There's another use of visit_type_uint16List() is this file, but it's in property getter host_memory_backend_get_host_nodes(), and property getters aren't used with the string input visitor.

Quoted 4 lines

2. qapi-visit.c::visit_type_Memdev_members()

-> calls visit_type_uint16List()
-> I think this never used for input, only for output / freeing

Yes, it's used by query-memdev with the QObject output visitor to build the value of @host-nodes.

Quoted 4 lines

3. qapi-visit.c::visit_type_NumaNodeOptions_members()

-> calls visit_type_uint16List()
-> I think this never used for input, only for output / freeing

It's used for input, but with the opts visitor, see parse_numa().

Quoted 4 lines

4. qapi-visit.c::visit_type_RockerOfDpaGroup_members

-> calls visit_type_uint32List()
-> I think this never used for input, only for output / freeing

Yes, it's used by query-rocker-of-dpa-groups with the QObject output visitor to build the value of @group-ids.

Quoted 4 lines

5. qapi-visit.c::visit_type_RxFilterInfo_members()

-> calls visit_type_intList()
-> I think this never used for input, only for output / freeing

Yes, it's used by query-rx-filter with the QObject output visitor to build the value of @vlan-table.

Quoted 4 lines

6. numa.c::query_memdev()

-> calls object_property_get_uint16List()
--> String parsed via visit_type_uint16List() into list

QOM, hard to understand.

The value of struct HostMemoryBackend member @host-nodes (a bitmap) is first converted to a list (sorted, no duplicates) with host_memory_backend_get_host_nodes() via object_property_get(), then converted to a string with the string output visitor. The resulting string is then converted back to a list with the string input visitor.

Despite the shenanigans going on in the string output visitor, I'd expect the resulting list to also be sorted and without duplicates.

Quoted 2 lines

-> qmp_query_memdev() uses this list
--> Not relevant if unique or sorted

Depends on the contract of QMP command query-memdev. Here's the relevant part.

# @host-nodes: host nodes for its memory policy

Useless.

"Sorted, no duplicates" might have become de facto ABI. Not sure. However, I believe your patch won't affect it, as per the argument I just made.

Quoted 4 lines

-> hmp_info_memdev() uses this list
--> List converted again to a string using string output visitor

-> I don't think unique/sorted is relevant here.

HMP is not a stable interface.

Quoted 1 line

Am I missing anything / is any of my statements wrong?

Searching the QAPI schema for lists of integers coughs up block latency histogram stuff, but that's unrelated, as far as I can tell.

Looks like we're good. I didn't expect that :)

[...]

#27 lore raw
Quoted 114 lines · Markus Armbruster

Copying Igor and Eduardo for a hostmem.c bug. Search for "core dumped".

Quoted 21 lines · David Hildenbrand
Quoted 15 lines
Quoted 5 lines

Tests have to be fixed up:
- Two BUGs were hardcoded that are fixed now
- The string-input-visitor now actually returns a parsed list and not an ordered set.

I'd expect this to necessitate an update of callers that expect a set, but...

Quoted 6 lines
---
 include/qapi/string-input-visitor.h |   4 +-
 qapi/string-input-visitor.c         | 410 ++++++++++++++++------------
 tests/test-string-input-visitor.c   |  18 +-
 3 files changed, 239 insertions(+), 193 deletions(-)
Quoted 5 lines

... there's none.

Let me know if you need help finding them. I think we tracked them down during the discussion that led to this series.

Quoted 6 lines

Indeed, I missed to document that. So here is the outcome:

1. backends/hostmem.c:host_memory_backend_set_host_nodes()

-> calls visit_type_uint16List(via bitmap)
-> the code can deal with duplicates/unsorted lists (bitmap_set)

Yes.

Quoted 2 lines

Side node: I am not sure if there should be some range checks, but maybe the bitmap is large enough .... hm ...

Fishy. MAX_NODES is 128. Tinker, tinker, ...

    $ upstream-qemu -nodefaults -object memory-backend-file,id=mem0,mem-path=x,size=4096,host-nodes=12345
    Segmentation fault (core dumped)

Igor, Eduardo, this is yours.

There's another use of visit_type_uint16List() is this file, but it's in property getter host_memory_backend_get_host_nodes(), and property getters aren't used with the string input visitor.

Quoted 4 lines

2. qapi-visit.c::visit_type_Memdev_members()

-> calls visit_type_uint16List()
-> I think this never used for input, only for output / freeing

Yes, it's used by query-memdev with the QObject output visitor to build the value of @host-nodes.

Quoted 4 lines

3. qapi-visit.c::visit_type_NumaNodeOptions_members()

-> calls visit_type_uint16List()
-> I think this never used for input, only for output / freeing

It's used for input, but with the opts visitor, see parse_numa().

Quoted 4 lines

4. qapi-visit.c::visit_type_RockerOfDpaGroup_members

-> calls visit_type_uint32List()
-> I think this never used for input, only for output / freeing

Yes, it's used by query-rocker-of-dpa-groups with the QObject output visitor to build the value of @group-ids.

Quoted 4 lines

5. qapi-visit.c::visit_type_RxFilterInfo_members()

-> calls visit_type_intList()
-> I think this never used for input, only for output / freeing

Yes, it's used by query-rx-filter with the QObject output visitor to build the value of @vlan-table.

Quoted 4 lines

6. numa.c::query_memdev()

-> calls object_property_get_uint16List()
--> String parsed via visit_type_uint16List() into list

QOM, hard to understand.

The value of struct HostMemoryBackend member @host-nodes (a bitmap) is first converted to a list (sorted, no duplicates) with host_memory_backend_get_host_nodes() via object_property_get(), then converted to a string with the string output visitor. The resulting string is then converted back to a list with the string input visitor.

Despite the shenanigans going on in the string output visitor, I'd expect the resulting list to also be sorted and without duplicates.

Quoted 2 lines

-> qmp_query_memdev() uses this list
--> Not relevant if unique or sorted

Depends on the contract of QMP command query-memdev. Here's the relevant part.

# @host-nodes: host nodes for its memory policy

Useless.

"Sorted, no duplicates" might have become de facto ABI. Not sure. However, I believe your patch won't affect it, as per the argument I just made.

Quoted 4 lines

-> hmp_info_memdev() uses this list
--> List converted again to a string using string output visitor

-> I don't think unique/sorted is relevant here.

Quoted 9 lines

HMP is not a stable interface.

Quoted 1 line

Am I missing anything / is any of my statements wrong?

Searching the QAPI schema for lists of integers coughs up block latency histogram stuff, but that's unrelated, as far as I can tell.

Looks like we're good. I didn't expect that :)

Haha, me too. Will add a short description to the patch message and maybe resend tomorrow!

Signature · 3 lines
Thanks,

David / dhildenb