qemu/qobject/json-parser.c
<<
>>
Prefs
   1/*
   2 * JSON Parser
   3 *
   4 * Copyright IBM, Corp. 2009
   5 *
   6 * Authors:
   7 *  Anthony Liguori   <aliguori@us.ibm.com>
   8 *
   9 * This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
  10 * See the COPYING.LIB file in the top-level directory.
  11 *
  12 */
  13
  14#include "qemu/osdep.h"
  15#include "qemu/ctype.h"
  16#include "qemu/cutils.h"
  17#include "qemu/unicode.h"
  18#include "qapi/error.h"
  19#include "qapi/qmp/qbool.h"
  20#include "qapi/qmp/qdict.h"
  21#include "qapi/qmp/qlist.h"
  22#include "qapi/qmp/qnull.h"
  23#include "qapi/qmp/qnum.h"
  24#include "qapi/qmp/qstring.h"
  25#include "json-parser-int.h"
  26
  27struct JSONToken {
  28    JSONTokenType type;
  29    int x;
  30    int y;
  31    char str[];
  32};
  33
  34typedef struct JSONParserContext {
  35    Error *err;
  36    JSONToken *current;
  37    GQueue *buf;
  38    va_list *ap;
  39} JSONParserContext;
  40
  41#define BUG_ON(cond) assert(!(cond))
  42
  43/**
  44 * TODO
  45 *
  46 * 0) make errors meaningful again
  47 * 1) add geometry information to tokens
  48 * 3) should we return a parsed size?
  49 * 4) deal with premature EOI
  50 */
  51
  52static QObject *parse_value(JSONParserContext *ctxt);
  53
  54/**
  55 * Error handler
  56 */
  57static void GCC_FMT_ATTR(3, 4) parse_error(JSONParserContext *ctxt,
  58                                           JSONToken *token, const char *msg, ...)
  59{
  60    va_list ap;
  61    char message[1024];
  62
  63    if (ctxt->err) {
  64        return;
  65    }
  66    va_start(ap, msg);
  67    vsnprintf(message, sizeof(message), msg, ap);
  68    va_end(ap);
  69    error_setg(&ctxt->err, "JSON parse error, %s", message);
  70}
  71
  72static int cvt4hex(const char *s)
  73{
  74    int cp, i;
  75
  76    cp = 0;
  77    for (i = 0; i < 4; i++) {
  78        if (!qemu_isxdigit(s[i])) {
  79            return -1;
  80        }
  81        cp <<= 4;
  82        if (s[i] >= '0' && s[i] <= '9') {
  83            cp |= s[i] - '0';
  84        } else if (s[i] >= 'a' && s[i] <= 'f') {
  85            cp |= 10 + s[i] - 'a';
  86        } else if (s[i] >= 'A' && s[i] <= 'F') {
  87            cp |= 10 + s[i] - 'A';
  88        } else {
  89            return -1;
  90        }
  91    }
  92    return cp;
  93}
  94
  95/**
  96 * parse_string(): Parse a JSON string
  97 *
  98 * From RFC 8259 "The JavaScript Object Notation (JSON) Data
  99 * Interchange Format":
 100 *
 101 *    char = unescaped /
 102 *        escape (
 103 *            %x22 /          ; "    quotation mark  U+0022
 104 *            %x5C /          ; \    reverse solidus U+005C
 105 *            %x2F /          ; /    solidus         U+002F
 106 *            %x62 /          ; b    backspace       U+0008
 107 *            %x66 /          ; f    form feed       U+000C
 108 *            %x6E /          ; n    line feed       U+000A
 109 *            %x72 /          ; r    carriage return U+000D
 110 *            %x74 /          ; t    tab             U+0009
 111 *            %x75 4HEXDIG )  ; uXXXX                U+XXXX
 112 *    escape = %x5C              ; \
 113 *    quotation-mark = %x22      ; "
 114 *    unescaped = %x20-21 / %x23-5B / %x5D-10FFFF
 115 *
 116 * Extensions over RFC 8259:
 117 * - Extra escape sequence in strings:
 118 *   0x27 (apostrophe) is recognized after escape, too
 119 * - Single-quoted strings:
 120 *   Like double-quoted strings, except they're delimited by %x27
 121 *   (apostrophe) instead of %x22 (quotation mark), and can't contain
 122 *   unescaped apostrophe, but can contain unescaped quotation mark.
 123 *
 124 * Note:
 125 * - Encoding is modified UTF-8.
 126 * - Invalid Unicode characters are rejected.
 127 * - Control characters \x00..\x1F are rejected by the lexer.
 128 */
 129static QString *parse_string(JSONParserContext *ctxt, JSONToken *token)
 130{
 131    const char *ptr = token->str;
 132    GString *str;
 133    char quote;
 134    const char *beg;
 135    int cp, trailing;
 136    char *end;
 137    ssize_t len;
 138    char utf8_buf[5];
 139
 140    assert(*ptr == '"' || *ptr == '\'');
 141    quote = *ptr++;
 142    str = g_string_new(NULL);
 143
 144    while (*ptr != quote) {
 145        assert(*ptr);
 146        switch (*ptr) {
 147        case '\\':
 148            beg = ptr++;
 149            switch (*ptr++) {
 150            case '"':
 151                g_string_append_c(str, '"');
 152                break;
 153            case '\'':
 154                g_string_append_c(str, '\'');
 155                break;
 156            case '\\':
 157                g_string_append_c(str, '\\');
 158                break;
 159            case '/':
 160                g_string_append_c(str, '/');
 161                break;
 162            case 'b':
 163                g_string_append_c(str, '\b');
 164                break;
 165            case 'f':
 166                g_string_append_c(str, '\f');
 167                break;
 168            case 'n':
 169                g_string_append_c(str, '\n');
 170                break;
 171            case 'r':
 172                g_string_append_c(str, '\r');
 173                break;
 174            case 't':
 175                g_string_append_c(str, '\t');
 176                break;
 177            case 'u':
 178                cp = cvt4hex(ptr);
 179                ptr += 4;
 180
 181                /* handle surrogate pairs */
 182                if (cp >= 0xD800 && cp <= 0xDBFF
 183                    && ptr[0] == '\\' && ptr[1] == 'u') {
 184                    /* leading surrogate followed by \u */
 185                    cp = 0x10000 + ((cp & 0x3FF) << 10);
 186                    trailing = cvt4hex(ptr + 2);
 187                    if (trailing >= 0xDC00 && trailing <= 0xDFFF) {
 188                        /* followed by trailing surrogate */
 189                        cp |= trailing & 0x3FF;
 190                        ptr += 6;
 191                    } else {
 192                        cp = -1; /* invalid */
 193                    }
 194                }
 195
 196                if (mod_utf8_encode(utf8_buf, sizeof(utf8_buf), cp) < 0) {
 197                    parse_error(ctxt, token,
 198                                "%.*s is not a valid Unicode character",
 199                                (int)(ptr - beg), beg);
 200                    goto out;
 201                }
 202                g_string_append(str, utf8_buf);
 203                break;
 204            default:
 205                parse_error(ctxt, token, "invalid escape sequence in string");
 206                goto out;
 207            }
 208            break;
 209        case '%':
 210            if (ctxt->ap) {
 211                if (ptr[1] != '%') {
 212                    parse_error(ctxt, token, "can't interpolate into string");
 213                    goto out;
 214                }
 215                ptr++;
 216            }
 217            /* fall through */
 218        default:
 219            cp = mod_utf8_codepoint(ptr, 6, &end);
 220            if (cp < 0) {
 221                parse_error(ctxt, token, "invalid UTF-8 sequence in string");
 222                goto out;
 223            }
 224            ptr = end;
 225            len = mod_utf8_encode(utf8_buf, sizeof(utf8_buf), cp);
 226            assert(len >= 0);
 227            g_string_append(str, utf8_buf);
 228        }
 229    }
 230
 231    return qstring_from_gstring(str);
 232
 233out:
 234    g_string_free(str, true);
 235    return NULL;
 236}
 237
 238/* Note: the token object returned by parser_context_peek_token or
 239 * parser_context_pop_token is deleted as soon as parser_context_pop_token
 240 * is called again.
 241 */
 242static JSONToken *parser_context_pop_token(JSONParserContext *ctxt)
 243{
 244    g_free(ctxt->current);
 245    ctxt->current = g_queue_pop_head(ctxt->buf);
 246    return ctxt->current;
 247}
 248
 249static JSONToken *parser_context_peek_token(JSONParserContext *ctxt)
 250{
 251    return g_queue_peek_head(ctxt->buf);
 252}
 253
 254/**
 255 * Parsing rules
 256 */
 257static int parse_pair(JSONParserContext *ctxt, QDict *dict)
 258{
 259    QObject *key_obj = NULL;
 260    QString *key;
 261    QObject *value;
 262    JSONToken *peek, *token;
 263
 264    peek = parser_context_peek_token(ctxt);
 265    if (peek == NULL) {
 266        parse_error(ctxt, NULL, "premature EOI");
 267        goto out;
 268    }
 269
 270    key_obj = parse_value(ctxt);
 271    key = qobject_to(QString, key_obj);
 272    if (!key) {
 273        parse_error(ctxt, peek, "key is not a string in object");
 274        goto out;
 275    }
 276
 277    token = parser_context_pop_token(ctxt);
 278    if (token == NULL) {
 279        parse_error(ctxt, NULL, "premature EOI");
 280        goto out;
 281    }
 282
 283    if (token->type != JSON_COLON) {
 284        parse_error(ctxt, token, "missing : in object pair");
 285        goto out;
 286    }
 287
 288    value = parse_value(ctxt);
 289    if (value == NULL) {
 290        parse_error(ctxt, token, "Missing value in dict");
 291        goto out;
 292    }
 293
 294    if (qdict_haskey(dict, qstring_get_str(key))) {
 295        parse_error(ctxt, token, "duplicate key");
 296        goto out;
 297    }
 298
 299    qdict_put_obj(dict, qstring_get_str(key), value);
 300
 301    qobject_unref(key_obj);
 302    return 0;
 303
 304out:
 305    qobject_unref(key_obj);
 306    return -1;
 307}
 308
 309static QObject *parse_object(JSONParserContext *ctxt)
 310{
 311    QDict *dict = NULL;
 312    JSONToken *token, *peek;
 313
 314    token = parser_context_pop_token(ctxt);
 315    assert(token && token->type == JSON_LCURLY);
 316
 317    dict = qdict_new();
 318
 319    peek = parser_context_peek_token(ctxt);
 320    if (peek == NULL) {
 321        parse_error(ctxt, NULL, "premature EOI");
 322        goto out;
 323    }
 324
 325    if (peek->type != JSON_RCURLY) {
 326        if (parse_pair(ctxt, dict) == -1) {
 327            goto out;
 328        }
 329
 330        token = parser_context_pop_token(ctxt);
 331        if (token == NULL) {
 332            parse_error(ctxt, NULL, "premature EOI");
 333            goto out;
 334        }
 335
 336        while (token->type != JSON_RCURLY) {
 337            if (token->type != JSON_COMMA) {
 338                parse_error(ctxt, token, "expected separator in dict");
 339                goto out;
 340            }
 341
 342            if (parse_pair(ctxt, dict) == -1) {
 343                goto out;
 344            }
 345
 346            token = parser_context_pop_token(ctxt);
 347            if (token == NULL) {
 348                parse_error(ctxt, NULL, "premature EOI");
 349                goto out;
 350            }
 351        }
 352    } else {
 353        (void)parser_context_pop_token(ctxt);
 354    }
 355
 356    return QOBJECT(dict);
 357
 358out:
 359    qobject_unref(dict);
 360    return NULL;
 361}
 362
 363static QObject *parse_array(JSONParserContext *ctxt)
 364{
 365    QList *list = NULL;
 366    JSONToken *token, *peek;
 367
 368    token = parser_context_pop_token(ctxt);
 369    assert(token && token->type == JSON_LSQUARE);
 370
 371    list = qlist_new();
 372
 373    peek = parser_context_peek_token(ctxt);
 374    if (peek == NULL) {
 375        parse_error(ctxt, NULL, "premature EOI");
 376        goto out;
 377    }
 378
 379    if (peek->type != JSON_RSQUARE) {
 380        QObject *obj;
 381
 382        obj = parse_value(ctxt);
 383        if (obj == NULL) {
 384            parse_error(ctxt, token, "expecting value");
 385            goto out;
 386        }
 387
 388        qlist_append_obj(list, obj);
 389
 390        token = parser_context_pop_token(ctxt);
 391        if (token == NULL) {
 392            parse_error(ctxt, NULL, "premature EOI");
 393            goto out;
 394        }
 395
 396        while (token->type != JSON_RSQUARE) {
 397            if (token->type != JSON_COMMA) {
 398                parse_error(ctxt, token, "expected separator in list");
 399                goto out;
 400            }
 401
 402            obj = parse_value(ctxt);
 403            if (obj == NULL) {
 404                parse_error(ctxt, token, "expecting value");
 405                goto out;
 406            }
 407
 408            qlist_append_obj(list, obj);
 409
 410            token = parser_context_pop_token(ctxt);
 411            if (token == NULL) {
 412                parse_error(ctxt, NULL, "premature EOI");
 413                goto out;
 414            }
 415        }
 416    } else {
 417        (void)parser_context_pop_token(ctxt);
 418    }
 419
 420    return QOBJECT(list);
 421
 422out:
 423    qobject_unref(list);
 424    return NULL;
 425}
 426
 427static QObject *parse_keyword(JSONParserContext *ctxt)
 428{
 429    JSONToken *token;
 430
 431    token = parser_context_pop_token(ctxt);
 432    assert(token && token->type == JSON_KEYWORD);
 433
 434    if (!strcmp(token->str, "true")) {
 435        return QOBJECT(qbool_from_bool(true));
 436    } else if (!strcmp(token->str, "false")) {
 437        return QOBJECT(qbool_from_bool(false));
 438    } else if (!strcmp(token->str, "null")) {
 439        return QOBJECT(qnull());
 440    }
 441    parse_error(ctxt, token, "invalid keyword '%s'", token->str);
 442    return NULL;
 443}
 444
 445static QObject *parse_interpolation(JSONParserContext *ctxt)
 446{
 447    JSONToken *token;
 448
 449    token = parser_context_pop_token(ctxt);
 450    assert(token && token->type == JSON_INTERP);
 451
 452    if (!strcmp(token->str, "%p")) {
 453        return va_arg(*ctxt->ap, QObject *);
 454    } else if (!strcmp(token->str, "%i")) {
 455        return QOBJECT(qbool_from_bool(va_arg(*ctxt->ap, int)));
 456    } else if (!strcmp(token->str, "%d")) {
 457        return QOBJECT(qnum_from_int(va_arg(*ctxt->ap, int)));
 458    } else if (!strcmp(token->str, "%ld")) {
 459        return QOBJECT(qnum_from_int(va_arg(*ctxt->ap, long)));
 460    } else if (!strcmp(token->str, "%lld")) {
 461        return QOBJECT(qnum_from_int(va_arg(*ctxt->ap, long long)));
 462    } else if (!strcmp(token->str, "%" PRId64)) {
 463        return QOBJECT(qnum_from_int(va_arg(*ctxt->ap, int64_t)));
 464    } else if (!strcmp(token->str, "%u")) {
 465        return QOBJECT(qnum_from_uint(va_arg(*ctxt->ap, unsigned int)));
 466    } else if (!strcmp(token->str, "%lu")) {
 467        return QOBJECT(qnum_from_uint(va_arg(*ctxt->ap, unsigned long)));
 468    } else if (!strcmp(token->str, "%llu")) {
 469        return QOBJECT(qnum_from_uint(va_arg(*ctxt->ap, unsigned long long)));
 470    } else if (!strcmp(token->str, "%" PRIu64)) {
 471        return QOBJECT(qnum_from_uint(va_arg(*ctxt->ap, uint64_t)));
 472    } else if (!strcmp(token->str, "%s")) {
 473        return QOBJECT(qstring_from_str(va_arg(*ctxt->ap, const char *)));
 474    } else if (!strcmp(token->str, "%f")) {
 475        return QOBJECT(qnum_from_double(va_arg(*ctxt->ap, double)));
 476    }
 477    parse_error(ctxt, token, "invalid interpolation '%s'", token->str);
 478    return NULL;
 479}
 480
 481static QObject *parse_literal(JSONParserContext *ctxt)
 482{
 483    JSONToken *token;
 484
 485    token = parser_context_pop_token(ctxt);
 486    assert(token);
 487
 488    switch (token->type) {
 489    case JSON_STRING:
 490        return QOBJECT(parse_string(ctxt, token));
 491    case JSON_INTEGER: {
 492        /*
 493         * Represent JSON_INTEGER as QNUM_I64 if possible, else as
 494         * QNUM_U64, else as QNUM_DOUBLE.  Note that qemu_strtoi64()
 495         * and qemu_strtou64() fail with ERANGE when it's not
 496         * possible.
 497         *
 498         * qnum_get_int() will then work for any signed 64-bit
 499         * JSON_INTEGER, qnum_get_uint() for any unsigned 64-bit
 500         * integer, and qnum_get_double() both for any JSON_INTEGER
 501         * and any JSON_FLOAT (with precision loss for integers beyond
 502         * 53 bits)
 503         */
 504        int ret;
 505        int64_t value;
 506        uint64_t uvalue;
 507
 508        ret = qemu_strtoi64(token->str, NULL, 10, &value);
 509        if (!ret) {
 510            return QOBJECT(qnum_from_int(value));
 511        }
 512        assert(ret == -ERANGE);
 513
 514        if (token->str[0] != '-') {
 515            ret = qemu_strtou64(token->str, NULL, 10, &uvalue);
 516            if (!ret) {
 517                return QOBJECT(qnum_from_uint(uvalue));
 518            }
 519            assert(ret == -ERANGE);
 520        }
 521    }
 522    /* fall through to JSON_FLOAT */
 523    case JSON_FLOAT:
 524        /* FIXME dependent on locale; a pervasive issue in QEMU */
 525        /* FIXME our lexer matches RFC 8259 in forbidding Inf or NaN,
 526         * but those might be useful extensions beyond JSON */
 527        return QOBJECT(qnum_from_double(strtod(token->str, NULL)));
 528    default:
 529        abort();
 530    }
 531}
 532
 533static QObject *parse_value(JSONParserContext *ctxt)
 534{
 535    JSONToken *token;
 536
 537    token = parser_context_peek_token(ctxt);
 538    if (token == NULL) {
 539        parse_error(ctxt, NULL, "premature EOI");
 540        return NULL;
 541    }
 542
 543    switch (token->type) {
 544    case JSON_LCURLY:
 545        return parse_object(ctxt);
 546    case JSON_LSQUARE:
 547        return parse_array(ctxt);
 548    case JSON_INTERP:
 549        return parse_interpolation(ctxt);
 550    case JSON_INTEGER:
 551    case JSON_FLOAT:
 552    case JSON_STRING:
 553        return parse_literal(ctxt);
 554    case JSON_KEYWORD:
 555        return parse_keyword(ctxt);
 556    default:
 557        parse_error(ctxt, token, "expecting value");
 558        return NULL;
 559    }
 560}
 561
 562JSONToken *json_token(JSONTokenType type, int x, int y, GString *tokstr)
 563{
 564    JSONToken *token = g_malloc(sizeof(JSONToken) + tokstr->len + 1);
 565
 566    token->type = type;
 567    memcpy(token->str, tokstr->str, tokstr->len);
 568    token->str[tokstr->len] = 0;
 569    token->x = x;
 570    token->y = y;
 571    return token;
 572}
 573
 574QObject *json_parser_parse(GQueue *tokens, va_list *ap, Error **errp)
 575{
 576    JSONParserContext ctxt = { .buf = tokens, .ap = ap };
 577    QObject *result;
 578
 579    result = parse_value(&ctxt);
 580    assert(ctxt.err || g_queue_is_empty(ctxt.buf));
 581
 582    error_propagate(errp, ctxt.err);
 583
 584    while (!g_queue_is_empty(ctxt.buf)) {
 585        parser_context_pop_token(&ctxt);
 586    }
 587    g_free(ctxt.current);
 588
 589    return result;
 590}
 591