QMP and QObject patches
-----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAABAgAGBQJWVsvGAAoJEDhwtADrkYZTtnwP+wcrBt/uqw3Q0k5OC4vwDa6z OYd8KVK60PozjbRDkiaZE7Iqu9eo47rbRCfTyta7/QFP49/ERVja7+QcFO4681Sj ELFQKCztU39lu8kNm4/+WYRdcTUKVTY2JQmz/d4qEDBD0EKGPnQ+r6PiCr5jS4Qn IScyU4dzVuNhkpzuphuJuoBnvPsnhlwNPtDkSZ1dV7At3zx52mQAlzjJID52Hr4K FIReQlhXQognZb3AtYrFilK/BMOA7Vj/HO5apzf72OkdL5lKYuSeSbxV/rqWB5jh tPyzv5Ufi6cZ+eNTQ6aDy/O8+9ktu5sAqpi4mX9IMwKsIdxu1GuqX+9MW8ecuE7M IOA8Clj17PjUPHKgExh29i3W32zxjryWpZAcRyNEf4ovUnbMPAYyAdEctTTTChGE sthkFBpqEflaEZPYIjsAMyViXMUkPtsigDzlHtkdf9BTiF6QMs3gqhHPLWgsPAzB gpjQc//X2e/77saaTdrwj10vy587xxRLYWF92aOVq9s7CKb5g/D3q5DuaeRpVOum w2WugVGxPs0Fsy2Wd0HuZFpeU6qBD9AdHfB0GMJ4TQs8K2XT9vLAc9cw6x4s3Bup PwbqfN+upLZHbiL0uZpXfq39w1JUr/QkH7jCMpu2xZvc0R472Nv6FGLDdyLAYJnG HNzLXWW9wGj4YjTPKYQD =YrWd -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/armbru/tags/pull-monitor-2015-11-26' into staging QMP and QObject patches # gpg: Signature made Thu 26 Nov 2015 09:07:18 GMT using RSA key ID EB918653 # gpg: Good signature from "Markus Armbruster <armbru@redhat.com>" # gpg: aka "Markus Armbruster <armbru@pond.sub.org>" * remotes/armbru/tags/pull-monitor-2015-11-26: qjson: Limit number of tokens in addition to total size qjson: surprise, allocating 6 QObjects per token is expensive qjson: store tokens in a GQueue qjson: Convert to parser to recursive descent qjson: replace QString in JSONLexer with GString qjson: Inline token_is_escape() and simplify qjson: Inline token_is_keyword() and simplify qjson: Give each of the six structural chars its own token type qjson: Spell out some silent assumptions check-qjson: Add test for JSON nesting depth limit qjson: Don't crash when input exceeds nesting limit qjson: Apply nesting limit more sanely monitor: Plug memory leak on QMP error Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
a5df35070a
@ -14,11 +14,16 @@
|
||||
#ifndef QEMU_JSON_LEXER_H
|
||||
#define QEMU_JSON_LEXER_H
|
||||
|
||||
#include "qapi/qmp/qstring.h"
|
||||
#include "qapi/qmp/qlist.h"
|
||||
#include "glib-compat.h"
|
||||
|
||||
typedef enum json_token_type {
|
||||
JSON_OPERATOR = 100,
|
||||
JSON_MIN = 100,
|
||||
JSON_LCURLY = JSON_MIN,
|
||||
JSON_RCURLY,
|
||||
JSON_LSQUARE,
|
||||
JSON_RSQUARE,
|
||||
JSON_COLON,
|
||||
JSON_COMMA,
|
||||
JSON_INTEGER,
|
||||
JSON_FLOAT,
|
||||
JSON_KEYWORD,
|
||||
@ -30,13 +35,14 @@ typedef enum json_token_type {
|
||||
|
||||
typedef struct JSONLexer JSONLexer;
|
||||
|
||||
typedef void (JSONLexerEmitter)(JSONLexer *, QString *, JSONTokenType, int x, int y);
|
||||
typedef void (JSONLexerEmitter)(JSONLexer *, GString *,
|
||||
JSONTokenType, int x, int y);
|
||||
|
||||
struct JSONLexer
|
||||
{
|
||||
JSONLexerEmitter *emit;
|
||||
int state;
|
||||
QString *token;
|
||||
GString *token;
|
||||
int x, y;
|
||||
};
|
||||
|
||||
|
@ -18,7 +18,7 @@
|
||||
#include "qapi/qmp/qlist.h"
|
||||
#include "qapi/error.h"
|
||||
|
||||
QObject *json_parser_parse(QList *tokens, va_list *ap);
|
||||
QObject *json_parser_parse_err(QList *tokens, va_list *ap, Error **errp);
|
||||
QObject *json_parser_parse(GQueue *tokens, va_list *ap);
|
||||
QObject *json_parser_parse_err(GQueue *tokens, va_list *ap, Error **errp);
|
||||
|
||||
#endif
|
||||
|
@ -14,21 +14,29 @@
|
||||
#ifndef QEMU_JSON_STREAMER_H
|
||||
#define QEMU_JSON_STREAMER_H
|
||||
|
||||
#include "qapi/qmp/qlist.h"
|
||||
#include <stdint.h>
|
||||
#include "glib-compat.h"
|
||||
#include "qapi/qmp/json-lexer.h"
|
||||
|
||||
typedef struct JSONToken {
|
||||
int type;
|
||||
int x;
|
||||
int y;
|
||||
char str[];
|
||||
} JSONToken;
|
||||
|
||||
typedef struct JSONMessageParser
|
||||
{
|
||||
void (*emit)(struct JSONMessageParser *parser, QList *tokens);
|
||||
void (*emit)(struct JSONMessageParser *parser, GQueue *tokens);
|
||||
JSONLexer lexer;
|
||||
int brace_count;
|
||||
int bracket_count;
|
||||
QList *tokens;
|
||||
GQueue *tokens;
|
||||
uint64_t token_size;
|
||||
} JSONMessageParser;
|
||||
|
||||
void json_message_parser_init(JSONMessageParser *parser,
|
||||
void (*func)(JSONMessageParser *, QList *));
|
||||
void (*func)(JSONMessageParser *, GQueue *));
|
||||
|
||||
int json_message_parser_feed(JSONMessageParser *parser,
|
||||
const char *buffer, size_t size);
|
||||
|
@ -3849,7 +3849,7 @@ static QDict *qmp_check_input_obj(QObject *input_obj, Error **errp)
|
||||
return input_dict;
|
||||
}
|
||||
|
||||
static void handle_qmp_command(JSONMessageParser *parser, QList *tokens)
|
||||
static void handle_qmp_command(JSONMessageParser *parser, GQueue *tokens)
|
||||
{
|
||||
Error *local_err = NULL;
|
||||
QObject *obj, *data;
|
||||
@ -3907,6 +3907,7 @@ static void handle_qmp_command(JSONMessageParser *parser, QList *tokens)
|
||||
err_out:
|
||||
monitor_protocol_emitter(mon, data, local_err);
|
||||
qobject_decref(data);
|
||||
error_free(local_err);
|
||||
QDECREF(input);
|
||||
QDECREF(args);
|
||||
}
|
||||
|
@ -570,7 +570,7 @@ static void process_command(GAState *s, QDict *req)
|
||||
}
|
||||
|
||||
/* handle requests/control events coming in over the channel */
|
||||
static void process_event(JSONMessageParser *parser, QList *tokens)
|
||||
static void process_event(JSONMessageParser *parser, GQueue *tokens)
|
||||
{
|
||||
GAState *s = container_of(parser, GAState, parser);
|
||||
QDict *qdict;
|
||||
|
@ -11,12 +11,9 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include "qapi/qmp/qstring.h"
|
||||
#include "qapi/qmp/qlist.h"
|
||||
#include "qapi/qmp/qdict.h"
|
||||
#include "qapi/qmp/qint.h"
|
||||
#include "qemu-common.h"
|
||||
#include "qapi/qmp/json-lexer.h"
|
||||
#include <stdint.h>
|
||||
|
||||
#define MAX_TOKEN_SIZE (64ULL << 20)
|
||||
|
||||
@ -30,7 +27,7 @@
|
||||
*/
|
||||
|
||||
enum json_lexer_state {
|
||||
IN_ERROR = 0,
|
||||
IN_ERROR = 0, /* must really be 0, see json_lexer[] */
|
||||
IN_DQ_UCODE3,
|
||||
IN_DQ_UCODE2,
|
||||
IN_DQ_UCODE1,
|
||||
@ -62,6 +59,8 @@ enum json_lexer_state {
|
||||
IN_START,
|
||||
};
|
||||
|
||||
QEMU_BUILD_BUG_ON((int)JSON_MIN <= (int)IN_START);
|
||||
|
||||
#define TERMINAL(state) [0 ... 0x7F] = (state)
|
||||
|
||||
/* Return whether TERMINAL is a terminal state and the transition to it
|
||||
@ -71,6 +70,8 @@ enum json_lexer_state {
|
||||
(json_lexer[(old_state)][0] == (terminal))
|
||||
|
||||
static const uint8_t json_lexer[][256] = {
|
||||
/* Relies on default initialization to IN_ERROR! */
|
||||
|
||||
/* double quote string */
|
||||
[IN_DQ_UCODE3] = {
|
||||
['0' ... '9'] = IN_DQ_STRING,
|
||||
@ -253,12 +254,12 @@ static const uint8_t json_lexer[][256] = {
|
||||
['0'] = IN_ZERO,
|
||||
['1' ... '9'] = IN_NONZERO_NUMBER,
|
||||
['-'] = IN_NEG_NONZERO_NUMBER,
|
||||
['{'] = JSON_OPERATOR,
|
||||
['}'] = JSON_OPERATOR,
|
||||
['['] = JSON_OPERATOR,
|
||||
[']'] = JSON_OPERATOR,
|
||||
[','] = JSON_OPERATOR,
|
||||
[':'] = JSON_OPERATOR,
|
||||
['{'] = JSON_LCURLY,
|
||||
['}'] = JSON_RCURLY,
|
||||
['['] = JSON_LSQUARE,
|
||||
[']'] = JSON_RSQUARE,
|
||||
[','] = JSON_COMMA,
|
||||
[':'] = JSON_COLON,
|
||||
['a' ... 'z'] = IN_KEYWORD,
|
||||
['%'] = IN_ESCAPE,
|
||||
[' '] = IN_WHITESPACE,
|
||||
@ -272,7 +273,7 @@ void json_lexer_init(JSONLexer *lexer, JSONLexerEmitter func)
|
||||
{
|
||||
lexer->emit = func;
|
||||
lexer->state = IN_START;
|
||||
lexer->token = qstring_new();
|
||||
lexer->token = g_string_sized_new(3);
|
||||
lexer->x = lexer->y = 0;
|
||||
}
|
||||
|
||||
@ -287,14 +288,20 @@ static int json_lexer_feed_char(JSONLexer *lexer, char ch, bool flush)
|
||||
}
|
||||
|
||||
do {
|
||||
assert(lexer->state <= ARRAY_SIZE(json_lexer));
|
||||
new_state = json_lexer[lexer->state][(uint8_t)ch];
|
||||
char_consumed = !TERMINAL_NEEDED_LOOKAHEAD(lexer->state, new_state);
|
||||
if (char_consumed) {
|
||||
qstring_append_chr(lexer->token, ch);
|
||||
g_string_append_c(lexer->token, ch);
|
||||
}
|
||||
|
||||
switch (new_state) {
|
||||
case JSON_OPERATOR:
|
||||
case JSON_LCURLY:
|
||||
case JSON_RCURLY:
|
||||
case JSON_LSQUARE:
|
||||
case JSON_RSQUARE:
|
||||
case JSON_COLON:
|
||||
case JSON_COMMA:
|
||||
case JSON_ESCAPE:
|
||||
case JSON_INTEGER:
|
||||
case JSON_FLOAT:
|
||||
@ -303,8 +310,7 @@ static int json_lexer_feed_char(JSONLexer *lexer, char ch, bool flush)
|
||||
lexer->emit(lexer, lexer->token, new_state, lexer->x, lexer->y);
|
||||
/* fall through */
|
||||
case JSON_SKIP:
|
||||
QDECREF(lexer->token);
|
||||
lexer->token = qstring_new();
|
||||
g_string_truncate(lexer->token, 0);
|
||||
new_state = IN_START;
|
||||
break;
|
||||
case IN_ERROR:
|
||||
@ -322,8 +328,7 @@ static int json_lexer_feed_char(JSONLexer *lexer, char ch, bool flush)
|
||||
* induce an error/flush state.
|
||||
*/
|
||||
lexer->emit(lexer, lexer->token, JSON_ERROR, lexer->x, lexer->y);
|
||||
QDECREF(lexer->token);
|
||||
lexer->token = qstring_new();
|
||||
g_string_truncate(lexer->token, 0);
|
||||
new_state = IN_START;
|
||||
lexer->state = new_state;
|
||||
return 0;
|
||||
@ -336,10 +341,9 @@ static int json_lexer_feed_char(JSONLexer *lexer, char ch, bool flush)
|
||||
/* Do not let a single token grow to an arbitrarily large size,
|
||||
* this is a security consideration.
|
||||
*/
|
||||
if (lexer->token->length > MAX_TOKEN_SIZE) {
|
||||
if (lexer->token->len > MAX_TOKEN_SIZE) {
|
||||
lexer->emit(lexer, lexer->token, lexer->state, lexer->x, lexer->y);
|
||||
QDECREF(lexer->token);
|
||||
lexer->token = qstring_new();
|
||||
g_string_truncate(lexer->token, 0);
|
||||
lexer->state = IN_START;
|
||||
}
|
||||
|
||||
@ -369,5 +373,5 @@ int json_lexer_flush(JSONLexer *lexer)
|
||||
|
||||
void json_lexer_destroy(JSONLexer *lexer)
|
||||
{
|
||||
QDECREF(lexer->token);
|
||||
g_string_free(lexer->token, true);
|
||||
}
|
||||
|
@ -22,15 +22,13 @@
|
||||
#include "qapi/qmp/qbool.h"
|
||||
#include "qapi/qmp/json-parser.h"
|
||||
#include "qapi/qmp/json-lexer.h"
|
||||
#include "qapi/qmp/json-streamer.h"
|
||||
|
||||
typedef struct JSONParserContext
|
||||
{
|
||||
Error *err;
|
||||
struct {
|
||||
QObject **buf;
|
||||
size_t pos;
|
||||
size_t count;
|
||||
} tokens;
|
||||
JSONToken *current;
|
||||
GQueue *buf;
|
||||
} JSONParserContext;
|
||||
|
||||
#define BUG_ON(cond) assert(!(cond))
|
||||
@ -46,59 +44,11 @@ typedef struct JSONParserContext
|
||||
|
||||
static QObject *parse_value(JSONParserContext *ctxt, va_list *ap);
|
||||
|
||||
/**
|
||||
* Token manipulators
|
||||
*
|
||||
* tokens are dictionaries that contain a type, a string value, and geometry information
|
||||
* about a token identified by the lexer. These are routines that make working with
|
||||
* these objects a bit easier.
|
||||
*/
|
||||
static const char *token_get_value(QObject *obj)
|
||||
{
|
||||
return qdict_get_str(qobject_to_qdict(obj), "token");
|
||||
}
|
||||
|
||||
static JSONTokenType token_get_type(QObject *obj)
|
||||
{
|
||||
return qdict_get_int(qobject_to_qdict(obj), "type");
|
||||
}
|
||||
|
||||
static int token_is_operator(QObject *obj, char op)
|
||||
{
|
||||
const char *val;
|
||||
|
||||
if (token_get_type(obj) != JSON_OPERATOR) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
val = token_get_value(obj);
|
||||
|
||||
return (val[0] == op) && (val[1] == 0);
|
||||
}
|
||||
|
||||
static int token_is_keyword(QObject *obj, const char *value)
|
||||
{
|
||||
if (token_get_type(obj) != JSON_KEYWORD) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return strcmp(token_get_value(obj), value) == 0;
|
||||
}
|
||||
|
||||
static int token_is_escape(QObject *obj, const char *value)
|
||||
{
|
||||
if (token_get_type(obj) != JSON_ESCAPE) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (strcmp(token_get_value(obj), value) == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Error handler
|
||||
*/
|
||||
static void GCC_FMT_ATTR(3, 4) parse_error(JSONParserContext *ctxt,
|
||||
QObject *token, const char *msg, ...)
|
||||
JSONToken *token, const char *msg, ...)
|
||||
{
|
||||
va_list ap;
|
||||
char message[1024];
|
||||
@ -176,9 +126,10 @@ static int hex2decimal(char ch)
|
||||
* \t
|
||||
* \u four-hex-digits
|
||||
*/
|
||||
static QString *qstring_from_escaped_str(JSONParserContext *ctxt, QObject *token)
|
||||
static QString *qstring_from_escaped_str(JSONParserContext *ctxt,
|
||||
JSONToken *token)
|
||||
{
|
||||
const char *ptr = token_get_value(token);
|
||||
const char *ptr = token->str;
|
||||
QString *str;
|
||||
int double_quote = 1;
|
||||
|
||||
@ -274,73 +225,34 @@ out:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static QObject *parser_context_pop_token(JSONParserContext *ctxt)
|
||||
{
|
||||
QObject *token;
|
||||
g_assert(ctxt->tokens.pos < ctxt->tokens.count);
|
||||
token = ctxt->tokens.buf[ctxt->tokens.pos];
|
||||
ctxt->tokens.pos++;
|
||||
return token;
|
||||
}
|
||||
|
||||
/* Note: parser_context_{peek|pop}_token do not increment the
|
||||
* token object's refcount. In both cases the references will continue
|
||||
* to be tracked and cleaned up in parser_context_free(), so do not
|
||||
* attempt to free the token object.
|
||||
/* Note: the token object returned by parser_context_peek_token or
|
||||
* parser_context_pop_token is deleted as soon as parser_context_pop_token
|
||||
* is called again.
|
||||
*/
|
||||
static QObject *parser_context_peek_token(JSONParserContext *ctxt)
|
||||
static JSONToken *parser_context_pop_token(JSONParserContext *ctxt)
|
||||
{
|
||||
QObject *token;
|
||||
g_assert(ctxt->tokens.pos < ctxt->tokens.count);
|
||||
token = ctxt->tokens.buf[ctxt->tokens.pos];
|
||||
return token;
|
||||
g_free(ctxt->current);
|
||||
assert(!g_queue_is_empty(ctxt->buf));
|
||||
ctxt->current = g_queue_pop_head(ctxt->buf);
|
||||
return ctxt->current;
|
||||
}
|
||||
|
||||
static JSONParserContext parser_context_save(JSONParserContext *ctxt)
|
||||
static JSONToken *parser_context_peek_token(JSONParserContext *ctxt)
|
||||
{
|
||||
JSONParserContext saved_ctxt = {0};
|
||||
saved_ctxt.tokens.pos = ctxt->tokens.pos;
|
||||
saved_ctxt.tokens.count = ctxt->tokens.count;
|
||||
saved_ctxt.tokens.buf = ctxt->tokens.buf;
|
||||
return saved_ctxt;
|
||||
assert(!g_queue_is_empty(ctxt->buf));
|
||||
return g_queue_peek_head(ctxt->buf);
|
||||
}
|
||||
|
||||
static void parser_context_restore(JSONParserContext *ctxt,
|
||||
JSONParserContext saved_ctxt)
|
||||
{
|
||||
ctxt->tokens.pos = saved_ctxt.tokens.pos;
|
||||
ctxt->tokens.count = saved_ctxt.tokens.count;
|
||||
ctxt->tokens.buf = saved_ctxt.tokens.buf;
|
||||
}
|
||||
|
||||
static void tokens_append_from_iter(QObject *obj, void *opaque)
|
||||
{
|
||||
JSONParserContext *ctxt = opaque;
|
||||
g_assert(ctxt->tokens.pos < ctxt->tokens.count);
|
||||
ctxt->tokens.buf[ctxt->tokens.pos++] = obj;
|
||||
qobject_incref(obj);
|
||||
}
|
||||
|
||||
static JSONParserContext *parser_context_new(QList *tokens)
|
||||
static JSONParserContext *parser_context_new(GQueue *tokens)
|
||||
{
|
||||
JSONParserContext *ctxt;
|
||||
size_t count;
|
||||
|
||||
if (!tokens) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
count = qlist_size(tokens);
|
||||
if (count == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ctxt = g_malloc0(sizeof(JSONParserContext));
|
||||
ctxt->tokens.pos = 0;
|
||||
ctxt->tokens.count = count;
|
||||
ctxt->tokens.buf = g_malloc(count * sizeof(QObject *));
|
||||
qlist_iter(tokens, tokens_append_from_iter, ctxt);
|
||||
ctxt->tokens.pos = 0;
|
||||
ctxt->buf = tokens;
|
||||
|
||||
return ctxt;
|
||||
}
|
||||
@ -348,12 +260,12 @@ static JSONParserContext *parser_context_new(QList *tokens)
|
||||
/* to support error propagation, ctxt->err must be freed separately */
|
||||
static void parser_context_free(JSONParserContext *ctxt)
|
||||
{
|
||||
int i;
|
||||
if (ctxt) {
|
||||
for (i = 0; i < ctxt->tokens.count; i++) {
|
||||
qobject_decref(ctxt->tokens.buf[i]);
|
||||
while (!g_queue_is_empty(ctxt->buf)) {
|
||||
parser_context_pop_token(ctxt);
|
||||
}
|
||||
g_free(ctxt->tokens.buf);
|
||||
g_free(ctxt->current);
|
||||
g_queue_free(ctxt->buf);
|
||||
g_free(ctxt);
|
||||
}
|
||||
}
|
||||
@ -363,8 +275,8 @@ static void parser_context_free(JSONParserContext *ctxt)
|
||||
*/
|
||||
static int parse_pair(JSONParserContext *ctxt, QDict *dict, va_list *ap)
|
||||
{
|
||||
QObject *key = NULL, *token = NULL, *value, *peek;
|
||||
JSONParserContext saved_ctxt = parser_context_save(ctxt);
|
||||
QObject *key = NULL, *value;
|
||||
JSONToken *peek, *token;
|
||||
|
||||
peek = parser_context_peek_token(ctxt);
|
||||
if (peek == NULL) {
|
||||
@ -384,7 +296,7 @@ static int parse_pair(JSONParserContext *ctxt, QDict *dict, va_list *ap)
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!token_is_operator(token, ':')) {
|
||||
if (token->type != JSON_COLON) {
|
||||
parse_error(ctxt, token, "missing : in object pair");
|
||||
goto out;
|
||||
}
|
||||
@ -402,7 +314,6 @@ static int parse_pair(JSONParserContext *ctxt, QDict *dict, va_list *ap)
|
||||
return 0;
|
||||
|
||||
out:
|
||||
parser_context_restore(ctxt, saved_ctxt);
|
||||
qobject_decref(key);
|
||||
|
||||
return -1;
|
||||
@ -411,17 +322,10 @@ out:
|
||||
static QObject *parse_object(JSONParserContext *ctxt, va_list *ap)
|
||||
{
|
||||
QDict *dict = NULL;
|
||||
QObject *token, *peek;
|
||||
JSONParserContext saved_ctxt = parser_context_save(ctxt);
|
||||
JSONToken *token, *peek;
|
||||
|
||||
token = parser_context_pop_token(ctxt);
|
||||
if (token == NULL) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!token_is_operator(token, '{')) {
|
||||
goto out;
|
||||
}
|
||||
assert(token && token->type == JSON_LCURLY);
|
||||
|
||||
dict = qdict_new();
|
||||
|
||||
@ -431,7 +335,7 @@ static QObject *parse_object(JSONParserContext *ctxt, va_list *ap)
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!token_is_operator(peek, '}')) {
|
||||
if (peek->type != JSON_RCURLY) {
|
||||
if (parse_pair(ctxt, dict, ap) == -1) {
|
||||
goto out;
|
||||
}
|
||||
@ -442,8 +346,8 @@ static QObject *parse_object(JSONParserContext *ctxt, va_list *ap)
|
||||
goto out;
|
||||
}
|
||||
|
||||
while (!token_is_operator(token, '}')) {
|
||||
if (!token_is_operator(token, ',')) {
|
||||
while (token->type != JSON_RCURLY) {
|
||||
if (token->type != JSON_COMMA) {
|
||||
parse_error(ctxt, token, "expected separator in dict");
|
||||
goto out;
|
||||
}
|
||||
@ -465,7 +369,6 @@ static QObject *parse_object(JSONParserContext *ctxt, va_list *ap)
|
||||
return QOBJECT(dict);
|
||||
|
||||
out:
|
||||
parser_context_restore(ctxt, saved_ctxt);
|
||||
QDECREF(dict);
|
||||
return NULL;
|
||||
}
|
||||
@ -473,17 +376,10 @@ out:
|
||||
static QObject *parse_array(JSONParserContext *ctxt, va_list *ap)
|
||||
{
|
||||
QList *list = NULL;
|
||||
QObject *token, *peek;
|
||||
JSONParserContext saved_ctxt = parser_context_save(ctxt);
|
||||
JSONToken *token, *peek;
|
||||
|
||||
token = parser_context_pop_token(ctxt);
|
||||
if (token == NULL) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!token_is_operator(token, '[')) {
|
||||
goto out;
|
||||
}
|
||||
assert(token && token->type == JSON_LSQUARE);
|
||||
|
||||
list = qlist_new();
|
||||
|
||||
@ -493,7 +389,7 @@ static QObject *parse_array(JSONParserContext *ctxt, va_list *ap)
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!token_is_operator(peek, ']')) {
|
||||
if (peek->type != JSON_RSQUARE) {
|
||||
QObject *obj;
|
||||
|
||||
obj = parse_value(ctxt, ap);
|
||||
@ -510,8 +406,8 @@ static QObject *parse_array(JSONParserContext *ctxt, va_list *ap)
|
||||
goto out;
|
||||
}
|
||||
|
||||
while (!token_is_operator(token, ']')) {
|
||||
if (!token_is_operator(token, ',')) {
|
||||
while (token->type != JSON_RSQUARE) {
|
||||
if (token->type != JSON_COMMA) {
|
||||
parse_error(ctxt, token, "expected separator in list");
|
||||
goto out;
|
||||
}
|
||||
@ -537,99 +433,68 @@ static QObject *parse_array(JSONParserContext *ctxt, va_list *ap)
|
||||
return QOBJECT(list);
|
||||
|
||||
out:
|
||||
parser_context_restore(ctxt, saved_ctxt);
|
||||
QDECREF(list);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static QObject *parse_keyword(JSONParserContext *ctxt)
|
||||
{
|
||||
QObject *token, *ret;
|
||||
JSONParserContext saved_ctxt = parser_context_save(ctxt);
|
||||
JSONToken *token;
|
||||
|
||||
token = parser_context_pop_token(ctxt);
|
||||
if (token == NULL) {
|
||||
goto out;
|
||||
assert(token && token->type == JSON_KEYWORD);
|
||||
|
||||
if (!strcmp(token->str, "true")) {
|
||||
return QOBJECT(qbool_from_bool(true));
|
||||
} else if (!strcmp(token->str, "false")) {
|
||||
return QOBJECT(qbool_from_bool(false));
|
||||
} else if (!strcmp(token->str, "null")) {
|
||||
return qnull();
|
||||
}
|
||||
|
||||
if (token_get_type(token) != JSON_KEYWORD) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (token_is_keyword(token, "true")) {
|
||||
ret = QOBJECT(qbool_from_bool(true));
|
||||
} else if (token_is_keyword(token, "false")) {
|
||||
ret = QOBJECT(qbool_from_bool(false));
|
||||
} else if (token_is_keyword(token, "null")) {
|
||||
ret = qnull();
|
||||
} else {
|
||||
parse_error(ctxt, token, "invalid keyword `%s'", token_get_value(token));
|
||||
goto out;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
out:
|
||||
parser_context_restore(ctxt, saved_ctxt);
|
||||
|
||||
parse_error(ctxt, token, "invalid keyword '%s'", token->str);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static QObject *parse_escape(JSONParserContext *ctxt, va_list *ap)
|
||||
{
|
||||
QObject *token = NULL, *obj;
|
||||
JSONParserContext saved_ctxt = parser_context_save(ctxt);
|
||||
JSONToken *token;
|
||||
|
||||
if (ap == NULL) {
|
||||
goto out;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
token = parser_context_pop_token(ctxt);
|
||||
if (token == NULL) {
|
||||
goto out;
|
||||
assert(token && token->type == JSON_ESCAPE);
|
||||
|
||||
if (!strcmp(token->str, "%p")) {
|
||||
return va_arg(*ap, QObject *);
|
||||
} 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)));
|
||||
} else if (!strcmp(token->str, "%ld")) {
|
||||
return QOBJECT(qint_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)));
|
||||
} 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)));
|
||||
}
|
||||
|
||||
if (token_is_escape(token, "%p")) {
|
||||
obj = va_arg(*ap, QObject *);
|
||||
} else if (token_is_escape(token, "%i")) {
|
||||
obj = QOBJECT(qbool_from_bool(va_arg(*ap, int)));
|
||||
} else if (token_is_escape(token, "%d")) {
|
||||
obj = QOBJECT(qint_from_int(va_arg(*ap, int)));
|
||||
} else if (token_is_escape(token, "%ld")) {
|
||||
obj = QOBJECT(qint_from_int(va_arg(*ap, long)));
|
||||
} else if (token_is_escape(token, "%lld") ||
|
||||
token_is_escape(token, "%I64d")) {
|
||||
obj = QOBJECT(qint_from_int(va_arg(*ap, long long)));
|
||||
} else if (token_is_escape(token, "%s")) {
|
||||
obj = QOBJECT(qstring_from_str(va_arg(*ap, const char *)));
|
||||
} else if (token_is_escape(token, "%f")) {
|
||||
obj = QOBJECT(qfloat_from_double(va_arg(*ap, double)));
|
||||
} else {
|
||||
goto out;
|
||||
}
|
||||
|
||||
return obj;
|
||||
|
||||
out:
|
||||
parser_context_restore(ctxt, saved_ctxt);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static QObject *parse_literal(JSONParserContext *ctxt)
|
||||
{
|
||||
QObject *token, *obj;
|
||||
JSONParserContext saved_ctxt = parser_context_save(ctxt);
|
||||
JSONToken *token;
|
||||
|
||||
token = parser_context_pop_token(ctxt);
|
||||
if (token == NULL) {
|
||||
goto out;
|
||||
}
|
||||
assert(token);
|
||||
|
||||
switch (token_get_type(token)) {
|
||||
switch (token->type) {
|
||||
case JSON_STRING:
|
||||
obj = QOBJECT(qstring_from_escaped_str(ctxt, token));
|
||||
break;
|
||||
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
|
||||
@ -646,56 +511,55 @@ static QObject *parse_literal(JSONParserContext *ctxt)
|
||||
int64_t value;
|
||||
|
||||
errno = 0; /* strtoll doesn't set errno on success */
|
||||
value = strtoll(token_get_value(token), NULL, 10);
|
||||
value = strtoll(token->str, NULL, 10);
|
||||
if (errno != ERANGE) {
|
||||
obj = QOBJECT(qint_from_int(value));
|
||||
break;
|
||||
return QOBJECT(qint_from_int(value));
|
||||
}
|
||||
/* fall through to JSON_FLOAT */
|
||||
}
|
||||
case JSON_FLOAT:
|
||||
/* FIXME dependent on locale */
|
||||
obj = QOBJECT(qfloat_from_double(strtod(token_get_value(token), NULL)));
|
||||
break;
|
||||
return QOBJECT(qfloat_from_double(strtod(token->str, NULL)));
|
||||
default:
|
||||
goto out;
|
||||
abort();
|
||||
}
|
||||
|
||||
return obj;
|
||||
|
||||
out:
|
||||
parser_context_restore(ctxt, saved_ctxt);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static QObject *parse_value(JSONParserContext *ctxt, va_list *ap)
|
||||
{
|
||||
QObject *obj;
|
||||
JSONToken *token;
|
||||
|
||||
obj = parse_object(ctxt, ap);
|
||||
if (obj == NULL) {
|
||||
obj = parse_array(ctxt, ap);
|
||||
}
|
||||
if (obj == NULL) {
|
||||
obj = parse_escape(ctxt, ap);
|
||||
}
|
||||
if (obj == NULL) {
|
||||
obj = parse_keyword(ctxt);
|
||||
}
|
||||
if (obj == NULL) {
|
||||
obj = parse_literal(ctxt);
|
||||
token = parser_context_peek_token(ctxt);
|
||||
if (token == NULL) {
|
||||
parse_error(ctxt, NULL, "premature EOI");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return obj;
|
||||
switch (token->type) {
|
||||
case JSON_LCURLY:
|
||||
return parse_object(ctxt, ap);
|
||||
case JSON_LSQUARE:
|
||||
return parse_array(ctxt, ap);
|
||||
case JSON_ESCAPE:
|
||||
return parse_escape(ctxt, ap);
|
||||
case JSON_INTEGER:
|
||||
case JSON_FLOAT:
|
||||
case JSON_STRING:
|
||||
return parse_literal(ctxt);
|
||||
case JSON_KEYWORD:
|
||||
return parse_keyword(ctxt);
|
||||
default:
|
||||
parse_error(ctxt, token, "expecting value");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
QObject *json_parser_parse(QList *tokens, va_list *ap)
|
||||
QObject *json_parser_parse(GQueue *tokens, va_list *ap)
|
||||
{
|
||||
return json_parser_parse_err(tokens, ap, NULL);
|
||||
}
|
||||
|
||||
QObject *json_parser_parse_err(QList *tokens, va_list *ap, Error **errp)
|
||||
QObject *json_parser_parse_err(GQueue *tokens, va_list *ap, Error **errp)
|
||||
{
|
||||
JSONParserContext *ctxt = parser_context_new(tokens);
|
||||
QObject *result;
|
||||
|
@ -11,50 +11,55 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include "qapi/qmp/qlist.h"
|
||||
#include "qapi/qmp/qint.h"
|
||||
#include "qapi/qmp/qdict.h"
|
||||
#include "qemu-common.h"
|
||||
#include "qapi/qmp/json-lexer.h"
|
||||
#include "qapi/qmp/json-streamer.h"
|
||||
|
||||
#define MAX_TOKEN_SIZE (64ULL << 20)
|
||||
#define MAX_TOKEN_COUNT (2ULL << 20)
|
||||
#define MAX_NESTING (1ULL << 10)
|
||||
|
||||
static void json_message_process_token(JSONLexer *lexer, QString *token, JSONTokenType type, int x, int y)
|
||||
static void json_message_free_tokens(JSONMessageParser *parser)
|
||||
{
|
||||
if (parser->tokens) {
|
||||
g_queue_free(parser->tokens);
|
||||
parser->tokens = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void json_message_process_token(JSONLexer *lexer, GString *input,
|
||||
JSONTokenType type, int x, int y)
|
||||
{
|
||||
JSONMessageParser *parser = container_of(lexer, JSONMessageParser, lexer);
|
||||
QDict *dict;
|
||||
JSONToken *token;
|
||||
|
||||
if (type == JSON_OPERATOR) {
|
||||
switch (qstring_get_str(token)[0]) {
|
||||
case '{':
|
||||
parser->brace_count++;
|
||||
break;
|
||||
case '}':
|
||||
parser->brace_count--;
|
||||
break;
|
||||
case '[':
|
||||
parser->bracket_count++;
|
||||
break;
|
||||
case ']':
|
||||
parser->bracket_count--;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
switch (type) {
|
||||
case JSON_LCURLY:
|
||||
parser->brace_count++;
|
||||
break;
|
||||
case JSON_RCURLY:
|
||||
parser->brace_count--;
|
||||
break;
|
||||
case JSON_LSQUARE:
|
||||
parser->bracket_count++;
|
||||
break;
|
||||
case JSON_RSQUARE:
|
||||
parser->bracket_count--;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
dict = qdict_new();
|
||||
qdict_put(dict, "type", qint_from_int(type));
|
||||
QINCREF(token);
|
||||
qdict_put(dict, "token", token);
|
||||
qdict_put(dict, "x", qint_from_int(x));
|
||||
qdict_put(dict, "y", qint_from_int(y));
|
||||
token = g_malloc(sizeof(JSONToken) + input->len + 1);
|
||||
token->type = type;
|
||||
memcpy(token->str, input->str, input->len);
|
||||
token->str[input->len] = 0;
|
||||
token->x = x;
|
||||
token->y = y;
|
||||
|
||||
parser->token_size += token->length;
|
||||
parser->token_size += input->len;
|
||||
|
||||
qlist_append(parser->tokens, dict);
|
||||
g_queue_push_tail(parser->tokens, token);
|
||||
|
||||
if (type == JSON_ERROR) {
|
||||
goto out_emit_bad;
|
||||
@ -64,41 +69,39 @@ static void json_message_process_token(JSONLexer *lexer, QString *token, JSONTok
|
||||
parser->bracket_count == 0)) {
|
||||
goto out_emit;
|
||||
} else if (parser->token_size > MAX_TOKEN_SIZE ||
|
||||
parser->bracket_count > MAX_NESTING ||
|
||||
parser->brace_count > MAX_NESTING) {
|
||||
g_queue_get_length(parser->tokens) > MAX_TOKEN_COUNT ||
|
||||
parser->bracket_count + parser->brace_count > MAX_NESTING) {
|
||||
/* Security consideration, we limit total memory allocated per object
|
||||
* and the maximum recursion depth that a message can force.
|
||||
*/
|
||||
goto out_emit;
|
||||
goto out_emit_bad;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
out_emit_bad:
|
||||
/* clear out token list and tell the parser to emit and error
|
||||
/*
|
||||
* Clear out token list and tell the parser to emit an error
|
||||
* indication by passing it a NULL list
|
||||
*/
|
||||
QDECREF(parser->tokens);
|
||||
parser->tokens = NULL;
|
||||
json_message_free_tokens(parser);
|
||||
out_emit:
|
||||
/* send current list of tokens to parser and reset tokenizer */
|
||||
parser->brace_count = 0;
|
||||
parser->bracket_count = 0;
|
||||
/* parser->emit takes ownership of parser->tokens. */
|
||||
parser->emit(parser, parser->tokens);
|
||||
if (parser->tokens) {
|
||||
QDECREF(parser->tokens);
|
||||
}
|
||||
parser->tokens = qlist_new();
|
||||
parser->tokens = g_queue_new();
|
||||
parser->token_size = 0;
|
||||
}
|
||||
|
||||
void json_message_parser_init(JSONMessageParser *parser,
|
||||
void (*func)(JSONMessageParser *, QList *))
|
||||
void (*func)(JSONMessageParser *, GQueue *))
|
||||
{
|
||||
parser->emit = func;
|
||||
parser->brace_count = 0;
|
||||
parser->bracket_count = 0;
|
||||
parser->tokens = qlist_new();
|
||||
parser->tokens = g_queue_new();
|
||||
parser->token_size = 0;
|
||||
|
||||
json_lexer_init(&parser->lexer, json_message_process_token);
|
||||
@ -118,5 +121,5 @@ int json_message_parser_flush(JSONMessageParser *parser)
|
||||
void json_message_parser_destroy(JSONMessageParser *parser)
|
||||
{
|
||||
json_lexer_destroy(&parser->lexer);
|
||||
QDECREF(parser->tokens);
|
||||
json_message_free_tokens(parser);
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ typedef struct JSONParsingState
|
||||
QObject *result;
|
||||
} JSONParsingState;
|
||||
|
||||
static void parse_json(JSONMessageParser *parser, QList *tokens)
|
||||
static void parse_json(JSONMessageParser *parser, GQueue *tokens)
|
||||
{
|
||||
JSONParsingState *s = container_of(parser, JSONParsingState, parser);
|
||||
s->result = json_parser_parse(tokens, s->ap);
|
||||
|
@ -1484,6 +1484,30 @@ static void unterminated_literal(void)
|
||||
g_assert(obj == NULL);
|
||||
}
|
||||
|
||||
static char *make_nest(char *buf, size_t cnt)
|
||||
{
|
||||
memset(buf, '[', cnt - 1);
|
||||
buf[cnt - 1] = '{';
|
||||
buf[cnt] = '}';
|
||||
memset(buf + cnt + 1, ']', cnt - 1);
|
||||
buf[2 * cnt] = 0;
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void limits_nesting(void)
|
||||
{
|
||||
enum { max_nesting = 1024 }; /* see qobject/json-streamer.c */
|
||||
char buf[2 * (max_nesting + 1) + 1];
|
||||
QObject *obj;
|
||||
|
||||
obj = qobject_from_json(make_nest(buf, max_nesting));
|
||||
g_assert(obj != NULL);
|
||||
qobject_decref(obj);
|
||||
|
||||
obj = qobject_from_json(make_nest(buf, max_nesting + 1));
|
||||
g_assert(obj == NULL);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
g_test_init(&argc, &argv, NULL);
|
||||
@ -1519,6 +1543,7 @@ int main(int argc, char **argv)
|
||||
g_test_add_func("/errors/invalid_array_comma", invalid_array_comma);
|
||||
g_test_add_func("/errors/invalid_dict_comma", invalid_dict_comma);
|
||||
g_test_add_func("/errors/unterminated/literal", unterminated_literal);
|
||||
g_test_add_func("/errors/limits/nesting", limits_nesting);
|
||||
|
||||
return g_test_run();
|
||||
}
|
||||
|
@ -351,7 +351,7 @@ typedef struct {
|
||||
QDict *response;
|
||||
} QMPResponseParser;
|
||||
|
||||
static void qmp_response(JSONMessageParser *parser, QList *tokens)
|
||||
static void qmp_response(JSONMessageParser *parser, GQueue *tokens)
|
||||
{
|
||||
QMPResponseParser *qmp = container_of(parser, QMPResponseParser, parser);
|
||||
QObject *obj;
|
||||
|
Loading…
Reference in New Issue
Block a user