qapi: merge QInt and QFloat in QNum

We would like to use a same QObject type to represent numbers, whether
they are int, uint, or floats. Getters will allow some compatibility
between the various types if the number fits other representations.

Add a few more tests while at it.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170607163635.17635-7-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[parse_stats_intervals() simplified a bit, comment in
test_visitor_in_int_overflow() tidied up, suppress bogus warnings]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
This commit is contained in:
Marc-André Lureau 2017-06-07 20:35:58 +04:00 committed by Markus Armbruster
parent 58634047b7
commit 01b2ffcedd
65 changed files with 630 additions and 664 deletions

View file

@ -1,2 +1,2 @@
util-obj-y = qnull.o qint.o qstring.o qdict.o qlist.o qfloat.o qbool.o
util-obj-y = qnull.o qnum.o qstring.o qdict.o qlist.o qbool.o
util-obj-y += qjson.o qobject.o json-lexer.o json-streamer.o json-parser.o

View file

@ -466,16 +466,16 @@ static QObject *parse_escape(JSONParserContext *ctxt, va_list *ap)
} else if (!strcmp(token->str, "%i")) {
return QOBJECT(qbool_from_bool(va_arg(*ap, int)));
} else if (!strcmp(token->str, "%d")) {
return QOBJECT(qint_from_int(va_arg(*ap, int)));
return QOBJECT(qnum_from_int(va_arg(*ap, int)));
} else if (!strcmp(token->str, "%ld")) {
return QOBJECT(qint_from_int(va_arg(*ap, long)));
return QOBJECT(qnum_from_int(va_arg(*ap, long)));
} else if (!strcmp(token->str, "%lld") ||
!strcmp(token->str, "%I64d")) {
return QOBJECT(qint_from_int(va_arg(*ap, long long)));
return QOBJECT(qnum_from_int(va_arg(*ap, long long)));
} else if (!strcmp(token->str, "%s")) {
return QOBJECT(qstring_from_str(va_arg(*ap, const char *)));
} else if (!strcmp(token->str, "%f")) {
return QOBJECT(qfloat_from_double(va_arg(*ap, double)));
return QOBJECT(qnum_from_double(va_arg(*ap, double)));
}
return NULL;
}
@ -491,24 +491,22 @@ static QObject *parse_literal(JSONParserContext *ctxt)
case JSON_STRING:
return QOBJECT(qstring_from_escaped_str(ctxt, token));
case JSON_INTEGER: {
/* A possibility exists that this is a whole-valued float where the
* fractional part was left out due to being 0 (.0). It's not a big
* deal to treat these as ints in the parser, so long as users of the
* resulting QObject know to expect a QInt in place of a QFloat in
* cases like these.
/*
* Represent JSON_INTEGER as QNUM_I64 if possible, else as
* QNUM_DOUBLE. Note that strtoll() fails with ERANGE when
* it's not possible.
*
* However, in some cases these values will overflow/underflow a
* QInt/int64 container, thus we should assume these are to be handled
* as QFloats/doubles rather than silently changing their values.
*
* strtoll() indicates these instances by setting errno to ERANGE
* qnum_get_int() will then work for any signed 64-bit
* JSON_INTEGER, and qnum_get_double() both for any
* JSON_INTEGER and any JSON_FLOAT (with precision loss for
* integers beyond 53 bits)
*/
int64_t value;
errno = 0; /* strtoll doesn't set errno on success */
value = strtoll(token->str, NULL, 10);
if (errno != ERANGE) {
return QOBJECT(qint_from_int(value));
return QOBJECT(qnum_from_int(value));
}
/* fall through to JSON_FLOAT */
}
@ -516,7 +514,7 @@ static QObject *parse_literal(JSONParserContext *ctxt)
/* FIXME dependent on locale; a pervasive issue in QEMU */
/* FIXME our lexer matches RFC 7159 in forbidding Inf or NaN,
* but those might be useful extensions beyond JSON */
return QOBJECT(qfloat_from_double(strtod(token->str, NULL)));
return QOBJECT(qnum_from_double(strtod(token->str, NULL)));
default:
abort();
}

View file

@ -11,8 +11,7 @@
*/
#include "qemu/osdep.h"
#include "qapi/qmp/qint.h"
#include "qapi/qmp/qfloat.h"
#include "qapi/qmp/qnum.h"
#include "qapi/qmp/qdict.h"
#include "qapi/qmp/qbool.h"
#include "qapi/qmp/qstring.h"
@ -180,37 +179,26 @@ size_t qdict_size(const QDict *qdict)
/**
* qdict_get_double(): Get an number mapped by 'key'
*
* This function assumes that 'key' exists and it stores a
* QFloat or QInt object.
* This function assumes that 'key' exists and it stores a QNum.
*
* Return number mapped by 'key'.
*/
double qdict_get_double(const QDict *qdict, const char *key)
{
QObject *obj = qdict_get(qdict, key);
assert(obj);
switch (qobject_type(obj)) {
case QTYPE_QFLOAT:
return qfloat_get_double(qobject_to_qfloat(obj));
case QTYPE_QINT:
return qint_get_int(qobject_to_qint(obj));
default:
abort();
}
return qnum_get_double(qobject_to_qnum(qdict_get(qdict, key)));
}
/**
* qdict_get_int(): Get an integer mapped by 'key'
*
* This function assumes that 'key' exists and it stores a
* QInt object.
* QNum representable as int.
*
* Return integer mapped by 'key'.
*/
int64_t qdict_get_int(const QDict *qdict, const char *key)
{
return qint_get_int(qobject_to_qint(qdict_get(qdict, key)));
return qnum_get_int(qobject_to_qnum(qdict_get(qdict, key)));
}
/**
@ -259,16 +247,21 @@ const char *qdict_get_str(const QDict *qdict, const char *key)
/**
* qdict_get_try_int(): Try to get integer mapped by 'key'
*
* Return integer mapped by 'key', if it is not present in
* the dictionary or if the stored object is not of QInt type
* 'def_value' will be returned.
* Return integer mapped by 'key', if it is not present in the
* dictionary or if the stored object is not a QNum representing an
* integer, 'def_value' will be returned.
*/
int64_t qdict_get_try_int(const QDict *qdict, const char *key,
int64_t def_value)
{
QInt *qint = qobject_to_qint(qdict_get(qdict, key));
QNum *qnum = qobject_to_qnum(qdict_get(qdict, key));
int64_t val;
return qint ? qint_get_int(qint) : def_value;
if (!qnum || !qnum_get_try_int(qnum, &val)) {
return def_value;
}
return val;
}
/**

View file

@ -1,62 +0,0 @@
/*
* QFloat Module
*
* Copyright IBM, Corp. 2009
*
* Authors:
* Anthony Liguori <aliguori@us.ibm.com>
*
* 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"
#include "qapi/qmp/qfloat.h"
#include "qapi/qmp/qobject.h"
#include "qemu-common.h"
/**
* qfloat_from_int(): Create a new QFloat from a float
*
* Return strong reference.
*/
QFloat *qfloat_from_double(double value)
{
QFloat *qf;
qf = g_malloc(sizeof(*qf));
qobject_init(QOBJECT(qf), QTYPE_QFLOAT);
qf->value = value;
return qf;
}
/**
* qfloat_get_double(): Get the stored float
*/
double qfloat_get_double(const QFloat *qf)
{
return qf->value;
}
/**
* qobject_to_qfloat(): Convert a QObject into a QFloat
*/
QFloat *qobject_to_qfloat(const QObject *obj)
{
if (!obj || qobject_type(obj) != QTYPE_QFLOAT) {
return NULL;
}
return container_of(obj, QFloat, base);
}
/**
* qfloat_destroy_obj(): Free all memory allocated by a
* QFloat object
*/
void qfloat_destroy_obj(QObject *obj)
{
assert(obj != NULL);
g_free(qobject_to_qfloat(obj));
}

View file

@ -1,61 +0,0 @@
/*
* QInt Module
*
* Copyright (C) 2009 Red Hat Inc.
*
* Authors:
* Luiz Capitulino <lcapitulino@redhat.com>
*
* 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"
#include "qapi/qmp/qint.h"
#include "qapi/qmp/qobject.h"
#include "qemu-common.h"
/**
* qint_from_int(): Create a new QInt from an int64_t
*
* Return strong reference.
*/
QInt *qint_from_int(int64_t value)
{
QInt *qi;
qi = g_malloc(sizeof(*qi));
qobject_init(QOBJECT(qi), QTYPE_QINT);
qi->value = value;
return qi;
}
/**
* qint_get_int(): Get the stored integer
*/
int64_t qint_get_int(const QInt *qi)
{
return qi->value;
}
/**
* qobject_to_qint(): Convert a QObject into a QInt
*/
QInt *qobject_to_qint(const QObject *obj)
{
if (!obj || qobject_type(obj) != QTYPE_QINT) {
return NULL;
}
return container_of(obj, QInt, base);
}
/**
* qint_destroy_obj(): Free all memory allocated by a
* QInt object
*/
void qint_destroy_obj(QObject *obj)
{
assert(obj != NULL);
g_free(qobject_to_qint(obj));
}

View file

@ -132,12 +132,11 @@ static void to_json(const QObject *obj, QString *str, int pretty, int indent)
case QTYPE_QNULL:
qstring_append(str, "null");
break;
case QTYPE_QINT: {
QInt *val = qobject_to_qint(obj);
char buffer[1024];
snprintf(buffer, sizeof(buffer), "%" PRId64, qint_get_int(val));
case QTYPE_QNUM: {
QNum *val = qobject_to_qnum(obj);
char *buffer = qnum_to_string(val);
qstring_append(str, buffer);
g_free(buffer);
break;
}
case QTYPE_QSTRING: {
@ -234,34 +233,6 @@ static void to_json(const QObject *obj, QString *str, int pretty, int indent)
qstring_append(str, "]");
break;
}
case QTYPE_QFLOAT: {
QFloat *val = qobject_to_qfloat(obj);
char buffer[1024];
int len;
/* FIXME: snprintf() is locale dependent; but JSON requires
* numbers to be formatted as if in the C locale. Dependence
* on C locale is a pervasive issue in QEMU. */
/* FIXME: This risks printing Inf or NaN, which are not valid
* JSON values. */
/* FIXME: the default precision of 6 for %f often causes
* rounding errors; we should be using DBL_DECIMAL_DIG (17),
* and only rounding to a shorter number if the result would
* still produce the same floating point value. */
len = snprintf(buffer, sizeof(buffer), "%f", qfloat_get_double(val));
while (len > 0 && buffer[len - 1] == '0') {
len--;
}
if (len && buffer[len - 1] == '.') {
buffer[len - 1] = 0;
} else {
buffer[len] = 0;
}
qstring_append(str, buffer);
break;
}
case QTYPE_QBOOL: {
QBool *val = qobject_to_qbool(obj);

159
qobject/qnum.c Normal file
View file

@ -0,0 +1,159 @@
/*
* QNum Module
*
* Copyright (C) 2009 Red Hat Inc.
*
* Authors:
* Luiz Capitulino <lcapitulino@redhat.com>
* Anthony Liguori <aliguori@us.ibm.com>
* Marc-André Lureau <marcandre.lureau@redhat.com>
*
* 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"
#include "qapi/error.h"
#include "qapi/qmp/qnum.h"
#include "qapi/qmp/qobject.h"
#include "qemu-common.h"
/**
* qnum_from_int(): Create a new QNum from an int64_t
*
* Return strong reference.
*/
QNum *qnum_from_int(int64_t value)
{
QNum *qn = g_new(QNum, 1);
qobject_init(QOBJECT(qn), QTYPE_QNUM);
qn->kind = QNUM_I64;
qn->u.i64 = value;
return qn;
}
/**
* qnum_from_double(): Create a new QNum from a double
*
* Return strong reference.
*/
QNum *qnum_from_double(double value)
{
QNum *qn = g_new(QNum, 1);
qobject_init(QOBJECT(qn), QTYPE_QNUM);
qn->kind = QNUM_DOUBLE;
qn->u.dbl = value;
return qn;
}
/**
* qnum_get_try_int(): Get an integer representation of the number
*
* Return true on success.
*/
bool qnum_get_try_int(const QNum *qn, int64_t *val)
{
switch (qn->kind) {
case QNUM_I64:
*val = qn->u.i64;
return true;
case QNUM_DOUBLE:
return false;
}
assert(0);
return false;
}
/**
* qnum_get_int(): Get an integer representation of the number
*
* assert() on failure.
*/
int64_t qnum_get_int(const QNum *qn)
{
int64_t val;
bool success = qnum_get_try_int(qn, &val);
assert(success);
return val;
}
/**
* qnum_get_double(): Get a float representation of the number
*
* qnum_get_double() loses precision for integers beyond 53 bits.
*/
double qnum_get_double(QNum *qn)
{
switch (qn->kind) {
case QNUM_I64:
return qn->u.i64;
case QNUM_DOUBLE:
return qn->u.dbl;
}
assert(0);
return 0.0;
}
char *qnum_to_string(QNum *qn)
{
char *buffer;
int len;
switch (qn->kind) {
case QNUM_I64:
return g_strdup_printf("%" PRId64, qn->u.i64);
case QNUM_DOUBLE:
/* FIXME: snprintf() is locale dependent; but JSON requires
* numbers to be formatted as if in the C locale. Dependence
* on C locale is a pervasive issue in QEMU. */
/* FIXME: This risks printing Inf or NaN, which are not valid
* JSON values. */
/* FIXME: the default precision of 6 for %f often causes
* rounding errors; we should be using DBL_DECIMAL_DIG (17),
* and only rounding to a shorter number if the result would
* still produce the same floating point value. */
buffer = g_strdup_printf("%f" , qn->u.dbl);
len = strlen(buffer);
while (len > 0 && buffer[len - 1] == '0') {
len--;
}
if (len && buffer[len - 1] == '.') {
buffer[len - 1] = 0;
} else {
buffer[len] = 0;
}
return buffer;
}
assert(0);
return NULL;
}
/**
* qobject_to_qnum(): Convert a QObject into a QNum
*/
QNum *qobject_to_qnum(const QObject *obj)
{
if (!obj || qobject_type(obj) != QTYPE_QNUM) {
return NULL;
}
return container_of(obj, QNum, base);
}
/**
* qnum_destroy_obj(): Free all memory allocated by a
* QNum object
*/
void qnum_destroy_obj(QObject *obj)
{
assert(obj != NULL);
g_free(qobject_to_qnum(obj));
}

View file

@ -14,11 +14,10 @@
static void (*qdestroy[QTYPE__MAX])(QObject *) = {
[QTYPE_NONE] = NULL, /* No such object exists */
[QTYPE_QNULL] = NULL, /* qnull_ is indestructible */
[QTYPE_QINT] = qint_destroy_obj,
[QTYPE_QNUM] = qnum_destroy_obj,
[QTYPE_QSTRING] = qstring_destroy_obj,
[QTYPE_QDICT] = qdict_destroy_obj,
[QTYPE_QLIST] = qlist_destroy_obj,
[QTYPE_QFLOAT] = qfloat_destroy_obj,
[QTYPE_QBOOL] = qbool_destroy_obj,
};