diff options
| author | Stefan Hajnoczi <stefanha@redhat.com> | 2026-07-03 07:42:51 +0200 |
|---|---|---|
| committer | Stefan Hajnoczi <stefanha@redhat.com> | 2026-07-03 07:42:51 +0200 |
| commit | 7f2007d1924565c7a38b2c6ba01ebc1a85db0a49 (patch) | |
| tree | 94184f69d31d71bee9bbd09f81b20636a2b21107 | |
| parent | b4bdad7dcef623d2874df4e79e0be01075096c3b (diff) | |
| parent | b6368e7c77aad190f44570f589ccaa4b5cb4818d (diff) | |
| download | qemu-7f2007d1924565c7a38b2c6ba01ebc1a85db0a49.tar.gz qemu-7f2007d1924565c7a38b2c6ba01ebc1a85db0a49.zip | |
Merge tag 'pull-qobject-2026-07-02' of https://repo.or.cz/qemu/armbru into staging
QObject patches for 2026-07-02
# -----BEGIN PGP SIGNATURE-----
#
# iQJGBAABCgAwFiEENUvIs9frKmtoZ05fOHC0AOuRhlMFAmpGUpgSHGFybWJydUBy
# ZWRoYXQuY29tAAoJEDhwtADrkYZTA3wP/jXwy30ND7OQE5HErDKp4priCm9cMPcR
# 30FxOS+E9bAT6KCDmnUqRVELebGn0x3gz9MXWO8qQcU4MHneN4M/42Dh/oa1t+xd
# Zva2rIfVW1FHPvzEfsKsUFPq1LaY+uM9XW2DdEjyv11RoMZwVHiWseURXwB2HNLH
# HNfxBOlGa8VTvI5kpRafN+DNRjeMEWcIXqZRHr51GeFfc6av0CTUYM2xdwRQTw3m
# GQGDvLIllxb9STwnSKieBdkCpBGSwffcjJipt7VmvORYNsi3pcJgAhWFzM9UWqPp
# ZENwziphyd/4XLAC4EaMlXnXlAzLJh+H7+th6Ylirkjo1sULO9cCFbYcANqljLdT
# GIIafRoe4zuZp1gFPgT51nA53Adzd1TWCT462R1uzbVJxoEdthsYVB7WMamBJySA
# MuuMjq4JVeROBgDbO/X7+EnzmI0torT24XpFxFjfBGb9zSnG7YRzSSf8sDQIwir+
# tlh0e6LzsVJckJ0IUAjglLKb49QUnmzrSx8+6LCH0iFMkHWwXdtd5Vyk5SofIXIy
# Dc6t2qH8USkOjwgs5vKdr9m8LEv2iELegpanmZDTd+zIEFR1WP1SRiirmkoD7QJY
# 0zK3gabC5uUpcfKx6EE2UXWpFW/tVDjMUN43QoL48/pVDuYUcqXJ8y2DEqjHV4Sd
# O5wd3ZOFdvVc
# =jshD
# -----END PGP SIGNATURE-----
# gpg: Signature made Thu 02 Jul 2026 13:59:20 CEST
# gpg: using RSA key 354BC8B3D7EB2A6B68674E5F3870B400EB918653
# gpg: issuer "armbru@redhat.com"
# gpg: Good signature from "Markus Armbruster <armbru@redhat.com>" [full]
# gpg: aka "Markus Armbruster <armbru@pond.sub.org>" [full]
# Primary key fingerprint: 354B C8B3 D7EB 2A6B 6867 4E5F 3870 B400 EB91 8653
* tag 'pull-qobject-2026-07-02' of https://repo.or.cz/qemu/armbru:
json-parser: add location to JSON parsing errors
json-streamer: do not heap-allocate JSONToken
json-streamer: remove token queue
json-streamer: make brace/bracket count unsigned
json-streamer: reuse parser
json-parser: replace with a push parser
qobject/json-writer: preallocate output buffer
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
| -rw-r--r-- | include/qobject/json-parser.h | 16 | ||||
| -rw-r--r-- | qobject/json-lexer.c | 11 | ||||
| -rw-r--r-- | qobject/json-parser-int.h | 13 | ||||
| -rw-r--r-- | qobject/json-parser.c | 587 | ||||
| -rw-r--r-- | qobject/json-streamer.c | 121 | ||||
| -rw-r--r-- | qobject/json-writer.c | 5 |
6 files changed, 431 insertions, 322 deletions
diff --git a/include/qobject/json-parser.h b/include/qobject/json-parser.h index 7345a9bd5c..e078b36b2d 100644 --- a/include/qobject/json-parser.h +++ b/include/qobject/json-parser.h @@ -17,17 +17,25 @@ typedef struct JSONLexer { int start_state, state; GString *token; + int cur_x, cur_y; int x, y; } JSONLexer; +typedef struct JSONParserContext { + Error *err; + GQueue *stack; + va_list *ap; +} JSONParserContext; + typedef struct JSONMessageParser { void (*emit)(void *opaque, QObject *json, Error *err); void *opaque; - va_list *ap; JSONLexer lexer; - int brace_count; - int bracket_count; - GQueue tokens; + JSONParserContext parser; + unsigned int brace_count; + unsigned int bracket_count; + unsigned int token_count; + bool error; uint64_t token_size; } JSONMessageParser; diff --git a/qobject/json-lexer.c b/qobject/json-lexer.c index 51341d96e4..7753ba6c09 100644 --- a/qobject/json-lexer.c +++ b/qobject/json-lexer.c @@ -277,7 +277,8 @@ void json_lexer_init(JSONLexer *lexer, bool enable_interpolation) lexer->start_state = lexer->state = enable_interpolation ? IN_START_INTERP : IN_START; lexer->token = g_string_sized_new(3); - lexer->x = lexer->y = 0; + lexer->cur_x = lexer->cur_y = 1; + lexer->x = lexer->y = 1; } static void json_lexer_feed_char(JSONLexer *lexer, char ch, bool flush) @@ -285,10 +286,10 @@ static void json_lexer_feed_char(JSONLexer *lexer, char ch, bool flush) int new_state; bool char_consumed = false; - lexer->x++; + lexer->cur_x++; if (ch == '\n') { - lexer->x = 0; - lexer->y++; + lexer->cur_x = 1; + lexer->cur_y++; } while (flush ? lexer->state != lexer->start_state : !char_consumed) { @@ -316,6 +317,8 @@ static void json_lexer_feed_char(JSONLexer *lexer, char ch, bool flush) case IN_START: g_string_truncate(lexer->token, 0); new_state = lexer->start_state; + lexer->x = lexer->cur_x; + lexer->y = lexer->cur_y; break; case JSON_ERROR: json_message_process_token(lexer, lexer->token, JSON_ERROR, diff --git a/qobject/json-parser-int.h b/qobject/json-parser-int.h index 8c01f23627..5a6b5c9af9 100644 --- a/qobject/json-parser-int.h +++ b/qobject/json-parser-int.h @@ -35,7 +35,12 @@ typedef enum json_token_type { JSON_MAX = JSON_END_OF_INPUT } JSONTokenType; -typedef struct JSONToken JSONToken; +typedef struct JSONToken { + JSONTokenType type; + int x; + int y; + char *str; +} JSONToken; /* json-lexer.c */ void json_lexer_init(JSONLexer *lexer, bool enable_interpolation); @@ -48,7 +53,9 @@ void json_message_process_token(JSONLexer *lexer, GString *input, JSONTokenType type, int x, int y); /* json-parser.c */ -JSONToken *json_token(JSONTokenType type, int x, int y, GString *tokstr); -QObject *json_parser_parse(GQueue *tokens, va_list *ap, Error **errp); +void json_parser_init(JSONParserContext *ctxt, va_list *ap); +void json_parser_reset(JSONParserContext *ctxt); +QObject *json_parser_feed(JSONParserContext *ctxt, const JSONToken *token, Error **errp); +void json_parser_destroy(JSONParserContext *ctxt); #endif diff --git a/qobject/json-parser.c b/qobject/json-parser.c index f6622b82b0..5935730c0c 100644 --- a/qobject/json-parser.c +++ b/qobject/json-parser.c @@ -24,32 +24,136 @@ #include "qobject/qstring.h" #include "json-parser-int.h" -struct JSONToken { - JSONTokenType type; - int x; - int y; - char str[]; -}; - -typedef struct JSONParserContext { - Error *err; - JSONToken *current; - GQueue *buf; - va_list *ap; -} JSONParserContext; +/* + * The JSON parser is a push parser, returning a completed top-level + * object, an error, or NULL (if the object is incomplete and no error + * happened) after every token. Therefore it has an explicit + * representation of its parser stack; each stack entry consists of a + * parser state and a QObject: - a QList, for an array that is being + * added to - a QDict, for a dictionary that is being added to - a + * QString, for the key of the next pair that will be added to a QDict + * + * The stack represents an arbitrary nesting of arrays and dictionaries + * (whose next key has been parsed); it can also have a dictionary whose + * next key has not been parsed, but that can only happen at the top level. + * Because of this, the stack contents are always of the form + * "(QList | QDict QString)* QDict?". + * + * An empty stack represents the beginning of the parsing process, with + * start state BEFORE_VALUE. + */ -#define BUG_ON(cond) assert(!(cond)) +typedef enum JSONParserState { + AFTER_LCURLY, + AFTER_LSQUARE, + BEFORE_KEY, + BEFORE_VALUE, + END_OF_KEY, + END_OF_VALUE, +} JSONParserState; + +typedef struct JSONParserStackEntry { + /* + * State when the container is completed or, for the top of the stack, + * entry state for the next token. + */ + JSONParserState state; + + /* + * A QString with the last parsed key, or a QList/QDict for the current + * container. + */ + QObject *partial; +} JSONParserStackEntry; -/** - * TODO +/* + * This is the JSON grammar that's parsed, with the state transition and + * action at each point of the grammar. While this is not a formal + * description, "-> action" represents the pseudocode of the action + * and "-> STATE" sets the top stack entry's state to STATE. + * + * The state alone is enough to tell you what to parse; the state plus + * the type of the top of stack tells you which action to take. + * + * // The initial state is BEFORE_VALUE. + * input := value -> END_OF_VALUE -> return parsed value + * (input | END_OF_INPUT) + * + * // entered on BEFORE_VALUE; after any of these rules are processed, the + * // parser has completed a QObject and is in the END_OF_VALUE state. + * // + * // When the parser reaches the END_OF_VALUE state, it examines the + * // top of the stack to see if it's coming from "input" (stack empty), + * // "array_items" (TOS is a QList) or "dict_pairs" (TOS is a QString; the + * // item below will be a QDict). It then proceeds with the corresponding + * // actions, which will be one of: + * // - return parsed value + * // - add value to QList + * // - pop QString with the key, add key/value to the QDict + * value := literal -> END_OF_VALUE + * | '[' -> push empty QList -> AFTER_LSQUARE + * after_lsquare -> END_OF_VALUE + * | '{' -> push empty QDict -> AFTER_LCURLY + * after_lcurly -> END_OF_VALUE + * + * // non-recursive values, entered on BEFORE_VALUE + * literal := INTEGER -> END_OF_VALUE + * | FLOAT -> END_OF_VALUE + * | KEYWORD -> END_OF_VALUE + * | STRING -> END_OF_VALUE + * | INTERP -> END_OF_VALUE * - * 0) make errors meaningful again - * 1) add geometry information to tokens - * 3) should we return a parsed size? - * 4) deal with premature EOI + * // entered on AFTER_LSQUARE + * after_lsquare := ']' -> pop completed QList -> END_OF_VALUE + * | ϵ -> BEFORE_VALUE + * array_items -> END_OF_VALUE + * + * // entered on BEFORE_VALUE, with TOS being a QList + * array_items := value -> add value to QList -> END_OF_VALUE + * (']' -> pop completed QList -> END_OF_VALUE + * | ',' -> BEFORE_VALUE + * array_items) -> END_OF_VALUE + * + * // entered on AFTER_LCURLY + * after_lcurly := '}' -> pop completed QDict -> END_OF_VALUE + * | ϵ -> BEFORE_KEY + * dict_pairs -> END_OF_VALUE + * + * // entered on BEFORE_KEY, with TOS being a QDict + * dict_pairs := (STRING | INTERP) -> push QString -> END_OF_KEY + * ':' -> BEFORE_VALUE + * value -> pop QString + add pair to QDict -> END_OF_VALUE + * ('}' -> pop completed QDict -> END_OF_VALUE + * | ',' -> BEFORE_KEY + * dict_pairs) -> END_OF_VALUE + * + * Parse errors ignore the token. json_parser_reset() can be + * called to restart parsing from scratch, with an empty stack. */ -static QObject *parse_value(JSONParserContext *ctxt); +#define BUG_ON(cond) assert(!(cond)) + +static inline JSONParserStackEntry *current_entry(JSONParserContext *ctxt) +{ + return g_queue_peek_tail(ctxt->stack); +} + +static void push_entry(JSONParserContext *ctxt, QObject *partial, + JSONParserState state) +{ + JSONParserStackEntry *entry = g_new(JSONParserStackEntry, 1); + entry->partial = partial; + entry->state = state; + g_queue_push_tail(ctxt->stack, entry); +} + +/* Drop the top entry and return the new top entry. */ +static JSONParserStackEntry *pop_entry(JSONParserContext *ctxt) +{ + JSONParserStackEntry *entry = g_queue_pop_tail(ctxt->stack); + g_free(entry); + return current_entry(ctxt); +} /** * Error handler @@ -67,7 +171,8 @@ static void G_GNUC_PRINTF(3, 4) parse_error(JSONParserContext *ctxt, va_start(ap, msg); vsnprintf(message, sizeof(message), msg, ap); va_end(ap); - error_setg(&ctxt->err, "JSON parse error, %s", message); + error_setg(&ctxt->err, "%d:%d: JSON parse error, %s", + token->y, token->x, message); } static int cvt4hex(const char *s) @@ -236,200 +341,10 @@ out: return NULL; } -/* 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 const JSONToken *parser_context_pop_token(JSONParserContext *ctxt) -{ - g_free(ctxt->current); - ctxt->current = g_queue_pop_head(ctxt->buf); - return ctxt->current; -} - -static const JSONToken *parser_context_peek_token(JSONParserContext *ctxt) -{ - return g_queue_peek_head(ctxt->buf); -} - -/** - * Parsing rules - */ -static int parse_pair(JSONParserContext *ctxt, QDict *dict) -{ - QObject *key_obj = NULL; - QString *key; - QObject *value; - const JSONToken *peek, *token; - - peek = parser_context_peek_token(ctxt); - if (peek == NULL) { - parse_error(ctxt, NULL, "premature EOI"); - goto out; - } - - key_obj = parse_value(ctxt); - key = qobject_to(QString, key_obj); - if (!key) { - parse_error(ctxt, peek, "key is not a string in object"); - goto out; - } - - token = parser_context_pop_token(ctxt); - if (token == NULL) { - parse_error(ctxt, NULL, "premature EOI"); - goto out; - } - - if (token->type != JSON_COLON) { - parse_error(ctxt, token, "missing : in object pair"); - goto out; - } - - value = parse_value(ctxt); - if (value == NULL) { - parse_error(ctxt, token, "Missing value in dict"); - goto out; - } - - if (qdict_haskey(dict, qstring_get_str(key))) { - parse_error(ctxt, token, "duplicate key"); - goto out; - } - - qdict_put_obj(dict, qstring_get_str(key), value); - - qobject_unref(key_obj); - return 0; - -out: - qobject_unref(key_obj); - return -1; -} - -static QObject *parse_object(JSONParserContext *ctxt) -{ - QDict *dict = NULL; - const JSONToken *token, *peek; - - token = parser_context_pop_token(ctxt); - assert(token && token->type == JSON_LCURLY); - - dict = qdict_new(); - - peek = parser_context_peek_token(ctxt); - if (peek == NULL) { - parse_error(ctxt, NULL, "premature EOI"); - goto out; - } - - if (peek->type != JSON_RCURLY) { - if (parse_pair(ctxt, dict) == -1) { - goto out; - } - - token = parser_context_pop_token(ctxt); - if (token == NULL) { - parse_error(ctxt, NULL, "premature EOI"); - goto out; - } - - while (token->type != JSON_RCURLY) { - if (token->type != JSON_COMMA) { - parse_error(ctxt, token, "expected separator in dict"); - goto out; - } - - if (parse_pair(ctxt, dict) == -1) { - goto out; - } - - token = parser_context_pop_token(ctxt); - if (token == NULL) { - parse_error(ctxt, NULL, "premature EOI"); - goto out; - } - } - } else { - (void)parser_context_pop_token(ctxt); - } - - return QOBJECT(dict); - -out: - qobject_unref(dict); - return NULL; -} - -static QObject *parse_array(JSONParserContext *ctxt) -{ - QList *list = NULL; - const JSONToken *token, *peek; - - token = parser_context_pop_token(ctxt); - assert(token && token->type == JSON_LSQUARE); - - list = qlist_new(); +/* Terminals */ - peek = parser_context_peek_token(ctxt); - if (peek == NULL) { - parse_error(ctxt, NULL, "premature EOI"); - goto out; - } - - if (peek->type != JSON_RSQUARE) { - QObject *obj; - - obj = parse_value(ctxt); - if (obj == NULL) { - parse_error(ctxt, token, "expecting value"); - goto out; - } - - qlist_append_obj(list, obj); - - token = parser_context_pop_token(ctxt); - if (token == NULL) { - parse_error(ctxt, NULL, "premature EOI"); - goto out; - } - - while (token->type != JSON_RSQUARE) { - if (token->type != JSON_COMMA) { - parse_error(ctxt, token, "expected separator in list"); - goto out; - } - - obj = parse_value(ctxt); - if (obj == NULL) { - parse_error(ctxt, token, "expecting value"); - goto out; - } - - qlist_append_obj(list, obj); - - token = parser_context_pop_token(ctxt); - if (token == NULL) { - parse_error(ctxt, NULL, "premature EOI"); - goto out; - } - } - } else { - (void)parser_context_pop_token(ctxt); - } - - return QOBJECT(list); - -out: - qobject_unref(list); - return NULL; -} - -static QObject *parse_keyword(JSONParserContext *ctxt) +static QObject *parse_keyword(JSONParserContext *ctxt, const JSONToken *token) { - const JSONToken *token; - - token = parser_context_pop_token(ctxt); assert(token && token->type == JSON_KEYWORD); if (!strcmp(token->str, "true")) { @@ -443,11 +358,9 @@ static QObject *parse_keyword(JSONParserContext *ctxt) return NULL; } -static QObject *parse_interpolation(JSONParserContext *ctxt) +static QObject *parse_interpolation(JSONParserContext *ctxt, + const JSONToken *token) { - const JSONToken *token; - - token = parser_context_pop_token(ctxt); assert(token && token->type == JSON_INTERP); if (!strcmp(token->str, "%p")) { @@ -479,11 +392,8 @@ static QObject *parse_interpolation(JSONParserContext *ctxt) return NULL; } -static QObject *parse_literal(JSONParserContext *ctxt) +static QObject *parse_literal(JSONParserContext *ctxt, const JSONToken *token) { - const JSONToken *token; - - token = parser_context_pop_token(ctxt); assert(token); switch (token->type) { @@ -531,61 +441,230 @@ static QObject *parse_literal(JSONParserContext *ctxt) } } -static QObject *parse_value(JSONParserContext *ctxt) -{ - const JSONToken *token; - - token = parser_context_peek_token(ctxt); - if (token == NULL) { - parse_error(ctxt, NULL, "premature EOI"); - return NULL; - } +/* Parsing state machine */ +static QObject *parse_begin_value(JSONParserContext *ctxt, + const JSONToken *token) +{ switch (token->type) { case JSON_LCURLY: - return parse_object(ctxt); + push_entry(ctxt, QOBJECT(qdict_new()), AFTER_LCURLY); + return NULL; case JSON_LSQUARE: - return parse_array(ctxt); + push_entry(ctxt, QOBJECT(qlist_new()), AFTER_LSQUARE); + return NULL; case JSON_INTERP: - return parse_interpolation(ctxt); + return parse_interpolation(ctxt, token); case JSON_INTEGER: case JSON_FLOAT: case JSON_STRING: - return parse_literal(ctxt); + return parse_literal(ctxt, token); case JSON_KEYWORD: - return parse_keyword(ctxt); + return parse_keyword(ctxt, token); default: parse_error(ctxt, token, "expecting value"); return NULL; } } -JSONToken *json_token(JSONTokenType type, int x, int y, GString *tokstr) +static QObject *parse_token(JSONParserContext *ctxt, const JSONToken *token) +{ + JSONParserStackEntry *entry; + JSONParserState state; + QString *key; + QObject *key_obj = NULL, *value = NULL; + + entry = current_entry(ctxt); + state = entry ? entry->state : BEFORE_VALUE; + switch (state) { + case AFTER_LCURLY: + /* Grab '}' for empty object or fall through to BEFORE_KEY */ + assert(qobject_type(entry->partial) == QTYPE_QDICT); + if (token->type == JSON_RCURLY) { + value = entry->partial; + entry = pop_entry(ctxt); + break; + } + entry->state = BEFORE_KEY; + /* fall through */ + + case BEFORE_KEY: + /* Expecting object key */ + assert(qobject_type(entry->partial) == QTYPE_QDICT); + if (token->type != JSON_STRING && token->type != JSON_INTERP) { + parse_error(ctxt, token, "expecting key"); + return NULL; + } + + key_obj = parse_begin_value(ctxt, token); + if (!key_obj) { + /* Parse error already reported */ + } else if (qobject_type(key_obj) != QTYPE_QSTRING) { + /* An interpolation was valid syntactically but not %s */ + parse_error(ctxt, token, "key is not a string in object"); + } else { + /* Store key in a special entry on the stack */ + push_entry(ctxt, key_obj, END_OF_KEY); + } + return NULL; + + case END_OF_KEY: + /* Expecting ':' after key */ + assert(qobject_type(entry->partial) == QTYPE_QSTRING); + if (token->type == JSON_COLON) { + entry->state = BEFORE_VALUE; + } else { + parse_error(ctxt, token, "expecting ':'"); + } + return NULL; + + case AFTER_LSQUARE: + /* Grab ']' for empty array or fall through to BEFORE_VALUE */ + assert(qobject_type(entry->partial) == QTYPE_QLIST); + if (token->type == JSON_RSQUARE) { + value = entry->partial; + entry = pop_entry(ctxt); + break; + } + entry->state = BEFORE_VALUE; + /* fall through */ + + case BEFORE_VALUE: + /* Expecting value */ + assert(!entry || qobject_type(entry->partial) != QTYPE_QDICT); + value = parse_begin_value(ctxt, token); + if (!value) { + /* Error or '['/'{' */ + return NULL; + } + /* Return value or insert it into a container */ + break; + + case END_OF_VALUE: + /* Grab ',' or ']' for array; ',' or '}' for object */ + if (qobject_to(QList, entry->partial)) { + /* Array */ + if (token->type != JSON_RSQUARE) { + if (token->type == JSON_COMMA) { + entry->state = BEFORE_VALUE; + } else { + parse_error(ctxt, token, "expected ',' or ']'"); + } + return NULL; + } + } else if (qobject_to(QDict, entry->partial)) { + /* Object */ + if (token->type != JSON_RCURLY) { + if (token->type == JSON_COMMA) { + entry->state = BEFORE_KEY; + } else { + parse_error(ctxt, token, "expected ',' or '}'"); + } + return NULL; + } + } else { + g_assert_not_reached(); + } + + /* Got ']' or '}'; return full value or insert into parent container */ + value = entry->partial; + entry = pop_entry(ctxt); + break; + } + + assert(value); + if (entry == NULL) { + /* Parse stack now empty, the top-level value is complete. */ + return value; + } + + /* + * Parse stack is not empty and entry->partial is the top of stack. + * It's a QString with the key (and a QDict is below it) if we're + * parsing an object, or a QList if we're parsing an array. + */ + key = qobject_to(QString, entry->partial); + if (key) { + const char *key_str; + QDict *dict; + + /* Pop off key, and store (key, value) in QDict. */ + entry = pop_entry(ctxt); + dict = qobject_to(QDict, entry->partial); + assert(dict); + key_str = qstring_get_str(key); + if (qdict_haskey(dict, key_str)) { + parse_error(ctxt, token, "duplicate key"); + qobject_unref(value); + return NULL; + } + qdict_put_obj(dict, key_str, value); + qobject_unref(key); + } else { + /* Array, just store value in the QList. */ + qlist_append_obj(qobject_to(QList, entry->partial), value); + } + + entry->state = END_OF_VALUE; + return NULL; +} + + +void json_parser_reset(JSONParserContext *ctxt) { - JSONToken *token = g_malloc(sizeof(JSONToken) + tokstr->len + 1); - - token->type = type; - memcpy(token->str, tokstr->str, tokstr->len); - token->str[tokstr->len] = 0; - token->x = x; - token->y = y; - return token; + JSONParserStackEntry *entry; + + ctxt->err = NULL; + while ((entry = g_queue_pop_tail(ctxt->stack)) != NULL) { + qobject_unref(entry->partial); + g_free(entry); + } } -QObject *json_parser_parse(GQueue *tokens, va_list *ap, Error **errp) +void json_parser_init(JSONParserContext *ctxt, va_list *ap) { - JSONParserContext ctxt = { .buf = tokens, .ap = ap }; - QObject *result; + ctxt->stack = g_queue_new(); + ctxt->ap = ap; + json_parser_reset(ctxt); +} - result = parse_value(&ctxt); - assert(ctxt.err || g_queue_is_empty(ctxt.buf)); +void json_parser_destroy(JSONParserContext *ctxt) +{ + json_parser_reset(ctxt); + g_queue_free(ctxt->stack); + ctxt->stack = NULL; +} + +/* + * Advance the parser based on the token that is passed. + * Return the finished top-level value if the token completes it, else + * NULL. + * Once an error is returned, the function must not be called again + * without first resetting the parser. + */ +QObject *json_parser_feed(JSONParserContext *ctxt, const JSONToken *token, + Error **errp) +{ + QObject *result = NULL; - error_propagate(errp, ctxt.err); + assert(!ctxt->err); + switch (token->type) { + case JSON_ERROR: + parse_error(ctxt, token, "stray '%s'", token->str); + break; + + case JSON_END_OF_INPUT: + /* Check for premature end of input */ + if (!g_queue_is_empty(ctxt->stack)) { + parse_error(ctxt, token, "premature end of input"); + } + break; - while (!g_queue_is_empty(ctxt.buf)) { - parser_context_pop_token(&ctxt); + default: + result = parse_token(ctxt, token); + break; } - g_free(ctxt.current); + error_propagate(errp, ctxt->err); return result; } diff --git a/qobject/json-streamer.c b/qobject/json-streamer.c index b93d97b995..b634bbe99c 100644 --- a/qobject/json-streamer.c +++ b/qobject/json-streamer.c @@ -1,5 +1,5 @@ /* - * JSON streaming support + * JSON parser - callback interface and error recovery * * Copyright IBM, Corp. 2009 * @@ -19,84 +19,93 @@ #define MAX_TOKEN_COUNT (2ULL << 20) #define MAX_NESTING (1 << 10) -static void json_message_free_tokens(JSONMessageParser *parser) -{ - JSONToken *token; - - while ((token = g_queue_pop_head(&parser->tokens))) { - g_free(token); - } -} - void json_message_process_token(JSONLexer *lexer, GString *input, JSONTokenType type, int x, int y) { JSONMessageParser *parser = container_of(lexer, JSONMessageParser, lexer); - QObject *json = NULL; Error *err = NULL; - JSONToken *token; + parser->token_size += input->len; + parser->token_count++; + + /* Detect message boundaries for error recovery purposes. */ switch (type) { case JSON_LCURLY: parser->brace_count++; break; case JSON_RCURLY: + if (!parser->brace_count) { + goto end_error_recovery; + } parser->brace_count--; break; case JSON_LSQUARE: parser->bracket_count++; break; case JSON_RSQUARE: + if (!parser->bracket_count) { + goto end_error_recovery; + } parser->bracket_count--; break; case JSON_ERROR: - error_setg(&err, "JSON parse error, stray '%s'", input->str); - goto out_emit; - case JSON_END_OF_INPUT: - if (g_queue_is_empty(&parser->tokens)) { - return; - } - json = json_parser_parse(&parser->tokens, parser->ap, &err); - goto out_emit; + end_error_recovery: + /* + * We come here due to receiving either JSON_ERROR or a + * JSON_R{CURLY,SQUARE}) that is known to be unbalanced. + * If in error recovery, end it immediately. If not in + * error recovery, json_parser_feed() will raise an error + * but error recovery won't be entered at all. + */ + parser->brace_count = 0; + parser->bracket_count = 0; + break; default: break; } - /* - * Security consideration, we limit total memory allocated per object - * and the maximum recursion depth that a message can force. - */ - if (parser->token_size + input->len + 1 > MAX_TOKEN_SIZE) { - error_setg(&err, "JSON token size limit exceeded"); - goto out_emit; - } - if (g_queue_get_length(&parser->tokens) + 1 > MAX_TOKEN_COUNT) { - error_setg(&err, "JSON token count limit exceeded"); - goto out_emit; - } - if (parser->bracket_count + parser->brace_count > MAX_NESTING) { - error_setg(&err, "JSON nesting depth limit exceeded"); - goto out_emit; - } - - token = json_token(type, x, y, input); - parser->token_size += input->len; - - g_queue_push_tail(&parser->tokens, token); + if (parser->error) { + /* error recovery, eat tokens until parentheses balance */ + } else { + /* + * Safety consideration, we limit total memory allocated per object + * and the maximum nesting depth that a message can force. + */ + if (parser->token_size >= MAX_TOKEN_SIZE) { + error_setg(&err, "JSON token size limit exceeded"); + } else if (parser->token_count > MAX_TOKEN_COUNT) { + error_setg(&err, "JSON token count limit exceeded"); + } else if (parser->bracket_count + parser->brace_count > MAX_NESTING) { + error_setg(&err, "JSON nesting depth limit exceeded"); + } else { + JSONToken token = (JSONToken) { + .type = type, + .x = x, + .y = y, + .str = input->str + }; + QObject *json = json_parser_feed(&parser->parser, &token, &err); + if (json) { + parser->emit(parser->opaque, json, NULL); + } + } - if ((parser->brace_count > 0 || parser->bracket_count > 0) - && parser->brace_count >= 0 && parser->bracket_count >= 0) { - return; + if (err) { + parser->emit(parser->opaque, NULL, err); + /* start recovery */ + parser->error = true; + } } - json = json_parser_parse(&parser->tokens, parser->ap, &err); - -out_emit: - parser->brace_count = 0; - parser->bracket_count = 0; - json_message_free_tokens(parser); - parser->token_size = 0; - parser->emit(parser->opaque, json, err); + if ((parser->brace_count == 0 && parser->bracket_count == 0) + || type == JSON_END_OF_INPUT) { + json_parser_reset(&parser->parser); + parser->error = false; + parser->brace_count = 0; + parser->bracket_count = 0; + parser->token_count = 0; + parser->token_size = 0; + } } void json_message_parser_init(JSONMessageParser *parser, @@ -106,12 +115,13 @@ void json_message_parser_init(JSONMessageParser *parser, { parser->emit = emit; parser->opaque = opaque; - parser->ap = ap; + parser->error = false; parser->brace_count = 0; parser->bracket_count = 0; - g_queue_init(&parser->tokens); + parser->token_count = 0; parser->token_size = 0; + json_parser_init(&parser->parser, ap); json_lexer_init(&parser->lexer, !!ap); } @@ -124,11 +134,10 @@ void json_message_parser_feed(JSONMessageParser *parser, void json_message_parser_flush(JSONMessageParser *parser) { json_lexer_flush(&parser->lexer); - assert(g_queue_is_empty(&parser->tokens)); } void json_message_parser_destroy(JSONMessageParser *parser) { json_lexer_destroy(&parser->lexer); - json_message_free_tokens(parser); + json_parser_destroy(&parser->parser); } diff --git a/qobject/json-writer.c b/qobject/json-writer.c index aac2c6ab71..c23c81709b 100644 --- a/qobject/json-writer.c +++ b/qobject/json-writer.c @@ -24,13 +24,16 @@ struct JSONWriter { GByteArray *container_is_array; }; +/* Should cover most QMP responses without reallocation */ +#define JSON_WRITER_INITIAL_SIZE 4096 + JSONWriter *json_writer_new(bool pretty) { JSONWriter *writer = g_new(JSONWriter, 1); writer->pretty = pretty; writer->need_comma = false; - writer->contents = g_string_new(NULL); + writer->contents = g_string_sized_new(JSON_WRITER_INITIAL_SIZE); writer->container_is_array = g_byte_array_new(); return writer; } |
