qobject_from_jsonv() was unusual in that it took a va_list*, instead
of the more typical va_list; this was so that callers could pass
NULL to avoid % interpolation.  While this works under the hood, it
is awkward for callers, so move the magic into qjson.c rather than
in the public interface, and finally improve the documentation of
qobject_from_jsonf().

test-qobject-input-visitor.c's visitor_input_test_init_internal()
was the only caller passing NULL, fix it to instead use a QObject
created by the various callers, who now use the appropriate form
of qobject_from_json*() according to whether % interpolation is
desired.

Once that is done, all remaining callers to qobject_from_jsonv() are
passing &error_abort; drop this parameter to match the counterpart
qobject_from_jsonf() which assert()s success instead.  Besides, all
current callers that need interpolation live in the testsuite, where
enforcing well-defined input by asserts can help catch typos, and
where we should not be operating on dynamic untrusted arbitrary
input in the first place.

Asserting success has another nice benefit: if we pass more than one
%p, but could return failure, we would have to worry about whether
all arguments in the va_list had consistent refcount treatment (it
should be an all-or-none decision on whether each QObject in the
va_list had its reference count altered - but whichever way we
prefer, it's a lot of overhead to ensure we do it for everything
in the va_list even if we failed halfway through).  But now that we
promise success, we can now easily promise that all %p arguments will
now be cleaned up when freeing the returned object.

Signed-off-by: Eric Blake <ebl...@redhat.com>
---
 include/qapi/qmp/qjson.h           |  2 +-
 tests/libqtest.c                   | 10 ++------
 qobject/qjson.c                    | 49 +++++++++++++++++++++++++++++++++++---
 tests/test-qobject-input-visitor.c | 18 ++++++++------
 4 files changed, 60 insertions(+), 19 deletions(-)

diff --git a/include/qapi/qmp/qjson.h b/include/qapi/qmp/qjson.h
index 6e84082d5f..9aacb1ccf6 100644
--- a/include/qapi/qmp/qjson.h
+++ b/include/qapi/qmp/qjson.h
@@ -19,7 +19,7 @@

 QObject *qobject_from_json(const char *string, Error **errp);
 QObject *qobject_from_jsonf(const char *string, ...) GCC_FMT_ATTR(1, 2);
-QObject *qobject_from_jsonv(const char *string, va_list *ap, Error **errp)
+QObject *qobject_from_jsonv(const char *string, va_list ap)
     GCC_FMT_ATTR(1, 0);

 QString *qobject_to_json(const QObject *obj);
diff --git a/tests/libqtest.c b/tests/libqtest.c
index 99a07c246f..cde737ec5a 100644
--- a/tests/libqtest.c
+++ b/tests/libqtest.c
@@ -448,7 +448,6 @@ QDict *qtest_qmp_receive(QTestState *s)
  */
 void qmp_fd_sendv(int fd, const char *fmt, va_list ap)
 {
-    va_list ap_copy;
     QObject *qobj;
     int log = getenv("QTEST_LOG") != NULL;
     QString *qstr;
@@ -463,13 +462,8 @@ void qmp_fd_sendv(int fd, const char *fmt, va_list ap)
     }
     assert(*fmt);

-    /* Going through qobject ensures we escape strings properly.
-     * This seemingly unnecessary copy is required in case va_list
-     * is an array type.
-     */
-    va_copy(ap_copy, ap);
-    qobj = qobject_from_jsonv(fmt, &ap_copy, &error_abort);
-    va_end(ap_copy);
+    /* Going through qobject ensures we escape strings properly. */
+    qobj = qobject_from_jsonv(fmt, ap);
     qstr = qobject_to_json(qobj);

     /*
diff --git a/qobject/qjson.c b/qobject/qjson.c
index 2e0930884e..210c290aa9 100644
--- a/qobject/qjson.c
+++ b/qobject/qjson.c
@@ -35,7 +35,8 @@ static void parse_json(JSONMessageParser *parser, GQueue 
*tokens)
     s->result = json_parser_parse_err(tokens, s->ap, &s->err);
 }

-QObject *qobject_from_jsonv(const char *string, va_list *ap, Error **errp)
+static QObject *qobject_from_json_internal(const char *string, va_list *ap,
+                                           Error **errp)
 {
     JSONParsingState state = {};

@@ -50,12 +51,31 @@ QObject *qobject_from_jsonv(const char *string, va_list 
*ap, Error **errp)
     return state.result;
 }

+/*
+ * Parses JSON input without interpolation.
+ *
+ * Returns a QObject matching the input on success, or NULL with
+ * an error set if the input is not valid JSON.
+ */
 QObject *qobject_from_json(const char *string, Error **errp)
 {
-    return qobject_from_jsonv(string, NULL, errp);
+    return qobject_from_json_internal(string, NULL, errp);
 }

 /*
+ * Parses JSON input with interpolation of % sequences.
+ *
+ * The set of sequences recognized is compatible with gcc's -Wformat
+ * warnings, although not all printf sequences are valid.  All use of
+ * % must occur outside JSON strings.
+ *
+ * %i - treat corresponding integer value as JSON bool
+ * %[l[l]]d, %PRId64 - treat sized integer value as signed JSON number
+ * %[l[l]]u, %PRIu64 - treat sized integer value as unsigned JSON number
+ * %f - treat double as JSON number (undefined for inf, NaN)
+ * %s - convert char * into JSON string (adds escapes, outer quotes)
+ * %p - embed QObject, transferring the reference to the returned object
+ *
  * IMPORTANT: This function aborts on error, thus it must not
  * be used with untrusted arguments.
  */
@@ -65,13 +85,36 @@ QObject *qobject_from_jsonf(const char *string, ...)
     va_list ap;

     va_start(ap, string);
-    obj = qobject_from_jsonv(string, &ap, &error_abort);
+    obj = qobject_from_json_internal(string, &ap, &error_abort);
     va_end(ap);

     assert(obj != NULL);
     return obj;
 }

+/*
+ * va_list form of qobject_from_jsonf().
+ *
+ * IMPORTANT: This function aborts on error, thus it must not
+ * be used with untrusted arguments.
+ */
+QObject *qobject_from_jsonv(const char *string, va_list ap)
+{
+    QObject *obj;
+    va_list ap_copy;
+
+    /*
+     * This seemingly unnecessary copy is required in case va_list
+     * is an array type.
+     */
+    va_copy(ap_copy, ap);
+    obj = qobject_from_json_internal(string, &ap_copy, &error_abort);
+    va_end(ap_copy);
+
+    assert(obj != NULL);
+    return obj;
+}
+
 typedef struct ToJsonIterState
 {
     int indent;
diff --git a/tests/test-qobject-input-visitor.c 
b/tests/test-qobject-input-visitor.c
index bcf02617dc..a9addd9f98 100644
--- a/tests/test-qobject-input-visitor.c
+++ b/tests/test-qobject-input-visitor.c
@@ -45,13 +45,11 @@ static void visitor_input_teardown(TestInputVisitorData 
*data,
    function so that the JSON string used by the tests are kept in the test
    functions (and not in main()). */
 static Visitor *visitor_input_test_init_internal(TestInputVisitorData *data,
-                                                 bool keyval,
-                                                 const char *json_string,
-                                                 va_list *ap)
+                                                 bool keyval, QObject *obj)
 {
     visitor_input_teardown(data, NULL);

-    data->obj = qobject_from_jsonv(json_string, ap, &error_abort);
+    data->obj = obj;
     g_assert(data->obj);

     if (keyval) {
@@ -69,10 +67,12 @@ Visitor *visitor_input_test_init_full(TestInputVisitorData 
*data,
                                       const char *json_string, ...)
 {
     Visitor *v;
+    QObject *obj;
     va_list ap;

     va_start(ap, json_string);
-    v = visitor_input_test_init_internal(data, keyval, json_string, &ap);
+    obj = qobject_from_jsonv(json_string, ap);
+    v = visitor_input_test_init_internal(data, keyval, obj);
     va_end(ap);
     return v;
 }
@@ -82,10 +82,12 @@ Visitor *visitor_input_test_init(TestInputVisitorData *data,
                                  const char *json_string, ...)
 {
     Visitor *v;
+    QObject *obj;
     va_list ap;

     va_start(ap, json_string);
-    v = visitor_input_test_init_internal(data, false, json_string, &ap);
+    obj = qobject_from_jsonv(json_string, ap);
+    v = visitor_input_test_init_internal(data, false, obj);
     va_end(ap);
     return v;
 }
@@ -100,7 +102,9 @@ Visitor *visitor_input_test_init(TestInputVisitorData *data,
 static Visitor *visitor_input_test_init_raw(TestInputVisitorData *data,
                                             const char *json_string)
 {
-    return visitor_input_test_init_internal(data, false, json_string, NULL);
+    QObject *obj = qobject_from_json(json_string, &error_abort);
+
+    return visitor_input_test_init_internal(data, false, obj);
 }

 static void test_visitor_in_int(TestInputVisitorData *data,
-- 
2.13.3


Reply via email to