Message ID | 1457636396-24983-2-git-send-email-berrange@redhat.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
On 03/10/2016 11:59 AM, Daniel P. Berrange wrote: > Currently the QmpInputVisitor assumes that all scalar > values are directly represented as their final types. > ie it assumes an 'int' is using QInt, and a 'bool' is > using QBool. > > This extends it so that QString is optionally permitted > for any of the non-string scalar types. This behaviour > is turned on by requesting the 'autocast' flag in the > constructor. > > This makes it possible to use QmpInputVisitor with a > QDict produced from QemuOpts, where everything is in > string format. > > Signed-off-by: Daniel P. Berrange <berrange@redhat.com> > --- > include/qapi/qmp-input-visitor.h | 3 + > qapi/qmp-input-visitor.c | 96 +++++++++++++++++++++++++++----- > tests/test-qmp-input-visitor.c | 115 ++++++++++++++++++++++++++++++++++++++- > 3 files changed, 196 insertions(+), 18 deletions(-) > > diff --git a/include/qapi/qmp-input-visitor.h b/include/qapi/qmp-input-visitor.h > index 3ed499c..c25cb7c 100644 > --- a/include/qapi/qmp-input-visitor.h > +++ b/include/qapi/qmp-input-visitor.h > @@ -21,6 +21,9 @@ typedef struct QmpInputVisitor QmpInputVisitor; > > QmpInputVisitor *qmp_input_visitor_new(QObject *obj); > QmpInputVisitor *qmp_input_visitor_new_strict(QObject *obj); > +QmpInputVisitor *qmp_input_visitor_new_full(QObject *obj, > + bool strict, > + bool autocast); We have so few uses of qmp_input_visitor_new* that it might be worth just having a single prototype, and maybe using an 'int flags' instead of a string of bool. But not a show-stopper for this patch (rather, an idea for a future patch). > - *obj = qint_get_int(qint); > + qstr = qobject_to_qstring(qobj); > + if (qstr && qstr->string && qiv->autocast) { > + errno = 0; Dead setting of errno, since... > + if (qemu_strtoll(qstr->string, NULL, 10, obj) == 0) { qemu_strtoll() handles it on your behalf, and you aren't using error_setg_errno(). > @@ -233,30 +245,61 @@ static void qmp_input_type_uint64(Visitor *v, const char *name, uint64_t *obj, > { > /* FIXME: qobject_to_qint mishandles values over INT64_MAX */ > QmpInputVisitor *qiv = to_qiv(v); > - QInt *qint = qobject_to_qint(qmp_input_get_object(qiv, name, true)); > + QObject *qobj = qmp_input_get_object(qiv, name, true); > + QInt *qint; > + QString *qstr; > > - if (!qint) { > - error_setg(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null", > - "integer"); > + qint = qobject_to_qint(qobj); > + if (qint) { > + *obj = qint_get_int(qint); > return; > } > > - *obj = qint_get_int(qint); > + qstr = qobject_to_qstring(qobj); > + if (qstr && qstr->string && qiv->autocast) { > + errno = 0; > + if (qemu_strtoull(qstr->string, NULL, 10, obj) == 0) { And again. Hmm. Do we need to worry about partial asymmetry? That is, qint_get_int() returns a signed number, but qemu_strtoull() parses unsigned; if the original conversion from JSON to qint uses a different parser, then we could have funny results where we get different results for things like: "key1":9223372036854775807, "key2":"9223372036854775807", even though the same string of digits is being parsed, based on whether the different parsers handle numbers larger than INT64_MAX differently. [Ultimately, I'd like QInt to be enhanced to track whether the input was signed or unsigned, and automatically make the output match the input when converting back to string; that is, track 65 bits of information instead of 64; but that's no sooner than 2.7 material] > static void qmp_input_type_bool(Visitor *v, const char *name, bool *obj, > Error **errp) > { > QmpInputVisitor *qiv = to_qiv(v); > - QBool *qbool = qobject_to_qbool(qmp_input_get_object(qiv, name, true)); > + QObject *qobj = qmp_input_get_object(qiv, name, true); > + QBool *qbool; > + QString *qstr; > > - if (!qbool) { > - error_setg(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null", > - "boolean"); > + qbool = qobject_to_qbool(qobj); > + if (qbool) { > + *obj = qbool_get_bool(qbool); > return; > } > > - *obj = qbool_get_bool(qbool); > + > + qstr = qobject_to_qstring(qobj); > + if (qstr && qstr->string && qiv->autocast) { > + if (!strcasecmp(qstr->string, "on") || > + !strcasecmp(qstr->string, "yes") || > + !strcasecmp(qstr->string, "true")) { > + *obj = true; > + return; > + } Do we also want to allow "0"/"1" for true/false? Overall, I'm a big fan of this patch.
On Mon, Mar 21, 2016 at 05:18:01PM -0600, Eric Blake wrote: > On 03/10/2016 11:59 AM, Daniel P. Berrange wrote: > > Currently the QmpInputVisitor assumes that all scalar > > values are directly represented as their final types. > > ie it assumes an 'int' is using QInt, and a 'bool' is > > using QBool. > > > > This extends it so that QString is optionally permitted > > for any of the non-string scalar types. This behaviour > > is turned on by requesting the 'autocast' flag in the > > constructor. > > > > This makes it possible to use QmpInputVisitor with a > > QDict produced from QemuOpts, where everything is in > > string format. > > > > Signed-off-by: Daniel P. Berrange <berrange@redhat.com> > > --- > > include/qapi/qmp-input-visitor.h | 3 + > > qapi/qmp-input-visitor.c | 96 +++++++++++++++++++++++++++----- > > tests/test-qmp-input-visitor.c | 115 ++++++++++++++++++++++++++++++++++++++- > > 3 files changed, 196 insertions(+), 18 deletions(-) > > - *obj = qint_get_int(qint); > > + qstr = qobject_to_qstring(qobj); > > + if (qstr && qstr->string && qiv->autocast) { > > + errno = 0; > > + if (qemu_strtoull(qstr->string, NULL, 10, obj) == 0) { > > And again. > > Hmm. Do we need to worry about partial asymmetry? That is, > qint_get_int() returns a signed number, but qemu_strtoull() parses > unsigned; if the original conversion from JSON to qint uses a different > parser, then we could have funny results where we get different results > for things like: > "key1":9223372036854775807, "key2":"9223372036854775807", > even though the same string of digits is being parsed, based on whether > the different parsers handle numbers larger than INT64_MAX differently. Is this something you want me to change for re-post, or just a general point for future ? ie should I be using qemu_strtoll instead of qemu_strtoull or something else ? qint itself doesn't seem to concern itself with parsing ints from strintgs, so presumably this is from json code ? > [Ultimately, I'd like QInt to be enhanced to track whether the input was > signed or unsigned, and automatically make the output match the input > when converting back to string; that is, track 65 bits of information > instead of 64; but that's no sooner than 2.7 material] > > > > static void qmp_input_type_bool(Visitor *v, const char *name, bool *obj, > > Error **errp) > > { > > QmpInputVisitor *qiv = to_qiv(v); > > - QBool *qbool = qobject_to_qbool(qmp_input_get_object(qiv, name, true)); > > + QObject *qobj = qmp_input_get_object(qiv, name, true); > > + QBool *qbool; > > + QString *qstr; > > > > - if (!qbool) { > > - error_setg(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null", > > - "boolean"); > > + qbool = qobject_to_qbool(qobj); > > + if (qbool) { > > + *obj = qbool_get_bool(qbool); > > return; > > } > > > > - *obj = qbool_get_bool(qbool); > > + > > + qstr = qobject_to_qstring(qobj); > > + if (qstr && qstr->string && qiv->autocast) { > > + if (!strcasecmp(qstr->string, "on") || > > + !strcasecmp(qstr->string, "yes") || > > + !strcasecmp(qstr->string, "true")) { > > + *obj = true; > > + return; > > + } > > Do we also want to allow "0"/"1" for true/false? These 3 strings I took from opts-visitor.c, so to maintain compat we probably should not allow 0/1, unless we decide to extend opts-visitor too Regards, Daniel
On 03/22/2016 09:49 AM, Daniel P. Berrange wrote: > On Mon, Mar 21, 2016 at 05:18:01PM -0600, Eric Blake wrote: >> On 03/10/2016 11:59 AM, Daniel P. Berrange wrote: >>> Currently the QmpInputVisitor assumes that all scalar >>> values are directly represented as their final types. >>> ie it assumes an 'int' is using QInt, and a 'bool' is >>> using QBool. >>> >>> This extends it so that QString is optionally permitted >>> for any of the non-string scalar types. This behaviour >>> is turned on by requesting the 'autocast' flag in the >>> constructor. >>> >> Hmm. Do we need to worry about partial asymmetry? That is, >> qint_get_int() returns a signed number, but qemu_strtoull() parses >> unsigned; if the original conversion from JSON to qint uses a different >> parser, then we could have funny results where we get different results >> for things like: >> "key1":9223372036854775807, "key2":"9223372036854775807", >> even though the same string of digits is being parsed, based on whether >> the different parsers handle numbers larger than INT64_MAX differently. > > Is this something you want me to change for re-post, or just a general > point for future ? ie should I be using qemu_strtoll instead of > qemu_strtoull or something else ? qint itself doesn't seem > to concern itself with parsing ints from strintgs, so presumably > this is from json code ? General comment for now. We already know we need a bigger audit of handling of values larger than INT64_MAX, so any cleanups related to that can be deferred to that later audit. But maybe a FIXME or TODO comment in the code in your submission, to remind us to think about it during the later audit, would help. >>> + qstr = qobject_to_qstring(qobj); >>> + if (qstr && qstr->string && qiv->autocast) { >>> + if (!strcasecmp(qstr->string, "on") || >>> + !strcasecmp(qstr->string, "yes") || >>> + !strcasecmp(qstr->string, "true")) { >>> + *obj = true; >>> + return; >>> + } >> >> Do we also want to allow "0"/"1" for true/false? > > These 3 strings I took from opts-visitor.c, so to maintain compat > we probably should not allow 0/1, unless we decide to extend > opts-visitor too Good point. Maybe a comment pointing in both places pointing out that we should keep them in sync is a worthwhile addition.
diff --git a/include/qapi/qmp-input-visitor.h b/include/qapi/qmp-input-visitor.h index 3ed499c..c25cb7c 100644 --- a/include/qapi/qmp-input-visitor.h +++ b/include/qapi/qmp-input-visitor.h @@ -21,6 +21,9 @@ typedef struct QmpInputVisitor QmpInputVisitor; QmpInputVisitor *qmp_input_visitor_new(QObject *obj); QmpInputVisitor *qmp_input_visitor_new_strict(QObject *obj); +QmpInputVisitor *qmp_input_visitor_new_full(QObject *obj, + bool strict, + bool autocast); void qmp_input_visitor_cleanup(QmpInputVisitor *v); diff --git a/qapi/qmp-input-visitor.c b/qapi/qmp-input-visitor.c index e659832..59d2165 100644 --- a/qapi/qmp-input-visitor.c +++ b/qapi/qmp-input-visitor.c @@ -35,6 +35,7 @@ struct QmpInputVisitor StackObject stack[QIV_STACK_SIZE]; int nb_stack; bool strict; + bool autocast; }; static QmpInputVisitor *to_qiv(Visitor *v) @@ -217,15 +218,26 @@ static void qmp_input_type_int64(Visitor *v, const char *name, int64_t *obj, Error **errp) { QmpInputVisitor *qiv = to_qiv(v); - QInt *qint = qobject_to_qint(qmp_input_get_object(qiv, name, true)); + QObject *qobj = qmp_input_get_object(qiv, name, true); + QInt *qint; + QString *qstr; - if (!qint) { - error_setg(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null", - "integer"); + qint = qobject_to_qint(qobj); + if (qint) { + *obj = qint_get_int(qint); return; } - *obj = qint_get_int(qint); + qstr = qobject_to_qstring(qobj); + if (qstr && qstr->string && qiv->autocast) { + errno = 0; + if (qemu_strtoll(qstr->string, NULL, 10, obj) == 0) { + return; + } + } + + error_setg(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null", + "integer"); } static void qmp_input_type_uint64(Visitor *v, const char *name, uint64_t *obj, @@ -233,30 +245,61 @@ static void qmp_input_type_uint64(Visitor *v, const char *name, uint64_t *obj, { /* FIXME: qobject_to_qint mishandles values over INT64_MAX */ QmpInputVisitor *qiv = to_qiv(v); - QInt *qint = qobject_to_qint(qmp_input_get_object(qiv, name, true)); + QObject *qobj = qmp_input_get_object(qiv, name, true); + QInt *qint; + QString *qstr; - if (!qint) { - error_setg(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null", - "integer"); + qint = qobject_to_qint(qobj); + if (qint) { + *obj = qint_get_int(qint); return; } - *obj = qint_get_int(qint); + qstr = qobject_to_qstring(qobj); + if (qstr && qstr->string && qiv->autocast) { + errno = 0; + if (qemu_strtoull(qstr->string, NULL, 10, obj) == 0) { + return; + } + } + + error_setg(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null", + "integer"); } static void qmp_input_type_bool(Visitor *v, const char *name, bool *obj, Error **errp) { QmpInputVisitor *qiv = to_qiv(v); - QBool *qbool = qobject_to_qbool(qmp_input_get_object(qiv, name, true)); + QObject *qobj = qmp_input_get_object(qiv, name, true); + QBool *qbool; + QString *qstr; - if (!qbool) { - error_setg(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null", - "boolean"); + qbool = qobject_to_qbool(qobj); + if (qbool) { + *obj = qbool_get_bool(qbool); return; } - *obj = qbool_get_bool(qbool); + + qstr = qobject_to_qstring(qobj); + if (qstr && qstr->string && qiv->autocast) { + if (!strcasecmp(qstr->string, "on") || + !strcasecmp(qstr->string, "yes") || + !strcasecmp(qstr->string, "true")) { + *obj = true; + return; + } + if (!strcasecmp(qstr->string, "off") || + !strcasecmp(qstr->string, "no") || + !strcasecmp(qstr->string, "false")) { + *obj = false; + return; + } + } + + error_setg(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null", + "boolean"); } static void qmp_input_type_str(Visitor *v, const char *name, char **obj, @@ -281,6 +324,8 @@ static void qmp_input_type_number(Visitor *v, const char *name, double *obj, QObject *qobj = qmp_input_get_object(qiv, name, true); QInt *qint; QFloat *qfloat; + QString *qstr; + char *endp; qint = qobject_to_qint(qobj); if (qint) { @@ -294,6 +339,15 @@ static void qmp_input_type_number(Visitor *v, const char *name, double *obj, return; } + qstr = qobject_to_qstring(qobj); + if (qstr && qstr->string && qiv->autocast) { + errno = 0; + *obj = strtod(qstr->string, &endp); + if (errno == 0 && endp != qstr->string && *endp == '\0') { + return; + } + } + error_setg(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null", "number"); } @@ -368,3 +422,15 @@ QmpInputVisitor *qmp_input_visitor_new_strict(QObject *obj) return v; } + +QmpInputVisitor *qmp_input_visitor_new_full(QObject *obj, + bool strict, bool autocast) +{ + QmpInputVisitor *v; + + v = qmp_input_visitor_new(obj); + v->strict = strict; + v->autocast = autocast; + + return v; +} diff --git a/tests/test-qmp-input-visitor.c b/tests/test-qmp-input-visitor.c index b05da5b..fd7eb63 100644 --- a/tests/test-qmp-input-visitor.c +++ b/tests/test-qmp-input-visitor.c @@ -40,6 +40,7 @@ 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 strict, bool autocast, const char *json_string, va_list *ap) { @@ -50,7 +51,7 @@ static Visitor *visitor_input_test_init_internal(TestInputVisitorData *data, data->obj = qobject_from_jsonv(json_string, ap); g_assert(data->obj); - data->qiv = qmp_input_visitor_new(data->obj); + data->qiv = qmp_input_visitor_new_full(data->obj, strict, autocast); g_assert(data->qiv); v = qmp_input_get_visitor(data->qiv); @@ -59,6 +60,21 @@ static Visitor *visitor_input_test_init_internal(TestInputVisitorData *data, return v; } +static GCC_FMT_ATTR(4, 5) +Visitor *visitor_input_test_init_full(TestInputVisitorData *data, + bool strict, bool autocast, + const char *json_string, ...) +{ + Visitor *v; + va_list ap; + + va_start(ap, json_string); + v = visitor_input_test_init_internal(data, strict, autocast, + json_string, &ap); + va_end(ap); + return v; +} + static GCC_FMT_ATTR(2, 3) Visitor *visitor_input_test_init(TestInputVisitorData *data, const char *json_string, ...) @@ -67,7 +83,8 @@ Visitor *visitor_input_test_init(TestInputVisitorData *data, va_list ap; va_start(ap, json_string); - v = visitor_input_test_init_internal(data, json_string, &ap); + v = visitor_input_test_init_internal(data, NULL, NULL, + json_string, &ap); va_end(ap); return v; } @@ -82,7 +99,8 @@ 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, json_string, NULL); + return visitor_input_test_init_internal(data, NULL, NULL, + json_string, NULL); } static void test_visitor_in_int(TestInputVisitorData *data, @@ -114,6 +132,33 @@ static void test_visitor_in_int_overflow(TestInputVisitorData *data, error_free_or_abort(&err); } +static void test_visitor_in_int_autocast(TestInputVisitorData *data, + const void *unused) +{ + int64_t res = 0, value = -42; + Visitor *v; + + v = visitor_input_test_init_full(data, false, true, + "\"-42\""); + + visit_type_int(v, NULL, &res, &error_abort); + g_assert_cmpint(res, ==, value); +} + +static void test_visitor_in_int_noautocast(TestInputVisitorData *data, + const void *unused) +{ + int64_t res = 0; + Visitor *v; + Error *err = NULL; + + v = visitor_input_test_init(data, "\"-42\""); + + visit_type_int(v, NULL, &res, &err); + g_assert(err != NULL); + error_free(err); +} + static void test_visitor_in_bool(TestInputVisitorData *data, const void *unused) { @@ -126,6 +171,32 @@ static void test_visitor_in_bool(TestInputVisitorData *data, g_assert_cmpint(res, ==, true); } +static void test_visitor_in_bool_autocast(TestInputVisitorData *data, + const void *unused) +{ + bool res = false; + Visitor *v; + + v = visitor_input_test_init_full(data, false, true, "\"true\""); + + visit_type_bool(v, NULL, &res, &error_abort); + g_assert_cmpint(res, ==, true); +} + +static void test_visitor_in_bool_noautocast(TestInputVisitorData *data, + const void *unused) +{ + bool res = false; + Visitor *v; + Error *err = NULL; + + v = visitor_input_test_init(data, "\"true\""); + + visit_type_bool(v, NULL, &res, &err); + g_assert(err != NULL); + error_free(err); +} + static void test_visitor_in_number(TestInputVisitorData *data, const void *unused) { @@ -138,6 +209,32 @@ static void test_visitor_in_number(TestInputVisitorData *data, g_assert_cmpfloat(res, ==, value); } +static void test_visitor_in_number_autocast(TestInputVisitorData *data, + const void *unused) +{ + double res = 0, value = 3.14; + Visitor *v; + + v = visitor_input_test_init_full(data, false, true, "\"3.14\""); + + visit_type_number(v, NULL, &res, &error_abort); + g_assert_cmpfloat(res, ==, value); +} + +static void test_visitor_in_number_noautocast(TestInputVisitorData *data, + const void *unused) +{ + double res = 0; + Visitor *v; + Error *err = NULL; + + v = visitor_input_test_init(data, "\"3.14\""); + + visit_type_number(v, NULL, &res, &err); + g_assert(err != NULL); + error_free(err); +} + static void test_visitor_in_string(TestInputVisitorData *data, const void *unused) { @@ -811,10 +908,22 @@ int main(int argc, char **argv) &in_visitor_data, test_visitor_in_int); input_visitor_test_add("/visitor/input/int_overflow", &in_visitor_data, test_visitor_in_int_overflow); + input_visitor_test_add("/visitor/input/int_autocast", + &in_visitor_data, test_visitor_in_int_autocast); + input_visitor_test_add("/visitor/input/int_noautocast", + &in_visitor_data, test_visitor_in_int_noautocast); input_visitor_test_add("/visitor/input/bool", &in_visitor_data, test_visitor_in_bool); + input_visitor_test_add("/visitor/input/bool_autocast", + &in_visitor_data, test_visitor_in_bool_autocast); + input_visitor_test_add("/visitor/input/bool_noautocast", + &in_visitor_data, test_visitor_in_bool_noautocast); input_visitor_test_add("/visitor/input/number", &in_visitor_data, test_visitor_in_number); + input_visitor_test_add("/visitor/input/number_autocast", + &in_visitor_data, test_visitor_in_number_autocast); + input_visitor_test_add("/visitor/input/number_noautocast", + &in_visitor_data, test_visitor_in_number_noautocast); input_visitor_test_add("/visitor/input/string", &in_visitor_data, test_visitor_in_string); input_visitor_test_add("/visitor/input/enum",
Currently the QmpInputVisitor assumes that all scalar values are directly represented as their final types. ie it assumes an 'int' is using QInt, and a 'bool' is using QBool. This extends it so that QString is optionally permitted for any of the non-string scalar types. This behaviour is turned on by requesting the 'autocast' flag in the constructor. This makes it possible to use QmpInputVisitor with a QDict produced from QemuOpts, where everything is in string format. Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- include/qapi/qmp-input-visitor.h | 3 + qapi/qmp-input-visitor.c | 96 +++++++++++++++++++++++++++----- tests/test-qmp-input-visitor.c | 115 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 196 insertions(+), 18 deletions(-)