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:
Peter Maydell 2018-08-25 10:11:54 +01:00
commit cc9821fa9a
34 changed files with 1478 additions and 1335 deletions

View file

@ -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

View file

@ -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;
}

View file

@ -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

View file

@ -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

View file

@ -1,5 +1,5 @@
/*
* JSON Parser
* JSON Parser
*
* Copyright IBM, Corp. 2009
*
@ -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

View file

@ -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

View file

@ -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"

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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'.
#

View file

@ -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"

View file

@ -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);

View file

@ -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()) {

View file

@ -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
View 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

View file

@ -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;
}

View file

@ -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)

View file

@ -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

View file

@ -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;
}

View file

@ -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

View file

@ -11,7 +11,6 @@
*/
#include "qemu/osdep.h"
#include "qemu-common.h"
#include "qapi/qmp/qnull.h"
QNull qnull_ = {

View file

@ -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

View file

@ -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"

View file

@ -12,7 +12,6 @@
#include "qemu/osdep.h"
#include "qapi/qmp/qstring.h"
#include "qemu-common.h"
/**
* qstring_new(): Create a new empty QString

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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");

View file

@ -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;

View file

@ -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
View 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;
}

View file

@ -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();
}

View file

@ -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);

View file

@ -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;
}