qemu/qobject/qjson.c

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

233 lines
5.7 KiB
C
Raw Permalink Normal View History

/*
* QObject JSON integration
*
* 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/error.h"
#include "qapi/qmp/json-parser.h"
#include "qapi/qmp/json-writer.h"
#include "qapi/qmp/qjson.h"
#include "qapi/qmp/qbool.h"
#include "qapi/qmp/qdict.h"
#include "qapi/qmp/qlist.h"
#include "qapi/qmp/qnum.h"
#include "qapi/qmp/qstring.h"
typedef struct JSONParsingState {
JSONMessageParser parser;
QObject *result;
Error *err;
} JSONParsingState;
json: Redesign the callback to consume JSON values The classical way to structure parser and lexer is to have the client call the parser to get an abstract syntax tree, the parser call the lexer to get the next token, and the lexer call some function to get input characters. Another way to structure them would be to have the client feed characters to the lexer, the lexer feed tokens to the parser, and the parser feed abstract syntax trees to some callback provided by the client. This way is more easily integrated into an event loop that dispatches input characters as they arrive. Our JSON parser is kind of between the two. The lexer feeds tokens to a "streamer" instead of a real parser. The streamer accumulates tokens until it got the sequence of tokens that comprise a single JSON value (it counts curly braces and square brackets to decide). It feeds those token sequences to a callback provided by the client. The callback passes each token sequence to the parser, and gets back an abstract syntax tree. I figure it was done that way to make a straightforward recursive descent parser possible. "Get next token" becomes "pop the first token off the token sequence". Drawback: we need to store a complete token sequence. Each token eats 13 + input characters + malloc overhead bytes. Observations: 1. This is not the only way to use recursive descent. If we replaced "get next token" by a coroutine yield, we could do without a streamer. 2. The lexer reports errors by passing a JSON_ERROR token to the streamer. This communicates the offending input characters and their location, but no more. 3. The streamer reports errors by passing a null token sequence to the callback. The (already poor) lexical error information is thrown away. 4. Having the callback receive a token sequence duplicates the code to convert token sequence to abstract syntax tree in every callback. 5. Known bug: the streamer silently drops incomplete token sequences. This commit rectifies 4. by lifting the call of the parser from the callbacks into the streamer. Later commits will address 3. and 5. The lifting removes a bug from qjson.c's parse_json(): it passed a pointer to a non-null Error * in certain cases, as demonstrated by check-qjson.c. json_parser_parse() is now unused. It's a stupid wrapper around json_parser_parse_err(). Drop it, and rename json_parser_parse_err() to json_parser_parse(). Signed-off-by: Markus Armbruster <armbru@redhat.com> Reviewed-by: Eric Blake <eblake@redhat.com> Message-Id: <20180823164025.12553-35-armbru@redhat.com>
2018-08-23 19:40:01 +03:00
static void consume_json(void *opaque, QObject *json, Error *err)
{
json: Redesign the callback to consume JSON values The classical way to structure parser and lexer is to have the client call the parser to get an abstract syntax tree, the parser call the lexer to get the next token, and the lexer call some function to get input characters. Another way to structure them would be to have the client feed characters to the lexer, the lexer feed tokens to the parser, and the parser feed abstract syntax trees to some callback provided by the client. This way is more easily integrated into an event loop that dispatches input characters as they arrive. Our JSON parser is kind of between the two. The lexer feeds tokens to a "streamer" instead of a real parser. The streamer accumulates tokens until it got the sequence of tokens that comprise a single JSON value (it counts curly braces and square brackets to decide). It feeds those token sequences to a callback provided by the client. The callback passes each token sequence to the parser, and gets back an abstract syntax tree. I figure it was done that way to make a straightforward recursive descent parser possible. "Get next token" becomes "pop the first token off the token sequence". Drawback: we need to store a complete token sequence. Each token eats 13 + input characters + malloc overhead bytes. Observations: 1. This is not the only way to use recursive descent. If we replaced "get next token" by a coroutine yield, we could do without a streamer. 2. The lexer reports errors by passing a JSON_ERROR token to the streamer. This communicates the offending input characters and their location, but no more. 3. The streamer reports errors by passing a null token sequence to the callback. The (already poor) lexical error information is thrown away. 4. Having the callback receive a token sequence duplicates the code to convert token sequence to abstract syntax tree in every callback. 5. Known bug: the streamer silently drops incomplete token sequences. This commit rectifies 4. by lifting the call of the parser from the callbacks into the streamer. Later commits will address 3. and 5. The lifting removes a bug from qjson.c's parse_json(): it passed a pointer to a non-null Error * in certain cases, as demonstrated by check-qjson.c. json_parser_parse() is now unused. It's a stupid wrapper around json_parser_parse_err(). Drop it, and rename json_parser_parse_err() to json_parser_parse(). Signed-off-by: Markus Armbruster <armbru@redhat.com> Reviewed-by: Eric Blake <eblake@redhat.com> Message-Id: <20180823164025.12553-35-armbru@redhat.com>
2018-08-23 19:40:01 +03:00
JSONParsingState *s = opaque;
qjson: Fix qobject_from_json() & friends for multiple values qobject_from_json() & friends use the consume_json() callback to receive either a value or an error from the parser. When they are fed a string that contains more than either one JSON value or one JSON syntax error, consume_json() gets called multiple times. When the last call receives a value, qobject_from_json() returns that value. Any other values are leaked. When any call receives an error, qobject_from_json() sets the first error received. Any other errors are thrown away. When values follow errors, qobject_from_json() returns both a value and sets an error. That's bad. Impact: * block.c's parse_json_protocol() ignores and leaks the value. It's used to to parse pseudo-filenames starting with "json:". The pseudo-filenames can come from the user or from image meta-data such as a QCOW2 image's backing file name. * vl.c's parse_display_qapi() ignores and leaks the error. It's used to parse the argument of command line option -display. * vl.c's main() case QEMU_OPTION_blockdev ignores the error and leaves it in @err. main() will then pass a pointer to a non-null Error * to net_init_clients(), which is forbidden. It can lead to assertion failure or other misbehavior. * check-qjson.c's multiple_values() demonstrates the badness. * The other callers are not affected since they only pass strings with exactly one JSON value or, in the case of negative tests, one error. The impact on the _nofail() functions is relatively harmless. They abort when any call receives an error. Else they return the last value, and leak the others, if any. Fix consume_json() as follows. On the first call, save value and error as before. On subsequent calls, if any, don't save them. If the first call saved a value, the next call, if any, replaces the value by an "Expecting at most one JSON value" error. Take care not to leak values or errors that aren't saved. Signed-off-by: Markus Armbruster <armbru@redhat.com> Reviewed-by: Eric Blake <eblake@redhat.com> Message-Id: <20180823164025.12553-44-armbru@redhat.com>
2018-08-23 19:40:10 +03:00
assert(!json != !err);
assert(!s->result || !s->err);
if (s->result) {
qobject_unref(s->result);
s->result = NULL;
error_setg(&s->err, "Expecting at most one JSON value");
}
if (s->err) {
qobject_unref(json);
error_free(err);
return;
}
json: Redesign the callback to consume JSON values The classical way to structure parser and lexer is to have the client call the parser to get an abstract syntax tree, the parser call the lexer to get the next token, and the lexer call some function to get input characters. Another way to structure them would be to have the client feed characters to the lexer, the lexer feed tokens to the parser, and the parser feed abstract syntax trees to some callback provided by the client. This way is more easily integrated into an event loop that dispatches input characters as they arrive. Our JSON parser is kind of between the two. The lexer feeds tokens to a "streamer" instead of a real parser. The streamer accumulates tokens until it got the sequence of tokens that comprise a single JSON value (it counts curly braces and square brackets to decide). It feeds those token sequences to a callback provided by the client. The callback passes each token sequence to the parser, and gets back an abstract syntax tree. I figure it was done that way to make a straightforward recursive descent parser possible. "Get next token" becomes "pop the first token off the token sequence". Drawback: we need to store a complete token sequence. Each token eats 13 + input characters + malloc overhead bytes. Observations: 1. This is not the only way to use recursive descent. If we replaced "get next token" by a coroutine yield, we could do without a streamer. 2. The lexer reports errors by passing a JSON_ERROR token to the streamer. This communicates the offending input characters and their location, but no more. 3. The streamer reports errors by passing a null token sequence to the callback. The (already poor) lexical error information is thrown away. 4. Having the callback receive a token sequence duplicates the code to convert token sequence to abstract syntax tree in every callback. 5. Known bug: the streamer silently drops incomplete token sequences. This commit rectifies 4. by lifting the call of the parser from the callbacks into the streamer. Later commits will address 3. and 5. The lifting removes a bug from qjson.c's parse_json(): it passed a pointer to a non-null Error * in certain cases, as demonstrated by check-qjson.c. json_parser_parse() is now unused. It's a stupid wrapper around json_parser_parse_err(). Drop it, and rename json_parser_parse_err() to json_parser_parse(). Signed-off-by: Markus Armbruster <armbru@redhat.com> Reviewed-by: Eric Blake <eblake@redhat.com> Message-Id: <20180823164025.12553-35-armbru@redhat.com>
2018-08-23 19:40:01 +03:00
s->result = json;
qjson: Fix qobject_from_json() & friends for multiple values qobject_from_json() & friends use the consume_json() callback to receive either a value or an error from the parser. When they are fed a string that contains more than either one JSON value or one JSON syntax error, consume_json() gets called multiple times. When the last call receives a value, qobject_from_json() returns that value. Any other values are leaked. When any call receives an error, qobject_from_json() sets the first error received. Any other errors are thrown away. When values follow errors, qobject_from_json() returns both a value and sets an error. That's bad. Impact: * block.c's parse_json_protocol() ignores and leaks the value. It's used to to parse pseudo-filenames starting with "json:". The pseudo-filenames can come from the user or from image meta-data such as a QCOW2 image's backing file name. * vl.c's parse_display_qapi() ignores and leaks the error. It's used to parse the argument of command line option -display. * vl.c's main() case QEMU_OPTION_blockdev ignores the error and leaves it in @err. main() will then pass a pointer to a non-null Error * to net_init_clients(), which is forbidden. It can lead to assertion failure or other misbehavior. * check-qjson.c's multiple_values() demonstrates the badness. * The other callers are not affected since they only pass strings with exactly one JSON value or, in the case of negative tests, one error. The impact on the _nofail() functions is relatively harmless. They abort when any call receives an error. Else they return the last value, and leak the others, if any. Fix consume_json() as follows. On the first call, save value and error as before. On subsequent calls, if any, don't save them. If the first call saved a value, the next call, if any, replaces the value by an "Expecting at most one JSON value" error. Take care not to leak values or errors that aren't saved. Signed-off-by: Markus Armbruster <armbru@redhat.com> Reviewed-by: Eric Blake <eblake@redhat.com> Message-Id: <20180823164025.12553-44-armbru@redhat.com>
2018-08-23 19:40:10 +03:00
s->err = err;
}
/*
* Parse @string as JSON value.
* If @ap is non-null, interpolate %-escapes.
* Takes ownership of %p arguments.
* On success, return the JSON value.
* On failure, store an error through @errp and return NULL.
* Ownership of %p arguments becomes indeterminate then. To avoid
* leaks, callers passing %p must terminate on error, e.g. by passing
* &error_abort.
*/
static QObject *qobject_from_jsonv(const char *string, va_list *ap,
Error **errp)
{
JSONParsingState state = {};
json: Redesign the callback to consume JSON values The classical way to structure parser and lexer is to have the client call the parser to get an abstract syntax tree, the parser call the lexer to get the next token, and the lexer call some function to get input characters. Another way to structure them would be to have the client feed characters to the lexer, the lexer feed tokens to the parser, and the parser feed abstract syntax trees to some callback provided by the client. This way is more easily integrated into an event loop that dispatches input characters as they arrive. Our JSON parser is kind of between the two. The lexer feeds tokens to a "streamer" instead of a real parser. The streamer accumulates tokens until it got the sequence of tokens that comprise a single JSON value (it counts curly braces and square brackets to decide). It feeds those token sequences to a callback provided by the client. The callback passes each token sequence to the parser, and gets back an abstract syntax tree. I figure it was done that way to make a straightforward recursive descent parser possible. "Get next token" becomes "pop the first token off the token sequence". Drawback: we need to store a complete token sequence. Each token eats 13 + input characters + malloc overhead bytes. Observations: 1. This is not the only way to use recursive descent. If we replaced "get next token" by a coroutine yield, we could do without a streamer. 2. The lexer reports errors by passing a JSON_ERROR token to the streamer. This communicates the offending input characters and their location, but no more. 3. The streamer reports errors by passing a null token sequence to the callback. The (already poor) lexical error information is thrown away. 4. Having the callback receive a token sequence duplicates the code to convert token sequence to abstract syntax tree in every callback. 5. Known bug: the streamer silently drops incomplete token sequences. This commit rectifies 4. by lifting the call of the parser from the callbacks into the streamer. Later commits will address 3. and 5. The lifting removes a bug from qjson.c's parse_json(): it passed a pointer to a non-null Error * in certain cases, as demonstrated by check-qjson.c. json_parser_parse() is now unused. It's a stupid wrapper around json_parser_parse_err(). Drop it, and rename json_parser_parse_err() to json_parser_parse(). Signed-off-by: Markus Armbruster <armbru@redhat.com> Reviewed-by: Eric Blake <eblake@redhat.com> Message-Id: <20180823164025.12553-35-armbru@redhat.com>
2018-08-23 19:40:01 +03:00
json_message_parser_init(&state.parser, consume_json, &state, ap);
json_message_parser_feed(&state.parser, string, strlen(string));
json_message_parser_flush(&state.parser);
json_message_parser_destroy(&state.parser);
if (!state.result && !state.err) {
error_setg(&state.err, "Expecting a JSON value");
}
error_propagate(errp, state.err);
return state.result;
}
QObject *qobject_from_json(const char *string, Error **errp)
{
return qobject_from_jsonv(string, NULL, errp);
}
/*
* Parse @string as JSON value with %-escapes interpolated.
* Abort on error. Do not use with untrusted @string.
* Return the resulting QObject. It is never null.
*/
QObject *qobject_from_vjsonf_nofail(const char *string, va_list ap)
{
va_list ap_copy;
QObject *obj;
/* va_copy() is needed when va_list is an array type */
va_copy(ap_copy, ap);
obj = qobject_from_jsonv(string, &ap_copy, &error_abort);
va_end(ap_copy);
assert(obj);
return obj;
}
/*
* Parse @string as JSON value with %-escapes interpolated.
* Abort on error. Do not use with untrusted @string.
* Return the resulting QObject. It is never null.
*/
QObject *qobject_from_jsonf_nofail(const char *string, ...)
{
QObject *obj;
va_list ap;
va_start(ap, string);
obj = qobject_from_vjsonf_nofail(string, ap);
va_end(ap);
return obj;
}
/*
* Parse @string as JSON object with %-escapes interpolated.
* Abort on error. Do not use with untrusted @string.
* Return the resulting QDict. It is never null.
*/
QDict *qdict_from_vjsonf_nofail(const char *string, va_list ap)
{
QDict *qdict;
qdict = qobject_to(QDict, qobject_from_vjsonf_nofail(string, ap));
assert(qdict);
return qdict;
}
/*
* Parse @string as JSON object with %-escapes interpolated.
* Abort on error. Do not use with untrusted @string.
* Return the resulting QDict. It is never null.
*/
QDict *qdict_from_jsonf_nofail(const char *string, ...)
{
QDict *qdict;
va_list ap;
va_start(ap, string);
qdict = qdict_from_vjsonf_nofail(string, ap);
va_end(ap);
return qdict;
}
static void to_json(JSONWriter *writer, const char *name,
const QObject *obj)
{
switch (qobject_type(obj)) {
case QTYPE_QNULL:
json_writer_null(writer, name);
break;
case QTYPE_QNUM: {
QNum *val = qobject_to(QNum, obj);
switch (val->kind) {
case QNUM_I64:
json_writer_int64(writer, name, val->u.i64);
break;
case QNUM_U64:
json_writer_uint64(writer, name, val->u.u64);
break;
case QNUM_DOUBLE:
json_writer_double(writer, name, val->u.dbl);
break;
default:
abort();
}
break;
}
case QTYPE_QSTRING: {
QString *val = qobject_to(QString, obj);
json_writer_str(writer, name, qstring_get_str(val));
break;
}
case QTYPE_QDICT: {
QDict *val = qobject_to(QDict, obj);
const QDictEntry *entry;
json_writer_start_object(writer, name);
for (entry = qdict_first(val);
entry;
entry = qdict_next(val, entry)) {
to_json(writer, qdict_entry_key(entry), qdict_entry_value(entry));
}
json_writer_end_object(writer);
break;
}
case QTYPE_QLIST: {
QList *val = qobject_to(QList, obj);
QListEntry *entry;
json_writer_start_array(writer, name);
QLIST_FOREACH_ENTRY(val, entry) {
to_json(writer, NULL, qlist_entry_obj(entry));
}
json_writer_end_array(writer);
break;
}
case QTYPE_QBOOL: {
QBool *val = qobject_to(QBool, obj);
json_writer_bool(writer, name, qbool_get_bool(val));
break;
}
default:
abort();
}
}
GString *qobject_to_json_pretty(const QObject *obj, bool pretty)
{
JSONWriter *writer = json_writer_new(pretty);
to_json(writer, NULL, obj);
return json_writer_get_and_free(writer);
}
GString *qobject_to_json(const QObject *obj)
{
return qobject_to_json_pretty(obj, false);
}