[Qemu-devel] [PATCH v4 05/22] qobject: Simplify qobject_from_jsonv()

Eric Blake posted 22 patches 8 years, 4 months ago
[Qemu-devel] [PATCH v4 05/22] qobject: Simplify qobject_from_jsonv()
Posted by Eric Blake 8 years, 4 months ago
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 <eblake@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


Re: [Qemu-devel] [PATCH v4 05/22] qobject: Simplify qobject_from_jsonv()
Posted by Markus Armbruster 8 years, 4 months ago
Eric Blake <eblake@redhat.com> writes:

> 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 <eblake@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);
>
>      /*

Wait!  Oh, the va_copy() moves iinto qobject_from_jsonv().  Okay, I
guess.

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

Imperative mood, please.  Same elsewhere.

Suggest "without interpolation of % sequences".

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

Use the opportunity to stop shouting IMPORTANT?

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

--verbose?

> +    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,

Re: [Qemu-devel] [PATCH v4 05/22] qobject: Simplify qobject_from_jsonv()
Posted by Eric Blake 8 years, 4 months ago
On 08/09/2017 02:59 AM, Markus Armbruster wrote:
> Eric Blake <eblake@redhat.com> writes:
> 
>> 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().
>>

>> -    /* 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);
>>
>>      /*
> 
> Wait!  Oh, the va_copy() moves iinto qobject_from_jsonv().  Okay, I
> guess.


>> +
>> +    /*
>> +     * This seemingly unnecessary copy is required in case va_list
>> +     * is an array type.
>> +     */
> 
> --verbose?

Code motion. But if the comment needs to be more verbose in the
destination than it was on the source, the rationale is that C99/POSIX
allows 'typedef something va_list[]' (that is, where va_list is an array
of some other type), although I don't know of any modern OS that
actually defines it like that.  Based on C pointer-decay rules, '&ap'
has a different type based on whether va_list was a struct/pointer or an
array type, when 'va_list ap' was passed as a parameter; so we can't
portably use qobject_from_json_internal(string, &ap, &error_abort).  The
va_copy() is what lets us guarantee that &ap_list is a pointer to a
va_list regardless of the type of va_list (because va_copy was declared
locally, rather than in a parameter list, and is therefore not subject
to pointer decay), and NOT an accidental pointer to first element of the
va_list array on platforms where va_list is an array.

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org

Re: [Qemu-devel] [PATCH v4 05/22] qobject: Simplify qobject_from_jsonv()
Posted by Markus Armbruster 8 years, 2 months ago
Eric Blake <eblake@redhat.com> writes:

> On 08/09/2017 02:59 AM, Markus Armbruster wrote:
>> Eric Blake <eblake@redhat.com> writes:
>> 
>>> 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().
>>>
>
>>> -    /* 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);
>>>
>>>      /*
>> 
>> Wait!  Oh, the va_copy() moves iinto qobject_from_jsonv().  Okay, I
>> guess.
[...]
>>> +
>>> +    /*
>>> +     * This seemingly unnecessary copy is required in case va_list
>>> +     * is an array type.
>>> +     */
>> 
>> --verbose?
>>
>>> +    va_copy(ap_copy, ap);
>>> +    obj = qobject_from_json_internal(string, &ap_copy, &error_abort);
>>> +    va_end(ap_copy);
>
> Code motion. But if the comment needs to be more verbose in the
> destination than it was on the source, the rationale is that C99/POSIX
> allows 'typedef something va_list[]' (that is, where va_list is an array
> of some other type), although I don't know of any modern OS that
> actually defines it like that.  Based on C pointer-decay rules, '&ap'
> has a different type based on whether va_list was a struct/pointer or an
> array type, when 'va_list ap' was passed as a parameter; so we can't
> portably use qobject_from_json_internal(string, &ap, &error_abort).  The
> va_copy() is what lets us guarantee that &ap_list is a pointer to a
> va_list regardless of the type of va_list (because va_copy was declared
> locally, rather than in a parameter list, and is therefore not subject
> to pointer decay), and NOT an accidental pointer to first element of the
> va_list array on platforms where va_list is an array.

I'm dense this Monday morning --- I still can't see where exactly
passing &ap directly goes wrong.

Two cases:

1. va_list is a typedef name for a non-array type T.

2. va_list is a typedef name for an array type E[].

What are the types of actual argument &ap and formal parameter va_list
*ap in either case?

How exactly does case 2 break?

Re: [Qemu-devel] [PATCH v4 05/22] qobject: Simplify qobject_from_jsonv()
Posted by Eric Blake 8 years, 2 months ago
On 10/02/2017 12:46 AM, Markus Armbruster wrote:

>>>> +    /*
>>>> +     * This seemingly unnecessary copy is required in case va_list
>>>> +     * is an array type.
>>>> +     */
>>>
>>> --verbose?
>>>
>>>> +    va_copy(ap_copy, ap);
>>>> +    obj = qobject_from_json_internal(string, &ap_copy, &error_abort);
>>>> +    va_end(ap_copy);
>>
>> Code motion. But if the comment needs to be more verbose in the
>> destination than it was on the source, the rationale is that C99/POSIX
>> allows 'typedef something va_list[]' (that is, where va_list is an array
>> of some other type), although I don't know of any modern OS that
>> actually defines it like that.  Based on C pointer-decay rules, '&ap'
>> has a different type based on whether va_list was a struct/pointer or an
>> array type, when 'va_list ap' was passed as a parameter; so we can't
>> portably use qobject_from_json_internal(string, &ap, &error_abort).  The
>> va_copy() is what lets us guarantee that &ap_list is a pointer to a
>> va_list regardless of the type of va_list (because va_copy was declared
>> locally, rather than in a parameter list, and is therefore not subject
>> to pointer decay), and NOT an accidental pointer to first element of the
>> va_list array on platforms where va_list is an array.
> 
> I'm dense this Monday morning --- I still can't see where exactly
> passing &ap directly goes wrong.
> 
> Two cases:
> 
> 1. va_list is a typedef name for a non-array type T.
> 
> 2. va_list is a typedef name for an array type E[].
> 
> What are the types of actual argument &ap and formal parameter va_list
> *ap in either case?
> 
> How exactly does case 2 break?

An example program is probably the best to visualize the problem:

$ cat typefun.c
#include <stdio.h>

typedef struct T {
    int i[5];
} T;
typedef struct E {
    int i;
} E;

typedef T list1;
typedef E list2[5];

void bar(const char *prefix, list1 *l1, list2 *l2) {
    printf ("%s: %zu %zu\n", prefix, sizeof(&l1), sizeof(&l2));
}

void foo(list1 l1, list2 l2) {
    printf ("parameter sizes: %zu %zu\n", sizeof(l1), sizeof(l2));
    bar("called with address of parameter", &l1, &l2);
}

int main(void) {
    list1 l1;
    list2 l2;
    printf ("local variable sizes: %zu %zu\n", sizeof(l1), sizeof(l2));
    bar("called with address of local variable", &l1, &l2);
    foo(l1, l2);
    return 0;
}

$ gcc -o typefun -Wall typefun.c
typefun.c: In function ‘foo’:
typefun.c:18:61: warning: ‘sizeof’ on array function parameter ‘l2’ will
return size of ‘E * {aka struct E *}’ [-Wsizeof-array-argument]
     printf ("parameter sizes: %zu %zu\n", sizeof(l1), sizeof(l2));
                                                             ^
typefun.c:17:26: note: declared here
 void foo(list1 l1, list2 l2) {
                          ^~
typefun.c:19:50: warning: passing argument 3 of ‘bar’ from incompatible
pointer type [-Wincompatible-pointer-types]
     bar("called with address of parameter", &l1, &l2);
                                                  ^
typefun.c:13:6: note: expected ‘E (*)[5] {aka struct E (*)[5]}’ but
argument is of type ‘E ** {aka struct E **}’
 void bar(const char *prefix, list1 *l1, list2 *l2) {
      ^~~

$ ./typefun
local variable sizes: 20 20
called with address of local variable: 8 8
parameter sizes: 20 8
called with address of parameter: 8 8

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org

Re: [Qemu-devel] [PATCH v4 05/22] qobject: Simplify qobject_from_jsonv()
Posted by Eric Blake 7 years, 11 months ago
On 10/02/2017 09:30 AM, Eric Blake wrote:
> On 10/02/2017 12:46 AM, Markus Armbruster wrote:
> 
>>>>> +    /*
>>>>> +     * This seemingly unnecessary copy is required in case va_list
>>>>> +     * is an array type.
>>>>> +     */
>>>>
>>>> --verbose?
>>>>
>>>>> +    va_copy(ap_copy, ap);
>>>>> +    obj = qobject_from_json_internal(string, &ap_copy, &error_abort);
>>>>> +    va_end(ap_copy);
>>>
>>> Code motion. But if the comment needs to be more verbose in the
>>> destination than it was on the source, the rationale is that C99/POSIX
>>> allows 'typedef something va_list[]' (that is, where va_list is an array
>>> of some other type), although I don't know of any modern OS that
>>> actually defines it like that.  Based on C pointer-decay rules, '&ap'
>>> has a different type based on whether va_list was a struct/pointer or an
>>> array type, when 'va_list ap' was passed as a parameter; so we can't
>>> portably use qobject_from_json_internal(string, &ap, &error_abort).  The
>>> va_copy() is what lets us guarantee that &ap_list is a pointer to a
>>> va_list regardless of the type of va_list (because va_copy was declared
>>> locally, rather than in a parameter list, and is therefore not subject
>>> to pointer decay), and NOT an accidental pointer to first element of the
>>> va_list array on platforms where va_list is an array.

When I first wrote that, I didn't know where it mattered in practice;
now I do.  Here's a gcc non-bug:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=14557

that documents a workaround of using a macro va_ptr() because there ARE
existing modern platforms that use an array type for va_list:
https://sourceware.org/ml/newlib/2017/msg01302.html

I don't know if va_ptr() is any more elegant than the va_copy() used in
my code motion.

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org

Re: [Qemu-devel] [PATCH v4 05/22] qobject: Simplify qobject_from_jsonv()
Posted by Eric Blake 8 years, 3 months ago
On 08/03/2017 08:25 PM, Eric Blake wrote:
> 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().
> 

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

Given your comments on vararg naming elsewhere in the series, I'm also
thinking this is a good chance to fix this to be named
qobject_from_vjsonf() (making it obvious it is the va_list form of
formatted json, and similar to printf/vprintf or my
qtest_startf/qtest_vstartf in my v7 preliminary cleanup series).

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org

Re: [Qemu-devel] [PATCH v4 05/22] qobject: Simplify qobject_from_jsonv()
Posted by Markus Armbruster 8 years, 2 months ago
Eric Blake <eblake@redhat.com> writes:

> On 08/03/2017 08:25 PM, Eric Blake wrote:
>> 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().
>> 
>
>> +/*
>> + * 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)
>> +{
>
> Given your comments on vararg naming elsewhere in the series, I'm also
> thinking this is a good chance to fix this to be named
> qobject_from_vjsonf() (making it obvious it is the va_list form of
> formatted json, and similar to printf/vprintf or my
> qtest_startf/qtest_vstartf in my v7 preliminary cleanup series).

Yes, please.