QObject patches for 2018-08-24
-----BEGIN PGP SIGNATURE----- iQIcBAABAgAGBQJbgFx1AAoJEDhwtADrkYZTaQQP/24SBzfCVDC4GR4zM2aNYca8 en8UkIcF/MvgJ5E7b95LvT58g3qvd32G5nG5r8stbSzk1JlWQfH30O1zV+5J2FBY kkcJe69oTP+Qe8ZBndQCdxM8sMdlbZBpAKa81j6pYZXwueWvGd9PDxhYMiHuvglz EdXE2DsAZ8at7mwNlwC0E6TSYeJiHBmwjOI6YnuE9ZCP4Cr5JYIJojl2loHhJRsd 7gZdL+6GGm/NPHeuLHdt7XyNEfS7ZJgPn+lV9wljukQbAXjbkOf5ko3VCZwclyOg JkzOWot04Fy+Ro0Zj2e2siU+0MJ3JxfCrx5TKRZU5hKimZj6Uo7oA5qkGtCBXG6J Vq1Zl4MBKLkfckv7Spxs6j7+xImQXV5PD0nO63KFkqqbhZwWeq2M5GUorSOddh27 pecChH2fH/y32StStHzM7m2PvRuCIGq1ZfTdG7OdG/qRkwOQG9R9mkAO3hZNq54O GxoBs9ghjbttTZCCPm/qofc9EypVD7brjCwDwKWKm4Bf9daqVDFdAZic6n12HLKV ysAl2N8d5cCtQyFN6stKNXIZArLuT/MNPps6LC6hRawZaODsDZGPhjI3KcLcHnQs Vp9AWAB8vOzyWE0kvIdh004bPwXzH9r4IqTTZmvf1C15TTtZrpQ1r7BudDWKm3De wjTE5H4ETy0h/TuHE6yk =6BNc -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/armbru/tags/pull-qobject-2018-08-24' into staging QObject patches for 2018-08-24 # gpg: Signature made Fri 24 Aug 2018 20:28:53 BST # gpg: using RSA key 3870B400EB918653 # gpg: Good signature from "Markus Armbruster <armbru@redhat.com>" # gpg: aka "Markus Armbruster <armbru@pond.sub.org>" # Primary key fingerprint: 354B C8B3 D7EB 2A6B 6867 4E5F 3870 B400 EB91 8653 * remotes/armbru/tags/pull-qobject-2018-08-24: (58 commits) json: Update references to RFC 7159 to RFC 8259 json: Support %% in JSON strings when interpolating json: Improve safety of qobject_from_jsonf_nofail() & friends json: Keep interpolation state in JSONParserContext tests/drive_del-test: Fix harmless JSON interpolation bug json: Clean up headers qobject: Drop superfluous includes of qemu-common.h json: Make JSONToken opaque outside json-parser.c json: Unbox tokens queue in JSONMessageParser json: Streamline json_message_process_token() json: Enforce token count and size limits more tightly qjson: Have qobject_from_json() & friends reject empty and blank json: Assert json_parser_parse() consumes all tokens on success json: Fix streamer not to ignore trailing unterminated structures json: Fix latent parser aborts at end of input qjson: Fix qobject_from_json() & friends for multiple values json: Improve names of lexer states related to numbers json: Replace %I64d, %I64u by %PRId64, %PRIu64 json: Leave rejecting invalid interpolation to parser json: Pass lexical errors and limit violations to callback ... Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
cc9821fa9a
@ -1715,6 +1715,7 @@ F: monitor.c
|
||||
F: docs/devel/*qmp-*
|
||||
F: scripts/qmp/
|
||||
F: tests/qmp-test.c
|
||||
F: tests/qmp-cmd-test.c
|
||||
T: git git://repo.or.cz/qemu/armbru.git qapi-next
|
||||
|
||||
qtest
|
||||
|
5
block.c
5
block.c
@ -1478,11 +1478,6 @@ static QDict *parse_json_filename(const char *filename, Error **errp)
|
||||
|
||||
options_obj = qobject_from_json(filename, errp);
|
||||
if (!options_obj) {
|
||||
/* Work around qobject_from_json() lossage TODO fix that */
|
||||
if (errp && !*errp) {
|
||||
error_setg(errp, "Could not parse the JSON options");
|
||||
return NULL;
|
||||
}
|
||||
error_prepend(errp, "Could not parse the JSON options: ");
|
||||
return NULL;
|
||||
}
|
||||
|
@ -20,9 +20,9 @@ operating system.
|
||||
2. Protocol Specification
|
||||
=========================
|
||||
|
||||
This section details the protocol format. For the purpose of this document
|
||||
"Client" is any application which is using QMP to communicate with QEMU and
|
||||
"Server" is QEMU itself.
|
||||
This section details the protocol format. For the purpose of this
|
||||
document, "Server" is either QEMU or the QEMU Guest Agent, and
|
||||
"Client" is any application communicating with it via QMP.
|
||||
|
||||
JSON data structures, when mentioned in this document, are always in the
|
||||
following format:
|
||||
@ -34,9 +34,8 @@ by the JSON standard:
|
||||
|
||||
http://www.ietf.org/rfc/rfc7159.txt
|
||||
|
||||
The protocol is always encoded in UTF-8 except for synchronization
|
||||
bytes (documented below); although thanks to json-string escape
|
||||
sequences, the server will reply using only the strict ASCII subset.
|
||||
The server expects its input to be encoded in UTF-8, and sends its
|
||||
output encoded in ASCII.
|
||||
|
||||
For convenience, json-object members mentioned in this document will
|
||||
be in a certain order. However, in real protocol usage they can be in
|
||||
@ -215,16 +214,31 @@ Some events are rate-limited to at most one per second. If additional
|
||||
dropped, and the last one is delayed. "Similar" normally means same
|
||||
event type. See qmp-events.txt for details.
|
||||
|
||||
2.6 QGA Synchronization
|
||||
2.6 Forcing the JSON parser into known-good state
|
||||
-------------------------------------------------
|
||||
|
||||
Incomplete or invalid input can leave the server's JSON parser in a
|
||||
state where it can't parse additional commands. To get it back into
|
||||
known-good state, the client should provoke a lexical error.
|
||||
|
||||
The cleanest way to do that is sending an ASCII control character
|
||||
other than '\t' (horizontal tab), '\r' (carriage return), or '\n' (new
|
||||
line).
|
||||
|
||||
Sadly, older versions of QEMU can fail to flag this as an error. If a
|
||||
client needs to deal with them, it should send a 0xFF byte.
|
||||
|
||||
2.7 QGA Synchronization
|
||||
-----------------------
|
||||
|
||||
When using QGA, an additional synchronization feature is built into
|
||||
the protocol. If the Client sends a raw 0xFF sentinel byte (not valid
|
||||
JSON), then the Server will reset its state and discard all pending
|
||||
data prior to the sentinel. Conversely, if the Client makes use of
|
||||
the 'guest-sync-delimited' command, the Server will send a raw 0xFF
|
||||
sentinel byte prior to its response, to aid the Client in discarding
|
||||
any data prior to the sentinel.
|
||||
When a client connects to QGA over a transport lacking proper
|
||||
connection semantics such as virtio-serial, QGA may have read partial
|
||||
input from a previous client. The client needs to force QGA's parser
|
||||
into known-good state using the previous section's technique.
|
||||
Moreover, the client may receive output a previous client didn't read.
|
||||
To help with skipping that output, QGA provides the
|
||||
'guest-sync-delimited' command. Refer to its documentation for
|
||||
details.
|
||||
|
||||
|
||||
3. QMP Examples
|
||||
|
@ -1,56 +0,0 @@
|
||||
/*
|
||||
* JSON lexer
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef QEMU_JSON_LEXER_H
|
||||
#define QEMU_JSON_LEXER_H
|
||||
|
||||
|
||||
typedef enum json_token_type {
|
||||
JSON_MIN = 100,
|
||||
JSON_LCURLY = JSON_MIN,
|
||||
JSON_RCURLY,
|
||||
JSON_LSQUARE,
|
||||
JSON_RSQUARE,
|
||||
JSON_COLON,
|
||||
JSON_COMMA,
|
||||
JSON_INTEGER,
|
||||
JSON_FLOAT,
|
||||
JSON_KEYWORD,
|
||||
JSON_STRING,
|
||||
JSON_ESCAPE,
|
||||
JSON_SKIP,
|
||||
JSON_ERROR,
|
||||
} JSONTokenType;
|
||||
|
||||
typedef struct JSONLexer JSONLexer;
|
||||
|
||||
typedef void (JSONLexerEmitter)(JSONLexer *, GString *,
|
||||
JSONTokenType, int x, int y);
|
||||
|
||||
struct JSONLexer
|
||||
{
|
||||
JSONLexerEmitter *emit;
|
||||
int state;
|
||||
GString *token;
|
||||
int x, y;
|
||||
};
|
||||
|
||||
void json_lexer_init(JSONLexer *lexer, JSONLexerEmitter func);
|
||||
|
||||
int json_lexer_feed(JSONLexer *lexer, const char *buffer, size_t size);
|
||||
|
||||
int json_lexer_flush(JSONLexer *lexer);
|
||||
|
||||
void json_lexer_destroy(JSONLexer *lexer);
|
||||
|
||||
#endif
|
@ -11,12 +11,36 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef QEMU_JSON_PARSER_H
|
||||
#define QEMU_JSON_PARSER_H
|
||||
#ifndef QAPI_QMP_JSON_PARSER_H
|
||||
#define QAPI_QMP_JSON_PARSER_H
|
||||
|
||||
#include "qemu-common.h"
|
||||
typedef struct JSONLexer {
|
||||
int start_state, state;
|
||||
GString *token;
|
||||
int x, y;
|
||||
} JSONLexer;
|
||||
|
||||
QObject *json_parser_parse(GQueue *tokens, va_list *ap);
|
||||
QObject *json_parser_parse_err(GQueue *tokens, va_list *ap, Error **errp);
|
||||
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;
|
||||
uint64_t token_size;
|
||||
} JSONMessageParser;
|
||||
|
||||
void json_message_parser_init(JSONMessageParser *parser,
|
||||
void (*emit)(void *opaque, QObject *json,
|
||||
Error *err),
|
||||
void *opaque, va_list *ap);
|
||||
|
||||
void json_message_parser_feed(JSONMessageParser *parser,
|
||||
const char *buffer, size_t size);
|
||||
|
||||
void json_message_parser_flush(JSONMessageParser *parser);
|
||||
|
||||
void json_message_parser_destroy(JSONMessageParser *parser);
|
||||
|
||||
#endif
|
||||
|
@ -1,46 +0,0 @@
|
||||
/*
|
||||
* JSON streaming support
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef QEMU_JSON_STREAMER_H
|
||||
#define QEMU_JSON_STREAMER_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, GQueue *tokens);
|
||||
JSONLexer lexer;
|
||||
int brace_count;
|
||||
int bracket_count;
|
||||
GQueue *tokens;
|
||||
uint64_t token_size;
|
||||
} JSONMessageParser;
|
||||
|
||||
void json_message_parser_init(JSONMessageParser *parser,
|
||||
void (*func)(JSONMessageParser *, GQueue *));
|
||||
|
||||
int json_message_parser_feed(JSONMessageParser *parser,
|
||||
const char *buffer, size_t size);
|
||||
|
||||
int json_message_parser_flush(JSONMessageParser *parser);
|
||||
|
||||
void json_message_parser_destroy(JSONMessageParser *parser);
|
||||
|
||||
#endif
|
@ -61,9 +61,6 @@
|
||||
#define QERR_IO_ERROR \
|
||||
"An IO error has occurred"
|
||||
|
||||
#define QERR_JSON_PARSING \
|
||||
"Invalid JSON syntax"
|
||||
|
||||
#define QERR_MIGRATION_ACTIVE \
|
||||
"There's a migration process in progress"
|
||||
|
||||
|
@ -25,7 +25,7 @@ typedef enum {
|
||||
|
||||
/*
|
||||
* QNum encapsulates how our dialect of JSON fills in the blanks left
|
||||
* by the JSON specification (RFC 7159) regarding numbers.
|
||||
* by the JSON specification (RFC 8259) regarding numbers.
|
||||
*
|
||||
* Conceptually, we treat number as an abstract type with three
|
||||
* concrete subtypes: floating-point, signed integer, unsigned
|
||||
|
@ -2,5 +2,6 @@
|
||||
#define QEMU_UNICODE_H
|
||||
|
||||
int mod_utf8_codepoint(const char *s, size_t n, char **end);
|
||||
ssize_t mod_utf8_encode(char buf[], size_t bufsz, int codepoint);
|
||||
|
||||
#endif
|
||||
|
21
monitor.c
21
monitor.c
@ -58,7 +58,6 @@
|
||||
#include "qapi/qmp/qnum.h"
|
||||
#include "qapi/qmp/qstring.h"
|
||||
#include "qapi/qmp/qjson.h"
|
||||
#include "qapi/qmp/json-streamer.h"
|
||||
#include "qapi/qmp/json-parser.h"
|
||||
#include "qapi/qmp/qlist.h"
|
||||
#include "qom/object_interfaces.h"
|
||||
@ -4256,20 +4255,14 @@ static void monitor_qmp_bh_dispatcher(void *data)
|
||||
|
||||
#define QMP_REQ_QUEUE_LEN_MAX (8)
|
||||
|
||||
static void handle_qmp_command(JSONMessageParser *parser, GQueue *tokens)
|
||||
static void handle_qmp_command(void *opaque, QObject *req, Error *err)
|
||||
{
|
||||
QObject *req, *id = NULL;
|
||||
Monitor *mon = opaque;
|
||||
QObject *id = NULL;
|
||||
QDict *qdict;
|
||||
MonitorQMP *mon_qmp = container_of(parser, MonitorQMP, parser);
|
||||
Monitor *mon = container_of(mon_qmp, Monitor, qmp);
|
||||
Error *err = NULL;
|
||||
QMPRequest *req_obj;
|
||||
|
||||
req = json_parser_parse_err(tokens, NULL, &err);
|
||||
if (!req && !err) {
|
||||
/* json_parser_parse_err() sucks: can fail without setting @err */
|
||||
error_setg(&err, QERR_JSON_PARSING);
|
||||
}
|
||||
assert(!req != !err);
|
||||
|
||||
qdict = qobject_to(QDict, req);
|
||||
if (qdict) {
|
||||
@ -4465,7 +4458,8 @@ static void monitor_qmp_event(void *opaque, int event)
|
||||
monitor_qmp_response_flush(mon);
|
||||
monitor_qmp_cleanup_queues(mon);
|
||||
json_message_parser_destroy(&mon->qmp.parser);
|
||||
json_message_parser_init(&mon->qmp.parser, handle_qmp_command);
|
||||
json_message_parser_init(&mon->qmp.parser, handle_qmp_command,
|
||||
mon, NULL);
|
||||
mon_refcount--;
|
||||
monitor_fdsets_cleanup();
|
||||
break;
|
||||
@ -4683,7 +4677,8 @@ void monitor_init(Chardev *chr, int flags)
|
||||
|
||||
if (monitor_is_qmp(mon)) {
|
||||
qemu_chr_fe_set_echo(&mon->chr, true);
|
||||
json_message_parser_init(&mon->qmp.parser, handle_qmp_command);
|
||||
json_message_parser_init(&mon->qmp.parser, handle_qmp_command,
|
||||
mon, NULL);
|
||||
if (mon->use_io_thread) {
|
||||
/*
|
||||
* Make sure the old iowatch is gone. It's possible when
|
||||
|
@ -120,7 +120,7 @@
|
||||
##
|
||||
# @JSONType:
|
||||
#
|
||||
# The four primitive and two structured types according to RFC 7159
|
||||
# The four primitive and two structured types according to RFC 8259
|
||||
# section 1, plus 'int' (split off 'number'), plus the obvious top
|
||||
# type 'value'.
|
||||
#
|
||||
|
@ -14,7 +14,6 @@
|
||||
#include "qemu/osdep.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qapi/qmp/dispatch.h"
|
||||
#include "qapi/qmp/json-parser.h"
|
||||
#include "qapi/qmp/qdict.h"
|
||||
#include "qapi/qmp/qjson.h"
|
||||
#include "qapi/qmp/qbool.h"
|
||||
|
@ -725,11 +725,6 @@ Visitor *qobject_input_visitor_new_str(const char *str,
|
||||
if (is_json) {
|
||||
obj = qobject_from_json(str, errp);
|
||||
if (!obj) {
|
||||
/* Work around qobject_from_json() lossage TODO fix that */
|
||||
if (errp && !*errp) {
|
||||
error_setg(errp, "JSON parse error");
|
||||
return NULL;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
args = qobject_to(QDict, obj);
|
||||
|
15
qga/main.c
15
qga/main.c
@ -18,7 +18,6 @@
|
||||
#include <syslog.h>
|
||||
#include <sys/wait.h>
|
||||
#endif
|
||||
#include "qapi/qmp/json-streamer.h"
|
||||
#include "qapi/qmp/json-parser.h"
|
||||
#include "qapi/qmp/qdict.h"
|
||||
#include "qapi/qmp/qjson.h"
|
||||
@ -597,24 +596,20 @@ static void process_command(GAState *s, QDict *req)
|
||||
}
|
||||
|
||||
/* handle requests/control events coming in over the channel */
|
||||
static void process_event(JSONMessageParser *parser, GQueue *tokens)
|
||||
static void process_event(void *opaque, QObject *obj, Error *err)
|
||||
{
|
||||
GAState *s = container_of(parser, GAState, parser);
|
||||
QObject *obj;
|
||||
GAState *s = opaque;
|
||||
QDict *req, *rsp;
|
||||
Error *err = NULL;
|
||||
int ret;
|
||||
|
||||
g_assert(s && parser);
|
||||
|
||||
g_debug("process_event: called");
|
||||
obj = json_parser_parse_err(tokens, NULL, &err);
|
||||
assert(!obj != !err);
|
||||
if (err) {
|
||||
goto err;
|
||||
}
|
||||
req = qobject_to(QDict, obj);
|
||||
if (!req) {
|
||||
error_setg(&err, QERR_JSON_PARSING);
|
||||
error_setg(&err, "Input must be a JSON object");
|
||||
goto err;
|
||||
}
|
||||
if (!qdict_haskey(req, "execute")) {
|
||||
@ -1320,7 +1315,7 @@ static int run_agent(GAState *s, GAConfig *config, int socket_activation)
|
||||
s->command_state = ga_command_state_new();
|
||||
ga_command_state_init(s, s->command_state);
|
||||
ga_command_state_init_all(s->command_state);
|
||||
json_message_parser_init(&s->parser, process_event);
|
||||
json_message_parser_init(&s->parser, process_event, s, NULL);
|
||||
|
||||
#ifndef _WIN32
|
||||
if (!register_signal_handlers()) {
|
||||
|
@ -12,63 +12,116 @@
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu-common.h"
|
||||
#include "qapi/qmp/json-lexer.h"
|
||||
#include "json-parser-int.h"
|
||||
|
||||
#define MAX_TOKEN_SIZE (64ULL << 20)
|
||||
|
||||
/*
|
||||
* Required by JSON (RFC 7159):
|
||||
* From RFC 8259 "The JavaScript Object Notation (JSON) Data
|
||||
* Interchange Format", with [comments in brackets]:
|
||||
*
|
||||
* \"([^\\\"]|\\[\"'\\/bfnrt]|\\u[0-9a-fA-F]{4})*\"
|
||||
* -?(0|[1-9][0-9]*)(.[0-9]+)?([eE][-+]?[0-9]+)?
|
||||
* [{}\[\],:]
|
||||
* [a-z]+ # covers null, true, false
|
||||
* The set of tokens includes six structural characters, strings,
|
||||
* numbers, and three literal names.
|
||||
*
|
||||
* Extension of '' strings:
|
||||
* These are the six structural characters:
|
||||
*
|
||||
* '([^\\']|\\[\"'\\/bfnrt]|\\u[0-9a-fA-F]{4})*'
|
||||
* begin-array = ws %x5B ws ; [ left square bracket
|
||||
* begin-object = ws %x7B ws ; { left curly bracket
|
||||
* end-array = ws %x5D ws ; ] right square bracket
|
||||
* end-object = ws %x7D ws ; } right curly bracket
|
||||
* name-separator = ws %x3A ws ; : colon
|
||||
* value-separator = ws %x2C ws ; , comma
|
||||
*
|
||||
* Extension for vararg handling in JSON construction:
|
||||
* Insignificant whitespace is allowed before or after any of the six
|
||||
* structural characters.
|
||||
* [This lexer accepts it before or after any token, which is actually
|
||||
* the same, as the grammar always has structural characters between
|
||||
* other tokens.]
|
||||
*
|
||||
* %((l|ll|I64)?d|[ipsf])
|
||||
* ws = *(
|
||||
* %x20 / ; Space
|
||||
* %x09 / ; Horizontal tab
|
||||
* %x0A / ; Line feed or New line
|
||||
* %x0D ) ; Carriage return
|
||||
*
|
||||
* [...] three literal names:
|
||||
* false null true
|
||||
* [This lexer accepts [a-z]+, and leaves rejecting unknown literal
|
||||
* names to the parser.]
|
||||
*
|
||||
* [Numbers:]
|
||||
*
|
||||
* number = [ minus ] int [ frac ] [ exp ]
|
||||
* decimal-point = %x2E ; .
|
||||
* digit1-9 = %x31-39 ; 1-9
|
||||
* e = %x65 / %x45 ; e E
|
||||
* exp = e [ minus / plus ] 1*DIGIT
|
||||
* frac = decimal-point 1*DIGIT
|
||||
* int = zero / ( digit1-9 *DIGIT )
|
||||
* minus = %x2D ; -
|
||||
* plus = %x2B ; +
|
||||
* zero = %x30 ; 0
|
||||
*
|
||||
* [Strings:]
|
||||
* string = quotation-mark *char quotation-mark
|
||||
*
|
||||
* char = unescaped /
|
||||
* escape (
|
||||
* %x22 / ; " quotation mark U+0022
|
||||
* %x5C / ; \ reverse solidus U+005C
|
||||
* %x2F / ; / solidus U+002F
|
||||
* %x62 / ; b backspace U+0008
|
||||
* %x66 / ; f form feed U+000C
|
||||
* %x6E / ; n line feed U+000A
|
||||
* %x72 / ; r carriage return U+000D
|
||||
* %x74 / ; t tab U+0009
|
||||
* %x75 4HEXDIG ) ; uXXXX U+XXXX
|
||||
* escape = %x5C ; \
|
||||
* quotation-mark = %x22 ; "
|
||||
* unescaped = %x20-21 / %x23-5B / %x5D-10FFFF
|
||||
* [This lexer accepts any non-control character after escape, and
|
||||
* leaves rejecting invalid ones to the parser.]
|
||||
*
|
||||
*
|
||||
* Extensions over RFC 8259:
|
||||
* - Extra escape sequence in strings:
|
||||
* 0x27 (apostrophe) is recognized after escape, too
|
||||
* - Single-quoted strings:
|
||||
* Like double-quoted strings, except they're delimited by %x27
|
||||
* (apostrophe) instead of %x22 (quotation mark), and can't contain
|
||||
* unescaped apostrophe, but can contain unescaped quotation mark.
|
||||
* - Interpolation, if enabled:
|
||||
* The lexer accepts %[A-Za-z0-9]*, and leaves rejecting invalid
|
||||
* ones to the parser.
|
||||
*
|
||||
* Note:
|
||||
* - Input must be encoded in modified UTF-8.
|
||||
* - Decoding and validating is left to the parser.
|
||||
*/
|
||||
|
||||
enum json_lexer_state {
|
||||
IN_ERROR = 0, /* must really be 0, see json_lexer[] */
|
||||
IN_DQ_UCODE3,
|
||||
IN_DQ_UCODE2,
|
||||
IN_DQ_UCODE1,
|
||||
IN_DQ_UCODE0,
|
||||
IN_DQ_STRING_ESCAPE,
|
||||
IN_DQ_STRING,
|
||||
IN_SQ_UCODE3,
|
||||
IN_SQ_UCODE2,
|
||||
IN_SQ_UCODE1,
|
||||
IN_SQ_UCODE0,
|
||||
IN_SQ_STRING_ESCAPE,
|
||||
IN_SQ_STRING,
|
||||
IN_ZERO,
|
||||
IN_DIGITS,
|
||||
IN_DIGIT,
|
||||
IN_EXP_DIGITS,
|
||||
IN_EXP_SIGN,
|
||||
IN_EXP_E,
|
||||
IN_MANTISSA,
|
||||
IN_MANTISSA_DIGITS,
|
||||
IN_NONZERO_NUMBER,
|
||||
IN_NEG_NONZERO_NUMBER,
|
||||
IN_DIGITS,
|
||||
IN_SIGN,
|
||||
IN_KEYWORD,
|
||||
IN_ESCAPE,
|
||||
IN_ESCAPE_L,
|
||||
IN_ESCAPE_LL,
|
||||
IN_ESCAPE_I,
|
||||
IN_ESCAPE_I6,
|
||||
IN_ESCAPE_I64,
|
||||
IN_INTERP,
|
||||
IN_WHITESPACE,
|
||||
IN_START,
|
||||
IN_START_INTERP, /* must be IN_START + 1 */
|
||||
};
|
||||
|
||||
QEMU_BUILD_BUG_ON((int)JSON_MIN <= (int)IN_START);
|
||||
QEMU_BUILD_BUG_ON((int)JSON_MIN <= (int)IN_START_INTERP);
|
||||
QEMU_BUILD_BUG_ON(IN_START_INTERP != IN_START + 1);
|
||||
|
||||
#define TERMINAL(state) [0 ... 0x7F] = (state)
|
||||
|
||||
@ -76,87 +129,27 @@ QEMU_BUILD_BUG_ON((int)JSON_MIN <= (int)IN_START);
|
||||
from OLD_STATE required lookahead. This happens whenever the table
|
||||
below uses the TERMINAL macro. */
|
||||
#define TERMINAL_NEEDED_LOOKAHEAD(old_state, terminal) \
|
||||
(json_lexer[(old_state)][0] == (terminal))
|
||||
(terminal != IN_ERROR && 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,
|
||||
['a' ... 'f'] = IN_DQ_STRING,
|
||||
['A' ... 'F'] = IN_DQ_STRING,
|
||||
},
|
||||
[IN_DQ_UCODE2] = {
|
||||
['0' ... '9'] = IN_DQ_UCODE3,
|
||||
['a' ... 'f'] = IN_DQ_UCODE3,
|
||||
['A' ... 'F'] = IN_DQ_UCODE3,
|
||||
},
|
||||
[IN_DQ_UCODE1] = {
|
||||
['0' ... '9'] = IN_DQ_UCODE2,
|
||||
['a' ... 'f'] = IN_DQ_UCODE2,
|
||||
['A' ... 'F'] = IN_DQ_UCODE2,
|
||||
},
|
||||
[IN_DQ_UCODE0] = {
|
||||
['0' ... '9'] = IN_DQ_UCODE1,
|
||||
['a' ... 'f'] = IN_DQ_UCODE1,
|
||||
['A' ... 'F'] = IN_DQ_UCODE1,
|
||||
},
|
||||
[IN_DQ_STRING_ESCAPE] = {
|
||||
['b'] = IN_DQ_STRING,
|
||||
['f'] = IN_DQ_STRING,
|
||||
['n'] = IN_DQ_STRING,
|
||||
['r'] = IN_DQ_STRING,
|
||||
['t'] = IN_DQ_STRING,
|
||||
['/'] = IN_DQ_STRING,
|
||||
['\\'] = IN_DQ_STRING,
|
||||
['\''] = IN_DQ_STRING,
|
||||
['\"'] = IN_DQ_STRING,
|
||||
['u'] = IN_DQ_UCODE0,
|
||||
[0x20 ... 0xFD] = IN_DQ_STRING,
|
||||
},
|
||||
[IN_DQ_STRING] = {
|
||||
[1 ... 0xBF] = IN_DQ_STRING,
|
||||
[0xC2 ... 0xF4] = IN_DQ_STRING,
|
||||
[0x20 ... 0xFD] = IN_DQ_STRING,
|
||||
['\\'] = IN_DQ_STRING_ESCAPE,
|
||||
['"'] = JSON_STRING,
|
||||
},
|
||||
|
||||
/* single quote string */
|
||||
[IN_SQ_UCODE3] = {
|
||||
['0' ... '9'] = IN_SQ_STRING,
|
||||
['a' ... 'f'] = IN_SQ_STRING,
|
||||
['A' ... 'F'] = IN_SQ_STRING,
|
||||
},
|
||||
[IN_SQ_UCODE2] = {
|
||||
['0' ... '9'] = IN_SQ_UCODE3,
|
||||
['a' ... 'f'] = IN_SQ_UCODE3,
|
||||
['A' ... 'F'] = IN_SQ_UCODE3,
|
||||
},
|
||||
[IN_SQ_UCODE1] = {
|
||||
['0' ... '9'] = IN_SQ_UCODE2,
|
||||
['a' ... 'f'] = IN_SQ_UCODE2,
|
||||
['A' ... 'F'] = IN_SQ_UCODE2,
|
||||
},
|
||||
[IN_SQ_UCODE0] = {
|
||||
['0' ... '9'] = IN_SQ_UCODE1,
|
||||
['a' ... 'f'] = IN_SQ_UCODE1,
|
||||
['A' ... 'F'] = IN_SQ_UCODE1,
|
||||
},
|
||||
[IN_SQ_STRING_ESCAPE] = {
|
||||
['b'] = IN_SQ_STRING,
|
||||
['f'] = IN_SQ_STRING,
|
||||
['n'] = IN_SQ_STRING,
|
||||
['r'] = IN_SQ_STRING,
|
||||
['t'] = IN_SQ_STRING,
|
||||
['/'] = IN_SQ_STRING,
|
||||
['\\'] = IN_SQ_STRING,
|
||||
['\''] = IN_SQ_STRING,
|
||||
['\"'] = IN_SQ_STRING,
|
||||
['u'] = IN_SQ_UCODE0,
|
||||
[0x20 ... 0xFD] = IN_SQ_STRING,
|
||||
},
|
||||
[IN_SQ_STRING] = {
|
||||
[1 ... 0xBF] = IN_SQ_STRING,
|
||||
[0xC2 ... 0xF4] = IN_SQ_STRING,
|
||||
[0x20 ... 0xFD] = IN_SQ_STRING,
|
||||
['\\'] = IN_SQ_STRING_ESCAPE,
|
||||
['\''] = JSON_STRING,
|
||||
},
|
||||
@ -169,19 +162,19 @@ static const uint8_t json_lexer[][256] = {
|
||||
},
|
||||
|
||||
/* Float */
|
||||
[IN_DIGITS] = {
|
||||
[IN_EXP_DIGITS] = {
|
||||
TERMINAL(JSON_FLOAT),
|
||||
['0' ... '9'] = IN_DIGITS,
|
||||
['0' ... '9'] = IN_EXP_DIGITS,
|
||||
},
|
||||
|
||||
[IN_DIGIT] = {
|
||||
['0' ... '9'] = IN_DIGITS,
|
||||
[IN_EXP_SIGN] = {
|
||||
['0' ... '9'] = IN_EXP_DIGITS,
|
||||
},
|
||||
|
||||
[IN_EXP_E] = {
|
||||
['-'] = IN_DIGIT,
|
||||
['+'] = IN_DIGIT,
|
||||
['0' ... '9'] = IN_DIGITS,
|
||||
['-'] = IN_EXP_SIGN,
|
||||
['+'] = IN_EXP_SIGN,
|
||||
['0' ... '9'] = IN_EXP_DIGITS,
|
||||
},
|
||||
|
||||
[IN_MANTISSA_DIGITS] = {
|
||||
@ -196,17 +189,17 @@ static const uint8_t json_lexer[][256] = {
|
||||
},
|
||||
|
||||
/* Number */
|
||||
[IN_NONZERO_NUMBER] = {
|
||||
[IN_DIGITS] = {
|
||||
TERMINAL(JSON_INTEGER),
|
||||
['0' ... '9'] = IN_NONZERO_NUMBER,
|
||||
['0' ... '9'] = IN_DIGITS,
|
||||
['e'] = IN_EXP_E,
|
||||
['E'] = IN_EXP_E,
|
||||
['.'] = IN_MANTISSA,
|
||||
},
|
||||
|
||||
[IN_NEG_NONZERO_NUMBER] = {
|
||||
[IN_SIGN] = {
|
||||
['0'] = IN_ZERO,
|
||||
['1' ... '9'] = IN_NONZERO_NUMBER,
|
||||
['1' ... '9'] = IN_DIGITS,
|
||||
},
|
||||
|
||||
/* keywords */
|
||||
@ -224,49 +217,25 @@ static const uint8_t json_lexer[][256] = {
|
||||
['\n'] = IN_WHITESPACE,
|
||||
},
|
||||
|
||||
/* escape */
|
||||
[IN_ESCAPE_LL] = {
|
||||
['d'] = JSON_ESCAPE,
|
||||
['u'] = JSON_ESCAPE,
|
||||
/* interpolation */
|
||||
[IN_INTERP] = {
|
||||
TERMINAL(JSON_INTERP),
|
||||
['A' ... 'Z'] = IN_INTERP,
|
||||
['a' ... 'z'] = IN_INTERP,
|
||||
['0' ... '9'] = IN_INTERP,
|
||||
},
|
||||
|
||||
[IN_ESCAPE_L] = {
|
||||
['d'] = JSON_ESCAPE,
|
||||
['l'] = IN_ESCAPE_LL,
|
||||
['u'] = JSON_ESCAPE,
|
||||
},
|
||||
|
||||
[IN_ESCAPE_I64] = {
|
||||
['d'] = JSON_ESCAPE,
|
||||
['u'] = JSON_ESCAPE,
|
||||
},
|
||||
|
||||
[IN_ESCAPE_I6] = {
|
||||
['4'] = IN_ESCAPE_I64,
|
||||
},
|
||||
|
||||
[IN_ESCAPE_I] = {
|
||||
['6'] = IN_ESCAPE_I6,
|
||||
},
|
||||
|
||||
[IN_ESCAPE] = {
|
||||
['d'] = JSON_ESCAPE,
|
||||
['i'] = JSON_ESCAPE,
|
||||
['p'] = JSON_ESCAPE,
|
||||
['s'] = JSON_ESCAPE,
|
||||
['u'] = JSON_ESCAPE,
|
||||
['f'] = JSON_ESCAPE,
|
||||
['l'] = IN_ESCAPE_L,
|
||||
['I'] = IN_ESCAPE_I,
|
||||
},
|
||||
|
||||
/* top level rule */
|
||||
[IN_START] = {
|
||||
/*
|
||||
* Two start states:
|
||||
* - IN_START recognizes JSON tokens with our string extensions
|
||||
* - IN_START_INTERP additionally recognizes interpolation.
|
||||
*/
|
||||
[IN_START ... IN_START_INTERP] = {
|
||||
['"'] = IN_DQ_STRING,
|
||||
['\''] = IN_SQ_STRING,
|
||||
['0'] = IN_ZERO,
|
||||
['1' ... '9'] = IN_NONZERO_NUMBER,
|
||||
['-'] = IN_NEG_NONZERO_NUMBER,
|
||||
['1' ... '9'] = IN_DIGITS,
|
||||
['-'] = IN_SIGN,
|
||||
['{'] = JSON_LCURLY,
|
||||
['}'] = JSON_RCURLY,
|
||||
['['] = JSON_LSQUARE,
|
||||
@ -274,23 +243,23 @@ static const uint8_t json_lexer[][256] = {
|
||||
[','] = JSON_COMMA,
|
||||
[':'] = JSON_COLON,
|
||||
['a' ... 'z'] = IN_KEYWORD,
|
||||
['%'] = IN_ESCAPE,
|
||||
[' '] = IN_WHITESPACE,
|
||||
['\t'] = IN_WHITESPACE,
|
||||
['\r'] = IN_WHITESPACE,
|
||||
['\n'] = IN_WHITESPACE,
|
||||
},
|
||||
[IN_START_INTERP]['%'] = IN_INTERP,
|
||||
};
|
||||
|
||||
void json_lexer_init(JSONLexer *lexer, JSONLexerEmitter func)
|
||||
void json_lexer_init(JSONLexer *lexer, bool enable_interpolation)
|
||||
{
|
||||
lexer->emit = func;
|
||||
lexer->state = IN_START;
|
||||
lexer->start_state = lexer->state = enable_interpolation
|
||||
? IN_START_INTERP : IN_START;
|
||||
lexer->token = g_string_sized_new(3);
|
||||
lexer->x = lexer->y = 0;
|
||||
}
|
||||
|
||||
static int json_lexer_feed_char(JSONLexer *lexer, char ch, bool flush)
|
||||
static void json_lexer_feed_char(JSONLexer *lexer, char ch, bool flush)
|
||||
{
|
||||
int char_consumed, new_state;
|
||||
|
||||
@ -304,7 +273,7 @@ static int json_lexer_feed_char(JSONLexer *lexer, char ch, bool flush)
|
||||
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) {
|
||||
if (char_consumed && !flush) {
|
||||
g_string_append_c(lexer->token, ch);
|
||||
}
|
||||
|
||||
@ -315,23 +284,23 @@ static int json_lexer_feed_char(JSONLexer *lexer, char ch, bool flush)
|
||||
case JSON_RSQUARE:
|
||||
case JSON_COLON:
|
||||
case JSON_COMMA:
|
||||
case JSON_ESCAPE:
|
||||
case JSON_INTERP:
|
||||
case JSON_INTEGER:
|
||||
case JSON_FLOAT:
|
||||
case JSON_KEYWORD:
|
||||
case JSON_STRING:
|
||||
lexer->emit(lexer, lexer->token, new_state, lexer->x, lexer->y);
|
||||
json_message_process_token(lexer, lexer->token, new_state,
|
||||
lexer->x, lexer->y);
|
||||
/* fall through */
|
||||
case JSON_SKIP:
|
||||
g_string_truncate(lexer->token, 0);
|
||||
new_state = IN_START;
|
||||
new_state = lexer->start_state;
|
||||
break;
|
||||
case IN_ERROR:
|
||||
/* XXX: To avoid having previous bad input leaving the parser in an
|
||||
* unresponsive state where we consume unpredictable amounts of
|
||||
* subsequent "good" input, percolate this error state up to the
|
||||
* tokenizer/parser by forcing a NULL object to be emitted, then
|
||||
* reset state.
|
||||
* parser by emitting a JSON_ERROR token, then reset lexer state.
|
||||
*
|
||||
* Also note that this handling is required for reliable channel
|
||||
* negotiation between QMP and the guest agent, since chr(0xFF)
|
||||
@ -340,11 +309,11 @@ static int json_lexer_feed_char(JSONLexer *lexer, char ch, bool flush)
|
||||
* never a valid ASCII/UTF-8 sequence, so this should reliably
|
||||
* induce an error/flush state.
|
||||
*/
|
||||
lexer->emit(lexer, lexer->token, JSON_ERROR, lexer->x, lexer->y);
|
||||
json_message_process_token(lexer, lexer->token, JSON_ERROR,
|
||||
lexer->x, lexer->y);
|
||||
g_string_truncate(lexer->token, 0);
|
||||
new_state = IN_START;
|
||||
lexer->state = new_state;
|
||||
return 0;
|
||||
lexer->state = lexer->start_state;
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -355,33 +324,29 @@ static int json_lexer_feed_char(JSONLexer *lexer, char ch, bool flush)
|
||||
* this is a security consideration.
|
||||
*/
|
||||
if (lexer->token->len > MAX_TOKEN_SIZE) {
|
||||
lexer->emit(lexer, lexer->token, lexer->state, lexer->x, lexer->y);
|
||||
json_message_process_token(lexer, lexer->token, lexer->state,
|
||||
lexer->x, lexer->y);
|
||||
g_string_truncate(lexer->token, 0);
|
||||
lexer->state = IN_START;
|
||||
lexer->state = lexer->start_state;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int json_lexer_feed(JSONLexer *lexer, const char *buffer, size_t size)
|
||||
void json_lexer_feed(JSONLexer *lexer, const char *buffer, size_t size)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < size; i++) {
|
||||
int err;
|
||||
|
||||
err = json_lexer_feed_char(lexer, buffer[i], false);
|
||||
if (err < 0) {
|
||||
return err;
|
||||
}
|
||||
json_lexer_feed_char(lexer, buffer[i], false);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int json_lexer_flush(JSONLexer *lexer)
|
||||
void json_lexer_flush(JSONLexer *lexer)
|
||||
{
|
||||
return lexer->state == IN_START ? 0 : json_lexer_feed_char(lexer, 0, true);
|
||||
if (lexer->state != lexer->start_state) {
|
||||
json_lexer_feed_char(lexer, 0, true);
|
||||
}
|
||||
json_message_process_token(lexer, lexer->token, JSON_END_OF_INPUT,
|
||||
lexer->x, lexer->y);
|
||||
}
|
||||
|
||||
void json_lexer_destroy(JSONLexer *lexer)
|
||||
|
54
qobject/json-parser-int.h
Normal file
54
qobject/json-parser-int.h
Normal file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* JSON Parser
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef JSON_PARSER_INT_H
|
||||
#define JSON_PARSER_INT_H
|
||||
|
||||
#include "qapi/qmp/json-parser.h"
|
||||
|
||||
|
||||
typedef enum json_token_type {
|
||||
JSON_MIN = 100,
|
||||
JSON_LCURLY = JSON_MIN,
|
||||
JSON_RCURLY,
|
||||
JSON_LSQUARE,
|
||||
JSON_RSQUARE,
|
||||
JSON_COLON,
|
||||
JSON_COMMA,
|
||||
JSON_INTEGER,
|
||||
JSON_FLOAT,
|
||||
JSON_KEYWORD,
|
||||
JSON_STRING,
|
||||
JSON_INTERP,
|
||||
JSON_SKIP,
|
||||
JSON_ERROR,
|
||||
JSON_END_OF_INPUT,
|
||||
} JSONTokenType;
|
||||
|
||||
typedef struct JSONToken JSONToken;
|
||||
|
||||
/* json-lexer.c */
|
||||
void json_lexer_init(JSONLexer *lexer, bool enable_interpolation);
|
||||
void json_lexer_feed(JSONLexer *lexer, const char *buffer, size_t size);
|
||||
void json_lexer_flush(JSONLexer *lexer);
|
||||
void json_lexer_destroy(JSONLexer *lexer);
|
||||
|
||||
/* json-streamer.c */
|
||||
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);
|
||||
|
||||
#endif
|
@ -13,6 +13,7 @@
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/cutils.h"
|
||||
#include "qemu/unicode.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qemu-common.h"
|
||||
#include "qapi/qmp/qbool.h"
|
||||
@ -21,15 +22,21 @@
|
||||
#include "qapi/qmp/qnull.h"
|
||||
#include "qapi/qmp/qnum.h"
|
||||
#include "qapi/qmp/qstring.h"
|
||||
#include "qapi/qmp/json-parser.h"
|
||||
#include "qapi/qmp/json-lexer.h"
|
||||
#include "qapi/qmp/json-streamer.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;
|
||||
|
||||
#define BUG_ON(cond) assert(!(cond))
|
||||
@ -43,7 +50,7 @@ typedef struct JSONParserContext
|
||||
* 4) deal with premature EOI
|
||||
*/
|
||||
|
||||
static QObject *parse_value(JSONParserContext *ctxt, va_list *ap);
|
||||
static QObject *parse_value(JSONParserContext *ctxt);
|
||||
|
||||
/**
|
||||
* Error handler
|
||||
@ -53,169 +60,170 @@ static void GCC_FMT_ATTR(3, 4) parse_error(JSONParserContext *ctxt,
|
||||
{
|
||||
va_list ap;
|
||||
char message[1024];
|
||||
|
||||
if (ctxt->err) {
|
||||
return;
|
||||
}
|
||||
va_start(ap, msg);
|
||||
vsnprintf(message, sizeof(message), msg, ap);
|
||||
va_end(ap);
|
||||
if (ctxt->err) {
|
||||
error_free(ctxt->err);
|
||||
ctxt->err = NULL;
|
||||
}
|
||||
error_setg(&ctxt->err, "JSON parse error, %s", message);
|
||||
}
|
||||
|
||||
/**
|
||||
* String helpers
|
||||
*
|
||||
* These helpers are used to unescape strings.
|
||||
*/
|
||||
static void wchar_to_utf8(uint16_t wchar, char *buffer, size_t buffer_length)
|
||||
static int cvt4hex(const char *s)
|
||||
{
|
||||
if (wchar <= 0x007F) {
|
||||
BUG_ON(buffer_length < 2);
|
||||
int cp, i;
|
||||
|
||||
buffer[0] = wchar & 0x7F;
|
||||
buffer[1] = 0;
|
||||
} else if (wchar <= 0x07FF) {
|
||||
BUG_ON(buffer_length < 3);
|
||||
|
||||
buffer[0] = 0xC0 | ((wchar >> 6) & 0x1F);
|
||||
buffer[1] = 0x80 | (wchar & 0x3F);
|
||||
buffer[2] = 0;
|
||||
} else {
|
||||
BUG_ON(buffer_length < 4);
|
||||
|
||||
buffer[0] = 0xE0 | ((wchar >> 12) & 0x0F);
|
||||
buffer[1] = 0x80 | ((wchar >> 6) & 0x3F);
|
||||
buffer[2] = 0x80 | (wchar & 0x3F);
|
||||
buffer[3] = 0;
|
||||
cp = 0;
|
||||
for (i = 0; i < 4; i++) {
|
||||
if (!qemu_isxdigit(s[i])) {
|
||||
return -1;
|
||||
}
|
||||
cp <<= 4;
|
||||
if (s[i] >= '0' && s[i] <= '9') {
|
||||
cp |= s[i] - '0';
|
||||
} else if (s[i] >= 'a' && s[i] <= 'f') {
|
||||
cp |= 10 + s[i] - 'a';
|
||||
} else if (s[i] >= 'A' && s[i] <= 'F') {
|
||||
cp |= 10 + s[i] - 'A';
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int hex2decimal(char ch)
|
||||
{
|
||||
if (ch >= '0' && ch <= '9') {
|
||||
return (ch - '0');
|
||||
} else if (ch >= 'a' && ch <= 'f') {
|
||||
return 10 + (ch - 'a');
|
||||
} else if (ch >= 'A' && ch <= 'F') {
|
||||
return 10 + (ch - 'A');
|
||||
}
|
||||
|
||||
return -1;
|
||||
return cp;
|
||||
}
|
||||
|
||||
/**
|
||||
* parse_string(): Parse a json string and return a QObject
|
||||
* parse_string(): Parse a JSON string
|
||||
*
|
||||
* string
|
||||
* ""
|
||||
* " chars "
|
||||
* chars
|
||||
* char
|
||||
* char chars
|
||||
* char
|
||||
* any-Unicode-character-
|
||||
* except-"-or-\-or-
|
||||
* control-character
|
||||
* \"
|
||||
* \\
|
||||
* \/
|
||||
* \b
|
||||
* \f
|
||||
* \n
|
||||
* \r
|
||||
* \t
|
||||
* \u four-hex-digits
|
||||
* From RFC 8259 "The JavaScript Object Notation (JSON) Data
|
||||
* Interchange Format":
|
||||
*
|
||||
* char = unescaped /
|
||||
* escape (
|
||||
* %x22 / ; " quotation mark U+0022
|
||||
* %x5C / ; \ reverse solidus U+005C
|
||||
* %x2F / ; / solidus U+002F
|
||||
* %x62 / ; b backspace U+0008
|
||||
* %x66 / ; f form feed U+000C
|
||||
* %x6E / ; n line feed U+000A
|
||||
* %x72 / ; r carriage return U+000D
|
||||
* %x74 / ; t tab U+0009
|
||||
* %x75 4HEXDIG ) ; uXXXX U+XXXX
|
||||
* escape = %x5C ; \
|
||||
* quotation-mark = %x22 ; "
|
||||
* unescaped = %x20-21 / %x23-5B / %x5D-10FFFF
|
||||
*
|
||||
* Extensions over RFC 8259:
|
||||
* - Extra escape sequence in strings:
|
||||
* 0x27 (apostrophe) is recognized after escape, too
|
||||
* - Single-quoted strings:
|
||||
* Like double-quoted strings, except they're delimited by %x27
|
||||
* (apostrophe) instead of %x22 (quotation mark), and can't contain
|
||||
* unescaped apostrophe, but can contain unescaped quotation mark.
|
||||
*
|
||||
* Note:
|
||||
* - Encoding is modified UTF-8.
|
||||
* - Invalid Unicode characters are rejected.
|
||||
* - Control characters \x00..\x1F are rejected by the lexer.
|
||||
*/
|
||||
static QString *qstring_from_escaped_str(JSONParserContext *ctxt,
|
||||
JSONToken *token)
|
||||
static QString *parse_string(JSONParserContext *ctxt, JSONToken *token)
|
||||
{
|
||||
const char *ptr = token->str;
|
||||
QString *str;
|
||||
int double_quote = 1;
|
||||
|
||||
if (*ptr == '"') {
|
||||
double_quote = 1;
|
||||
} else {
|
||||
double_quote = 0;
|
||||
}
|
||||
ptr++;
|
||||
char quote;
|
||||
const char *beg;
|
||||
int cp, trailing;
|
||||
char *end;
|
||||
ssize_t len;
|
||||
char utf8_buf[5];
|
||||
|
||||
assert(*ptr == '"' || *ptr == '\'');
|
||||
quote = *ptr++;
|
||||
str = qstring_new();
|
||||
while (*ptr &&
|
||||
((double_quote && *ptr != '"') || (!double_quote && *ptr != '\''))) {
|
||||
if (*ptr == '\\') {
|
||||
ptr++;
|
||||
|
||||
switch (*ptr) {
|
||||
while (*ptr != quote) {
|
||||
assert(*ptr);
|
||||
switch (*ptr) {
|
||||
case '\\':
|
||||
beg = ptr++;
|
||||
switch (*ptr++) {
|
||||
case '"':
|
||||
qstring_append(str, "\"");
|
||||
ptr++;
|
||||
qstring_append_chr(str, '"');
|
||||
break;
|
||||
case '\'':
|
||||
qstring_append(str, "'");
|
||||
ptr++;
|
||||
qstring_append_chr(str, '\'');
|
||||
break;
|
||||
case '\\':
|
||||
qstring_append(str, "\\");
|
||||
ptr++;
|
||||
qstring_append_chr(str, '\\');
|
||||
break;
|
||||
case '/':
|
||||
qstring_append(str, "/");
|
||||
ptr++;
|
||||
qstring_append_chr(str, '/');
|
||||
break;
|
||||
case 'b':
|
||||
qstring_append(str, "\b");
|
||||
ptr++;
|
||||
qstring_append_chr(str, '\b');
|
||||
break;
|
||||
case 'f':
|
||||
qstring_append(str, "\f");
|
||||
ptr++;
|
||||
qstring_append_chr(str, '\f');
|
||||
break;
|
||||
case 'n':
|
||||
qstring_append(str, "\n");
|
||||
ptr++;
|
||||
qstring_append_chr(str, '\n');
|
||||
break;
|
||||
case 'r':
|
||||
qstring_append(str, "\r");
|
||||
ptr++;
|
||||
qstring_append_chr(str, '\r');
|
||||
break;
|
||||
case 't':
|
||||
qstring_append(str, "\t");
|
||||
ptr++;
|
||||
qstring_append_chr(str, '\t');
|
||||
break;
|
||||
case 'u': {
|
||||
uint16_t unicode_char = 0;
|
||||
char utf8_char[4];
|
||||
int i = 0;
|
||||
case 'u':
|
||||
cp = cvt4hex(ptr);
|
||||
ptr += 4;
|
||||
|
||||
ptr++;
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
if (qemu_isxdigit(*ptr)) {
|
||||
unicode_char |= hex2decimal(*ptr) << ((3 - i) * 4);
|
||||
/* handle surrogate pairs */
|
||||
if (cp >= 0xD800 && cp <= 0xDBFF
|
||||
&& ptr[0] == '\\' && ptr[1] == 'u') {
|
||||
/* leading surrogate followed by \u */
|
||||
cp = 0x10000 + ((cp & 0x3FF) << 10);
|
||||
trailing = cvt4hex(ptr + 2);
|
||||
if (trailing >= 0xDC00 && trailing <= 0xDFFF) {
|
||||
/* followed by trailing surrogate */
|
||||
cp |= trailing & 0x3FF;
|
||||
ptr += 6;
|
||||
} else {
|
||||
parse_error(ctxt, token,
|
||||
"invalid hex escape sequence in string");
|
||||
goto out;
|
||||
cp = -1; /* invalid */
|
||||
}
|
||||
ptr++;
|
||||
}
|
||||
|
||||
wchar_to_utf8(unicode_char, utf8_char, sizeof(utf8_char));
|
||||
qstring_append(str, utf8_char);
|
||||
} break;
|
||||
if (mod_utf8_encode(utf8_buf, sizeof(utf8_buf), cp) < 0) {
|
||||
parse_error(ctxt, token,
|
||||
"%.*s is not a valid Unicode character",
|
||||
(int)(ptr - beg), beg);
|
||||
goto out;
|
||||
}
|
||||
qstring_append(str, utf8_buf);
|
||||
break;
|
||||
default:
|
||||
parse_error(ctxt, token, "invalid escape sequence in string");
|
||||
goto out;
|
||||
}
|
||||
} else {
|
||||
char dummy[2];
|
||||
|
||||
dummy[0] = *ptr++;
|
||||
dummy[1] = 0;
|
||||
|
||||
qstring_append(str, dummy);
|
||||
break;
|
||||
case '%':
|
||||
if (ctxt->ap && ptr[1] != '%') {
|
||||
parse_error(ctxt, token, "can't interpolate into string");
|
||||
goto out;
|
||||
}
|
||||
ptr++;
|
||||
/* fall through */
|
||||
default:
|
||||
cp = mod_utf8_codepoint(ptr, 6, &end);
|
||||
if (cp < 0) {
|
||||
parse_error(ctxt, token, "invalid UTF-8 sequence in string");
|
||||
goto out;
|
||||
}
|
||||
ptr = end;
|
||||
len = mod_utf8_encode(utf8_buf, sizeof(utf8_buf), cp);
|
||||
assert(len >= 0);
|
||||
qstring_append(str, utf8_buf);
|
||||
}
|
||||
}
|
||||
|
||||
@ -233,48 +241,19 @@ out:
|
||||
static JSONToken *parser_context_pop_token(JSONParserContext *ctxt)
|
||||
{
|
||||
g_free(ctxt->current);
|
||||
assert(!g_queue_is_empty(ctxt->buf));
|
||||
ctxt->current = g_queue_pop_head(ctxt->buf);
|
||||
return ctxt->current;
|
||||
}
|
||||
|
||||
static JSONToken *parser_context_peek_token(JSONParserContext *ctxt)
|
||||
{
|
||||
assert(!g_queue_is_empty(ctxt->buf));
|
||||
return g_queue_peek_head(ctxt->buf);
|
||||
}
|
||||
|
||||
static JSONParserContext *parser_context_new(GQueue *tokens)
|
||||
{
|
||||
JSONParserContext *ctxt;
|
||||
|
||||
if (!tokens) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ctxt = g_malloc0(sizeof(JSONParserContext));
|
||||
ctxt->buf = tokens;
|
||||
|
||||
return ctxt;
|
||||
}
|
||||
|
||||
/* to support error propagation, ctxt->err must be freed separately */
|
||||
static void parser_context_free(JSONParserContext *ctxt)
|
||||
{
|
||||
if (ctxt) {
|
||||
while (!g_queue_is_empty(ctxt->buf)) {
|
||||
parser_context_pop_token(ctxt);
|
||||
}
|
||||
g_free(ctxt->current);
|
||||
g_queue_free(ctxt->buf);
|
||||
g_free(ctxt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parsing rules
|
||||
*/
|
||||
static int parse_pair(JSONParserContext *ctxt, QDict *dict, va_list *ap)
|
||||
static int parse_pair(JSONParserContext *ctxt, QDict *dict)
|
||||
{
|
||||
QObject *value;
|
||||
QString *key = NULL;
|
||||
@ -286,7 +265,7 @@ static int parse_pair(JSONParserContext *ctxt, QDict *dict, va_list *ap)
|
||||
goto out;
|
||||
}
|
||||
|
||||
key = qobject_to(QString, parse_value(ctxt, ap));
|
||||
key = qobject_to(QString, parse_value(ctxt));
|
||||
if (!key) {
|
||||
parse_error(ctxt, peek, "key is not a string in object");
|
||||
goto out;
|
||||
@ -303,7 +282,7 @@ static int parse_pair(JSONParserContext *ctxt, QDict *dict, va_list *ap)
|
||||
goto out;
|
||||
}
|
||||
|
||||
value = parse_value(ctxt, ap);
|
||||
value = parse_value(ctxt);
|
||||
if (value == NULL) {
|
||||
parse_error(ctxt, token, "Missing value in dict");
|
||||
goto out;
|
||||
@ -321,7 +300,7 @@ out:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static QObject *parse_object(JSONParserContext *ctxt, va_list *ap)
|
||||
static QObject *parse_object(JSONParserContext *ctxt)
|
||||
{
|
||||
QDict *dict = NULL;
|
||||
JSONToken *token, *peek;
|
||||
@ -338,7 +317,7 @@ static QObject *parse_object(JSONParserContext *ctxt, va_list *ap)
|
||||
}
|
||||
|
||||
if (peek->type != JSON_RCURLY) {
|
||||
if (parse_pair(ctxt, dict, ap) == -1) {
|
||||
if (parse_pair(ctxt, dict) == -1) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
@ -354,7 +333,7 @@ static QObject *parse_object(JSONParserContext *ctxt, va_list *ap)
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (parse_pair(ctxt, dict, ap) == -1) {
|
||||
if (parse_pair(ctxt, dict) == -1) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
@ -375,7 +354,7 @@ out:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static QObject *parse_array(JSONParserContext *ctxt, va_list *ap)
|
||||
static QObject *parse_array(JSONParserContext *ctxt)
|
||||
{
|
||||
QList *list = NULL;
|
||||
JSONToken *token, *peek;
|
||||
@ -394,7 +373,7 @@ static QObject *parse_array(JSONParserContext *ctxt, va_list *ap)
|
||||
if (peek->type != JSON_RSQUARE) {
|
||||
QObject *obj;
|
||||
|
||||
obj = parse_value(ctxt, ap);
|
||||
obj = parse_value(ctxt);
|
||||
if (obj == NULL) {
|
||||
parse_error(ctxt, token, "expecting value");
|
||||
goto out;
|
||||
@ -414,7 +393,7 @@ static QObject *parse_array(JSONParserContext *ctxt, va_list *ap)
|
||||
goto out;
|
||||
}
|
||||
|
||||
obj = parse_value(ctxt, ap);
|
||||
obj = parse_value(ctxt);
|
||||
if (obj == NULL) {
|
||||
parse_error(ctxt, token, "expecting value");
|
||||
goto out;
|
||||
@ -457,40 +436,39 @@ static QObject *parse_keyword(JSONParserContext *ctxt)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static QObject *parse_escape(JSONParserContext *ctxt, va_list *ap)
|
||||
static QObject *parse_interpolation(JSONParserContext *ctxt)
|
||||
{
|
||||
JSONToken *token;
|
||||
|
||||
if (ap == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
token = parser_context_pop_token(ctxt);
|
||||
assert(token && token->type == JSON_ESCAPE);
|
||||
assert(token && token->type == JSON_INTERP);
|
||||
|
||||
if (!strcmp(token->str, "%p")) {
|
||||
return va_arg(*ap, QObject *);
|
||||
return va_arg(*ctxt->ap, QObject *);
|
||||
} else if (!strcmp(token->str, "%i")) {
|
||||
return QOBJECT(qbool_from_bool(va_arg(*ap, int)));
|
||||
return QOBJECT(qbool_from_bool(va_arg(*ctxt->ap, int)));
|
||||
} else if (!strcmp(token->str, "%d")) {
|
||||
return QOBJECT(qnum_from_int(va_arg(*ap, int)));
|
||||
return QOBJECT(qnum_from_int(va_arg(*ctxt->ap, int)));
|
||||
} else if (!strcmp(token->str, "%ld")) {
|
||||
return QOBJECT(qnum_from_int(va_arg(*ap, long)));
|
||||
} else if (!strcmp(token->str, "%lld") ||
|
||||
!strcmp(token->str, "%I64d")) {
|
||||
return QOBJECT(qnum_from_int(va_arg(*ap, long long)));
|
||||
return QOBJECT(qnum_from_int(va_arg(*ctxt->ap, long)));
|
||||
} else if (!strcmp(token->str, "%lld")) {
|
||||
return QOBJECT(qnum_from_int(va_arg(*ctxt->ap, long long)));
|
||||
} else if (!strcmp(token->str, "%" PRId64)) {
|
||||
return QOBJECT(qnum_from_int(va_arg(*ctxt->ap, int64_t)));
|
||||
} else if (!strcmp(token->str, "%u")) {
|
||||
return QOBJECT(qnum_from_uint(va_arg(*ap, unsigned int)));
|
||||
return QOBJECT(qnum_from_uint(va_arg(*ctxt->ap, unsigned int)));
|
||||
} else if (!strcmp(token->str, "%lu")) {
|
||||
return QOBJECT(qnum_from_uint(va_arg(*ap, unsigned long)));
|
||||
} else if (!strcmp(token->str, "%llu") ||
|
||||
!strcmp(token->str, "%I64u")) {
|
||||
return QOBJECT(qnum_from_uint(va_arg(*ap, unsigned long long)));
|
||||
return QOBJECT(qnum_from_uint(va_arg(*ctxt->ap, unsigned long)));
|
||||
} else if (!strcmp(token->str, "%llu")) {
|
||||
return QOBJECT(qnum_from_uint(va_arg(*ctxt->ap, unsigned long long)));
|
||||
} else if (!strcmp(token->str, "%" PRIu64)) {
|
||||
return QOBJECT(qnum_from_uint(va_arg(*ctxt->ap, uint64_t)));
|
||||
} else if (!strcmp(token->str, "%s")) {
|
||||
return QOBJECT(qstring_from_str(va_arg(*ap, const char *)));
|
||||
return QOBJECT(qstring_from_str(va_arg(*ctxt->ap, const char *)));
|
||||
} else if (!strcmp(token->str, "%f")) {
|
||||
return QOBJECT(qnum_from_double(va_arg(*ap, double)));
|
||||
return QOBJECT(qnum_from_double(va_arg(*ctxt->ap, double)));
|
||||
}
|
||||
parse_error(ctxt, token, "invalid interpolation '%s'", token->str);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@ -503,7 +481,7 @@ static QObject *parse_literal(JSONParserContext *ctxt)
|
||||
|
||||
switch (token->type) {
|
||||
case JSON_STRING:
|
||||
return QOBJECT(qstring_from_escaped_str(ctxt, token));
|
||||
return QOBJECT(parse_string(ctxt, token));
|
||||
case JSON_INTEGER: {
|
||||
/*
|
||||
* Represent JSON_INTEGER as QNUM_I64 if possible, else as
|
||||
@ -538,7 +516,7 @@ static QObject *parse_literal(JSONParserContext *ctxt)
|
||||
}
|
||||
case JSON_FLOAT:
|
||||
/* FIXME dependent on locale; a pervasive issue in QEMU */
|
||||
/* FIXME our lexer matches RFC 7159 in forbidding Inf or NaN,
|
||||
/* FIXME our lexer matches RFC 8259 in forbidding Inf or NaN,
|
||||
* but those might be useful extensions beyond JSON */
|
||||
return QOBJECT(qnum_from_double(strtod(token->str, NULL)));
|
||||
default:
|
||||
@ -546,7 +524,7 @@ static QObject *parse_literal(JSONParserContext *ctxt)
|
||||
}
|
||||
}
|
||||
|
||||
static QObject *parse_value(JSONParserContext *ctxt, va_list *ap)
|
||||
static QObject *parse_value(JSONParserContext *ctxt)
|
||||
{
|
||||
JSONToken *token;
|
||||
|
||||
@ -558,11 +536,11 @@ static QObject *parse_value(JSONParserContext *ctxt, va_list *ap)
|
||||
|
||||
switch (token->type) {
|
||||
case JSON_LCURLY:
|
||||
return parse_object(ctxt, ap);
|
||||
return parse_object(ctxt);
|
||||
case JSON_LSQUARE:
|
||||
return parse_array(ctxt, ap);
|
||||
case JSON_ESCAPE:
|
||||
return parse_escape(ctxt, ap);
|
||||
return parse_array(ctxt);
|
||||
case JSON_INTERP:
|
||||
return parse_interpolation(ctxt);
|
||||
case JSON_INTEGER:
|
||||
case JSON_FLOAT:
|
||||
case JSON_STRING:
|
||||
@ -575,25 +553,32 @@ static QObject *parse_value(JSONParserContext *ctxt, va_list *ap)
|
||||
}
|
||||
}
|
||||
|
||||
QObject *json_parser_parse(GQueue *tokens, va_list *ap)
|
||||
JSONToken *json_token(JSONTokenType type, int x, int y, GString *tokstr)
|
||||
{
|
||||
return json_parser_parse_err(tokens, ap, NULL);
|
||||
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;
|
||||
}
|
||||
|
||||
QObject *json_parser_parse_err(GQueue *tokens, va_list *ap, Error **errp)
|
||||
QObject *json_parser_parse(GQueue *tokens, va_list *ap, Error **errp)
|
||||
{
|
||||
JSONParserContext *ctxt = parser_context_new(tokens);
|
||||
JSONParserContext ctxt = { .buf = tokens, .ap = ap };
|
||||
QObject *result;
|
||||
|
||||
if (!ctxt) {
|
||||
return NULL;
|
||||
result = parse_value(&ctxt);
|
||||
assert(ctxt.err || g_queue_is_empty(ctxt.buf));
|
||||
|
||||
error_propagate(errp, ctxt.err);
|
||||
|
||||
while (!g_queue_is_empty(ctxt.buf)) {
|
||||
parser_context_pop_token(&ctxt);
|
||||
}
|
||||
|
||||
result = parse_value(ctxt, ap);
|
||||
|
||||
error_propagate(errp, ctxt->err);
|
||||
|
||||
parser_context_free(ctxt);
|
||||
g_free(ctxt.current);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -12,34 +12,29 @@
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu-common.h"
|
||||
#include "qapi/qmp/json-lexer.h"
|
||||
#include "qapi/qmp/json-streamer.h"
|
||||
#include "qapi/error.h"
|
||||
#include "json-parser-int.h"
|
||||
|
||||
#define MAX_TOKEN_SIZE (64ULL << 20)
|
||||
#define MAX_TOKEN_COUNT (2ULL << 20)
|
||||
#define MAX_NESTING (1ULL << 10)
|
||||
|
||||
static void json_message_free_token(void *token, void *opaque)
|
||||
{
|
||||
g_free(token);
|
||||
}
|
||||
#define MAX_NESTING (1 << 10)
|
||||
|
||||
static void json_message_free_tokens(JSONMessageParser *parser)
|
||||
{
|
||||
if (parser->tokens) {
|
||||
g_queue_foreach(parser->tokens, json_message_free_token, NULL);
|
||||
g_queue_free(parser->tokens);
|
||||
parser->tokens = NULL;
|
||||
JSONToken *token;
|
||||
|
||||
while ((token = g_queue_pop_head(&parser->tokens))) {
|
||||
g_free(token);
|
||||
}
|
||||
}
|
||||
|
||||
static void json_message_process_token(JSONLexer *lexer, GString *input,
|
||||
JSONTokenType type, int x, int y)
|
||||
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;
|
||||
GQueue *tokens;
|
||||
|
||||
switch (type) {
|
||||
case JSON_LCURLY:
|
||||
@ -54,79 +49,82 @@ static void json_message_process_token(JSONLexer *lexer, GString *input,
|
||||
case JSON_RSQUARE:
|
||||
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;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
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 += input->len;
|
||||
|
||||
g_queue_push_tail(parser->tokens, token);
|
||||
|
||||
if (type == JSON_ERROR) {
|
||||
goto out_emit_bad;
|
||||
} else if (parser->brace_count < 0 ||
|
||||
parser->bracket_count < 0 ||
|
||||
(parser->brace_count == 0 &&
|
||||
parser->bracket_count == 0)) {
|
||||
/*
|
||||
* 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;
|
||||
} else if (parser->token_size > MAX_TOKEN_SIZE ||
|
||||
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_bad;
|
||||
}
|
||||
|
||||
return;
|
||||
token = json_token(type, x, y, input);
|
||||
parser->token_size += input->len;
|
||||
|
||||
g_queue_push_tail(&parser->tokens, token);
|
||||
|
||||
if ((parser->brace_count > 0 || parser->bracket_count > 0)
|
||||
&& parser->bracket_count >= 0 && parser->bracket_count >= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
json = json_parser_parse(&parser->tokens, parser->ap, &err);
|
||||
|
||||
out_emit_bad:
|
||||
/*
|
||||
* Clear out token list and tell the parser to emit an error
|
||||
* indication by passing it a NULL list
|
||||
*/
|
||||
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. Remove our own
|
||||
* reference to parser->tokens before handing it out to parser->emit.
|
||||
*/
|
||||
tokens = parser->tokens;
|
||||
parser->tokens = g_queue_new();
|
||||
parser->emit(parser, tokens);
|
||||
json_message_free_tokens(parser);
|
||||
parser->token_size = 0;
|
||||
parser->emit(parser->opaque, json, err);
|
||||
}
|
||||
|
||||
void json_message_parser_init(JSONMessageParser *parser,
|
||||
void (*func)(JSONMessageParser *, GQueue *))
|
||||
void (*emit)(void *opaque, QObject *json,
|
||||
Error *err),
|
||||
void *opaque, va_list *ap)
|
||||
{
|
||||
parser->emit = func;
|
||||
parser->emit = emit;
|
||||
parser->opaque = opaque;
|
||||
parser->ap = ap;
|
||||
parser->brace_count = 0;
|
||||
parser->bracket_count = 0;
|
||||
parser->tokens = g_queue_new();
|
||||
g_queue_init(&parser->tokens);
|
||||
parser->token_size = 0;
|
||||
|
||||
json_lexer_init(&parser->lexer, json_message_process_token);
|
||||
json_lexer_init(&parser->lexer, !!ap);
|
||||
}
|
||||
|
||||
int json_message_parser_feed(JSONMessageParser *parser,
|
||||
void json_message_parser_feed(JSONMessageParser *parser,
|
||||
const char *buffer, size_t size)
|
||||
{
|
||||
return json_lexer_feed(&parser->lexer, buffer, size);
|
||||
json_lexer_feed(&parser->lexer, buffer, size);
|
||||
}
|
||||
|
||||
int json_message_parser_flush(JSONMessageParser *parser)
|
||||
void json_message_parser_flush(JSONMessageParser *parser)
|
||||
{
|
||||
return json_lexer_flush(&parser->lexer);
|
||||
json_lexer_flush(&parser->lexer);
|
||||
assert(g_queue_is_empty(&parser->tokens));
|
||||
}
|
||||
|
||||
void json_message_parser_destroy(JSONMessageParser *parser)
|
||||
|
@ -13,7 +13,6 @@
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qapi/qmp/qbool.h"
|
||||
#include "qemu-common.h"
|
||||
|
||||
/**
|
||||
* qbool_from_bool(): Create a new QBool from a bool
|
||||
|
@ -13,9 +13,7 @@
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qapi/qmp/json-lexer.h"
|
||||
#include "qapi/qmp/json-parser.h"
|
||||
#include "qapi/qmp/json-streamer.h"
|
||||
#include "qapi/qmp/qjson.h"
|
||||
#include "qapi/qmp/qbool.h"
|
||||
#include "qapi/qmp/qdict.h"
|
||||
@ -27,16 +25,29 @@
|
||||
typedef struct JSONParsingState
|
||||
{
|
||||
JSONMessageParser parser;
|
||||
va_list *ap;
|
||||
QObject *result;
|
||||
Error *err;
|
||||
} JSONParsingState;
|
||||
|
||||
static void parse_json(JSONMessageParser *parser, GQueue *tokens)
|
||||
static void consume_json(void *opaque, QObject *json, Error *err)
|
||||
{
|
||||
JSONParsingState *s = container_of(parser, JSONParsingState, parser);
|
||||
JSONParsingState *s = opaque;
|
||||
|
||||
s->result = json_parser_parse_err(tokens, s->ap, &s->err);
|
||||
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;
|
||||
}
|
||||
s->result = json;
|
||||
s->err = err;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -54,13 +65,15 @@ static QObject *qobject_from_jsonv(const char *string, va_list *ap,
|
||||
{
|
||||
JSONParsingState state = {};
|
||||
|
||||
state.ap = ap;
|
||||
|
||||
json_message_parser_init(&state.parser, parse_json);
|
||||
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;
|
||||
}
|
||||
|
@ -17,7 +17,6 @@
|
||||
#include "qapi/qmp/qnum.h"
|
||||
#include "qapi/qmp/qstring.h"
|
||||
#include "qemu/queue.h"
|
||||
#include "qemu-common.h"
|
||||
|
||||
/**
|
||||
* qlist_new(): Create a new QList
|
||||
|
@ -11,7 +11,6 @@
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu-common.h"
|
||||
#include "qapi/qmp/qnull.h"
|
||||
|
||||
QNull qnull_ = {
|
||||
|
@ -14,7 +14,6 @@
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qapi/qmp/qnum.h"
|
||||
#include "qemu-common.h"
|
||||
|
||||
/**
|
||||
* qnum_from_int(): Create a new QNum from an int64_t
|
||||
|
@ -8,7 +8,6 @@
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu-common.h"
|
||||
#include "qapi/qmp/qbool.h"
|
||||
#include "qapi/qmp/qnull.h"
|
||||
#include "qapi/qmp/qnum.h"
|
||||
|
@ -12,7 +12,6 @@
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qapi/qmp/qstring.h"
|
||||
#include "qemu-common.h"
|
||||
|
||||
/**
|
||||
* qstring_new(): Create a new empty QString
|
||||
|
@ -183,6 +183,8 @@ check-block-$(CONFIG_POSIX) += tests/qemu-iotests-quick.sh
|
||||
|
||||
check-qtest-generic-y = tests/qmp-test$(EXESUF)
|
||||
gcov-files-generic-y = monitor.c qapi/qmp-dispatch.c
|
||||
check-qtest-generic-y += tests/qmp-cmd-test$(EXESUF)
|
||||
|
||||
check-qtest-generic-y += tests/device-introspect-test$(EXESUF)
|
||||
gcov-files-generic-y = qdev-monitor.c qmp.c
|
||||
check-qtest-generic-y += tests/cdrom-test$(EXESUF)
|
||||
@ -779,6 +781,7 @@ libqos-usb-obj-y = $(libqos-spapr-obj-y) $(libqos-pc-obj-y) tests/libqos/usb.o
|
||||
libqos-virtio-obj-y = $(libqos-spapr-obj-y) $(libqos-pc-obj-y) tests/libqos/virtio.o tests/libqos/virtio-pci.o tests/libqos/virtio-mmio.o tests/libqos/malloc-generic.o
|
||||
|
||||
tests/qmp-test$(EXESUF): tests/qmp-test.o
|
||||
tests/qmp-cmd-test$(EXESUF): tests/qmp-cmd-test.o
|
||||
tests/device-introspect-test$(EXESUF): tests/device-introspect-test.o
|
||||
tests/rtc-test$(EXESUF): tests/rtc-test.o
|
||||
tests/m48t59-test$(EXESUF): tests/m48t59-test.o
|
||||
|
1054
tests/check-qjson.c
1054
tests/check-qjson.c
File diff suppressed because it is too large
Load Diff
@ -65,9 +65,13 @@ static void test_drive_without_dev(void)
|
||||
|
||||
static void test_after_failed_device_add(void)
|
||||
{
|
||||
char driver[32];
|
||||
QDict *response;
|
||||
QDict *error;
|
||||
|
||||
snprintf(driver, sizeof(driver), "virtio-blk-%s",
|
||||
qvirtio_get_dev_type());
|
||||
|
||||
qtest_start("-drive if=none,id=drive0");
|
||||
|
||||
/* Make device_add fail. If this leaks the virtio-blk device then a
|
||||
@ -75,9 +79,9 @@ static void test_after_failed_device_add(void)
|
||||
*/
|
||||
response = qmp("{'execute': 'device_add',"
|
||||
" 'arguments': {"
|
||||
" 'driver': 'virtio-blk-%s',"
|
||||
" 'driver': %s,"
|
||||
" 'drive': 'drive0'"
|
||||
"}}", qvirtio_get_dev_type());
|
||||
"}}", driver);
|
||||
g_assert(response);
|
||||
error = qdict_get_qdict(response, "error");
|
||||
g_assert_cmpstr(qdict_get_try_str(error, "class"), ==, "GenericError");
|
||||
|
@ -21,10 +21,10 @@
|
||||
#include <sys/un.h>
|
||||
|
||||
#include "libqtest.h"
|
||||
#include "qemu-common.h"
|
||||
#include "qemu/cutils.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qapi/qmp/json-parser.h"
|
||||
#include "qapi/qmp/json-streamer.h"
|
||||
#include "qapi/qmp/qdict.h"
|
||||
#include "qapi/qmp/qjson.h"
|
||||
#include "qapi/qmp/qlist.h"
|
||||
@ -446,14 +446,15 @@ typedef struct {
|
||||
QDict *response;
|
||||
} QMPResponseParser;
|
||||
|
||||
static void qmp_response(JSONMessageParser *parser, GQueue *tokens)
|
||||
static void qmp_response(void *opaque, QObject *obj, Error *err)
|
||||
{
|
||||
QMPResponseParser *qmp = container_of(parser, QMPResponseParser, parser);
|
||||
QObject *obj;
|
||||
QMPResponseParser *qmp = opaque;
|
||||
|
||||
obj = json_parser_parse(tokens, NULL);
|
||||
if (!obj) {
|
||||
fprintf(stderr, "QMP JSON response parsing failed\n");
|
||||
assert(!obj != !err);
|
||||
|
||||
if (err) {
|
||||
error_prepend(&err, "QMP JSON response parsing failed: ");
|
||||
error_report_err(err);
|
||||
abort();
|
||||
}
|
||||
|
||||
@ -468,7 +469,7 @@ QDict *qmp_fd_receive(int fd)
|
||||
bool log = getenv("QTEST_LOG") != NULL;
|
||||
|
||||
qmp.response = NULL;
|
||||
json_message_parser_init(&qmp.parser, qmp_response);
|
||||
json_message_parser_init(&qmp.parser, qmp_response, &qmp, NULL);
|
||||
while (!qmp.response) {
|
||||
ssize_t len;
|
||||
char c;
|
||||
@ -507,16 +508,6 @@ void qmp_fd_vsend(int fd, const char *fmt, va_list ap)
|
||||
{
|
||||
QObject *qobj;
|
||||
|
||||
/*
|
||||
* qobject_from_vjsonf_nofail() chokes on leading 0xff as invalid
|
||||
* JSON, but tests/test-qga.c needs to send that to test QGA
|
||||
* synchronization
|
||||
*/
|
||||
if (*fmt == '\377') {
|
||||
socket_send(fd, fmt, 1);
|
||||
fmt++;
|
||||
}
|
||||
|
||||
/* Going through qobject ensures we escape strings properly */
|
||||
qobj = qobject_from_vjsonf_nofail(fmt, ap);
|
||||
|
||||
@ -604,6 +595,36 @@ void qtest_qmp_send(QTestState *s, const char *fmt, ...)
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void qmp_fd_vsend_raw(int fd, const char *fmt, va_list ap)
|
||||
{
|
||||
bool log = getenv("QTEST_LOG") != NULL;
|
||||
char *str = g_strdup_vprintf(fmt, ap);
|
||||
|
||||
if (log) {
|
||||
fprintf(stderr, "%s", str);
|
||||
}
|
||||
socket_send(fd, str, strlen(str));
|
||||
g_free(str);
|
||||
}
|
||||
|
||||
void qmp_fd_send_raw(int fd, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
qmp_fd_vsend_raw(fd, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void qtest_qmp_send_raw(QTestState *s, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
qmp_fd_vsend_raw(s->qmp_fd, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
QDict *qtest_qmp_eventwait_ref(QTestState *s, const char *event)
|
||||
{
|
||||
QDict *response;
|
||||
|
@ -96,6 +96,17 @@ QDict *qtest_qmp(QTestState *s, const char *fmt, ...)
|
||||
void qtest_qmp_send(QTestState *s, const char *fmt, ...)
|
||||
GCC_FMT_ATTR(2, 3);
|
||||
|
||||
/**
|
||||
* qtest_qmp_send_raw:
|
||||
* @s: #QTestState instance to operate on.
|
||||
* @fmt...: text to send, formatted like sprintf()
|
||||
*
|
||||
* Sends text to the QMP monitor verbatim. Need not be valid JSON;
|
||||
* this is useful for negative tests.
|
||||
*/
|
||||
void qtest_qmp_send_raw(QTestState *s, const char *fmt, ...)
|
||||
GCC_FMT_ATTR(2, 3);
|
||||
|
||||
/**
|
||||
* qtest_qmpv:
|
||||
* @s: #QTestState instance to operate on.
|
||||
@ -948,6 +959,8 @@ static inline int64_t clock_set(int64_t val)
|
||||
QDict *qmp_fd_receive(int fd);
|
||||
void qmp_fd_vsend(int fd, const char *fmt, va_list ap) GCC_FMT_ATTR(2, 0);
|
||||
void qmp_fd_send(int fd, const char *fmt, ...) GCC_FMT_ATTR(2, 3);
|
||||
void qmp_fd_send_raw(int fd, const char *fmt, ...) GCC_FMT_ATTR(2, 3);
|
||||
void qmp_fd_vsend_raw(int fd, const char *fmt, va_list ap) GCC_FMT_ATTR(2, 0);
|
||||
QDict *qmp_fdv(int fd, const char *fmt, va_list ap) GCC_FMT_ATTR(2, 0);
|
||||
QDict *qmp_fd(int fd, const char *fmt, ...) GCC_FMT_ATTR(2, 3);
|
||||
|
||||
|
213
tests/qmp-cmd-test.c
Normal file
213
tests/qmp-cmd-test.c
Normal file
@ -0,0 +1,213 @@
|
||||
/*
|
||||
* QMP command test cases
|
||||
*
|
||||
* Copyright (c) 2017 Red Hat Inc.
|
||||
*
|
||||
* Authors:
|
||||
* Markus Armbruster <armbru@redhat.com>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "libqtest.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qapi/qapi-visit-introspect.h"
|
||||
#include "qapi/qmp/qdict.h"
|
||||
#include "qapi/qobject-input-visitor.h"
|
||||
|
||||
const char common_args[] = "-nodefaults -machine none";
|
||||
|
||||
/* Query smoke tests */
|
||||
|
||||
static int query_error_class(const char *cmd)
|
||||
{
|
||||
static struct {
|
||||
const char *cmd;
|
||||
int err_class;
|
||||
} fails[] = {
|
||||
/* Success depends on build configuration: */
|
||||
#ifndef CONFIG_SPICE
|
||||
{ "query-spice", ERROR_CLASS_COMMAND_NOT_FOUND },
|
||||
#endif
|
||||
#ifndef CONFIG_VNC
|
||||
{ "query-vnc", ERROR_CLASS_GENERIC_ERROR },
|
||||
{ "query-vnc-servers", ERROR_CLASS_GENERIC_ERROR },
|
||||
#endif
|
||||
#ifndef CONFIG_REPLICATION
|
||||
{ "query-xen-replication-status", ERROR_CLASS_COMMAND_NOT_FOUND },
|
||||
#endif
|
||||
/* Likewise, and require special QEMU command-line arguments: */
|
||||
{ "query-acpi-ospm-status", ERROR_CLASS_GENERIC_ERROR },
|
||||
{ "query-balloon", ERROR_CLASS_DEVICE_NOT_ACTIVE },
|
||||
{ "query-hotpluggable-cpus", ERROR_CLASS_GENERIC_ERROR },
|
||||
{ "query-vm-generation-id", ERROR_CLASS_GENERIC_ERROR },
|
||||
{ NULL, -1 }
|
||||
};
|
||||
int i;
|
||||
|
||||
for (i = 0; fails[i].cmd; i++) {
|
||||
if (!strcmp(cmd, fails[i].cmd)) {
|
||||
return fails[i].err_class;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void test_query(const void *data)
|
||||
{
|
||||
const char *cmd = data;
|
||||
int expected_error_class = query_error_class(cmd);
|
||||
QDict *resp, *error;
|
||||
const char *error_class;
|
||||
|
||||
qtest_start(common_args);
|
||||
|
||||
resp = qmp("{ 'execute': %s }", cmd);
|
||||
error = qdict_get_qdict(resp, "error");
|
||||
error_class = error ? qdict_get_str(error, "class") : NULL;
|
||||
|
||||
if (expected_error_class < 0) {
|
||||
g_assert(qdict_haskey(resp, "return"));
|
||||
} else {
|
||||
g_assert(error);
|
||||
g_assert_cmpint(qapi_enum_parse(&QapiErrorClass_lookup, error_class,
|
||||
-1, &error_abort),
|
||||
==, expected_error_class);
|
||||
}
|
||||
qobject_unref(resp);
|
||||
|
||||
qtest_end();
|
||||
}
|
||||
|
||||
static bool query_is_blacklisted(const char *cmd)
|
||||
{
|
||||
const char *blacklist[] = {
|
||||
/* Not actually queries: */
|
||||
"add-fd",
|
||||
/* Success depends on target arch: */
|
||||
"query-cpu-definitions", /* arm, i386, ppc, s390x */
|
||||
"query-gic-capabilities", /* arm */
|
||||
/* Success depends on target-specific build configuration: */
|
||||
"query-pci", /* CONFIG_PCI */
|
||||
/* Success depends on launching SEV guest */
|
||||
"query-sev-launch-measure",
|
||||
/* Success depends on Host or Hypervisor SEV support */
|
||||
"query-sev",
|
||||
"query-sev-capabilities",
|
||||
NULL
|
||||
};
|
||||
int i;
|
||||
|
||||
for (i = 0; blacklist[i]; i++) {
|
||||
if (!strcmp(cmd, blacklist[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
SchemaInfoList *list;
|
||||
GHashTable *hash;
|
||||
} QmpSchema;
|
||||
|
||||
static void qmp_schema_init(QmpSchema *schema)
|
||||
{
|
||||
QDict *resp;
|
||||
Visitor *qiv;
|
||||
SchemaInfoList *tail;
|
||||
|
||||
qtest_start(common_args);
|
||||
resp = qmp("{ 'execute': 'query-qmp-schema' }");
|
||||
|
||||
qiv = qobject_input_visitor_new(qdict_get(resp, "return"));
|
||||
visit_type_SchemaInfoList(qiv, NULL, &schema->list, &error_abort);
|
||||
visit_free(qiv);
|
||||
|
||||
qobject_unref(resp);
|
||||
qtest_end();
|
||||
|
||||
schema->hash = g_hash_table_new(g_str_hash, g_str_equal);
|
||||
|
||||
/* Build @schema: hash table mapping entity name to SchemaInfo */
|
||||
for (tail = schema->list; tail; tail = tail->next) {
|
||||
g_hash_table_insert(schema->hash, tail->value->name, tail->value);
|
||||
}
|
||||
}
|
||||
|
||||
static SchemaInfo *qmp_schema_lookup(QmpSchema *schema, const char *name)
|
||||
{
|
||||
return g_hash_table_lookup(schema->hash, name);
|
||||
}
|
||||
|
||||
static void qmp_schema_cleanup(QmpSchema *schema)
|
||||
{
|
||||
qapi_free_SchemaInfoList(schema->list);
|
||||
g_hash_table_destroy(schema->hash);
|
||||
}
|
||||
|
||||
static bool object_type_has_mandatory_members(SchemaInfo *type)
|
||||
{
|
||||
SchemaInfoObjectMemberList *tail;
|
||||
|
||||
g_assert(type->meta_type == SCHEMA_META_TYPE_OBJECT);
|
||||
|
||||
for (tail = type->u.object.members; tail; tail = tail->next) {
|
||||
if (!tail->value->has_q_default) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void add_query_tests(QmpSchema *schema)
|
||||
{
|
||||
SchemaInfoList *tail;
|
||||
SchemaInfo *si, *arg_type, *ret_type;
|
||||
char *test_name;
|
||||
|
||||
/* Test the query-like commands */
|
||||
for (tail = schema->list; tail; tail = tail->next) {
|
||||
si = tail->value;
|
||||
if (si->meta_type != SCHEMA_META_TYPE_COMMAND) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (query_is_blacklisted(si->name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
arg_type = qmp_schema_lookup(schema, si->u.command.arg_type);
|
||||
if (object_type_has_mandatory_members(arg_type)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ret_type = qmp_schema_lookup(schema, si->u.command.ret_type);
|
||||
if (ret_type->meta_type == SCHEMA_META_TYPE_OBJECT
|
||||
&& !ret_type->u.object.members) {
|
||||
continue;
|
||||
}
|
||||
|
||||
test_name = g_strdup_printf("qmp/%s", si->name);
|
||||
qtest_add_data_func(test_name, si->name, test_query);
|
||||
g_free(test_name);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QmpSchema schema;
|
||||
int ret;
|
||||
|
||||
g_test_init(&argc, &argv, NULL);
|
||||
|
||||
qmp_schema_init(&schema);
|
||||
add_query_tests(&schema);
|
||||
ret = g_test_run();
|
||||
|
||||
qmp_schema_cleanup(&schema);
|
||||
return ret;
|
||||
}
|
252
tests/qmp-test.c
252
tests/qmp-test.c
@ -1,10 +1,10 @@
|
||||
/*
|
||||
* QMP protocol test cases
|
||||
*
|
||||
* Copyright (c) 2017 Red Hat Inc.
|
||||
* Copyright (c) 2017-2018 Red Hat Inc.
|
||||
*
|
||||
* Authors:
|
||||
* Markus Armbruster <armbru@redhat.com>,
|
||||
* Markus Armbruster <armbru@redhat.com>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
@ -13,13 +13,10 @@
|
||||
#include "qemu/osdep.h"
|
||||
#include "libqtest.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qapi/qapi-visit-introspect.h"
|
||||
#include "qapi/qapi-visit-misc.h"
|
||||
#include "qapi/qmp/qdict.h"
|
||||
#include "qapi/qmp/qlist.h"
|
||||
#include "qapi/qobject-input-visitor.h"
|
||||
#include "qapi/util.h"
|
||||
#include "qapi/visitor.h"
|
||||
#include "qapi/qmp/qstring.h"
|
||||
|
||||
const char common_args[] = "-nodefaults -machine none";
|
||||
@ -45,10 +42,67 @@ static void test_version(QObject *version)
|
||||
visit_free(v);
|
||||
}
|
||||
|
||||
static bool recovered(QTestState *qts)
|
||||
{
|
||||
QDict *resp;
|
||||
bool ret;
|
||||
|
||||
resp = qtest_qmp(qts, "{ 'execute': 'no-such-cmd' }");
|
||||
ret = !strcmp(get_error_class(resp), "CommandNotFound");
|
||||
qobject_unref(resp);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void test_malformed(QTestState *qts)
|
||||
{
|
||||
QDict *resp;
|
||||
|
||||
/* syntax error */
|
||||
qtest_qmp_send_raw(qts, "{]\n");
|
||||
resp = qtest_qmp_receive(qts);
|
||||
g_assert_cmpstr(get_error_class(resp), ==, "GenericError");
|
||||
qobject_unref(resp);
|
||||
g_assert(recovered(qts));
|
||||
|
||||
/* lexical error: impossible byte outside string */
|
||||
qtest_qmp_send_raw(qts, "{\xFF");
|
||||
resp = qtest_qmp_receive(qts);
|
||||
g_assert_cmpstr(get_error_class(resp), ==, "GenericError");
|
||||
qobject_unref(resp);
|
||||
g_assert(recovered(qts));
|
||||
|
||||
/* lexical error: funny control character outside string */
|
||||
qtest_qmp_send_raw(qts, "{\x01");
|
||||
resp = qtest_qmp_receive(qts);
|
||||
g_assert_cmpstr(get_error_class(resp), ==, "GenericError");
|
||||
qobject_unref(resp);
|
||||
g_assert(recovered(qts));
|
||||
|
||||
/* lexical error: impossible byte in string */
|
||||
qtest_qmp_send_raw(qts, "{'bad \xFF");
|
||||
resp = qtest_qmp_receive(qts);
|
||||
g_assert_cmpstr(get_error_class(resp), ==, "GenericError");
|
||||
qobject_unref(resp);
|
||||
g_assert(recovered(qts));
|
||||
|
||||
/* lexical error: control character in string */
|
||||
qtest_qmp_send_raw(qts, "{'execute': 'nonexistent', 'id':'\n");
|
||||
resp = qtest_qmp_receive(qts);
|
||||
g_assert_cmpstr(get_error_class(resp), ==, "GenericError");
|
||||
qobject_unref(resp);
|
||||
g_assert(recovered(qts));
|
||||
|
||||
/* lexical error: interpolation */
|
||||
qtest_qmp_send_raw(qts, "%%p\n");
|
||||
/* two errors, one for "%", one for "p" */
|
||||
resp = qtest_qmp_receive(qts);
|
||||
g_assert_cmpstr(get_error_class(resp), ==, "GenericError");
|
||||
qobject_unref(resp);
|
||||
resp = qtest_qmp_receive(qts);
|
||||
g_assert_cmpstr(get_error_class(resp), ==, "GenericError");
|
||||
qobject_unref(resp);
|
||||
g_assert(recovered(qts));
|
||||
|
||||
/* Not even a dictionary */
|
||||
resp = qtest_qmp(qts, "null");
|
||||
g_assert_cmpstr(get_error_class(resp), ==, "GenericError");
|
||||
@ -253,184 +307,6 @@ static void test_qmp_oob(void)
|
||||
qtest_quit(qts);
|
||||
}
|
||||
|
||||
/* Query smoke tests */
|
||||
|
||||
static int query_error_class(const char *cmd)
|
||||
{
|
||||
static struct {
|
||||
const char *cmd;
|
||||
int err_class;
|
||||
} fails[] = {
|
||||
/* Success depends on build configuration: */
|
||||
#ifndef CONFIG_SPICE
|
||||
{ "query-spice", ERROR_CLASS_COMMAND_NOT_FOUND },
|
||||
#endif
|
||||
#ifndef CONFIG_VNC
|
||||
{ "query-vnc", ERROR_CLASS_GENERIC_ERROR },
|
||||
{ "query-vnc-servers", ERROR_CLASS_GENERIC_ERROR },
|
||||
#endif
|
||||
#ifndef CONFIG_REPLICATION
|
||||
{ "query-xen-replication-status", ERROR_CLASS_COMMAND_NOT_FOUND },
|
||||
#endif
|
||||
/* Likewise, and require special QEMU command-line arguments: */
|
||||
{ "query-acpi-ospm-status", ERROR_CLASS_GENERIC_ERROR },
|
||||
{ "query-balloon", ERROR_CLASS_DEVICE_NOT_ACTIVE },
|
||||
{ "query-hotpluggable-cpus", ERROR_CLASS_GENERIC_ERROR },
|
||||
{ "query-vm-generation-id", ERROR_CLASS_GENERIC_ERROR },
|
||||
{ NULL, -1 }
|
||||
};
|
||||
int i;
|
||||
|
||||
for (i = 0; fails[i].cmd; i++) {
|
||||
if (!strcmp(cmd, fails[i].cmd)) {
|
||||
return fails[i].err_class;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void test_query(const void *data)
|
||||
{
|
||||
const char *cmd = data;
|
||||
int expected_error_class = query_error_class(cmd);
|
||||
QDict *resp, *error;
|
||||
const char *error_class;
|
||||
|
||||
qtest_start(common_args);
|
||||
|
||||
resp = qmp("{ 'execute': %s }", cmd);
|
||||
error = qdict_get_qdict(resp, "error");
|
||||
error_class = error ? qdict_get_str(error, "class") : NULL;
|
||||
|
||||
if (expected_error_class < 0) {
|
||||
g_assert(qdict_haskey(resp, "return"));
|
||||
} else {
|
||||
g_assert(error);
|
||||
g_assert_cmpint(qapi_enum_parse(&QapiErrorClass_lookup, error_class,
|
||||
-1, &error_abort),
|
||||
==, expected_error_class);
|
||||
}
|
||||
qobject_unref(resp);
|
||||
|
||||
qtest_end();
|
||||
}
|
||||
|
||||
static bool query_is_blacklisted(const char *cmd)
|
||||
{
|
||||
const char *blacklist[] = {
|
||||
/* Not actually queries: */
|
||||
"add-fd",
|
||||
/* Success depends on target arch: */
|
||||
"query-cpu-definitions", /* arm, i386, ppc, s390x */
|
||||
"query-gic-capabilities", /* arm */
|
||||
/* Success depends on target-specific build configuration: */
|
||||
"query-pci", /* CONFIG_PCI */
|
||||
/* Success depends on launching SEV guest */
|
||||
"query-sev-launch-measure",
|
||||
/* Success depends on Host or Hypervisor SEV support */
|
||||
"query-sev",
|
||||
"query-sev-capabilities",
|
||||
NULL
|
||||
};
|
||||
int i;
|
||||
|
||||
for (i = 0; blacklist[i]; i++) {
|
||||
if (!strcmp(cmd, blacklist[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
SchemaInfoList *list;
|
||||
GHashTable *hash;
|
||||
} QmpSchema;
|
||||
|
||||
static void qmp_schema_init(QmpSchema *schema)
|
||||
{
|
||||
QDict *resp;
|
||||
Visitor *qiv;
|
||||
SchemaInfoList *tail;
|
||||
|
||||
qtest_start(common_args);
|
||||
resp = qmp("{ 'execute': 'query-qmp-schema' }");
|
||||
|
||||
qiv = qobject_input_visitor_new(qdict_get(resp, "return"));
|
||||
visit_type_SchemaInfoList(qiv, NULL, &schema->list, &error_abort);
|
||||
visit_free(qiv);
|
||||
|
||||
qobject_unref(resp);
|
||||
qtest_end();
|
||||
|
||||
schema->hash = g_hash_table_new(g_str_hash, g_str_equal);
|
||||
|
||||
/* Build @schema: hash table mapping entity name to SchemaInfo */
|
||||
for (tail = schema->list; tail; tail = tail->next) {
|
||||
g_hash_table_insert(schema->hash, tail->value->name, tail->value);
|
||||
}
|
||||
}
|
||||
|
||||
static SchemaInfo *qmp_schema_lookup(QmpSchema *schema, const char *name)
|
||||
{
|
||||
return g_hash_table_lookup(schema->hash, name);
|
||||
}
|
||||
|
||||
static void qmp_schema_cleanup(QmpSchema *schema)
|
||||
{
|
||||
qapi_free_SchemaInfoList(schema->list);
|
||||
g_hash_table_destroy(schema->hash);
|
||||
}
|
||||
|
||||
static bool object_type_has_mandatory_members(SchemaInfo *type)
|
||||
{
|
||||
SchemaInfoObjectMemberList *tail;
|
||||
|
||||
g_assert(type->meta_type == SCHEMA_META_TYPE_OBJECT);
|
||||
|
||||
for (tail = type->u.object.members; tail; tail = tail->next) {
|
||||
if (!tail->value->has_q_default) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void add_query_tests(QmpSchema *schema)
|
||||
{
|
||||
SchemaInfoList *tail;
|
||||
SchemaInfo *si, *arg_type, *ret_type;
|
||||
char *test_name;
|
||||
|
||||
/* Test the query-like commands */
|
||||
for (tail = schema->list; tail; tail = tail->next) {
|
||||
si = tail->value;
|
||||
if (si->meta_type != SCHEMA_META_TYPE_COMMAND) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (query_is_blacklisted(si->name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
arg_type = qmp_schema_lookup(schema, si->u.command.arg_type);
|
||||
if (object_type_has_mandatory_members(arg_type)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ret_type = qmp_schema_lookup(schema, si->u.command.ret_type);
|
||||
if (ret_type->meta_type == SCHEMA_META_TYPE_OBJECT
|
||||
&& !ret_type->u.object.members) {
|
||||
continue;
|
||||
}
|
||||
|
||||
test_name = g_strdup_printf("qmp/%s", si->name);
|
||||
qtest_add_data_func(test_name, si->name, test_query);
|
||||
g_free(test_name);
|
||||
}
|
||||
}
|
||||
|
||||
/* Preconfig tests */
|
||||
|
||||
static void test_qmp_preconfig(void)
|
||||
@ -474,19 +350,11 @@ static void test_qmp_preconfig(void)
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QmpSchema schema;
|
||||
int ret;
|
||||
|
||||
g_test_init(&argc, &argv, NULL);
|
||||
|
||||
qtest_add_func("qmp/protocol", test_qmp_protocol);
|
||||
qtest_add_func("qmp/oob", test_qmp_oob);
|
||||
qmp_schema_init(&schema);
|
||||
add_query_tests(&schema);
|
||||
qtest_add_func("qmp/preconfig", test_qmp_preconfig);
|
||||
|
||||
ret = g_test_run();
|
||||
|
||||
qmp_schema_cleanup(&schema);
|
||||
return ret;
|
||||
return g_test_run();
|
||||
}
|
||||
|
@ -147,8 +147,9 @@ static void test_qga_sync_delimited(gconstpointer fix)
|
||||
unsigned char c;
|
||||
QDict *ret;
|
||||
|
||||
qmp_fd_send_raw(fixture->fd, "\xff");
|
||||
qmp_fd_send(fixture->fd,
|
||||
"\xff{'execute': 'guest-sync-delimited',"
|
||||
"{'execute': 'guest-sync-delimited',"
|
||||
" 'arguments': {'id': %u } }",
|
||||
r);
|
||||
|
||||
|
@ -13,6 +13,21 @@
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/unicode.h"
|
||||
|
||||
static bool is_valid_codepoint(int codepoint)
|
||||
{
|
||||
if (codepoint > 0x10FFFFu) {
|
||||
return false; /* beyond Unicode range */
|
||||
}
|
||||
if ((codepoint >= 0xFDD0 && codepoint <= 0xFDEF)
|
||||
|| (codepoint & 0xFFFE) == 0xFFFE) {
|
||||
return false; /* noncharacter */
|
||||
}
|
||||
if (codepoint >= 0xD800 && codepoint <= 0xDFFF) {
|
||||
return false; /* surrogate code point */
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* mod_utf8_codepoint:
|
||||
* @s: string encoded in modified UTF-8
|
||||
@ -83,13 +98,8 @@ int mod_utf8_codepoint(const char *s, size_t n, char **end)
|
||||
cp <<= 6;
|
||||
cp |= byte & 0x3F;
|
||||
}
|
||||
if (cp > 0x10FFFF) {
|
||||
cp = -1; /* beyond Unicode range */
|
||||
} else if ((cp >= 0xFDD0 && cp <= 0xFDEF)
|
||||
|| (cp & 0xFFFE) == 0xFFFE) {
|
||||
cp = -1; /* noncharacter */
|
||||
} else if (cp >= 0xD800 && cp <= 0xDFFF) {
|
||||
cp = -1; /* surrogate code point */
|
||||
if (!is_valid_codepoint(cp)) {
|
||||
cp = -1;
|
||||
} else if (cp < min_cp[len - 2] && !(cp == 0 && len == 2)) {
|
||||
cp = -1; /* overlong, not \xC0\x80 */
|
||||
}
|
||||
@ -99,3 +109,48 @@ out:
|
||||
*end = (char *)p;
|
||||
return cp;
|
||||
}
|
||||
|
||||
/**
|
||||
* mod_utf8_encode:
|
||||
* @buf: Destination buffer
|
||||
* @bufsz: size of @buf, at least 5.
|
||||
* @codepoint: Unicode codepoint to encode
|
||||
*
|
||||
* Convert Unicode codepoint @codepoint to modified UTF-8.
|
||||
*
|
||||
* Returns: the length of the UTF-8 sequence on success, -1 when
|
||||
* @codepoint is invalid.
|
||||
*/
|
||||
ssize_t mod_utf8_encode(char buf[], size_t bufsz, int codepoint)
|
||||
{
|
||||
assert(bufsz >= 5);
|
||||
|
||||
if (!is_valid_codepoint(codepoint)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (codepoint > 0 && codepoint <= 0x7F) {
|
||||
buf[0] = codepoint & 0x7F;
|
||||
buf[1] = 0;
|
||||
return 1;
|
||||
}
|
||||
if (codepoint <= 0x7FF) {
|
||||
buf[0] = 0xC0 | ((codepoint >> 6) & 0x1F);
|
||||
buf[1] = 0x80 | (codepoint & 0x3F);
|
||||
buf[2] = 0;
|
||||
return 2;
|
||||
}
|
||||
if (codepoint <= 0xFFFF) {
|
||||
buf[0] = 0xE0 | ((codepoint >> 12) & 0x0F);
|
||||
buf[1] = 0x80 | ((codepoint >> 6) & 0x3F);
|
||||
buf[2] = 0x80 | (codepoint & 0x3F);
|
||||
buf[3] = 0;
|
||||
return 3;
|
||||
}
|
||||
buf[0] = 0xF0 | ((codepoint >> 18) & 0x07);
|
||||
buf[1] = 0x80 | ((codepoint >> 12) & 0x3F);
|
||||
buf[2] = 0x80 | ((codepoint >> 6) & 0x3F);
|
||||
buf[3] = 0x80 | (codepoint & 0x3F);
|
||||
buf[4] = 0;
|
||||
return 4;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user